文章目录
实验为目的
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:
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中抽离出来的