探讨断路器模式
依賴隔離,Docker實(shí)現(xiàn)進(jìn)程隔離,使得容器之間互不影響,而Hystrix,使用的是該模式線程池的隔離,會為每個HystrixCommand,創(chuàng)建一個獨(dú)立的線程池,這樣就算某個HystrixCommand,都裝下了依賴服務(wù),出現(xiàn)延遲過高的情況,也只是對著該依賴服務(wù)的調(diào)用產(chǎn)生影響,并不會拖慢其他服務(wù),使用了HystrixCommand,還將某個函數(shù)包裝成某個命令時,Hystrix框架就會自動的實(shí)現(xiàn)依賴隔離,所以依賴隔離,服務(wù)降級,在使用的時候,都是一體化實(shí)現(xiàn)的,這樣利用Hystrix來實(shí)現(xiàn),服務(wù)容錯保護(hù),在編程模型上,非常方便了,除了依賴隔離,服務(wù)降級之外,Hystrix還有一個重要元素,服務(wù)熔斷
我們先來看看服務(wù)的熔斷,超時配置我先注釋掉,接下來就是熔斷了,熔斷依舊是使用這個注解,里面也是Command,commandProperties,這里面怎么配置呢,我們繼續(xù)打開這個HystrixCommandPropertiescom.netflix.hystrix.HystrixCommandProperties熔斷主要是用到這四個this.circuitBreakerEnabled = getProperty(propertyPrefix, key, "circuitBreaker.enabled",
builder.getCircuitBreakerEnabled(), default_circuitBreakerEnabled);this.circuitBreakerRequestVolumeThreshold = getProperty(propertyPrefix, key,
"circuitBreaker.requestVolumeThreshold",
builder.getCircuitBreakerRequestVolumeThreshold(),
default_circuitBreakerRequestVolumeThreshold);this.circuitBreakerSleepWindowInMilliseconds = getProperty(propertyPrefix, key,
"circuitBreaker.sleepWindowInMilliseconds",
builder.getCircuitBreakerSleepWindowInMilliseconds(),
default_circuitBreakerSleepWindowInMilliseconds);this.circuitBreakerErrorThresholdPercentage = getProperty(propertyPrefix, key,
"circuitBreaker.errorThresholdPercentage",
builder.getCircuitBreakerErrorThresholdPercentage(),
default_circuitBreakerErrorThresholdPercentage);這四行的配置,比如第一行好了,我們來看一下他的英文的注釋,我們來找到這個circuitBreakerEnabled,這個是他定義的字段// Whether circuit breaker should be enabled.
private final HystrixProperty<Boolean> circuitBreakerEnabled; 點(diǎn)進(jìn)去,這就有了,其實(shí)就是這四個// number of requests that must be made within a statisticalWindow before
// open/close decisions are made using stats
private final HystrixProperty<Integer> circuitBreakerRequestVolumeThreshold; // milliseconds after tripping circuit before allowing retry
private final HystrixProperty<Integer> circuitBreakerSleepWindowInMilliseconds; // Whether circuit breaker should be enabled.
private final HystrixProperty<Boolean> circuitBreakerEnabled; // % of 'marks' that must be failed to trip the circuit
private final HystrixProperty<Integer> circuitBreakerErrorThresholdPercentage; 前面四個,我們需要的是前面四個,我們來設(shè)置一下@HystrixCommand(commandProperties = {@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),//設(shè)置熔斷@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")
})第一個我們剛剛也說了,設(shè)置熔斷,下面三個參數(shù)的具體含義,等一下我們細(xì)講,我先設(shè)置好,大家可以先觀察現(xiàn)象,這四個我已經(jīng)設(shè)置好了,我們再來試一下http://localhost:8010/getProductInfoList已經(jīng)啟動,我們再來訪問一下,他一直都是提示太擁擠了,一直都是訪問錯誤,這個我們要怎么來測試呢@RestController
@DefaultProperties(defaultFallback = "defaultFallback")
public class HystrixController {// 3.@HystrixCommand(commandProperties = {@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),//設(shè)置熔斷@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")})@GetMapping("/getProductInfoList")public String getProductInfoList(){RestTemplate restTemplate = new RestTemplate();return restTemplate.postForObject("http://127.0.0.1:7900/product/listForOrder", Arrays.asList("157875196366160022"),String.class); } private String fallback(){return "太擁擠了,請稍后再試~~";} private String defaultFallback(){return "默認(rèn)提示:太擁擠了,請稍后再試~~";}
}我們不妨這樣子來測試它的熔斷,這里傳一個參數(shù),就叫number,如果他是偶數(shù)的話他就去請求,就去請求product的服務(wù),如果是偶數(shù)我們就直接返回成功,否則他就會請求product服務(wù),他一旦請求的話,如果走到這里的話肯定會觸發(fā)fallbackreturn restTemplate.postForObject("http://127.0.0.1:7900/product/listForOrder", Arrays.asList("157875196366160022"),String.class);因?yàn)檫@是要兩秒的等待時間,而我們這邊沒有設(shè)置超時時間,所以這邊的超時時間是一秒,重啟一下,http://localhost:8010/getProductInfoList?number=1再開一個http://localhost:8010/getProductInfoList?number=2這個是number等于2,等待他啟動好,先來訪問一下1,等于1就會觸發(fā)降級,等于2就是success這個是success,這個就是一直太擁擠了,我們來讓他來產(chǎn)生以下熔斷,我不停的刷新這個窗口,我讓他錯誤率達(dá)到60%,那我現(xiàn)在來訪問一下這個,神奇吧,你看我繼續(xù)訪問,他就變成success了,你看我再來一次,不停的刷,隔一會就好了,看一下我們剛才觀察的現(xiàn)象,這是我們剛才用到的三個配置,這三個配置你可以看到
有一個共同的詞circuitBreaker,英文直譯過來,就是斷路開關(guān),斷路器,斷路器本身是電路上的一種開關(guān)裝置,我小時候還玩過保險絲,當(dāng)然不是插著電的時候去玩,不然牛逼就吹大了,當(dāng)然是取下來之后玩的,電流過大的時候,還會燒壞保險絲,這個時候你還得手動的去拉一下閘,其實(shí)就是跳閘了,我上學(xué)那會印象特別深刻,就是在寢室里面,有同學(xué)還在用電腦看片,另外一個同學(xué)又是燒水啊,用電吹風(fēng)什么的,結(jié)果電吹風(fēng)一開呢,好了,整一層都被他搞跳閘了,人家看片也沒法看了,特別掃興,話有說回來,我們說到電路,對電路本身也是一種保護(hù),你想想他如果不跳閘的話,那么電流過大,還可能會燒壞電器,所以斷路器的作用呢,是及時的切斷故障電路,放到Hystrix里面也是類似的,當(dāng)某個服務(wù)單元,發(fā)生故障,類似電器發(fā)生短路之后,通過斷路器的故障監(jiān)控,這里就類似于熔斷的保險絲,直接切斷原來的主邏輯調(diào)用,寫過一篇關(guān)于斷路器的文章,這是鏈接https://martinfowler.com/bliki/CircuitBreaker.html微服務(wù)和分布式里面,容錯是必須要考慮的,通常的做法有兩種,一種是重試機(jī)制,對于預(yù)期的短暫的問題,可以使用重試是可以解決的,第一次不成功我再重試一次,他就成功了,但是對于更長時間解決的問題,你不斷嘗試也是沒有太大意義的,這個時候你就可以使用斷路器模式了,斷路器模式是將受保護(hù)的服務(wù),封裝在一個斷路器對象里面,當(dāng)故障達(dá)到一定的值,斷路器將會跳閘,斷路器對象返回錯誤,剛才那篇文章里面就描述了這幅圖
非常關(guān)鍵,這幅圖描述了斷路器狀態(tài),狀態(tài)機(jī)有三種狀態(tài),close,open,還有half open,close是關(guān)閉器的關(guān)閉狀態(tài),調(diào)用失敗次數(shù)累計(jì),到了一定的閾值,或者說一定的比例,就會啟動熔斷機(jī)制,open是熔斷器打開狀態(tài),此時對服務(wù)直接返回錯誤,但設(shè)計(jì)了一個時鐘選項(xiàng),默認(rèn)的時鐘,到了這個時間之后,就會進(jìn)入半熔斷狀態(tài),也就是half open,允許定量的服務(wù)請求,如果調(diào)用都成功,或者一定的比例,則認(rèn)為恢復(fù)了,他就會關(guān)閉熔斷器,否則就會認(rèn)為還沒好,又回到熔斷器打開狀態(tài),這三個參數(shù)就是控制熔斷器的關(guān)鍵參數(shù)
我剛剛特別強(qiáng)調(diào)了,設(shè)計(jì)了一個時鐘選項(xiàng),默認(rèn)的時鐘達(dá)到了一定的時間,進(jìn)入半熔斷狀態(tài),那sleepWindow,這個參數(shù),這里面的window,是什么意思呢,很多地方會翻譯成時間窗口,斷路器是否需要打開統(tǒng)計(jì)一下,請求和錯誤數(shù)據(jù)的時候,他其實(shí)是有一個范圍的,這個時間范圍就被稱為時間窗口,當(dāng)斷路器打開,對主邏輯進(jìn)行熔斷之后,Hystrix會啟動一個休眠時間窗,在這個時間窗內(nèi),降級邏輯臨時的成為主邏輯,當(dāng)休眠時間窗到期,斷路器將進(jìn)入半開狀態(tài),釋放到一定的請求到原來的主邏輯上,如果此次正常返回,主邏輯繼續(xù)閉合,主邏輯恢復(fù),如果這次請求依然有問題,斷路器將繼續(xù)進(jìn)入打開狀態(tài),休眠時間窗重新計(jì)時,我們看第一個參數(shù),這個參數(shù)我們設(shè)置的是1萬毫秒,也就是10秒,這個休眠時間窗,結(jié)束之后,會將斷路器設(shè)置為half open,嘗試熔斷請求命令,如果依然失敗,就將斷路器依舊設(shè)置為打開狀態(tài),如果成功就設(shè)置為關(guān)閉狀態(tài),我們再來看第二個參數(shù),這個參數(shù)好理解,設(shè)置在滾動時間窗口中,斷路器的最少請求數(shù),我們這里設(shè)置的是10,最后一個參數(shù),設(shè)置斷路器打開,錯誤百分比條件,這里設(shè)置的是60,表示在滾動時間窗口中,如果發(fā)生了10次調(diào)用,在這10次調(diào)用中,有7次發(fā)生了異常,那就70%了,那你70%就超過了我的60%了,這個時候斷路器會設(shè)置為打開狀態(tài),否則就會設(shè)置為關(guān)閉狀態(tài),現(xiàn)在大家明白這三個參數(shù)的含義,再回想我們剛才的現(xiàn)象,是不是很容易理解了呢,其實(shí)這里關(guān)鍵就是多了一個半開狀態(tài),如果你還是覺得不直觀,后續(xù)我們會將Hystrix和dashboard面板的使用,被HsytrixCommand接口調(diào)用的情況,會被Hystrix記錄下來,然后我們可以在一個dashboard上,在一個可視化的界面上,觀察到,因此大家可以記住熔斷三個狀態(tài)的轉(zhuǎn)換,和這些參數(shù),后續(xù)我們到Hystrix的面板上,能更直觀的觀察到
package com.learn.controller;import java.util.Arrays;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;@RestController
@DefaultProperties(defaultFallback = "defaultFallback")
public class HystrixController {//@HystrixCommand(fallbackMethod = "fallback")//2、超時設(shè)置
// @HystrixCommand(commandProperties = {
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000") //超時時間設(shè)置為3秒
// })
// 3.@HystrixCommand(commandProperties = {@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),//設(shè)置熔斷@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")})
// @HystrixCommand@GetMapping("/getProductInfoList")public String getProductInfoList(@RequestParam("number") Integer number){
// public String getProductInfoList(){if(number % 2 == 0){return "success";}RestTemplate restTemplate = new RestTemplate();return restTemplate.postForObject("http://127.0.0.1:7900/product/listForOrder", Arrays.asList("157875196366160022"),String.class);}private String fallback(){return "太擁擠了,請稍后再試~~";}private String defaultFallback(){return "默認(rèn)提示:太擁擠了,請稍后再試~~";}
}
?
總結(jié)