Apache thrift 研究小记

      最近花了一些时间研究thrift,一种跨语言分布式RPC服务通信框架,通过编写.thrift文件声明接口类和方法,客户端调用定义的方法,Server端实现定义的接口(有一种webservice升级版的感脚)。

      为什么研究它呢?因为BTS Team打算将NairService与NairSdk中的通信协议换成thrift,这样方便更多语言接入更高效快捷。目前thrift支持的语言实在太多:C/C++, C#, Golang, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, Cocoa, Smalltalk等等,在多种不同的语言之间通信thrift可以作为二进制的高性能的通讯中间件,支持数据(对象)序列化和多种类型的RPC服务。

      Thrift适用于程序静态的数据交换,需要先确定好他的数据结构,他是完全静态化的,当数据结构发生变化时,必须重新编辑IDL文件,代码生成,再编译载入的流程,跟其他IDL工具相比较可以视为是Thrift的弱项,但Thrift适用于搭建大型数据交换及存储的通用工具,对于大型系统中的内部数据传输相对于JSON和xml无论在性能、传输大小上有明显的优势,所以这也是Hadoop Hbase、cassandra向它靠拢的原因吧。

      Thrift 主要由5个部分组成:

      1、类型系统以及 IDL 编译器gen:负责由用户给定的 IDL 文件生成相应语言的接口代码。

      2、TProtocol:实现 RPC 的协议层,可以选择多种不同的对象串行化方式,如 XML、JSON, Binary和压缩Binary等,对应协议模型如下:

                TBinaryProtocol – 二进制格式

                TCompactProtocol – 压缩格式

                TJSONProtocol – JSON格式

                TSimpleJSONProtocol –提供JSON只写协议, 生成的文件很容易通过脚本语言解析

                TDebugProtocol – 使用易懂的可读的文本格式,以便于debug

      3、TTransport:实现 RPC 的传输层,同样可以选择不同的传输层实现,如socket, 非阻塞的 socket, MemoryBuffer 等,对应传输模型如下:

                TFileTransport:文件(日志)传输类,允许client将文件传给server,允许server将收到的数据写到文件中

                THttpTransport:采用Http传输协议进行数据传输

                TSocket:采用TCP Socket进行数据传输

                TZlibTransport:压缩后对数据进行传输,或者将收到的数据解压

      4、TProcessor:作为协议层和用户提供的服务实现之间的纽带,负责调用服务实现的接口。

      5、TServer:聚合 TProtocol, TTransport 和 TProcessor 几个对象; 当然TServer只是一个虚基类,派生出了如下四种服务模型,可在实际规模项目中进行抉择:

                TSimpleServer – 简单的单线程服务模型,一次只处理一个client请求,依我看这只能算thrift的玩具模型,不能运用到产线。

                TThreadedServer - 多线程服务模型,使用阻塞式IO,每个请求创建一个线程,这种模型只能使用在服务器与服务器之间的thrift通信,因为服务器之间长连接不会太大,每个请求开辟一个线程,不会浪费服务器太多线程资源开销。

                TThreadPoolServer – 线程池服务模型,使用标准的阻塞式IO(同步I/O方式),预先创建一组线程处理请求。

                TNonblockingServer – 多线程服务模型,使用非阻塞式IO(异步I/O方式,需使用TFramedTransport数据传输方式)

            以上四个server中,TNonblockingServer实现最复杂,但性能最好,一般应用会采用TNonblockingServer。

            Thrift的层次实现:Processor层是thrift与传统RPC对比多出来的一层设计.

            

    编写IDL示例描述

    假设我们要使用thrift RPC完成一个数据传输任务,数据格式和PRC接口用一个thrift文件描述,具体如下:

    

 //bookinfo.thrift     用于描述书籍信息的thrift文件
     namespace cpp example
     struct Book_Info {
         i32 book_id,
         string book_name,
         string book_author,
         double book_price,
         string book_publisher,
     }
     //bookinterface.thrift,client向server传输数据的RPC接口
     namespace cpp example
     include "bookinfo.thrift"
     service BookServlet {
         bool Sender(1: list<book.Book_Info> books);
         oneway void Sender2(1: list<book.Book_Info> books);
     }

      bookinterface.thrift文件定义了一个service,它包含两个接口,server端需要实现这两个接口以对client提供服务。其中,第一个接口函数是阻塞式的,即要等待server返回值以后才能继续,另外一个声明为oneway类型(返回值为void),表明该函数是非阻塞式的,将数据发给server后不必等待返回结果,但使用该函数时,需要考虑server的承受能力,适度的调整发送频率。

      Thrift文件与生成的代码对应关系

      每个thrift文件会产生四个文件,分别为:${thrift_name}_constants.h,${thrift_name}_constants.cpp,${thrift_name}_types.h,${thrift_name}_types.cpp

      对于含有service的thrift文件,会额外生成两个文件,分别为:${service_name}.h,${service_name}.cpp

      对于含有service的thrift文件,会生成一个可用的server桩:{service_name}._server.skeleton.cpp

      对于本文中的例子,会产生以下文件:

           bookinfo _constants.h            bookinfo_constants.cpp

           bookinfo _types.h                   bookinfo_types.cpp

           bookinterface _constants.h     bookinterface _constants.cpp

           bookinterface _types.h            bookinterface _types.cpp

           BookServlet.h                          BookServlet.cpp

           BookServlet_server.skeleton.cpp  //主程序入口

      编写client和server

      thrift IDL 使用gen命令编译时,只能生成server代码,所以client代码需要人工手动编写。

      Client编写步骤:

      1、定义TTransport,为你的client设置传输方式(如socket, http等)。

      2、定义Protocal,使用装饰模式(Decorator设计模式)封装TTransport,为你的数据设置编码格式(如二进制格式,JSON格式等)

      3、实例化client对象,调用服务接口。

     说明:如果用户在thrift文件中定义了一个叫${server_name}的service,则会生成一个叫${server_name}Client的对象,比如,我给出的例子中,thrift会自动生成一个叫BookServletClient的类,Client端的代码编写如下:

      #include "gen-cpp/BookServlet.h" //一定要包含该头文件
      #include "TimeService.h"
      #include <thrift/protocol/TBinaryProtocol.h>
      #include <thrift/server/TSimpleServer.h>
      #include <thrift/transport/TServerSocket.h>
      #include <thrift/transport/TBufferTransports.h>
      using boost::shared_ptr;
      int main(int argc, char** argv) {
          shared_ptr<TTransport> socket(new TSocket("localhost", 9090));
          shared_ptr<TTransport> transport(new TBufferedTransport(socket));
          shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
          example::BookServletClient client(protocol);
          try {
                   transport->open();
                   vector<example::Book_Info> books;
                   …...coding…
                  client.Sender(books);//RPC函数,调用serve端的该函数
                  transport->close();
          } catch (TException &tx) {
                  printf("ERROR: %s\n", tx.what());
          }
     }

      Server编写步骤:

      1、定义一个TProcess,这个是thrift根据用户定义的thrift文件自动生成的类

      2、使用TServerTransport获得一个TTransport

      3、使用TTransportFactory,可选地将原始传输转换为一个适合的应用传输(典型的是使用TBufferedTransportFactory)

      4、使用TProtocolFactory,为TTransport创建一个输入和输出

      5、创建TServer对象(单线程,可以使用TSimpleServer;对于多线程,用户可使用TThreadPoolServer或者TNonblockingServer),调用它的server()函数。

     #include "gen-cpp/BookServlet.h"
     #include <protocol/TBinaryProtocol.h>
     #include <server/TSimpleServer.h>
     #include <transport/TServerSocket.h>
     #include <transport/TBufferTransports.h>
     using namespace ::apache::thrift;
     using namespace ::apache::thrift::protocol;
     using namespace ::apache::thrift::transport;
     using namespace ::apache::thrift::server;
     using boost::shared_ptr;
     using namespace example;
     class BookServletHandler : virtual public BookServletIf {
                public:   BookServletHandler() {
                                // Your initialization goes here
                }
                //用户需实现这个接口
                bool Sender(const std::vector<example::Book_Info> & books) {
                                // Your implementation goes here
                                printf("Sender\n");
                }
                //用户需实现这个接口
                void Sender2(const std::vector<example::Book_Info> & books) {
                                 // Your implementation goes here
                                 printf("Sender2\n");
               }
      };
      
      int main(int argc, char **argv) {
           int port = 9090;
           shared_ptr<BookServletHandler> handler(new BookServletHandler());
           shared_ptr<TProcessor> processor(new BookServletProcessor(handler));
           shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
           shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
           shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
           TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
           server.serve();
           return 0;
      }

      以上测试程序只是用到TSimpleServer作为测试研究,在实际开发中还需根据项目运用场景选择其余几种服务模型,因thrift开源比较早,经受住了时间的考验,目前最新版本为thrift-0.9.3,官方更新程度也比较活跃,个别项目可以根据需要采用thrift框架进行开发。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值