什么是Thrift
thrift是一个跨语言通信的工具,支持的语言多,而且还提供服务器端的众多网络模型,使服务端的开发可以只专于服务本身的逻辑。用户通过Thrift的IDL(接口定义语言)来描述接口函数及数据类型,然后通过Thrift的编译环境生成各种语言类型的接口文件。
简单来说,Thrift是RPC下的序列化协议和传输协议。
- 什么是IDL?
IDL(全称:Interface Description Language,接口描述语言):
既然要进行 RPC,就需要知道对方的接口是什么,需要传什么参数,同时也需要知道返回值是什么样的。接口描述语言就定义了双方达成RPC通信的方式。
Thrift工作原理
本地函数调用调用方和被调用方都在一个程序内部,只是cpu在执行调用的时候切换去执行被调用的函数,执行完被调用函数之后,再切换回来执行调用之后的代码。
远端过程调用(RPC)的调用方和被调用方不在同一个进程内,甚至都不在同一台机子上,因此远端过程调用中,必然涉及网络传输。Thrift框架的远端过程调用的工作过程如下:
- 通过IDL定义一个接口的thrift文件,然后通过thrift的多语言编译功能,将接口定义的thrift文件翻译成对应的语言版本的接口文件;
- Thrift生成的特定语言的接口文件中包括客户端部分和服务器部分;
- 客户端通过接口文件中的客户端部分生成一个Client对象,这个客户端对象中包含所有接口函数的存根实现,然后用户代码就可以通过这个Client对象来调用thrift文件中的那些接口函数了,但是,客户端调用接口函数时实际上调用的是接口函数的本地存根实现;
- 接口函数的存根实现将调用请求发送给thrift服务器端,然后thrift服务器根据调用的函数名和函数参数,调用实际的实现函数来完成具体的操作;
- Thrift服务器在完成处理之后,将函数的返回值发送给调用的Client对象;
- Thrift的Client对象将函数的返回值再交付给用户的调用函数。
RPC在调用方和被调用方一般不在一台机子上,它们之间通过网络传输进行通信,一般的RPC都是采用tcp连接,如果同一条tcp连接同一时间段只能被一个调用所独占,这种情况是同步调用。
在一些的RPC服务框架中,为了提升网络通信的效率,客户端发起调用之后不被阻塞,这种情况是异步调用,它的通信效率比同步调用高,但是实现起来比较复杂。
Thrift IDL语法
Thrift IDL语法和Protobuf语法很像,这里挑一些东西做简单介绍,具体可以参考:Thrift IDL语法
Type
- bool: 布尔值 (true or false)
- byte: 有符号字节
- i16: 16位有符号整型
- i32: 32位有符号整型
- i64: 64位有符号整型
- double: 64位浮点型
- string: Encoding agnostic text or binary string
- binary: Blob (byte array) a sequence of unencoded bytes,这是string类型的一种变形,主要是为.java使用
Struct
- 字段会有optional和required之分和protobuf一样,但是如果不指定则为无类型—可以不填充该值,但是在序列化传输的时候也会序列化进去,optional是不填充则不序列化,required是必须填充也必须序列化。
- 成员是被正整数编号过的,其中的编号是不能重复的,这个是为了在传输过程中编码使用。
- 每个字段可以设置默认值。
- 同一文件可以定义多个struct,也可以定义在不同的文件,进行include引入。
- 成员分割符可以是逗号(,)或是分号(;),而且可以混用,但是为了清晰期间,建议在定义中只使用一种。
struct Report
{
1: required string msg, //序列化中,必须有该字段
2: optional i32 type = 0; //序列化中可以没有该字段
3: i32 time //对于基本类型,会序列化到字节数组中,解析的时候不会校验。对于对象类型,如果不为空,则序列化到字节数组中
}
Containers
Thrift容器与目前流行编程语言的容器类型相对应,有3种可用容器类型:
- list: 元素类型为t的有序表,容许元素重复。对应c++的vector,Java的ArrayList或者其他语言的数组
- set: 元素类型为t的无序表,不容许元素重复。对应c++中的set,Java中的HashSet,python中的set,php中没有set,则转换为list类型了
- map: 键类型为t,值类型为t的kv对,键不容许重复。对用c++中的map, Java的HashMap, PHP 对应 array, Python/Ruby 的dictionary。
注:为了最大的兼容性,map的key最好是thrift的基本类型,有些语言不支持复杂类型的key,JSON协议只支 持那些基本类型的key。
struct Test {
1: map<Number, UserId> user_map,
2: set<Number> num_sets,
3: list<Stusers> users
}
Enum
enum EnOpType {
CMD_OK = 0, // (0)
CMD_NOT_EXIT = 2000, // (2000)
CMD_EXIT = 2001, // (2001)
CMD_ADD = 2002 // (2002)}
struct StUser {
1: required i32 userId;
2: required string userName;
3: optional EnOpType cmd_code = EnOpType.CMD_OK;
4: optional string language = “english”
}
Consts
Thrift允许定义跨语言使用的常量,复杂的类型和结构体可使用JSON形式表示。
const i32 INT_CONST = 1234;
const EnOpType myEnOpType = EnOpType.CMD_EXIT; //2001
Exceptions
exception UserException {
1: i32 errorCode,
2: string message,
3: StUser userinfo
}
Services
服务的定义方法在语义(semantically 上等同于面向对象语言中的接口。
Thrift编译器会产生执行这些接口的client和server stub。 Thrift编译器会根据选择的目标语言为server产生服务接口代码,为client产生stubs。
service SeTest {
void ping() throws (1:UserException e),
bool postTweet(1: StUser user);
StUser searchTweets(1:string name);
}
Service的约束
- 不支持多态
- 方法无法重载
Namespace
Thrift中的命名空间类似于C++中的namespace和Java中的package,它们提供了一种组织(隔离)代码的简便方式。名字空间也可以用于解决类型定义中的名字冲突。
namespace cpp com.example.test
namespace java com.example.test
namespace php com.example.test
Includes
include "test.thrift"
...
struct StSearchResult {
1: in32 uid;
...
}
RPC框架对比
gRPC和Thrift对比可以参考:gRPC vs Thrift