thrift网络通讯架构解析与实例验证

来源:互联网 发布:php 去除双引号 编辑:程序博客网 时间:2024/06/04 23:27

Thrift实现了C/S模式,通过代码生成工具将接口定义文件生成服务器端和客户端代码(可以为不同语言),从而实现服务端和客户端跨语言的支持。用户在Thirft描述文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后用户实现服务(客户端调用服务,服务器端提服务)便可以了。

一、Thrift Rpc整体架构

Thrift的协议栈如图:


在Client和Server的最顶层都是用户自定义的处理逻辑,也就是说用户只需要编写用户逻辑,就可以完成整套的RPC调用流程。用户逻辑的下一层是Thrift自动生成的代码,这些代码主要用于结构化数据的解析,发送和接收,同时服务器端的自动生成代码中还包含了RPC请求的转发(Client的A调用转发到Server A函数进行处理)

● 底层IO模块,负责实际的数据传输,包括Socket,文件,或者压缩数据流等。

● TTransport负责以字节流方式发送和接收Message,是底层IO模块在Thrift框架中的实现,每一个底层IO模块都会有一个对应TTransport来负责Thrift的字节流(Byte Stream)数据在该IO模块上的传输。例如TSocket对应Socket传输,TFileTransport对应文件传输。

● TProtocol主要负责结构化数据组装成Message,或者从Message结构中读出结构化数据。TProtocol将一个有类型的数据转化为字节流以交给TTransport进行传输,或者从TTransport中读取一定长度的字节数据转化为特定类型的数据。如int32会被TBinaryProtocol Encode为一个四字节的字节数据,或者TBinaryProtocol从TTransport中取出四个字节的数据Decode为int32。

● TServer负责接收Client的请求,并将请求转发到Processor进行处理。TServer主要任务就是高效的接受Client的请求,特别是在高并发请求的情况下快速完成请求。

● Processor(或者TProcessor)负责对Client的请求做出相应,包括RPC请求转发,调用参数解析和用户逻辑调用,返回值写回等处理步骤。Processor是服务器端从Thrift框架转入用户逻辑的关键流程。Processor同时也负责向Message结构中写入数据或者读出数据。


二、数据传输方式(TTransport)

  • TSocket : 阻塞式socker;
  • THttpTransport : 采用HTTP传输协议进行数据传输;
  • TFramedTransport : 以frame为单位进行传输,非阻塞式服务中使用;
  • TFileTransport : 以文件形式进行传输;
  • TMemoryTransport : 将内存用于I/O. java实现时内部实际使用了简单的ByteArrayOutputStream;
  • TZlibTransport : 使用zlib进行压缩, 与其他传输方式联合使用。当前无java实现;
  • TBufferedTransport : 对某个transport对象操作的数据进行buffer,即从buffer中读取数据进行传输,或将数据直接写入到buffer
Thrift实现中,一个关键的设计选择就是将传输层从代码生成层解耦。从根本上,生成的Thrift代码只需要知道如何读和写数据。数据的源和目的地无关紧要,可以使一个socket,一段共享内存,或本地磁盘上的一个文件。TTransport(Thrift transport)接口支持以下方法:
open    Opens the tranpsortclose    Closes the tranportisOpen  Indicates whether the transport is openread    Reads from the transportwrite   Writes to the transportflush   Forces any pending writes
除以上的TTransport接口外,还有一个TServerTransport接口,用来接受或创建原始传输对象。它的接口如下:
open   Opens the transportlisten   Begins listening for connectionsaccept  Returns a new client transportclose   Closes the transport

三、传输协议(TProtocol)

thrift做到很好的让用户在服务器端与客户端选择对应的传输协议,总体上一般为2种传输协议:二进制或者文本,如果想要节省带宽可以采用二进制的协议,如果希望方便抓包、调试则可以选择文本协议,用户可用根据自己的项目需求选择对应的协议。

  • TCompactProtocol : 紧凑的、高效的二进制传输协议;
  • TBinaryProtocol : 基于二进制传输的协议,使用方法与TCompactProtocol 相同
  • TJSONProtocol : 使用json格式编码传输协议
  • TDebugProtocol : 使用易懂的可读的文本格式,以便于debug
TCompactProtocol 高效的编码方式,使用了类似于ProtocolBuffer的Variable-Length Quantity (VLQ) 编码方式,主要思路是对整数采用可变长度,同时尽量利用没有使用Bit。对于一个int32并不保证一定是4个字节编码,实际中可能是1个字节,也可能是5个字节,但最多是五个字节。TCompactProtocol并不保证一定是最优的,但多数情况下都会比TBinaryProtocol性能要更好。
TProtocol接口非常直接,它根本上支持两件事: 1) 双向有序的消息传递; 2) 基本类型、容器及结构体的编码。
writeMessageBegin(name, type, seq)writeMessageEnd()writeStructBegin(name)writeStructEnd()writeFieldBegin(name, type, id)writeFieldEnd()writeFieldStop()writeMapBegin(ktype, vtype, size)writeMapEnd()writeListBegin(etype, size)writeListEnd()writeSetBegin(etype, size)writeSetEnd()writeBool(bool)writeByte(byte)writeI16(i16)writeI32(i32)writeI64(i64)writeDouble(double)writeString(string)name, type, seq = readMessageBegin()readMessageEnd()name = readStructBegin()readStructEnd()name, type, id = readFieldBegin()readFieldEnd()k, v, size = readMapBegin()readMapEnd()etype, size = readListBegin()readListEnd()etype, size = readSetBegin()readSetEnd()bool = readBool()byte = readByte()i16 = readI16()i32 = readI32()i64 = readI64()double = readDouble()string = readString()
注意到每个write函数有且仅有一个相应的read方法。WriteFieldStop()异常是一个特殊的方法,标志一个结构的结束。读一个结构的过程是readFieldBegin()直到遇到stop域,然后readStructEnd()。生成的代码依靠这个调用顺序,来确保一个协议编码器所写的每一件事,都可被一个相应的协议解码器读取。 这组功能在设计上更加注重健壮性,而非必要性。例如,writeStructEnd()不是严格必需的,因为一个结构体的结束可用stop域表示。

四、服务器网络模型(TServer)

  • TSimpleServer : 简单的单线程网络模型, 同时只能服务一个client,通常是结合TSocket用于测试;
  • TThreadedServer : 多线程网络模型,使用阻塞式IO,为每个请求创建一个线程;
  • TThreadPoolServer : 线程池网络模型,使用阻塞式IO,将每个请求都加入到线程池中;
  • TNonblockingServer : 多线程服务模型,使用非阻塞式IO(需使用TFramedTransport数据传输方式);
Thrift 使用 libevent 作为服务的事件驱动器, libevent 其实就是 epoll更高级的封装而已(在linux下是epoll)。处理大量更新的话,主要是在TThreadedServer和TNonblockingServer中进行选择。TNonblockingServer能够使用少量线程处理大量并发连接,但是延迟较高;TThreadedServer的延迟较低。实际中,TThreadedServer的吞吐量可能会比TNonblockingServer高,但是TThreadedServer的CPU占用要比TNonblockingServer高很多。

TServer对象通常如下工作:

1)使用TServerTransport获得一个TTransport2)使用TTransportFactory,可选地将原始传输转换为一个适合的应用传输(典型的是使用TBufferedTransportFactory)3)使用TProtocolFactory,为TTransport创建一个输入和输出4)调用TProcessor对象的process()方法
Thrift中定义一个server的方法如下:
TSimpleServer server(    boost::make_shared<CalculatorProcessor>(boost::make_shared<CalculatorHandler>()),    boost::make_shared<TServerSocket>(9090),    boost::make_shared<TBufferedTransportFactory>(),    boost::make_shared<TBinaryProtocolFactory>());  TThreadedServer server(    boost::make_shared<CalculatorProcessorFactory>(boost::make_shared<CalculatorCloneFactory>()),    boost::make_shared<TServerSocket>(9090), //port    boost::make_shared<TBufferedTransportFactory>(),    boost::make_shared<TBinaryProtocolFactory>());  const int workerCount = 4;//线程池容量  boost::shared_ptr<ThreadManager> threadManager = ThreadManager::newSimpleThreadManager(workerCount);  threadManager->threadFactory(boost::make_shared<PlatformThreadFactory>());  threadManager->start();  TThreadPoolServer server(    boost::make_shared<CalculatorProcessorFactory>(boost::make_shared<CalculatorCloneFactory>()),    boost::make_shared<TServerSocket>(9090),    boost::make_shared<TBufferedTransportFactory>(),    boost::make_shared<TBinaryProtocolFactory>(),    threadManager);  TNonBlockingServer server(    boost::make_shared<CalculatorProcessorFactory>(boost::make_shared<CalculatorCloneFactory>()),    boost::make_shared<TServerSocket>(9090),    boost::make_shared<TFramedTransportFactory>(),    boost::make_shared<TBinaryProtocolFactory>(),    threadManager);  server.serve();//启动server

五、TProcessor/Processor

Processor是由Thrift生成的TProcessor的子类,主要对TServer中一次请求的 InputProtocol和OutputTProtocol进行操作,也就是从InputProtocol中读出Client的请求数据,向OutputProtcol中写入用户逻辑的返回值。Processor是TServer从Thrift框架转到用户逻辑的关键流程。同时TProcessor.process是一个非常关键的处理函数,因为Client所有的RPC调用都会经过该函数处理并转发。
Thrift在生成Processor的时候,会遵守一些命名规则,可以参考 Thrift Generator部分的介绍。
TProcessor对于一次RPC调用的处理过程可以概括为:
1、TServer接收到RPC请求之后,调用TProcessor.process进行处理
2、TProcessor.process首先调用TTransport.readMessageBegin接口,读出RPC调用的名称和RPC调用类型。如果RPC调用类型是RPC Call,则调用TProcessor.process_fn继续处理,对于未知的RPC调用类型,则抛出异常。
3、TProcessor.process_fn根据RPC调用名称到自己的processMap中查找对应的RPC处理函数。如果存在对应的RPC处理函数,则调用该处理函数继续进行请求响应。不存在则抛出异常。
a)在这一步调用的处理函数,并不是最终的用户逻辑。而是对用户逻辑的一个包装。
b)processMap是一个标准的std::map。Key为RPC名称。Value是对应的RPC处理函数的函数指针。 processMap的初始化是在Processor初始化的时候进行的。Thrift虽然没有提供对processMap做修改的API,但是仍可以通过继承TProcessor来实现运行时对processMap进行修改,以达到打开或关闭某些RPC调用的目的。
4、RPC处理函数是RPC请求处理的最后一个步骤,它主要完成以下三个步骤:
a) 调用RPC请求参数的解析类,从TProtocol中读入数据完成参数解析。不管RPC调用的参数有多少个,Thrift都会将参数放到一个Struct中去。Thrift会检查读出参数的字段ID和字段类型是否与要求的参数匹配。对于不符合要求的参数都会跳过。这样,RPC接口发生变化之后,旧的处理函数在不做修改的情况,可以通过跳过不认识的参数,来继续提供服务。进而在RPC框架中提供了接口的多Version支持。
b) 参数解析完成之后,调用用户逻辑,完成真正的请求响应。
c) 用户逻辑的返回值使用返回值打包类进行打包,写入TProtocol。

六、ThriftClient

ThriftClient跟TProcessor一样都主要操作InputProtocol和OutputProtocol,不同的是ThritClient将RPC调用分为Send和receive两个步骤。
1、Send步骤,将用户的调用参数作为一个整体的Struct写入TProcotol,并发送到TServer。
2、Send结束之后,ThriftClient便立刻进入Receive状态等待TServer的相应。对于TServer返回的响应,使用返回值解析类进行返回值解析,完成RPC调用。


七、Thrift通讯实例

1、编写thrift接口文件student.thrift 
struct Student{ 1: i32 sno, 2: string sname, 3: bool ssex, 4: i16 sage,}service Serv{ i32 put(1: Student s),}

2、根据.thrift文件生成代码文件
thrift -r --gen cpp student.thrift
在目录下生成了gen-cpp文件夹,内有文件 Serv.cpp  Serv.h  Serv_server.skeleton.cpp  student_constants.cpp  student_constants.h  student_types.cpp  student_types.h,
文件Serv_server.skeleton.cpp是一个简单的server端文件,可以以此为参考来编写代码

3、编写服务端代码server.cpp
#include <concurrency/ThreadManager.h> //zml#include <concurrency/PosixThreadFactory.h> //zml#include "gen-cpp/Serv.h"#include <protocol/TBinaryProtocol.h>#include <server/TSimpleServer.h>#include <transport/TServerSocket.h>#include <transport/TBufferTransports.h>#include <server/TNonblockingServer.h> //zml    using namespace ::apache::thrift;using namespace ::apache::thrift::protocol;using namespace ::apache::thrift::transport;using namespace ::apache::thrift::server;    using namespace::apache::thrift::concurrency; //zml        using boost::shared_ptr;  #define THREAD_NUM 2const int g_port = 9090;class ServHandler : virtual public ServIf { public:  ServHandler() {  // Your initialization goes here  }  int32_t put(const Student& s) {  // Your implementation goes here  printf("put student.sno=%d\n", s.sno);  return s.sno;  }};int thrift_server_run(){  //创建thrift server  shared_ptr<ServHandler> handler(new ServHandler());  shared_ptr<TProcessor> processor(new ServProcessor(handler));  shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());    shared_ptr<ThreadManager> threadManager = ThreadManager::newSimpleThreadManager(THREAD_NUM);  shared_ptr<PosixThreadFactory> threadFactory = shared_ptr<PosixThreadFactory> (new PosixThreadFactory()); //PosixThreadFactory可以自定义(继承于ThreadFactory)  threadManager->threadFactory(threadFactory);  threadManager->start();     TNonblockingServer server(processor, protocolFactory, g_port, threadManager);  try {    server.serve();  }  catch(TException e) {    printf("Server.serve() failed\n");    exit(-1);  }  return 0;}int main(int argc, char **argv) {  thrift_server_run();  while(1) {    sleep(10);  }  return 0;}

4、编写服务端代码client.cpp
#include "gen-cpp/Serv.h"  // 替换成你的.h  #include <transport/TSocket.h>#include <transport/TBufferTransports.h>#include <protocol/TBinaryProtocol.h>using namespace apache::thrift;using namespace apache::thrift::protocol;using namespace apache::thrift::transport;using boost::shared_ptr;int main(){boost::shared_ptr<TSocket> socket(new TSocket("localhost", 9090));//对接nonblockingServer时必须的,对普通server端时用boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));boost::shared_ptr<TTransport> transport(new TFramedTransport(socket)); boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));ServClient client(protocol);//设置发送、接收、连接超时socket->setConnTimeout(2000);socket->setRecvTimeout(2000);socket->setSendTimeout(2000);transport->open();//insert your code here  Student stu;stu.sno = 1;stu.sname = "zml";stu.ssex = 0;stu.sage = 25;int ret = client.put(stu);printf("client put ret=%d\n", ret);transport->close();return 0;}

5、编译执行
g++ -g -DHAVE_NETINET_IN_H -I/usr/local/include/thrift -L/usr/local/lib/ gen-cpp/Serv.cpp gen-cpp/student_types.cpp gen-cpp/student_constants.cpp client.cpp -o client -lpthread -lthrift -lrt
g++ -g -DHAVE_NETINET_IN_H -I. -I/usr/local/include/thrift -L/usr/local/lib gen-cpp/Serv.cpp gen-cpp/student_types.cpp gen-cpp/student_constants.cpp server.cpp -o server -lthriftnb -levent -lthrift -lrt

开启服务端,并且执行客户端,服务端打印:
lijinqi@ubuntu:~/test/thrift$ ./serverThrift: Thu Sep 21 16:31:13 2017 TNonblockingServer: Serving on port 9090, 1 io threads.Thrift: Thu Sep 21 16:31:13 2017 TNonblockingServer: using libevent 2.0.21-stable method epollThrift: Thu Sep 21 16:31:13 2017 TNonblocking: IO thread #0 registered for listen.Thrift: Thu Sep 21 16:31:13 2017 TNonblocking: IO thread #0 registered for notify.Thrift: Thu Sep 21 16:31:13 2017 TNonblockingServer: IO thread #0 entering loop...put student.sno=1

客户端打印
lijinqi@ubuntu:~/test/thrift$ ./client client put ret=1


参考:
http://cpper.info/2016/03/17/Thrift-Reserch.html
http://zheming.wang/blog/2014/08/28/94D1F945-40EC-45E4-ABAF-3B32DFFE4043/
https://my.oschina.net/zmlblog/blog/177245

原创粉丝点击