淺談如何使用Netty開發高效能的RPC伺服器
如何使用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自帶的連線池