(新排版)轉自:https://blog.csdn.net/ruihin/article/details/77579794
一、Hystrix說明
1、服務雪崩效應
是一種因服務提供者的不可用導緻服務調用者的不可用,并将不可用 逐漸放大 的過程。比如: A為服務提供者, B為A的服務調用者, C和D是B的服務調用者. 當A的不可用,引起B的不可用,并将不可用逐漸放大C和D時, 服務雪崩就形成了
2、雪崩原因:
服務提供者不可用
1) 硬體故障
硬體損壞造成的伺服器主機當機。(網絡硬體故障造成的服務提供者的不可通路)
2) 程式bug
3) 緩存擊穿
緩存應用重新開機, 所有緩存被清空時,以及短時間内大量緩存失效時. 大量的緩存不命中, 使請求直擊後端,造成服務提供者超負荷運作,引起服務不可用
4) 使用者的大量請求
在秒殺和大促開始前,如果準備不充分,使用者發起大量請求造成服務提供者的不可用
重試加大流量
服務調用者不可用
同步等待造成的資源耗盡:使用同步調用時, 會産生大量的等待線程占用系統資源,一旦線程資源被耗盡,服務調用者提供的服務也将處于不可用狀态, 造成服務雪崩效應産生。
3.雪崩應對政策
流量控制
1) 網關限流
因為Nginx的高性能, 目前一線網際網路公司大量采用Nginx+Lua的網關進行流量控制, 由此而來的OpenResty也越來越熱門。
2) 使用者互動限流
具體措施:a、采用加載動畫,提高使用者的忍耐等待時間。b、送出按鈕添加強制等待時間機制。
3) 關閉重試
改進緩存模式
1) 緩存預加載
2) 同步改為異步重新整理
服務自動擴容
AWS的auto scaling
服務調用者降級服務
1) 資源隔離
主要是對調用服務的線程池進行隔離
2) 對依賴服務進行分類
依賴服務分為: 強依賴和弱依賴
強依賴服務不可用會導緻目前業務中止;而弱依賴服務的不可用不會導緻目前業務的中止。
3) 不可用服務的調用快速失敗
一般通過逾時機制, 熔斷器和熔斷後的降級方法來實作。
4.解決方案:
使用Hystrix預防服務雪崩,Netflix的Hystrix 是一個幫助解決分布式系統互動時逾時處理和容錯的類庫,它同樣擁有保護系統的能力。
Hystrix的設計原則包括:資源隔離、熔斷器、指令模式。
二、Hystrix解決方式
Hystrix:通過服務熔斷(也可以稱為斷路)、降級、限流(隔離)、異步RPC等手段控制依賴服務的延遲與失敗。
1. Circuit Breaker :熔斷器
熔斷隻是作用在服務調用這一端,隻需改consumer端。
熔斷器開關互相轉換的邏輯
a.服務的健康狀況= 請求失敗數/ 請求總數.
b.熔斷器開關由關閉到打開的狀态轉換是通過目前服務健康狀況和設定門檻值比較決定的
b1.關閉時, 請求被允許通過熔斷器. 如果目前健康狀況高于設定門檻值, 開關繼續保持關閉. 如果目前健康狀況低于設定門檻值, 開關則切換為打開狀态
b2.打開狀态, 經過一段時間後, 熔斷器會自動進入半開狀态, 這時熔斷器隻允許一個請求通過. 當該請求調用成功時, 熔斷器恢複到關閉狀态. 若該請求失敗, 熔斷器繼續保持打開狀态, 接下來的請求被禁止通過
c.保證服務調用者在調用異常服務時, 快速傳回結果, 避免大量的同步等待
d.在一段時間後繼續偵測請求執行結果, 提供恢複服務調用的可能
參數設定
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)采用線程/信号的方式,通過隔離限制依賴的并發量和阻塞擴散
線程隔離:
a1.即将每個依賴服務配置設定獨立的線程池進行資源隔離, 進而避免服務雪崩
a2.線上建議線程池不要設定過大,否則大量堵塞線程有可能會拖慢伺服器
a3.優點:
a31.使用線程可以完全隔離第三方代碼,請求線程可以快速放回
a32.當一個失敗的依賴再次變成可用時,線程池将清理,并立即恢複可用,而不是一個長時間的恢複
a33.可以完全模拟異步調用,友善異步程式設計
a4.缺點:
a41.線程池的主要缺點是它增加了cpu,因為每個指令的執行涉及到排隊(預設使用SynchronousQueue避免排隊),排程和上下文切換
a42.對使用ThreadLocal等依賴線程狀态的代碼增加複雜性,需要手動傳遞和清理線程狀态
注:Netflix公司内部認為線程隔離開銷足夠小,不會造成重大的成本或性能的影響
信号隔離:
b1.用于限制并發通路,防止阻塞擴散, 與線程隔離最大不同在于執行依賴代碼的線程依然是請求線程(該線程需要通過信号申請)
b2.如果用戶端是可信的且可以快速傳回,可以使用信号隔離替換線程隔離,降低開銷
b3.信号量的大小可以動态調整, 線程池大小不可以
缺點:不能設定逾時和實作異步通路,是以,隻有在依賴服務是足夠可靠的情況下才使用信号量
3)除了HystrixBadRequestException異常之外,所有從run()方法抛出的異常都算作失敗,并觸發降級getFallback()和斷路器邏輯
4)HystrixBadRequestException用在非法參數或非系統故障異常等不應觸發回退邏輯的場景
5)參數解釋
CommandKey:依賴命名
1、每個CommandKey代表一個依賴抽象,相同的依賴要使用相同的CommandKey名稱
2、依賴隔離的根本就是對相同CommandKey的依賴做隔離.
CommandGroupKey:依賴分組
1、指令分組用于對依賴操作分組,便于統計和彙總
2、CommandGroup是每個指令最少配置的必選參數,在不指定ThreadPoolKey的情況下,字面值用于對不同依賴的線程池/信号區分
ThreadPoolKey:線程池/信号
1、當對同一業務依賴做隔離時使用CommandGroup做區分,但是對同一依賴的不同遠端調用如(一個是Redis一個是http),可以使用HystrixThreadPoolKey做隔離區分
2、在業務上都是相同的組,但是需要在資源上做隔離時,可以使用HystrixThreadPoolKey區分
Request-Cache:請求緩存
1、重寫getCacheKey方法,實作區分不同請求的邏輯
2、請求緩存可以讓(CommandKey/CommandGroup)相同的情況下,直接共享結果,降低依賴調用次數,在高并發和CacheKey碰撞率高場景下可以提升性能
SEMAPHORE:信号量隔離
1、隔離本地代碼或可快速傳回遠端調用(如memcached,redis)可以直接使用信号量隔離,降低線程隔離開銷。
4.asynchronous:異步RPC
三、Hystrix metrics:容錯計數
1. Metrics
1)Hystrix的Metrics中儲存了目前服務的健康狀況, 包括服務調用總次數和服務調用失敗次數等
2)根據Metrics的計數,熔斷器進而能計算出目前服務的調用失敗率, 用來和設定的門檻值比較進而決定熔斷器的狀态切換邏輯
version-2.1.5之後的滑動視窗實作,使用RxJava的Observable.window()實作滑動視窗
四、配置參數說明
1. HystrixCommandProperties:HystrixProperty類型
1) Metrics
metricsRollingStatisticalWindowInMilliseconds
統計滾動的時間視窗,預設:5000毫秒(取自circuitBreakerSleepWindowInMilliseconds)
metricsRollingStatisticalWindowBucket
統計視窗的Buckets的數量,預設:10個,每秒一個Buckets統計
metrics.rollingPercentile.enable
是否開啟監控統計功能,預設:true
metrics.rollingStats.timeInMilliseconds
metrics.rollingStats.numBuckets
metrics.rollingPercentile.timeInMilliseconds
metrics.rollingPercentile.numBuckets
metrics.rollingPercentile.bucketSize
metrics.healthSnapshot.intervalInMilliseconds
circuitBreaker.requestVolumeThreshold
斷路器請求門檻值,熔斷器在整個統計時間内是否開啟的閥值,預設20。也就是在metricsRollingStatisticalWindowInMilliseconds(預設10s)内至少請求20次,熔斷器才發揮起作用
Circuit Breaker
circuitBreaker.sleepWindowInMilliseconds
斷路器休眠時間,熔斷時間視窗,預設:5秒.熔斷器中斷請求5秒後會進入半打開狀态,放下一個請求進來重試,如果該請求成功就關閉熔斷器,否則繼續等待一個熔斷時間視窗
circuitBreaker.enabled
斷路器開關,是否啟用熔斷器,預設true. 啟動
circuitBreaker.errorThresholdPercentag
斷路器錯誤請求百分比,預設:50%。當出錯率超過50%後熔斷器啟動
circuitBreaker.forceOpen
斷路器強制開啟,是否強制開啟熔斷器阻斷所有請求,預設:false,不開啟。置為true時,所有請求都将被拒絕,直接到fallback
circuitBreaker.forceClosed
斷路器強制關閉,是否允許熔斷器忽略錯誤,預設false, 不開啟
2) Execution
execution.isolation.semaphore.maxConcurrentRequests
使用信号量隔離時,指令調用最大的并發數,預設:10
execution.isolation.strategy
使用指令調用隔離方式,預設:采用線程隔離,ExecutionIsolationStrategy.THREAD
execution.isolation.thread.timeoutInMilliseconds
使用線程隔離時,調用逾時時間,預設:1秒
executionIsolationThreadPoolKeyOverride
線程池的key,用于決定指令在哪個線程池執行
execution.isolation.thread.interruptOnTimeout
使用線程隔離時,是否對指令執行逾時的線程調用中斷(Thread.interrupt())操作預設:true
execution.timeout.enabled
3) Fallback
fallback.isolation.semaphore.maxConcurrentRequest
使用信号量隔離時,指令fallback(降級)調用最大的并發數,預設:10
fallback.enabled
是否開啟fallback降級政策預設:true
Request Context
requestLogEnabled
是否開啟請求日志,預設:true
requestCacheEnabled
是否開啟請求緩存,預設:true
2. HystrixCollapserProperties:HystrixProperty類型
maxRequestsInBatch
請求合并是允許的最大請求數,預設: Integer.MAX_VALUE
timerDelayInMilliseconds
批處理過程中每個指令延遲的時間,預設:10毫秒
requestCache.enabled
批處理過程中是否開啟請求緩存,預設:開啟
3. HystrixThreadPoolProperties:
corePoolSize
配置線程池大小,預設值10個. 建議值:請求高峰時99.5%的平均響應時間+ 向上預留一些即可
maxQueueSize
配置線程值等待隊列長度,預設值:-1,建議值:-1,表示不等待直接拒絕,測試表明線程池使用直接決絕政策+ 合适大小的非回縮線程池效率最高.是以不建議修改此值。當使用非回縮線程池時,queueSizeRejectionThreshold,keepAliveTimeMinutes參數無效
queueSizeRejectionThreshold
隊列大小拒絕門檻值
keepAliveTimeMinutes
metrics.rollingStats.timeInMilliseconds
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
publicRestTemplaterestTemplate() {
//用于調用"提供者"的方法
return newRestTemplate();
}
4) controller層:
@GetMapping("/order/{userId}/{orderNo}")
publicStringfindOrder(@PathVariableLong userId,@PathVariableString orderNo)throwsInterruptedException {
returnuserConsumerService.findOrder(userId,orderNo);
}
5) service層:添加fallback屬性
StringfindOrder(Long userId,String orderNo)throwsInterruptedException;
6) service impl層:建立回調類
@HystrixCommand(fallbackMethod="findOrderFallback",commandProperties= {
//timeoutInMilliseconds 使用線程隔離時,調用逾時時間
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1000")
})
publicStringfindOrder(Long userId,String orderNo)throwsInterruptedException {
Random random =newRandom();
sleepRandom= random.nextInt(2000);
System.out.println("sleepRandom="+sleepRandom);
Thread.sleep(sleepRandom);
ConsumerBeehiveUser user = findById(userId);
if(user !=null) {
returnuser.getUsername() +" 的訂單"+ orderNo +" 找到啦!sleepRandom="+sleepRandom;
}
return"使用者不存在!sleepRandom="+sleepRandom;
}
publicStringfindOrderFallback(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 classConsumerBeehiveApplication {
@Bean
@LoadBalanced
publicRestTemplaterestTemplate() {
//用于調用"提供者"的方法
return newRestTemplate();
}
public static voidmain(String[] args) {
SpringApplication.run(ConsumerBeehiveApplication.class,args);
}
}
2) controller層:
@GetMapping("/testCircuitBreaker/{id}")
publicStringtestCircuitBreaker(@PathVariableintid) {
StringBuilder sb =newStringBuilder();
for(inti =0;i <50;i++) {
String result =userConsumerService.testCircuitBreaker(i);
System.out.println("testCircuitBreaker controller:"+result);
sb.append(result).append("\n");
}
returnsb.toString();
}
3) service層:
StringtestCircuitBreaker(intid);
4) service impl層:
@HystrixCommand(fallbackMethod="testCircuitBreakerFallback",commandProperties= {
//errorThresholdPercentage 斷路器錯誤請求百分比
@HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value="50")
})
publicStringtestCircuitBreaker(intid) {
if(id %2==0&& id <10) {// 直接傳回
return"consumer testCircuitBreaker "+id;
}else{// 無限循環模拟逾時
intj =0;
while(true) {
j++;
}
}
}
publicStringtestCircuitBreakerFallback(intid) {
String template =restTemplate.getForObject("http://provider-user/user/testCircuitBreaker/"+id,String.class);
return"fallback:"+template;
}