Thrift 的总体架构html
Thrift 包含一个完整的堆栈结构用于构建客户端和服务器端。其中代码框架层是根据 Thrift 定义的服务接口描述文件生成的客户端和服务器端代码框架,数据读写操做层是根据 Thrift 文件生成代码实现数据的读写操做java
Thrift 包含三个主要的组件:protocol,transport 和 server。缓存
protocol 定义了消息是怎样序列化的;安全
transport 定义了消息是怎样在客户端和服务器端之间通讯的;服务器
server 用于从 transport 接收序列化的消息,根据 protocol
反序列化之,调用用户定义的消息处理器,并序列化消息处理器的响应,而后再将它们写回 transport。网络
TProtocol(协议层) 定义数据传输格式架构
TBinaryProtocol:二进制编码格式进行数据传输;并发
TCompactProtocol:压缩的、密集的数据传输协议,基于Variable-length quantity的zigzag 编码格式;框架
TJSONProtocol:以JSON数据编码协议进行数据传输;异步
TSimpleJSONProtocol:提供JSON只写协议, 生成的文件很容易经过脚本语言解析;
TDebugProtocol:使用易懂的可读的文本格式,以便于debug
TTransport(传输层),定义数据传输方式,能够为TCP/IP传输,内存共享或者文件共享等)被用做运行时库。
TSocket:阻塞型 socket,用于客户端,采用系统函数 read 和 write 进行读写数据。
TServerSocket:非阻塞型 socket,用于服务器端,accecpt 到的 socket 类型都是 TSocket(即阻塞型 socket)。
TBufferedTransport 和 TFramedTransport 都是有缓存的,均继承TBufferBase,调用下一层 TTransport 类进行读写操做吗,结构极为类似。其中 TFramedTransport 以帧为传输单位,帧结构为:4个字节(int32_t)+传输字节串,头4个字节是存储后面字节串的长度,该字节串才是正确须要传输的数据,所以 TFramedTransport 每传一帧要比 TBufferedTransport 和 TSocket 多传4个字节。
TMemoryBuffer 继承 TBufferBase,用于程序内部通讯用,不涉及任何网络I/O,可用于三种模式:
(1)OBSERVE模式,不可写数据到缓存;(2)TAKE_OWNERSHIP模式,需负责释放缓存;(3)COPY模式,拷贝外面的内存块到TMemoryBuffer;
TFileTransport 直接继承 TTransport,用于写数据到文件。对事件的形式写数据,主线程负责将事件入列,写线程将事件入列,并将事件里的数据写入磁盘。这里面用到了两个队列,类型为 TFileTransportBuffer,一个用于主线程写事件,另外一个用于写线程读事件,这就避免了线程竞争。在读完队列事件后,就会进行队列交换,因为由两个指针指向这两个队列,交换只要交换指针便可。它还支持以 chunk(块)的形式写数据到文件。
TFDTransport 是很是简单地写数据到文件和从文件读数据,它的 write 和 read 函数都是直接调用系统函数 write 和 read 进行写和读文件。
TSimpleFileTransport 直接继承 TFDTransport,没有添加任何成员函数和成员变量,不一样的是构造函数的参数和在 TSimpleFileTransport 构造函数里对父类进行了初始化(打开指定文件并将fd传给父类和设置父类的close_policy为CLOSE_ON_DESTROY)。
TZlibTransport 跟 TBufferedTransport 和 TFramedTransport同样,调用下一层 TTransport 类进行读写操做。它采用<zlib.h>提供的 zlib 压缩和解压缩库函数来进行压解缩,写时先压缩再调用底层 TTransport 类发送数据,读时先调用 TTransport 类接收数据再进行解压,最后供上层处理。
TSSLSocket 继承 TSocket,阻塞型 socket,用于客户端。采用 openssl 的接口进行读写数据。checkHandshake()函数调用 SSL_set_fd 将 fd 和 ssl 绑定在一块儿,以后就能够经过 ssl 的 SSL_read和SSL_write 接口进行读写网络数据。
TSSLServerSocket 继承 TServerSocket,非阻塞型 socket, 用于服务器端。accecpt 到的 socket 类型都是 TSSLSocket 类型。
THttpClient 和 THttpServer 是基于 Http1.1 协议的继承 Transport 类型,均继承 THttpTransport,其中 THttpClient 用于客户端,THttpServer 用于服务器端。二者都调用下一层 TTransport 类进行读写操做,均用到TMemoryBuffer 做为读写缓存,只有调用 flush() 函数才会将真正调用网络 I/O 接口发送数据。
Thrift 的服务模型 Server
TSimpleServer
TNonblockingServer
THsHaServer
TThreadedSelectorServer
TThreadPoolServer
TSimpleServer
TSimplerServer 接受一个链接,处理链接请求,直到客户端关闭了链接,它才回去接受一个新的链接。正由于它只在一个单独的线程中以阻塞 I/O 的方式完成这些工做,因此它只能服务一个客户端链接,其余全部客户端在被服务器端接受以前都只能等待。
TSimpleServer 主要用于测试目的,不要在生产环境中使用它!
TNonblockingServer vs. THsHaServer
TNonblockingServer 使用非阻塞的 I/O 解决了 TSimpleServer 一个客户端阻塞其余全部客户端的问题。它使用了 java.nio.channels.Selector,经过调用 select(),它使得你阻塞在多个链接上,而不是阻塞在单一的链接上。当一或多个链接准备好被接受/读/写时,select() 调用便会返回。TNonblockingServer 处理这些链接的时候,要么接受它,要么从它那读数据,要么把数据写到它那里,而后再次调用 select() 来等待下一个可用的链接。通用这种方式,server 可同时服务多个客户端,而不会出现一个客户端把其余客户端所有“饿死”的状况。
然而,还有个棘手的问题:全部消息是被调用 select() 方法的同一个线程处理的。假设有10个客户端,处理每条消息所需时间为100毫秒,那么,latency 和吞吐量分别是多少?当一条消息被处理的时候,其余9个客户端就等着被 select,因此客户端须要等待1秒钟才能从服务器端获得回应,吞吐量就是10个请求/秒。若是能够同时处理多条消息的话,会很不错吧?
所以,THsHaServer(半同步/半异步的 server)就应运而生了。它使用一个单独的线程来处理网络I/O,一个独立的 worker 线程池来处理消息。这样,只要有空闲的 worker 线程,消息就会被当即处理,所以多条消息能被并行处理。用上面的例子来讲,如今的 latency 就是100毫秒,而吞吐量就是100个请求/秒。
为了演示作了一个测试,有10客户端和一个修改过的消息处理器——它的功能仅仅是在返回以前简单地 sleep 100 毫秒。使用的是有10个 worker 线程的 THsHaServer。消息处理器的代码看上去就像下面这样:
public ResponseCode sleep() throws TException{
try { Thread.sleep(100); } catch (Exception ex) {} return ResponseCode.Success;
}
特别申明,本章节的测试结果摘自站外文章,详情请看文末连接
结果正如咱们想像的那样,THsHaServer 可以并行处理全部请求,而 TNonblockingServer 只能一次处理一个请求。
THsHaServer vs. TThreadedSelectorServer
Thrift 0.8 引入了另外一种 server 实现,即 TThreadedSelectorServer。它与 THsHaServer 的主要区别在于,TThreadedSelectorServer 容许你用多个线程来处理网络 I/O。它维护了两个线程池,一个用来处理网络 I/O,另外一个用来进行请求的处理。当网络 I/O 是瓶颈的时候,TThreadedSelectorServer 比 THsHaServer 的表现要好。为了展示它们的区别进行一个测试,令其消息处理器在不作任何工做的状况下当即返回,以衡量在不一样客户端数量的状况下的平均 latency 和吞吐量。对 THsHaServer,使用32个 worker 线程;对 TThreadedSelectorServer,使用16个 worker 线程和16个 selector 线程。
结果显示,TThreadedSelectorServer 比 THsHaServer 的吞吐量高得多,而且维持在一个更低的 latency 上。
TThreadedSelectorServer vs. TThreadPoolServer
最后,还剩下 TThreadPoolServer。TThreadPoolServer 与其余三种 server 不一样的是:
1.有一个专用的线程用来接受链接
2.一旦接受了一个链接,它就会被放入 ThreadPoolExecutor 中的一个 worker 线程里处理。
3.worker 线程被绑定到特定的客户端链接上,直到它关闭。一旦链接关闭,该 worker 线程就又回到了线程池中。
4.你能够配置线程池的最小、最大线程数,默认值分别是5(最小)和 Integer.MAX_VALUE(最大)。
这意味着,若是有1万个并发的客户端链接,你就须要运行1万个线程。因此它对系统资源的消耗不像其余类型的 server 同样那么“友好”。此外,若是客户端数量超过了线程池中的最大线程数,在有一个 worker 线程可用以前,请求将被一直阻塞在那里。
咱们已经说过,TThreadPoolServer 的表现很是优异。在我正在使用的计算机上,它能够支持1万个并发链接而没有任何问题。若是你提早知道了将要链接到你服务器上的客户端数量,而且你不介意运行大量线程的话,TThreadPoolServer 对你多是个很好的选择。
我想你能够从上面的描述能够帮你作出决定:哪种 Thrift server 适合你。
TThreadedSelectorServer 对大多数案例来讲都是个安全之选。若是你的系统资源容许运行大量并发线程的话,建议你使用 TThreadPoolServer。
使用 Thrift 强大的代码生成引擎来生成 java 代码,并经过详细的步骤实现 Thrift Server 和 Client 调用。
第一步:定义好 IDL 描述语言
第二步:生成后的 文件
第三步:pom.xml 中引入 Thrift 的依赖
第四步:实现服务端Server
第五步:实现异步客户端Client
别忘了关闭,当初没关闭,致使系统跑一会就挂了。