高可用系統架構與hystrix(附實踐案例長文)

一:什麼是hystrix

在分佈式系統中,每個服務都可能會調用很多其他服務,被調用的那些服務就是依賴服務,有的時候某些依賴服務出現故障也是很正常的。Hystrix可以讓我們在分佈式系統中對服務間的調用進行控制,加入一些調用延遲或者依賴故障的容錯機制。Hystrix通過將依賴服務進行資源隔離,進而組織某個依賴服務出現故障的時候,這種故障在整個系統所有的依賴服務調用中進行蔓延,同時Hystrix還提供故障時的fallback降級機制

總而言之,Hystrix通過這些方法幫助我們提升分佈式系統的可用性和穩定性。hystrix,就是一種高可用保障的一個框架,類似於spring(ioc,mvc),mybatis,activiti,lucene,框架,預先封裝好的為了解決某個特定領域的特定問題的一套代碼庫。

高可用系統架構與hystrix(附實踐案例長文)

二:Hystrix的設計原則

(1)阻止任何一個依賴服務耗盡所有的資源,比如tomcat中的所有線程資源

(2)避免請求排隊和積壓,採用限流和fail fast來控制故障

(3)提供fallback降級機制來應對故障

(4)使用資源隔離技術,比如bulkhead(艙壁隔離技術),swimlane(泳道技術),circuit breaker(短路技術),來限制任何一個依賴服務的故障的影響

(5)通過近實時的統計/監控/報警功能,來提高故障發現的速度

(6)通過近實時的屬性和配置熱修改功能,來提高故障處理和恢復的速度

(7)保護依賴服務調用的所有故障情況,而不僅僅只是網絡故障情況

三:實現高可用系統架構的方式

HA,HAProxy,主備服務間的切換,主備實例,多冗餘實例,這些都是高可用最最基礎的東西,這裡就不再描述了。主要說一下Hystrix的高可用實現方式。

1:資源隔離: 讓你的系統裡,某一塊東西,在故障的情況下,不會耗盡系統所有的資源,比如線程資源。我實際的項目中的一個case,有一塊東西,是要用多線程做一些事情,小夥伴做項目的時候,沒有太留神,留了一個bug。在遇到一些故障的情況下,每個線程在跑的時候,因為那個bug,直接就死循環了,導致那塊東西啟動了大量的線程,每個線程都死循環,最終導致系統資源耗盡,直接崩潰。如果做了資源隔離,只會導致那個功能的資源耗盡,而不會導致整個系統資源耗盡。

2:限流: 防止高併發的流量湧入進來,導致系統負載過高,直接掛掉。比如說突然間一秒鐘100萬QPS,只允許10萬QPS進入系統,其他90萬QPS直接被拒絕。

3:熔斷: 能保證服務調用者在調用異常服務時, 快速返回結果, 避免大量的同步等待. 並且熔斷器能在一段時間後繼續偵測請求執行結果, 提供恢復服務調用的可能。比如說mysql掛掉了,每次請求都是報錯的,做了熔斷之後,後續的請求過來直接不接收了,拒絕訪問,10分鐘之後再嘗試去看看mysql恢復沒有。

4:降級: 就是當某個服務熔斷之後,服務器將不再被調用,此時客戶端可以自己準備一個本地的fallback回調,返回一個缺省值。 例如mysql掛了,系統發現了,自動降級,從內存裡存的少量數據中,去提取一些數據出來。

5:運維監控: 監控+報警+優化,各種異常的情況,有問題就及時報警,優化一些系統的配置和參數,或者代碼。

四:實際案例(商品詳情頁的緩存服務)

1:背景

商品詳情頁,如何用最快的結果將商品數據填充到一個頁面中,然後將頁面顯示出來。緩存服務將負責商品詳情頁的所有信息的緩存操作。商品發生更改時,通過緩存服務操作mq來進行緩存更新。當請求沒有找到對應的緩存信息時,也會調用緩存服務,緩存服務則通過調用商品服務,店鋪服務和廣告信息服務來拉取信息更新緩存。

緩存服務的高可用性在整個商品詳情頁面系統中起著非常重要的作用。緩存服務依賴於商品服務,店鋪服務和廣告信息服務。

高可用系統架構與hystrix(附實踐案例長文)

2:實現架構

這裡通過spring boot + http client + hystrix去構建一個微服務的分佈式緩存服務系統。(根據實際的生產情況也可以用RPC調用。至於http調用和rpc調用的區別請自行腦補。)

3:實現方案及hystrix的原理分析:

1)商品服務的接口故障導致緩存服務不可用

  • 問題:緩存服務去拉取各種底層的源數據服務的數據,調用其接口時,可能會出現子系統不可用的問題。例如:底層服務商品服務的接口調用超時,200ms,2s都沒有返回,暫用了大量的線程資源,導致緩存服務資源耗盡。店鋪服務和廣告信息服務的數據也都沒有線程資源去調用了,最終導致整個緩存服務不可用。

  • 方案:通過hystrix的線程資源隔離技術進行商品服務接口的資源隔離。hystrix裡面分兩種資源隔離方式,線程池隔離技術與信號量隔離技術。信號量沒有線程池,直接讓tomcat的線程調用依賴服務的,而線程池是自己維護一個單獨的線程池和tomcat線程做對接。線程池適合絕大多數的場景,對依賴服務的網絡請求的調用和訪問。如果僅對內部的一些比較複雜的業務邏輯的訪問,不涉及任何的網絡請求,那麼只要做信號量的普通限流就可以了。

  • 代碼實現:這裡咱們用的是ExecutionIsolationStrategy.THREAD線程池隔離

高可用系統架構與hystrix(附實踐案例長文)

高可用系統架構與hystrix(附實踐案例長文)

2)command的精細化控制

總共有三個概念。commandGroup,command,threadpool。

一般情況下,建議按以下情況進行這三個變量的設置。

  • command key,代表了一類command,一般來說,代表了底層的依賴服務的一個接口

  • command group,代表了某一個底層的依賴服務,合理,一個依賴服務可能會暴露出來多個接口,每個接口就是一個command key。在邏輯上去組織起來一堆command key的調用,統計信息,成功次數,timeout超時次數,失敗次數,可以看到某一個服務整體的一些訪問情況。推薦是根據一個服務去劃分出一個線程池,command key默認都是屬於同一個線程池的。

  • threadpool key:默認的threadpool key就是command group名稱,每個command都會跟它的threadpool key對應的thread pool綁定在一起。如果一個服務暴露出來的幾個接口,訪問量很不一樣,差異非常之大。可以設置同一個commandgroup,不同threadpool Key實現同一個服務,不同的接口使用不同的線程池。

總的來說,多個command key屬於一個command group,在做統計的時候,會放在一起統計。每個command key有自己的線程池,每個接口有自己的線程池,去做資源隔離和限流。但是對於thread pool資源隔離來說,可能是希望能夠拆分的更加一致一些,比如在一個功能模塊內,對不同的請求可以使用不同的thread pool。command group一般來說,可以是對應一個服務,多個command key對應這個服務的多個接口,多個接口的調用共享同一個線程池。如果說你的command key,要用自己的線程池,可以定義自己的threadpool key,就ok了。

可以通過coreSize設置線程池的大小,默認是10。

設置queueSizeRejectionThreshold,控制queue滿後reject的threshold,控制隊列的大小。默認是5。

高可用系統架構與hystrix(附實踐案例長文)

3)使用request cache請求緩存技術優化商品數據查詢接口

首先,有一個概念,叫做reqeust context,請求上下文。一般來說,在一個web應用中,我們會在一個filter裡面,對每一個請求都施加一個請求上下文,就是說,tomcat容器內,每一次請求,就是一次請求上下文,然後在這次請求上下文中,我們會去執行N多代碼,調用N多依賴服務,有的依賴服務可能還會調用好幾次。在hystrix的一次請求上下文中,如果有多個command,參數都是一樣的,調用的接口也是一樣的,其實結果可以認為也是一樣的,那麼這個時候,我們就可以讓第一次command執行,返回的結果,被緩存在內存中,然後這個請求上下文中,後續的其他對這個依賴的調用全部從內存中取用緩存結果就可以了。不用再一次請求上下文中反覆多次的執行一樣的command,提升整個請求的性能。

HystrixCommand和HystrixObservableCommand都可以指定一個緩存key,然後hystrix會自動進行緩存,接著在同一個request context內,再次訪問的時候,就會直接取用緩存。用請求緩存,可以避免重複執行網絡請求。

在一個請求執行之前,都必須先初始化一個request context

HystrixRequestContext context = HystrixRequestContext.initializeContext();

然後在請求結束之後,需要關閉request context

context.shutdown();

一般來說,在java web來的應用中,都是通過filter過濾器來實現的。

高可用系統架構與hystrix(附實踐案例長文)

高可用系統架構與hystrix(附實踐案例長文)

高可用系統架構與hystrix(附實踐案例長文)

4)利用fallback降級機制實現異常情況下商品品牌數據的讀取

下面四種情況,會觸發降級機制。

  • 系統報錯,例如訪問mysql報錯,redis報錯,zookeeper報錯,kafka報錯。

  • 對每個外部依賴,無論是服務接口,中間件,資源隔離,對外部依賴只能用一定量的資源去訪問,線程池/信號量,如果資源池已滿。

  • 訪問外部依賴的時候,訪問時間過長,可能就會導致超時,報一個TimeoutException異常,timeout

  • 如果短路器發現異常事件的佔比達到了一定的比例,直接開啟斷熔機制

兩種最經典的降級後的處理機制:

  • 純內存數據: 如果說外部依賴有異常,fallback這裡,直接嘗試從ehcache中獲取數據

  • 默認值:本來你是從mysql,redis,或者其他任何地方去獲取數據的,獲取調用其他服務的接口的,結果人家故障了,人家掛了,fallback,可以返回一個默認值

我們現在有個商品數據,brandId,品牌,一般來說,假設,正常的邏輯,拿到了一個商品數據以後,用brandId再調用一次請求,到其他的服務去獲取品牌的最新名稱。假如說,那個品牌服務掛掉了,那麼我們可以嘗試本地內存中,會保留一份時間比較過期的一份品牌數據。fallback降級就從本地內存中獲取一份過期的數據,先湊合著用著。

高可用系統架構與hystrix(附實踐案例長文)

5)利用斷熔機制保證商品服務的高可用(又稱短路器)

斷熔機制的原理:

  • HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()。如果經過短路器的流量超過了設定的閾值,會做短路判斷。例如要求在10s內,經過短路器的流量必須達到20個;在10s內,經過短路器的流量才10個,那麼根本不會去判斷要不要短路。

  • HystrixCommandProperties.circuitBreakerErrorThresholdPercentage() 。如果斷路器統計到的異常調用的佔比超過了設定的閾值。就會開啟短路。比如說在10s內,經過短路器的流量達到了30個;同時其中異常的訪問數量,佔到了一定的比例,比如說60%的請求都是異常(報錯,timeout,reject),就會開啟短路。然後斷路器從close狀態轉換到open狀態

  • 斷路器打開的時候,所有經過該斷路器的請求全部被短路,不調用後端服務,直接走fallback降級

  • 經過了一段時間之後,HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds(),會half-open,讓一條請求經過短路器,看能不能正常調用。如果調用成功了,那麼就自動恢復。

高可用系統架構與hystrix(附實踐案例長文)

6)監控與報警

HystrixCommand執行的時候,會生成一些執行耗時等方面的統計信息。這些信息對於系統的運維來說,是很有幫助的,因為我們通過這些統計信息可以看到整個系統是怎麼運行的。hystrix對每個command key都會提供一份metric,而且是秒級統計粒度的。

這些統計信息,無論是單獨看,還是聚合起來看,都是很有用的。如果將一個請求中的多個command的統計信息拿出來單獨查看,包括耗時的統計,對debug系統是很有幫助的。聚合起來的metric對於系統層面的行為來說,是很有幫助的,很適合做報警或者報表。hystrix dashboard就很適合。

hystrix的dashboard可以支持實時監控metric。

高可用系統架構與hystrix(附實踐案例長文)


分享到:


相關文章: