18. SpringCloud之Hystrix源碼

image.png

1、前言

hystrix是通過對 接口方法 進(jìn)行AOP代理的方式 來 達(dá)到 對某一個請求 進(jìn)行熔斷降級的功能的。

所以,我們研究hystrix的核心源碼,首先是要找到這個切面在哪生成的,干了什么。

2、@EnableCircuitBreaker

我們引入hystrix的時候,除了導(dǎo)入依賴之外,還會在啟動類上加上@EnableCircuitBreaker注解表示啟用hystrix的熔斷降級等組件。

@Enable****之類的注解都表示啟動什么什么功能。點(diǎn)進(jìn)源碼看, 其實一般都會做一些導(dǎo)入類,注冊的事情。

比如@EnableCircuitBreaker 點(diǎn)進(jìn)去就會 import EnableCircuitBreakerImportSelector類。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableCircuitBreakerImportSelector.class)
public @interface EnableCircuitBreaker {

}

所以我們先看這個類干了什么、

2.1、EnableCircuitBreakerImportSelector

點(diǎn)進(jìn)源碼,發(fā)現(xiàn)集成了 SpringFactoryImportSelector類

@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableCircuitBreakerImportSelector extends
      SpringFactoryImportSelector<EnableCircuitBreaker> {

   @Override
   protected boolean isEnabled() {
      return getEnvironment().getProperty(
            "spring.cloud.circuit.breaker.enabled", Boolean.class, Boolean.TRUE);
   }

}

點(diǎn)進(jìn) SpringFactoryImportSelector 類

可以看到 實現(xiàn)了DeferredImportSelector接口,

DeferredImportSelector接口又 繼承 ImportSelector接口,實現(xiàn)selectImports方法。

image.png

實現(xiàn)ImportSelector接口的類主要就是用來 導(dǎo)入一些類的。

那么實現(xiàn)ImportSelector接口的類,被實例化bean的時候,會調(diào)到selectImports,傳入@Import它的那個類的元數(shù)據(jù)對象, 然后返回 一個 需要被 注冊到Spring容器中的 類全限定性名數(shù)組。

2.1.1、SPI機(jī)制

而SpringFactoryImportSelector 父類實現(xiàn)的selectImports 則會返回META-INF/Spring.factories里配置的 key = org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker 的 類的全限定性列表, 讓Spring 注冊這些類的實例。

image.png

2.1.2、META-INF/Spring.factories

這個文件在org.springframework.cloud:spring-cloud-netflix-core:2.0.0.RELEASE2包下

image.png

所以最終SpringFactoryImportSelector類的selectImports最終返回(導(dǎo)入)org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration這個類,讓Spring注冊它的實例。

3、切面的注冊

點(diǎn)進(jìn)org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration

可以看到用@Bean注冊了一個切面

image.png

就是我們平時用的aop,切入點(diǎn)為加了@HystrixCommand的方法, 我們使用hystrix的時候用的就是這個注解。

image.png

最終會用 環(huán)繞通知 @Around 來 對 加了@HystrixCommand的方法進(jìn)行 增強(qiáng)。那么hystrix對接口方法的 熔斷降級,服務(wù)隔離等功能就在這個methodsAnnotatedWithHystrixCommand方法里。

4、代理邏輯

接下來我們看下 環(huán)繞通知里 關(guān)于 熔斷降級,服務(wù)隔離的核心源碼。

CommandExecutor.execute 會返回結(jié)果,最終return出去,點(diǎn)進(jìn)去

image.png

里面分同步調(diào)用,異步調(diào)用。

image.png

異步調(diào)用的話,會調(diào)用HystrixExecutable的queue()方法,返回一個Future對象,異步執(zhí)行,不等待返回結(jié)果。

image.png

同步調(diào)用在調(diào)用queue方法返回Future對象之外, 還會調(diào)用Future對象的get方法,阻塞等待返回結(jié)果,以達(dá)到同步的效果。

image.png

所以,我們看queue()方法就好了。

HystrixCommand.queue()

點(diǎn)進(jìn)toObservable()方法

image.png

toObservable() 會創(chuàng)建一堆的匿名對象,我們看Hystrix主體的邏輯的那個

image.png

代碼applyHystrixSemantics對象的call方法,最終調(diào)用applyHystrixSemantics(_cmd)方法

點(diǎn)進(jìn)去。

image.png

這里就是Hystrix的核心邏輯所在。

4.1、判斷是否允許接受請求

circuitBreaker.attemptExecution()是用來判斷是否允許接受請求

點(diǎn)進(jìn)HystrixCircuitBreakerImpl實現(xiàn)的attemptExecution()方法

image.png

4.1.1、是否強(qiáng)制開啟斷路器和是否強(qiáng)制關(guān)閉斷路器

是否強(qiáng)制開啟斷路器和是否強(qiáng)制關(guān)閉斷路器都是 由我們的配置決定的,對應(yīng)HystrixCommandProperties的這兩個屬性。

image.png

默認(rèn)值都為false

image.png
image.png

4.1.2、滾動窗口時間的判斷

回顧一下熔斷機(jī)制里的滾動窗口時間的作用, 就是當(dāng) 熔斷開啟后, 拒絕請求,過了滾動窗口時間之后, 熔斷器狀態(tài)會變成 半開狀態(tài),然后下一次請求成功,則將熔斷器從半開狀態(tài)變?yōu)?關(guān)閉狀態(tài),如果請求失敗,則還是變?yōu)?開啟狀態(tài),拒絕請求。等再過了滾動窗口時間之后,又進(jìn)行這樣的機(jī)制,周而復(fù)始。

看下isAfterSleepWindow()源碼 對 是否需要開啟半開狀態(tài)的判斷

private boolean isAfterSleepWindow() {
    // 上一次熔斷器開啟的的時間
    final long circuitOpenTime = circuitOpened.get();
    // 當(dāng)前時間
    final long currentTime = System.currentTimeMillis();
    // 滾動窗口的時間
    final long sleepWindowTime = properties.circuitBreakerSleepWindowInMilliseconds().get();
    // 如果當(dāng)前時間 > 上一次 開啟熔斷器的時間 + 滾動窗口的時間,則返回ture,更新為半開狀態(tài)
    return currentTime > circuitOpenTime + sleepWindowTime;
}

這里滾動窗口時間也是一個配置

image.png

默認(rèn)5s

image.png
image.png

4.1.3、總結(jié)

允許接受請求的條件 :

  1. 如果配置強(qiáng)制關(guān)閉熔斷器
  2. 或者是 熔斷器關(guān)閉
  3. 或者是 熔斷器非關(guān)閉狀態(tài)下,上次開啟熔斷器的時間到現(xiàn)在,已經(jīng)過了滾動窗口時間,可以設(shè)置為半開狀態(tài)的話,則允許接受請求

不允許接受請求的條件 :

  1. 如果沒配置強(qiáng)制關(guān)閉熔斷器,配置了強(qiáng)制開啟熔斷器,
  2. 或者是熔斷器 打開,且上次開啟熔斷器的時間到現(xiàn)在,還沒有過滾動窗口時間

4.2、如果允許,那么則會嘗試結(jié)合隔離策略,判斷是否 真的會執(zhí)行業(yè)務(wù)方法。

image.png

4.2.1、信號量隔離

首先getExecutionSemaphore() 就會去獲取 隔離策略。

如果是信號量隔離,判斷是否存在信號量隔離對象, 沒有 就會創(chuàng)建一個TryableSemaphoreActual對象,并傳入最大請求數(shù)。有就直接返回。

如果不是信號量隔離,返回默認(rèn)的TryableSemaphore對象。

image.png

TryableSemaphoreActual 對象會有一個原子型的Integer計數(shù)器,這個就是用來記錄當(dāng)前正在并發(fā)處理的請求數(shù)的。接受請求,就+1,請求處理完就減1。tryAcquire方法里 會與最大允許請求數(shù)判斷,如果是否達(dá)到上線了,就不接受請求。信號量隔離的原理就是這樣。

image.png

如果是信號量隔離的話,那么就返回TryableSemaphoreActual對象,代碼回到外面。

會定義一個匿名類對象

image.png

這個對象的call方法 是請求處理完成后,用來 對TryableSemaphoreActual對象里的 計數(shù)器減一的。

image.png

4.2.1.1、判斷是否 接受請求

代碼接下來就調(diào)用TryableSemaphoreActual對象的tryAcquire方法判斷當(dāng)前是否正在處理的請求數(shù)是否大于 最大允許請求數(shù)。

image.png

點(diǎn)進(jìn)tryAcquire(),先對計數(shù)器+1,然后拿計數(shù)器加1后的值 與配置的最大允許請求數(shù)進(jìn)行比較。

image.png

4.2.1.2、計數(shù)器加過后的結(jié)果<= 配置的最大允許請求數(shù)

如果計數(shù)器加過后的結(jié)果大于 配置的最大允許請求數(shù),則允許接受請求。那么,接下來就會調(diào)用 業(yè)務(wù)方法。并且再調(diào)用業(yè)務(wù)方法完成后,會調(diào) 對計數(shù)器-1的那個匿名類對象 對計數(shù)器減1。


image.png

4.2.1.3、計數(shù)器加過后的結(jié)果> 配置的最大允許請求數(shù),降級

如果計數(shù)器加過后的結(jié)果大于 配置的最大允許請求數(shù),則不允許接受請求。走else, 拒絕請求。

image.png
image.png

然后 走降級方法

image.png

4.2.2、線程池隔離

前面講到 getExecutionSemaphore() 會獲取 隔離策略。如果不是信號量隔離的話,會返回TryableSemaphoreNoOp對象。

image.png

這個對象的tryAcquire()方法恒返回true, release()方法也是空的

image.png

就相當(dāng)于信號量隔離完全不起作用。

那么這里的判斷就一定會走這里,因為TryableSemaphoreNoOp對象的tryAcquire() 恒返回true。代碼會往下執(zhí)行。

image.png

4.2.2.1、業(yè)務(wù)方法的調(diào)用

點(diǎn)進(jìn)executeCommandAndObserve(_cmd),看executeCommandWithSpecifiedIsolation(_cmd)方法

image.png

再看return的 getUserExecutionObservable(_cmd)方法,點(diǎn)進(jìn)getExecutionObservable()

image.png

選擇HystrixCommand實現(xiàn)類

image.png

Run

image.png
image.png

點(diǎn)execute方法

image.png

選擇MethodExecutionAction,點(diǎn)進(jìn)executeWithArgs方法

image.png

再點(diǎn)execute

image.png

最終反射調(diào)用method

image.png

4.2.2.2、如果線程池滿了就會走降級方法。

4.3、如果不允許接受請求,降級

  1. 如果是不允許的話,則會走else,調(diào)用handleShortCircuitViaFallback()方法 ,調(diào)用降級方法。

    image.png
image.png

4.4、降級總結(jié)

比如熔斷器開啟,線程池,信號量都滿了,則會走到降級方法,也是會反射調(diào)用到 fallback 方法,fallback 降級方法也是有信號量和線程池的大小控制 的,也就是信號量或線程池是多少大小,fallback 降級方法也會接收多少降級的請求。

如果調(diào)用降級方法的信號量或線程池 都滿了,則拋出響應(yīng)的異常信息

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容