1. 程式人生 > >Hystrix:服務雪崩效應的特點及應對

Hystrix:服務雪崩效應的特點及應對

一、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狀態:

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;
}