Hystrix降級和熔斷實驗

實驗爲目的

1)Hystrix 線程池打滿再來請求服務降級和熔斷

2)hystrix 某個時間窗口內的buket時間內服務請求書和錯誤率導致降級和熔斷

3)hystrix 滑動窗口配置,影響熔斷以及熔斷狀態切換時間

4)hystrix 熔斷狀態切換(CLOSE,OPNE,HALF_OPEN)

5)hystrix.stream 或者Hystrix Dashboard 使用

PS: HystrixDashboard只是爲了展示和驗證,看代碼時因爲不熟悉RxJava的,滑動窗口統計部分可能看起來比較費力,可以通過這裏儀表盤驗證自己的想法和預測。

feign中使用Hystrix核心鏈路

在使用Feign的場景下,一次遠程調用經歷了入戲圖中幾個組件,每個組件有自己的配置,比如超時時間,線程池數量,方法耗時。
在這裏插入圖片描述

這幾個組件的一些配置可能會使得Hystrix觸發降級和熔斷【比如超時和線程池滿了,以及業務異常】,所以有必要對上面的相關因素做一些瞭解,否則實驗過程中會出現一些出乎意外的結果。

請求流程

不做無準備之仗,再次之前先要對Hystrix的執行流程要有個清晰的認識,前面也看到,有feign,ribbon這兩個組件,去掉之後還有rxjava語法,我在看的時候遇到很多不懂的地方這裏將總結的流程發出來。
在這裏插入圖片描述

圖中大致可以看到幾個核心的邏輯:

  • Hystrix判斷是否熔斷打開
    • 一個bucket時間內請求數達到閾值,並且錯誤率達到閾值----> 熔斷打開
    • 一個bucket時間內請求數未達到閾值 ----> 熔斷不打開 【即便是錯誤率100%】
  • Hystrix 熔斷狀態切換
  • Hystrix 執行結果匯入滑動窗口

還能得出一些邏輯:

  • 熔斷直接進入降級邏輯
  • 降級不一定是熔斷狀態
  • 熔斷是在達到熔斷指標之後下一次請求進入的時候判斷得出的
  • 統計結果跟時間窗口每個bucket存儲的各項數據指標有關係

實戰演示相關準備

提供方代碼

一個很普通的Controller,以服務提供者角色對外提供服務

@Slf4j
@RestController
@RequestMapping("/user")
public class UserResource implements UserServiceFeignApi {

    private Random random = new Random();

    /**
     * @description 模擬服務提供方方法耗時很長,主要是位讓hystrix線程池打滿,看看線程池慢之後降級,熔斷
     * @author yzMa
     * @date 2019/8/6
     * @param  
     * @return  
     */
    @GetMapping("/get/{id}")
    @Override
    public UserModel getById(@PathVariable Long id) throws InterruptedException {
        int nextVal = 600000; //random.nextInt(10000);
        log.info("sleep time ={}",nextVal);
        Thread.sleep(nextVal);

        UserModel userModel = new UserModel();
        userModel.setId(id);
        userModel.setName("myz"+nextVal);
        return userModel;
    }

    /**
     * @description  模擬方法拋出異常,快速返回,以及隨機業務耗時
     * @author yzMa
     * @date 2019/11/1
     * @param
     * @return
     */
    @GetMapping("/hi/{name}")
    @Override
    public String sayHi(@PathVariable(name = "name") String name,
                        @RequestParam(name = "fast",defaultValue = "false") boolean fast,
                        @RequestParam(name = "throwEx",defaultValue = "false") boolean throwEx) throws InterruptedException {

        String val = "hi,"+name;
        if(throwEx){
            throw new RuntimeException("服務端異常");
        }

        if(fast){
            return val;
        }

        int waitTime = random.nextInt(2000);
        log.info("wait time {} ms",waitTime);

        Thread.sleep(waitTime);

        return val;
    }
}

兩個方法在演示中的作用:

UserServiceFeignApi#getById(Long)
  • 模擬服務提供方方法耗時很長,主要是位讓hystrix線程池打滿,看看線程池慢之後降級,熔斷
UserServiceFeignApi#sayHi(String,boolean,boolean);
  • 模擬服務提供方方法拋出異常,快速返回,以及隨機業務耗時

二方包代碼

@FeignClient(name = "sc-user",fallbackFactory = UserServiceFallbackFactory.class)
public interface UserServiceFeignApi {
    String USER_PREFIX = "/user";

    @GetMapping(USER_PREFIX+"/get/{id}")
    UserModel getById(@PathVariable("id") Long id) throws InterruptedException;

    @GetMapping(USER_PREFIX+"/hi/{name}")
    String sayHi(@PathVariable(name = "name") String name,
                 @RequestParam(name = "fast",defaultValue = "false") boolean fast,
                 @RequestParam(name = "throwEx",defaultValue = "false") boolean throwEx) throws InterruptedException;
}

服務方提供的接口,翻遍消費方調用,可以直接被消費方掃描,就像執行本地接口方法

消費方代碼

@Slf4j
@RestController
@RequestMapping("/test/user")
public class TestUserController {

    @Autowired
    private UserServiceFeignApi userServiceFeignApi;

    @GetMapping("/get/{id}")
    public UserModel get(@PathVariable Long id) throws InterruptedException {

        long startTime = System.currentTimeMillis();
        System.out.println("開始執行"+startTime);
        UserModel userModel = userServiceFeignApi.getById(id);
        System.out.println("消耗時間:"+(System.currentTimeMillis()-startTime));
        return userModel;
    }

    @GetMapping("/hello/{name}")
    public String hello(@PathVariable String name,
                        @RequestParam(defaultValue = "false")boolean fast,
                        @RequestParam(defaultValue = "false")boolean throwEx) throws InterruptedException {

        return userServiceFeignApi.sayHi(name,fast,throwEx);
    }
}

代碼依然很簡單,直接調用feign接口。

降級邏輯

@Slf4j
@Component
public class UserServiceFallback implements UserServiceFeignApi {
    @Override
    public UserModel getById(Long id) throws InterruptedException {
        log.info("getById| fallback id={}",id);
        UserModel userModel = new UserModel();
        userModel.setId(0L);
        userModel.setName("fallback");
        return userModel;
    }

    @Override
    public String sayHi(String name,boolean fast,boolean throwEx) throws InterruptedException {
        return fast?("fallback-service-fast "+name) :("fallback-service "+name);
    }
}

降級邏輯也很簡單

Hystrix 隔離配置

hystrix:
  command:
    default: #key 方法即便
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 500000 #【注意URL超時和線程Future超時】 默認500秒超時 主要是爲了訪問某些讓線程hold住  測試下面的熔斷場景
    UserServiceFeignApi#sayHi(String,boolean,boolean): # HystrxiCommand在Feign中默認的commandKey就是類似於方法簽名
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 5000 # 5s 超時
      circuitBreaker:
        requestVolumeThreshold: 6
        sleepWindowInMilliseconds: 60000
  threadpool:
    sc-user: #HystrixCommand在Feign中默認的的groupKey就是serviceId
      allowMaximumSizeToDivergeFromCoreSize: true
      coreSize: 2 #默認時間
      maximumSize: 4
      keepAliveTimeMinutes: 1
      maxQueueSize: -1
      queueSizeRejectionThreshold: 6 # maxQueueSize=-1 時候這個配置時無效的

配置相關含義介紹

  • HystrixCommand的goupKey ,在Feign表現爲serviceId=sc-user ,該serviceId表示的類以及類的所有方法公用一個線程池,來執行遠程調用。hystrix線程池屬於應用級別
  • HystrixCommand的CommandKey,在Feign表現爲Feign接口的方法比如UserServiceFeignApi#sayHi(String,boolean,boolean)有自己獨立的配置
    • 隔離策略
    • 超時時間
    • 熔斷器配置
      • 滑動窗口的時長
      • 滑動窗口的buket的數量,因此可以推導出有一個窗口有多少個buket,再結合定時任務移動一個bucket就能統計出每個bucket的指標信息決定是否要熔斷
        hystrix 隔離策略,熔斷配置,線程池的future超時屬於方法級別
  • Hystrix 默認沒有指定特定的commandKey的時候使用默認commandKey=default hystrix 默認配置

開始試驗

測試hystrix線程池打滿再次請求fallback

我們用jmeter來發送請求,線程數跟Hystrix線程池數量一樣,

Future超時時間
URL超時時間
提供方接口耗時都很長
這樣一來運行Jmeter的時候線程池就全部打滿,並被hold住,再次在瀏覽器發送請求。

測試方法爲UserServiceFeignApi#getById(Long) 如下的特徵:

  • 方法耗時時間可以sleep時間長一些

  • hystrix的線程池Future的超時時間大於Feign(即URL的超時時間)

  • Feign的超時時間大於方法耗時

  • Feign中設置的超時時間最終設置的是上圖的http部分,默認是java.net.URL的超時時間

    • feign:
        hystrix:
          enabled: true # 該版本需要手工啓用
        client:
          config:
            sc-user: # 在客戶端配置的服務提供方 配置信息的key
              readTimeout: 400000 #4000  Url的超時時間
              connectTimeout: 20000
      

測試提供方拋出異常降級和熔斷

  • 見後邊提供方代碼

測試時間窗口內buket的請求數和錯誤率降級和熔斷

  • 見後邊提供方代碼

Hystrix 滑動窗口介紹

受篇幅影響 Rxjava的滑動窗口部分可以到這裏查看,這裏的rxjava部分完全是從Hystrix中抽離出來的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章