天天看點

Hystrix降級和熔斷實驗實驗為目的feign中使用Hystrix核心鍊路實戰示範相關準備開始試驗

文章目錄

  • 實驗為目的
  • feign中使用Hystrix核心鍊路
    • 請求流程
  • 實戰示範相關準備
    • 提供方代碼
    • 二方包代碼
    • 消費方代碼
      • 降級邏輯
    • Hystrix 隔離配置
  • 開始試驗
    • 測試hystrix線程池打滿再次請求fallback
    • 測試提供方抛出異常降級和熔斷
    • 測試時間視窗内buket的請求數和錯誤率降級和熔斷
    • 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降級和熔斷實驗實驗為目的feign中使用Hystrix核心鍊路實戰示範相關準備開始試驗

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

請求流程

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

Hystrix降級和熔斷實驗實驗為目的feign中使用Hystrix核心鍊路實戰示範相關準備開始試驗
圖中大緻可以看到幾個核心的邏輯:
  • 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;
    }
}
           

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

  • 模拟服務提供方方法耗時很長,主要是位讓hystrix線程池打滿,看看線程池慢之後降級,熔斷
  • 模拟服務提供方方法抛出異常,快速傳回,以及随機業務耗時

二方包代碼

@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:
      circuitBreaker:
        sleepWindowInMilliseconds: 5000
        requestVolumeThreshold: 20
        errorThresholdPercentage: 50
      metrics:
        rollingStats:
          timeInMilliseconds: 10000
        healthSnapshot:
          intervalInMilliseconds:500
      execution:
        isolation:
          semaphore:
            maxConcurrentRequests: 2
          strategy: SEMAPHOR
          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中抽離出來的