Thrift 是一个基于静态代码生成的跨语言的RPC协议栈实现,它可以生成包括 C++, Java, Python, Ruby, PHP 等主流语言的代码,这些代码实现了 RPC 的协议层和传输层功能,从而让用户可以集中精力于服务的调用和实现。


1. 软件栈

Thrift 对软件栈的定义非常的清晰, 使得各个组件能够松散的耦合, 针对不同的应用场景, 选择不同的方式去搭建服务。

技术栈

  • 传输层(Transport Layer):传输层负责直接从网络中读取和写入数据,它定义了具体的网络传输协议;比如说 TCP/IP 传输、MemoryBuffer 等
  • 协议层(Protocol Layer):协议层定义了数据传输格式,负责网络传输数据的序列化和反序列化;比如说 JSON、XML、二进制数据等
  • 处理层(Processor Layer):处理层作为协议层和用户提供的服务实现之间的纽带,负责调用服务实现的接口,是由具体的IDL(接口描述语言)生成的,封装了具体的底层网络传输和序列化方式,并委托给用户实现的 Handler 进行处理
  • 服务层(Server Layer):整合上述组件,根据 Thrift 定义的服务接口描述文件生成的客户端和服务器端代码框架,提供具体的网络线程/IO 服务模型,形成最终的服务
  • 业务逻辑层(Your code)

接下来我们按照自底向上的顺序介绍 thrift 的各个模块。

1.1. 传输层(Transport Layer)

Transport 与网络数据通信相关,thrift 通信协议有基于 TCP/IP 协议的实现。在现在的网络通信服务器中, TCP/IP 协议栈由 socket 来实现。Thrift 也不例外,在 thrift 源码中,是通过将 socket 包装成各种功能不同的 TTransport 来使用:

  • TSocket:使用阻塞式 I/O 进行传输,是常见的模式
  • TNonblockingTransport:使用非阻塞方式,用于构建异步客户端
  • TFrameTransport:使用非阻塞方式,按块的大小进行传输

1.2. 协议层(Protocol Layer)

Protocol 是 transport 的上一层,transport 负责数据传输,protocol 负责对数据进行解析,将数据解析成对应的数据结构代码,供程序直接调用。Thrift 支持各种语言,通过一个 x.thrift 的接口描述文件来通信。Thrift 的接口描述文件是各种语言通用的,其通过 thrift compiler 来生成对应的源代码,例如 C++ 对应的命令为 thrift --gen cpp x.thrift

Thrift 可以让用户选择客户端与服务端之间传输通信协议的类别,在传输协议上总体划分为文本(text)和二进制(binary)传输协议。为节约带宽,提高传输效率,一般情况下使用二进制类型的传输协议为多数,有时还会使用基于文本类型的协议,这需要根据项目/产品中的实际需求。常用协议有以下几种:

  • TBinaryProtocol:二进制编码格式进行数据传输
  • TCompactProtocol:高效率的、密集的二进制编码格式进行数据传输
  • TJSONProtocol:使用 JSON 文本的数据编码协议进行数据传输
  • TSimpleJSONProtocol:只提供 JSON 只写的协议,适用于通过脚本语言解析

1.3. 处理层(Processor Layer)

服务调用组件。 Processor 封装了从输入流读取数据和向输出流写入数据的能力。输入和输出流由 Protocol objectss 表示。

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

1.4. 服务层(Server Layer)

Server 主要做以下工作:

  • Create a transport
  • Create input/output protocols for the transport
  • Create a processor based on the input/output protocols
  • Wait for incoming connections and hand them off to the processor

根据单线程/多线程,同步/异步分为以下几种:

  • TSimpleServer:单线程服务器端,使用标准的阻塞式 I/O
  • TThreadPoolServer:多线程服务器端,使用标准的阻塞式 I/O
  • TNonblockingServer:单线程服务器端,使用非阻塞式 I/O
  • THsHaServer:半同步半异步服务器端,基于非阻塞式 I/O 读写和多线程工作任务处理
  • TThreadedSelectorServer:多线程选择器服务器端,对 THsHaServer 在异步 I/O 模型上进行增强

2. 特点

  1. 开发速度快

    通过编写 RPC 接口 Thrift IDL 文件,利用编译生成器自动生成服务端骨架(Skeletons)和客户端桩(Stubs)。从而省去开发者自定义和维护接口编解码、消息传输、服务器多线程模型等基础工作。

    • 服务端:只需要按照服务骨架即接口,编写好具体的业务处理程序(Handler)即实现类即可。
    • 客户端:只需要拷贝 IDL 定义好的客户端桩和服务对象,然后就像调用本地对象的方法一样调用远端服务。
  2. 接口维护简单

    通过维护 Thrift 格式的 IDL(接口描述语言)文件(注意写好注释),即可作为给 Client 使用的接口文档使用,也自动生成接口代码,始终保持代码和文档的一致性。且 Thrift 协议可灵活支持接口的可扩展性。

3. 数据类型

Thrifty 的数据类型包括预定义的基本类型,容器类型,用户自定义的结构体和异常,以及服务。

  1. 基本类型(Base Type)

    • bool:布尔值,一个字节
    • i8(byte):8 位有符号整数
    • i16:16 位有符号整数
    • i32:32 位有符号整数
    • i64:64 位有符号整数
    • double:64 位浮点数
    • binary:一个字节数组
    • string:编码不可知的文本或二进制字符串
  2. 容器类型(Container)

    • list<t>:元素类型为 t 的有序列表,容许元素重复
    • set<t>:元素类型为 t 的无序表,不容许元素重复
    • map<k,v>:key/value 映射,key 不允许重复
    • 其中容器中的元素类型可以是除了 service 外的任何合法 Thrift 类型(包括结构体和异常)
  3. 结构体类型 struct

    Thrift 中的 struct 类似于 C 语言,其在面向对象的语言中转换为 class。一个 struct 其由多个 field 组成,每个 field 包括唯一的整数标识符、typename 和可选的默认值组成。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    struct Location{
        1: required double latitude;
        2: required double longitude;
    }    // 没有逗号
    
    struct Tweet{
        1: required i32 userId;
        2: required string userName;
        3: required string text;
        4: optional Location loc;
        5: optional TweetType tweetType = TweetType.TWEET;    // 详见 enum
        16: optional string language = "Endlish";
    }
    
    • field 可用关键字 optionalrequired 进行标识
    • struct 不能继承,但是可以嵌套,但不能嵌套自己
    • 同一文件可以定义多个 struct,也可以定义在不同的文件中,利用 include 导入
  4. 异常类型 exception

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

  5. 服务类型 service

    服务的定义方法在语义上等同于面向对象语言中的接口,Thrift compiler 将用你所选择的语言据此生成 service interface code (for the server) and stubs (for the client)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    service Twitter{
        // A method definition looks like C code. It has a return type, arguments
        // and optionally a list of exceptions that it may throw. Note that argument
        // lists and exception list are specified using the exact same syntax as
        // field lists in structs.
        void ping(),
        bool postTweet(1:Tweet tweet) throws (1:TwitterUnavailable unavailable),
        TweetSearchResult searchTweets(1:string query); 
    
        // The 'oneway' modifier indicates that the client only makes a request and
        // does not wait for any response at all. Oneway methods MUST be void.
        oneway void zip();
    }
    
    • 函数定义可以用分号或者逗号结尾
    • 参数和返回值类型可以是基本类型或者结构体
    • void 是函数的有效返回类型
    • 服务支持继承,一个 service 可使用 extends 关键字继承另一个 service
    • 服务不支持重载

4. 其他特性

4.1. Typedef

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

1
2
typedef i32 MyInteger   // 结尾无分号
typedef Tweet ReTweet   // struct 也可 typedef

4.2. enum

Thrift 支持 C 风格的 enum ,编译器从 0 开始分配默认值。Thrift 还不支持嵌套枚举,枚举常量大小不超过 32 位正整数。

1
2
3
4
5
6
enum TweetType{
    TWEET,
    RETWEET = 2,    // 可以设置特定的整数值
    DM = 0xa,       // 支持 16 进制
    REPLY
}

4.3. constant

Thrift 允许定义跨语言使用的常量,复杂类型和结构体使用 JSON 格式赋值。

1
2
const i32 INT_CONST = 1234;
const map<string, string> MAP_CONST = {"hello": "world", "goodnight": "Kyoani"}

5. Generated Files(C++)

  • All constants go into a single .cpp/.h pair
  • All type definitions (enums and structs) go into another .cpp/.h pair
  • Each service gets its own .cpp/.h pair
1
2
3
4
5
6
7
8
$ tree gen-cpp
|-- example_constants.cpp
|-- example_constants.h
|-- example_types.cpp
|-- example_types.h
|-- Twitter.cpp
|-- Twitter.h
`-- Twitter_server.skeleton.cpp

Thrift 数据类型和 C++ 数据类型对应表

Thrift C++
bool bool
byte int8_t
i16 int16_t
i32 int32_t
i64 int64_t
double double
binary std::string
string std::string
list std::vector
set std::set
map<t1, t2> std::map<t1, t2>