您好,登錄后才能下訂單哦!
這篇文章主要介紹Spring Cloud中Hystrix功能的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
一、概述
在微服務架構中,我們將系統拆分成了很多服務單元,各單元的應用間通過服務注冊與訂閱的方式互相依賴。由于每個單元都在不同的進程中運行,依賴通過遠程調用的方式執行,這樣就有可能因為網絡原因或是依賴服務自身間題出現調用故障或延遲,而這些問題會直接導致調用方的對外服務也出現延遲,若此時調用方的請求不斷增加,最后就會因等待出現故障的依賴方響應形成任務積壓,最終導致自身服務的癱瘓。
所以我們引入了斷路器,類似于物理上的電路,當電流過載時,就斷開電路,就是我們俗稱的“跳閘”。同理,服務間的調用也是如此,當不斷的出現服務延遲、故障等影響到系統性能的調用,就把這個服務調用切斷!
Spring Cloud Hystrix 實現了斷路器、線程隔離等一系列服務保護功能。它也是基于 Net?ix 的開源框架 Hystrix 實現的,該框架的目標在于通過控制那些訪問遠程系統、服務和第三方庫的節點,從而對延遲和故障提供更強大的容錯能力。Hystrix 具備服務降級、服務熔斷、線程和信號隔離、請求緩存、請求合并以及服務監控等強大功能。
二、Hystrix 工作流程
1.創建 HystrixCommand(用在依賴的服務返回單個操作結果的時候) 或 HystrixObserableCommand(用在依賴的服務返回多個操作結果的時候) 對象。
2.命令執行。其中 HystrixComand 實現了下面前兩種執行方式;而 HystrixObservableCommand 實現了后兩種執行方式:
execute():同步執行,從依賴的服務返回一個單一的結果對象, 或是在發生錯誤的時候拋出異常。
queue():異步執行, 直接返回 一個Future對象, 其中包含了服務執行結束時要返回的單一結果對象。
observe():返回 Observable 對象,它代表了操作的多個結果,它是一個 Hot Obserable(不論 "事件源" 是否有 "訂閱者",都會在創建后對事件進行發布,所以對于 Hot Observable 的每一個 "訂閱者" 都有可能是從 "事件源" 的中途開始的,并可能只是看到了整個操作的局部過程)。
toObservable(): 同樣會返回 Observable 對象,也代表了操作的多個結果,但它返回的是一個Cold Observable(沒有 "訂閱者" 的時候并不會發布事件,而是進行等待,直到有 "訂閱者" 之后才發布事件,所以對于 Cold Observable 的訂閱者,它可以保證從一開始看到整個操作的全部過程)。
3.若當前命令的請求緩存功能是被啟用的, 并且該命令緩存命中, 那么緩存的結果會立即以 Observable 對象的形式 返回。
4.檢查斷路器是否為打開狀態。如果斷路器是打開的,那么Hystrix不會執行命令,而是轉接到 fallback 處理邏輯(第 8 步);如果斷路器是關閉的,檢查是否有可用資源來執行命令(第 5 步)。
5.線程池/請求隊列/信號量是否占滿。如果命令依賴服務的專有線程池和請求隊列,或者信號量(不使用線程池的時候)已經被占滿, 那么 Hystrix 也不會執行命令, 而是轉接到 fallback 處理邏輯(第8步)。
6.Hystrix 會根據我們編寫的方法來決定采取什么樣的方式去請求依賴服務。
HystrixCommand.run() :返回一個單一的結果,或者拋出異常。
HystrixObservableCommand.construct(): 返回一個Observable 對象來發射多個結果,或通過 onError 發送錯誤通知。
7.Hystrix會將 "成功"、"失敗"、"拒絕"、"超時" 等信息報告給斷路器, 而斷路器會維護一組計數器來統計這些數據。斷路器會使用這些統計數據來決定是否要將斷路器打開,來對某個依賴服務的請求進行 "熔斷/短路"。
8.當命令執行失敗的時候, Hystrix 會進入 fallback 嘗試回退處理, 我們通常也稱該操作為 "服務降級"。而能夠引起服務降級處理的情況有下面幾種:
第4步: 當前命令處于"熔斷/短路"狀態,斷路器是打開的時候。
第5步: 當前命令的線程池、 請求隊列或 者信號量被占滿的時候。
第6步:HystrixObservableCommand.construct() 或 HystrixCommand.run() 拋出異常的時候。
9.當Hystrix命令執行成功之后, 它會將處理結果直接返回或是以Observable 的形式返回。
tips:如果我們沒有為命令實現降級邏輯或者在降級處理邏輯中拋出了異常, Hystrix 依然會返回一個 Observable 對象, 但是它不會發射任何結果數據, 而是通過 onError 方法通知命令立即中斷請求,并通過onError()方法將引起命令失敗的異常發送給調用者。
三、Hystrix 熔斷保護機制
1.假設大量的請求數量超過了 HystrixCommandProperties.circuitBreakerRequestVolumeThreshold() 的閾值,熔斷器將會從關閉狀態變成打開狀態;
2.假設依賴調用失敗的百分比超過了 HystrixCommandProperties.circuitBreakerErrorThresholdPercentage() 的閾值,熔斷器將會從關閉狀態變成打開狀態;
3.在熔斷器處于打開狀態的期間,所有對這個依賴進行的調用都會短路,即不進行真正的依賴調用,返回失敗;
在等待(冷卻)的時間超過 HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds() 的值后,熔斷器將處于半開的狀態,將允許單個請求去調用依賴,如果這次的依賴調用還是失敗,熔斷器狀態將再次變成打開,這個打開狀態持續時間是
4.HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds() 配置的值;如果這次的依賴調用成功,熔斷器狀態將變成關閉,后續依賴調用可正常執行。
四、Hystrix 依賴隔離機制
線程池隔離
Hystrix 則使用“艙壁模式”實現線程池的隔離,它會為每一個依賴服務創建 一個獨立的線程池,這樣就算某個依賴服務出現延遲過高的情況,也只是對該依賴服務的調用產生影響,而不會拖慢其他的依賴服務。缺點是涉及到線程切換的性能損耗,但是官方給出的結果是性能損耗是可以接受的。
信號量隔離
信號量隔離可實現對依賴調用最高并發請求數的限制,每次依賴調用都會先判斷信號量是否達到閾值,如果達到極限值則拒絕調用。信號量的開銷遠比線程池的開銷小,但是它不能設置超時和實現異步訪問。所以,只有在依賴服務是足夠可靠的情況下才使用信號量 。以下是兩種配置信號量隔離的方式:
Setter 方式
HystrixCommand.Setter setter = HystrixCommand.Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("strGroupCommand")) .andCommandKey(HystrixCommandKey.Factory.asKey("strCommand")) .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("strThreadPool")); // 配置信號量隔離 HystrixCommandProperties.Setter commandPropertiesSetter = HystrixCommandProperties.Setter().withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); setter.andCommandPropertiesDefaults(commandPropertiesSetter);
注解方式
@HystrixCommand(fallbackMethod = "fallbackMethod", commandProperties = { // 設置隔離策略,THREAD 表示線程池 SEMAPHORE:信號池隔離 @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"), // 當隔離策略選擇信號池隔離的時候,用來設置信號池的大小(最大并發數) @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"), } )
五、hystrix 實戰
SpringBoot 版本號:2.1.6.RELEASE
SpringCloud 版本號:Greenwich.RELEASE
1. pom.xml
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
2. 在 SpringBoot 的啟動類上引入 @EnableCircuitBreaker 注解,開啟斷路器功能。
3. 上面 hystrix 工作原理中提到斷路器有四種執行方式:
execute() - 同步執行
@HystrixCommand(fallbackMethod = "fallbackMethod") public String strConsumer() { ResponseEntity<String> result = restTemplate.getForEntity("http://cloud-eureka-client/hello", String.class); return result.getBody(); }
fallbackMethod —— 回調方法,在服務調用異常、斷路器打開、線程池/請求隊列/信號量占滿時會走回調邏輯。必須和服務方法定義在同一個類中,對修飾符沒有特定的要求,定義為 private、 protected、 public 均可。
queue() - 異步執行
@HystrixCommand(fallbackMethod = "fallbackMethod", ignoreExceptions = {IllegalAccessException.class}) public Future<String> asyncStrConsumer() { Future<String> asyncResult = new AsyncResult<String>() { @Override public String invoke() { ResponseEntity<String> result = restTemplate.getForEntity("http://cloud-eureka-client/hello", String.class); return result.getBody(); } }; return asyncResult; }
ignoreExceptions 表示拋出該異常時不走降級回調邏輯,忽略此異常。
observe () 執行方式
@HystrixCommand(observableExecutionMode = ObservableExecutionMode.EAGER) protected Observable<String> construct() { ResponseEntity<String> result = restTemplate.getForEntity("http://cloud-eureka-client/hello", String.class); return Observable.just(result.getBody()); }
toObservable() 執行方式
@HystrixCommand(observableExecutionMode = ObservableExecutionMode.LAZY) protected Observable<String> construct() { ResponseEntity<String> result = restTemplate.getForEntity("http://cloud-eureka-client/hello", String.class); return Observable.just(result.getBody()); }
4. 命令名稱、分組以及線程池劃分
Hystrix 會根據組來組織和統計命令的告警、儀表盤等信息。
默認情況下,Hystrix 會讓相同組名的命令使用同一個線程池。
通常情況下,盡量通過 HystrixThreadPoolKey 的方式來指定線程池的劃分,而不是通過組名的默認方式實現劃分,因為多個不同的命令可能從業務邏輯上來看屬于同一個組,但是往往從實現本身上需要跟其他命令進行隔離。
@HystrixCommand(fallbackMethod = "fallbackMethod", groupKey = "strGroupCommand", commandKey = "strCommand", threadPoolKey = "strThreadPool") public String strConsumer(@CacheKey Long id) { ResponseEntity<String> result = restTemplate.getForEntity("http://cloud-eureka-client/hello", String.class); return result.getBody(); }
groupKey 默認是類名,commandKey 默認是方法名 ,threadPoolKey 默認和 groupKey 一致。
5. 請求緩存和請求合并
5.1 請求緩存
緩存的作用和好處,真的是無需多言了。請求緩存,顧名思義,就是將對同一個 key 的請求結果,緩存下來。那么下次對這個 key 的請求,數據就直接在緩存中返回,減少響應時間;
在 Hystrix 中使用緩存,主要是三個注解:@CacheResult、@CacheKey、@CacheRemove
@CacheResult:用來標記請求結果應該被緩存,必須與 @HystrixCommand 一起使用。
@CacheKey:用來修飾方法參數,表示緩存的 key 名,優先級高于 @CacheResult 設置的 key 名。
@CacheRemove:當數據更新的時候,為了數據的一致性,我們需要使緩存失效。@CacheRemove 就是用來標記請求結果的緩存失效(commandKey 是必填參數,表示要失效的緩存 key)。
@CacheResult(cacheKeyMethod = "getCacheKey") @HystrixCommand(fallbackMethod = "fallbackMethod") public String strConsumer(@CacheKey Long id) { ResponseEntity<String> result = restTemplate.getForEntity("http://cloud-eureka-client/hello", String.class); return result.getBody(); }
5.2 請求合并
微服務架構中的依賴通常通過遠程調用實現,而遠程調用中最常見的問題就是通信消耗與連接數占用。
在高并發的情況之下,因通信次數的增加,總的通信時間消耗將會變得不那么理想。同時,因為依賴服務的線程池資源有限,將出現排隊等待與響應延遲的清況。為了優化這兩個問題,Hystrix 提供了 HystrixCollapser 來實現請求的合并,以減少通信消耗和線程數的占用。
@RestController public class UserConsumer { @Autowired private UserService userService; /** * 通過 id 獲取用戶接口 * * @param id * @return */ @HystrixCollapser(batchMethod = "getByIds", collapserProperties = { // 10ms 內的請求合并為一次批量請求 @HystrixProperty(name = "timerDelayInMilliseconds", value = "10"), // 批處理過程中是否開啟緩存 @HystrixProperty(name = "requestCache.enabled", value = "10"), }) public String getById(Long id) { return userService.getUserById(id); } /** * 通過 ids 批量獲取用戶信息接口 * * @param ids id 集合 * @return */ @HystrixCommand public Set<String> getByIds(List<Long> ids) { return userService.getUserByIds(ids); } }
雖然通過請求合并可以減少請求的數量以緩解依賴服務線程池的資源,但是在使用的時候也需要注意它所帶來的額外開銷:用于請求合并的延遲時間窗會使得依賴服務的請求延遲增高。
是否開啟緩存合并,我們一般考慮下面兩個因素:
如果依賴服務的請求命令本身是一個高延遲的命令,那么可以使用請求合并器,因為高延遲,時間窗的時間消耗顯得微不足道了。
如果一個時間窗內只有1-2個請求,那么這樣的依賴服務不適合使用請求合并器。這種情況不但不能提升系統性能,反而會成為系統瓶頸;相反,如果一個時間窗內具有很高的并發量,并且服務提供方也實現了批量處理接口,那么使用請求合并器可以有效減少網絡連接數量并極大提升系統吞吐量。
6. HystrixCommand 屬性介紹
@HystrixCommand(fallbackMethod = "fallbackMethod", groupKey = "strGroupCommand", commandKey = "strCommand", threadPoolKey = "strThreadPool", commandProperties = { // 設置隔離策略,THREAD 表示線程池 SEMAPHORE:信號池隔離 @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"), // 當隔離策略選擇信號池隔離的時候,用來設置信號池的大小(最大并發數) @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"), // 配置命令執行的超時時間 @HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"), // 是否啟用超時時間 @HystrixProperty(name = "execution.timeout.enabled", value = "true"), // 執行超時的時候是否中斷 @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"), // 執行被取消的時候是否中斷 @HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"), // 允許回調方法執行的最大并發數 @HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"), // 服務降級是否啟用,是否執行回調函數 @HystrixProperty(name = "fallback.enabled", value = "true"), // 是否啟用斷路器 @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), // 該屬性用來設置在滾動時間窗中,斷路器熔斷的最小請求數。例如,默認該值為 20 的時候,如果滾動時間窗(默認10秒)內僅收到了19個請求, 即使這19個請求都失敗了,斷路器也不會打開。 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"), // 該屬性用來設置在滾動時間窗中,表示在滾動時間窗中,在請求數量超過 circuitBreaker.requestVolumeThreshold 的情況下,如果錯誤請求數的百分比超過50, 就把斷路器設置為 "打開" 狀態,否則就設置為 "關閉" 狀態。 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"), // 該屬性用來設置當斷路器打開之后的休眠時間窗。 休眠時間窗結束之后,會將斷路器置為 "半開" 狀態,嘗試熔斷的請求命令,如果依然失敗就將斷路器繼續設置為 "打開" 狀態,如果成功就設置為 "關閉" 狀態。 @HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"), // 斷路器強制打開 @HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"), // 斷路器強制關閉 @HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"), // 滾動時間窗設置,該時間用于斷路器判斷健康度時需要收集信息的持續時間 @HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"), // 該屬性用來設置滾動時間窗統計指標信息時劃分"桶"的數量,斷路器在收集指標信息的時候會根據設置的時間窗長度拆分成多個 "桶" 來累計各度量值,每個"桶"記錄了一段時間內的采集指標。 // 比如 10 秒內拆分成 10 個"桶"收集這樣,所以 timeinMilliseconds 必須能被 numBuckets 整除。否則會拋異常 @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"), // 該屬性用來設置對命令執行的延遲是否使用百分位數來跟蹤和計算。如果設置為 false, 那么所有的概要統計都將返回 -1。 @HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"), // 該屬性用來設置百分位統計的滾動窗口的持續時間,單位為毫秒。 @HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"), // 該屬性用來設置百分位統計滾動窗口中使用 “ 桶 ”的數量。 @HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"), // 該屬性用來設置在執行過程中每個 “桶” 中保留的最大執行次數。如果在滾動時間窗內發生超過該設定值的執行次數, // 就從最初的位置開始重寫。例如,將該值設置為100, 滾動窗口為10秒,若在10秒內一個 “桶 ”中發生了500次執行, // 那么該 “桶” 中只保留 最后的100次執行的統計。另外,增加該值的大小將會增加內存量的消耗,并增加排序百分位數所需的計算時間。 @HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"), // 該屬性用來設置采集影響斷路器狀態的健康快照(請求的成功、 錯誤百分比)的間隔等待時間。 @HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"), // 是否開啟請求緩存 @HystrixProperty(name = "requestCache.enabled", value = "true"), // HystrixCommand的執行和事件是否打印日志到 HystrixRequestLog 中 @HystrixProperty(name = "requestLog.enabled", value = "true"), }, threadPoolProperties = { // 該參數用來設置執行命令線程池的核心線程數,該值也就是命令執行的最大并發量 @HystrixProperty(name = "coreSize", value = "10"), // 該參數用來設置線程池的最大隊列大小。當設置為 -1 時,線程池將使用 SynchronousQueue 實現的隊列,否則將使用 LinkedBlockingQueue 實現的隊列。 @HystrixProperty(name = "maxQueueSize", value = "-1"), // 該參數用來為隊列設置拒絕閾值。 通過該參數, 即使隊列沒有達到最大值也能拒絕請求。 // 該參數主要是對 LinkedBlockingQueue 隊列的補充,因為 LinkedBlockingQueue 隊列不能動態修改它的對象大小,而通過該屬性就可以調整拒絕請求的隊列大小了。 @HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"), } ) public String strConsumer() { ResponseEntity<String> result = restTemplate.getForEntity("http://cloud-eureka-client/hello", String.class); return result.getBody(); }
以上是“Spring Cloud中Hystrix功能的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。