從零學SpringCloud系列(四):服務容錯保護Hystrix

一、Hystrix介紹

在微服務架構中,我們可能將系統拆分中多個服務單元,由於每個服務單元都運行在不同的進程中,他們彼此之間通過遠程調用的方式執行,這樣就有可能因爲網絡原因或者是依賴服務自身問題出現調用故障或延遲,而這些問題 會直接 導致調用方對外提供服務也出現延遲,若此時調用方的請求不斷累加,最後會造成任務積壓導致自身系統癱瘓。

針對上述問題,Spring Cloud Hystrix實現了斷路器、線程隔離等一系列服務保護功能。Hystrix主要作用如下:

1)服務隔離和服務熔斷

服務隔離:每次 接收到請求,都找一個單獨的空間執行,這樣你出了問題,就不會影響其他的業務。

服務熔斷:發現某個服務不可用時,執行兜底方法,觸發降級,返回我們預置的內容或異常。

2)服務降級、限流和快速失敗。其實,服務降級和快速失敗是差不多的意思。

3)請求合併和請求緩存

4)本身整合了對單體和集羣的監控

二、Hystrix架構和處理流程解析

1、架構圖

Hystrix整個工作流程概括如下:

 1、創建HystrixCommand或HystrixObservableCommand對象,用於封裝請求,並且在構造方法 中配置請求執行需要的參數;

2、執行命令,H ystrix提供了4種執行命令的方法,後面我們在介紹。

3、判斷結果是否使用緩存響應請求,若啓用了緩存,且緩存可用,直接使用緩存響應請求。Hystrix支持請求緩存, 但是需要用戶 自定義啓動。

4、判斷斷路器是否打開,如果打開了, 直接跳到第8步

5、判斷線程池/隊列/信號量是否佔滿,如果已經佔滿,直接跳轉到8步

6、執行HystrixObservableCommand.construct()或HystrixCommand.run(),如果執行失敗或者超時,跳轉到第8步;否則走9步

7、計算 斷路器的健康度

8、走Fallback處理邏輯

9、返回請求響應

三、Hyxstrix的 兩種命令模式

Command會以隔離的形式完成run方法的調用,用在依賴的服務返回單個操作結果的時候。

ObservableCommand會使用當前 線程進行調用,用在依賴的服務返回多個操作結果的 時候。

命令模式,將來自客戶端的請求封裝成一個對象,從而讓你可以使用不同的請求對客戶端進行參數化。他可以被用於試下 “行爲請求者” 與 “行爲實現着”的解耦,以便使兩者可以適應變化。

四、 斷路器詳細執行流程

下圖展示了HystrixCommand或HystrixObservableCommand如何與HystrixCircuitBreaker(斷路器)以及與它的邏輯和決策流程進行交互,包括在計數器在斷路器上的行爲方式。

下面我們對上圖中涉及到的 幾個方法的執行邏輯進行介紹:

  • isOpen():判斷 斷路器的打開/關閉狀態。詳細邏輯如下所示:

如果斷路器打開標識未true,則直接返回true,標識斷路器處於打開狀態。否則,就從度量指標對象 metrics中獲取HealthCounts統計對象做進一步判斷(該對象記錄了一個滾動時間窗內的請求信息快照,默認時間窗爲10s)

1、如果請求總數(QPS)在預設的閾值範圍內就返回false,表示斷路器處於未打開狀態。該閾值的配置參數circuitBreakerRequestVolumeThreshold,默認值爲20;

2、 如果錯誤百分比在 閾值範圍內就返回false,表示斷路器處於未打開狀態。該閾值的配置參數爲circuitBreakerErrorThresholdPercentage,默認值爲50;

3、如果上面兩個條件都不滿足,則將斷路器設置打開狀態。同時,如果是從關閉狀態切換到打開狀態的話,就將當前時間記錄到上面提到的circuitOpenedOrLastTestedTime對象中。

  • allowRequest():判斷請求是否被允許,具體執行邏輯如下:

先根據配置對象properties中的斷路器判斷強制打開或關閉屬性是否被設置。如果強制打開,就直接返回false,拒絕請求。 如果強制關閉,它會允許所有請求,但是, 同時也會調用isOpen()來執行 斷路器的計算邏輯,如果模擬斷路器 打開/關閉的行爲。在默認情況下,斷路器並不會進入這兩個強制打開或者關閉的分支中去,而是通過 !Open() || allowSingleTest()來判斷是否允許請求訪問。

通過我們查看allowSingleTest()的具體實現,我們可以看到,這裏使用了在isOpen()函數中當斷路器從閉合到打開時候記錄的時間戳。當斷路器在打開狀態的時候,這裏會判斷斷開時的時間戳 + 配置中的circuitBreakerSleepWindowInMilliseconds時間是否小於當前時間,是的話, 就將當前時間更新到記錄斷路器打開的時間對象circuitOpenedOrLastTestedTime中,並且 允許此次請求。簡單的來說,通過circuitBreakerSleepWindowInMilliseconds屬性設置了一個斷路器 打開之後的休眠時間(默認5s),在該休眠時間到達之後,將再次允許請求嘗試訪問,此時斷路器處於 “半開狀態”,若此時請求繼續失敗,斷路器又進入打開狀態,並繼續等待下一個休眠窗口過去之後再次嘗試,若請求成功,則將斷路器重新置於關閉狀態,所以通過allowSingleTest()與isOpen()方法的配合,實現了斷路器打開和關閉狀態的切換。

  • markSuccess():該函數用來在 “半開路” 狀態時使用。若Hystrix命令調用成功,通過調用它將打開的斷路器關閉,並重置度量指標對象。

五、依賴隔離

“艙壁模式”對於屬性Docker的人一定不陌生,Docker通過該模式 實現進程的隔離,使得容器之間不互互相影響。而Hystrix則使用該模式實現線程池的隔離,他會爲每一個依賴服務從創建一個獨立的線程池,這樣就算某個依賴服務出現延遲過高的情況,也是對該依賴的服務調用產生影響,而不會拖慢其他的服務。

1、線程池和信號量

Hystrix 提供了兩種隔離策略,一種是線程和線程池(Thread & Thread Pools),另一種是信號量(Semaphores)。Hystrix推薦 使用的隔離策略是Semaphores。

線程池之間是相互獨立的,每個線程池默認包含10個線程。對於每一個依賴項,都使用一個線程池來處理,如果這個依賴項拒絕服務或者超時(快速失敗、失效不返回結果、回退),那由它導致的線程滿載, 不會影響到其他線程池。

簡而言之,線程池提供的隔離允許在不導致停機的情況下優雅的處理客戶 機庫和子系統性能特徵的持續變化和動態組合。

2.隔離策略中的三種“key”

CommandKey,針對相同的接口一般CommandKey值相同,目的是把HystrixCommand,HystrixCircuitBreaker,HytrixCommandMerics以及其他相關對象關聯在一起,形成一個原子組。
CommandGroupKey,對CommandKey分組,用於真正的隔離。相同CommandGroupKey會使用同一個線程池或者信號量。一般情況相同業務功能會使用相同的CommandGroupKey。
ThreadPoolKey,如果說CommandGroupKey只是邏輯隔離,那麼ThreadPoolKey就是物理隔離,當沒有設置ThreadPoolKey的時候,線程池或者信號量的劃分按照CommandGroupKey,當設置了ThreadPoolKey,那麼線程池和信號量的劃分就按照ThreadPoolKey來處理,相同ThreadPoolKey採用同一個線程池或者信號量。
簡而言之,ThreadPoolKey是用作於物理隔離(距離很遠的機房)的,CommandGroupKey是用於做邏輯隔離的。Hystrix在底層用一個Map集合的方式,來管理這些線程池的集合,Map的key對應ThreadPoolKey(未設置ThreadPoolKey的時候,CommandGroupKey就是ThreadPoolKey)或者CommandGroupKey,value對應具體線程池,類似“Map<String,pool>”的形式,線程(pool)裏面對應的就是一個一個的線程(Thread)。

六、請求緩存

對於單次請求裏面執行的多次相同命令,可以使用緩存來獲取結果。譬如在一次商品購買邏輯中,需要反覆查詢同一個商品3次,每次查詢的結果都是一致的,對於這種讀取數據的形式,就可以使用考慮使用緩存。緩存可以減少重複請求,降低數據庫壓力。

在org.init.springCloud包下新建cache包,創建CacheCommandTest測試類,內部類CacheCommand的構造器中傳入一個自定義的緩存key值,我們在同一次請求上下文中,執行三次同樣的命令,來查看是否使用了緩存:
 

package org.init.springCloud.cache;
 
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
 
public class CacheCommandTest {
	
	public static void main(String[] args) {
		HystrixRequestContext context = HystrixRequestContext.initializeContext();//開啓一個上下文
		String caCheKey = "myCaCheKey";
		CacheCommand cc1 = new CacheCommand(caCheKey);
		CacheCommand cc2 = new CacheCommand(caCheKey);
		CacheCommand cc3 = new CacheCommand(caCheKey);
		
		cc1.execute();
		cc2.execute();
		cc3.execute();
		
		System.out.println("是否是從緩存中讀取的數據:"+cc1.isResponseFromCache());
		System.out.println("是否是從緩存中讀取的數據:"+cc2.isResponseFromCache());
		System.out.println("是否是從緩存中讀取的數據:"+cc3.isResponseFromCache());
		
		context.shutdown();//關閉上下文
	}
 
	static class CacheCommand extends HystrixCommand<String>{
		String cacheKey;
		public CacheCommand(String cacheKey){
			super(HystrixCommandGroupKey.Factory.asKey("myGroup"));
			this.cacheKey = cacheKey;
		}
		
		@Override
		protected String run() throws Exception {
			System.out.println("執行成功");
			return "success";
		}
		
		@Override
		protected String getFallback() {
			System.out.println("執行失敗");
			return "fail";
		}
		
		@Override
		protected String getCacheKey() {
			return this.cacheKey;
		}
	}
	
}

七、請求合併

微服務架構中依賴通常通過遠程調用的方式實現,而遠程 調用中最常見的 問題就是通信消耗和連接數 佔用。在高併發情況 之下,因爲通信次數的增加,總的通信時間消耗會變得 不那麼理想。同時,因爲依賴服務的線程資源有限,將出現排隊等待和響應延遲的情況。爲了優化這兩個問題,Hystrix提供了HystrixCollapser來實現請求合併,以減少通信消耗和線程數的佔用。

HystrixCollapser實現了在HystrixCommand之前放置一個 合併處理器,將處於一個很短時間窗(默認是10s)內對同一個依賴服務的多個請求進行整合並以批量方式發起請求功能(服務提供方需要提供響應的批量查詢接口)。通過HystrixCollapser封裝,開發者不需要關注線程合併的細節過程,只需要關注批量化服務和處理。

八、Hystrix Dashboard儀表盤

 Hystrix Dashboard主要用來實時監控Hystrix的各項指標信息。通過該儀表盤反饋的實時信息,惡意幫助我們快速發現系統中存在的問題, 從及時的採取應對措施。

 

項目地址:https://github.com/zhenghaoxiao/spring-cloud-in-action/tree/dev 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章