從不可描述的服務雪崩到初探Hystrix

hoohack發表於2019-03-11

什麼是服務雪崩?什麼是服務保護?服務保護的措施有哪些?熔斷怎麼做?限流怎麼做?服務隔離怎麼做?降級怎麼做?

真實案例

舉一個自己遇到的真實的例子。 介面A依賴了服務B,介面A的部署情況是有兩個機房部署,服務B的部署情況也是兩個機房部署。使用者請求介面失敗會重試,部署架構圖如下:

服務部署架構

說明:服務部署用到的是Linux+Nginx+PHP技術棧。

當時遇到的情況是服務B所在的機房掛了,導致介面A呼叫服務B超時,超時返回之後nginx重試到A的另一個節點,繼續呼叫服務B,A的所有節點都失敗後,返回給客戶端失敗結果,客戶端進行重試,於是再進行一次剛剛的步驟,這些超時的請求佔用了PHP的程式沒有釋放,同時使用者側體驗感知到緩慢,於是不斷重新整理重試,導致流量暴漲,PHP的程式池被耗盡了,於是介面A就無法訪問了,其他依賴介面A的功能也無法使用,導致整個站點雪崩。

這是典型的服務沒有進行隔離導致功能雪崩的例子,那麼問題來了,如果要對這次的故障進行改進,為介面和服務之間加入一層服務保護,那麼要怎麼做呢?

業界比較常見的服務保護主要有以下這些:

1、限流

當發現服務失敗數量達到某個閾值,拒絕訪問,限制更多流量的到來,防止過多失敗的請求將資源耗盡。

2、服務隔離

將不同型別的介面隔離部署,單個型別介面的失敗甚至程式池被耗盡不會影響其他介面的正常訪問,比如在資訊平臺中,如果釋出和閱讀的介面分開部署了,那麼即使釋出功能失效,閱讀功能還能繼續使用。

3、熔斷

從介面請求連線就拒絕訪問,類似家裡用的保險絲,使用的電器總和超過了電壓就熔斷保險絲,整個電路短了,保護整個區域的電路防止更多的損失。

4、降級

對於簡單的展示功能,如果有失敗的請求,返回預設值。對於整個站點或客戶端,如果伺服器負載過高,將其他非核心業務停止,以讓出更多資源給其他服務使用。

以上是筆者所知道服務出現雪崩的情況以及保護服務的措施,在Java領域中,業界用得比較多的是Hystrix,那麼就來看看它是怎麼實現上面這些措施。

Hystrix是什麼?

Hystrix是一個通過增加延遲容錯和容錯邏輯來控制分散式服務之間互動的一個庫。Hystrix通過執行緒隔離,防止錯誤級聯傳遞,導致服務雪崩,從而提高服務穩定性。

Hystrix的主要目標

1、通過隔離第三方客戶端庫訪問依賴關係,防止和控制延遲和故障;

2、防止複雜分散式系統的級聯失敗;

3、快速響應失敗並迅速恢復;

4、提供回滾以及友好降級;

5、實現近實時監控,告警和操作控制

Hystrix設計原則

1、防止單個依賴耗盡了服務容器的使用者執行緒

2、降低負載以及快速失敗,而不是排隊

3、當可以阻止服務的失敗時提供回退策略

4、使用隔離技術減少任意依賴的影響

5、通過近實時指標、監控和告警優化發現時間

6、在Hystrix的大多數方面,通過配置更改的低延遲和對動態屬性更改的支援,使得可以在低延遲的情況下進行實時修改操作,從而優化恢復時間

7、防止整個依賴關係客戶端執行中的故障,而不僅僅是網路流量

Hystrix如何做到上面的目標

1、所有外部的呼叫都封裝到HystrixCommand或HystrixObservableCommand物件,這些物件通常在單獨的執行緒下執行。

2、超時呼叫的時間,超過定義的閾值。有一個預設值,但是對於大多數的依賴,你可以自定義該屬性使得略高於每個依賴測量的99.5%的效能。

3、為每一個依賴項維護一個執行緒池(或者訊號),如果依賴項的執行緒池滿了,新的依賴請求不會繼續排隊等待,而是馬上被拒絕訪問。

4、計算成功、失敗、超時和執行緒拒絕的數量。

5、如果依賴服務的失敗百分比超過閾值,則手動或自動啟動斷路器,在一段時間內停止對指定服務的所有請求。

6、為請求失敗、被拒絕、超時或短路情況提供回退邏輯。

7、近乎實時地監控指標和配置更改。

一段程式碼demo

講完這麼多,還是看看程式碼更實在,從Hystrix官網上擷取了一段程式碼如下:

public class Order {

    private final int orderId;
    private UserAccount user;

    public Order(int orderId) {
        this.orderId = orderId;

        user = new GetUserAccountCommand(new HttpCookie("mockKey", "mockValueFromHttpRequest")).execute();
    }

}
複製程式碼

更多程式碼內容:github.com/Netflix/Hys…

上面就是Hystrix使用的例項,在實際程式碼中,就是new一個Command,然後呼叫execute方法獲取結果,那麼這一個過程中Hystrix做了什麼呢?

Hystrix的工作流程

服務部署架構

上面這個圖是從Hystrix的官方文件中找到的,能看懂這個文件幾乎就能看懂Hystrix是怎麼執行的了。通過圖中的順序來解讀Hystrix的執行流程。

1、初始化,有兩種方式初始化一個Hystrix命令,通過new HystrixCommand或者new HystrixObservableCommand建立,使用服務例項和請求服務需要的引數來構造一個Hystrix命令。

2、成功建立Hystrix後,有四種方法執行實際的命令並得到返回結果。這裡Hystrix還使用了響應式程式設計來設計,這個主題比較大,一時半會解釋不出,之後再深入探索。

對於使用HystrixCommand建立命令的例項,執行execute或者queue;而對於使用HystrixObservableCommand建立命令的例項,執行observe或者toObservable方法,可以請求服務然後得到執行結果。這四個方法的特性是:

execute - 會阻塞,然後返回依賴服務的結果
queue - 返回一個Future,然後可以通過get方法獲得以來服務的結果。
observe - 訂閱包含依賴服務響應結果的訂閱器,當有結果時返回一個訂閱器。
toObservable - 返回一個訂閱器,當訂閱它時,會知曉Hystrix命令並返回結果。
複製程式碼

execute的原始碼如下:

public R execute() {
    try {
        return queue().get();
    } catch (Exception e) {
        throw Exceptions.sneakyThrow(decomposeException(e));
    }
}

public Future<R> queue() {
    /*
     * The Future returned by Observable.toBlocking().toFuture() does not implement the
     * interruption of the execution thread when the "mayInterrupt" flag of Future.cancel(boolean) is set to true;
     * thus, to comply with the contract of Future, we must wrap around it.
     */
    final Future<R> delegate = toObservable().toBlocking().toFuture();
    // 其他定義
}
複製程式碼

從原始碼看到,execute方法會呼叫queue().get()方法,queue()會呼叫toObservable().toBlocking().toFuture(),說明每一個Hystrix命令最終都回到Observable物件的實現,即使是為了返回一個簡單的值。

3、判斷Hystrix是否啟用快取且對應請求有快取值,則返回快取的結果。

4、如果3沒有快取,Hystrix會檢查它的熔斷器,如果此時熔斷器開啟了,那麼Hystrix不會執行命令,直接返回降級結果。

5、如果訊號或者執行緒池拒絕請求,返回降級結果。

6、Hystrix通過呼叫HystrixCommand.run()或者HystrixObservableCommand.construct()方法來觸發呼叫外部服務的操作,如果超時或者失敗,返回降級結果。 如果run或者construct方法超過了命令定義的超時值,執行緒會丟擲TimeoutException,此時Hystrix捕捉到異常,就會忽略run或construct方法的返回值,進入fallback。

注意:沒有任何方式可以阻止延遲的執行緒停止工作,在JVM中,Hystrix可以做到最好的就是丟擲一個InterruptedException,如果Hystrix封裝的服務沒有捕獲InterruptedException,Hystrix執行緒池中的執行緒會繼續它的工作。
複製程式碼

7、不管請求如何進行:成功、失敗、超時、熔斷,Hystrix都會上報健康狀態到熔斷器,記錄服務狀態,用於判斷是否啟動/半啟動熔斷器。

8、fallback,進行降級操作,會觸發回退操作的條件: construct或者run方法丟擲異常 熔斷器開啟 執行緒池以及佇列或者訊號容量不足 Hystrix命令超時

對於每一個Hystrix命令,都需要覆蓋getFallback方法,在fallback函式中實現降級的方案,如果需要在fallback中使用網路呼叫,那麼需要通過另一個HystrixCommand或者HystrixObservableCommand。在HystrixCommand中是實現getFallback方法,在HystrixObservableCommand中,是實現sumeWithFallback方法。

如果沒有實現fallback方法,或者fallback方法丟擲了異常,Hystrix還是會返回一個Observerable,但是不會返回內容並通過一個onError通知來馬上終止。通過onError通知,發生異常的會被返回Hystrix的呼叫者。儘量不要寫出可能會丟擲異常的fallback實現。

9、如果一切正常,那麼Hystrix會傳送成功的結果到Observable,程式再去獲取。

總結

以上就是Hystrix的執行流程,因為最近想了解在PHP中如何實現服務熔斷,於是在學習Java中做的比較好的Hystrix是怎麼實現的。接下來會繼續深入學習Hystrix的熔斷器實現,下次再分享Hystrix熔斷器的實現原理。

瞭解一個庫的執行流程,除了有助於開發時排查遇到的較棘手的問題,還可以學習一個庫的設計理念,從這些庫中吸收一些框架設計優點,之後如果需要實現相關功能時,就可以作為參考。

原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

如果本文對你有幫助,請點個贊吧,謝謝^_^

更多精彩內容,請關注個人公眾號。

從不可描述的服務雪崩到初探Hystrix

相關文章