Thrift 开发简记

目标

需支持种类繁多的数据类型、跨语言、跨平台、高性能、兼容性且可扩展

  • 数据类型系统
  • 传输层 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)以及服务继承:
    1. 注意一个service生成一个为server的服务接口和为客户端的stubs存根(桩);
    2. oneway修饰的接口函数表示客户端仅请求,不需要等待响应,且该oneway修饰的方式返回值只能为void;
    3. 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文件时:

    1. 不要修改已存在的数值id值;
    2. 任何你新添加的fields应该为optional的,避免新旧版本不兼容;
    3. 应该为新添加的fields设置合理的默认值;
    4. 非必需的字段可以删除,只要不再使用该数值id号以及后来的新加的fields不要再用该id;
    5. 改变一个默认值是可以的,因为默认值一般是不会被传输的,因此如果一个特别的字段没有被设置值,那么程序会看到自己端的默认值为其定义,而发送者的默认值有可能与接受者的默认值不同。
  • 版本系统问题(添加域是optional且有默认值时,否则可能会不一致):

  • 添加域:

    • 旧客户端、新服务端,客户端发给服务端时,服务端针对没有的域使用默认的值。

    • 新客户端、旧服务端,客户端发给服务端时,服务端对于无法识别的域则直接忽略丢弃。

  • 移除域:

    • 旧客户端、新服务端,客户端发给服务端时,服务端针对没有的域时直接忽略。

    • 新客户端、旧服务端,客户端发给服务端时,服务端可能存在不一致的风险问题。

原文地址:https://www.cnblogs.com/haomiao/p/11646779.html