本文是源碼分析 Sentinel 系列的第十三篇,已經非常詳細的介紹了 Sentinel 的架構體系、滑動窗口、調用鏈上下文、限流、熔斷的實現原理,相信各位讀者朋友們對Sentinel有一個較爲體系化的認知了,這個時候是該開始如何在生產環境進行運用了。
本文將以 Dubbo 服務調用爲案例剖析場景,嘗試對官方提供的 Dubbo 適配器做一個研究學習並對此做出自己的評價,拋出我的觀點,期待與大家共同探討,交流。
一個 Dubbo RPC 的簡易調用過程如下圖所示:
消費者會維護一個服務提供者列表,然後再發一起一個服務調用的時候會首先根據負載均衡算法從中選擇一個服務提供者,然後發起 RPC 調用,在請求真實發送之前會依次通過客戶端設置的過濾器鏈(Filter),然後經過網絡傳輸到到達服務提供者,並執行完服務提供者端的 Filter,最後進入到業務邏輯執行並返回結果。
Sentinel 與 Dubbo 的整合就是利用了 Dubbo 的 Filter 機制,爲 Dubbo 提供對應的 過濾器,無縫實現限流、熔斷等功能,做到業務無感知,即業務代碼無需使用 Sentinel 相關的 API。
接下來請大家帶着在 Dubbo 中如何使用限流、熔斷方面來看官方給出的解決方案。
思考題:在看接下來的內容之前,建議大家稍作停頓,思考一下,在服務調用模型中,限流、熔斷通常在哪個端做比較合適。
1、從消費端來看限流與熔斷
從消費端的視角,雖然提供了服務端的負載均衡,但從客戶端不管是向192.168.1.3還是向192.168.1.4發送RPC調用,都會經過同一個 Sentinel Dubbo Filter。這個看似簡單明瞭,但這是我們接下來思考的最基本最核心的點。
我們先來看看官方提供的 Dubbo 適配器的核心實現:
SentinelDubboConsumerFilter#invoke
消費端這邊使用到了兩個資源名稱,一個是接口級別,例如 com.demo.service.UserService,另外一是方法級別,例如 com.demo.servcie.UserServce#findUser(Ljava.lang.String)。
定義了兩個資源後,Sentinel 會使用滑動窗口機制,爲上述兩個資源收集實時的調用信息,爲後續的限流、熔斷提供數據依據。
限流規則是依附於具體某一個項目的,例如如下圖所示:
限流、熔斷都是根據資源級別,如果需要對消費端的調用進行限流的話,就需要爲這兩個資源配置對應的限流規則,如果不配置則默認通過,表示不限流。
1.1 服務調用端(消費方)是否需要配置限流規則
在 dubbo 的服務調用場景中,在消費端設置限流的規則的話,這個調用鏈是針對整個集羣所有服務提供者的,例如當前集羣中包含3個服務提供者,每個服務提供者用於1000tps的服務能力,那消費端的限流,應該配置的限流TPS應該爲3000tps,如果設置爲1000tps,則無法完整的利用服務端的能力,基於這樣的情況,通常消費端無需配置限流規則。
那是不是說消費端就沒必要配置限流規則呢?其實也不是,有如下這個場景,例如調用第三方外部的計費類服務接口,對方通常爲特定的賬戶等級設置對應的TPS上限,如果超過該調用頻率就會拋出錯誤,這種情況還是需要設置限流規則,確保消費端以不超過要求進行調用,避免業務異常。
1.2 服務調用端(消費方)是否需要配置熔斷
引入熔斷的目的是避免服務端單節點響應慢而導致這個服務不穩定,例如示例中有3個服務提供者,如果192.168.1.3的服務提供者由於觸發了BUG導致響應時間大大增加,導致發往該服務提供者的請求大概率超時,在這樣的情況下希望在接下來某段時間內消費方發往這這個服務提供者的請求快速熔斷降級,返回錯誤,由客戶端重試其他服務提供者。其實現效果如下:
當前的 Sentinel 默認是否能滿足上述的需求呢?
我們以 Sentinel 基於異常比例熔斷策略來進行闡述,如果資源的調用異常比例超過一定值是會觸發降級熔斷,拋出 DegradeException 異常。
由於總共只有三個服務提供者,其中發往192.168.1.3的請求大概率會由於超時失敗,則異常比例會超過設置的熔斷降級規則,會觸發降級,造成的效果是整個服務調用都會發送熔斷降級,即調用192.168.1.4,5兩個請求都不會被熔斷,造成整個服務調用不可用,與期望不一致。即還是會出現一個節點的不穩定而導致整個服務不穩定的情況。
其造成的根本原因是因爲其資源的定義並沒有包含服務提供者的信息,改進的初步方案:
- 在過濾器中再定義一個資源,加上服務提供的IP與端口號,例如 SphU.entry(“com.d.s.UserService@ip:port”),對單個服務提供者進行單獨收集調用信息,並且需要提供一可配置的項,用來標記該類型的資源在做熔斷判斷可使用某一個資源的配置,例如配置爲 com.d.s.UserService,表示用這個配置規則來判斷熔斷。
- 在熔斷規則判斷的時候,該資源使用被引用資源的熔斷規則進行判斷。
最後來解答一下,熔斷規則通常只需要配置在調用方即可。
2、從服務來看限流與熔斷
由於服務端看限流與熔斷就比較簡單,因爲服務端與客戶端存在一個非常大的區別是客戶端存在負載均衡機制,一個消費端對於同一資源來說,會調用多個服務提供者,而服務提供者對於一個資源來就是其自身,故限流規則,熔斷規則都是針對個體,其複雜度降低。
爲了知識體系的完備性,我們來看一下 Sentinel Dubbo 在服務端的適配器的實現。
SentinelDubboProviderFilter#invoke
這裏有二個關鍵點:
- 使用了 ContextUtil 的 entry 方法,定義本次調用的上下文環境名稱爲:resourceName,默認爲接口名與方法名、參數列表,例如 com.d.s.UserServce#findUser(Ljava.lang.String),源頭爲消費端的應用名稱。
- 定義兩個資源,這裏與消費端相同,就不做重複解讀。
關於這個 ContextUtil 的 entry 方法非常關鍵,因爲 Sentinel 中數據節點的統計是以 ContextName 爲維度的。
例如想對一個應用所有的操作 redis 操作統一設置爲一個資源名,redisOpsResource,即想控制該應用整體的 redis 操作 tps,其場景如下:
例如初衷設計爲 opsReisTotal 的整個 tps 爲 500,例如從UserService#findser鏈路的訪問 redis tps 爲 400,而從 Order#createOrder 鏈路訪問 redis tps 爲 400,此時 redis 的整體 tps 已經達到了 800 tps,但你會發現並不會觸發限流,因爲對資源 RredisOpResource 的調用信息統計是以 context name 爲維度的,不同的 context name 互不影響,從而無法做到全局控制。
3、總結
本文結合 Sentinel 官方對於 Dubbo 提供的適配器並加以理解,提出瞭如下觀點,歡迎大家留言探討,相互交流,共同進步。
- 限流規則根據不同的使用場景可以在客戶端、服務端配置。
- 熔斷規則通常在服務調用方配置即可。
- Sentinel 目前的熔斷還實現的比較簡單,無法解決集羣中因爲某一個節點的訪問慢而觸發熔斷,並使服務穩定的能力。
- Sentienl 的實時統計是以調用上下文(Context Name),即 ContextUtil.entry 方法定義的上下文爲維度的,這點非常關鍵,不然容易踩坑。
好了,本文就介紹到這裏了,您的點贊是對我持續輸出高質量文章最大的鼓勵。
歡迎加筆者微信號(dingwpmz),加羣探討,筆者優質專欄目錄:
1、源碼分析RocketMQ專欄(40篇+)
2、源碼分析Sentinel專欄(12篇+)
3、源碼分析Dubbo專欄(28篇+)
4、源碼分析Mybatis專欄
5、源碼分析Netty專欄(18篇+)
6、源碼分析JUC專欄
7、源碼分析Elasticjob專欄
8、Elasticsearch專欄(20篇+)
9、源碼分析MyCat專欄