白話SpringCloud | 第五章:服務容錯保護(Hystrix)
前言
前一章節,我們知道了如何利用 RestTemplate
+ Ribbon
和 Feign
的方式進行服務的呼叫。在微服務架構中,一個服務可能會呼叫很多的其他微服務應用,雖然做了多叢集部署,但可能還會存在諸如網路原因或者服務提供者自身處理的原因,或多或少都會出現請求失敗或者請求延遲問題,若服務提供者長期未對請求做出迴應,服務消費者又不斷的請求下,可能就會造成服務提供者服務崩潰,進而服務消費者也一起跟著不可用,嚴重的時候就發生了 系統雪崩
了。鑑於此,產生了斷路器等一系列的服務保護機制。本章節,就來說下如何利用 Hystrix
進行容錯處理。
一點知識
按照此係列的慣例,我們先來了解下一些相關的知識。
注:以下部分內容轉至大佬純潔的微笑: ofollow,noindex" target="_blank">熔斷器Hystrix 。
容錯處理手段
容錯處理是指軟體執行時,能對由非正常因素引起的執行錯誤給出適當的處理或資訊提示,使軟體執行正常結束——百度百科
從百度百科的解釋中可以看出,簡單理解,所謂的 容錯處理
其實就是捕獲異常了,不讓異常影響系統的正常執行,正如 java
中的 try catch
一樣。
而在微服務呼叫中,自身異常可自行處理外,對於依賴的服務若發生錯誤,或者呼叫異常,或者呼叫時間過長等原因時,避免長時間等待,造成系統資源耗盡。
一般上都會通過 設定請求的超時時間
,如 http
請求中的 ConnectTimeout
和 ReadTimeout
;再或者就是使用 熔斷器
模式,隔離問題服務,防止級聯錯誤等。
雪崩效應
在微服務架構中,存在很多的微服務單元,各個微服務之間通過網路進行通訊,難免出現依賴關係,若某一個單元 出現故障
,就很容易因依賴關係而引發故障的蔓延,產生 “雪崩效應”
,最終導致整個系統的癱瘓。
下面這張圖,相比大家都有看過了。
如圖所示:A作為服務提供者,B為A的服務消費者,C和D是B的服務消費者。A不可用引起了B的不可用,並將不可用像滾雪球一樣放大到C和D時,雪崩效應就形成了。也就應了那句話:星星之火,可以燎原!
熔斷器
熔斷器,和現實生活中的 空氣開關
作用很像。它可以實現 快速失敗 ,如果它在一段時間內偵測到許多類似的錯誤, 會強迫其以後的多個呼叫快速失敗,不再訪問遠端伺服器,從而防止應用程式不斷地嘗試執行可能會失敗的操作 ,使得應用程式繼續執行而不用等待修正錯誤,或者浪費CPU時間去等到長時間的超時產生。 熔斷器也可以使應用程式能夠診斷錯誤是否已經修正,如果已經修正,應用程式會再次嘗試呼叫操作。
熔斷器模式就像是那些容易導致錯誤的操作的一種代理。 這種代理能夠記錄最近呼叫發生錯誤的次數,然後決定使用允許操作繼續,或者立即返回錯誤 。
可以看出,熔斷器一共有三種狀態,之間轉換關係如下:
- 關閉狀態
當熔斷器處於關閉狀態時,請求是可以被放行的;
當熔斷器統計的失敗次數觸發開關時,轉為開啟狀態。 - 開啟狀態
當熔斷器處於開啟狀態時,所有請求都是不被放行的,直接返回失敗;
只有在經過一個設定的時間視窗週期後,熔斷器才會轉換到半開狀態 - 半開狀態
當熔斷器處於半開狀態時,當前只能有一個請求被放行;
這個被放行的請求獲得遠端服務的響應後,假如是成功的,熔斷器轉換為關閉狀態,否則轉換到開啟狀態。
個人覺得,主要還是快速失敗,避免請求堆積,壓垮伺服器。進而起到保護服務高可用的目的。
Hystrix實踐
何為Hystrix
Hystrix是一個實現了超時機制和斷路器模式的工具類庫。
Hystrix是有Netflix開源的一個延遲和容錯庫,用於隔離訪問遠端系統、服務或第三方庫,防止級聯失敗,從而提升系統的可用性和容錯性。
Hystrix容錯機制:
- 包裹請求 :使用HystrixCommand包裹對依賴的呼叫邏輯,每個命令在獨立執行緒中執行,這是用到了設計模式“命令模式”。
- 跳閘機制 :當某服務的錯誤率超過一定閾值時,Hystrix可以自動或手動跳閘,停止請求該服務一段時間。
- 資源隔離 :Hystrix為每個依賴都維護了一個小型的執行緒池,如果該執行緒池已滿,發往該依賴的請求就被立即拒絕,而不是排隊等候,從而加速判定失敗。
- 監控 :Hystrix可以近乎實時的監控執行指標和配置的變化。如成功、失敗、超時、被拒絕的請求等。
- 回退機制 :當請求失敗、超時、被拒絕,或當斷路器開啟時,執行回退邏輯。回退邏輯可自定義。
- 自我修復 :斷路器開啟一段時間後,會自動進入半開狀態,斷路器開啟、關閉、半開的邏輯轉換。
下圖就是 Hystrix
的回退策略,防止級聯故障。
常規方式整合Hystrix
建立個工程 spring-cloud-hystrix
工程。
0.引入POM依賴。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
1.啟動類,加入註解 @EnableHystrix
,同時申明一個實現負載均衡的 RestTemplate
。(關於消費者服務可檢視: SpringCloud/chapter-four/" rel="nofollow,noindex" target="_blank">第四章:服務消費者(RestTemple+Ribbon+Feign) ,這裡不再闡述了。)
/** * 熔斷器示例 * @author oKong * */ @SpringBootApplication @EnableHystrix @EnableDiscoveryClient @Slf4j public class HystrixApplication { public static void main(String[] args) throws Exception { SpringApplication.run(HystrixApplication.class, args); log.info("sprign-cloud-hystrix啟動!"); } @Bean @LoadBalanced public RestTemplate restTemplat() { return new RestTemplate(); } }
2.編寫一個測試類,加入 @HystrixCommand
,指定 fallbackMethod
方法。
RibbonController.java
/** * ribbon 常規方式-示例 * @author oKong * */ @RestController @Slf4j public class RibbonController { @Autowired RestTemplate restTemplate; @GetMapping("/ribbon") @HystrixCommand(fallbackMethod="fallback") public String hello(String name) { log.info("使用restTemplate呼叫服務,引數name:{}", name); return restTemplate.getForObject("http://eureka-client/hello?name=" + name, String.class); } /** * 發生熔斷時呼叫的方法 * @param name * @param throwable 發生異常時的異常資訊 * @return */ public String fallback(String name,Throwable throwable) { log.error("熔斷髮生了:{}", throwable); log.warn("restTemplate呼叫服務發生熔斷,引數name:{}", name); return "restTemplate呼叫服務發生熔斷,引數name:" + name; } }
注意:這裡 fallback
方法加入了一個引數 throwable
,當發生熔斷時,可以獲悉發生熔斷的異常資訊,便於定位問題和原因。
3.啟動應用,訪問:http://127.0.0.1:8038/ribbon?name=oKong 。正常情況下, spring-cloud-eureka-client
應用正常執行時,返回正常結果:
現在我們停止提供者服務,再次訪問,可以看見已經進入熔斷方法了:
控制檯可以看見異常輸出:
由於例項尚未被剔除註冊中心的服務列表,所以提示是連線超時,等待一段時間後,再次訪問服務,可以看見是提示例項不存在了:
注意:對於 @HystrixCommand
註解,我們可以放在任何一個呼叫函式裡面,以此實現呼叫方法發生異常或者錯誤時,可以快速返回,避免持續請求,造成資源的耗盡。
Feign整合Hystrix
如上小節說示例的,當我們方法很多時,要是分別編寫一個 fallback
估計也是崩潰的,雖然可以使用一個通用的 fallback
,但未進行特殊設定下,也是無法知道具體是哪個方法發生熔斷的。
而對於 Feign
,我們可以使用一種更加優雅的形式進行。我們可以指定 @FeignClient
註解的 fallback
屬性,或者是 fallbackFactory
屬性,後者可以獲取異常資訊的。
修改 spring-cloud-hystrix
工程。
0.引入 Feigin
的POM依賴。
<!-- feign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
1.啟動類,加入 @EnableFeignClients
啟用 Feign
.
** * 熔斷器示例 * @author oKong * */ @SpringBootApplication @EnableHystrix @EnableDiscoveryClient @EnableFeignClients @Slf4j public class HystrixApplication { public static void main(String[] args) throws Exception { SpringApplication.run(HystrixApplication.class, args); log.info("sprign-cloud-hystrix啟動!"); } @Bean @LoadBalanced public RestTemplate restTemplat() { return new RestTemplate(); } }
- 建立一個服務介面類
IHelloClient.java
,同時定義fallback
或者fallbackFactory
屬性值。 注意:兩者 同時設定時,優先呼叫fallback
,fallbackFactory
不進行呼叫了。
@FeignClient(name="eureka-client",/*fallback=HelloClientFailImpl.class,*/ fallbackFactory = HelloClientFallbackFactory.class) public interface IHelloClient { /** * 定義介面 * @param name * @return */ @RequestMapping(value="/hello", method=RequestMethod.GET) public String hello(@RequestParam("name") String name); }
- 建立
fallback
和fallbackFactory
屬性對應類。
HelloClientFailImpl.java
@Component("fallback") @Slf4j public class HelloClientFailImpl implements IHelloClient{ @Override public String hello(String name) { log.error("restTemplate呼叫[hello]服務發生熔斷,引數name:{}", name); return "restTemplate呼叫[hello]服務發生熔斷,引數name:" + name; } }
HelloClientFallbackFactory/java
@Component @Slf4j public class HelloClientFallbackFactory implements FallbackFactory<IHelloClient>{ @Autowired @Qualifier("fallback") IHelloClient helloClient; @Override public IHelloClient create(Throwable cause) { log.error("feign呼叫發生異常,觸發熔斷", cause); return helloClient; } }
可以知道,正常 fallback
就是一個介面的實現類,當傳送異常時,會呼叫此介面實現類進行服務呼叫。而 FallbackFactory
是也是一個介面實現類,需要實現 feign.hystrix.FallbackFactory<T>
介面,在發生熔斷時,呼叫 create
方法,同時返回被呼叫介面的實現類,以便進行fallback處理。
3.配置檔案開啟feign的熔斷器功能。
feign.hystrix.enabled=true
或者,申明一個 Feign.Builder
類也是可以的,我們從 org.springframework.cloud.openfeign.FeignClientsConfiguration
可以看出,啟用 feign
的條件:
所以正常,我們只需要在配置檔案中加入 feign.hystrix.enabled
為 true
即可, 注意:此屬性在IDE下未進行提示的。
或者就如此類一樣,申明一個bean:
@Bean public Feign.Builder feignHystrixBuilder() { return HystrixFeign.builder(); }
也是可以的。
4.編寫一個測試類 FeignController
:
/** * feign 熔斷器示例 * @author oKong * */ @RestController @Slf4j public class FeignController { @Autowired IHelloClient helloClient; @GetMapping("/feign") public String hello(String name) { log.info("使用feign呼叫服務,引數name:{}", name); return helloClient.hello(name); } }
5.再次啟動應用,訪問:http://127.0.0.1:8038/feign?name=oKong ,正常呼叫如下:
關閉服務提供者,再次訪問,瀏覽器返回了錯誤提示:
同時,我們使用了 FallbackFactory
,控制檯打印出了具體異常:
針對熔斷超時時間等相關設定,可以通過 @HystrixCommand
註解的各屬性進行配置,主要還是 commandProperties
屬性值,具體的引數可檢視 com.netflix.hystrix.HystrixCommandProperties
類,也可以針對某個呼叫方法進行特殊設定。具體的可以看看這篇文章: hystrix的基本介紹和配置屬性說明 ,或者可以去大佬 程式設計師DD部落格 查閱下關於 Hystrix
相關知識點: 服務容錯保護(Hystrix斷路器)【Dalston版】 、 服務容錯保護(Hystrix依賴隔離)【Dalston版】 ,版本雖然是D版的,但原理是差不多的~
參考資料
總結
本章節主要講解了如何整合 Hystrix
。本身 Hystrix
已經包含了 服務降級
、 依賴隔離
、 熔斷器
等功能了,我們使用時並沒進行特殊設定,預設都是生效的。對於一些關於 Hystrix
的高階用法,比如 訊號量
隔離、執行緒池的設定等等,還有一些像 超時時間
等,由於此方面瞭解的不多,這裡就不班門弄斧了。大家可去官方網站或者谷歌搜尋下相關材料下,對於一些業務場景,可進行一些自定義設定。主要還是針對 @HystrixCommand
註解的相關配置。 關於呼叫統一異常的處理相關實踐,比如當提供方異常時,呼叫方如何進行統一異常處理,或者服務不可用時,進行統一的異常捕獲,告知外圍呼叫者服務不可用等資訊。這些相關實踐部分會在之後的實踐篇系列文章中進行闡述的,也許不是最佳的實踐,僅希望提供一個參考方案吧。這裡就不表了,敬請期待~
最後
目前網際網路上大佬都有分享 SpringCloud
系列教程,內容可能會類似,望多多包涵了。 原創不易,碼字不易 ,還希望大家多多支援。若文中有錯誤之處,還望提出,謝謝。
老生常談
499452441 lqdevOps
個人部落格: http://blog.lqdev.cn
原始碼示例: https://github.com/xie19900123/spring-cloud-learning
原文地址: http://blog.lqdev.cn/2018/09/23/SpringCloud/chapter-five/