Spring Cloud 原始碼學習之 Hystrix 入門

weixin_33806914發表於2018-09-16

歡迎訪問陳同學部落格原文
Hystrix 功能非常多,本文僅對 Hystrix 原始碼做入門學習。為便於閱讀,文中原始碼有較大刪減,僅保留入門學習必要的原始碼,降低其他邏輯的干擾。

從 Hystrix 名字說起

Spring Cloud 眾多元件,瞭解其名字背後的寓意也是一種樂趣。

下面是我拼的一張圖,分別為:Hystrix、豪豬、刺蝟

Hystrix 譯為 "豪豬",豪豬以棘刺聞名,集肉用、藥用、欣賞價值於一體。刺蝟的小短刺和豪豬長矛比起來,根本不在同一個level。超市中70塊一斤的豬肉指不定就是豪豬,當然,也可能是丁磊家的黑豬。

豪豬的棘刺能保護自己不受天敵傷害,代表了強大的防禦能力。Netflix 將該元件取名為 Hystrix,宣言為 "defend your app",寓意應該是:當系統受到傷害時,能夠像豪豬的棘刺一樣保護系統

Spring Cloud Hystrix 基於 Netflix Hystrix 實現,具備服務降級、服務熔斷、執行緒與訊號隔離、請求快取、請求合併以及服務監控等強大功能。

入門學習素材

本文使用下面的樣例程式碼來做原始碼學習。

ServiceA 中 hello() 方法由 @HystrixCommand 註解標記,呼叫 ServiceB 的 hello() 介面。若呼叫失敗,則執行 error() 方法。

@HystrixCommand(fallbackMethod = "error")
public String hello() {
    return restTemplate.getForEntity("http://serviceB/hello", String.class).getBody();
}

public String error() {
    return "error";
}

ServiceB hello() 丟擲異常,以便 ServiceA執行 error() 方法。

@GetMapping("/hello")
public String hello() {
    throw new RuntimeException("error occurred");
}

樣例程式碼表示的就是 服務降級,服務降級換些名詞來描述就是:B計劃、應急預案、備用方案、替補,以便在出現問題時,"預備隊"可以立馬頂上。

有時,技術名詞晦澀難懂,但經驗與智慧都來自於現實世界。

程式碼執行入口

Spring 中也有一種類似 Java SPI 的載入機制,允許在 META-INF/spring.factories 檔案中配置介面實現類,Spring 會自動處理。開發人員僅需引入 jar 包,就能達到插拔式效果,十分方便。

引入 spring-cloud-starter-hystrix 依賴,spring-cloud-netflix-core 的 jar 包中包含 spring.factories 檔案,其中有 Hytrix 和 其他元件相關配置。

HystrixCircuitBreakerConfiguration 中,注入了 HystrixCommandAspect

@Bean
public HystrixCommandAspect hystrixCommandAspect() {
    return new HystrixCommandAspect();
}    

HystrixCommandAspect 用於處理被註解 @HystrixCommand 標記的方法。通過名字和下面程式碼可以知道,Hystrix 基於 AOP 機制實現,對目標方法做了代理,然後實現了自己一系列功能特性。

@Aspect
public class HystrixCommandAspect {
    @Pointcut("@annotation(...annotation.HystrixCommand)")
    public void hystrixCommandAnnotationPointcut() {
    }

    @Around("hystrixCommandAnnotationPointcut()")
    public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {
    }
}

處理邏輯就在 methodsAnnotatedWithHystrixCommand() 中。

處理邏輯

methodsAnnotatedWithHystrixCommand() 用來執行目標方法,Hystrix 將需要執行的Method(如ServiceA的hello() ) 最終封裝成了 HystrixInvokable 來執行。

public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {
    // 被@HystrixCommand標記的hello()方法
    Method method = getMethodFromTarget(joinPoint);
    MetaHolderFactory metaHolderFactory = ...get(HystrixPointcutType.of(method));
    MetaHolder metaHolder = metaHolderFactory.create(joinPoint);
    // 準備各種材料後,建立HystrixInvokable
    HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);

    Object result;
    try {
        if (!metaHolder.isObservable()) {
            // 利用工具CommandExecutor來執行
            result = CommandExecutor.execute(invokable, executionType, metaHolder);
        }
    } 
    return result;
}

HystrixInvokable 只是一個空介面,沒有任何方法,只是用來標記具備可執行的能力。

HystrixInvokable 又是如何建立的?它具體的實現類又是什麼?先看看 HystrixCommandFactory.getInstance().create() 的程式碼。

public HystrixInvokable create(MetaHolder metaHolder) {
    return new GenericCommand(...create(metaHolder));
}

實現類是 GenericCommand,我們看看類圖。

194948103f5a4a0491e7326a70c50c59.png

三個抽象父類 AbstractHystrixCommand、HystrixCommand、AbstractCommand 幫助 GenericCommand 做了不少公共的事情,而 GenericCommand 負責執行具體的方法和fallback時的方法。

// 執行具體的方法,如:ServiceA的hello()
protected Object run() throws Exception {
    return process(new Action() {
        @Override
        Object execute() {
            return getCommandAction().execute(getExecutionType());
        }
    });
}
// 執行fallback方法,如:ServiceA的error()
protected Object getFallback() {
    final CommandAction commandAction = getFallbackAction();
        return process(new Action() {
            @Override
            Object execute() {
                MetaHolder metaHolder = commandAction.getMetaHolder();
                Object[] args = createArgsForFallback(...);
                return commandAction.executeWithArgs(..., args);
            }
        });
}

目標方法執行細節

執行過程想來應該很簡單,即先執行目標方法,失敗則執行fallback方法。

再來看看 methodsAnnotatedWithHystrixCommand() 的具體執行程式碼,它完成了 Hystrix 的整個執行過程。

Object result = CommandExecutor.execute(invokable, executionType, metaHolder);

CommandExecutor.execute() 首先將 invokable 轉換為 HystrixExecutable,再執行 HystrixExecutable 的execute() 方法。

public static Object execute(HystrixInvokable invokable, ExecutionType executionType, MetaHolder metaHolder) throws RuntimeException {
    switch (executionType) {
        // 這裡僅貼出這一種case  
        case SYNCHRONOUS: {
            // 轉為 HystrixExecutable 並執行
            return castToExecutable(invokable, executionType).execute();
        }
    }
}

HystrixExecutable 的 execute() 方法由 HystrixCommand.execute() 實現,程式碼如下:

public R execute() {
    // 呼叫下面的queue()
    return queue().get();
}

public Future<R> queue() {
    final Future<R> delegate = toObservable().toBlocking().toFuture();
    final Future<R> f = new Future<R>() { ... };

    if (f.isDone()) {
        try {
            f.get();
            return f;
        }
    }

    return f;
}

利用 JUC 的 Future 來非同步執行,通過 f.get() 來獲取 hello() 方法的執行結果。Hystrix 結合了 RxJava 來實現非同步程式設計,我做了下除錯,看了stackframe,執行過程層層呼叫,略微噁心。RxJava 有點複雜,同時也需要了解響應式程式設計模型,這裡直接跳過。

ServiceA 的 hello() 還是由 GenericCommand 來執行的,如下圖,getCommandAction() 這個 CommandAction 指的就是被執行的hello()方法,利用Java反射機制來執行。

上圖右邊部分標記出來的就是 RxJava 中的部分呼叫鏈,下面的截圖簡單展示下最後的呼叫。

OnSubscribeDefer.call() -> HystrixCommand.getExecutionObservable() -> GenericCommand.run()

小結

本文只是一個簡單小例子,沒有涉及到 Hystrix 的其他特性,後面將接著學習。另,Hystrix 的官方 Wiki 是非常好的學習材料。


歡迎關注陳同學的公眾號,一起學習,一起成長

相關文章