1. 程式人生 > >SpringCloud之Hystrix服務降級入門全攻略

SpringCloud之Hystrix服務降級入門全攻略

理論知識

*

Hystrix是什麼?

Hystrix是由Netflix開源的一個服務隔離元件,通過服務隔離來避免由於依賴延遲、異常,引起資源耗盡導致系統不可用的解決方案。這說的有點兒太官方了,它的功能主要有以下三個: *

服務降級

​ SpringCloud是通過HTTP Rest的方式在“微服務”之間進行呼叫的,所以每一個“微服務”都是一個web專案。既然它是一個web專案,它就就有可能會發生錯誤,這個錯誤有可能是伺服器記憶體不足、客戶端傳參錯誤、網路問題等,也有可能是人為的(這個就是**服務熔斷**)。也就是說,會因為一些原因從而不能給呼叫者返回正確的資訊。 ​ 對於我們目前的單個SpringBoot專案來說,我們使用Ajax等一些方式呼叫介面時,如果伺服器發生錯誤,我們在前端就會對這個錯誤進行處理。有可能是重試呼叫介面,或者給使用者一個友好的提示,比如“服務繁忙,稍後再試”啥的。 ​ 但是在分散式系統中,同樣也會發生一些“錯誤”,而且在多個服務之間呼叫時,如果不能對這些“錯誤”進行友好的處理,就會導致我們整個專案癱瘓,這是萬萬不能發生的。所以**Hystrix**利用**服務降級**來很好的解決了這個問題。這個其實就類似於我們的***try-catch***這樣的機制,發生錯誤了,我就執行catch中的程式碼。 ​ 通過服務降級,能保證在某個或某些服務出問題的時間,不會導致整個專案出現問題,避免級聯故障,從而來提高分散式系統的彈性。 *

服務熔斷

​ 建設先看下邊的服務降級程式碼,將整個服務降級的程式碼部分全部看完,再來看下邊這段理論,你一定會茅塞頓開的。 ​ Hystrix意為“斷路器”,就和我們生活中的保險絲,開關一個道理。 ​ 當我們給整個服務配置了服務降級後,如果服務提供者發生了錯誤後,就會呼叫降級後的方法來保證程式的執行。但是呢?有一個問題,呼叫者並不知道它呼叫的這個服務出錯了,就會在業務發生的時候一直呼叫,然後服務會一直報錯,然後去呼叫降級方法。好比下圖中: ![](https://gitee.com/lyn4ever/picgo-img/raw/master/img/20200320003601.png) ​ 它們的對話如下: ​ **Client:**我要呼叫你的方法A ​ **Server:**不行,我報錯了。你呼叫降級方法吧,你的我的都行! ​ **Client**:哎呀,伺服器報錯了,那我就呼叫一下降級方法吧。 ​ 過了一會兒。。。。。。 ​ **Client:**我要呼叫你的方法A ​ **Server:**剛才不是說了嗎?我報錯了。你呼叫降級方法吧,你的我的都行! ​ **Client**:哎呀,伺服器報錯了,那我就呼叫一下降級方法吧。 ​ 又過了一會兒。。。。。。 ​ **Client:**我要呼叫你的方法A ​ **Server:**沒完了是吧?我說過我報錯了,你去呼叫這個降級方法啊。非要讓我的程式碼又執行一次? 以上的對話說明了一個問題,當服務端發生了錯誤後,客戶端會呼叫降級方法。但是,當有一個新的訪問時,客戶端會一直呼叫服務端,讓服務端執行一些明知會報錯的程式碼。這能不能避免啊,我知道我錯了,你訪問我的時候,就直接去訪問降級方法,不要再讓我執行錯的程式碼。 ​ 這就是服務熔斷,就好比我們家中的保險絲。當檢測到家中的用電負荷過大時,就斷開一些用電器,來保證其他的可用。在分散式系統中,就是呼叫一個系統時,在一定時間內,這個服務發生的錯誤次數達到一定的值時, 我們就開啟這個斷路器,不讓呼叫過去,而是讓他直接去呼叫降級方法。再過一段時間後,當一次呼叫時,發現這個服務通了,就將這個斷路器改為“半開”狀態,讓呼叫一個一個的慢慢過去,如果一直沒有發生錯誤,就將這個斷路器關閉,讓所有的服務全部通過。 *

服務限流

​ 服務限流就容易理解多了,顧名思義,這是對訪問的流量進行限制,就比如上邊的場景,我們還可能通過服務限流的方法來解決高併發以及秒殺等問題。主流的限流演算法主要有:**漏桶演算法**和**令牌演算法**

開始碼程式碼吧

> ​ 不貼程式碼,說這麼多有什麼用?這不是耍流氓嗎? * #### 先建立一個我們需要的幾個專案: | 模組名稱 | 程式碼中專案名稱 | 備註 | | :--------------------------: | :-------------------: | :---------------------------------: | | Eureka註冊中心 | eureka-alone-7000 | 測試期間,使用一個註冊中心而不是叢集 | | 客戶端(消費者,服務呼叫者) | hystrix-consumer-80 | 使用Feign或OpenFeign進行服務呼叫 | | 服務端(提供者,服務提供者) | hystrix-provider-8001 | | >
​ 這三個專案的建立程式碼略([專案程式碼地址](https://github.com/Lyn4ever29/spingcloudlearn)) * 在客戶端和服務端都加入Hystrix的依賴(當然是在哪端進行服務降級就在哪端使用) ```xml org.springframework.cloud spring-cloud-starter-hystrix ``` *

服務降級

服務降級有兩種解決思路:可以分別從服務呼叫者和服務提供者進行服務降級,也就是進行錯誤的“兜底” ###### 1. 從服務提供者方進行服務降級 我們先在提供者方的下列方法模擬一個“響應超時錯誤”。 ```java /** * 這個方法會造成服務呼叫超時的錯誤 * 其實本身體不是錯誤,而是服務響應時間超過了我們要求的時間,就認為它錯了 * @param id * @return */ public String timeOutError(Integer id){ return "服務呼叫超時"; } ``` 我們就給它定義一個錯誤回撥方法,加上如下註解: ```java /** * 這個方法會造成服務呼叫超時的錯誤 * 其實本身體不是錯誤,而是服務響應時間超過了我們要求的時間,就認為它錯了 * @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; } ``` 上邊這個註解要注意三點: 1. fallCallbackMethod中的這個引數就是“兜底”方法 2. fallCallbackMethod中的這個方法的宣告要和本方法一致 3. commandProperties屬性中可以寫多個@HystrixProperty註解,其中的name和value就是配置對應的屬性,上例中的這個就是配置響應超時 最後在主啟動類上加上這個註解 ```java @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秒後。 ![](https://gitee.com/lyn4ever/picgo-img/raw/master/img/20200319222751.png) > ​ 如果你覺得上邊這個超時的錯誤演示很麻煩,可以直接在方法中寫一個執行時錯誤,比如:```int i = 10/0;```也會進行fallbackMethod的呼叫。之所以要用這個超時配置,就是為了讓你知道Hystrix可以對什麼樣的錯誤進行fallback,它的更多配置參考[https://github.com/Netflix/Hystrix/wiki/Configuration](https://github.com/Netflix/Hystrix/wiki/Configuration) ###### 2.從服務提供者方進行服務降級 和在服務提供方進行服務降級相比,在服務呼叫方(客戶端、消費者)進行服務降級是更常用的方法。這兩者相比,前者是要讓服務提供者對自己可發生的錯誤進行“預處理”,這樣,一定要保證呼叫者訪問到我才會呼叫這個“兜底”方法。但是,大家想一下,如果我這個服務宕機了呢?客戶端根本就呼叫不到我,它怎麼可能接收到我的“兜底”方法呢?所以,在客戶端進行服務降級是更常用的方法。 > 一個小疑問,如果我在客戶端和服務端都進行了服務降級,是都會呼叫?還是先呼叫哪個?自己想嘍,稍微動動你聰明的小腦袋。 * 為了不和上一個專案的程式碼衝突,我將上邊這個@Service給注掉(也就是讓Spring來管理它),從而用另外一個介面的實現,下邊是我們新的serive類 ```java @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; } } ``` * 在主啟動類上新增如下註解: ```java @EnableHystrix //注意這個和服務端的註解是不一樣的 ``` * 在application.yml中開戶feign對Hystrix的支援 ```yaml feign: hystrix: enabled:true ``` * 將之前在provider專案中的@HystrixCommond放在feign的介面中 ![](https://gitee.com/lyn4ever/picgo-img/raw/master/img/20200319231554.png) ###### 3.改進下解決方案 * 以上的兩種方案看似可行, 但是,實際呢?心想,這是一個合格程式設計師應該做的事嗎?每個介面我們都要寫一個fallback方法?然後和我們的業務程式碼要寫在一起?就好的“低耦合,高內聚”呢? * 第一種解決方案,就是使用@DefaultProperties在整個Controller類上,顧名思義,就是給它一個預設的“兜底”方法,就不用每一個需要降級的方法進行設定fallbackMethod了,我們只需要加上@HystrixCommand好了。這個方法太過簡單,不做程式碼演示,在文末的程式碼中專門寫了註釋 * 第二種解決方法:我們在客戶端不是通過Feign呼叫的嗎?是有一個Feign的本地介面類,我們直接對這個類進行設定就好了。直接上程式碼。 ```java @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); } ``` ```java @Component public class ProviderServiceImpl implements IProviderService { @Override public String hello(Integer id) { return "呼叫遠端服務錯誤了"; } } ``` * 以上兩種方法的對比: * 第一種和我們的業務類進行了耦合,而且如果要對每個方法進行fallback,就要多寫一個方法,程式碼太過臃腫。但是,它提供了一個DefaultProperties註解,可以提供預設的方法,這個後者是沒有的。這種方法適合直接使用Ribbon結合RestTemplate進行呼叫的方法 * 第二種提供了一個Feign介面的實現類來處理服務降級問題,將所有的fallback方法寫到了一起,和我們的業務程式碼完全解耦了。對比第一個,我們可以定義一個統一的方法來實現DefalutPropeties。這種方法適合Feign作為客戶端的呼叫,比較推薦這種。

服務熔斷

​ 請再回去看一下上邊的關於服務熔斷的理論知識,我相信你一定能看懂。當啟用服務降級時,會預設啟用服務熔斷機制,我們只需要對一些引數進行配置就可以了,就是在上邊的@HystrixCommand中的一些屬性,比如: ```java @HystrixCommand(fallbackMethod = "TimeOutErrorHandler",commandProperties = { @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")//請求失敗率達到多少比例後會開啟斷路器 }) ``` > 這些配置可以在[https://github.com/Netflix/Hystrix/wiki/Configuration](https://github.com/Netflix/Hystrix/wiki/Configuration)瞭解,也可以開啟檢視HystrixCommandProperties類中的屬性(使用idea一搜索就有),全部都有預設配置 ### 服務限流 東西太多,關注我期待後續。 ### 專案程式碼和更多的學習地址 關注微信公從號“小魚與Java”,回覆“SpringCloud”獲取 ![](https://lyn4ever.gitee.io/img/wx/g