目錄
1.HystrixCommandProperties:HystrixProperty類型
2.HystrixCollapserProperties:HystrixProperty類型
3.HystrixThreadPoolProperties:
官方文檔:https://github.com/Netflix/Hystrix/wiki
轉載:
https://blog.csdn.net/weixin_40584932/article/details/80903772
https://blog.csdn.net/wugemao/article/details/80760273
一、Hystrix說明
1.服務雪崩效應:
一種因 服務提供者 的不可用導致 服務調用者 的不可用,並將不可用 逐漸放大 的過程。
1) A爲服務提供者, B爲A的服務調用者, C和D是B的服務調用者. 當A的不可用,引起B的不可用,並將不可用逐漸放大C和D時, 服務雪崩就形成了
2.雪崩原因:
1) 服務提供者不可用
a.硬件故障
a1.硬件損壞造成的服務器主機宕機
a2.網絡硬件故障造成的服務提供者的不可訪問
b.程序Bug
c.緩存擊穿:緩存應用重啓, 所有緩存被清空時,以及短時間內大量緩存失效時. 大量的緩存不命中, 使請求直擊後端,造成服務提供者超負荷運行,引起服務不可用
d.用戶大量請求:在秒殺和大促開始前,如果準備不充分,用戶發起大量請求造成服務提供者的不可用
2) 重試加大流量
a.用戶重試:用戶由於忍受不了界面上長時間的等待,而不斷刷新頁面甚至提交表單
b.代碼邏輯重試:服務調用端的會存在大量服務異常後的重試邏輯
3) 服務調用者不可用
a.同步等待造成的資源耗盡:使用 同步調用 時, 會產生大量的等待線程佔用系統資源. 一旦線程資源被耗盡,服務調用者提供的服務也將處於不可用狀態, 造成服務雪崩效應產生
3.雪崩應對策略:
1) 流量控制
a.網關限流
因爲Nginx的高性能, 目前一線互聯網公司大量採用Nginx+Lua的網關進行流量控制, 由此而來的OpenResty也越來越熱門.
b.用戶交互限流
具體措施:
a21. 採用加載動畫,提高用戶的忍耐等待時間.
a22. 提交按鈕添加強制等待時間機制.
c.關閉重試
2) 改進緩存模式
a.緩存預加載
b.同步改爲異步刷新
3) 服務自動擴容
a.AWS的auto scaling
4) 服務調用者降級服務
a.資源隔離:主要是對調用服務的線程池進行隔離.
b.對依賴服務進行分類
依賴服務分爲: 強依賴和若依賴. 強依賴服務不可用會導致當前業務中止,而弱依賴服務的不可用不會導致當前業務的中止.
c.不可用服務的調用快速失敗
一般通過 超時機制, 熔斷器 和熔斷後的 降級方法 來實現
4.解決方案:
1)使用Hystrix預防服務雪崩
2)Netflix的 Hystrix 是一個幫助解決分佈式系統交互時超時處理和容錯的類庫, 它同樣擁有保護系統的能力
3)Hystrix的設計原則包括:資源隔離、熔斷器、命令模式
二、Hystrix解決方式
Hystrix:通過服務熔斷(也可以稱爲斷路)、降級、限流(隔離)、異步RPC等手段控制依賴服務的延遲與失敗
1.Circuit Breaker :熔斷器,
熔斷只是作用在服務調用這一端,只需改consumer端
1)熔斷器開關相互轉換的邏輯
a.服務的健康狀況 = 請求失敗數 / 請求總數.
b.熔斷器開關由關閉到打開的狀態轉換是通過當前服務健康狀況和設定閾值比較決定的
b1.關閉時, 請求被允許通過熔斷器. 如果當前健康狀況高於設定閾值, 開關繼續保持關閉. 如果當前健康狀況低於設定閾值, 開關則切換爲打開狀態
b2.打開狀態, 經過一段時間後, 熔斷器會自動進入半開狀態, 這時熔斷器只允許一個請求通過. 當該請求調用成功時, 熔斷器恢復到關閉狀態. 若該請求失敗, 熔斷器繼續保持打開狀態, 接下來的請求被禁止通過
c.保證服務調用者在調用異常服務時, 快速返回結果, 避免大量的同步等待
d.在一段時間後繼續偵測請求執行結果, 提供恢復服務調用的可能
2)參數設
a.circuitBreaker.requestVolumeThreshold //滑動窗口的大小,默認爲20
b.circuitBreaker.sleepWindowInMilliseconds //過多長時間,熔斷器再次檢測是否開啓,默認爲5000,即5s鍾
c.circuitBreaker.errorThresholdPercentage //錯誤率,默認50%
每當20個請求中,有50%失敗時,熔斷器就會打開,此時再調用此服務,將會直接返回失敗,不再調遠程服務。直到5s鍾之後,重新檢測該觸發條件,判斷是否把熔斷器關閉,或者繼續打開
2.downgrade:降級,fallback
1)當某個服務熔斷之後,服務器將不再被調用,此時客戶端可以自己準備一個本地的fallback回調,返回一個缺省值
3.Isolation:限流(隔離)
1)可在服務端做這個限流邏輯,也可在客戶端做
2)採用線程/信號的方式,通過隔離限制依賴的併發量和阻塞擴散
a.線程隔離:
a1.即將每個依賴服務分配獨立的線程池進行資源隔離, 從而避免服務雪崩
a2.線上建議線程池不要設置過大,否則大量堵塞線程有可能會拖慢服務器
a3.優點:
a31.使用線程可以完全隔離第三方代碼,請求線程可以快速放回
a32.當一個失敗的依賴再次變成可用時,線程池將清理,並立即恢復可用,而不是一個長時間的恢復
a33.可以完全模擬異步調用,方便異步編程
a4.缺點:
a41.線程池的主要缺點是它增加了cpu,因爲每個命令的執行涉及到排隊(默認使用SynchronousQueue避免排隊),調度和上下文切換
a42.對使用ThreadLocal等依賴線程狀態的代碼增加複雜性,需要手動傳遞和清理線程狀態
注:Netflix公司內部認爲線程隔離開銷足夠小,不會造成重大的成本或性能的影響
b.信號隔離:
b1.用於限制併發訪問,防止阻塞擴散, 與線程隔離最大不同在於執行依賴代碼的線程依然是請求線程(該線程需要通過信號申請)
b2.如果客戶端是可信的且可以快速返回,可以使用信號隔離替換線程隔離,降低開銷
b3.信號量的大小可以動態調整, 線程池大小不可以
缺點:不能設置超時和實現異步訪問,所以,只有在依賴服務是足夠可靠的情況下才使用信號量
3)除了HystrixBadRequestException異常之外,所有從run()方法拋出的異常都算作失敗,並觸發降級getFallback()和斷路器邏輯
4)HystrixBadRequestException用在非法參數或非系統故障異常等不應觸發回退邏輯的場景
5)參數解釋
a.CommandKey:依賴命名
a1.每個CommandKey代表一個依賴抽象,相同的依賴要使用相同的CommandKey名稱
a2.依賴隔離的根本就是對相同CommandKey的依賴做隔離.
b.CommandGroupKey:依賴分組
b1.命令分組用於對依賴操作分組,便於統計,彙總
b2.CommandGroup是每個命令最少配置的必選參數,在不指定ThreadPoolKey的情況下,字面值用於對不同依賴的線程池/信號區分
c.ThreadPoolKey:線程池/信號
c1.當對同一業務依賴做隔離時使用CommandGroup做區分,但是對同一依賴的不同遠程調用如(一個是Redis一個是http),可以使用HystrixThreadPoolKey做隔離區分
c2.在業務上都是相同的組,但是需要在資源上做隔離時,可以使用HystrixThreadPoolKey區分
d.Request-Cache:請求緩存
d1.重寫getCacheKey方法,實現區分不同請求的邏輯
d2.請求緩存可以讓(CommandKey/CommandGroup)相同的情況下,直接共享結果,降低依賴調用次數,在高併發和CacheKey碰撞率高場景下可以提升性能
e.SEMAPHORE:信號量隔離
e1.隔離本地代碼或可快速返回遠程調用(如memcached,redis)可以直接使用信號量隔離,降低線程隔離開銷
4.asynchronous:異步RPC
三、Hystrix metrics:容錯計數
1.Metrics:
1)Hystrix的Metrics中保存了當前服務的健康狀況, 包括服務調用總次數和服務調用失敗次數等
2)根據Metrics的計數, 熔斷器從而能計算出當前服務的調用失敗率, 用來和設定的閾值比較從而決定熔斷器的狀態切換邏輯
2.1.5之後的滑動窗口實現
1)使用RxJava的Observable.window()實現滑動窗口
四、配置參數說明
1.HystrixCommandProperties:HystrixProperty類型
1)Metrics
a.metricsRollingStatisticalWindowInMilliseconds:統計滾動的時間窗口,默認:5000毫秒(取自circuitBreakerSleepWindowInMilliseconds)
b.metricsRollingStatisticalWindowBuckets:統計窗口的Buckets的數量,默認:10個,每秒一個Buckets統計
c.metrics.rollingPercentile.enabled:是否開啓監控統計功能,默認:true
d.metrics.rollingStats.timeInMilliseconds:
e.metrics.rollingStats.numBuckets:
f.metrics.rollingPercentile.timeInMilliseconds:
h.metrics.rollingPercentile.numBuckets:
i.metrics.rollingPercentile.bucketSize:
g.metrics.healthSnapshot.intervalInMilliseconds
h.circuitBreaker.requestVolumeThreshold:斷路器請求閾值,熔斷器在整個統計時間內是否開啓的閥值,默認20。也就是在metricsRollingStatisticalWindowInMilliseconds(默認10s)內至少請求20次,熔斷器才發揮起作用
2)Circuit Breaker
a.circuitBreaker.sleepWindowInMilliseconds:斷路器休眠時間,熔斷時間窗口,默認:5秒.熔斷器中斷請求5秒後會進入半打開狀態,放下一個請求進來重試,如果該請求成功就關閉熔斷器,否則繼續等待一個熔斷時間窗口
b.circuitBreaker.enabled:斷路器開關,是否啓用熔斷器,默認true. 啓動
c.circuitBreaker.errorThresholdPercentage:斷路器錯誤請求百分比,默認:50%。當出錯率超過50%後熔斷器啓動
d.circuitBreaker.forceOpen:斷路器強制開啓,是否強制開啓熔斷器阻斷所有請求,默認:false,不開啓。置爲true時,所有請求都將被拒絕,直接到fallback
e.circuitBreaker.forceClosed:斷路器強制關閉,是否允許熔斷器忽略錯誤,默認false, 不開啓
3)Execution
a.execution.isolation.semaphore.maxConcurrentRequests:使用信號量隔離時,命令調用最大的併發數,默認:10
b.execution.isolation.strategy:使用命令調用隔離方式,默認:採用線程隔離,ExecutionIsolationStrategy.THREAD
c.execution.isolation.thread.timeoutInMilliseconds:使用線程隔離時,調用超時時間,默認:1秒
d.executionIsolationThreadPoolKeyOverride:線程池的key,用於決定命令在哪個線程池執行
e.execution.isolation.thread.interruptOnTimeout:使用線程隔離時,是否對命令執行超時的線程調用中斷(Thread.interrupt())操作.默認:true
f.execution.timeout.enabled:
4)Fallback
a.fallback.isolation.semaphore.maxConcurrentRequests:使用信號量隔離時,命令fallback(降級)調用最大的併發數,默認:10
b.fallback.enabled:是否開啓fallback降級策略 默認:true
5)Request Context
a.requestLogEnabled:是否開啓請求日誌,默認:true
b.requestCacheEnabled:是否開啓請求緩存,默認:true
2.HystrixCollapserProperties:HystrixProperty類型
1)maxRequestsInBatch:請求合併是允許的最大請求數,默認: Integer.MAX_VALUE
2)timerDelayInMilliseconds:批處理過程中每個命令延遲的時間,默認:10毫秒
3)requestCache.enabled:批處理過程中是否開啓請求緩存,默認:開啓
3.HystrixThreadPoolProperties:
1)corePoolSize:配置線程池大小,默認值10個. 建議值:請求高峯時99.5%的平均響應時間 + 向上預留一些即可
2)maxQueueSize:配置線程值等待隊列長度,默認值:-1,建議值:-1,表示不等待直接拒絕,測試表明線程池使用直接決絕策略+ 合適大小的非回縮線程池效率最高.所以不建議修改此值。當使用非回縮線程池時,queueSizeRejectionThreshold,keepAliveTimeMinutes 參數無效
3)queueSizeRejectionThreshold:隊列大小拒絕閾值
4)keepAliveTimeMinutes
5)metrics.rollingStats.timeInMilliseconds
6)metrics.rollingStats.numBuckets
五、Spring cloud hystrix示例
1.熔斷:
只是作用在服務調用這一端,因此只需要改動consumer項目相關代碼
1)pom.xml:Feign中已經依賴了Hystrix,所以在maven配置上不用做任何改動
2)application.properties:
# Hystrix配置:Feign中已經依賴了Hystrix,所以在maven配置上不用做任何改動
feign:
hystrix:
enabled: true
3)**Appliaction.java啓動類:
@EnableFeignClients //開啓feigin註解
@EnableCircuitBreaker //開啓Hystrix
@EnableDiscoveryClient //開啓註冊中心
@SpringBootApplication //spring-boot啓動
注:還可用@SpringCloudApplication代替,@SpringCloudApplication包括以下註解: @Target({ElementType.TYPE}
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
//用於調用"提供者"的方法
return new RestTemplate();
}
4)controller層:
@GetMapping("/order/{userId}/{orderNo}")
public String findOrder(@PathVariable Long userId, @PathVariable String orderNo)
throws InterruptedException {
return userConsumerService.findOrder(userId,orderNo);
}
5)service層:添加fallback屬性
String findOrder(Long userId,String orderNo) throws InterruptedException;
6)service impl層:創建回調類
@HystrixCommand(fallbackMethod = "findOrderFallback", commandProperties = {
//timeoutInMilliseconds 使用線程隔離時,調用超時時間
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")
})
public String findOrder(Long userId,String orderNo) throws InterruptedException {
Random random = new Random();
sleepRandom = random.nextInt(2000);
System.out.println("sleepRandom="+sleepRandom);
Thread.sleep(sleepRandom);
ConsumerBeehiveUser user = findById(userId);
if (user != null) {
return user.getUsername() + " 的訂單" + orderNo + " 找到啦!sleepRandom="+sleepRandom;
}
return "用戶不存在!sleepRandom="+sleepRandom;
}
public String findOrderFallback(Long userId, String orderNo) {
return "訂單查找失敗!sleepRandom="+sleepRandom;
}
7)查看hystrix狀態:
a.查看hystrix基本情況:http://localhost:8002/health
b.查看hystrix詳情:http://localhost:8002/hystrix.stream
8)參數說明:
a.快照時間窗:斷路器確定是否打開需要統計一些請求和錯誤數據,而統計的時間範圍就是快照時間窗,默認爲最近的10秒。
b.請求總數下限:在快照時間窗內,必須滿足請求總數下限纔有資格根據熔斷。默認爲20,意味着在10秒內,如果該hystrix命令的調用此時不足20次,即使所有的請求都超時或其他原因失敗,斷路器都不會打開。
c.錯誤百分比下限:當請求總數在快照時間窗內超過了下限,比如發生了30次調用,如果在這30次調用中,有16次發生了超時異常,也就是超過50%的錯誤百分比,在默認設定50%下限情況下,這時候就會將斷路器打開。
注:fallback是降級處理
2.隔離:
1)*Application:
@SpringBootApplication
@EnableDiscoveryClient //開啓eureka服務
@EnableFeignClients //開啓feigin註解
@EnableCircuitBreaker //開啓Hystrix @EnableCircuitBreaker或@EnableHystrix
@EnableHystrixDashboard //開啓dashboard圖形監控
public class ConsumerBeehiveApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
//用於調用"提供者"的方法
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerBeehiveApplication.class, args);
}
}
2)controller層:
@GetMapping("/testCircuitBreaker/{id}")
public String testCircuitBreaker(@PathVariable int id) {
StringBuilder sb = new StringBuilder();
for(int i = 0; i < 50; i++) {
String result = userConsumerService.testCircuitBreaker(i);
System.out.println("testCircuitBreaker controller:"+result);
sb.append(result).append("\n");
}
return sb.toString();
}
3)service層:
String testCircuitBreaker(int id);
4)service impl層:
@HystrixCommand(fallbackMethod = "testCircuitBreakerFallback", commandProperties = {
//errorThresholdPercentage 斷路器錯誤請求百分比
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50")
})
public String testCircuitBreaker(int id) {
if(id % 2 == 0 && id < 10) { // 直接返回
return "consumer testCircuitBreaker "+id;
} else { // 無限循環模擬超時
int j = 0;
while (true) {
j++;
}
}
}
public String testCircuitBreakerFallback(int id) {
String template = restTemplate.
getForObject("http://provider-user/user/testCircuitBreaker/"+id, String.class);
return "fallback:"+template;
}
六、概念
服務熔斷:
一般是指軟件系統中,由於某些原因使得服務出現了過載現象,爲防止造成整個系統故障,從而採用的一種保護措施,所以很多地方把熔斷亦稱爲過載保護。很多時候剛開始可能只是系統出現了局部的、小規模的故障,然而由於種種原因,故障影響的範圍越來越大,最終導致了全局性的後果。
適用場景:防止應用程序直接調用那些很可能會調用失敗的遠程服務或共享資源
服務降級:
當服務器壓力劇增的情況下,根據當前業務情況及流量對一些服務和頁面有策略的降級,以此釋放服務器資源以保證核心任務的正常運行
七、工作原理
Hystrix 是一個幫助解決分佈式系統交互時超時處理和容錯的類庫, 它同樣擁有保護系統的能力。Netflix的衆多開源項目之一。
1. 隔離:
Hystrix隔離方式採用線程/信號的方式,通過隔離限制依賴的併發量和阻塞擴散
1)線程隔離
Hystrix在用戶請求和服務之間加入了線程池。
Hystrix爲每個依賴調用分配一個小的線程池,如果線程池已滿調用將被立即拒絕,默認不採用排隊.加速失敗判定時間。線程數是可以被設定的。
原理:用戶的請求將不再直接訪問服務,而是通過線程池中的空閒線程來訪問服務,如果線程池已滿,則會進行降級處理,用戶的請求不會被阻塞,至少可以看到一個執行結果(例如返回友好的提示信息),而不是無休止的等待或者看到系統崩潰。
隔離前:
隔離後:
b)信號隔離:
信號隔離也可以用於限制併發訪問,防止阻塞擴散, 與線程隔離最大不同在於執行依賴代碼的線程依然是請求線程(該線程需要通過信號申請, 如果客戶端是可信的且可以快速返回,可以使用信號隔離替換線程隔離,降低開銷。信號量的大小可以動態調整, 線程池大小不可以。
2. 熔斷:
如果某個目標服務調用慢或者有大量超時,此時,熔斷該服務的調用,對於後續調用請求,不在繼續調用目標服務,直接返回,快速釋放資源。如果目標服務情況好轉則恢復調用。
熔斷器:Circuit Breaker
熔斷器是位於線程池之前的組件。用戶請求某一服務之後,Hystrix會先經過熔斷器,此時如果熔斷器的狀態是打開(跳起),則說明已經熔斷,這時將直接進行降級處理,不會繼續將請求發到線程池。熔斷器相當於在線程池之前的一層屏障。每個熔斷器默認維護10個bucket ,每秒創建一個bucket ,每個blucket記錄成功,失敗,超時,拒絕的次數。當有新的bucket被創建時,最舊的bucket會被拋棄。
熔斷器的狀態機:
- Closed:熔斷器關閉狀態,調用失敗次數積累,到了閾值(或一定比例)則啓動熔斷機制;
- Open:熔斷器打開狀態,此時對下游的調用都內部直接返回錯誤,不走網絡,但設計了一個時鐘選項,默認的時鐘達到了一定時間(這個時間一般設置成平均故障處理時間,也就是MTTR),到了這個時間,進入半熔斷狀態;
- Half-Open:半熔斷狀態,允許定量的服務請求,如果調用都成功(或一定比例)則認爲恢復了,關閉熔斷器,否則認爲還沒好,又回到熔斷器打開狀態;
八、流程
流程說明:
1
:每次調用創建一個新的HystrixCommand,把依賴調用封裝在run()方法中。
2
:執行execute()/queue做同步或異步調用。
3
:判斷熔斷器(circuit-breaker)是否打開,如果打開跳到步驟
8,
進行降級策略,如果關閉進入步驟。
4
:判斷線程池/隊列/信號量是否跑滿,如果跑滿進入降級步驟
8,
否則繼續後續步驟。
5
:調用HystrixCommand的run方法。運行依賴邏輯
5a:依賴邏輯調用超時,進入步驟
8。
6
:判斷邏輯是否調用成功
6a:返回成功調用結果
6b:調用出錯,進入步驟
8。
7
:計算熔斷器狀態,所有的運行狀態(成功, 失敗, 拒絕,超時)上報給熔斷器,用於統計從而判斷熔斷器狀態。
8
:getFallback()降級邏輯。
以下四種情況將觸發getFallback調用:
(
1
):run()方法拋出非HystrixBadRequestException異常
(
2
):run()方法調用超時
(
3
):熔斷器開啓攔截調用
(
4
):線程池/隊列/信號量是否跑滿
8a:沒有實現getFallback的Command將直接拋出異常
8b:fallback降級邏輯調用成功直接返回
8c:降級邏輯調用失敗拋出異常
9
:返回執行成功結果
九、執行方式
同步執行:即一旦開始執行該命令,當前線程就得阻塞着直到該命令返回結果,然後才能繼續執行下面的邏輯
異步執行:命令開始執行會返回一個Future<T>的對象,不阻塞後面的邏輯,開發者自己根據需要去獲取結果。
響應式執行:命令開始執行會返回一個Observable<T> 對象,開發者可以給給Obeservable對象註冊上Observer或者Action1對象,響應式地處理命令執行過程中的不同階段。當調用HystrixCommand的observe()方法,或使用Observable的工廠方法(just(),from())即爲響應式執行,這個功能的實現是基於Netflix的另一個開源項目RxJava(https://github.com/Netflix/RxJava)來的,更細節的用法可以參考:https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Reactive-Execution。