1. 程式人生 > >Spring Cloud(四):服務容錯保護 Hystrix【Finchley 版】

Spring Cloud(四):服務容錯保護 Hystrix【Finchley 版】

回調 alt 差異 ner 隔離 簡化 保護 不可用 無法

Spring Cloud(四):服務容錯保護 Hystrix【Finchley 版】

分布式系統中經常會出現某個基礎服務不可用造成整個系統不可用的情況,這種現象被稱為服務雪崩效應。為了應對服務雪崩,一種常見的做法是手動服務降級。而 Hystrix 的出現,給我們提供了另一種選擇。

技術分享圖片

Hystrix [h?st’r?ks] 的中文含義是 “豪豬”,豪豬周身長滿了刺,能保護自己不受天敵的傷害,代表了一種防禦機制,這與 Hystrix 本身的功能不謀而合,因此 Netflix 團隊將該框架命名為 Hystrix,並使用了對應的卡通形象做作為 logo。

服務雪崩效應

定義

服務雪崩效應是一種因 服務提供者 的不可用導致 服務調用者 的不可用,並將不可用 逐漸放大 的過程。如果所示:
技術分享圖片
上圖中,A 為服務提供者,B 為 A 的服務調用者,C 和 D 是 B 的服務調用者。當 A 的不可用,引起 B 的不可用,並將不可用逐漸放大 C 和 D 時,服務雪崩就形成了。

形成的原因

我把服務雪崩的參與者簡化為 服務提供者服務調用者,並將服務雪崩產生的過程分為以下三個階段來分析形成的原因:

  1. 服務提供者不可用
  2. 重試加大流量
  3. 服務調用者不可用

技術分享圖片

服務雪崩的每個階段都可能由不同的原因造成,比如造成 服務不可用 的原因有:

  • 硬件故障
  • 程序 Bug
  • 緩存擊穿
  • 用戶大量請求

硬件故障可能為硬件損壞造成的服務器主機宕機,網絡硬件故障造成的服務提供者的不可訪問。
緩存擊穿一般發生在緩存應用重啟,所有緩存被清空時,以及短時間內大量緩存失效時。大量的緩存不命中,使請求直擊後端,造成服務提供者超負荷運行,引起服務不可用。
在秒殺和大促開始前,如果準備不充分,用戶發起大量請求也會造成服務提供者的不可用。

而形成 重試加大流量 的原因有:

  • 用戶重試
  • 代碼邏輯重試

在服務提供者不可用後,用戶由於忍受不了界面上長時間的等待,而不斷刷新頁面甚至提交表單。
服務調用端的會存在大量服務異常後的重試邏輯。
這些重試都會進一步加大請求流量。

最後, 服務調用者不可用

產生的主要原因是:

  • 同步等待造成的資源耗盡

當服務調用者使用 同步調用 時,會產生大量的等待線程占用系統資源。一旦線程資源被耗盡,服務調用者提供的服務也將處於不可用狀態,於是服務雪崩效應產生了。

應對策略

針對造成服務雪崩的不同原因,可以使用不同的應對策略:

  1. 流量控制
  2. 改進緩存模式
  3. 服務自動擴容
  4. 服務調用者降級服務

流量控制 的具體措施包括:

  • 網關限流
  • 用戶交互限流
  • 關閉重試

因為 Nginx 的高性能,目前一線互聯網公司大量采用 Nginx+Lua 的網關進行流量控制,由此而來的 OpenResty 也越來越熱門。

用戶交互限流的具體措施有: 1. 采用加載動畫,提高用戶的忍耐等待時間。2. 提交按鈕添加強制等待時間機制。

改進緩存模式 的措施包括:

  • 緩存預加載
  • 同步改為異步刷新

服務自動擴容 的措施主要有:

  • AWS 的 auto scaling

服務調用者降級服務 的措施包括:

  • 資源隔離
  • 對依賴服務進行分類
  • 不可用服務的調用快速失敗

資源隔離主要是對調用服務的線程池進行隔離。

我們根據具體業務,將依賴服務分為: 強依賴和若依賴。強依賴服務不可用會導致當前業務中止,而弱依賴服務的不可用不會導致當前業務的中止。

不可用服務的調用快速失敗一般通過 超時機制, 熔斷器 和熔斷後的 降級方法 來實現。

使用 Hystrix 預防服務雪崩

服務降級(Fallback)

對於查詢操作,我們可以實現一個 fallback 方法,當請求後端服務出現異常的時候,可以使用 fallback 方法返回的值。fallback 方法的返回值一般是設置的默認值或者來自緩存。

資源隔離

貨船為了進行防止漏水和火災的擴散,會將貨倉分隔為多個,如下圖所示:

技術分享圖片

這種資源隔離減少風險的方式被稱為: Bulkheads(艙壁隔離模式)。
Hystrix 將同樣的模式運用到了服務調用者上。

在 Hystrix 中,主要通過線程池來實現資源隔離。通常在使用的時候我們會根據調用的遠程服務劃分出多個線程池。例如調用產品服務的 Command 放入 A 線程池,調用賬戶服務的 Command 放入 B 線程池。這樣做的主要優點是運行環境被隔離開了。這樣就算調用服務的代碼存在 bug 或者由於其他原因導致自己所在線程池被耗盡時,不會對系統的其他服務造成影響。
通過對依賴服務的線程池隔離實現,可以帶來如下優勢:

  • 應用自身得到完全的保護,不會受不可控的依賴服務影響。即便給依賴服務分配的線程池被填滿,也不會影響應用自身的額其余部分。
  • 可以有效的降低接入新服務的風險。如果新服務接入後運行不穩定或存在問題,完全不會影響到應用其他的請求。
  • 當依賴的服務從失效恢復正常後,它的線程池會被清理並且能夠馬上恢復健康的服務,相比之下容器級別的清理恢復速度要慢得多。
  • 當依賴的服務出現配置錯誤的時候,線程池會快速的反應出此問題(通過失敗次數、延遲、超時、拒絕等指標的增加情況)。同時,我們可以在不影響應用功能的情況下通過實時的動態屬性刷新(後續會通過 Spring Cloud Config 與 Spring Cloud Bus 的聯合使用來介紹)來處理它。
  • 當依賴的服務因實現機制調整等原因造成其性能出現很大變化的時候,此時線程池的監控指標信息會反映出這樣的變化。同時,我們也可以通過實時動態刷新自身應用對依賴服務的閾值進行調整以適應依賴方的改變。
  • 除了上面通過線程池隔離服務發揮的優點之外,每個專有線程池都提供了內置的並發實現,可以利用它為同步的依賴服務構建異步的訪問。

總之,通過對依賴服務實現線程池隔離,讓我們的應用更加健壯,不會因為個別依賴服務出現問題而引起非相關服務的異常。同時,也使得我們的應用變得更加靈活,可以在不停止服務的情況下,配合動態配置刷新實現性能配置上的調整。

雖然線程池隔離的方案帶了如此多的好處,但是很多使用者可能會擔心為每一個依賴服務都分配一個線程池是否會過多地增加系統的負載和開銷。對於這一點,使用者不用過於擔心,因為這些顧慮也是大部分工程師們會考慮到的,Netflix 在設計 Hystrix 的時候,認為線程池上的開銷相對於隔離所帶來的好處是無法比擬的。同時,Netflix 也針對線程池的開銷做了相關的測試,以證明和打消 Hystrix 實現對性能影響的顧慮。

下圖是 Netflix Hystrix 官方提供的一個 Hystrix 命令的性能監控,該命令以每秒 60 個請求的速度(QPS)向一個單服務實例進行訪問,該服務實例每秒運行的線程數峰值為 350 個。

技術分享圖片

從圖中的統計我們可以看到,使用線程池隔離與不使用線程池隔離的耗時差異如下表所示:

比較情況未使用線程池隔離使用了線程池隔離耗時差距
中位數 2ms 2ms 2ms
90 百分位 5ms 8ms 3ms
99 百分位 28ms 37ms 9ms

在 99% 的情況下,使用線程池隔離的延遲有 9ms,對於大多數需求來說這樣的消耗是微乎其微的,更何況為系統在穩定性和靈活性上所帶來的巨大提升。雖然對於大部分的請求我們可以忽略線程池的額外開銷,而對於小部分延遲本身就非常小的請求(可能只需要 1ms),那麽 9ms 的延遲開銷還是非常昂貴的。實際上 Hystrix 也為此設計了另外的一個解決方案:信號量(Semaphores)。

Hystrix 中除了使用線程池之外,還可以使用信號量來控制單個依賴服務的並發度,信號量的開銷要遠比線程池的開銷小得多,但是它不能設置超時和實現異步訪問。所以,只有在依賴服務是足夠可靠的情況下才使用信號量。在 HystrixCommand 和 HystrixObservableCommand 中 2 處支持信號量的使用:

  • 命令執行:如果隔離策略參數 execution.isolation.strategy 設置為 SEMAPHORE,Hystrix 會使用信號量替代線程池來控制依賴服務的並發控制。
  • 降級邏輯:當 Hystrix 嘗試降級邏輯時候,它會在調用線程中使用信號量。

信號量的默認值為 10,我們也可以通過動態刷新配置的方式來控制並發線程的數量。對於信號量大小的估算方法與線程池並發度的估算類似。僅訪問內存數據的請求一般耗時在 1ms 以內,性能可以達到 5000rps,這樣級別的請求我們可以將信號量設置為 1 或者 2,我們可以按此標準並根據實際請求耗時來設置信號量。

斷路器模式

斷路器模式源於 Martin Fowler 的 Circuit Breaker 一文。“斷路器” 本身是一種開關裝置,用於在電路上保護線路過載,當線路中有電器發生短路時,“斷路器” 能夠及時的切斷故障電路,防止發生過載、發熱、甚至起火等嚴重後果。

在分布式架構中,斷路器模式的作用也是類似的,當某個服務單元發生故障(類似用電器發生短路)之後,通過斷路器的故障監控(類似熔斷保險絲),直接切斷原來的主邏輯調用。但是,在 Hystrix 中的斷路器除了切斷主邏輯的功能之外,還有更復雜的邏輯,下面我們來看看它更為深層次的處理邏輯。

斷路器開關相互轉換的邏輯如下圖:

技術分享圖片

當 Hystrix Command 請求後端服務失敗數量超過一定閾值,斷路器會切換到開路狀態 (Open)。這時所有請求會直接失敗而不會發送到後端服務。

這個閾值涉及到三個重要參數:快照時間窗、請求總數下限、錯誤百分比下限。這個參數的作用分別是:
快照時間窗:斷路器確定是否打開需要統計一些請求和錯誤數據,而統計的時間範圍就是快照時間窗,默認為最近的 10 秒。
請求總數下限:在快照時間窗內,必須滿足請求總數下限才有資格進行熔斷。默認為 20,意味著在 10 秒內,如果該 Hystrix Command 的調用此時不足 20 次,即時所有的請求都超時或其他原因失敗,斷路器都不會打開。
錯誤百分比下限:當請求總數在快照時間窗內超過了下限,比如發生了 30 次調用,如果在這 30 次調用中,有 16 次發生了超時異常,也就是超過 50% 的錯誤百分比,在默認設定 50% 下限情況下,這時候就會將斷路器打開。

斷路器保持在開路狀態一段時間後 (默認 5 秒),自動切換到半開路狀態 (HALF-OPEN)。這時會判斷下一次請求的返回情況,如果請求成功,斷路器切回閉路狀態 (CLOSED),否則重新切換到開路狀態 (OPEN)。

使用 Feign Hystrix

因為熔斷只是作用在服務調用這一端,因此我們根據上一篇的示例代碼只需要改動 eureka-consumer-feign 項目相關代碼就可以。

POM 配置

因為 Feign 中已經依賴了 Hystrix 所以在 maven 配置上不用做任何改動。

配置文件

在原來的 application.yml 配置的基礎上修改

1
2
3
4
5
6
7
8
9
10
11
12
spring:
application:
name: eureka-consumer-feign-hystrix
eureka:
client:
service-url:
defaultZone: http://localhost:7000/eureka/
server:
port: 9003
feign:
hystrix:
enabled: true

創建回調類

創建 HelloRemoteHystrix 類實現 HelloRemote 中實現回調的方法

1
2
3
4
5
6
7
8
9
@Component
public class HelloRemoteHystrix implements HelloRemote {

@Override
public String hello(@RequestParam(value = "name") String name) {
return "Hello World!";
}

}

添加 fallback 屬性

HelloRemote類添加指定 fallback 類,在服務熔斷的時候返回 fallback 類中的內容。

1
2
3
4
5
6
7
@FeignClient(name = "eureka-producer", fallback = HelloRemoteHystrix.class)
public interface HelloRemote {

@GetMapping("/hello/")
String hello(@RequestParam(value = "name") String name);

}

別的就不用動了,很簡單吧!

測試

依次啟動 eureka-server、eureka-producer 和剛剛的 eureka-consumer-hystrix 這三個項目。

訪問:http://localhost:9003/hello/windmt
返回:[0]Hello, windmt! Sun Apr 15 23:14:25 CST 2018

說明加入 Hystrix 後,不影響正常的訪問。接下來我們手動停止 eureka-producer 項目再次測試:

訪問:http://localhost:9003/hello/windmt
返回:Hello World!

這時候我們再次啟動 eureka-producer 項目進行測試:

訪問:http://localhost:9003/hello/windmt
返回:[0]Hello, windmt! Sun Apr 15 23:14:52 CST 2018

根據返回結果說明熔斷成功。

總結

通過使用 Hystrix,我們能方便的防止雪崩效應,同時使系統具有自動降級和自動恢復服務的效果。

相關閱讀

Spring Cloud(一):服務治理技術概覽
Spring Cloud(二):服務註冊與發現 Eureka
Spring Cloud(三):服務提供與調用 Eureka
Spring Cloud(四):服務容錯保護 Hystrix
Spring Cloud(五):Hystrix 監控面板
Spring Cloud(六):Hystrix 監控數據聚合 Turbine
Spring Cloud(七):配置中心(Git 版與動態刷新)
Spring Cloud(八):配置中心(服務化與高可用)
Spring Cloud(九):配置中心(消息總線)
Spring Cloud(十):服務網關 Zuul(路由)
Spring Cloud(十一):服務網關 Zuul(過濾器)
Spring Cloud(十二):分布式鏈路跟蹤(Sleuth 與 Zipkin)

示例代碼:GitHub

參考

Spring Cloud - Feign Hystrix Support
springcloud(四):熔斷器 Hystrix
防雪崩利器:熔斷器 Hystrix 的原理與使用
Spring Cloud 構建微服務架構:服務容錯保護(Hystrix 服務降級)【Dalston 版】
Spring Cloud 構建微服務架構:服務容錯保護(Hystrix 依賴隔離)【Dalston 版】
Spring Cloud 構建微服務架構:服務容錯保護(Hystrix 斷路器)【Dalston 版】
微服務框架 Spring Cloud 介紹 Part5: 在微服務系統中使用 Hystrix, Hystrix Dashboard 與 Turbine
使用 Spring Cloud 與 Docker 實戰微服務

  • 本文作者: Yibo
  • 本文鏈接: https://windmt.com/2018/04/15/spring-cloud-4-hystrix/
  • 版權聲明: 本博客所有文章除特別聲明外,均采用 CC BY-NC-SA 4.0 許可協議。轉載請註明出處!

Spring Cloud(四):服務容錯保護 Hystrix【Finchley 版】