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


理論知識

  • Hystrix是什么?

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

  • 服務降級

    ​ SpringCloud是通過HTTP Rest的方式在“微服務”之間進行調用的,所以每一個“微服務”都是一個web項目。既然它是一個web項目,它就就有可能會發生錯誤,這個錯誤有可能是服務器內存不足、客戶端傳參錯誤、網絡問題等,也有可能是人為的(這個就是服務熔斷)。也就是說,會因為一些原因從而不能給調用者返回正確的信息。

    ​ 對於我們目前的單個SpringBoot項目來說,我們使用Ajax等一些方式調用接口時,如果服務器發生錯誤,我們在前端就會對這個錯誤進行處理。有可能是重試調用接口,或者給用戶一個友好的提示,比如“服務繁忙,稍后再試”啥的。

    ​ 但是在分布式系統中,同樣也會發生一些“錯誤”,而且在多個服務之間調用時,如果不能對這些“錯誤”進行友好的處理,就會導致我們整個項目癱瘓,這是萬萬不能發生的。所以Hystrix利用服務降級來很好的解決了這個問題。這個其實就類似於我們的try-catch這樣的機制,發生錯誤了,我就執行catch中的代碼。

    ​ 通過服務降級,能保證在某個或某些服務出問題的時間,不會導致整個項目出現問題,避免級聯故障,從而來提高分布式系統的彈性。

  • 服務熔斷

    ​ 建設先看下邊的服務降級代碼,將整個服務降級的代碼部分全部看完,再來看下邊這段理論,你一定會茅塞頓開的。

    ​ Hystrix意為“斷路器”,就和我們生活中的保險絲,開關一個道理。

    ​ 當我們給整個服務配置了服務降級后,如果服務提供者發生了錯誤后,就會調用降級后的方法來保證程序的運行。但是呢?有一個問題,調用者並不知道它調用的這個服務出錯了,就會在業務發生的時候一直調用,然后服務會一直報錯,然后去調用降級方法。好比下圖中:

    ​ 它們的對話如下:

    Client:我要調用你的方法A

    Server:不行,我報錯了。你調用降級方法吧,你的我的都行!

    Client:哎呀,服務器報錯了,那我就調用一下降級方法吧。

    ​ 過了一會兒。。。。。。

    Client:我要調用你的方法A

    Server:剛才不是說了嗎?我報錯了。你調用降級方法吧,你的我的都行!

    Client:哎呀,服務器報錯了,那我就調用一下降級方法吧。

    ​ 又過了一會兒。。。。。。

    Client:我要調用你的方法A

    Server:沒完了是吧?我說過我報錯了,你去調用這個降級方法啊。非要讓我的代碼又運行一次?

以上的對話說明了一個問題,當服務端發生了錯誤后,客戶端會調用降級方法。但是,當有一個新的訪問時,客戶端會一直調用服務端,讓服務端運行一些明知會報錯的代碼。這能不能避免啊,我知道我錯了,你訪問我的時候,就直接去訪問降級方法,不要再讓我執行錯的代碼。

​ 這就是服務熔斷,就好比我們家中的保險絲。當檢測到家中的用電負荷過大時,就斷開一些用電器,來保證其他的可用。在分布式系統中,就是調用一個系統時,在一定時間內,這個服務發生的錯誤次數達到一定的值時, 我們就打開這個斷路器,不讓調用過去,而是讓他直接去調用降級方法。再過一段時間后,當一次調用時,發現這個服務通了,就將這個斷路器改為“半開”狀態,讓調用一個一個的慢慢過去,如果一直沒有發生錯誤,就將這個斷路器關閉,讓所有的服務全部通過。

  • 服務限流

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

開始碼代碼吧

​ 不貼代碼,說這么多有什么用?這不是耍流氓嗎?

  • 先創建一個我們需要的幾個項目:

    模塊名稱 代碼中項目名稱 備注
    Eureka注冊中心 eureka-alone-7000 測試期間,使用一個注冊中心而不是集群
    客戶端(消費者,服務調用者) hystrix-consumer-80 使用Feign或OpenFeign進行服務調用
    服務端(提供者,服務提供者) hystrix-provider-8001

    ​ 這三個項目的創建代碼略(項目代碼地址

  • 在客戶端和服務端都加入Hystrix的依賴(當然是在哪端進行服務降級就在哪端使用)

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
  • 服務降級

    服務降級有兩種解決思路:可以分別從服務調用者和服務提供者進行服務降級,也就是進行錯誤的“兜底”

1. 從服務提供者方進行服務降級

我們先在提供者方的下列方法模擬一個“響應超時錯誤”。

  /**
     * 這個方法會造成服務調用超時的錯誤
     * 其實本身體不是錯誤,而是服務響應時間超過了我們要求的時間,就認為它錯了
     * @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;
}

上邊這個注解要注意三點:

  1. fallCallbackMethod中的這個參數就是“兜底”方法
  2. fallCallbackMethod中的這個方法的聲明要和本方法一致
  3. 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/Configuration

2.從服務提供者方進行服務降級

和在服務提供方進行服務降級相比,在服務調用方(客戶端、消費者)進行服務降級是更常用的方法。這兩者相比,前者是要讓服務提供者對自己可發生的錯誤進行“預處理”,這樣,一定要保證調用者訪問到我才會調用這個“兜底”方法。但是,大家想一下,如果我這個服務宕機了呢?客戶端根本就調用不到我,它怎么可能接收到我的“兜底”方法呢?所以,在客戶端進行服務降級是更常用的方法。

一個小疑問,如果我在客戶端和服務端都進行了服務降級,是都會調用?還是先調用哪個?自己想嘍,稍微動動你聰明的小腦袋。

  • 為了不和上一個項目的代碼沖突,我將上邊這個@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);
}
@Component
public class ProviderServiceImpl implements IProviderService {
    @Override
    public String hello(Integer id) {
        return "調用遠程服務錯誤了";
    }
}
  • 以上兩種方法的對比:
    • 第一種和我們的業務類進行了耦合,而且如果要對每個方法進行fallback,就要多寫一個方法,代碼太過臃腫。但是,它提供了一個DefaultProperties注解,可以提供默認的方法,這個后者是沒有的。這種方法適合直接使用Ribbon結合RestTemplate進行調用的方法
    • 第二種提供了一個Feign接口的實現類來處理服務降級問題,將所有的fallback方法寫到了一起,和我們的業務代碼完全解耦了。對比第一個,我們可以定義一個統一的方法來實現DefalutPropeties。這種方法適合Feign作為客戶端的調用,比較推薦這種。

服務熔斷

​ 請再回去看一下上邊的關於服務熔斷的理論知識,我相信你一定能看懂。當啟用服務降級時,會默認啟用服務熔斷機制,我們只需要對一些參數進行配置就可以了,就是在上邊的@HystrixCommand中的一些屬性,比如:

@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了解,也可以打開查看HystrixCommandProperties類中的屬性(使用idea一搜索就有),全部都有默認配置

服務限流

東西太多,關注我期待后續。

項目代碼和更多的學習地址

關注微信公從號“小魚與Java”,回復“SpringCloud”獲取


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM