為什麼需要容錯限流
- 複雜分散式系統通常有很多依賴,如果一個應用不能對來自依賴 故障進行隔離,那麼應用本身就處在被拖垮的風險中。在一個高流量的網站中,某個單一後端一旦發生延遲,將會在數秒內導致 所有應用資源被耗盡(一個臭雞蛋影響一籃筐)。
- 如秒殺、搶購、雙十一等場景,在某一時間點會有爆發式的網路流量湧入,如果沒有好的網路流量限制,任由流量壓到後臺服務例項,很有可能造成資源耗盡,服務無法響應,甚至嚴重的導致應用崩潰。
Hystrix是什麼
Hystrix 能使你的系統在出現依賴服務失效的時候,通過隔離系統所依賴的服務,防止服務級聯失敗,同時提供失敗回退機制,更優雅地應對失效,並使你的系統能更快地從異常中恢復。
Hystrix能做什麼
- 在通過第三方客戶端訪問(通常是通過網路)依賴服務出現高延遲或者失敗時,為系統提供保護和控制
- 在分散式系統中防止級聯失敗
- 快速失敗(Fail fast)同時能快速恢復
- 提供失敗回退(Fallback)和優雅的服務降級機制
- 提供近似實時的監控、報警和運維控制手段
Hystrix設計原則
- 防止單個依賴耗盡容器(例如 Tomcat)內所有使用者執行緒
- 降低系統負載,對無法及時處理的請求快速失敗(fail fast)而不是排隊
- 提供失敗回退,以在必要時讓失效對使用者透明化
- 使用隔離機制(例如『艙壁』/『泳道』模式,熔斷器模式等)降低依賴服務對整個系統的影響
- 針對系統服務的度量、監控和報警,提供優化以滿足近似實時性的要求
- 在 Hystrix 絕大部分需要動態調整配置並快速部署到所有應用方面,提供優化以滿足快速恢復的要求
- 能保護應用不受依賴服務的整個執行過程中失敗的影響,而不僅僅是網路請求
Hystrix設計思想來源
艙壁隔離模式
貨船為了進行防止漏水和火災的擴散,會將貨倉分隔為多個,當發生災害時,將所在貨倉進行隔離就可以降低整艘船的風險。
斷路器模式
熔斷器就像家裡的保險絲,當電流過載了就會跳閘,不過Hystrix的熔斷機制相對複雜一些。
熔斷器開關由關閉到開啟的狀態轉換是通過當前服務健康狀況和設定閾值比較決定的.
- 當熔斷器開關關閉時,請求被允許通過熔斷器。如果當前健康狀況高於設定閾值,開關繼續保持關閉。如果當前健康狀況低於設定閾值,開關則切換為開啟狀態。
- 當熔斷器開關開啟時,請求被禁止通過。
- 當熔斷器開關處於開啟狀態,經過一段時間後,熔斷器會自動進入半開狀態,這時熔斷器只允許一個請求通過。當該請求呼叫成功時,熔斷器恢復到關閉狀態。若該請求失敗,熔斷器繼續保持開啟狀態, 接下來的請求被禁止通過。
Hystrix工作流程
官網原圖
中文版
流程說明
- 每次呼叫建立一個新的HystrixCommand,把依賴呼叫封裝在run()方法中.
- 執行execute()/queue做同步或非同步呼叫.
- 當前呼叫是否已被快取,是則直接返回結果,否則進入步驟 4
- 判斷熔斷器(circuit-breaker)是否開啟,如果開啟跳到步驟 8,進行降級策略,如果關閉進入步驟 5
- 判斷執行緒池/佇列/訊號量是否跑滿,如果跑滿進入降級步驟8,否則繼續後續步驟 6
- 呼叫HystrixCommand的run方法.執行依賴邏輯
- 6.1. 呼叫是否出現異常,否:繼續,是進入步驟8,
- 6.2. 呼叫是否超時,否:返回撥用結果,是進入步驟8
- 蒐集5、6步驟所有的執行狀態(成功, 失敗, 拒絕,超時)上報給熔斷器,用於統計從而判斷熔斷器狀態
- getFallback()降級邏輯.四種觸發getFallback呼叫情況(圖中步驟8的箭頭來源): 返回執行成功結果
兩種資源隔離模式
執行緒池隔離模式
使用一個執行緒池來儲存當前的請求,執行緒池對請求作處理,設定任務返回處理超時時間,堆積的請求堆積入執行緒池佇列。這種方式需要為每個依賴的服務申請執行緒池,有一定的資源消耗,好處是可以應對突發流量(流量洪峰來臨時,處理不完可將資料儲存到執行緒池隊裡慢慢處理)。
訊號量隔離模式
使用一個原子計數器(或訊號量)來記錄當前有多少個執行緒在執行,請求來先判斷計數器的數值,若超過設定的最大執行緒個數則丟棄改型別的新請求,若不超過則執行計數操作請求來計數器+1,請求返回計數器-1。這種方式是嚴格的控制執行緒且立即返回模式,無法應對突發流量(流量洪峰來臨時,處理的執行緒超過數量,其他的請求會直接返回,不繼續去請求依賴的服務)。
執行緒池隔離模式 VS 訊號量隔離模式
Hystrix主要配置項
快速上手
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.12</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-metrics-event-stream</artifactId>
<version>1.5.12</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>1.5.12</version>
</dependency>
複製程式碼
HystrixConfig
@Configuration
public class HystrixConfig {
/**
* 宣告一個HystrixCommandAspect代理類,現攔截HystrixCommand的功能
*/
@Bean
public HystrixCommandAspect hystrixCommandAspect() {
return new HystrixCommandAspect();
}
}
複製程式碼
HelloService
@Service
public class HelloService {
@HystrixCommand(fallbackMethod = "helloError",
commandProperties = {
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "2")},
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "5"),
@HystrixProperty(name = "maximumSize", value = "5"),
@HystrixProperty(name = "maxQueueSize", value = "10")
})
public String sayHello(String name) {
try {
Thread.sleep( 15000 );
return "Hello " + name + " !";
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
public String helloError(String name) {
return "伺服器繁忙,請稍後訪問~";
}
}
複製程式碼
啟動類
@SpringBootApplication
@RestController
public class HystrixSimpleApplication {
@Autowired
private HelloService helloService;
public static void main(String[] args) {
SpringApplication.run( HystrixSimpleApplication.class, args );
}
@GetMapping("/hi")
public String hi(String name) {
return helloService.sayHello( name );
}
}
複製程式碼
測試
訪問 http://localhost:80809/hi?name=zhangsan
curl -X GET -d 'name=zhangsan' http://localhost:8080/hi
複製程式碼
返回
伺服器繁忙,請稍後訪問~
複製程式碼
原始碼
參考
歡迎掃碼或微信搜尋公眾號《程式設計師果果》關注我,關注有驚喜~