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

  • 2020 年 2 月 13 日
  • 筆記

說明

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

前言

前情回顧

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

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

本講目錄

這一講開始講解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),而不是排隊。

隔離策略分執行緒隔離和訊號隔離。

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

  1. target:你要調用的服務,這裡是HardCodedTarget,裡面包含服務名稱等資訊
  2. dispatch:map,介面的每個方法的Method對象 -> SynchronousMethodHandler
  3. setterFactory:空
  4. nullableFallbackFactory:我們給的那個降級對象的工程,fallback工程

接下來還設置了contract資訊,Contract是解析第三方註解的組件,設置為了HystrixDelegatingContract,顧名思義,就是說,設置了這個組件之後,後面就可以解析你在各個介面上hystrix相關的一些註解。

總結

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

申明

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