④SpringCloud 實戰:引入Hystrix元件,分散式系統容錯
阿新 • • 發佈:2020-12-03
這是SpringCloud實戰系列中第4篇文章,瞭解前面第兩篇文章更有助於更好理解本文內容:
[①SpringCloud 實戰:引入Eureka元件,完善服務治理](https://jinglingwang.cn/archives/eureka)
[②SpringCloud 實戰:引入Feign元件,發起服務間呼叫](https://jinglingwang.cn/archives/feign)
[③SpringCloud 實戰:使用 Ribbon 客戶端負載均衡](https://jinglingwang.cn/archives/ribbon)
## 簡介
Hystrix 是一個延遲和容錯庫,旨在隔離對遠端系統、服務和第三方庫的訪問點,停止級聯故障,並在故障不可避免的複雜分散式系統中實現恢復能力。
## 服務雪崩
在分散式微服務的架構體系下,一般都會存在多層級服務服務的呼叫鏈,當鏈路中的某個服務發生異常,最後導致整個系統不可用,這種現象稱為服務雪崩效應。
![](https://img2020.cnblogs.com/blog/709068/202012/709068-20201203095257343-1190177416.png)
如上圖所示,從最開始的整個系統正常狀態,到單個服務出現異常,再到多個服務出現異常,到最後整個系統可不用,整個過程就是服務雪崩效應。如果在單個服務出現異常的時候,我們能及時發現、預防、處理,也就不會出現級聯效果導致整個系統不可用。Hystrix 就是來保證上面的情況發生時能停止級聯故障,保證系統穩定執行的。
## Hystrix遵循的設計原則
1. 避免執行緒耗盡
由於被呼叫方出現問題,呼叫方無法及時獲取響應結果,而一直在傳送請求,最終會耗盡所有執行緒的資源。
2. 快速失敗
當被呼叫方出現問題後,呼叫方發起的請求可以快速失敗並返回,這樣就不用一直阻塞住,同時也釋放了執行緒資源。
3. 支援回退
發起的請求在返回失敗後,我們可以讓使用者有回退的邏輯,比如獲取備用資料,從快取中獲取資料,記錄日誌等操作。
4. 資源隔離
當你的服務依賴了 A、B、C 三個服務,當只有 C 服務出問題的時候,如果沒做隔離,最終也會發生雪崩效應,導致整個服務不可用,如果我們進行了資源隔離,A、B、C 三個服務都是相互隔離的,即使 C 服務出問題了,那也不影響 A 和 B。這其實就跟不要把所有的雞蛋放進一個籃子裡是一樣的道理。
5. 近實時監控
它能幫助我們瞭解整個系統目前的狀態,有哪些服務有問題,當前流量有多大,出問題後及時告警等。
## **Hystrix 兩種隔離方式**
Hystrix 支援執行緒池和訊號量兩種隔離方式,預設使用的執行緒池隔離。
執行緒池隔離是當用戶請求到 A 服務後,A 服務需要呼叫其他服務,這個時候可以為不同的服務建立獨立的執行緒池,假如 A 需要呼叫 B 和 C,那麼可以建立 2 個獨立的執行緒池,將呼叫 B 服務的執行緒丟入到一個執行緒池,將呼叫 C 服務的執行緒丟入到另一個執行緒池,這樣就起到隔離效果,就算其中某個執行緒池請求滿了,無法處理請求了,對另一個執行緒池也沒有影響。
訊號量隔離就比較簡單了,訊號量就是一個計數器,比如初始化值是 100,那麼每次請求過來的時候就會減 1,當訊號量計數為 0 的時候,請求就會被拒絕,等之前的請求處理完成後,訊號量會加 1,同時也起到了限流的作用,這就是訊號量隔離,訊號量隔離是在請求主執行緒中執行的。
執行緒池隔離的特點是 Command 執行在獨立的執行緒池中,可以支援超時,是單獨的執行緒,支援非同步。訊號量隔離執行在呼叫的主執行緒中,不支援超時,只能同步呼叫。
## Hystrix 實戰
### 引入Hystrix 依賴
1. 引入相關依賴
```xml
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
```
2. 在`ribbon-client`服務的啟動類上添加註解`@EnableHystrix`
這時候服務已經可以啟動成功了
### Hystrix 的三種使用方式
### 1.@HystrixCommand 註解方式
HystrixCommand 註解作用於方法上,哪個方法想要使用 Hystrix 來進行保護,就在這個方法上增加 HystrixCommand 註解。
比如在我們的queryPort方法上新增@HystrixCommand註解:
```java
@HystrixCommand(commandKey = "queryPort")
@GetMapping("queryPort")
public String queryPort(){
return providerFeign.queryPort();
}
```
其中commandKey不指定的話,會預設使用方法名,這裡也是queryPort;
@HystrixCommand 有很多預設的配置,比如超時時間,隔離方式等;我們可以手動指定配置資訊有比如 commandKey、groupKey、fallbackMethod 等。
**配置回退方法fallbackMethod**
使用@HystrixCommand 註解方式配置回退方法,需要將回退方法定義在HystrixCommand所在的類中,且回退方法的簽名與呼叫的方法簽名(入參,返回值)應該保持一致,比如:
```java
private String queryPortFallBack(){
return "sorry queryPort,jinglingwang.cn no back!";
}
//呼叫方法改造
@HystrixCommand(commandKey = "queryPort",fallbackMethod = "queryPortFallBack")
```
然後我們把eureka-provider服務停掉或者故意超時,訪問介面會出現如下圖所示的結果:
![](https://img2020.cnblogs.com/blog/709068/202012/709068-20201203095333921-437918536.png)
**我們也可以結合`@HystrixProperty`註解來豐富我們的配置**
```java
@HystrixCommand(commandKey = "queryPort",commandProperties ={
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000"),//超時時間,預設1000,即1秒
@HystrixProperty(name = "execution.isolation.strategy",value = "SEMAPHORE"),//訊號量隔離級別
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests",value = "50") //訊號量模式下,最大請求併發數,預設10
},fallbackMethod = "queryPortFallBack")
@GetMapping("queryPort")
public String queryPort(){
return providerFeign.queryPort();
}
```
上面的一些配置資訊我們還可以配置到配置檔案中,效果是一樣的:
```xml
# queryPort 是@HystrixCommand註解裡面的commandKey
# 隔離方式,SEMAPHORE:訊號量隔離,THREAD:執行緒隔離(預設值)
hystrix.command.queryPort.execution.isolation.strategy = SEMAPHORE
# 訊號量模式下,最大請求併發數,預設10
hystrix.command.queryPort.execution.isolation.semaphore.maxConcurrentRequests = 50
# 超時時間,預設值是1000,也就是1秒;在HystrixCommandProperties類可以看到
hystrix.command.queryPort.execution.isolation.thread.timeoutInMilliseconds = 3000
```
**下面的程式碼展示了執行緒隔離級別下的配置示例:**
```java
@HystrixCommand(commandKey = "queryTempPort",
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "30"),
@HystrixProperty(name = "maxQueueSize", value = "101"),
@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"),
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"),
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440")
}
,fallbackMethod = "queryTempPortFallBack")
@GetMapping("queryTempPort")
public String queryTempPort(){
return providerTempFeign.queryPort();
}
```
我們也可以使用`@DefaultProperties`註解來配置預設屬性;
@DefaultProperties是作用在類上面的,可以配置一些比如groupKey、threadPoolKey、commandProperties、threadPoolProperties、ignoreExceptions和raiseHystrixExceptions等屬性。方法級別的@HystrixCommand命令中單獨指定了的屬性會覆蓋預設的屬性,比如:
```java
@RestController
@DefaultProperties(groupKey = "DefaultGroupKey")
public class RibbonController{
...
@HystrixCommand(commandKey = "queryTempPort",groupKey="eureka-provider-temp",
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "30"),
@HystrixProperty(name = "maxQueueSize", value = "101"),
@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"),
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"),
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440")
}
,fallbackMethod = "queryTempPortFallBack")
@GetMapping("queryTempPort")
public String queryTempPort(){
return providerTempFeign.queryPort();
}
}
```
### 2.Feign 整合 Hystrix
**開啟Feign對Hystrix的支援**
在配置檔案新增如下配置
```java
# 如果為true,則將使用Hystrix斷路器包裝OpenFeign客戶端,預設是false
feign.hystrix.enabled=true
```
**配置fallback**
1. 為Feign配置回退方法,將fallback屬性設定成回退的類名,例如:
```java
@Component
public class ProviderTempFeignFallback implements ProviderTempFeign{
@Override
public String queryPort(){
return "sorry ProviderTempFeign, jinglingwang.cn no back!";
}
}
@FeignClient(value = "eureka-provider-temp",fallback = ProviderTempFeignFallback.class)
public interface ProviderTempFeign{
@RequestMapping("/queryPort")
String queryPort();
}
```
1. 我們保留上面的@HystrixCommand註解,然後啟動專案,把eureka-provider專案的介面加一個斷點,保證介面會超時。同時配置有兩個fallback時,發現最後生效的是@HystrixCommand註解配置的fallback,說明@HystrixCommand註解的優先順序要高一些,返回結果如圖:
![](https://img2020.cnblogs.com/blog/709068/202012/709068-20201203095352875-46131937.png)
然後我們把@HystrixCommand註解註釋掉,再重啟,成功執行了Feign配置的fallback,效果如圖:
![](https://img2020.cnblogs.com/blog/709068/202012/709068-20201203095403605-1020875720.png)
**fallback返回失敗的原因**
如果需要訪問導致失敗回退的原因,可以使用@FeignClient內的fallbackFactory屬性。
```java
@Component
public class ProviderFeignFallbackFactory implements FallbackFactory{
@Override
public ProviderFeign create(Throwable cause){
return new ProviderFeign(){
@Override
public String queryPort(){
return "sorry ProviderFeignFallbackFactory, jinglingwang.cn no back! why? ==>" + cause.getCause();
}
};
}
}
@FeignClient(value = "eureka-provider",fallbackFactory = ProviderFeignFallbackFactory.class)
public interface ProviderFeign{
/**
* 呼叫服務提供方,其中會返回服務提供者的埠資訊
* @return jinglingwang.cn
*/
@RequestMapping("/queryPort")
String queryPort();
}
```
### 3.閘道器中使用Hystrix
閘道器中使用Hystrix等到了整合閘道器的時候再細講。
### hystrix配置總結
1. 預設配置是全域性有效的
```java
# 配置 Hystrix 預設的配置
# To set thread isolation to SEMAPHORE
hystrix.command.default.execution.isolation.strategy: SEMAPHORE
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 3000
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests: 40
```
2. 單獨為Feign Client 來指定超時時間
```java
# 單獨為 ProviderFeign 配置
hystrix.command.ProviderFeign.execution.isolation.strategy = SEMAPHORE
# 超時時間
hystrix.command.ProviderFeign.execution.isolation.thread.timeoutInMilliseconds = 5000
# 最大請求併發數,預設10
hystrix.command.ProviderFeign.execution.isolation.semaphore.maxConcurrentRequests: 200
```
3. 單獨為ProviderTempFeign類的queryPort()方法進行配置
```java
# 單獨為ProviderTempFeign類的queryPort()方法配置
hystrix.command.ProviderTempFeign#queryPort().execution.isolation.strategy = THREAD
# 超時時間
hystrix.command.ProviderTempFeign#queryPort().execution.isolation.thread.timeoutInMilliseconds = 5000
```
4. 使用 @HystrixCommand 註解配置
具體做法可以參考上面的示例程式碼
Hystrix的配置項有很多,其他屬性的配置key可以參考`HystrixCommandProperties`類。
### **如何合理的配置Hystrix和Ribbon超時時間**
Hystrix 的超時時間是和Ribbon有關聯的,如果配置的不對,可能會出現莫名其妙的問題。
在Hystrix原始碼裡面是建議`hystrixTimeout`應該大於等於`ribbonTimeout`的時間的,否則會輸出一句警告:
```java
LOGGER.warn("The Hystrix timeout of " + hystrixTimeout + "ms for the command " + commandKey +
" is set lower than the combination of the Ribbon read and connect timeout, " + ribbonTimeout + "ms.");
```
而在取`ribbonTimeout`配置值的時候,是有一個計算公式的:
`ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1);`
假如我們Ribbon的超時時間配置如下:
```java
#讀超時
ribbon.ReadTimeout=3000
#連線超時
ribbon.ConnectTimeout=3000
#同一臺例項最大重試次數,不包括首次呼叫
ribbon.MaxAutoRetries=0
#重試負載均衡其他的例項最大重試次數,不包括首次呼叫
ribbon.MaxAutoRetriesNextServer=1
```
將上面的值代入到公式計算,得到結果:ribbonTimeout=(3000+3000)*(0+1)*(1+1),結果為12000,也就是說Hystrix 的超時時間建議配置值要大於等於12000,也就是12秒。
### Hystrix Dashboard
引入dashboard依賴:
```xml
org.springframework.cloud
spring-cloud-starter-netflix-hystrix-dashboard
```
在啟動類上加入註解@EnableHystrixDashboard
然後啟動專案,訪問[http://localhost:7071/hystrix](http://localhost:7071/hystrix),我們可以看到如下頁面:
![](https://img2020.cnblogs.com/blog/709068/202012/709068-20201203095420499-42242448.png)
但是這時候並不能直接使用,需要對專案進行監控,首先要有對應的 Stream 地址,Stream 產生資料來源,然後將儀表板指向Hystrix客戶端應用程式中的單個例項/hystrix.stream端點。
我們在被ribbon-client專案中加入 `spring-boot-starter-actuator` 依賴,只有加入了 actuator 才能暴露出 hystrix.stream 端點。
然後再配置檔案新增如下配置:
```xml
management.endpoints.web.exposure.include = hystrix.stream
```
啟動專案,訪問[http://localhost:7071/actuator/hystrix.stream](http://localhost:7071/actuator/hystrix.stream)介面,你會發現頁面一直在顯示ping;
然後把該地址配置到上面的頁面中,點選monitor,OK,等待loading。
然後我們隨便訪問一些介面,就可以看到監控內容了。
![](https://img2020.cnblogs.com/blog/709068/202012/709068-20201203095433100-610688564.png)
## Hystrix 總結
1. Hystrix 支援@HystrixCommand 命令和配置檔案兩種方式進行配置
2. Hystrix 支援兩種隔離級別,在閘道器中建議使用訊號量的方式,能起到一定限流的作用
3. Hystrix 的執行緒池隔離級別可以為每個client分別配置執行緒池,起到資源隔離的作用
4. Hystrix 的執行緒池隔離級別中使用 ThreadLocal 時資料可能會丟失,需要單獨處理
5. Hystrix 的fallback我們可以用來記錄日誌或者進行相應的業務告警
6. Hystrix 超時時間的合理計算和ribbon的配置有關係,否則可能出現莫名其妙的問題
程式碼示例:[Github ribbon client](https://github.com/Admol/learning_examples/tree/master/SpringCloud-Demo/ribbon