Hystrix 的運作原理
- 構造一個 HystrixCommand 或 HystrixObservableCommand 對象
- 執行指令。
- 檢查緩存是否被命中,如果命中則直接傳回。
- 檢查斷路器開關是否斷開。如果是開路,則直接熔斷,經過回退邏輯。
- 檢查線程池/隊列/信号量是否已滿。如果線程池/隊列/信号量已滿,則直接拒絕請求并遵循回退邏輯。
- 如果不滿足上述條件,則調用 HYST rixObservableCommand.construct() 方法 HystrixCommand.run Method() 執行業務邏輯。
- 判斷業務邏輯方法運作是否有異常或逾時。如果是這樣,它将直接降級并使用回退邏輯。
- 上報統計資料,由使用者計算斷路器狀态。
- 傳回結果
從流程圖中可以發現錯誤統計隻有在5和7的情況下才會上報。
斷路器的工作原理
斷路器的開關控制邏輯如下:
- 在一個統計時間視窗(HYSTrixCommandProperties.metricsRollingStatisticalWindowInMilliseconds())内,處理的請求數達到設定的最小門檻值(HYST)rixCommandProperties.circuitBreakerRequestVolumeThreshold()),錯誤百分比超過設定的最大門檻值(HYSTrixCommandProperties.circuitBreakerThreshold() ) )此時斷路器分閘,斷路器狀态由合閘切換為分閘。
- 當斷路器斷開時,它将直接融斷所有請求(快速失敗)并經過回退邏輯。
- 經過一個休眠視窗時間(HYST rixCommandProperties.circuitBreakerSleepWindowInMilliseconds()),Hystrix會釋放一個進行後續服務并将斷路器開關切換到半開(half OPEN)。如果請求失敗,斷路器将熔斷開關切換到OPEN狀态,繼續熔斷所有請求,直到下一個休眠時間視窗到來;如果請求成功,斷路器将切換到 CLOSED 狀态,此時允許所有請求通過,直到發生一步,斷路器開關才會切換到 OPEN 狀态。
斷路器源代碼
Hystrix 斷路器的實作類是 HystrixCircuitBreaker。源代碼如下:
/** * 連接配接到 {@link HystrixCommand} 執行的斷路器邏輯,如果失敗超過定義的門檻值,将停止允許執行。 * 斷路器會在執行 HystrixCommand 時調用斷路器邏輯。如果故障超過定義的門檻值,斷路器熔斷開關将打開,這将阻止任務執行。 * <p> * 預設的(也是唯一的)實作将允許在定義的sleepWindow 之後進行一次重試,直到執行成功,此時它将再次關閉電路并允許再次執行 * <p> * 預設(且唯一)的實作将允許在定義的 sleepWindow 之後進行一次重試,直到成功執行,此時它将再次關閉電路并允許再次執行。 */ public interface HystrixCircuitBreaker { /** * 每個 {@link HystrixCommand} 請求都會詢問是否允許繼續。沒有副作用并且是幂等,不修改任何内部狀态,并考慮了半開邏輯
* 每個HystrixCommand 請求詢問是否允許繼續。 它是幂等的,不修改任何内部狀态。考慮半開放邏輯,當一個sleep window到來時,會釋放一些請求給後續邏輯 * @return boolean 是否允許請求(是否允許請求) */
boolean allowRequest(); /** * 斷路器目前是否打開(跳閘)。 * 判斷熔斷器開關是否為OPEN(如果是OPEN或者half_OPEN時傳回true。如果是CLOSE則傳回false。沒有副作用,是幂等的)。 * @return 斷路器的布爾狀态(傳回斷路器狀态) */ boolean isOpen(); /** * 在 {@link HystrixCommand} 成功執行時調用,作為處于半開狀态時的回報機制的一部分。 * <p> * 當斷路器處于半開狀态時,作為回報機制的一部分,從 HystrixCommand 的成功執行中調用。 */ void markSuccess(); /** * 在 {@link HystrixCommand} 執行不成功時調用,作為處于半開狀态時的回報機制的一部分。 * 當斷路器半開時,作為回報機制的一部分,它會從 HystrixCommand 執行不成功的調用。 */ void markNonSuccess(); /** * 在指令執行開始時調用以嘗試執行。這是非幂等的 - 它可能會修改内部狀态。 * <p> * 在指令執行開始時調用嘗試執行,主要使用的時間是判斷請求是否可以執行。這不是幂等的 - 它可能會修改内部狀态。 */
boolean attemptExecution();
} 斷路器的預設實作是它的内部類
/** * @ExcludeFromJavadoc * @ThreadSafe */ class Factory { // String類型的HystrixCommandKey.name()(我們不能直接使用 HystrixCommandKey,因為我們不能保證它正确實作了 hashcode/equals) private static ConcurrentHashMap<String, HystrixCircuitBreaker> circuitBreakersByCommand = new ConcurrentHashMap<String, HystrixCircuitBreaker>(); /** * 根據 HystrixCommandKey擷取HystrixCircuitBreaker * 擷取給定 {@link HystrixCommandKey} 的 {@link HystrixCircuitBreaker} 執行個體。 * <p> * 這是線程安全的,并確定每個 {@link HystrixCommandKey} 隻有 1 個 {@link HystrixCircuitBreaker}。 * * {@link HystrixCommand} 執行個體的 * @param key {@link HystrixCommandKey} 請求 {@link HystrixCircuitBreaker} * @param group Pass-thru to {@link HystrixCircuitBreaker} * @param properties Pass-thru to {@link HystrixCircuitBreaker} * @param metrics 傳遞到 {@link HystrixCircuitBreaker} * @return {@link HystrixCircuitBreaker} for {@link HystrixCommandKey} */ public static HystrixCircuitBreaker getInstance(HystrixCommandKey key, HystrixCommandGroupKey group, HystrixCommandProperties properties, HystrixCommandMetrics metrics) { // 根據 HystrixCommandKey 擷取斷路器 HystrixCircuitBreaker previousCached = circuitBreakersByCommand.get(key.name()); if (previouslyCached != null) { return previousCached; } // 如果我們到達這裡,這是第一次,是以我們需要初始化 // 建立并添加到映射中...使用 putIfAbsent 原子地處理 // 2個線程同時到達該點的可能會競争,是以采用ConcurrentHashMap 為我們提供線程安全 // 如果 2 個線程在這裡命中,則隻會添加一個線程,而另一個将獲得非空響應。 // 第一次沒有拿到斷路器,需要初始化 // 這裡直接使用concurrenchashmap的putIfAbsent方法。這是一個原子操作。如果這裡添加了兩個線程執行,那麼隻有一個線程會将值放入容器中 // 讓我們儲存鎖定步驟 HystrixCircuitBreaker cbForCommand = circuitBreakersByCommand.putIfAbsent(key.name(), new HystrixCircuitBreakerImpl(key, group, properties, metrics) ); if (cbForCommand == null) { // 這意味着 putIfAbsent 步驟剛剛建立了一個新的執行個體,是以讓我們再次檢索并傳回它 return circuitBreakersByCommand.get(key.name()); } else { // 這意味着發生了競争,并且在嘗試“放置”另一個之前到達那裡時 // 我們取而代之的是檢索它,現在将傳回它 return cbForCommand; } } /** * 根據HystrixCommandKey擷取HystrixCircuitBreaker。如果它不傳回 NULL * 擷取給定 {@link HystrixCommandKey} 的 {@link HystrixCircuitBreaker} 執行個體,如果不存在,則為 null。 * * {@link HystrixCommand} 執行個體的 @param key {@link HystrixCommandKey} 請求 {@link HystrixCircuitBreaker} * @return {@link HystrixCircuitBreaker} 為 {@link HystrixCommandKey} */ public static HystrixCircuitBreaker getInstance(HystrixCommandKey key) { return circuitBreakersByCommand.get(key.name()); } /** * 清除所有斷路器。如果新請求進來,執行個體将被重新建立。 * 清除所有斷路器。如果有新的請求,斷路器将重新建立并放置在容器中。 */ static void reset() { circuitBreakersByCommand.clear(); } }
/**
* 預設斷路器實作
* {@link HystrixCircuitBreaker} 的預設生産實作。
*
* @ExcludeFromJavadoc
* @ThreadSafe
*/
/* package */
class HystrixCircuitBreakerImpl implements HystrixCircuitBreaker {
private final HystrixCommandProperties properties;
私有的最終 HystrixCommandMetrics 名額;
enum Status {
// 斷路器狀态,閉合,斷開,半開
CLOSED, OPEN, HALF_OPEN;
}
// 指派不是線程安全的。如果想實作不加鎖,可以使用atomicreference<v>來更新對象引用的atom。
// AtomicReference原子引用保證Status的原子性修改
private final AtomicReference<Status> status = new AtomicReference<Status>(Status.CLOSED);
// 記錄斷路器分閘的時間點(時間戳)。如果時間大于0,則表示斷路器打開或半開
private final AtomicLong circuitOpened = new AtomicLong(-1);
private final AtomicReference<Subscription> activeSubscription = new AtomicReference<Subscription>(null);
protected HystrixCircuitBreakerImpl(HystrixCommandKey key, HystrixCommandGroupKey commandGroup, final HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
this.properties = properties;
this.metrics = metrics;
//在定時器上,這将在指令執行發生時設定開/關之間的電路
Subscription s = subscribeToStream();
activeSubscription.set(s);
}
private Subscription subscribeToStream() {
/*
* 此流将重新計算健康流中每個 onNext 的 OPEN/CLOSED 狀态
*/
return metrics.getHealthCountsStream()
.observe()
.subscribe(new Subscriber<HealthCounts>() {
@Override
public void onCompleted() {
}
public void onError(Throwable e) {
public void onNext(HealthCounts hc) {
// check if we are past the statisticalWindowVolumeThreshold
// Check the minimum number of requests in a time window
//檢查是否是超過statisticalWindowVolumeThreshold值
//檢查時間視窗請求的最小數目
if (hc.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {
// 當沒有超過統計視窗的最小量門檻值時,斷路器的狀态沒有變化。
// 如果它原來是被關閉,它保持關閉
// 如果它是半開的,我們需要等待一個成功的指令執行
// 如果它被打開,我們需要等待睡眠視窗過去
} else {
// 檢查錯誤比例門檻值
if (hc.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) {
//we are not past the minimum error threshold for the stat window,
// so no change to circuit status.
// if it was CLOSED, it stays CLOSED
// if it was half-open, we need to wait for a successful command execution
// if it was open, we need to wait for sleep window to elapse
// 當沒有超過統計視窗的最小錯誤門檻值時,電路狀态沒有變化。
// 如果它是 CLOSED,它保持 CLOSED
// 如果它是半開的,我們需要等待一個成功的指令執行
// 如果它是開放的,我們需要等待睡眠視窗過去
} else {
// 我們的失敗率太高,我們需要将狀态設定為 OPEN
if (status.compareAndSet(Status.CLOSED, Status.OPEN)) {
circuitOpened.set(System.currentTimeMillis());
}
}
}
});
@Override
public void markSuccess() {
// The circuit breaker is processing half open and the HystrixCommand is executed successfully. Set the status to off
//斷路器正在處理半開和HystrixCommand被成功執行。将狀态設定為關閉
if (status.compareAndSet(Status.HALF_OPEN, Status.CLOSED)) {
//這個線程獲得關閉電路的權限——它重置了流以從0重新開始
metrics.resetStream();
Subscription previousSubscription = activeSubscription.get();
if (previousSubscription != null) {
previousSubscription.unsubscribe();
}
Subscription newSubscription = subscribeToStream();
activeSubscription.set(newSubscription);
circuitOpened.set(-1L);
}
public void markNonSuccess() {
//該斷路器是半開和HystrixCommand被成功執行。将狀态設定為打開
if (status.compareAndSet(Status.HALF_OPEN, Status.OPEN)) {
//此線程赢得重新打開電路的競賽 - 它重置睡眠視窗的開始時間
circuitOpened.set(System.currentTimeMillis());
@Override
public boolean isOpen() {
// 擷取配置判斷斷路器是否處于強制斷開的狀态
if (properties.circuitBreakerForceOpen().get()) {
return true;
}
// 擷取配置判斷斷路器是否強制閉合的狀态
if (properties.circuitBreakerForceClosed().get()) {
return false;
return circuitOpened.get() >= 0;
public boolean allowRequest() {
//擷取配置來判斷斷路器是否被強制打開
if (properties.circuitBreakerForceOpen().get()) {
return false;
// Obtain the configuration to judge whether the circuit breaker is forced to close
// 擷取配置判斷斷路器是否強制閉合的
if (properties.circuitBreakerForceClosed().get()) {
return true;
if (circuitOpened.get() == -1) {
} else {
// If it is half open, the return does not allow Command execution
// 如果是半開,則傳回不允許指令執行
if (status.get().equals(Status.HALF_OPEN)) {
return false;
} else {
// Check if the sleep window is over
// 檢查睡眠視窗是否結束
return isAfterSleepWindow();
private boolean isAfterSleepWindow() {
final long circuitOpenTime = circuitOpened.get();
final long currentTime = System.currentTimeMillis();
// Gets the configured time window for sleep
// 擷取睡眠視窗配置的時間
final long sleepWindowTime = properties.circuitBreakerSleepWindowInMilliseconds().get();
return currentTime > circuitOpenTime + sleepWindowTime;
public boolean attemptExecution() {
// Obtain the configuration to judge whether the circuit breaker is forced to open
//擷取配置來判斷斷路器是否處于被強制打開的狀态
// 擷取判斷斷路器是否強制合閘的配置
if (isAfterSleepWindow()) {
//隻有在睡眠視窗時間過後的第一個請求才應該執行
//如果執行指令成功,狀态将轉換為CLOSED
//如果執行指令失敗,狀态将轉換為OPEN
//如果正在執行的指令被取消訂閱,狀态将轉換為 OPEN
if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN)) {
return true;
} else {
return false;
}
}
- ispen():判斷熔斷器開關是否為OPEN(如果是OPEN或者half_OPEN則傳回true,如果是CLOSE則傳回false。沒有副作用,是幂等的)。
- allowRequest():每個HystrixCommand請求詢問是否允許繼續(當斷路器開關閉合或下一個睡眠視窗傳回true時),它是幂等的,不修改任何内部狀态. 考慮到半開放的邏輯,當一個sleep window到來時,它會釋放一些請求給後續的邏輯。
- attemptExecution():在指令執行開始時調用以嘗試執行。主要是用時間來判斷請求是否可以執行。這是非幂等的,可能會修改内部狀态。需要注意的是,isOpen()和allowRequest()方法是幂等的,可以重複調用;attemptExecution()方法有副作用,不能重複調用。
使用 Zuul、Ribbon、Feign、Eureka 和 Sleuth、Zipkin 建立簡單spring cloud微服務用例-spring cloud 入門教程
微服務內建SPRING CLOUD SLEUTH、ELK 和 ZIPKIN 進行監控-spring cloud 入門教程
使用Hystrix 、Feign 和 Ribbon建構微服務-spring cloud 入門教程
使用 Spring Boot Admin 監控微服務-spring cloud 入門教程
基于Redis做Spring Cloud Gateway 中的速率限制實踐-spring cloud 入門教程
內建SWAGGER2服務-spring cloud 入門教程
Hystrix 簡介-spring cloud 入門教程
Hystrix 原理深入分析-spring cloud 入門教程
使用Apache Camel建構微服務-spring cloud 入門教程
內建 Kubernetes 來建構微服務-spring cloud 入門教程
內建SPRINGDOC OPENAPI 的微服務實踐-spring cloud 入門教程
SPRING CLOUD 微服務快速指南-spring cloud 入門教程
基于GraphQL的微服務實踐-spring cloud 入門教程
最火的Spring Cloud Gateway 為經過身份驗證的使用者啟用速率限制實踐-spring cloud 入門教程