上一篇我们已经介绍了hystrix及其用法,现在我们就继续分析Hystrix,和分析Hystrix的一些高级特性,请求缓存,降级。
1.构建一个HystrixCommand或者HystrixObservableCommand HystrixCommand:返回一个数据 HystrixObservableCommand:返回多条数据 编写一个class继承HystrixCommand或HystrixObservableCommand,实现方法run/construct方法, 在构造函数中传入参数,并设置线程池
2.执行Command 要执行Command,必须new一个Command并调用 execute(),queue(),observe(),toObservable() 4个方法
execute:同步,直接阻塞直到依赖服务返回 queue:异步,返回一个Future,属于异步调用,后面可以通过Future获取单条结果 observe:调用的时候会立即执行 run/construct toObservable:调用的时候不会立即执行 run/construct,,要等订阅的时候才会执行。有延迟执行的特性 execute queue 只适用于 HystrixCommand
3.检查是否开启缓存 检查请求缓存request cache 如果存在数据直接返回
4.检查短路器是否开启 如果开启直接去调用降级方法
5.检查线程池/队列/semaphore是否已经满了 semaphore没有队列 线程池有等待队列 如果满了直接调用 降级方法
6.具体执行command 执行失败异常,调用降级方法 执行超时,调用降级方法(信号量没有超时) 超时了不会kill执行run/construct的线程(所以后面可能执行成功)
7.短路健康检查 对于一个依赖服务的调用不管是成功还是失败,拒绝,超时,都会通知断路器,断路器进行统计成功/失败的比例,从而判断是否打开短路器
8.调用fallback 在以下几种情况中,hystrix会调用fallback降级机制:run()或construct()抛出一个异常,短路器打开,线程池/队列/semaphore满了,command执行超时了
一般在降级机制中,都建议给出一些默认的返回值,比如静态的一些代码逻辑,或者从内存中的缓存中提取一些数据,尽量在这里不要再进行网络请求了

不同的command执行方式,其fallback为空或者异常时的返回结果不同
对于execute(),直接抛出异常 对于queue(),返回一个Future,调用get()时抛出异常 对于observe(),返回一个Observable对象,但是调用subscribe()方法订阅它时,理解抛出调用者的onError方法 对于toObservable(),返回一个Observable对象,但是调用subscribe()方法订阅它时,理解抛出调用者的onError方法
请求缓存 查看是否有请求缓存,如果有,直接拿缓存返回 首先,我们需要一个概念,request context 请求上下文,在tomcat中,每一个请求都是一个请求上下文。所以我们通常在filter中,先初始化一个request context
在一个请求上下文中,我们可能会需要调用不同的依赖服务,有的依赖服务还会重复调用多次。在一次请求上下文中了,如果我们多次调用同一个command,调用同一个接口,参数都一样,那么我们可以认为,返回的结果也一样,这样就可以取第一次调用的结果返回,用在一次请求上下文中反复多次的执行一样的command,提升整个请求的性能
HystrixCommand和HystrixObservableCommand都可以指定一个缓存key,然后hystrix会自动进行缓存,接着在同一个request context内,再次访问的时候,就会直接取用缓存
用请求缓存,可以避免重复执行网络请求
多次调用一个command,那么只会执行一次,后面都是直接取缓存
对于请求缓存(request caching),请求合并(request collapsing),请求日志(request log),等等技术,都必须自己管理HystrixReuqestContext的声明周期
在一个请求执行之前,都必须先初始化一个request context
HystrixRequestContext context = HystrixRequestContext.initializeContext();
然后在请求结束之后,需要关闭request context
context.shutdown();
一般来说,在java web来的应用中,都是通过filter过滤器来实现的
public class HystrixRequestContextServletFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
chain.doFilter(request, response);
} finally {
context.shutdown();
}
}
}
@Bean
public FilterRegistrationBean indexFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean(new IndexFilter());
registration.addUrlPatterns("/");
return registration;
} fallback降级
当我们调用依赖服务,当调用依赖服务报错,超时,线程池/信号量,如果资源池已满,或者断路器打开了,这个时候需要去调用fallback方法进行降级
两种最经典的降级机制:纯内存数据,默认值 纯内存数据 : fallback降级就从本地内存中获取一份过期的数据,先凑合着用着 默认值: 设定一个默认值
设置超时 在分布式系统中,我们需要调用各种各样的依赖服务,然而,依赖服务的开发者是谁我们不知道,并且水平如何我们也不知道,所以可能会导致依赖的接口性能不是很稳定,可能出现各种超时,导致线程资源hang住,吞吐量大幅度下降,甚至服务崩溃。
大量接口调用很慢,大量线程就会卡死,资源隔离,线程池的线程就卡死,所以需要超时控制。 (1)控制是否大开timeout机制,默认true
//execution.timeout.enabled
HystrixCommandProperties.Setter().withExecutionTimeoutEnabled(boolean value) (2)手动设置超时时间默认1000毫秒
//execution.isolation.thread.timeoutInMilliseconds
HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(int value) 1、短路器打开的前提是经过短路器的流量超过了一定的阈值。比如说在10s内,经过短路器的流量必须达到20个才会判断要不要短路。
2、到达短路器的流量异常占比必须超过一定的阈值。比如我们说在10s,经过短路器的流量达到了30个,同事其中异常的访问数量,占到了一定的比例,比如说50%的请求都是异常的,那就开启短路。
3、短路器达到了一定的阈值,且异常占比也达到了阈值,那么此时短路器就会从close状态转换到open状态。
4、短路器打开的时候,所有经过该短路器的请求全部被短路,不调用后端服务,直接fallback降级。
5、经过一段时间之后,短路器会处于半开状态,让一条请求经过短路器,看能否正常调用,如果调用成功,那么短路器就会自动关闭。
1、circuitBreaker.enabled:控制短路器是否允许工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发短路,默认是true。HystrixCommandProperties.Setter().withCircuitBreakerEnabled(boolean value)
2、circuitBreaker.requestVolumeThreshold:设置一个rolling window,滑动窗口中,最少要有多少个请求时才触发开启短路。(如果设置为20(默认值),那么在一个10秒的滑动窗口内,如果只有19个请求,即使这19个请求都是异常的,也是不会触发开启短路器的。)HystrixCommandProperties.Setter().withCircuitBreakerRequestVolumeThreshold(int value)
3、circuitBreaker.sleepWindowInMilliseconds:设置在短路之后,需要在多长时间内直接reject请求,然后在这段时间之后,再重新到holf-open状态,尝试允许请求通过以及自动恢复,默认值是5000毫秒。HystrixCommandProperties.Setter().withCircuitBreakerSleepWindowInMilliseconds(int value)
4、circuitBreaker.errorThresholdPercentage:设置异常请求量的百分比,当异常请求达到这个百分比时,就触发打开短路器,默认是50,也就是50%。HystrixCommandProperties.Setter().withCircuitBreakerErrorThresholdPercentage(int value)
5、circuitBreaker.forceOpen:如果设置为true的话,直接强迫打开短路器,相当于是手动短路了,手动降级,默认false。HystrixCommandProperties.Setter().withCircuitBreakerForceOpen(boolean value)
6、circuitBreaker.forceClosed:如果设置为ture的话,直接强迫关闭短路器,相当于是手动停止短路了,手动升级,默认false。HystrixCommandProperties.Setter().withCircuitBreakerForceClosed(boolean value)
/**
* 获取商品信息
*/
public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {
private Long productId;
public GetProductInfoCommand(Long productId) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService"))
.andCommandKey(HystrixCommandKey.Factory.asKey("GetProductInfoCommand"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("GetProductInfoPool"))
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
.withCoreSize(15)
.withQueueSizeRejectionThreshold(10))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withCircuitBreakerRequestVolumeThreshold(30)//10秒内有30个请求时,触发断路器
.withCircuitBreakerErrorThresholdPercentage(40)//当异常达到百分之40时,触发断路器
.withCircuitBreakerSleepWindowInMilliseconds(3000))//在3秒内直接refect请求走降级,3秒过后进入半开状态
);
this.productId = productId;
}
@Override
protected ProductInfo run() throws Exception {
System.out.println("调用接口,查询商品数据,productId=" + productId);
if(productId.equals(-1L)) {
throw new Exception();
}
return JSONObject.parseObject("数据", ProductInfo.class);
}
@Override
protected ProductInfo getFallback() {
ProductInfo productInfo = new ProductInfo();
productInfo.setName("降级商品");
return productInfo;
}
}