之前有说到过,分布式系统降级的方式可以通过配置中心手动降级。今天介绍一下通过Hystrix实现自动降级。
话不多说,下面先来一个Demo(对于Hystrix的依赖,这里就不再介绍了)。
public class GetStockServiceCommand extends HystrixCommand<String> {
private StockService stockService;
public GetStockServiceCommand (StockService stockService) {
super(setter());
this.stockService = stockService;
}
private static Setter setter() {
// 服务分组
HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("stock");
// 命令配置
HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
.withFallbackEnabled(true)
.withFallbackIsolationSemaphoreMaxConcurrentRequests(100)
.withExecutionIsolationThreadInterruptOnFutureCancel(true)
.withExecutionIsolationThreadInterruptOnTimeout(true)
.withExecutionTimeoutEnabled(true)
.withExecutionTimeoutInMilliseconds(1000);
return HystrixCommand.Setter.withGroupKey(groupKey).andCommandPropertiesDefaults(commandProperties);
}
@Override
protected String run() throws Exception {
// 可以通过异常/Thread.sleep()模拟超时
return stockService.getStock();
}
@Override
protected String getFallback() {
return "有货";
}
}
public class StockService {
public String getStock() {
throw new RuntimeException("出现异常了!");
}
}
public class GetStockTest {
public static void main(String[] args) {
StockService stockService = new StockService();
GetStockServiceCommand stockServiceCommand = new GetStockServiceCommand(stockService);
String result = stockServiceCommand.execute();// 同步执行
System.out.println(result);
// 异步调用, 可自由控制获取结果时机
// Future future = helloworldCommand.queue();
// get操作不能超过command定义的超时时间, 默认1秒
// result = future.get(100, TimeUnit.MILLISECONDS);
}
}
GetStockTest为测试入口,为了对stockService服务做自动降级。对stockService做了一层Command包装,然后调用execute()去执行GetStockServiceCommand里面的run()方法。因为stockService.getStock()方法抛出了异常,所以会执行降级操作,也就是getFallback()方法会被执行并返回。
使用HystrixCommandProperties
配置和getFallback()
方法可以实现降级处理。下面详细介绍一下配置参数:
除了上面的部分参数,对于getFallback()
还需要注意以下的几点:
fallbackIsolationSemaphoreMaxConcurrentRequests
控制,如果失败率非常高,则需要重新配置该参数。如果并发数超过了该配置,则不会再执行getFallback(),而是快速失败。如抛出HystrixRuntimeException
的异常。Command首先调用HystrixCircuitBreaker#allowRequest判断是否熔断了,如果没有则执行Command#run方法;若熔断了则直接调用Command#getFallback方法降级处理。
通过circuitBreakerSleepWindowInMilliseconds可以控制一个时间窗口内,可进行一次请求测试。若测试成功,则闭合熔断开关,否则还是打开状态,从而实现了快速失败和恢复。关于熔断有以下几个概念需要了解一下:
上面所指的失败包含:异常、超时、线程池拒绝、信号量拒绝的总和。
HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()
.withCircuitBreakerEnabled(true)// 默认为true
.withCircuitBreakerForceClosed(false)// 默认为false
.withCircuitBreakerForceOpen(false)// 默认为false
.withCircuitBreakerErrorThresholdPercentage(50)// 默认50%
.withCircuitBreakerRequestVolumeThreshold(20)// 默认为20
.withCircuitBreakerSleepWindowInMilliseconds(5000)// 默认5秒
通过下面的方法可以获取熔断器的状态:
Hystrix在内存中存储采样数据,支持如下3种采样:
HystrixThreadPoolProperties.Setter threadPoolProperties = HystrixThreadPoolProperties.Setter()
.withMetricsRollingStatisticalWindowInMilliseconds(1000)
.withMetricsRollingStatisticalWindowBuckets(10);
HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()
.withMetricsRollingStatisticalWindowInMilliseconds(10000)
.withMetricsRollingStatisticalWindowBuckets(10);
HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()
.withMetricsRollingStatisticalWindowInMilliseconds(10000)
.withMetricsHealthSnapshotIntervalInMilliseconds(500);
该统计在熔断机制中使用时,如果计算熔断的频率非常高,则需要控制好采样的频率。如果太频繁,就有可能造成CPU计算密集。所以选择Hystrix要注意此处的性能消耗和调优,如果此处是瓶颈,则可以费除掉统计。
HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()
.withMetricsRollingPercentileWindowInMilliseconds(60000)
.withMetricsRollingPercentileWindowBuckets(6);
上面默认采样滚转时间窗口为60S,有6个桶,即每10S一个桶统计。
流程说明
1、每次调用创建一个新的HystrixCommand,把依赖调用封装在run()方法中。
2、执行execute/queue做同步或异步调用。
3、判断熔断器(circuit-breaker)是否打开,如果打开跳到步骤8,进行降级策略,如果关闭进入步骤4。
4、判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8,否则继续后续步骤。
5、调用HystrixCommand的run方法,运行依赖逻辑。
5a、依赖逻辑调用超时,进入步骤8。
6、判断逻辑是否调用成功。
6a、返回成功调用结果。
6b、调用出错,进入步骤8。
7、计算熔断器状态,所有的运行状态(成功、失败、拒绝、超时)上报给熔断器,用于统计从而判断熔断器状态。
8、getFallback()降级逻辑。
以下四种情况将触发getFallback调用:
(1) run()方法抛出非HystrixBadRequestException异常
(2) run()方法调用超时
(3) 熔断器开启拦截调用
(4) 线程池/队列/信号量是否跑满
8a、没有实现getFallback的Command将直接抛出异常。
8b、fallback降级逻辑调用成功直接返回。
8c、降级逻辑调用失败抛出异常。
9、返回执行成功结果。
把执行依赖代码的线程与请求线程分离,请求线程可以自由控制离开的时间(异步过程)。通过线程池大小可以控制并发量,当线程池饱和时可以提前拒绝服务,防止依赖问题扩散。线上建议线程池不要设置过大,否则大量堵塞线程有可能会拖慢服务器。
信号隔离也可以用于限制并发访问,防止阻塞扩散,与线程隔离最大不同在于执行依赖代码的线程依然是请求线程(该线程需要通过信号申请),如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离,降低开销。
参考:《亿级流量网站架构核心技术》、https://github.com/Netflix/Hystrix/wiki
链接:http://moguhu.com/article/detail?articleId=81