Spring cloud的保護機制(Hystrix)
一: 概述
很多系統中應考慮橫向擴展,單點故障問題。在出現故障時,爲了較少故障的影響,保障集羣的高可用,應增加保護機制,Hystrix就是其中的一種保護機制
- Hystrix通過添加延遲閾值以及容錯的邏輯,來幫助我們控制分佈式系統間組件的交互。
- Hystrix通過隔離服務間的訪問點、停止他們之間的級聯故障,提供可回退操作來實現容錯。
Hystrix的功能:
- 當所依賴的網絡服務發生延遲或者失敗時,對訪問的客戶端程序進行保護。
- 在分佈式系統中,停止級聯故障
- 網絡服務恢復正常時,可以快速恢復客戶端的訪問能力
- 調用失敗時執行服務回退
- 可支持實時監控、報警、和其他操作
1.1 Hystrix程序
1.2 Hystrix的使用
內部流程
從之前的例子看,當服務器出現無響應現象的時候,Hystrix會自動使用容錯機制,看似簡單,其實有一套較爲複雜的執行邏輯
- 在命令開始執行時,會做一些準備工作(如:爲命令創建相應的線程池等)
- 判斷是否打開了緩存,打開了緩存就直接查找緩存並返回結果
- 判斷斷路器是否打開,如果打開了,就表示鏈路不可以用,直接執行回退方法
- 判斷線程池、信號量等條件(如:線程池超負荷,則返回回退方法,否則,就去執行命令的內容)
- 執行命令,判斷是否要對斷路器進行處理,執行完成後,如果滿足一定條件,則需要開啓斷路器
整個流程最主要的點,就是在於斷路器是否被打開
1.3 命令執行
- toObservable:返回一個最原始的可觀察的實例,是一個RxJava的類,可觀察命令的執行過程,並且將執行的信息轉遞給訂閱者(命令不會馬上執行,只有當返回的可觀察實例被訂閱之後,纔會真正執行)
- observe: 用toObservable獲得最原始的可觀察實例後,再使用一個replay subject作爲原始toObservable的訂閱者(會立刻執行)
- queue
- execute
1.4 配置屬性
1.5 回退
列舉三種情況下觸發回退
- 斷路器被打開
- 線程池、隊列、信號量滿載
- 實際執行命令失敗
package com.atm.cloud.config;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;
public class MyTimeOutConfig extends HystrixCommand<String> {
public MyTimeOutConfig() {
// 第二種方法:
// super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
// 超時時間設置成2s
super(Setter.withGroupKey(
HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(2000)));
// 第一種方法:設置超時時間(設置成2s)
HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(
20000);
}
@Override
protected String run() throws Exception {
// 手動延遲3s
// Thread.sleep(3000);
System.out.println("執行命令...");
return "Success";
}
// 回退方法
@Override
protected String getFallback() {
System.out.println("執行回退...");
// return super.getFallback();
return "Fallback...";
}
}
如果斷路器打開,則會執行回退方法,
如果斷路器打開但沒有提供回退方法,系統則會報異常
1.6 回退的模式
Hystrix回退機制比較靈活,
你可以在A命令的回退方法中執行B命令,如果B命令執行也失敗,同意也可以觸發B命令的回退
1.7 斷路器開啓
斷路器一旦開啓,就會直接
- 調用回退方法,
- 不再執行命令,
- 也不會更新鏈路的健康狀況
斷路器開啓要滿足兩個條件
- 整個鏈路達到一定閾值,默認情況下,10秒內產生超過20次請求,則符合第一個條件
- 滿足第一個條件下,如果請求的錯誤百分比大於閾值,則會打開斷路器,默認爲50%
package com.atm.cloud.open;
import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandMetrics.HealthCounts;
import com.netflix.hystrix.HystrixCommandProperties;
public class CloseMain {
public static void main(String[] args) throws Exception {
// 10秒內大於3次請求,滿足第一個條件
ConfigurationManager
.getConfigInstance()
.setProperty(
"hystrix.command.default.circuitBreaker.requestVolumeThreshold",
3);
boolean isTimeout = true;
for (int i = 0; i < 10; i++) {// 發送10次請求,一開始均爲失敗,則會打開斷路器
TestCommand c = new TestCommand(isTimeout);// 總會超時
c.execute();
// 輸出健康信息
HealthCounts hc = c.getMetrics().getHealthCounts();
System.out.println("斷路器狀態:" + c.isCircuitBreakerOpen() + ", 請求數量:"
+ hc.getTotalRequests());
if (c.isCircuitBreakerOpen()) {// 如果斷路器打開
isTimeout = false;//
System.out.println("============ 斷路器打開了,等待休眠期結束");
Thread.sleep(6000);// 等待休眠期結束,6s之後嘗試性發一次請求
}
}
}
static class TestCommand extends HystrixCommand<String> {
private boolean isTimeout;
public TestCommand(boolean isTimeout) {
super(Setter.withGroupKey(
HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(500)));// 500毫秒沒有響應,則打開斷路器
this.isTimeout = isTimeout;
}
@Override
protected String run() throws Exception {
if (isTimeout) {
Thread.sleep(800);// 方法執行時間爲800毫秒
} else {
Thread.sleep(200);
}
return "";
}
@Override
protected String getFallback() {
return "fallback";
}
}
}
1.8 斷路器關閉
- 當斷路器打開的時候,不再執行命令,直接進行回退方法,這段時間稱爲休眠期,默認時間爲5s
- 休眠期結束之後,會嘗試性執行一次命令。
- 此時,斷路器狀態處於半開狀態,嘗試執行成功之後,就會關閉斷路器並且清空健康信息,
- 如果失敗,斷路器會繼續保持打開的狀態
1.9 隔離機制
命令的真正執行,除了斷路器要關閉外,還需要再過一關
- 執行命令的線程池或者信號量是否滿載
- 如果滿載,命令就不會執行,而是直接出發回退
Hystrix提供了兩種隔離策略
- thread(線程,消耗可能大點,異步超時)
- semaphore(信號量,不支持超時、異步)
1.9.1 編寫命令
package com.atm.cloud;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
public class MyCommand extends HystrixCommand<String> {
public int index;
public MyCommand(int index) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory
.asKey("ExampleGroup")));
this.index = index;
}
@Override
protected String run() throws Exception {
Thread.sleep(500);
System.out.println("run(),當前索引:" + index);
return "success";
}
@Override
protected String getFallback() {
System.out.println("getFallback(),當前索引:" + index);
return "fallback";
}
}
- 線程隔離測試,編寫測試類
package com.atm.cloud;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
public class MyCommand extends HystrixCommand<String> {
public int index;
public MyCommand(int index) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory
.asKey("ExampleGroup")));
this.index = index;
}
@Override
protected String run() throws Exception {
Thread.sleep(500);
System.out.println("run(),當前索引:" + index);
return "success";
}
@Override
protected String getFallback() {
System.out.println("getFallback(),當前索引:" + index);
return "fallback";
}
}
- 信號量隔離測試
package com.atm.cloud;
import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy;
public class SemaphoreMain {
public static void main(String[] args) throws Exception {
// 信號量策略,默認最大併發數10
ConfigurationManager.getConfigInstance().setProperty(
"hystrix.command.default.execution.isolation.strategy",
ExecutionIsolationStrategy.SEMAPHORE);
// 設置最大併發數爲2
ConfigurationManager
.getConfigInstance()
.setProperty(
"hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests",
2);
for (int i = 0; i < 6; i++) {
final int index = i;
Thread t = new Thread() {
public void run() {
MyCommand c = new MyCommand(index);
c.execute();
}
};
t.start();
}
Thread.sleep(5000);
}
}
1.10 請求緩存
在一起請求中,多個地方調用同一個接口,可考慮使用緩存
需要用到CommandKey
合併請求,請求緩存,在一次請求的過程中才能實現,因此需要先初始化請求上下文