【一塊兒學源碼-微服務】Hystrix 源碼一:Hystrix基礎原理與Demo搭建

說明

原創不易,如若轉載 請標明來源!java

歡迎關注本人微信公衆號:壹枝花算不算浪漫
更多內容也可查看本人博客:一枝花算不算浪漫git

前言

前情回顧

上一個系列文章講解了Feign的源碼,主要是Feign動態代理實現的原理,及配合Ribbon實現負載均衡的機制。github

這裏咱們講解一個新的組件Hystrix,也是和Feign進行融合的。spring

本講目錄

這一講開始講解Hystrix相關代碼,固然仍是基於上一個組件Feign的基礎上開始講解的,這裏默認你對Feign已經有了大體的瞭解。編程

使用過spring cloud的小夥伴對這個組件都不會陌生,Hystrix是保證系統高可用一個很重要的組件,主要提供一下幾個功能:緩存

  1. 對依賴服務調用時出現的調用延遲和調用失敗進行控制和容錯保護
  2. 在複雜的分佈式系統中,阻止某一個依賴服務的故障在整個系統中蔓延,服務A->服務B->服務C,服務C故障了,服務B也故障了,服務A故障了,整套分佈式系統所有故障,總體宕機
  3. 提供fail-fast(快速失敗)和快速恢復的支持
  4. 提供fallback優雅降級的支持
  5. 支持近實時的監控、報警以及運維操做

目錄以下:微信

  1. Hystrix基礎原理
  2. Hystrix Demo搭建
  3. Hystrix源碼閱讀及調試說明
  4. Hystrix入口程序初探

組件分析

Hystrix基礎原理

命令模式

將全部請求外部系統(或者叫依賴服務)的邏輯封裝到 HystrixCommand 或者 HystrixObservableCommand 對象中。網絡

Run()方法爲實現業務邏輯,這些邏輯將會在獨立的線程中被執行當請求依賴服務時出現拒絕服務、超時或者短路(多個依賴服務順序請求,前面的依賴服務請求失敗,則後面的請求不會發出)時,執行該依賴服務的失敗回退邏輯(Fallback)。併發

隔離策略

Hystrix 爲每一個依賴項維護一個小線程池(或信號量);若是它們達到設定值(觸發隔離),則發往該依賴項的請求將當即被拒絕,執行失敗回退邏輯(Fallback),而不是排隊。app

隔離策略分線程隔離和信號隔離。

  1. 線程隔離

    第三方客戶端(執行Hystrix的run()方法)會在單獨的線程執行,會與調用的該任務的線程進行隔離,以此來防止調用者調用依賴所消耗的時間過長而阻塞調用者的線程。

    使用線程隔離的好處:

    • 應用程序能夠不受失控的第三方客戶端的威脅,若是第三方客戶端出現問題,能夠經過降級來隔離依賴。
    • 當失敗的客戶端服務恢復時,線程池將會被清除,應用程序也會恢復,而不至於使整個Tomcat容器出現故障。
    • 若是一個客戶端庫的配置錯誤,線程池能夠很快的感知這一錯誤(經過增長錯誤比例,延遲,超時,拒絕等),並能夠在不影響應用程序的功能狀況下來處理這些問題(能夠經過動態配置來進行實時的改變)。
    • 若是一個客戶端服務的性能變差,能夠經過改變線程池的指標(錯誤、延遲、超時、拒絕)來進行屬性的調整,而且這些調整能夠不影響其餘的客戶端請求。
    • 簡而言之,由線程供的隔離功能可使客戶端和應用程序優雅的處理各類變化,而不會形成中斷。

    線程池的缺點

    • 線程最主要的缺點就是增長了CPU的計算開銷,每一個command都會在單獨的線程上執行,這樣的執行方式會涉及到命令的排隊、調度和上下文切換。

    • Netflix在設計這個系統時,決定接受這個開銷的代價,來換取它所提供的好處,而且認爲這個開銷是足夠小的,不會有重大的成本或者是性能影響。

  2. 信號隔離

    信號隔離是經過限制依賴服務的併發請求數,來控制隔離開關。信號隔離方式下,業務請求線程和執行依賴服務的線程是同一個線程(例如Tomcat容器線程)。

觀察者模式
  • Hystrix經過觀察者模式對服務進行狀態監聽
  • 每一個任務都包含有一個對應的Metrics,全部Metrics都由一個ConcurrentHashMap來進行維護,Key是CommandKey.name()
  • 在任務的不一樣階段會往Metrics中寫入不一樣的信息,Metrics會對統計到的歷史信息進行統計彙總,供熔斷器以及Dashboard監控時使用
Metrics
  • Metrics內部又包含了許多內部用來管理各類狀態的類,全部的狀態都是由這些類管理的
  • 各類狀態的內部也是用ConcurrentHashMap來進行維護的
熔斷機制

熔斷機制是一種保護性機制,當系統中某個服務失敗率太高時,將開啓熔斷器,對該服務的後續調用,直接拒絕,進行Fallback操做。

熔斷所依靠的數據便是Metrics中的HealthCount所統計的錯誤率。

如何判斷是否應該開啓熔斷器?

必須同時知足兩個條件:

  1. 請求數達到設定的閥值;
  2. 請求的失敗數 / 總請求數 > 錯誤佔比閥值%。
降級策略

當construct()或run()執行失敗時,Hystrix調用fallback執行回退邏輯,回退邏輯包含了通用的響應信息,這些響應從內存緩存中或者其餘固定邏輯中獲得,而不該有任何的網絡依賴。

若是必定要在失敗回退邏輯中包含網絡請求,必須將這些網絡請求包裝在另外一個 HystrixCommand 或 HystrixObservableCommand 中,即屢次降級。

失敗降級也有頻率限時,若是同一fallback短期請求過大,則會拋出拒絕異常。

緩存機制

同一對象的不一樣HystrixCommand實例,只執行一次底層的run()方法,並將第一個響應結果緩存起來,其後的請求都會從緩存返回相同的數據。

因爲請求緩存位於construct()或run()方法調用以前,因此,它減小了線程的執行,消除了線程、上下文等開銷。

Hystrix基礎原理總結

用一張簡單地流程圖總結:

Hystrix基礎原理.jpg

Hystrix Demo搭建

Demo工程仍是使用以前的項目,git地址:https://github.com/barrywangmeng/spring-cloud-learn

eureka-server:註冊中心
serviceA: 提供對外接口
serviceB: 經過feign調用serviceA接口

在serviceB項目中添加hystrix相關pom依賴及配置,這裏就不列出來了,小夥伴們能夠直接下載這個項目看一下。

接着就是改造對serviceA調用的FeignClient:

image.png

image.png

咱們能夠調整serviceB中feign調用超時時間配置類模擬觸發Hystrix降級邏輯:

image.png

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,來看下它的構造參數:

  1. target:你要調用的服務,這裏是HardCodedTarget,裏面包含服務名稱等信息
  2. dispatch:map,接口的每一個方法的Method對象 -> SynchronousMethodHandler
  3. setterFactory:空
  4. nullableFallbackFactory:咱們給的那個降級對象的工程,fallback工程

image.png

接下來還設置了contract信息,Contract是解析第三方註解的組件,設置爲了HystrixDelegatingContract,顧名思義,就是說,設置了這個組件以後,後面就能夠解析你在各個接口上hystrix相關的一些註解。

總結

上面已經分析了Hystrix基礎原理與Demo的搭建,基礎原理中用一張簡單地圖畫了Hystrix實現的流程,後面會更加詳細的依據這個圖進行講解。

申明

本文章首發自本人博客:https://www.cnblogs.com/wang-meng 和公衆號:壹枝花算不算浪漫,如若轉載請標明來源!

感興趣的小夥伴可關注我的公衆號:壹枝花算不算浪漫

WechatIMG33.jpeg

相關文章
相關標籤/搜索