目标
需支持种类繁多的数据类型、跨语言、跨平台、高性能、兼容性且可扩展
- 数据类型系统
- 传输层 Transport
- 编码/解码层(或序列化/反序列化,协议层) Protocol
- 版本系统 支持可插拔、兼容的数据的机制
- 处理器 生成代码和RPC调用 Processor
特性和非特性
特性
- 中间语言描述文件
- 多语言支持
- 命名空间
- 类型和复合类型、容器
- 服务、服务继承
- 异常
- 异步
非特性
- 非自包含结构体
- 结构体继承
- 多态
- 重载
- 多型容器
- null类型返回值
IDL中间描述语言
- 基本的数据类型(bool/byte/i16/i32/i64/double/binary/string)
- 复合类型(list
, set , map<t1,t2>) - 枚举类型(enum)
- 结构体(struct)
- 命名空间(namespace)
- typedef 重声明类型
- 注释
- 常量const修饰
- 异常exception,语义和功能含义上类似于struct,只是关键字不同而已
- include外部thrift文件
- service服务(service)以及服务继承:
- 注意一个service生成一个为server的服务接口和为客户端的stubs存根(桩);
- oneway修饰的接口函数表示客户端仅请求,不需要等待响应,且该oneway修饰的方式返回值只能为void;
- service服务支持继承,但结构体不支持。
基本概念
Thrift 分层结构堆栈
+-------------------------------------------+
| Server |
| (single-threaded, event-driven etc) |
+-------------------------------------------+
| Processor |
| (compiler generated, RPC) |
+-------------------------------------------+
| Protocol |
| (JSON, compact etc) |
+-------------------------------------------+
| Transport |
| (raw TCP, HTTP,File etc) |
+-------------------------------------------+
Transport传输(抽象层)
- 生成的代码使用传输层来实现数据转移,一般基于TCP/IP协议栈实现通信,也可用其他如共享内存、磁盘文件等;
- 提供了简单的读、写抽象操作,以解耦序列化/反序列化操作(I/O层抽象(如虚函数查找)以及实际的I/O系统操作的权衡考虑);
- 提供接口一般有:open、isOpen、close、read、write、flush;
- 此外除了以上接口,还thrift提供了ServerTransport接口用以接收或创建原始的传输对象;
- 该ServerTransport主要用于服务器端为incoming连接对象来创建一个新的传输对象,其提供的接口有open、listen、accept、close;
- 此外开发者也可基于提供的抽象接口实现自定义的传输层实现。
Protocol协议(编码/解码层、抽象层)
- 其抽象了用以将一个内存数据结构转为流或帧的数据格式,Protocol将数据类型的编码或解码过程;
- 因此Protocol需要负责实现双向的解码/编码,以实现序列化和反序列化;
- 已提供的可用的:JSON、XML、plain text、compact binart等;
- 提供的接口比较多:成对的writeMessageBegin、writeMessageEnd、writeMapBegin、writeMapEnd,以及读对应的读操作等顾名思义的接口名称;
- Thrift的Protocol主要是面向流的设计,其不需要任何显式的框架,也不需要知道字符串长度、列表项数等。
Processor处理器(RPC实现部分)
- 其封装了从输入流读取数据和写入数据至输出流;
- 其中Protocol协议对象代表了此处的输入输出流;
- 已提供的接口:process;
- 特定服务的处理器实现已由编译器生成;
- Processor本质上读取数据是由input protocol获取,委托处理给用户实现的handler处理器处理;
- 其通过output protocol写响应数据流。
Server服务器
Server拥有以下的一些特性描述:
- 创建一个Transport传输对象;
- 为该Transport对象创建input/output的Protocols;
- 基于该input/output创建一个Processor处理器对象;
- 等待incoming 连接并把它们交给Processor处理器。
编译器生成代码
thrift.exe -r --gen cpp example.thrift
(说明:example IDL文件包含名为Twitter的service)
生成文件:
|-- example_constants.cpp
|-- example_constants.h
|-- example_types.cpp
|-- example_types.h
|-- Twitter.cpp
|-- Twitter.h
`-- Twitter_server.skeleton.cpp
文件说明:
-
example_constants.h/cpp:为IDL文件中的常量,所有的常量被包装为一个类内里,且生成一个该类的全局对象。
-
example_types.h/cpp:为IDL文件中的声明的类型。
-
其中枚举被结构体包装;
-
结构体被包装为类;
-
异常exception被包装为类;
-
typedef的还是被typedef;
-
另外还有一些operator<<输出流辅助重载函数,以及swap交互函数等。
-
Twitter_server.skeleton.cpp:一个生成的以TSimpleServer的server端的实现骨架示例程序代码。其中服务器端可继承实现TwitterIf相关接口处理程序,实现具体的服务处理。
基本代码:
::apache::thrift::stdcxx::shared_ptr<TwitterHandler> handler(new TwitterHandler());
::apache::thrift::stdcxx::shared_ptr<TProcessor> processor(new TwitterProcessor(handler));
::apache::thrift::stdcxx::shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
::apache::thrift::stdcxx::shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
::apache::thrift::stdcxx::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
server.serve();
描述:
-
主要的一个具体的处理程序handler(TwitterHandler);
-
一个processor(TwitterProcessor);
-
一个serverTransport;
-
另外的transportFactory以及protocolFactory,甚至TSimpleServer也可换用其他的方式。
-
Twitter.h/cpp:生成的服务接口
基本上IDL中一个service一个对应的.h/cpp对;
接口中的各函数返回值和参数的均包装为类,包括void、bool等基本类型;
以上生成的文件,除了Twitter_server.skeleton.cpp,均可为客户端和服务端共享; -
如上说明:
-
服务端一般实现TwitterIf接口或者说一个继承该TwitterIf接口的handler;
-
客户端一般可直接使用Twitter.h中的TwitterClient;
-
当然客户端还需要配置对应protocol对象和transport对象以及实际的transport包装的通信方式,如socket;
-
基本上均可分层次使用不同的protocol对象和transport对象以及底层通信。
另外重要的:
- client端能够主动与server端通信,但server端不能主动与client端通信而只能被动地对client端的请求作出应答;
- 要想实现服务器向客户端推送数据的双向通信,则可;
- 方法1:客户端轮询,让服务器返回数据,延迟大而且浪费资源开销大;
- 方法2:也即是让客户端也成为服务器,让服务器也扮演客户端的角色,不过此方式需要在通信双方之间建立两个通信通道,开启两个端口,比较繁琐,但是也很普遍;
- 方法3:采用socket或其他的双向传输方式支持来实现。
最佳实践
-
当需要修改thrift的IDL文件时:
- 不要修改已存在的数值id值;
- 任何你新添加的fields应该为optional的,避免新旧版本不兼容;
- 应该为新添加的fields设置合理的默认值;
- 非必需的字段可以删除,只要不再使用该数值id号以及后来的新加的fields不要再用该id;
- 改变一个默认值是可以的,因为默认值一般是不会被传输的,因此如果一个特别的字段没有被设置值,那么程序会看到自己端的默认值为其定义,而发送者的默认值有可能与接受者的默认值不同。
-
版本系统问题(添加域是optional且有默认值时,否则可能会不一致):
-
添加域:
-
旧客户端、新服务端,客户端发给服务端时,服务端针对没有的域使用默认的值。
-
新客户端、旧服务端,客户端发给服务端时,服务端对于无法识别的域则直接忽略丢弃。
-
-
移除域:
-
旧客户端、新服务端,客户端发给服务端时,服务端针对没有的域时直接忽略。
-
新客户端、旧服务端,客户端发给服务端时,服务端可能存在不一致的风险问题。
-