天天看點

軟體架構場景之—— 熔斷:如何預防一個服務故障崩掉整個系統?業務場景技術選型Hystrix 的設計思路注意事項

業務場景

在一個新零售架構系統中,有一個通用使用者服務(很多頁面都會使用),它包含兩個接口,第一個接口是使用者狀态接口,包含使用者車輛所在位置,并且在使用者資訊展示頁面都會使用到,比如客服系統中的使用者資訊頁面。第二個接口是需要我們傳回使用者一個可操作的權限清單,它包含一個通用權限,也包含使用者定制權限,而且每次使用者打開 App 時都會使用它

第一個接口會遇到的問題:請求慢

使用者狀态的接口、服務間的調用關系如下圖所示

軟體架構場景之—— 熔斷:如何預防一個服務故障崩掉整個系統?業務場景技術選型Hystrix 的設計思路注意事項

在 Basic Data Service 中,有個接口 /currentCarLocation 需要調用第三方系統的資料,但第三方響應速度很慢且有時還會抽風,導緻響應時間更長,接口經常出現逾時報錯

有一次,使用者回報 App 整體運作速度慢到無法接受的程度。通過背景監控,我們檢視了幾個 Thread Dump ,發現 User API 與 Basic Data Service 的線程請求數爆滿,且所有的線程都在通路第三方接口。因為連接配接數滿了,其他頁面便不再受理 User API 的請求,最終導緻 App 整體出現了卡頓,針對這個問題做過相關處理,考慮響應時間長,就把逾時的時間設定很長,雖然逾時報錯機率小了,其他頁面也保持正常,但是會導緻客服背景檢視使用者資訊的頁面響應時間長

第二個接口會遇到的問題:流量洪峰緩存逾時

使用者權限的接口、服務間的調用關系與上面類似,如下圖所示

軟體架構場景之—— 熔斷:如何預防一個服務故障崩掉整個系統?業務場景技術選型Hystrix 的設計思路注意事項

關于服務間的關系調用具體流程分為以下三個步驟

  • App 通路 User API;
  • User API 通路基礎資料服務的接口 /commonAccesses;
  • 基礎資料服務提供一個通用權限清單。因為權限清單對所有使用者都一樣,是以我們把它放在了 Redis 中,如果通用權限在 Redis 中找不到,我們再去資料庫中查找

在服務間的關系調用流程中,曾經遇到過的一些問題

因為曆史代碼的原因,在流量高峰時 Redis 中的通用權限清單逾時了,那一瞬間所有的線程都需要去資料庫中讀取資料,導緻 DB 中的 CPU 立馬飙到了 100%,DB 挂後,緊接着 Basic Data Service 也挂了,因所有的線程堵塞了,擷取不到資料庫連接配接,導緻 Basic Data Service 無法接受新的請求。而 User API 因調用了 Basic Data Service 的線程出現了堵塞,以至于 User API 服務的所有線程也出現堵塞,即 User API 也挂了,導緻 App 上的所有操作都不能使用,是以出大事了

技術選型

為了解決以上兩個問題,需要引入一個技術,且它還得滿足以下兩個條件

(1)線程隔離

針對第一個問題,希望的處理方式是這樣,比如 User API 中每個服務配置的最大連接配接數是 1000,每次 API 調用 BasicDataService 的 /currentCarLocation 的速度就會很慢

是以,我們希望控制 /currentCarLocation 的調用請求數,保證不超過 50 條,以此保證至少還有 950 條的連接配接可用來處理正常請求。如果 /currentCarLocation 的調用請求數超過 50 條,我們就設計一些備用邏輯進行處理,比如在界面上給使用者進行提示

(2)熔斷

針對第二個問題,由于 DB 沒有死鎖,流量洪峰緩存逾時單純是因為壓力太大,此時我們可以使用 Basic Data Service 暫緩一點兒時間,讓它不接受新的請求,這樣 Redis 的資料會被補上,資料庫的連接配接也會降下來,我們的服務也就沒事了

是以,希望這個技術能實作以下兩點需求

  • 發現近期某個接口的請求老出異常,先别通路接口的服務;
  • 發現某個接口的請求老逾時,先判斷接口的服務是否不堪重負,如果不堪重負,先别通路它

Hystrix 的設計思路

Spring Cloud Hystrix 的設計思想是事前配置熔斷機制,也就是說,要事先預見流量是什麼情況?系統負載能力如何?然後預先配置好熔斷的機制。但這種操作的缺點是,一旦實際流量或系統狀況與預測的不一樣,那麼預先配置好的機制就達不到預期的效果,是以,開源 Hystrix 的公司 Netflix 想使用一個動态适應更靈活的熔斷機制。不過 2018 年後官方已不再開發新功能,轉向開發 Resilience4j 了,對于原有功能隻做簡單維護

Hystrix 為什麼能滿足我們的需求?

  • (1)線程隔離機制

在 Hystrix 機制中,目前服務與其他接口存在強依賴關系,且每個依賴都有一個隔離的線程池

比如下面這張架構圖,目前服務調用接口 A 時,并發線程的最大個數是 10,調用接口 M 時,并發線程的最大個數是 5

軟體架構場景之—— 熔斷:如何預防一個服務故障崩掉整個系統?業務場景技術選型Hystrix 的設計思路注意事項

一般來說,目前服務依賴的一個接口響應慢時,目前運作的線程會一直處于未釋放狀态,最終把所有的連接配接線程卷入慢接口中。為此,在隔離線程的過程中,Hystrix 的做法是每個依賴接口(也可以配置成幾個接口共用)維護一個線程池,然後通過線程池的大小、排隊數等隔離每個服務對依賴接口的調用,這樣就不會出現前面的問題

當然,在 Hystrix 機制中,我們除了使用線程池來隔離線程,還可以使用信号量(計數器)

比如還是調用接口 A,因并發線程的最大個數是 10,在信号量隔離的機制中,Hystix 并不使用 1 個 size 為 10 的線程池來隔離,而是使用一個信号 semaphoresA,每當調用接口 A 時 semaphoresA++,A 調用完後 semaphoresA--,semaphoresA 一旦超過 10,不再調用

因為我們在使用線程池時經常需要切換線程,資源損耗較大,而信号量的優點恰巧就是切換快,大大解決了我們的煩惱。不過它也有一個缺點,即接口一旦開始調用就無法中斷。因為調用依賴的線程是目前請求的主線程,不像線程隔離,調用依賴的是另外 1 個線程,目前請求的主線程可以根據逾時時間把它中斷

  • (2)熔斷機制

1. 在哪種條件下會觸發熔斷?

熔斷判斷規則是某段時間内調用失敗數超過特定的數量或比率時,就會觸發熔斷。那這個資料是如何統計出來的呢?

在 Hystrix 機制中,我們會配置一個不斷滾動的統計時間視窗 metrics.rollingStats.timeInMilliseconds,在每個統計時間視窗中,當調用接口的總數量達到 circuitBreakerRequestVolumeThreshold,且接口調用逾時或異常的調用次數與總調用次數的占比超過 circuitBreakerErrorThresholdPercentage,此時就會觸發熔斷

2. 熔斷了會怎麼樣?

如果熔斷被觸發了,在 circuitBreakerSleepWindowInMilliseconds 的時間内,我們便不再對外調用接口,而是直接調用本地的一個降級方法,如下代碼所示

@HystrixCommand(fallbackMethod = "getCurrentCarLocationFallback")
           

3. 熔斷後怎麼恢複?

circuitBreakerSleepWindowInMilliseconds 到時間後,Hystrix 首先會放開對接口的限制(斷路器狀态 HALF-OPEN),然後嘗試使用 1 個請求去調用接口,如果調用成功,則恢複正常(斷路器狀态 CLOSED),如果調用失敗或出現逾時等待,就需要再重新等待circuitBreakerSleepWindowInMilliseconds 的時間,之後再重試

  • (3)滾動(滑動)時間視窗

比如我們把滑動事件的時間視窗設定為 10 秒,并不是說我們需要在 1 分 10 秒時統計一次,1 分 20 秒時再統計一次,而是我們需要統計每一個 10 秒的時間視窗

是以,我們還需要設定一個 metrics.rollingStats.numBuckets,假設我們設定 metrics.rollingStats.numBuckets 為 10,表示時間視窗劃分為 10 小份,每 1 份是 1 秒。然後我們就會 1 分 0 秒 - 1 分 10 秒統計 1 次、1 分 1 秒 - 1 分 11 秒統計 1 次、1 分 2 秒 - 1 分 12 秒統計 1 次……(即每隔 1 秒都有 1 個時間視窗)

下圖就是 1 個 10 秒時間視窗,我們把它分成了 10 個桶

軟體架構場景之—— 熔斷:如何預防一個服務故障崩掉整個系統?業務場景技術選型Hystrix 的設計思路注意事項

每個桶中 Hystrix 首先會統計調用請求的成功數、失敗數、逾時數和拒絕數,再單獨統計每 10 個桶的資料(到了第 11 個桶時就是統計第 2 個桶到第 11 個桶的合計資料)

  • (4)Hystrix 調用接口的請求處理流程

這是 1 次調用成功的流程,如下圖所示

軟體架構場景之—— 熔斷:如何預防一個服務故障崩掉整個系統?業務場景技術選型Hystrix 的設計思路注意事項

這是 1 次調用失敗的流程,如下圖所示

軟體架構場景之—— 熔斷:如何預防一個服務故障崩掉整個系統?業務場景技術選型Hystrix 的設計思路注意事項

Hystrix 調用接口的請求處理流程結束後,我們就可以直接啟用它了。在 Spring Cloud 中啟用 Hystrix 的操作也比較簡單,不過多贅述了。最後,關于 Hystrix,它還有包含 request caching(請求緩存) 和 request collapsing(請求合并)這兩個功能,它們與熔斷關系不大

注意事項

把 Hystrix 的設計思路搞清楚後,使用它之前還需要考慮幾個注意事項

(1)資料一緻性

假設服務 A 更新了資料庫,在調用服務 B 時直接降級了,那服務 A 的資料庫更新是否需要復原?

再舉一個複雜點的例子,比如服務 A 調用了服務 B,服務 B 調用了服務 C,我們在服務 A 中成功更新了資料庫并成功調用了服務 B,而服務 B 調用服務 C 時降級了,直接調用了 Fallback 方法,此時就會出現兩個問題:服務 B 向服務 A 傳回成功還是失敗?服務 A 的資料庫更新需不需要復原?

以上兩個例子展現的就是資料一緻性的問題。關于這個問題并沒有一個固定的設計标準,隻是在不同需求下使用熔斷時,結合具體的情況設計即可

(2)逾時降級

比如服務 A 調用服務 B 時,因為調用過程中 B 沒有在設定的時間内傳回結果,被判斷逾時了,是以服務 A 又調用了降級的方法,其實服務 B 在接收到服務 A 的請求後,已經在執行工作并且沒有中斷。等服務 B 處理成功後,還是會傳回處理成功的結果給服務 A。可是服務 A 又已經走了降級的方法,而服務 B 又已經把工作做完了,此時就會導緻服務 B 中的資料出現異常

(3)使用者體驗

請求觸發熔斷後,一般會出現以下三種情況

  • 使用者讀資料的請求時遇到有些接口降級了,導緻部分資料擷取不到,這時我們需要在界面上給使用者提供一定的提示,或讓使用者發現不了這部分資料的缺失;
  • 使用者寫資料的請求時,熔斷觸發降級後,有些寫操作就會改為異步,後續處理對使用者沒有任何影響,但我們要根據實際情況判斷是否需要給使用者提供一定的提示;
  • 使用者寫資料的請求時,熔斷觸發降級後,操作可能就復原掉,此時我們必須提示讓使用者重新操作

是以,服務調用觸發了熔斷降級時,需要把這些情況都考慮到以此保證使用者體驗,而不是僅僅保證伺服器不當機

(4)熔斷監控

熔斷使用上線後,其實我們隻是完成了熔斷設計的第一步。因為 Hystrix 是一個事前配置的熔斷架構,關于熔斷配置到底對不對,效果好不好,隻有實際使用後才知道

實際使用時,還需要盯着 Hystrix 的監控面闆檢視各個服務的熔斷資料,然後根據實際情況再做調整。隻有這樣,我們才能在真正使用熔斷時将伺服器的異常損失降到最低

繼續閱讀