Hystrix & Resilience4j 與差錯控制

本文是筆者 2019 年暑期在 HULU 的實習項目,拖了一年以後才把這篇博客整理出來,留做記念吧。git

延遲與差錯控制問題

首先讓咱們熟悉一下問題的背景。一個現代的後端服務(下文稱之爲 App)可能會有多個依賴服務(下文簡稱爲依賴),依賴須要經過網絡請求,但這些依賴不必定老是能保持穩定。好比,推薦系統後臺可能須要從一個依賴中獲取讀取用戶配置,從另外一個依賴中讀取用戶歷史。這樣就存在一個問題:若是服務有很是多的依賴,那麼在任意時刻,都頗有可能有某個依賴出問題。github

依賴出現問題通常表現爲對一些請求會拋異常,若是沒有兜底邏輯,App 就不能處理這些請求了。另外,出問題的依賴延時會變長,這會致使該依賴的請求大量佔用線程池、TCP 鏈接等資源,直至耗盡資源。因而,若是 App 沒有差錯容忍能力,那麼本 App 的 downtime 可能會是全部的依賴的 downtime 之和,並且 App 跟隨上游依賴掛掉以後,還會引發下游服務跟隨掛掉,致使雪崩式崩潰,這是不可接受的。後端

image.png

爲了不 App 跟隨上游依賴掛掉,至少須要作到如下兩點:網絡

  • 有一個 fallback 邏輯,也即當上遊的依賴服務掛掉以後的兜底邏輯。通常而言,這應該是個簡單的本地邏輯,好比返回合理的空值。
  • 可以檢測服務的健康狀態,若是服務掛掉,儘快切換到 fallback 邏輯。同時,可以在服務恢復正常後及時切換回來。

Hystrix & Resilience4j 就是完成後一個要求的工具。它們能夠檢測依賴的健康狀態,並在依賴狀態不佳時及時切換到兜底邏輯,以及依賴恢復正常以後切換回來。模塊化

Hystrix 原理

在這裏偏原理的介紹一下 Hystrix. Hystrix 首先將依賴的調用抽象成函數(方法)調用,函數須要用戶本身繼承 Hystrix 提供的類來實現,在函數中可使用 HttpClient 等進行依賴的調用。函數調用有三種可能的結果:正常返回,拋出異常,超時。簡單起見,超時這裏也納入異常。Hystrix 的基本原理就是對於每一個依賴,分別統計其在一段時間內拋出異常的機率,異常機率太高時即認定依賴已經不健康。函數

Hystrix 斷定依賴出現問題以後,新的請求不會再調用依賴,而是會調用 Fallback 邏輯(兜底邏輯)。此時咱們稱爲短路狀態,依賴被短路。短路狀態下,Hystrix 每隔一段時間會嘗試調用一下依賴,在必定次數的嘗試成功以後,斷定依賴已經恢復,並取消短路狀態,不然會繼續短路。工具

經過短路掉不正常的依賴,Hystrix 一方面能夠下降本服務的壓力,防止大量的超時拉高本服務的響應時間,也防止佔用線程池等資源。同時也避免給依賴帶來更大的壓力。測試

Hystrix 詳細原理參考 官方 wiki.spa

Hystrix 配置探究

Hystrix 通常用來管理依賴服務,因此通常搭配 HttpClient 使用,如 Apache HttpClient. 這樣 Hystrix & Http Client 主要會有以下參數:線程

  • HttpClient 鏈接數目
  • HttpClient 超時
  • Hystrix 線程池大小
  • Hystrix 超時

個人實習任務就是經過一個 mock 系統對這些參數和咱們使用 Hystrix 的方式進行調優。Mock 系統包括一個虛擬的依賴,主要是能夠在運行時方便的調整其響應時間分佈和返回的 payload 大小;以及一個虛擬的 App,其調用依賴的方式與咱們的線上服務相同。有了 Mock 系統以後,咱們能夠再現依賴的不一樣狀態,而後不一樣參數下觀察調用依賴時的開銷與行爲等等。

經過前輩的經驗和個人實驗補充,咱們獲得瞭如下配置原則。

首先,須要經過實測獲得依賴的平均響應時間,u99 (upper99 時間,也即 99% 的響應耗時小於該時間) 等響應時間分佈參數,據此設置 HttpClient & Hystrix 的超時時間。Hystrix 的超時設置成略長於 HttpClient 便可,由於 Hystrix 的超時即便被觸發,Hystrix 受 Java 限制也沒法殺死進程,並不會釋放資源,所以超時應該由 HttpClient 控制。超時時間設置太短會致使大量超時觸發並拋出大量的異常,但若是設置的過長,則會在依賴響應延時變長時迅速消耗線程池等資源。好比,若是超時設置成 100ms, 在 1000 rps 流量下,一旦依賴掛掉,在超時釋放資源以前,這個依賴在 100ms 以內會消耗掉 100 個 TCP 鏈接和線程。超時合理的設置能夠參考服務的 u99 (upper99) 響應時間,比這個時間略長 1.5 倍左右。

線程池設置主要根據服務的平均響應時間和 RPS 肯定,根據排隊論,理論上最小值應該是 N=RPS×1s/λN=RPS×1s/λ, 其中 λλ 就是平均響應時間,1s1s 也即一秒鐘。固然,實際使用時要比這個值設置的大一些,好比 1.5 倍到 2 倍,平均響應時間也要根據服務壓力比較大時的平均響應時間指定。這個參數最好設置成 Hystrix 與 HttpClient 等同,特別不要設置成 Hystrix 線程池比 HttpClient 最大鏈接數目大,不然 Hystrix 的線程會在等待 HttpClient 資源時排隊會排隊,致使延時上升。

線程池耗盡後,Hystrix 對新來的請求也會直接調用 fallback 邏輯。

另外,特別須要注意的是,使用 Hystrix 時應該實現 Fallback 邏輯,不要留空,而且注意代碼邏輯中妥善處理異常。這是由於 Hystrix 在沒有 Fallback 邏輯時會向上級拋出異常,若是業務邏輯沒有處理好這些異常,可能會繼續上拋直到本服務的 Servlet 容器,如 Jetty. 雖然 Servlet 容器通常不會所以掛掉,但異常處理代價是高昂的,特別是若是代碼中隨手打印了調用棧,打印異常的調用棧是串行的,所以在 RPS 較高的服務中很容易把整個 Java 進程搞掛,而此時可能正是流量高峯,因而雪上加霜。

對此,我的的思考是異常本不該該大量拋出,處理異常的代價整體而言是要比正常邏輯代價高不少的。調用依賴失敗在網絡環境下實際上是比較正常的一種狀況,而且可能大量發生,用異常表示依賴調用失敗是略顯不合理。通常異常處理代碼不用過於在乎效率,由於異常是罕見的。但惟獨這種情境下必須考慮效率,由於高 RPS 下異常可能頻率很是高。特別注意,Hystrix 短路時若是沒有 Fallback 邏輯,就會大量拋出異常。短路邏輯顧名思義,其處理代價應該小於正常邏輯,但若是 Hystrix 拋出異常而且沒有儘快被業務邏輯處理掉,則短路狀態下處理代價就有可能很高,致使服務掛掉。

這些結論大部分都是以前的前輩調研和總結的,個人 Mock 平臺上的實驗主要發現了 HttpClient & Hystrix 的參數互相之間的優先關係,以及異常處理的問題。咱們後來覆盤時有一些總結:

  1. 這個 Mock 系統的服務和其依賴都是假的,形象稱之爲「空對空」,系統行爲過於理想,沒法再現實際業務場景下服務的 CPU 負載等複雜行爲,致使其參考價值不高。
  2. 有條件的場合,最好考慮把真實的線上服務在一個受控環境中啓動,並經過流量重放等方式進行測試。可是,我當時須要測試的服務過於複雜。它是推薦系統的後臺,依賴過多,把這些依賴所有 Mock 過於困難。
  3. 整體而言,復現網絡環境、網絡相關的問題是很困難的。或許,增強監控,出問題時及時登陸到出問題的機器節點上觀察是更合適的選擇。

Resilience4j 簡介

Hystrix 已經再也不活躍開發。其使用要藉助繼承機制,略顯繁瑣。Hystrix 自己也有必定的開銷。而 Resilience4j 是其官方推薦的後續。二者的功能類似,但 Resilience4j 默認使用 Java8 提供的 lambda 表達式實現,更爲簡潔優雅,同時也更爲輕量、模塊化。據筆者測試,其開銷也要遠遠小於 Hystrix. 具體介紹參考其官方 github.

相關文章
相關標籤/搜索