1 Thrift: 文档补充

thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Go,Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。

1.1 说明:

thrift显然有丰富的特点。然而却缺乏好的文档。本指南尝试填补这一漏洞。但是请注意,这是一个参考指南目的为通过一步一步的例子来说明如何使用thrift,是用作参考的thrift教程。

本指南的结构和组织的许多方面都借鉴了(优秀的)Google Protocol Buffer Language Guide。我感谢该文件的作者

补充: 本文为原版本的翻译,只对实际内容做了部分翻译,部分连接亦未显示。

1.2 版权:

Copyright © 2013 Diwaker Gupta This work is licensed under the Creative Commons Attribution-NonCommercial 3.0 Unported License.

1.3 贡献:

I welcome feedback and contributions to this guide. You can find the source code over at GitHub. Alternatively, you can file a bug.

1.4 致谢:

I thank the authors of Thrift for the software, the authors of the Google Protocol Buffer documentation for the inspiration and the Thrift community for the feedback. Special thanks to Dave Engberg from Evernote for his input.

1.5 关于作者:

I’m an open source geek and a software architect. I blog over at Floating Sun and you can find more about me here.

2 语法

2.1 类型

thrift中的类型包括基础类型、结构、容器、异常、服务等几个部分。

2.2 基本类型

bool: A boolean value (true or false), one byte
byte: A signed byte
i16: A 16-bit signed integer
i32: A 32-bit signed integer
i64: A 64-bit signed integer
double: A 64-bit floating point number
binary: A byte array
string: 编码不可知的文本或二进制字符串

thrift不支持无符号整型,原因是很多目标语言不支持,比如java。

2.3 容器(集合)

Thrift容器(我更喜欢翻译成集合)与类型密切相关,它与当前流行编程语言提供的容器类型相对应,采用java泛型风格表示的。Thrift提供了3种容器类型:

  • List:一系列t1类型的元素组成的有序表,元素可以重复
  • Set:一系列t1类型的元素组成的无序表,元素唯一
  • Map<t1,t2>:key/value对(key的类型是t1且key唯一,value类型是t2)

容器中的元素类型可以是除了service以外的任何合法thrift类型(包括结构体和异常)。

2.4 结构体和异常

Thrift结构体在概念上同C语言结构体类型—-一种将相关属性聚集(封装)在一起的方式。在面向对象语言中,thrift结构体被转换成类。

异常在语法和功能上类似于结构体,只不过异常使用关键字exception而不是struct关键字声明。但它在语义上不同于结构体—当定义一个RPC服务时,开发者可能需要声明一个远程方法抛出一个异常。

结构体和异常的声明将在后面详细介绍。

2.5 服务

服务的定义方法在语法上等同于面向对象语言中定义接口。Thrift编译器会产生实现这些接口的client和server端。

服务的声明将在后面详细介绍

2.6 类型定义

Thrift支持C/C++风格的typedef:

typedef i32 MyInteger
typedef Tweet ReTweet

2.7 枚举类型

用法:

enum TweetType {
TWEET,
RETWEET = 2,
DM = 0xa,
REPLY
} 

struct Tweet {
1: required i32 userId;
2: required string userName;
3: required string text;
4: optional Location loc;
5: optional TweetType tweetType = TweetType.TWEET
16: optional string language = "english"
}

说明:

  1. 编译器默认从0开始赋值
  2. 可以赋予某个常量某个整数
  3. 允许常量是十六进制整数
  4. 末尾没有逗号
  5. 给常量赋缺省值时,使用常量的全称

2.8 注释

Thrfit支持shell注释风格,C/C++语言中单行或者多行注释风格

2.9 命名空间

Thrift中的命名空间同C++中的namespace和java中的package类似,它们均提供了一种组织(隔离)代码的方式。因为每种语言均有自己的命名空间定义方式(如python中有module),thrift允许开发者针对特定语言定义namespace:

namespace cpp com.project   //转化成namespace com { namespace example {
namespace java com.project  //转换成package com.example

2.10 文件包含

Thrift允许thrift文件包含,用户需要使用thrift文件名作为前缀访问被包含的对象,如:

include "tweet.thrift"      //thrift文件名要用双引号包含,末尾没有逗号或者分号
...
 
struct TweetSearchResult {
1: list<tweet.Tweet> tweets; //注意tweet前缀
 
}

2.11 常量

Thrift允许用户定义常量,复杂的类型和结构体可使用JSON形式表示。

const i32 INT_CONST = 1234;    // a
const map<string,string> MAP_CONST = {"hello": "world", "goodnight": "moon"}

2.12 结构体

语法:

struct Tweet {
1: required i32 userId;     //每个域有一个唯一的,正整数标识符
2: required string userName;    //每个域可以标识为required或者optional(也可以不注明)
3: required string text;
4: optional Location loc;   //结构体可以包含其他结构体
16: optional string language = "english"    //域可以有缺省值
}
 
struct Location {   //一个thrift中可定义多个结构体,并存在引用关系
1: required double latitude;
2: required double longitude;
}

规范的struct定义中的每个域均会使用required或者optional关键字进行标识。如果required标识的域没有赋值,thrift将给予提示。如果optional标识的域没有赋值,该域将不会被序列化传输。如果某个optional标识域有缺省值而用户没有重新赋值,则该域的值一直为缺省值。

与service不同,结构体不支持继承,即,一个结构体不能继承另一个结构体。

2.13 服务

在流行的序列化/反序列化框架(如protocol buffer)中,thrift是少有的提供多语言间RPC服务的框架。

Thrift编译器会根据选择的目标语言为server产生服务接口代码,为client产生桩代码。

//“Twitter”与“{”之间需要有空格!!!
service Twitter {

// 方法定义方式类似于C语言中的方式,它有一个返回值,一系列参数和可选的异常
// 列表. 注意,参数列表和异常列表定义方式与结构体中域定义方式一致.
void ping(),         

// 参数是只读的(const),不可以作为返回值
bool postTweet(1:Tweet tweet); 

TweetSearchResult searchTweets(1:string query); 

// ”oneway”标识符表示client发出请求后不必等待回复(非阻塞)直接进行下面的操作,
// ”oneway”方法的返回值必须是void
oneway void zip()   
 
}

3 代码生成

本节介绍thrift产生各种目标语言代码的方式。本节从几个基本概念开始,逐步引导开发者了解产生的代码是怎么样组织的,进而帮助开发者更快地明白thrift的使用方法。

概念

Thrift的网络栈如下所示:

+-------------------------------------------+
| cGRE                                      |
| Server                                    |
| (single-threaded, event-driven etc)       |
+-------------------------------------------+
| cBLU                                      |
| Processor                                 |
| (compiler generated)                      |
+-------------------------------------------+
| cGRE                                      |
| Protocol                                  |
| (JSON, compact etc)                       |
+-------------------------------------------+
| cGRE                                      |
| Transport                                 |
| (raw TCP, HTTP etc)                       |
+-------------------------------------------+

3.1 Transport

Transport层提供了一个简单的网络读写抽象层。这使得thrift底层的transport从系统其它部分(如:序列化/反序列化)解耦。以下是一些Transport接口提供的方法:

open
close
read
write
flush

另外,Thrift使用ServerTransport接口接受或者创建原始transport对象。ServerTransport用在server端,为到来的连接创建Transport对象。

大多数Thrift支持的语言使用http和文件io作为传输

3.2 Protocol

Protocol抽象层定义了一种将内存中数据结构映射成可传输格式的机制换句话说,Protocol定义了datatype怎样使用底层的Transport对自己进行编解码。因此,Protocol的实现要给出编码机制并负责对数据进行序列化
Protocol接口的定义如下:

writeMessageBegin(name, type, seq)
 
writeMessageEnd()
 
writeStructBegin(name)
 
writeStructEnd()
 
writeFieldBegin(name, type, id)
 
writeFieldEnd()
 
writeFieldStop()
 
writeMapBegin(ktype, vtype, size)
 
writeMapEnd()
 
writeListBegin(etype, size)
 
writeListEnd()
 
writeSetBegin(etype, size)
 
writeSetEnd()
 
writeBool(bool)
 
writeByte(byte)
 
writeI16(i16)
 
writeI32(i32)
 
writeI64(i64)
 
writeDouble(double)
 
writeString(string)
 
name, type, seq = readMessageBegin()
 
readMessageEnd()
 
name = readStructBegin()
 
readStructEnd()
 
name, type, id = readFieldBegin()
 
readFieldEnd()
 
k, v, size = readMapBegin()
 
readMapEnd()
 
etype, size = readListBegin()
 
readListEnd()
 
etype, size = readSetBegin()
 
readSetEnd()
 
bool = readBool()
 
byte = readByte()
 
i16 = readI16()
 
i32 = readI32()
 
i64 = readI64()
 
double = readDouble()
 
string = readString()

大多数Thrift支持的语言序列化使用二进制和json。

3.3 Processor

Processor封装了从输入数据流中读数据和向数据数据流中写数据的操作。读写数据流用Protocol对象表示。Processor的结构体非常简单:

interface TProcessor {
bool process(TProtocol in, TProtocol out) throws TException
}

与服务相关的processor实现由编译器产生。Processor主要工作流程如下:从连接中读取数据(使用输入protocol),将处理授权给handler(由用户实现),最后将结果写到连接上(使用输出protocol)

3.4 Server

Server将以上所有特性集成在一起:

1. 创建一个transport对象
2. 为transport对象创建输入输出protocol	
3. 基于输入输出protocol创建processor
4. 等待连接请求并将之交给processor处理

3.5 应用举例

– todo