說明
原創不易,如若轉載 請標明來源!
歡迎關注本人微信公眾號:壹枝花算不算浪漫
更多內容也可查看本人博客:一枝花算不算浪漫
前言
前情回顧
上一個系列文章講解了Feign的源碼,主要是Feign動態代理實現的原理,及配合Ribbon實現負載均衡的機制。
這里我們講解一個新的組件Hystrix,也是和Feign進行融合的。
本講目錄
這一講開始講解Hystrix相關代碼,當然還是基於上一個組件Feign的基礎上開始講解的,這里默認你對Feign已經有了大致的了解。
使用過spring cloud的小伙伴對這個組件都不會陌生,Hystrix是保證系統高可用一個很重要的組件,主要提供一下幾個功能:
- 對依賴服務調用時出現的調用延遲和調用失敗進行控制和容錯保護
- 在復雜的分布式系統中,阻止某一個依賴服務的故障在整個系統中蔓延,服務A->服務B->服務C,服務C故障了,服務B也故障了,服務A故障了,整套分布式系統全部故障,整體宕機
- 提供fail-fast(快速失敗)和快速恢復的支持
- 提供fallback優雅降級的支持
- 支持近實時的監控、報警以及運維操作
目錄如下:
- Hystrix基礎原理
- Hystrix Demo搭建
- Hystrix源碼閱讀及調試說明
- Hystrix入口程序初探
組件分析
Hystrix基礎原理
命令模式
將所有請求外部系統(或者叫依賴服務)的邏輯封裝到 HystrixCommand 或者 HystrixObservableCommand 對象中。
Run()方法為實現業務邏輯,這些邏輯將會在獨立的線程中被執行當請求依賴服務時出現拒絕服務、超時或者短路(多個依賴服務順序請求,前面的依賴服務請求失敗,則后面的請求不會發出)時,執行該依賴服務的失敗回退邏輯(Fallback)。
隔離策略
Hystrix 為每個依賴項維護一個小線程池(或信號量);如果它們達到設定值(觸發隔離),則發往該依賴項的請求將立即被拒絕,執行失敗回退邏輯(Fallback),而不是排隊。
隔離策略分線程隔離和信號隔離。
-
線程隔離
第三方客戶端(執行Hystrix的run()方法)會在單獨的線程執行,會與調用的該任務的線程進行隔離,以此來防止調用者調用依賴所消耗的時間過長而阻塞調用者的線程。
使用線程隔離的好處:
- 應用程序可以不受失控的第三方客戶端的威脅,如果第三方客戶端出現問題,可以通過降級來隔離依賴。
- 當失敗的客戶端服務恢復時,線程池將會被清除,應用程序也會恢復,而不至於使整個Tomcat容器出現故障。
- 如果一個客戶端庫的配置錯誤,線程池可以很快的感知這一錯誤(通過增加錯誤比例,延遲,超時,拒絕等),並可以在不影響應用程序的功能情況下來處理這些問題(可以通過動態配置來進行實時的改變)。
- 如果一個客戶端服務的性能變差,可以通過改變線程池的指標(錯誤、延遲、超時、拒絕)來進行屬性的調整,並且這些調整可以不影響其他的客戶端請求。
- 簡而言之,由線程供的隔離功能可以使客戶端和應用程序優雅的處理各種變化,而不會造成中斷。
線程池的缺點
-
線程最主要的缺點就是增加了CPU的計算開銷,每個command都會在單獨的線程上執行,這樣的執行方式會涉及到命令的排隊、調度和上下文切換。
-
Netflix在設計這個系統時,決定接受這個開銷的代價,來換取它所提供的好處,並且認為這個開銷是足夠小的,不會有重大的成本或者是性能影響。
-
信號隔離
信號隔離是通過限制依賴服務的並發請求數,來控制隔離開關。信號隔離方式下,業務請求線程和執行依賴服務的線程是同一個線程(例如Tomcat容器線程)。
觀察者模式
- Hystrix通過觀察者模式對服務進行狀態監聽
- 每個任務都包含有一個對應的Metrics,所有Metrics都由一個ConcurrentHashMap來進行維護,Key是CommandKey.name()
- 在任務的不同階段會往Metrics中寫入不同的信息,Metrics會對統計到的歷史信息進行統計匯總,供熔斷器以及Dashboard監控時使用
Metrics
- Metrics內部又包含了許多內部用來管理各種狀態的類,所有的狀態都是由這些類管理的
- 各種狀態的內部也是用ConcurrentHashMap來進行維護的
熔斷機制
熔斷機制是一種保護性機制,當系統中某個服務失敗率過高時,將開啟熔斷器,對該服務的后續調用,直接拒絕,進行Fallback操作。
熔斷所依靠的數據即是Metrics中的HealthCount所統計的錯誤率。
如何判斷是否應該開啟熔斷器?
必須同時滿足兩個條件:
- 請求數達到設定的閥值;
- 請求的失敗數 / 總請求數 > 錯誤占比閥值%。
降級策略
當construct()或run()執行失敗時,Hystrix調用fallback執行回退邏輯,回退邏輯包含了通用的響應信息,這些響應從內存緩存中或者其他固定邏輯中得到,而不應有任何的網絡依賴。
如果一定要在失敗回退邏輯中包含網絡請求,必須將這些網絡請求包裝在另一個 HystrixCommand 或 HystrixObservableCommand 中,即多次降級。
失敗降級也有頻率限時,如果同一fallback短時間請求過大,則會拋出拒絕異常。
緩存機制
同一對象的不同HystrixCommand實例,只執行一次底層的run()方法,並將第一個響應結果緩存起來,其后的請求都會從緩存返回相同的數據。
由於請求緩存位於construct()或run()方法調用之前,所以,它減少了線程的執行,消除了線程、上下文等開銷。
Hystrix基礎原理總結
用一張簡單地流程圖總結:
Hystrix Demo搭建
Demo工程還是使用之前的項目,git地址:https://github.com/barrywangmeng/spring-cloud-learn
eureka-server:注冊中心
serviceA: 提供對外接口
serviceB: 通過feign調用serviceA接口
在serviceB項目中添加hystrix相關pom依賴及配置,這里就不列出來了,小伙伴們可以直接下載這個項目看一下。
接着就是改造對serviceA調用的FeignClient:
我們可以調整serviceB中feign調用超時時間配置類模擬觸發Hystrix降級邏輯:
Hystrix源碼閱讀及調試說明
我們在調試的過程中,為了方便走正常不降級邏輯的debug調試,特地會修改feign及hystrix的超時時間。
因為hystrix中大量使用了響應式編程(rxJava),代碼中包含大量的觀察者模式設計,各種回調函數糅雜在一起,所以代碼顯得很難懂。
這里我們不糾結更多的rxJava源碼,為了調試,每個回調方法都會打上斷點。
關於Hystrix daboard相關的內容這里也不會講解,實際項目中會使用其他第三方組件來做服務監控,這里不做更多研究。
Hystrix入口程序初探
之前我們講過,如果不配置feign.hystrix.enabled:true這個配置的話,默認用的是DefaultTargeter
, 配置了的話就改變為HystrixTargeter
。
我們來看看HystrixTargeter.target()
方法:
class HystrixTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
// 里面包含encoder、decoder等feign的組件信息
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
// factory.getName: serviceA 返回的setterFactory 是null
SetterFactory setterFactory = getOptional(factory.getName(), context,
SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
// 獲取設置的feignClient的fallback屬性
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(factory.getName(), context, target, builder, fallback);
}
// 獲取設置的feignClient的fallbackFactory屬性
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
// 配置了降級factory的話,直接進入這個邏輯
return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
}
return feign.target(target);
}
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
Target.HardCodedTarget<T> target,
HystrixFeign.Builder builder,
Class<?> fallbackFactoryClass) {
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>)
getFromContext("fallbackFactory", feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
// 調用我們自定義的fallback工廠中的create方法
Object exampleFallback = fallbackFactory.create(new RuntimeException());
Assert.notNull(exampleFallback,
String.format(
"Incompatible fallbackFactory instance for feign client %s. Factory may not produce null!",
feignClientName));
// target.type() 就是ServiceAFeignClient 這個feignClient接口的名稱 這里就是做些判斷
if (!target.type().isAssignableFrom(exampleFallback.getClass())) {
throw new IllegalStateException(
String.format(
"Incompatible fallbackFactory instance for feign client %s. Factory produces instances of '%s', but should produce instances of '%s'",
feignClientName, exampleFallback.getClass(), target.type()));
}
// 執行HystrixFeign中的target方法
return builder.target(target, fallbackFactory);
}
}
我們設置的這個FallbackFactory負責在每次超時、拒絕(線程池滿)、異常的時候,create()方法返回一個降級機制的對象
從服務(ServiceA)的獨立的spring容器中取出來一個獨立的FallbackFactory,調用每個服務的時候,他對應的FallbackFactory都是存在於那個服務關聯的獨立的spring容器中的。
接着進入到Hystrix.target()
中:
public final class HystrixFeign {
public static Builder builder() {
return new Builder();
}
public static final class Builder extends Feign.Builder {
private Contract contract = new Contract.Default();
private SetterFactory setterFactory = new SetterFactory.Default();
/**
* @see #target(Class, String, FallbackFactory)
*/
public <T> T target(Target<T> target, FallbackFactory<? extends T> fallbackFactory) {
return build(fallbackFactory).newInstance(target);
}
Feign build(final FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override public InvocationHandler create(Target target,
Map<Method, MethodHandler> dispatch) {
// 設置invocationHandlerFactory為HystrixInvocationHandler
return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory);
}
});
// 設置contact為HystrixDelegatingContract
super.contract(new HystrixDelegatingContract(contract));
// 調用父類的build方法
return super.build();
}
}
}
public class ReflectiveFeign extends Feign {
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if(Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// 和之前一樣,生成一個JDK動態代理對象
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
}
最終實際用來去處理這個請求的,其實是InvocationHandler,他是JDK動態代理的核心,基於JDK動態代理機制,生成一個動態代理的對象之后,對這個對象所有的方法調用,都會走關聯的那個InvocationHandler。
我們這里設置的是HystrixInvocationHandler
,來看下它的構造參數:
- target:你要調用的服務,這里是HardCodedTarget,里面包含服務名稱等信息
- dispatch:map,接口的每個方法的Method對象 -> SynchronousMethodHandler
- setterFactory:空
- nullableFallbackFactory:我們給的那個降級對象的工程,fallback工程
接下來還設置了contract信息,Contract是解析第三方注解的組件,設置為了HystrixDelegatingContract,顧名思義,就是說,設置了這個組件之后,后面就可以解析你在各個接口上hystrix相關的一些注解。
總結
上面已經分析了Hystrix基礎原理與Demo的搭建,基礎原理中用一張簡單地圖畫了Hystrix實現的流程,后面會更加詳細的依據這個圖進行講解。
申明
本文章首發自本人博客:https://www.cnblogs.com/wang-meng 和公眾號:壹枝花算不算浪漫,如若轉載請標明來源!
感興趣的小伙伴可關注個人公眾號:壹枝花算不算浪漫