Spring-Cloud 學習筆記-(5)熔斷器Hystrix


Spring-Cloud 學習筆記-(5)熔斷器Hystrix

1、前言

  • 上個章節我們做了什么?

    上個章節我們使用了Ribbon實現了服務之間調用的負載均衡,具體可以分為三個步驟

    1. 引ribbon依賴
    2. 在啟動類中的RestTemplate 加注解@LoadBalanced
    3. 把serviceId直接寫在RestTemplate 請求的url中調用

    並且我們針對ribbon底層實現原理,走了一遍源碼。

  • 這個章節我們會做什么?

    熔斷器Hystrix

2、Hystrix介紹

2.1、簡介

Hystrix,英文翻譯是豪豬,是一種保護機制,Netflix公司的一款組件。

主頁:https://github.com/Netflix/Hystrix/

Hystix是Netflix開源的一個延遲和容錯庫,用於隔離訪問遠程服務、第三方庫,防止出現級聯失敗。

2.2、雪崩問題

上一章我們服務的調用方(order-service)調用了服務的提供方(user-serivce)查詢用戶的方法,我們可以稱order-service依賴於user-service,一旦我們user-service不可用,也會導致了order-service也不可用,類似這種級聯的失敗,我們可以稱作雪崩。

2.2.1、雪崩效應產生原因

  1. 服務的級聯失敗:就是剛剛說的A服務依賴B服務,B服務失敗了,倒是A服務也掛了,如果還有服務依賴A服務,這樣它也會掛了,就這樣一直延伸下去導致整個項目的不可用。
  2. 服務連接數被耗盡:失敗的服務占用了連接數,倒是正常的服務依舊訪問不了。

描述的詳細一點可以這么理解(圖片來自於:https://github.com/Netflix/Hystrix/wiki)

2.2.2、Hystrix如何解決雪崩問題

  1. 服務的熔斷和降級:

    熔斷:當用戶的請求調用一個服務,這個服務掛了,阻塞了,我們設置一個超時時常,如果超過這個時間,我們會快速的返回一個失敗的友好提示給客戶端。

    降級:以前訪問一個功能,我們可以提供所有的服務,但是現在我們有個地方有點問題,我們只能給你提供核心服務,不重要的暫時就訪問不了了。

  2. 線程的隔離:

    比如我們Tomcat線程有500個,一個用戶的請求來了調用5個服務,我們分一個線程給他,讓這個線程去調用服務,調用成功返回結果,也就是說以前所有的服務都可以用這500個線程,這樣500個線程用完了,這個項目就掛了,現在的做法是什么呢,我們有針對性的給這些服務分配線程,比如一個服務分配100個線程,這樣就算有一個服務掛了, 就算服務I不可用,那只會阻塞這個100個線程,其余的400個線程還是正常,依舊可以調用其他正常的服務,我們把這種把不同的服務請求,用不同的線程池去隔離,就算你資源耗盡,僅僅會消耗當前線程池的連接數叫做線程的隔離。

    官網對線程的隔離圖解

當服務繁忙時,如果服務出現異常,不是粗暴的直接報錯,而是返回一個友好的提示,雖然拒絕了用戶的訪問,但是會返回一個結果。

這就好比去買魚,平常超市買魚會額外贈送殺魚的服務。等到逢年過節,超時繁忙時,可能就不提供殺魚服務了,這就是服務的降級。

系統特別繁忙時,一些次要服務暫時中斷,優先保證主要服務的暢通,一切資源優先讓給主要服務來使用,在雙十一、618時,京東天貓都會采用這樣的策略。

3、服務的降級和線程隔離

3.1、代碼:

在服務的調用方(order-service)

3.1.1、引依賴:

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

3.1.2、加注解

@SpringBootApplication
@EnableCircuitBreaker//開啟服務的熔斷
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class);
    }

    /**
     * 把RestTemplate注入到Spring容器中
     */
    @Bean
    @LoadBalanced //讓RestTemplate內置一個負載均衡器
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

其實我們作為Eureka的服務端需要加注解@EnableEurekaServer,同樣我們作為eureka的客戶端也需要加一個注解

@EnableDiscoveryClient,只是我們Eureka比較智能,如果你有spring-cloud-starter-netflix-eureka-client

這個依賴,eureka就會默認你是一個eureka客戶端,所以@EnableDiscoveryClient可以不用加。所以一個正常的springcloud微服務,基本上都會有三個注解,@SpringBootApplication@EnableDiscoveryClient@EnableCircuitBreaker,所以springcloud很人性化的把這三個注解合成一個注解SpringCloudApplication,所以大家嫌麻煩可以直接加一個SpringCloudApplication注解就可以了。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication//啟動類
@EnableDiscoveryClient//eureka客戶端
@EnableCircuitBreaker//熔斷
public @interface SpringCloudApplication {
}

3.1.2、修改代碼:

上面說過,如果服務失敗,我們快速返回一個失敗信息,所以現在我們要做的是寫一個快速失敗的處理。

在OrderController中

//OrderController類


@RequestMapping("{user_id}")
//開啟服務的線程合理和降級處理,並指定失敗后調用的方法↓
@HystrixCommand(fallbackMethod = "findUserByIdFallbace")
public BaseData findUserById(@PathVariable("user_id")int id){
    Order order = orderService.findById(id);
    return new BaseData(order);
}


/**     findUserById失敗后調用的方法
 *     方法參數和返回值要和上面的完全一樣
 */
public BaseData findUserByIdFallbace(int id){
    return new BaseData("服務器擁擠,請稍后再試!",null);
}

3.1.4、模擬服務調用異常

UserServiceImpl中

//UserServiceImpl類


/**
 * 根據id查詢用戶基本信息
 * @param id  用戶id
 * @return 用戶對象
 */
@Override
public User findById(int id) {
    //模擬服務器延遲
    try {
        //休眠兩秒
        TimeUnit.SECONDS.sleep(2L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    User user = userMap.get(id);
    user.setPort(port);
    return user;
}

3.1.5、測試

啟動服務,訪問 http://localhost:8781/api/v1/order/2

3.2、升級版

剛剛我們是針對某一個類寫了一個降級方法,但是如果Controller中有很多方法我們就要寫很多的降級方法。所以我們可以針對一個類所有方法降級

3.1、代碼

3.1.1、注解修改

@RestController
@RequestMapping("api/v1/order")
@DefaultProperties(defaultFallback = "defaultFallback") //為整個類開啟服務的熔斷
public class OrderController {

//....

    @RequestMapping("{user_id}")
    //開啟服務的線程合理和降級處理
    @HystrixCommand
    public BaseData findUserById(@PathVariable("user_id")int id){
        Order order = orderService.findById(id);
        return new BaseData(order);
    }

3.1.2、降級方法修改:

/**     
 *     方法參數為空
 */
public BaseData defaultFallback(){
    return new BaseData("服務器擁擠,請稍后再試!",null);
}

3.1.3、測試

3.3、配置修改

3.3.1、單一方法Hystrix配置

打開控制台(F12),這里我們看的出來,雖然user-service我們設置了睡眠時間是2秒,但是每次一秒就返回結果了,說明Hytrix默認的超時時長是1秒,但是由於業務邏輯需要,比如發送郵件、銀行轉賬、等我們超時時長都可以稍微設置長一點,一些簡單查詢可以超時時長可以設置稍微短一點。

3.3.1.1、注解修改

//OrderController類


@RequestMapping("{user_id}")
/**
*commandProperties,中我們可以配置一些屬性,可以配置多個。
*但是這些配置的nama 和value 是什么呢...我們看源碼,這些配置都在HystrixCommandProperties類中
*/
@HystrixCommand(commandProperties = {
    @HystrixProperty(name = "",value = "")
})
public BaseData findUserById(@PathVariable("user_id")int id){
    Order order = orderService.findById(id);
    return new BaseData(order);
}
//HystrixCommandProperties類

//執行 超時時長 單位毫秒  我們ctrl+f 搜索一下default_executionTimeoutInMilliseconds,看看這個配置的key是什么...
private static final Integer default_executionTimeoutInMilliseconds = 1000;
//找到配置 key:execution.isolation.thread.timeoutInMilliseconds

this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key, "execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds);

所以我們要修改某一個方法熔斷配置,可以在@HystrixProperty中配置對於的name和value,比如我們配置超時時長為3秒,我們就可以

//OrderController類

@RequestMapping("{user_id}")
//開啟服務的線程合理和降級處理,並指定失敗后調用的方法
@HystrixCommand(commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public BaseData findUserById(@PathVariable("user_id")int id){
    Order order = orderService.findById(id);
    return new BaseData(order);
}

3.3.1.2、測試

返回成功

3.3.2、全局配置

修改配置文件application.yml文件

#hystrix超時時長配置
hystrix:
  command:
    default:
      execution.isolation.thread.timeoutInMilliseconds: 3000

我們刪除之前的方法上的超時配置,重啟一下order-service測試一下

依舊沒有問題。

4、服務的熔斷

4.1、熔斷的原理

  1. 一個請求過來,如果是關閉狀態,那請求繼續。

  2. 如果請求失敗超過一定的閾值(默認最近的20次請求有50%以上的請求失敗)則熔斷器打開。

  3. 后續請求發現熔斷器是開啟狀態,將直接返回錯誤信息,不會等待。

  4. 從熔斷器打開時候開始計時,熔斷器會經過一個休眠時間窗(默認5秒),超過5秒后熔斷器會進入半開狀態。

  5. 半開狀態的熔斷器會放一定量的請求通過進行嘗試,如果依舊超時,熔斷器繼續進入關閉狀態,然后在經歷休眠,如此反復,直到半開狀態的熔斷器放過去的請求成功了,熔斷器會繼續進入關閉狀態。

    這些默認值也是在

    //HystrixCommandProperties類
    
    //打開熔斷器的最小請求次數
    private static final Integer default_circuitBreakerRequestVolumeThreshold = 20;
    //休眠時間窗
    private static final Integer default_circuitBreakerSleepWindowInMilliseconds = 5000;
    //設置打開熔斷並啟動回退邏輯的錯誤比率
    private static final Integer default_circuitBreakerErrorThresholdPercentage = 50;
    

4.2、測試

修改代碼便於測試

  1. 把user-service中的睡眠時間刪除
  2. 修改OrderController代碼
//OrderController類

@RequestMapping("{user_id}")
//開啟服務的線程合理和降級處理
@HystrixCommand(
    commandProperties = {
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "5000"),
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60")
    }
)
public BaseData findUserById(@PathVariable("user_id")int id){
    //手動控制請求成功失敗 技術
    if(id%2==0){
        throw new RuntimeException("");
    }
    Order order = orderService.findById(id);
    return new BaseData(order);
}
  1. 測試

    訪問:http://localhost:8781/api/v1/order/1 成功

    訪問:http://localhost:8781/api/v1/order/2 失敗

    如果我們一直訪問http://localhost:8781/api/v1/order/2 一直失敗

    根據上面我們的配置在最近的10次請求中,如果失敗超過60%,這個時候熔斷器就會開啟,就算我們訪問http://localhost:8781/api/v1/order/1 成功的請求也會立即返回失敗信息,這個時候會經歷休眠時間窗5秒,超過5秒熔斷器進入半開狀態,我們訪問http://localhost:8781/api/v1/order/1,成功。


免責聲明!

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



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