作者:畢玄
文章來源:微信公衆号HelloJava
在
來測試下你的Java程式設計能力文章裡有一些關于Java網絡通信的題目,翻出幾年前的一篇文章再給大夥看看,這應該算是怎麼寫一個高性能RPC架構的還不錯的實踐,感興趣的其實也可以自己去寫個玩玩,這個過程會是學到很多東西的好方法。
下面文章寫于2011年。
McQueenRPC(代碼在github上)每秒支撐的請求數上升了好幾倍,測試結果的演變為:
37k –> 56k –> 65k –> 88k –> 93k –> 143k –> 148k –> 153k –> 160k –> 163k –> 168k
以上測試結果為在100并發、100 request byte、100 response byte以及單連接配接下的背景下得出的,在這篇blog中來分享下這個架構所做的一些優化動作,希望能給編寫rpc架構或使用netty的同學們一點點幫助,也希望得到高手們更多的指點。
1、37k –> 56k
由于目前大部分的NIO架構采用的均為1個socket io線程處理多個連接配接的io事件,如果io線程時間占用太長的話,就會導緻收到的響應處理的比較慢的現象,這步優化就是針對反序列化過程占用io線程而做的,采用的方法即為在讀取流時僅根據長度資訊把所有的bytes都讀好,然後直接作為收到的資訊傳回給業務線程,業務線程在進行處理前先做反序列化動作,感興趣的同學可以看看McQueenRPC中:NettyProtocolDecoder,以及NettyServerHandler。
2、56k –> 65k
在測試的過程中,發現YGC的耗時比較長,在咨詢了sun的人後告訴我主要是由于有舊生代的資料結構引用了大量新生代對象造成的,經過對程式的分析,猜測是benchmark代碼本身用于記錄請求響應時間資訊的ConcurrentLinkedQueue造成的,在某超級大牛的訓示下,換成了在每個線程中用數組的方式,按區間來記錄響應時間資訊等,感興趣的同學可以看看McQueenRPC中:SimpleProcessorBenchmarkClientRunnable
3、65k –> 88k
在某超級大牛的分析下,發現目前的情況下io線程的上下文切換還是比較頻繁,導緻io線程處理效率不夠高,預設情況下,NIO架構多數采用的均為接到一個包後,将這個包交由反序列化的處理器進行處理,對于包中有多個請求資訊或響應資訊的情況,則采用一個一個通知的方式,而rpc架構在接到一個請求或響應對象時的做法通常是喚醒等待的業務線程,是以對于一個包中有多個請求或響應的狀況就會導緻io線程需要多次喚醒業務線程,這個地方改造的方法是nio架構一次性的将包中所有的請求或響應對象通知給業務線程,然後由業務線程pipeline的去喚醒其他的業務線程,感興趣的同學可以看看McQueenRPC中:NettyProtocolDecoder,以及NettyClientHandler。
4、88k –> 93k
這步沒什麼可說的,隻是多支援了hessian序列化,然後這個結果是用hessian序列化測試得出的,注意的是hessian不要使用3.1.x或3.2.x版本,這兩個系列的版本性能極差,建議使用hessian 4.0.x版本。
5、93k –> 143k
在到達93k時,看到測試的結果中有不少請求的響應時間會超過10ms,于是用btrace一步一步跟蹤查找是什麼地方會出現這種現象,後來發現是由于之前在确認寫入os send buffer時采用的是await的方式,而這會導緻需要等待io線程來喚醒,增加了線程上下文切換以及io線程的負擔,但這個地方又不能不做處理,後來就改造成僅基于listener的方式進行處理,如寫入失敗會直接建立一個響應對象,這次改造後效果非常明顯,感興趣的同學可以看看McQueenRPC中: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也是有不少值得學習的地方的。
8、153k –> 160k
server接到消息的處理過程也修改成類似client的pipeline機制,同時将之前擷取協定處理器和序列化/反序列化的處理器的地方由map改成了數組,具體可以參考NettyServerHandler、ProtocolFactory和Codecs。
9、160k –> 163k
Grizzly的leader對grizzly部分的代碼做了很多的修改,結果創造了目前rpc benchmark的最高紀錄:163k。
10、163k –> 168k
@minzhou 對McQueenRPC的代碼進行了優化,将之前在Decoder中做的構造String對象的部分挪到了業務線程中,于是TPS也有了一定的上升,感謝。
上面就是到目前為止的所有優化動作,其中的很多我估計高手的話是不會犯錯的,我走的彎路多了些,總結來說rpc架構的優化動作為:
1、盡可能減少io線程的占用時間,把能做的事都挪到别的線程裡去做;
2、盡可能減少線程上下文切換;
3、盡可能使用高效的序列化/反序列化;