分佈式服務彈性框架「Hystrix」實踐與源碼研究(一)

文章初衷

爲了應對未來在線(特別是無線端)業務量的成倍增加,後端服務的分佈式化程度須要不斷提升,對於服務的延遲和容錯管理將面臨更大挑戰,公司框架和開源團隊選擇內部推廣Netflix的Hystrix,一是爲了推動各部門的服務使用覆蓋率,二是爲了增長C Sharp語言版本的參與度(目前公司至少三成服務由.NET編寫)。該博文屬於我的對Hystrix研究和實踐經驗。html

什麼是Hystrix?git

Hystrix是世界最大在線影片租賃服務商Netflix開源,針對分佈式系統的延遲和容錯庫。該庫由Java寫成,項目源於Netflix API團隊在2011年啓動的彈性工程項目。項目在github上發佈至今,已經有接近三千顆星,只有少數優秀的開源項目才能享受到千星級別的待遇,Hystrix成功可見一斑。
github

 

 

 爲何使用Hystrix?

在大中型分佈式系統中,一般系統不少依賴(HTTP,hession,Netty,Dubbo等),以下圖:數據庫

在高併發訪問下,這些依賴的穩定性與否對系統的影響很是大,可是依賴有不少不可控問題:如網絡鏈接緩慢,資源繁忙,暫時不可用,服務脫機等.後端

以下圖:QPS爲50的依賴 I 出現不可用,可是其餘依賴仍然可用.設計模式

當依賴I 阻塞時,大多數服務器的線程池就出現阻塞(BLOCK),影響整個線上服務的穩定性.以下圖:緩存

在複雜的分佈式架構的應用程序有不少的依賴,都會不可避免地在某些時候失敗。高併發的依賴失敗時若是沒有隔離措施,當前應用服務就有被拖垮的風險。安全

例如:一個依賴30個SOA服務的系統,每一個服務99.99%可用。
99.99%的30次方 ≈ 99.7%
0.3% 意味着一億次請求 會有 3,000,00次失敗
換算成時間大約每個月有2個小時服務不穩定.
隨着服務依賴數量的變多,服務不穩定的機率會成指數性提升.
解決問題方案:對依賴作隔離,Hystrix就是處理依賴隔離的框架,同時也是能夠幫咱們作依賴服務的治理和監控.

到底能作什麼呢?

1)Hystrix使用命令模式HystrixCommand(Command)包裝依賴調用邏輯,每一個命令在單獨線程中/信號受權下執行服務器

2)提供熔斷器組件,能夠自動運行或手動調用,中止當前依賴一段時間(10秒),熔斷器默認錯誤率閾值爲50%,超過將自動運行。網絡

3)可配置依賴調用超時時間,超時時間通常設爲比99.5%平均時間略高便可.當調用超時時,直接返回或執行fallback邏輯。

4)爲每一個依賴提供一個小的線程池(或信號),若是線程池已滿調用將被當即拒絕,默認不採用排隊.加速失敗斷定時間。

5)依賴調用結果分:成功,失敗(拋出異常),超時,線程拒絕,短路。 請求失敗(異常,拒絕,超時,短路)時執行fallback(降級)邏輯。

  

 

6)提供近實時依賴的統計和監控

7)支持異步執行。支持併發請求緩存。自動批處理失敗請求。

Hystrix設計理念

想要知道如何使用,必須先明白其核心設計理念,Hystrix基於命令模式,經過UML圖先直觀的認識一下這一設計模式

可見,Command是在Receiver和Invoker之間添加的中間層,Command實現了對Receiver的封裝。那麼Hystrix的應用場景如何與上圖對應呢?

API既能夠是Invoker又能夠是reciever,經過繼承Hystrix核心類HystrixCommand來封裝這些API(例如,遠程接口調用,數據庫查詢之類可能會產生延時的操做)。就能夠爲API提供彈性保護了。

Hello World

Hello World的例子旨在展現,如何在項目中低侵入式的改造,使API置於Hystrix保護之下!

引入maven依賴

<!-- 依賴版本 -->
<hystrix.version>1.3.16</hystrix.version>
<hystrix-metrics-event-stream.version>1.1.2</hystrix-metrics-event-stream.version> 
 
<dependency>
     <groupId>com.netflix.hystrix</groupId>
     <artifactId>hystrix-core</artifactId>
     <version>${hystrix.version}</version>
 </dependency>
     <dependency>
     <groupId>com.netflix.hystrix</groupId>
     <artifactId>hystrix-metrics-event-stream</artifactId>
     <version>${hystrix-metrics-event-stream.version}</version>
 </dependency>
<!-- 倉庫地址 -->
<repository>
     <id>nexus</id>
     <name>local private nexus</name>
     <url>http://maven.oschina.net/content/groups/public/</url>
     <releases>
          <enabled>true</enabled>
     </releases>
     <snapshots>
          <enabled>false</enabled>
     </snapshots>
</repository>

下面是一個沒有使用Hystrix保護的sayHello的服務以及它的調用

//API調用,可能會產生延時
public class HelloService {
     public static String sayHello(final String name)
     {
         return String.format("Hello %s!", name);
     }
 }
//客戶端直接調用API
public class Client{
      public static void main(String[] args)
      {
           System.out.println(HelloService.sayHello("World"));     
      }  
}    

假設字符串生成過程是一個須要保護的操做,下面咱們用Hystrix進行封裝。

須要注意的是,雖然使用命令模式,可是咱們這裏不建議覆蓋execute方法,而是實現run的模版方法,多數框架的實現會採用template設計模式,而且將模版方法設置爲protected簽名,這樣作的好處是,既能夠將具體的業務交給業務實現者,又能夠爲之添加其餘功能,而業務實現者只須要關注本身的業務就行了。好比這裏HystrixCommand.execute方法其實是調用了HystrixCommand.queue().get(),而queue方法除了最終調用run以外,還須要爲run方法提供超時和異常等保護功能,外部也不能直接調用非安全的run方法,這一實踐很是值得咱們學習。

OK,如今咱們經過實現run方法來包裝sayHello功能,咱們經過一個私有域_name,經過構造函數來傳遞消息,獲取構造參數的拷貝來保持不變性。

public class SayHelloCommand extends HystrixCommand<String> {
    private final String _name;
    public SayHelloCommand(String name)
    {
        super(HystrixCommandGroupKey.Factory.asKey("HelloService"));
        _name = new String(name);//unmutable
        
    }
    @Override
    protected String run() {

        return String.format("Hello %s!", _name);
    }
}

API改造以下,做爲門面方法最好不要改動函數的簽名(除非參數和返回類型有變更,這是由於客戶端代碼的改動代價每每是巨大的),同時提供版本sayHelloAsync,該方法提供了異步功能

public class HelloService {
//    public static String sayHello(final String name)
//    {
//        return "Hello " + name + "!";
//    }
    
    /**
     * sayHello under protection of Hystrix
     * @param name
     * @return <code>"Hello " + name + "!"</code> 
     */
    public static String sayHello(final String name)
    {
        return new SayHelloCommand(name).execute();
    }
    
    /**
     * call async
     * @param name
     * @return
     */
    public static Future<String> sayHelloAsync(final String name)
    {
        return new SayHelloCommand(name).queue();
    }
}

 接下來咱們來看看,如何在超時熔斷狀況下使用FallBack策略,這點在項目中是至關有用的,好比超時後訪問數據備庫,或者直接返回重試響應

首先SayHelloCommand構造函數使用Hystrix的Setter來設置超時時間,這裏解釋下Setter這個類涉及到的幾個最佳實踐

1.Setter使用builder模式,想一想構造函數有不少參數要設置,做爲構造參數傳遞會大大下降可閱讀性,用靜態工廠方法一個個設置又可能形成多線程併發下的不一致性,並且這種bug每每很是難以定位,因此builder模式是很是好的實踐。將Setter做爲構造器傳給HystrixCommand的構造函數,Setter中又不少靜態方法,能夠經過方法名明確的知道元素的意義。

2.Setter是HystrixCommand內部靜態類,Hystrix代碼大量的使用了內部靜態類,來做爲該類的工廠方法,或者構造器,我以爲這樣劃分使代碼職責更加清晰,比單獨的工廠類更易於維護。

3.Setter使用函數式串聯,每一個靜態工廠方法返回Setter實例,這樣咱們能夠把構造過程串聯起來,使代碼更加易於閱讀。

4.Setter是不可變類,每一個靜態工廠方法返回一個新的Setter拷貝,因此Setter是線程安全的。

OK,Setter介紹到這裏,這裏咱們繼續設置超時時間爲500

public SayHelloCommand(final String name)
    {
        //builder for HystrixCommand
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloServiceGroup"))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withTimeoutInMilliseconds(500)));
        _name = new String(name);
    }

run方法咱們使用Thread.sleep(600)來特地達到超時的效果,同時實現getFallback方法,程序超時後會當即運行FallBack

    @Override
    protected String getFallback() {
        return String.format("[FallBack]Hello %s!", _name);
    }

    @Override
    protected String run() throws Exception {
        //TimeOut
        Thread.sleep(600);
        return String.format("Hello %s!", _name);
    }

最終輸出:

[FallBack]Hello World!

回顧重點

1.Hystrix能夠爲分佈式服務提供彈性保護

2.Hystrix經過命令模式封裝調用,來實現彈性保護,繼承HystrixCommand而且實現run方法,就完成了最簡單的封裝。

3. 實現getFallBack方法能夠爲熔斷或者異常提供後備處理方法。

4.HystrixCommand中Setter類的最佳實踐。

5.模版方法在框架中的實踐。

相關文章
相關標籤/搜索