NFS-RPC框架优化过程
NFS-RPC框架从编写之初,到现在为止(应该还会有些提升,不过估计不大),每秒支撑的请求数上升了好几倍,测试结果的演变为:
37k –> 56k –> 65k –> 88k –> 93k –> 143k –> 148k –> 153k
以上测试结果为在100并发、100 request byte、100 response byte以及单连接下的背景下得出的,在这篇blog中来分享下这个框架所做的一些优化动作,希望能给编写rpc框架或使用mina/netty/grizzly的同学们一点点帮助,也希望得到高手们更多的指点。
1、37k –> 56k
由于目前大部分的NIO框架采用的均为1个socket io线程处理多个连接的io事件,如果io线程时间占用太长的话,就会导致收到的响应处理的比较慢的现象,这步优化就是针对反序列化过程占用io线程而做的,采用的方法即为在读取流时仅根据长度信息把所有的bytes都读好,然后直接作为收到的信息返回给业务线程,业务线程在进行处理前先做反序列化动作,感兴趣的同学可以看看NFS-RPC中:code.google.nfs.rpc.netty.serialize.NettyProtocolDecoder或code.google.nfs.rpc.mina.serialize.MinaProtocolDecoder,以及code.google.nfs.rpc.mina.server.MinaServerHandler或code.google.nfs.rpc.netty.server.NettyServerHandler。
2、56k –> 65k
在测试的过程中,发现YGC的耗时比较长,在咨询了sun的人后告诉我主要是由于有旧生代的数据结构引用了大量新生代对象造成的,经过对程序的分析,猜测是benchmark代码本身用于记录请求响应时间信息的ConcurrentLinkedQueue造成的,在某超级大牛的指示下,换成了在每个线程中用数组的方式,按区间来记录响应时间信息等,感兴趣的同学可以看看NFS-RPC中:code.google.nfs.rpc.benchmark.SimpleProcessorBenchmarkClientRunnable
3、65k –> 88k
在某超级大牛的分析下,发现目前的情况下io线程的上下文切换还是比较频繁,导致io线程处理效率不够高,默认情况下,NIO框架多数采用的均为接到一个包后,将这个包交由反序列化的处理器进行处理,对于包中有多个请求信息或响应信息的情况,则采用一个一个通知的方式,而rpc框架在接到一个请求或响应对象时的做法通常是唤醒等待的业务线程,因此对于一个包中有多个请求或响应的状况就会导致io线程需要多次唤醒业务线程,这个地方改造的方法是nio框架一次性的将包中所有的请求或响应对象通知给业务线程,然后由业务线程pipeline的去唤醒其他的业务线程,感兴趣的同学可以看看NFS-RPC中:code.google.nfs.rpc.netty.serialize.NettyProtocolDecoder或code.google.nfs.rpc.mina.serialize.MinaProtocolDecoder,以及code.google.nfs.rpc.netty.client.NettyClientHandler或code.google.nfs.rpc.mina.client.MinaClientProcessor。
4、88k –> 93k
这步没什么可说的,只是多支持了hessian序列化,然后这个结果是用hessian序列化测试得出的。
5、93k –> 143k
在到达93k时,看到测试的结果中有不少请求的响应时间会超过10ms,于是用btrace一步一步跟踪查找是什么地方会出现这种现象,后来发现是由于之前在确认写入os send buffer时采用的是await的方式,而这会导致需要等待io线程来唤醒,增加了线程上下文切换以及io线程的负担,但这个地方又不能不做处理,后来就改造成仅基于listener的方式进行处理,如写入失败会直接创建一个响应对象,这次改造后效果非常明显,感兴趣的同学可以看看nfs-rpc中:code.google.nfs.rpc.mina.client.MinaClient或code.google.nfs.rpc.netty.client.NettyClient。
6、143k –> 148k
这步是在@killme2008 的指点下,将tcpNoDelay设置为了false,但这个设置不适用于低压力的情况,在10个线程的情况下tps由3w降到了2k,因此tcpNoDelay这个建议还是设置成true。
7、148k –> 153k
这步没什么可说的,只是多支持了protobuf序列化,这个结果是用protobuf序列化测试得出的,之前还测试过下@bnu_chenshuo 写的一个protorpc,是基于protobuf的rpc加上netty实现的,结果也很强悍,测试出来是149k,看来protobuf的rpc也是有不少值得学习的地方的。
上面就是到目前为止的所有优化动作,其中的很多我估计高手的话是不会犯错的,我走的弯路多了些,总结来说rpc框架的优化动作为:
1、尽可能减少io线程的占用时间;
2、尽可能减少线程上下文切换;
3、尽可能使用高效的序列化/反序列化;
ps: Grizzly的同学对nfs-rpc-grizzly的代码做了不少的修改,让grizzly的benchmark result从62k上升到了141k,如果你使用grizzly来做rpc,我觉得这部分的代码是很值得看看的,:)
NFS-RPC框架在后面将继续做更多的测试和优化,例如采用aio、coroutine、测试CNK问题等,敬请关注:http://code.google.com/p/nfs-rpc