
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方法。

實現(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 注冊這些類的實例。

2.1.2、META-INF/Spring.factories
這個文件在org.springframework.cloud:spring-cloud-netflix-core:2.0.0.RELEASE2包下

所以最終SpringFactoryImportSelector類的selectImports最終返回(導(dǎo)入)org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration這個類,讓Spring注冊它的實例。
3、切面的注冊
點(diǎn)進(jìn)org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration
可以看到用@Bean注冊了一個切面

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

最終會用 環(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)去

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

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

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

所以,我們看queue()方法就好了。
HystrixCommand.queue()
點(diǎn)進(jìn)toObservable()方法

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

代碼applyHystrixSemantics對象的call方法,最終調(diào)用applyHystrixSemantics(_cmd)方法
點(diǎn)進(jìn)去。

這里就是Hystrix的核心邏輯所在。
4.1、判斷是否允許接受請求
circuitBreaker.attemptExecution()是用來判斷是否允許接受請求
點(diǎn)進(jìn)HystrixCircuitBreakerImpl實現(xiàn)的attemptExecution()方法

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

默認(rèn)值都為false


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;
}
這里滾動窗口時間也是一個配置

默認(rèn)5s


4.1.3、總結(jié)
允許接受請求的條件 :
- 如果配置強(qiáng)制關(guān)閉熔斷器
- 或者是 熔斷器關(guān)閉
- 或者是 熔斷器非關(guān)閉狀態(tài)下,上次開啟熔斷器的時間到現(xiàn)在,已經(jīng)過了滾動窗口時間,可以設(shè)置為半開狀態(tài)的話,則允許接受請求
不允許接受請求的條件 :
- 如果沒配置強(qiáng)制關(guān)閉熔斷器,配置了強(qiáng)制開啟熔斷器,
- 或者是熔斷器 打開,且上次開啟熔斷器的時間到現(xiàn)在,還沒有過滾動窗口時間
4.2、如果允許,那么則會嘗試結(jié)合隔離策略,判斷是否 真的會執(zhí)行業(yè)務(wù)方法。

4.2.1、信號量隔離
首先getExecutionSemaphore() 就會去獲取 隔離策略。
如果是信號量隔離,判斷是否存在信號量隔離對象, 沒有 就會創(chuàng)建一個TryableSemaphoreActual對象,并傳入最大請求數(shù)。有就直接返回。
如果不是信號量隔離,返回默認(rèn)的TryableSemaphore對象。

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

如果是信號量隔離的話,那么就返回TryableSemaphoreActual對象,代碼回到外面。
會定義一個匿名類對象

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

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

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

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

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


然后 走降級方法

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

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

就相當(dāng)于信號量隔離完全不起作用。
那么這里的判斷就一定會走這里,因為TryableSemaphoreNoOp對象的tryAcquire() 恒返回true。代碼會往下執(zhí)行。

4.2.2.1、業(yè)務(wù)方法的調(diào)用
點(diǎn)進(jìn)executeCommandAndObserve(_cmd),看executeCommandWithSpecifiedIsolation(_cmd)方法

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

選擇HystrixCommand實現(xiàn)類

Run


點(diǎn)execute方法

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

再點(diǎn)execute

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

4.2.2.2、如果線程池滿了就會走降級方法。
4.3、如果不允許接受請求,降級
-
如果是不允許的話,則會走else,調(diào)用handleShortCircuitViaFallback()方法 ,調(diào)用降級方法。
image.png

4.4、降級總結(jié)
比如熔斷器開啟,線程池,信號量都滿了,則會走到降級方法,也是會反射調(diào)用到 fallback 方法,fallback 降級方法也是有信號量和線程池的大小控制 的,也就是信號量或線程池是多少大小,fallback 降級方法也會接收多少降級的請求。
如果調(diào)用降級方法的信號量或線程池 都滿了,則拋出響應(yīng)的異常信息
