## 什么是Hystrix
在分布式系統中,服務與服務之間依賴錯綜復雜,一種不可避免的情況就是某些服務將會出現失敗。Hystrix是一個庫,它提供了服務與服務之間的容錯功能,主要體現在延遲容錯和容錯,從而做到控制分布式系統中的聯動故障。Hystrix通過隔離服務的訪問點,阻止聯動故障,并提供故障的解決方案,從而提高了這個分布式系統的彈性。
## Hystrix解決了什么問題
在復雜的分布式系統中,可能有成百上千個依賴服務,這些服務由于某種故障,比如機房的不可靠性、網絡服務商的不可靠性等因素,導致某個服務不可用,如果系統不隔離該不可用的服務,可能會導致整個系統不可用。
例如,對于依賴30個服務的應用程序,每個服務的正常運行時間為99.99%,這是您期望的
99.9930 = 99.7%的正常運行時間 10億次請求中有0.3%= 3,000,000次失敗 2小時停機時間/月,即使所有的依賴都有很好的正常運行時間。
實際情況可能比這更糟糕。
如果不設計整個系統的韌性,即使所有依賴關系表現良好,即使0.01%的停機時間對數十個服務中的每一個服務的總體影響等同于每個月停機的潛在時間。
當所以的服務都出UP狀態,即Ok狀態,一個請求流程可能是這樣:

當某一個服務出現了延遲,可能會阻止整個該請求:

在高并發的情況下,單個服務的延遲,可能導致所有的請求都處于延遲狀態,可能在幾秒鐘就使服務處于負載飽和的狀態。
服務的單個點的請求故障,會導致整個服務出現故障,更為糟糕的是該故障服務,會導致其他的服務出現負載飽和,資源耗盡,直到不可用,從而導致這個分布式系統都不可用。這就是“雪崩”。

當通過第三方客戶端執行網絡訪問時,這些問題會加劇。第三方客戶就是一個“黑匣子”,其中實施細節被隱藏,并且可以隨時更改,網絡或資源配置對于每個客戶端庫都是不同的,通常難以監視和 更改。
通過的故障包括:
網絡連接失敗或降級。 服務和服務器失敗或變慢。 新的庫或服務部署會改變行為或性能特征。 客戶端庫有錯誤。
所有這些都代表需要隔離和管理的故障和延遲,以便單個故障依賴關系不能導致整個應用程序或系統的故障。
## Hystrix的設計原則
原則如下:
防止單個服務的故障,耗盡整個系統服務的容器(比如tomcat)的線程資源。
減少負載并快速失敗,而不是排隊。
在可行的情況下提供回退以保護用戶免受故障。
使用隔離技術(如隔板,泳道和斷路器模式)來限制任何一個依賴的影響。
通過近乎實時的指標,監控和警報來優化發現故障的時間。
通過配置更改的低延遲傳播優化恢復時間,并支持Hystrix大多數方面的動態屬性更改,從而允許您使用低延遲反饋循環進行實時操作修改。
保護整個依賴客戶端執行中的故障,而不僅僅是在網絡流量上進行保護降級、限流。
## Hystrix 是怎么實現它的設計目標的?
通過HystrixCommand 或者HystrixObservableCommand 將所有的外部系統(或者稱為依賴)包裝起來,整個包裝對象是單獨運行在一個線程之中(這是典型的命令模式)。
超時請求應該超過你定義的閾值
為每個依賴關系維護一個小的線程池(或信號量); 如果它變滿了,那么依賴關系的請求將立即被拒絕,而不是排隊等待。
統計成功,失敗(由客戶端拋出的異常),超時和線程拒絕。
打開斷路器可以在一段時間內停止對特定服務的所有請求,如果服務的錯誤百分比通過閾值,手動或自動的關閉斷路器。
當請求被拒絕、連接超時或者斷路器打開,直接執行fallback邏輯。
近乎實時監控指標和配置變化。
當您使用Hystrix包裝每個底層依賴項時,上圖所示的體系結構如下圖所示。 每個依賴關系彼此隔離,在延遲發生時可以飽和的資源受到限制,迅速執行fallback的邏輯,該邏輯決定了在依賴關系中發生任何類型的故障時會做出什么響應:
:-:
## Hystrix是怎么工作的?
架構圖
下圖顯示通過Hystrix向服務依賴關系發出請求時會發生什么:
:-:
具體將從以下幾個方面進行描述:
1.構建一個HystrixCommand或者HystrixObservableCommand 對象。
第一步是構建一個HystrixCommand或HystrixObservableCommand對象來表示你對依賴關系的請求。 其中構造函數需要和請求時的參數一致。
構造HystrixCommand對象,如果依賴關系預期返回單個響應。 可以這樣寫:
1 HystrixCommand command = new HystrixCommand(arg1, arg2);
同理,可以構建HystrixObservableCommand :
12 HystrixObservableCommand command = new HystrixObservableCommand(arg1, arg2);
2.執行Command
通過使用Hystrix命令對象的以下四種方法之一,可以執行該命令有四種方法(前兩種方法僅適用于簡單的HystrixCommand對象,并不適用于HystrixObservableCommand):
execute()–阻塞,,然后返回從依賴關系接收到的單個響應(或者在發生錯誤時拋出異常)
queue()–返回一個可以從依賴關系獲得單個響應的future 對象
observe()–訂閱Observable代表依賴關系的響應,并返回一個Observable,該Observable會復制該來源Observable
toObservable() –返回一個Observable,當您訂閱它時,將執行Hystrix命令并發出其響應
1234 K value = command.execute();FuturefValue = command.queue();ObservableohValue = command.observe(); ObservableocValue = command.toObservable();
同步調用execute()調用queue().get(). queue()依次調用toObservable().toBlocking().toFuture()。 這就是說,最終每個HystrixCommand都由一個Observable實現支持,甚至是那些旨在返回單個簡單值的命令。
3.響應是否有緩存?
如果為該命令啟用請求緩存,并且如果緩存中對該請求的響應可用,則此緩存響應將立即以“可觀察”的形式返回。
4.斷路器是否打開?
當您執行該命令時,Hystrix將檢查斷路器以查看電路是否打開。
如果電路打開(或“跳閘”),則Hystrix將不會執行該命令,但會將流程路由到(8)獲取回退。
如果電路關閉,則流程進行到(5)以檢查是否有可用于運行命令的容量。
5.線程池/隊列/信號量是否已經滿負載?
如果與命令相關聯的線程池和隊列(或信號量,如果不在線程中運行)已滿,則Hystrix將不會執行該命令,但將立即將流程路由到(8)獲取回退。
6.HystrixObservableCommand.construct() 或者 HystrixCommand.run()
在這里,Hystrix通過您為此目的編寫的方法調用對依賴關系的請求,其中之一是:
HystrixCommand.run() - 返回單個響應或者引發異常
HystrixObservableCommand.construct() - 返回一個發出響應的Observable或者發送一個onError通知
如果run()或construct()方法超出了命令的超時值,則該線程將拋出一個TimeoutException(或者如果命令本身沒有在自己的線程中運行,則會產生單獨的計時器線程)。 在這種情況下,Hystrix將響應通過8進行路由。獲取Fallback,如果該方法不取消/中斷,它會丟棄最終返回值run()或construct()方法。
請注意,沒有辦法強制潛在線程停止工作 - 最好的Hystrix可以在JVM上執行它來拋出一個InterruptedException。 如果由Hystrix包裝的工作不處理InterruptedExceptions,Hystrix線程池中的線程將繼續工作,盡管客戶端已經收到了TimeoutException。 這種行為可能使Hystrix線程池飽和,盡管負載“正確地流失”。 大多數Java HTTP客戶端庫不會解釋InterruptedExceptions。 因此,請確保在HTTP客戶端上正確配置連接和讀/寫超時。
如果該命令沒有引發任何異常并返回響應,則Hystrix在執行某些日志記錄和度量報告后返回此響應。 在run()的情況下,Hystrix返回一個Observable,發出單個響應,然后進行一個onCompleted通知; 在construct()的情況下,Hystrix返回由construct()返回的相同的Observable。
7.計算Circuit 的健康
Hystrix向斷路器報告成功,失敗,拒絕和超時,該斷路器維護了一系列的計算統計數據組。
它使用這些統計信息來確定電路何時“跳閘”,此時短路任何后續請求直到恢復時間過去,在首次檢查某些健康檢查之后,它再次關閉電路。
8.獲取Fallback
當命令執行失敗時,Hystrix試圖恢復到你的回退:當construct()或run()(6.)拋出異常時,當命令由于電路斷開而短路時(4.),當 命令的線程池和隊列或信號量處于容量(5.),或者當命令超過其超時長度時。
編寫Fallback ,它不一依賴于任何的網絡依賴,從內存中獲取獲取通過其他的靜態邏輯。如果你非要通過網絡去獲取Fallback,你可能需要些在獲取服務的接口的邏輯上寫一個HystrixCommand。
9.返回成功的響應
如果 Hystrix command成功,如果Hystrix命令成功,它將以Observable的形式返回對呼叫者的響應或響應。 根據您在上述步驟2中調用命令的方式,此Observable可能會在返回給您之前進行轉換:
:-:
execute() - 以與.queue()相同的方式獲取Future,然后在此Future上調用get()來獲取Observable發出的單個值
queue() - 將Observable轉換為BlockingObservable,以便將其轉換為Future,然后返回此未來
observe() - 立即訂閱Observable并啟動執行命令的流程; 返回一個Observable,當您訂閱它時,重播排放和通知
toObservable() - 返回Observable不變; 您必須訂閱它才能實際開始導致命令執行的流程
## 斷路器(Circuit Breaker)
下圖顯示HystrixCommand或HystrixObservableCommand如何與HystrixCircuitBreaker及其邏輯和決策流程進行交互,包括計數器在斷路器中的行為。
:-:
發生電路開閉的過程如下:
1.假設電路上的音量達到一定閾值(HystrixCommandProperties.circuitBreakerRequestVolumeThreshold())…
2.并假設錯誤百分比超過閾值錯誤百分比(HystrixCommandProperties.circuitBreakerErrorThresholdPercentage())…
3.然后斷路器從CLOSED轉換到OPEN。
4.雖然它是開放的,它使所有針對該斷路器的請求短路。
5.經過一段時間(HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()),下一個單個請求是通過(這是HALF-OPEN狀態)。 如果請求失敗,斷路器將在睡眠窗口持續時間內返回到OPEN狀態。 如果請求成功,斷路器將轉換到CLOSED,邏輯1.重新接管。
## 隔離(Isolation)
Hystrix采用隔板模式來隔離彼此的依賴關系,并限制對其中任何一個的并發訪問。
:-:
# 依賴和部署

* 服務降級
* 服務熔斷
* 依賴隔離
* 監控
## 服務降級
1. 引入依賴
~~~
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
~~~
2. 啟動類加上注解
~~~
@EnableCircuitBreaker
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.eclab.product.iclient")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
~~~
隨著業務的不斷增加,注解也越來越多,此時有些注解可以使用另外的注解代替:
~~~
/*@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker*/
@SpringCloudApplication
@EnableFeignClients(basePackages = "com.eclab.product.iclient")
@EnableHystrixDashboard
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
~~~
服務降級實例:
~~~
@HystrixCommand(fallbackMethod = "fallback")
@GetMapping("/getProductInfoList")
public String getProductInfoList(@RequestParam("i") Integer i){
if (i % 2 == 0){
return "Sucess";
}
RestTemplate restTemplate = new RestTemplate();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return restTemplate.postForObject("http://localhost:8081/product/listForOrder",
Arrays.asList("157875152258154"),
String.class);
}
private String fallback(){
return "太擁擠了,請稍后再試~";
}
~~~
在服務降級中,除了調用的目標服務不可用導致的錯誤引起降級之外,自身的異常也可以進行降級
降級的方法除了自定外,還可以有個全局的通用方法,將注解加在類上:
~~~
@RestController
@DefaultProperties(defaultFallback = "defaultFallback")
public class HystrixController {
//超時配置
/* @HystrixCommand(fallbackMethod = "fallback",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds" , value = "3000")
}
)*/
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled" , value = "true"), //設置熔斷
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold" , value = "10"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds" , value = "1000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage" , value = "60")
})
@GetMapping("/getProductInfoList")
public String getProductInfoList(@RequestParam("i") Integer i){
if (i % 2 == 0){
return "Sucess";
}
RestTemplate restTemplate = new RestTemplate();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return restTemplate.postForObject("http://localhost:8081/product/listForOrder",
Arrays.asList("157875152258154"),
String.class);
}
private String fallback(){
return "太擁擠了,請稍后再試~";
}
private String defaultFallback(){
return "默認提示:太擁擠了,請稍后再試~";
}
}
~~~
上面的代碼中的超時設置,可以用來配置是否降級
# 依賴隔離
* 線程池隔離
* Hystrix自動實現依賴隔離
熔斷的配置:
在需要進行熔斷配置的方法上加上Hystrix的注解
~~~
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled" , value = "true"), //設置熔斷
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold" , value = "10"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds" , value = "1000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage" , value = "60")
})
~~~
* Circuit Breaker : 斷路器
[斷路器詳解,馬丁](https://martinfowler.com/bliki/CircuitBreaker.html)
斷路器是將受保護的對象封裝在可以監控故障的斷路對象里面,當故障達到一定的值,將會引發跳閘,斷路器對象返回錯誤
* 圖解(斷路器模式狀態機):
:-:
* circuitBreaker.sleepWindowInMilliseconds:時間窗口
當斷路器打開,對主邏輯進行熔斷之后,Hystrix會開啟一個休眠時間窗口,將降級邏輯臨時提升為主邏輯,當休眠時間到期,斷路器將進入半開狀態,釋放一次請求到原來的主邏輯上,如果此次請求正常返回,那么斷路器將繼續閉合,主邏輯恢復,如果此次請求依然失敗,斷路器進入打開狀態,休眠時間窗繼續計時。
* circuitBreaker.requestVolumeThreshold : 設置在滾動窗口中斷路器的最小請求數
* circuitBreaker.errorThresholdPercentage : 斷路器打開的錯誤百分比條件
在配置文件中統一配置:
~~~
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
getProductInfoList:
execution:
isolation:
thread:
timeoutInMilliseconds: 800
~~~
# Hystrix Dashboard
Hystrix-dashboard是一款針對Hystrix進行實時監控的工具,通過Hystrix Dashboard我們可以在直觀地看到各Hystrix Command的請求響應時間, 請求成功率等數據。
* Hystrix Dashboard
1、添加依賴
~~~
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
~~~
2、在啟動類上加上注解
~~~
@EnableHystrixDashboard
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
~~~
3、在瀏覽器中進行訪問

在輸入框中填入相關信息:
:-:
選擇單機版:[http://hystrix-app](http://hystrix-app/):port/actuator/hystrix.stream
填寫對應的端口和ip,時間和應用名,即可進入主界面
:-:
訪問下熔斷的接口:
:-:
使用postman壓力測試:
:-:
結果:
:-:
錯誤次數累計,開始熔斷打開:
:-: