SpringCloud之Hystrix服務降級入門全攻略
理論知識
Hystrix是什麼?
Hystrix是由Netflix開源的一個服務隔離元件,通過服務隔離來避免由于依賴延遲、異常,引起資源耗盡導緻系統不可用的解決方案。這說的有點兒太官方了,它的功能主要有以下三個:
服務降級
SpringCloud是通過HTTP Rest的方式在“微服務”之間進行調用的,是以每一個“微服務”都是一個web項目。既然它是一個web項目,它就就有可能會發生錯誤,這個錯誤有可能是伺服器記憶體不足、用戶端傳參錯誤、網絡問題等,也有可能是人為的(這個就是服務熔斷)。也就是說,會因為一些原因進而不能給調用者傳回正确的資訊。
對于我們目前的單個SpringBoot項目來說,我們使用Ajax等一些方式調用接口時,如果伺服器發生錯誤,我們在前端就會對這個錯誤進行處理。有可能是重試調用接口,或者給使用者一個友好的提示,比如“服務繁忙,稍後再試”啥的。
但是在分布式系統中,同樣也會發生一些“錯誤”,而且在多個服務之間調用時,如果不能對這些“錯誤”進行友好的處理,就會導緻我們整個項目癱瘓,這是萬萬不能發生的。是以Hystrix利用服務降級來很好的解決了這個問題。這個其實就類似于我們的try-catch這樣的機制,發生錯誤了,我就執行catch中的代碼。
通過服務降級,能保證在某個或某些服務出問題的時間,不會導緻整個項目出現問題,避免級聯故障,進而來提高分布式系統的彈性。
服務熔斷
建設先看下邊的服務降級代碼,将整個服務降級的代碼部分全部看完,再來看下邊這段理論,你一定會茅塞頓開的。
Hystrix意為“斷路器”,就和我們生活中的保險絲,開關一個道理。
當我們給整個服務配置了服務降級後,如果服務提供者發生了錯誤後,就會調用降級後的方法來保證程式的運作。但是呢?有一個問題,調用者并不知道它調用的這個服務出錯了,就會在業務發生的時候一直調用,然後服務會一直報錯,然後去調用降級方法。好比下圖中:
它們的對話如下:
Client:我要調用你的方法A
Server:不行,我報錯了。你調用降級方法吧,你的我的都行!
Client:哎呀,伺服器報錯了,那我就調用一下降級方法吧。
過了一會兒。。。。。。
Server:剛才不是說了嗎?我報錯了。你調用降級方法吧,你的我的都行!
又過了一會兒。。。。。。
Server:沒完了是吧?我說過我報錯了,你去調用這個降級方法啊。非要讓我的代碼又運作一次?
以上的對話說明了一個問題,當服務端發生了錯誤後,用戶端會調用降級方法。但是,當有一個新的通路時,用戶端會一直調用服務端,讓服務端運作一些明知會報錯的代碼。這能不能避免啊,我知道我錯了,你通路我的時候,就直接去通路降級方法,不要再讓我執行錯的代碼。
這就是服務熔斷,就好比我們家中的保險絲。當檢測到家中的用電負荷過大時,就斷開一些用電器,來保證其他的可用。在分布式系統中,就是調用一個系統時,在一定時間内,這個服務發生的錯誤次數達到一定的值時, 我們就打開這個斷路器,不讓調用過去,而是讓他直接去調用降級方法。再過一段時間後,當一次調用時,發現這個服務通了,就将這個斷路器改為“半開”狀态,讓調用一個一個的慢慢過去,如果一直沒有發生錯誤,就将這個斷路器關閉,讓所有的服務全部通過。
服務限流
服務限流就容易了解多了,顧名思義,這是對通路的流量進行限制,就比如上邊的場景,我們還可能通過服務限流的方法來解決高并發以及秒殺等問題。主流的限流算法主要有:漏桶算法和令牌算法
開始碼代碼吧
不貼代碼,說這麼多有什麼用?這不是耍流氓嗎?
先建立一個我們需要的幾個項目:
子產品名稱 代碼中項目名稱 備注
Eureka注冊中心 eureka-alone-7000 測試期間,使用一個注冊中心而不是叢集
用戶端(消費者,服務調用者) hystrix-consumer-80 使用Feign或OpenFeign進行服務調用
服務端(提供者,服務提供者) hystrix-provider-8001
這三個項目的建立代碼略(項目代碼位址)
在用戶端和服務端都加入Hystrix的依賴(當然是在哪端進行服務降級就在哪端使用)
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
服務降級有兩種解決思路:可以分别從服務調用者和服務提供者進行服務降級,也就是進行錯誤的“兜底”
-
從服務提供者方進行服務降級
我們先在提供者方的下列方法模拟一個“響應逾時錯誤”。
/**
* 這個方法會造成服務調用逾時的錯誤
* 其實本身體不是錯誤,而是服務響應時間超過了我們要求的時間,就認為它錯了
* @param id
* @return
*/
public String timeOutError(Integer id){
return "服務調用逾時";
}
我們就給它定義一個錯誤回調方法,加上如下注解:
- 這個方法會造成服務調用逾時的錯誤
- 其實本身體不是錯誤,而是服務響應時間超過了我們要求的時間,就認為它錯了
- @param id
-
@return
*/
@HystrixCommand(fallbackMethod = "TimeOutErrorHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public String timeOutError(Integer id){
try {
//我們讓這個方法休眠5秒,是以一定會發生錯誤,也就會調用下邊的fallbakcMethod方法
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "服務正常調用"+id;
}
- 這個就是當上邊方法的“兜底”方法
public String TimeOutErrorHandler(Integer id) {
return "對不起,系統處理逾時"+id;
上邊這個注解要注意三點:
fallCallbackMethod中的這個參數就是“兜底”方法
fallCallbackMethod中的這個方法的聲明要和本方法一緻
commandProperties屬性中可以寫多個@HystrixProperty注解,其中的name和value就是配置對應的屬性,上例中的這個就是配置響應逾時
最後在主啟動類上加上這個注解
@SpringBootApplication
@EnableEurekaClient //本服務啟動後會自動注冊進eureka服務中
@EnableCircuitBreaker
public class ProviderAppication_8001 {
public static void main(String[] args) {
SpringApplication.run(ProviderAppication_8001.class, args);
}
這個我們是在服務提供者方面進行的錯誤處理,是以對服務調用者不做任何處理,啟動三個項目(consumer,provder,eureka)。然後通路
http://localhost/consumer/hello/999,理論上是要傳回服務調用正常999,但是呢,由于我們人為造成了逾時錯誤,是以就一定會傳回fallback中的對不起,系統處理逾時999,而且這個傳回是會在3秒後。
如果你覺得上邊這個逾時的錯誤示範很麻煩,可以直接在方法中寫一個運作時錯誤,比如:int i = 10/0;也會進行fallbackMethod的調用。之是以要用這個逾時配置,就是為了讓你知道Hystrix可以對什麼樣的錯誤進行fallback,它的更多配置參考
https://github.com/Netflix/Hystrix/wiki/Configuration2.從服務提供者方進行服務降級
和在服務提供方進行服務降級相比,在服務調用方(用戶端、消費者)進行服務降級是更常用的方法。這兩者相比,前者是要讓服務提供者對自己可發生的錯誤進行“預處理”,這樣,一定要保證調用者通路到我才會調用這個“兜底”方法。但是,大家想一下,如果我這個服務當機了呢?用戶端根本就調用不到我,它怎麼可能接收到我的“兜底”方法呢?是以,在用戶端進行服務降級是更常用的方法。
一個小疑問,如果我在用戶端和服務端都進行了服務降級,是都會調用?還是先調用哪個?自己想喽,稍微動動你聰明的小腦袋。
為了不和上一個項目的代碼沖突,我将上邊這個@Service給注掉(也就是讓Spring來管理它),進而用另外一個接口的實作,下邊是我們新的serive類
@Service
public class OrignService implements IExampleService {
/**
* 不用這個做示範,就空實作
*/
@Override
public String timeOutError(Integer id) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "服務正常調用"+id;
}
/**
* 不發生錯誤的正确方法
*/
@Override
public String correct(Integer id) {
return "通路正常,服務端沒有進行任何錯誤"+id;
}
在主啟動類上添加如下注解:
@EnableHystrix //注意這個和服務端的注解是不一樣的
在application.yml中開戶feign對Hystrix的支援
feign:
hystrix:
enabled:true
将之前在provider項目中的@HystrixCommond放在feign的接口中
3.改進下解決方案
以上的兩種方案看似可行, 但是,實際呢?心想,這是一個合格程式員應該做的事嗎?每個接口我們都要寫一個fallback方法?然後和我們的業務代碼要寫在一起?就好的“低耦合,高内聚”呢?
第一種解決方案,就是使用@DefaultProperties在整個Controller類上,顧名思義,就是給它一個預設的“兜底”方法,就不用每一個需要降級的方法進行設定fallbackMethod了,我們隻需要加上@HystrixCommand好了。這個方法太過簡單,不做代碼示範,在文末的代碼中專門寫了注釋
第二種解決方法:我們在用戶端不是通過Feign調用的嗎?是有一個Feign的本地接口類,我們直接對這個類進行設定就好了。直接上代碼。
@Component
//@FeignClient(value = "hystrix-provider") //這是之前的調用
@FeignClient(value = "hystrix-provider",fallback = ProviderServiceImpl.class) //這回使用了Hystrix的服務降級
public interface IProviderService {
@GetMapping("provider/hello/{id}")
public String hello(@PathVariable("id") Integer id);
public class ProviderServiceImpl implements IProviderService {
@Override
public String hello(Integer id) {
return "調用遠端服務錯誤了";
}
以上兩種方法的對比:
第一種和我們的業務類進行了耦合,而且如果要對每個方法進行fallback,就要多寫一個方法,代碼太過臃腫。但是,它提供了一個DefaultProperties注解,可以提供預設的方法,這個後者是沒有的。這種方法适合直接使用Ribbon結合RestTemplate進行調用的方法
第二種提供了一個Feign接口的實作類來處理服務降級問題,将所有的fallback方法寫到了一起,和我們的業務代碼完全解耦了。對比第一個,我們可以定義一個統一的方法來實作DefalutPropeties。這種方法适合Feign作為用戶端的調用,比較推薦這種。
請再回去看一下上邊的關于服務熔斷的理論知識,我相信你一定能看懂。當啟用服務降級時,會預設啟用服務熔斷機制,我們隻需要對一些參數進行配置就可以了,就是在上邊的@HystrixCommand中的一些屬性,比如:
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000"),
@HystrixProperty(name="circuitBreaker.enabled",value="true"),//開戶斷路器
@HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value="20"),//請求次數的峰值
@HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value="10000"),//檢測錯誤次數的時間範圍
@HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value="60")//請求失敗率達到多少比例後會打開斷路器
這些配置可以在
了解,也可以打開檢視HystrixCommandProperties類中的屬性(使用idea一搜尋就有),全部都有預設配置
原文位址
https://www.cnblogs.com/Lyn4ever/p/12528913.html