目錄
- 前言
- 斷路器
- HystrixCircuitBreaker
-
- HystrixCircuitBreakerImpl
-
- 屬性
- 熔斷器打開
- markSuccess
- markNonSuccess
- allowRequest
- 嘗試執行-attemptExecution
- 總結
前言
我們前面對
Hystrix
的名額統計方式做了詳細的介紹。
1.5.0
版本之前使用的是
HystrixRollingNumber
環形數組來作為名額的收集的子產品,而
1.5.0
版本之後使用的是更靈活的響應式收集模式。有了各項請求的名額那麼這一篇文章我們就來看看
Hystrix
的斷路器是如何使用這些名額來作為依據的。
斷路器
Hystrix的斷路器有三種狀态:打開、半開、關閉。
- 關閉狀态:請求正常進入
- 打開狀态:拒絕所有的請求,如果這時有降級的邏輯走降級邏輯
- 半開狀态:打開狀态的預設過5s狀态會自動變成半開狀态,該狀态下允許一個請求進入,如果請求成功關閉斷路器 如果請求失敗重新進入打開狀态。
如下借用官網的一張圖:
我們下面主要是來看Hystrix是如何實作這三種狀态的轉換的。
HystrixCircuitBreaker
該接口定義了如下方法:
/**
* 每次請求都會判斷是否允許請求 該方法具有幂等性
*/
boolean allowRequest();
/**
* 斷路器是否處于打開的狀态
*/
boolean isOpen();
/**
* 标記請求成功
*/
void markSuccess();
/**
* 标記請求失敗
*/
void markNonSuccess();
/**
* 嘗試執行
*/
boolean attemptExecution();
上面方法都很直接 并且我們可以認為
HystrixCircuitBreaker
有且僅有一個實作
HystrixCircuitBreakerImpl
(
NoOpCircuitBreaker
也是一種實作 但是沒有太大意義)
HystrixCircuitBreakerImpl
作為
HystrixCircuitBreaker
的唯一的實作
HystrixCircuitBreakerImpl
重要程度不言而喻。
(注意看源碼的注釋)
屬性
//配置
private final HystrixCommandProperties properties;
//名額統計器 這個我們前面沒有說到 但是你可以簡單的了解它是一個聚合(對各種統計流的聚合)
//特别是聚合了HealthCountsStream
private final HystrixCommandMetrics metrics;
//斷路器的三種狀态
enum Status {
CLOSED, OPEN, HALF_OPEN;
}
//儲存目前斷路器的狀态預設是關閉的
private final AtomicReference<Status> status = new AtomicReference<Status>(Status.CLOSED);
//熔斷器關閉的記錄為-1
private final AtomicLong circuitOpened = new AtomicLong(-1);
//儲存對HealthCountsStream的訂閱結果 用于重訂閱。那可能會問 為什麼要重訂閱
//其實就是為了初始化統計資料 例如從打開狀态的進入關閉狀态 這個時候需要重新統計名額來為下一次判定做準備
private final AtomicReference<Subscription> activeSubscription = new AtomicReference<Subscription>(null);
熔斷器打開
//這個方法很重要 通過訂閱HealthCountsStream來實時判斷是否要打開熔斷開關
private Subscription subscribeToStream() {
return metrics.getHealthCountsStream()
.observe()
.subscribe(new Subscriber<HealthCounts>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(HealthCounts hc) {
//判斷目前的請求總量是否超過我們設定的請求量的門檻值
//可以通過hystrix.command.default.circuitBreaker.requestVolumeThreshold來配置請求門檻值
//預設是 20個
if (hc.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {
} else {
//如果失敗率大于我們配置的失敗率 就把熔斷狀态改為打開的狀态并記錄打開的時間
//失敗率預設是50% 10s 20個請求 50%都失敗了 才會打開熔斷的開關
if (hc.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) {
} else {
if (status.compareAndSet(Status.CLOSED, Status.OPEN)) { circuitOpened.set(System.currentTimeMillis());
}
}
}
}
});
}
上面的方法主要是一個訂閱的邏輯 然後根據訂閱的資料來判定是否需要打開 斷路器
- 先判斷10s内的總請求數量是否大于
可通過預設值20
來配置hystrix.command.default.circuitBreaker.requestVolumeThreshold
- 其次判斷失敗率是否超過
可通過預設值 50%
來配置hystrix.command.default.circuitBreaker.errorThresholdPercentage
- 如果上面條件都滿足就打開斷路器 并記錄時間
markSuccess
@Override
public void markSuccess() {
//通過CAS關閉斷路器
if (status.compareAndSet(Status.HALF_OPEN, Status.CLOSED)) {
//重置統計流
metrics.resetStream();
//如果目前activeSubscription也有訂閱 那麼重新訂閱
Subscription previousSubscription = activeSubscription.get();
if (previousSubscription != null) {
previousSubscription.unsubscribe();
}
Subscription newSubscription = subscribeToStream();
activeSubscription.set(newSubscription);
circuitOpened.set(-1L);
}
}
markNonSuccess
@Override
public void markNonSuccess() {
if (status.compareAndSet(Status.HALF_OPEN, Status.OPEN)) {
circuitOpened.set(System.currentTimeMillis());
}
}
标記不成功 将斷路器從半開狀态變為打開狀态。
circuitOpened
記錄打開的時間戳
allowRequest
//isAfterSleepWindow 這個方法的邏輯主要判斷在斷路器打開的狀态下 預設5s 要嘗試去發送一筆請求
//判斷打開的時間是否超過5s
@Override
private boolean isAfterSleepWindow() {
//circuitOpened 這個變量 在斷路器打開的狀态下存儲的是時間戳
final long circuitOpenTime = circuitOpened.get();
final long currentTime = System.currentTimeMillis();
//可以通過hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds來配置
final long sleepWindowTime = properties.circuitBreakerSleepWindowInMilliseconds().get();
return currentTime > circuitOpenTime + sleepWindowTime;
}
public boolean allowRequest() {
//判斷是否配置斷路器強制打開
if (properties.circuitBreakerForceOpen().get()) {
return false;
}
//判斷是否配置斷路器強制關閉
if (properties.circuitBreakerForceClosed().get()) {
return true;
}
//如果斷路器是關閉的狀态 傳回true
if (circuitOpened.get() == -1) {
return true;
} else {
//進到這裡 說明斷路器是打開的狀态(可能是半開 可能是打開)
//如果斷路器是半開的狀态傳回false
if (status.get().equals(Status.HALF_OPEN)) {
return false;
} else {
//如果是打開的狀态 要檢查目前狀态停留的時間是否超過了門檻值
return isAfterSleepWindow();
}
}
}
嘗試執行-attemptExecution
@Override
public boolean attemptExecution() {
if (properties.circuitBreakerForceOpen().get()) {
return false;
}
if (properties.circuitBreakerForceClosed().get()) {
return true;
}
if (circuitOpened.get() == -1) {
return true;
} else {
//如果斷路器處于打開的狀态 并且如果打開狀态超過門檻值 就将狀态設定成半開狀态
//設定成功之後 true 讓目前請求可以執行
if (isAfterSleepWindow()) {
if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN)) {
return true;
} else {
return false;
}
} else {
return false;
}
}
}
上面的代碼設定
if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN))
這個分支很關鍵。将打開狀态下的斷路器設定成半打開狀态 設定成功之後立馬傳回true 允許一筆請求通過。設定失敗說明目前狀态是半開狀态,是以保證了隻有一筆請求能通過。
總結
斷路器的所有的邏輯都是在
HystrixCircuitBreakerImpl
中,是以它是斷路器的核心。幸運的是它的實作并不複雜 了解名額擷取這一塊是關鍵。