Dubbo——底層handler

核心Handler和線程模型

Dubbo中Handler(ChannelHandler)的5種狀態:

狀態 描述
connected Channel已經被創建
disconnected Channel已被斷開
sent 消息被髮送
received 消息被接收
caught 捕獲到異常

Dubbo針對每個特性都會實現對應的ChannelHandler:

Handler 作用
ExchangeHandlerAdapter 用於查找服務方法並調用
HeaderExchangeHandler 封裝處理Request/Response和Telnet調用能力
DecodeHandler 支持在Dubbo線程池中做解碼
ChannelHandlerDispatcher 封裝多Handler廣播調用
AllChannelHandler 支持Dubbo線程池調用業務方法
HeartbeatHandler 支持心跳處理
MultiMessageHandler 支持流中多消息報文批處理
ConnectionOrderedChannelHandler 單獨線程池處理RCP的連接和斷開
MessageOnlyChannelHandler 僅在線程池處理接收報文,其他事件在I/O線程處理
WrappedChannelHandler 基於內存key-value存儲封裝和共享線程池能力,比如記錄線程池等
NettyServerHandler 封裝Netty服務端事件,處理連接、斷開、讀取、寫入和異常等
NettyClientHandler 封裝Netty客戶端事件,處理連接、斷開、讀取、寫入和異常等

Dubbo中提供了大量的handler去承載特性和擴展,這些Handler最終會和底層通信框架做關聯,比如Netty等。一次完整的RPC調用貫穿了一系列的Handler,如果直接掛載到底層通信框架(Netty),因爲整個鏈路比較長,則需要出發大量鏈式查找和事件,不僅低效,而且浪費資源。

在Dubbo框架中,NettyServerHandler實現了ChannelInboundHandler、NettyClientHandler實現了ChannelOutboundHandler。Dubbo通過裝飾器模式層包裝handler,從而不需要將每個Handler都追加到Pipeline中。在NettyServer和NettyClient中最多有3個Handler,分別是編碼、解碼和NettyServerHandler或NettyClientHandler。

在DubboProtocol中通過內部類繼承自ExchangeHandlerAdapter,完成服務提供方Invoker實例的查找並進行服務的真實調用:

在這裏插入圖片描述

上面的Handler是實現是觸發業務方法調用的關鍵,在服務暴露時服務端已經按照特定的規則(端口、接口名、接口版本和接口分組)把實例Invoker存儲到HashMap中,客戶端調用過來時必須攜帶相同信息構造的key,找到對應Exporter然後調用。

在①中查找當前已經暴露的服務。在②中主要包含實例的Filter和真實業務對象,當觸發invoker#invoke方法時,就會執行具體的業務邏輯。在DubboProtocol中,跟蹤getInvoker調用,會發現在服務端唯一標識的服務是由4部分組成的:端口、接口名、接口版本和接口分組

在這裏插入圖片描述

在①中主要獲取協議暴露的端口,比如Dubbo協議默認的端口爲20880。在②中獲取客戶端傳遞過來的接口名稱(大部分場景都是接口名)。在③中主要根據服務端口、接口名、接口分組和接口版本構造唯一的key。在④中簡單地從HashMap中取出對應的Exporter並調用Invoker屬性值。

Dubbo爲了編織這些Handler,適應不同的場景,提供了一套可以定製的線程模型。爲了使概念更氫氣,我麼描述的I/O線程是指底層直接負責讀寫報文,比如Netty線程池。Dubbo中提供的線程池負責業務方法調用,稱爲業務線程。如果一些事件邏輯可以很快執行完成,比如做個標記而已,則可以直接在I/O線程中處理。如果事件處理耗時或阻塞,比如讀寫數據庫操作等,則應該將耗時或阻塞的任務轉到業務線程池執行。因爲I/O線程用於接收請求,如果I/O線程飽和,則不會接收新的請求。

Dubbo線程模型:
在這裏插入圖片描述

Dispatcher就是線程池派發器。這裏需要注意的是,Dispatcher真實的職責是創建具有線程派發能力的ChannelHandler,比如AllChannelHandler、MessageOnlyChannelHandler和ExecutionChannelHandler等,其本身並不具備線程派發能力。

Dispatcher術語Dubbo中的擴展點,這個擴展點用來動態產生Handler,以滿足不同額場景。目前Dubbo支持6種策略調用:

線程分發策略:

分發策略 分發實現 作用
all Alldispatcher 將左右I/O事件交給Dubbo線程池處理,Dubbo默認啓用
connection ConnectionOrderedDiispatcher 單獨線程池處理連接斷開事件,和Dubbo線程池分開
direct DirectDispatcher 所有方法調用和事件處理在I/O線程中,不推薦
execution ExecutionDispatcher 只在線程池處理接收請求,其他事件在I/O線程池中
message MessageOnlyChannelHandler 只在線程池處理請求和響應事件,其他事件在I/O線程池中
mockdispatcher MockDispatcher 默認返回Null

具體業務方需要根據使用場景的不同的策略。建議使用默認策略即可,如果在TCP連接中需要做安全加密或校驗,則可以使用ConnectionOrderDispatcher策略。如果引入新的線程池,則不可避免地導致額外的線程切換,用戶可以在Dubbo配置總指定dispatcher屬性讓具體策略生效。

Dubbo請求響應Handler

在Dubbo框架內部,所有方法都會被抽象成Request/Response,每次調用(一次會話)都會創建一個請求Request,如果是方法調用則會返回一個Response對象。
HeaderExchangeHandler用來處理這種場景,它主要負責以下4種事情:

  1. 更新發送和讀取請求時間戳
  2. 判斷請求格式或編解碼是否出錯,並響應客戶端失則的具體原因。
  3. 處理Request請求和Response正常響應。
  4. 支持Telnet調用

在這裏插入圖片描述

  • ①負責響應讀取時間並更新時間戳,在Dubbo心跳處理中會使用當前值並判斷是否超過空閒時間。
  • ②主要處理事件類型,目前主要處理readonly事件,用於Dubbo優雅停機。當註冊中心反註冊元數據時,因爲網絡原因,客戶端不能及時感知註冊中心事件,服務端會發送readonly報文告知下線。
  • ④處理收到的Response響應,告知業務調用方。
  • ⑤校驗客端不支持Telnet調用,因爲只有服務提供方暴露服務纔有意義。這裏有個小改進,因爲客戶端支持異步參數回調,但爲什麼這裏不能支持Telne調用呢?異步參數回調客戶端實際上也會暴露一個服務,因此針對這種場景Telnet應該是允許調用的。
  • ⑥觸發Telnet調用,並將字符串返回給Telnet客戶端。

處理請求和響應(HeaderExchangeHandler#handleRequest,handleResponse):

處理請求報文:
在這裏插入圖片描述
在這裏插入圖片描述

在處理請求時,因爲在編解碼層報錯會透傳到Handler,所以在①中首先會判斷是否因爲請求報文不正確,如果發生錯誤,則服務端會將具體異常包裝成字符串返回,如果直接使用異常對象,則可能造成無法序列化的錯誤。在②中觸發Dubbo協議方法調用,並且把方法調用返回值發送給客戶端。如果調用發生未知錯誤,則會通過③做容錯並返回。當發送請求時,會在DefaultFuture中保存請求對象並阻塞請求線程,在④中會喚醒阻塞線程並將Response中的結果通知調用方。

Dubbo心跳Handler

Dubbo默認客戶端和服務端都會發送心跳報文,用來保持TCP長連接狀態。在客戶端和服務端,Dubbo內部開啓一個線程循環掃描並檢測連接是否超時,在服務端如果發現超時則會主動關閉客戶端連接,在客戶端發現超時則會主動重新創建連接。默認心跳檢測時間是60秒,具體應用可以通過heartbeat進行配置。

Dubbo在服務端和客戶端都複用心跳實現代碼,抽象成HeartBeatTask任務進行處理:

在這裏插入圖片描述

  • ①遍歷所有的Channel,在服務端對應的是所有客戶端連接,在客戶端對應的是服務端連接
  • ②主要忽略已經關閉的Socket連接。
  • ③判斷當前TCP連接是否空閒,如果空閒就發送心跳報文。目前判斷是否空閒的,根據Channel是否有讀或寫來決定,比如1分鐘內沒有讀或寫就發送心跳報文。
  • ④處理客戶端超時重新建立TCP連接,目前的策略是檢查是否在3分鐘內(用戶可以設置)都沒有成功接收或發送報文。如果在服務端檢測則會通過⑤主動關閉遠程連接。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章