文章目錄
- 實驗為目的
- 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觸發降級和熔斷【比如逾時和線程池滿了,以及業務異常】,是以有必要對上面的相關因素做一些了解,否則實驗過程中會出現一些出乎意外的結果。
請求流程
不做無準備之仗,再次之前先要對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;
}
}
兩個方法在示範中的作用:
- 模拟服務提供方方法耗時很長,主要是位讓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中抽離出來的