Thrift是一种支持多语言的软件框架,在各个服务之间的RPC通信领域应用非常广泛。RPC(远程过程调用)是一个计算机通信协议,该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。(参考远程过程调用)。
Thrift通过一个中间语言(IDL, 接口定义语言)来定义RPC的接口和数据类型,然后通过一个编译器生成不同语言的代码(目前支持C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk和OCaml),并由生成的代码负责RPC协议层和传输层的实现。
一、核心组件和特点
Thrift的核心组件有:
- TProtocol 协议和编解码组件
- TTransport 传输组件
- TProcessor 服务调用组件
- TServer,Client 服务器和客户端组件
- IDL 服务描述组件,负责生产跨平台客户端
作为一个高性能的RPC框架,Thrift的主要特点有:
- 基于二进制的高性能的编解码框架
- 基于NIO的底层通信
- 相对简单的服务调用模型
- 使用IDL支持跨平台调用
二、Thrift Type
Thrift Type包含了基本类型,自定义的结构体,容器,异常等,以下内容主要来自https://matt33.com/2016/04/07/thrift-learn/
2.1 基本类型(Base Types)
- bool: 布尔变量(A boolean value, one byte);
- byte: 8位有符号整数(A signed byte);
- i16: 16位有符号整数(A 16-bit signed integer);
- i32: 32位有符号整数(A 32-bit signed integer);
- i64: 64位有符号整数(A 64-bit signed integer);
- double: 64位浮点数(A 64-bit floating point number);
- binary: byte数组(A byte array);
- string: 字符串(Encoding agnostic text or binary string);
2.2 容器类型(Containers)
- list: 一系列由T类型的数据组成的有序列表,元素可以重复;
- set: 一系列由T类型的数据组成的无序集合,元素不可重复
- map: 一个字典结构,key为T1类型,value为T2类型;
2.3 结构体(Struct)
结构体中包含一系列的强类型域,等同于无继承的class。可以看出struct写法很类似C语言的结构体。
struct Example {
1:i32 number=10,
2:i64 bigNumber,
3:list<double> decimals,
4:string name="thrifty"
}
2.4 可选与必选
Thrift提供两个关键字required,optional,分别用于表示对应的字段时必填的还是可选的。例如:
struct People {
1: required string name;
2: optional i32 age;
}
表示name是必填的,age是可选的。
2.5 联合(Union)
在一个结构体中,如果field之间的关系是互斥的,即只能有一个field被使用被赋值。在这种情况下,我们可以使用union来声明这个结构体,而不是一堆堆optional的field,语意上也更明确了。例如:
union JavaObjectArg {
1: i32 int_arg;
2: i64 long_arg;
3: string string_arg;
4: bool bool_arg;
5: binary binary_arg;
6: double double_arg;
}
三、RPC 调用分以下两种:
- 同步调用:客户方等待调用执行完成并返回结果。
- 异步调用:客户方调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果。若客户方不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果。
四、Thrift模型
如上图架构中各个层级,我们看其中的两个
4.1 协议层(传输格式)
- TBinaryProtocol: 二进制格式;
- TCompactProtocol:高效率的、密集的二进制编码格式进行数据传输;
- TJSONProtocol:JSON格式;
- TSimpleJSONProtocol:提供JSON只写协议, 生成的文件很容易通过脚本语言解析;
- TDebugProtocol:使用易懂的可读的文本格式,以便于debug。
4.2 传输层(数据传输方式)
- TSocket:阻塞式socker;
- TFramedTransport:使用非阻塞方式,以frame为单位进行传输。
- TFileTransport:以文件形式进行传输。
- TMemoryTransport:将内存用于I/O. java实现时内部实际使用了简单的ByteArrayOutputStream。
- TZlibTransport:使用zlib进行压缩, 与其他传输方式联合使用。当前无java实现。
- TNonblockingTransport —— 使用非阻塞方式,用于构建异步客户端
4.3 服务模型
- TSimpleServer:单线程服务器端使用标准的阻塞式 I/O,简单的单线程服务模型,常用于测试;
- TThreadPoolServer:多线程服务模型,使用标准的阻塞式IO;
- TNonblockingServer:多线程服务模型,使用非阻塞式IO(需使用TFramedTransport数据传输方式)。
4.4 Thrift RPC过程:
Client端通过Client Stub将接口、方法、参数按照协议规定的进行编码,并且通过网络传输到Server端。Server端的收到请求后交给Server Stub进行解码并发起后端调用,最终将执行结果返回Client
更详细的过程:
(1). client代码像普通函数调用一样调用client stub函数,这个调用是本地调用,调用参数会和平常函数调用一样进行返回地址和调用参数压栈操作;
(2). client stub会把调用参数和其它信息(比如调用方法名、调用属性等,可以称为metadata)进行打包封装成message,然后通过系统调用发送该message,这个打包的过程是个序列化的过程;
(3). client寻求到服务端的地址,然后通过本地操作系统经由某种协议,将上述message发送给server端;
(4). server端的操作系统接收message后将其传递给server stub;
(5). server stub将message解包,得到原始传递过来的各项调用参数,解包的过程是反序列化的过程;
(6). server stub调用server端的本地函数,然后将得到的结果按照上述类似的步骤反向传递给client作为结果返回。当然整个过程也可能不那么顺利,那么也应该产生合适的状态码、异常信息作为返回。
所以RPC实际是通过client stub和server stub起到一个代理的作用,将client的请求转发到server端去操作,并将操作得到的结果回传,因为传递是跨进程、跨主机的,所以必须进行序列化和反序列化的过程来保证消息的正确性。关于stub的概念:https://www.jianshu.com/p/9ccdea882688
4.5 影响RPC性能点有哪些
- 传输协议:HTTP、TCP、UDP
- 序列化方案:Java自带的序列化、其他序列化框架Protobuf、Kryo、Hession
- 工作模式:同步or异步
五、安装使用
关于安装过程可以参考 和https://matt33.com/2016/04/07/thrift-learn/这两篇文章
编写一个thrift文件,定义服务端的接口定义个服务接口
namespace java com.momo.learn.learn.thrift
struct Param{
1: required i32 id;
2: required string name;
3: optional i32 age;
}
service HelloThriftService {
i32 hello(1:string param1, 2:Param param2, 3:map<string,string> param3);
}
执行下边命令
thrift —gen java HelloThriftService.thrift
会看到下边这个图,相当于定义了接口的签名,服务端和客户端分别实现和引用接口就可以完成使用
参考文章:
https://matt33.com/2016/04/07/thrift-learn/
重点推荐:https://www.kancloud.cn/digest/thrift/118986