1. 程式人生 > >淺談如何使用Netty開發高性能的RPC服務器

淺談如何使用Netty開發高性能的RPC服務器

接口實現 消息 size cal 對象 length .html 過程 設計

如何使用Netty進行RPC服務器的開發,技術原理涉及如下:
1、定義RPC請求消息、應答消息結構,裏面要包括RPC的接口定義模塊,如遠程調用的類名、方法名、參數結構、參數值等信息。

2、服務端初始化的時候通過容器加載RPC接口定義和RPC接口實現類對象的映射關系,然後等待客戶端發起調用請求。

3、客戶端發起的RPC消息通過網絡,以字節流的方式發送給RPC服務端,RPC服務端接收到字節流的請求之後,去對應的容器裏面,查找客戶端接口映射的具體實現對象。

4、RPC服務端找到實現對象的參數信息,通過反射機制創建該對象的實例,並返回調用處理結果,最後封裝成RPC應答消息通知到客戶端。

5、客戶端通過網絡,收到字節流形式的RPC應答消息,進行拆包、解析之後,顯示遠程調用結果。

上面說的是很簡單,但是實現的時候,我們還要考慮如下的問題:
1、RPC服務器的傳輸層是基於TCP協議的,出現粘包咋辦?這樣客戶端的請求,服務端不是會解析失敗?好在Netty裏面已經提供了解決TCP粘包問題的解碼器:LengthFieldBasedFrameDecoder,可以靠它輕松搞定TCP粘包問題。

2、Netty服務端的線程模型是單線程、多線程(一個線程負責客戶端連接,連接成功之後,丟給後端IO的線程池處理)、還是主從模式(客戶端連接、後端IO處理都是基於線程池的實現)。當然在這裏,我出於性能考慮,使用了Netty主從線程池模型。

3、Netty的IO處理線程池,如果遇到非常耗時的業務,出現阻塞了咋辦?這樣不是很容易把後端的NIO線程給掛死、阻塞?對於復雜的後端業務,分派到專門的業務線程池裏面,進行異步回調處理。

4、RPC消息的傳輸是通過字節流在NIO的通道(Channel)之間傳輸,那具體如何實現呢?本文,是通過基於Java原生對象序列化機制的編碼、解碼器(ObjectEncoder、ObjectDecoder)進行實現的。當然出於性能考慮,這個可能不是最優的方案。更優的方案是把消息的編碼、解碼器,搞成可以配置實現的。具體比如可以通過:protobuf、JBoss Marshalling方式進行解碼和編碼,以提高網絡消息的傳輸效率。

5、RPC服務器要考慮多線程、高並發的使用場景,所以線程安全是必須的。此外盡量不要使用synchronized進行加鎖,改用輕量級的ReentrantLock方式進行代碼塊的條件加鎖。比如本文中的RPC消息處理回調,就有這方面的使用。

6、RPC服務端的服務接口對象和服務接口實現對象要能輕易的配置,輕松進行加載、卸載。在這裏,本文是通過Spring容器進行統一的對象管理。

綜上所述,本文設計的RPC服務器調用的流程圖如下所示:
技術分享圖片

客戶端並發發起RPC調用請求,然後RPC服務端使用Netty連接器,分派出N個NIO連接線程,這個時候Netty連接器的任務結束。然後NIO連接線程是統一放到Netty NIO處理線程池進行管理,這個線程池裏面會對具體的RPC請求連接進行消息編碼、消息解碼、消息處理等等一系列操作。最後進行消息處理(Handler)的時候,處於性能考慮,這裏的設計是,直接把復雜的消息處理過程,丟給專門的RPC業務處理線程池集中處理,然後Handler對應的NIO線程就立即返回、不會阻塞。這個時候RPC調用結束,客戶端會異步等待服務端消息的處理結果,本文是通過消息回調機制實現(MessageCallBack)。

再來說一說Netty對於RPC消息的解碼、編碼、處理對應的模塊和流程,具體如下圖所示:
技術分享圖片

從上圖可以看出客戶端、服務端對RPC消息編碼、解碼、處理調用的模塊以及調用順序了。Netty就是把這樣一個一個的處理器串在一起,形成一個責任鏈,統一進行調用。

開發Netty RPC需要註意的點:
Netty客戶端異步獲取相應結果到主線程
Netty做長鏈接的時候註意如下:
1.需要心跳檢測機制,保證鏈接的穩定。
2.考慮重連,容易丟包。
3.采用連接池,netty自帶的連接池

本文整理自此文章。

淺談如何使用Netty開發高性能的RPC服務器