1. 程式人生 > >白話SpringCloud | 第五章:服務容錯保護(Hystrix)

白話SpringCloud | 第五章:服務容錯保護(Hystrix)

前言

前一章節,我們知道了如何利用RestTemplate+RibbonFeign的方式進行服務的呼叫。在微服務架構中,一個服務可能會呼叫很多的其他微服務應用,雖然做了多叢集部署,但可能還會存在諸如網路原因或者服務提供者自身處理的原因,或多或少都會出現請求失敗或者請求延遲問題,若服務提供者長期未對請求做出迴應,服務消費者又不斷的請求下,可能就會造成服務提供者服務崩潰,進而服務消費者也一起跟著不可用,嚴重的時候就發生了系統雪崩了。鑑於此,產生了斷路器等一系列的服務保護機制。本章節,就來說下如何利用Hystrix進行容錯處理。

一點知識

按照此係列的慣例,我們先來了解下一些相關的知識。

注:以下部分內容轉至大佬純潔的微笑:

熔斷器Hystrix

容錯處理手段

容錯處理是指軟體執行時,能對由非正常因素引起的執行錯誤給出適當的處理或資訊提示,使軟體執行正常結束——百度百科

從百度百科的解釋中可以看出,簡單理解,所謂的容錯處理其實就是捕獲異常了,不讓異常影響系統的正常執行,正如java中的try catch一樣。

而在微服務呼叫中,自身異常可自行處理外,對於依賴的服務若發生錯誤,或者呼叫異常,或者呼叫時間過長等原因時,避免長時間等待,造成系統資源耗盡。 一般上都會通過設定請求的超時時間,如http請求中的ConnectTimeoutReadTimeout;再或者就是使用熔斷器模式,隔離問題服務,防止級聯錯誤等。

雪崩效應

在微服務架構中,存在很多的微服務單元,各個微服務之間通過網路進行通訊,難免出現依賴關係,若某一個單元出現故障,就很容易因依賴關係而引發故障的蔓延,產生“雪崩效應”,最終導致整個系統的癱瘓。

下面這張圖,相比大家都有看過了。

雪崩效應

如圖所示:A作為服務提供者,B為A的服務消費者,C和D是B的服務消費者。A不可用引起了B的不可用,並將不可用像滾雪球一樣放大到C和D時,雪崩效應就形成了。也就應了那句話:星星之火,可以燎原!

熔斷器

熔斷器,和現實生活中的空氣開關作用很像。它可以實現快速失敗,如果它在一段時間內偵測到許多類似的錯誤,會強迫其以後的多個呼叫快速失敗,不再訪問遠端伺服器,從而防止應用程式不斷地嘗試執行可能會失敗的操作

,使得應用程式繼續執行而不用等待修正錯誤,或者浪費CPU時間去等到長時間的超時產生。熔斷器也可以使應用程式能夠診斷錯誤是否已經修正,如果已經修正,應用程式會再次嘗試呼叫操作。

熔斷器模式就像是那些容易導致錯誤的操作的一種代理。這種代理能夠記錄最近呼叫發生錯誤的次數,然後決定使用允許操作繼續,或者立即返回錯誤

熔斷器狀態轉換圖

可以看出,熔斷器一共有三種狀態,之間轉換關係如下:

  • 關閉狀態 當熔斷器處於關閉狀態時,請求是可以被放行的; 當熔斷器統計的失敗次數觸發開關時,轉為開啟狀態。
  • 開啟狀態 當熔斷器處於開啟狀態時,所有請求都是不被放行的,直接返回失敗; 只有在經過一個設定的時間視窗週期後,熔斷器才會轉換到半開狀態
  • 半開狀態 當熔斷器處於半開狀態時,當前只能有一個請求被放行; 這個被放行的請求獲得遠端服務的響應後,假如是成功的,熔斷器轉換為關閉狀態,否則轉換到開啟狀態。

個人覺得,主要還是快速失敗,避免請求堆積,壓垮伺服器。進而起到保護服務高可用的目的。

Hystrix實踐

何為Hystrix

Hystrix是一個實現了超時機制和斷路器模式的工具類庫。

Hystrix是有Netflix開源的一個延遲和容錯庫,用於隔離訪問遠端系統、服務或第三方庫,防止級聯失敗,從而提升系統的可用性和容錯性。

Hystrix容錯機制:

  • 包裹請求:使用HystrixCommand包裹對依賴的呼叫邏輯,每個命令在獨立執行緒中執行,這是用到了設計模式“命令模式”。
  • 跳閘機制:當某服務的錯誤率超過一定閾值時,Hystrix可以自動或手動跳閘,停止請求該服務一段時間。
  • 資源隔離:Hystrix為每個依賴都維護了一個小型的執行緒池,如果該執行緒池已滿,發往該依賴的請求就被立即拒絕,而不是排隊等候,從而加速判定失敗。
  • 監控:Hystrix可以近乎實時的監控執行指標和配置的變化。如成功、失敗、超時、被拒絕的請求等。
  • 回退機制:當請求失敗、超時、被拒絕,或當斷路器開啟時,執行回退邏輯。回退邏輯可自定義。
  • 自我修復:斷路器開啟一段時間後,會自動進入半開狀態,斷路器開啟、關閉、半開的邏輯轉換。

下圖就是Hystrix的回退策略,防止級聯故障。

Hystrix fallback prevents cascading failures

常規方式整合Hystrix

建立個工程spring-cloud-hystrix工程。 0.引入POM依賴。

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>

1.啟動類,加入註解@EnableHystrix,同時申明一個實現負載均衡的RestTemplate。(關於消費者服務可檢視:第四章:服務消費者(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應用正常執行時,返回正常結果:

正常情況

現在我們停止提供者服務,再次訪問,可以看見已經進入熔斷方法了:

異常情況

控制檯可以看見異常輸出:

異常資訊

由於例項尚未被剔除註冊中心的服務列表,所以提示是連線超時,等待一段時間後,再次訪問服務,可以看見是提示例項不存在了:

No instances available for 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();
    }
    
}
  1. 建立一個服務介面類IHelloClient.java,同時定義fallback或者fallbackFactory屬性值。注意:兩者 同時設定時,優先呼叫fallbackfallbackFactory不進行呼叫了。
@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);
}
  1. 建立fallbackfallbackFactory屬性對應類。

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的條件:

HystrixFeignConfiguration

所以正常,我們只需要在配置檔案中加入feign.hystrix.enabledtrue即可,注意:此屬性在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 ,正常呼叫如下:

正常呼叫

關閉服務提供者,再次訪問,瀏覽器返回了錯誤提示:

fallback

同時,我們使用了FallbackFactory,控制檯打印出了具體異常:

異常資訊

針對熔斷超時時間等相關設定,可以通過@HystrixCommand註解的各屬性進行配置,主要還是commandProperties屬性值,具體的引數可檢視com.netflix.hystrix.HystrixCommandProperties類,也可以針對某個呼叫方法進行特殊設定。具體的可以看看這篇文章:hystrix的基本介紹和配置屬性說明,或者可以去大佬程式設計師DD部落格查閱下關於Hystrix相關知識點:服務容錯保護(Hystrix斷路器)【Dalston版】服務容錯保護(Hystrix依賴隔離)【Dalston版】,版本雖然是D版的,但原理是差不多的~

參考資料

總結

本章節主要講解了如何整合Hystrix。本身Hystrix已經包含了服務降級依賴隔離熔斷器等功能了,我們使用時並沒進行特殊設定,預設都是生效的。對於一些關於Hystrix的高階用法,比如訊號量隔離、執行緒池的設定等等,還有一些像超時時間等,由於此方面瞭解的不多,這裡就不班門弄斧了。大家可去官方網站或者谷歌搜尋下相關材料下,對於一些業務場景,可進行一些自定義設定。主要還是針對@HystrixCommand註解的相關配置。關於呼叫統一異常的處理相關實踐,比如當提供方異常時,呼叫方如何進行統一異常處理,或者服務不可用時,進行統一的異常捕獲,告知外圍呼叫者服務不可用等資訊。這些相關實踐部分會在之後的實踐篇系列文章中進行闡述的,也許不是最佳的實踐,僅希望提供一個參考方案吧。這裡就不表了,敬請期待~

最後

目前網際網路上大佬都有分享SpringCloud系列教程,內容可能會類似,望多多包涵了。原創不易,碼字不易,還希望大家多多支援。若文中有錯誤之處,還望提出,謝謝。

老生常談

  • 個人QQ:499452441
  • 微信公眾號:lqdevOps

公眾號