Thrift Servers性能对比
Thrift 是一个跨语言的序列化/rpc框架,它有三个主要组成:协议protocol,传输transport和服务server。协议定义消息如何被序列化的。传输定义消息如何在client与server之间传送。server从transport收到序列化后的消息,按照协议反序列化消息然后调用用户定义的消息处理函数,然后反序列化hander来的response再写回transport。Thrift的模块化架构使得它能有多样的servers可选。下面是java中可用server的列表:
TSimpleServer
TNonblockingServer
THsHaServer
TThreadedSelectorServer
TThreadPoolServer
有选择是好事,但哪个server才适合你?在这篇文章里,我将描述各servers之间的不同并通过benchmark结果来阐述性能特点(在附录B中有详细benchmark)。让我们从最简单的一个开始:TSimpleServer
TSimpleServer
TSimpleServer接收一个连接,处理这个连接上的请求直到client关闭该连接,才去重新接受一个新连接。因为所有事情都在一个线程且是阻塞I/O, 它仅能同时服务一个连接,其他client不得不等待直到被接收。TSimpleServer主要用于测试目的,不要在生产环境中使用。
TNonblockingServer VS THsHaServer
TNonblockingServer使用非阻塞I/O解决了TSimpleServer一个client会阻塞所有其他client的问题。它使用 java.nio.channels.Selector, 通过调用select()允许你阻塞在多个连接上而不是一个连接。当一个或多个连接可接受/读/写时 select() 返回。TNonblockingServer 处理这些连接要么接受连接,要么从此读数据或写数据到连接,并再次调用select()等待下个可用的连接。安装这种方式,可以服务多个client而不会一个client饿死其他client。
然而,有个棘手问题。消息都是由调用select()的线程来处理的. 让我们假设有10个client,每个消息处理需要100ms。延迟和吞吐量多少?当一个message被处理的时候,其他9个client等待被选择,所以,需要1sclient才能得到server返回的response,吞吐量事10 req/s。如果消息能被同时处理是不是会好很多?
这就是THsHaServer(半同步/半异步服务)的切入点。它使用单线程处理网络io,和分开的工作线程池处理消息处理。这样如果有空闲的工作线程,消息就会立马被处理,多个消息也能被并行的处理。使用上面的例子,现在延迟事100ms,吞吐量奖是100 req/s.
为了证明这点,我运行了一个benchmark,有10个client和一个消息处理函数,消息处理仅仅是简单sleep 100ms然后返回。我使用10工作线程的THsHaServer。 handler如下所示:
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。TThreadedSelectorServer和THsHaServer最大的不同是允许你有为网络I/O分配多个线程。它保持两个线程池,一个为了处理网络I/O, 一个处理请求。TThreadedSelectorServer在网络io是瓶颈时表现比THsHaServer好。为了显示他们的不同,我运行一个处理函数不做任何事立即返回的benchmark, 并测量不同client数量下的平均延迟和吞吐量。我使用32线程的THsHaServer 和16工作线程/16selector线程的TThreadedSelectorServer。
结果显示TThreadedSelectorServer比THsHaServer有更高的吞吐量且更低的延迟。
TThreadedSelectorServer vs. TThreadPoolServer
最后是TThreadPoolServer, TThreadPoolServer和其他3个servers不同地方在于:
有一个专用线程接受连接
一旦一个连接被接受了,被安排给ThreadPoolExecutor中一个工作线程来处理
这个工作线程服务该指定client连接直到关闭。一旦该连接关闭,该工作线程回到线程池
你可以配置线程池的最小和最大线程数。对应的默认值事5和Integer.MAX_VALUE
这意味着如果有10000个并发client连接,你需要运行10000个线程。就本身而论,这不如其他servers对资源友好。并且,如果client的数量超过线程池的最大数值,请求将被阻塞住直到有工作线程可用。
话虽如此,TThreadPoolServer表现的非常好;我用它支撑10000个并发连接没有任何问题。如果你能提前知道你的client数目并且也不介意多一点线程,TThreadPoolServer对你可能是个好选择。
我希望这篇文章能帮你决定哪种Thrift server适合你。
我觉得对于多数情况TThreadedSelectorServer是个安全的选择。如果你你可以接受运行大量并发进程,你也可以考虑TThreadPoolServer。