微服務斷路器模式實現:Istio vs Hystrix

ServiceMesher發表於2019-03-08
作者:Nicolas Frankel 
譯者:羅廣明 
原文:www.exoscale.com/syslog/isti…

編者按

本文作者由淺及深,從核心問題的引入到具體模式的程式碼實現,闡述了微服務兩種斷路器模式的實現原理、優缺點以及二者的比較。

前言

不可否認的是,在過去的幾年裡,Docker和Kubernetes等技術已經徹底改變了我們對軟體開發和部署的理解。

但是,儘管軟體開發行業的快速發展促使開發人員採用最新的技術,但是後退一步,更好地檢視支援這些技術的已建立的模式是很重要的。

斷路器模式是微服務體系結構中廣泛採用的模式之一。我們將比較使用兩種不同方法實現它的優缺點: Hystrix和Istio。

微服務斷路器模式實現:Istio vs Hystrix

微服務同步通訊的核心問題

設想一個非常簡單的微服務體系結構,包括:

  1. 一個後端服務

  2. 一個前端服務

我們假設後端和前端通過同步HTTP呼叫進行通訊。客戶端C1C2 呼叫前端獲取一些資訊。由於前端沒有客戶端所需的所有資料,因此它呼叫後端以獲得缺失的部分資料。但是因為網路通訊,很多事情會發生:

  • 前端和後端之間的網路故障

  • 後端可能會因為錯誤而當機

  • 一個被後端依賴的服務(e.g.資料庫)可能當機

根據墨菲定律(“任何可能出錯的都會出錯”),前端和後端之間的通訊遲早會失敗。

如果我們研究從前端到後端單個呼叫的生命週期,並考慮後端由於某種原因當機,那麼在某個時候,前端將因超時取消呼叫。

將範圍縮小到應用程式級別,多個客戶機同時呼叫前端,這將轉換為對後端的多個呼叫: 前端將很快被請求淹沒,並淹沒在超時中。

在這個場景中,唯一合理的解決方案是fail-fast: 前端應該意識到後端出現了問題,並立即將故障返回給自己的客戶端。

斷路器模式

在電路領域中,斷路器是為保護電路而設計的一種自動操作的電氣開關。它的基本功能是在檢測到故障後中斷電流。然後可以重置(手動或自動),以在故障解決後恢復正常操作。

這看起來與我們的問題非常相似: 為了保護應用程式不受過多請求的影響,最好在

後端檢測到重複出現的錯誤時立即中斷前端和後端之間的通訊。在他的 Release It一書中, Michael Nygard 使用了這個類比,併為應用於上述超時問題的設計模式提供了一個案例。它背後的流程非常簡單:

  • 如果呼叫失敗,將失敗呼叫的數量增加1

  • 如果呼叫失敗次數超過某個閾值,則開啟電路

  • 如果電路開啟,立即返回錯誤或預設響應

  • 如果電路是開啟的,過了一段時間,半開啟電路

  • 如果電路是半開的,下一個呼叫失敗,再開啟它

  • 如果電路是半開的,下一個呼叫成功,關閉它

這可以用下圖來總結:

微服務斷路器模式實現:Istio vs Hystrix

Istio斷路器

Istio是一個服務網格(Service Mesh),微服務應用程式的可配置基礎結構層。它使服務例項之間的通訊靈活、可靠和快速,並提供服務發現、負載平衡、加密、身份驗證和授權、對斷路器模式的支援等功能。

Istio的控制平面在底層叢集管理平臺(如Kubernetes、Mesos等)上提供了一個抽象層,並要求以這種方式管理應用程式。

作為其核心,Istio由位於應用程式例項前面的Envoy代理例項組成,並且使用了sidecar容器模式和Pilot(一個管理它們的工具)。這種代理策略有很多優點:

  • 自動為HTTP, gRPC, WebSocket和TCP流量做負載平衡。

  • 通過豐富的路由規則、重試、失敗和錯誤注入對流量行為進行細粒度控制。

  • 可插入的策略層和配置API,支援訪問控制、速率限制和配額。

  • 一個叢集內所有流量的自動度量、日誌和跟蹤,包括叢集的加入和退出。

  • 在具有強大的身份驗證和授權的叢集中進行安全的服務間通訊。

因為對

後端

的出站呼叫通過Envoy代理,所以很容易檢測到它們何時超時。然後代理可以攔截進一步的呼叫並立即返回,從而有效地執行fail-fast。特別地,這使得斷路器模式能夠以黑箱方式執行。

配置Istio斷路器

正如我們所說,Istio構建在您選擇的叢集管理平臺上,並要求應用程式在這個平臺部署。Kubernetes通過DestinationRule實現斷路器模式,或者更具體的路徑TrafficPolicy(原斷路器)->OutlierDetection,根據以下模型:

微服務斷路器模式實現:Istio vs Hystrix

引數如下:

描述
consecutiveErrors斷路器開啟前的出錯次數。
interval斷路器檢查分析的時間間隔。
baseEjectionTime最小的開放時間。該電路將保持一段時間,等於最小彈射持續時間和電路已開啟的次數的乘積。
maxEjectionPercent可以彈出的上游服務的負載平衡池中主機的最大百分比。

與上述公稱斷路器相比,有兩個主要偏差:

  1. 沒有半開放的狀態。然而,斷路器持續開啟的時間取決於被呼叫服務之前失敗的次數。持續的故障服務將導致斷路器的開路時間越來越長。

  2. 在基本模式中,只有一個被呼叫的應用程式(

    後端
    )。在更實際的生產環境中,負載均衡器後面可能部署同一個應用程式的多個例項。某些情況下可能會失敗,而有些人可能會工作,因為Istio也是負載平衡器的作用,能夠跟蹤失敗的,把他們從負載均衡池,在一定程度上: maxEjectionPercent屬性的作用是保持一小部分的例項池。

Istio實現斷路器的方法是一種黑盒方法。它的視角很高,只有出了問題才能開啟電路。另一方面,它的設定非常簡單,不需要任何底層程式碼的知識,並且可以作為事後配置。

Hystrix斷路器

Hystrix是一個最初由Netflix提供的開源Java庫。它是一個延遲容忍和容錯的庫,用於隔離對遠端系統、服務和第三方庫的訪問點,停止級聯故障,並在不可避免出現故障的複雜分散式系統中啟用彈性。

Hystrix有很多特點,包括:

  • 保護通過第三方客戶端庫訪問(通常是通過網路)的依賴項的延遲和失敗。

  • 防止複雜分散式系統中的級聯故障。

  • 失敗快,恢復快。

  • 回退並儘可能優雅地降級。

  • 啟用近實時監視、警報和操作控制。

當然,斷路器的模式體現了這些特點。因為Hystrix是一個庫,它以白盒方式實現它。

Resilience4J Netflix最近宣佈,它已經停止開發Hystrix庫,轉而開發目前知名度較低的 Resilience4J 專案。 即使客戶端程式碼可能稍有不同,Hystrix和Resilience4J的實現方法也是相似的。

一個Hystrix斷路器的例子

以電子商務web應用程式為例。該應用的架構由不同的微服務組成,每個微服務都基於一個業務特性:

  • 身份驗證

  • 目錄瀏覽

  • 購物車管理

  • 定價和引用

  • 其它

當顯示目錄項時,將查詢定價/報價微服務的價格。如果它壞了,不管是不是斷路器,價格都不會退回來,也不可能訂購任何東西。

從企業的角度來看,任何停機時間不僅會影響品牌的認知度,還會降低銷售。大多數銷售策略都傾向於銷售,儘管價格並不完全正確。實現此銷售策略的解決方案可以是快取定價/報價服務在可用時返回的價格,並在服務關閉時返回快取的價格。

Hystrix提供了一個斷路器實現,允許在電路開啟時執行fallback機制,從而實現了這種方法。

這是Hystrix模型的一個非常簡單的類圖:

微服務斷路器模式實現:Istio vs Hystrix

最關鍵的地方就在 HystrixCommand方法 run()getFallback():

  • run() 是要實際執行的程式碼

    e.g.
    從報價服務中獲取價格

  • getFallabck() 獲取當斷路器開啟時的fallback結果

    e.g.
    返回快取的價格

這可以轉化為以下程式碼,使用Spring的RestTemplate:

 public class FetchQuoteCommand extends HystrixCommand<Double> { ​     private final UUID productId;                                               // 1     private final RestTemplate template;                                        // 2     private final Cache<UUID, Double> cache;                                    // 3 ​     public FetchQuoteCommand(UUID productId,                              RestTemplate template,                              Cache<UUID, Double> cache) {         super(HystrixCommandGroupKey.Factory.asKey("GetQuote"));                // 4         this.template = template;         this.cache = cache;         this.productId = productId;     } ​     @Override     protected Double run() {         Double quote = template.getForObject("https://acme.com/api/quote/{id}", // 5                                              Double.class,                                              productId);         cache.put(productId, quote);                                            // 6         return quote;     } ​     @Override     protected Double getFallback() {         return cache.get(productId);                                            // 7     } }複製程式碼

這需要作出一些解釋:

  1. 該命令包裝產品的id,將其建模為UUID

  2. Spring的RestTemplate 用於進行REST呼叫。任何其他實現方式都可以。

  3. 一個共享的JCache例項,用於在服務可用時儲存引號。

  4. Hystrix命令需要一個組鍵,以便在需要時將它們組合在一起。這是Hystrix的另一個特性,超出了本文的範圍。有興趣的讀者可以在Hystrix wiki中閱讀有關命令組的內容。

  5. 執行對引用服務的呼叫。如果它失敗,Hystrix斷路器流程啟動。

  6. 如果呼叫成功,則將返回的引用快取到JCache共享例項中。

  7. 當斷路器開啟時呼叫getFallback()。在這種情況下,從快取中獲取引用。

Hystrix wiki提供了更高階的例子,例如fallback本身就是一個需要執行的命令。

將Hystrix與Spring Cloud整合

雖然上面的程式碼可以工作,但是每次引用時都需要建立一個Hystrix命令物件。

Spring Cloud是建立在Spring Boot(本身依賴Spring框架)之上的庫,它提供了與Spring的良好整合。它讓你在處理Hystrix命令物件的例項化時,只需註釋所需的fallback方法:

 public class FetchQuoteService { ​     private final RestTemplate template;     private final Cache<UUID, Double> cache; ​     public SpringCloudFetchQuoteCommand(RestTemplate template,                                         Cache<UUID, Double> cache) {         this.template = template;         this.cache = cache;     } ​     @HystrixCommand(fallbackMethod = "getQuoteFromCache")                       // 1     public Double getQuoteFor(UUID productId) {                                 // 2         Double quote = template.getForObject("https://acme.com/api/quote/{id}", // 3                                              Double.class,                                              productId);         cache.put(productId, quote);                                            // 4         return quote;     } ​     public Double getQuoteFromCache(UUID productId) {                           // 5         return cache.get(productId);     } }複製程式碼
  1. 這個方法應該用@HystrixCommand註釋. fallbackMethod元素引用fallback方法. 顯然,這將通過反射來處理,並且不是型別安全的——畢竟這是一個字串。

  2. Spring Cloud Hystrix允許在方法呼叫時傳遞產品的id引數。與上面簡單的Hystrix命令相比,這允許有一個通用的服務物件。Hystrix命令的建立由Spring Cloud在執行時處理。

  3. 核心邏輯沒有改變。

  4. 同樣,快取過程保持不變。

  5. fallback方法是一種常規方法。 它將使用與主方法完全相同的引數值來呼叫, 因此,它必須具有相同的引數型別(以相同的順序)。因為getQuoteFor()方法接受UUID,所以這個方法也接受UUID

無論是獨立的還是由Spring Boot Cloud封裝的,Hystrix都需要在程式碼級處理斷路器。因此,需要提前計劃,更改需要部署更新後的二進位制檔案。然而,當事情出錯時,這允許有一個非常好的自定製的行為。

Istio vs Hystrix: battle of circuit breakers

如果存在失敗的可能性,給定時間,就會出現失敗,嚴重依賴網路的微服務需要針對失敗進行設計。斷路器模式是處理服務缺乏可用性的一種方法: 它不會對請求進行排隊並阻塞呼叫者,而是快速失敗(fail-fast)並立即返回。

實現斷路器的方法有兩種,一種是黑盒方式,另一種是白盒方式。Istio作為一種代理管理工具,使用了黑盒方式.它實現起來很簡單,不依賴於底層技術棧,而且可以在事後配置。

另一方面,Hystrix庫使用白盒方式。它允許所有不同型別的fallback:

  • 單個預設值

  • 一個快取

  • 呼叫其他服務

它還提供了級聯回退(cascading fallbacks)。這些額外的特性是有代價的: 它需要在開發階段就做出fallback的決策。

這兩種方法之間的最佳匹配可能會依靠自己的上下文: 在某些情況下,如引用的服務,一個白盒戰略後備可能是一個更好的選擇,而對於其他情況下快速失敗可能是完全可以接受的,如一個集中的遠端登入服務。

當然,沒有什麼能阻止你同時使用它們。

參考

微服務斷路器模式實現:Istio vs Hystrix


相關文章