文章目錄
實驗爲目的
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中抽離出來的