天天看點

Hystrix 原理深入分析-spring cloud 入門教程

Hystrix 的運作原理

Hystrix 原理深入分析-spring cloud 入門教程
  1. 構造一個 HystrixCommand 或 HystrixObservableCommand 對象
  2. 執行指令。
  3. 檢查緩存是否被命中,如果命中則直接傳回。
  4. 檢查斷路器開關是否斷開。如果是開路,則直接熔斷,經過回退邏輯。
  5. 檢查線程池/隊列/信号量是否已滿。如果線程池/隊列/信号量已滿,則直接拒絕請求并遵循回退邏輯。
  6. 如果不滿足上述條件,則調用 HYST rixObservableCommand.construct() 方法 HystrixCommand.run Method() 執行業務邏輯。
  7. 判斷業務邏輯方法運作是否有異常或逾時。如果是這樣,它将直接降級并使用回退邏輯。
  8. 上報統計資料,由使用者計算斷路器狀态。
  9. 傳回結果

從流程圖中可以發現錯誤統計隻有在5和7的情況下才會上報。

斷路器的工作原理

Hystrix 原理深入分析-spring cloud 入門教程

斷路器的開關控制邏輯如下:

  1. 在一個統計時間視窗(HYSTrixCommandProperties.metricsRollingStatisticalWindowInMilliseconds())内,處理的請求數達到設定的最小門檻值(HYST)rixCommandProperties.circuitBreakerRequestVolumeThreshold()),錯誤百分比超過設定的最大門檻值(HYSTrixCommandProperties.circuitBreakerThreshold() ) )此時斷路器分閘,斷路器狀态由合閘切換為分閘。
  2. 當斷路器斷開時,它将直接融斷所有請求(快速失敗)并經過回退邏輯。
  3. 經過一個休眠視窗時間(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 入門教程

繼續閱讀