认识Hystrix
Hystrix是Netflix开源的一款容错框架。在高并发访问下,系统所依赖的服务的稳定性对系统掉影响非常大。依赖有很多的不可控因素,比如网络连接慢,资源繁忙,暂时不可用等。只要有其中一个依赖服务不稳定,就会拖跨整个服务。我们要构建稳定、可靠的分布式系统,就必须要有这样一套容错方法。
Hystrix提供了线程隔离、信号量隔离、熔断、降级回退等容错方法
容错流程
隔离
线程隔离
场景:
比如我们现在有3个业务调用分别是查询订单、查询商品、查询用户,且这三个业务请求都是依赖第三方服务-订单服务、商品服务、用户服务。三个服务均是通过RPC调用。当查询订单服务,假如线程阻塞了,这个时候后续有大量的查询订单请求过来,那么容器中的线程数量则会持续增加直致CPU资源耗尽到100%,整个服务对外不可用
此时,每来一个请求(创建一个线程),阻塞在等待查询上。并发量大,创建了很多线程阻塞在查询订单上,耗尽了资源。
线程隔离就是给查询订单服务固定大线程数量,与其他服务的线程资源区分开来。这样就起到了隔离的作用
缺点
线程池的主要缺点就是它增加了计算的开销,每个业务请求(被包装成命令)在执行的时候,会涉及到请求排队,调度和上下文切换
信号量隔离
信号量隔离的方式是限制了总的并发数
,每一次请求过来,请求线程和调用依赖服务的线程是同一个线程.
总结
[图片上传失败...(image-b29ee4-1548676005721)]
线程隔离用在有网络开销
信号量隔离用在没有网络开销的地方
使用解析
public class GetOrderCircuitBreakerCommand extends HystrixCommand<String> {
public GetOrderCircuitBreakerCommand(String name){
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("testCommandKey"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(name))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withCircuitBreakerEnabled(true)//默认是true,本例中为了展现该参数
.withCircuitBreakerForceOpen(false)//默认是false,本例中为了展现该参数
.withCircuitBreakerForceClosed(false)//默认是false,本例中为了展现该参数
.withCircuitBreakerErrorThresholdPercentage(5)//(1)错误百分比超过5%
.withCircuitBreakerRequestVolumeThreshold(10)//(2)10s以内调用次数10次,同时满足(1)(2)熔断器打开
.withCircuitBreakerSleepWindowInMilliseconds(5000)//隔5s之后,熔断器会尝试半开(关闭),重新放进来请求
.withExecutionTimeoutInMilliseconds(1000)
)
.andThreadPoolPropertiesDefaults(
HystrixThreadPoolProperties.Setter()
.withMaxQueueSize(10) //配置队列大小
.withCoreSize(2) // 配置线程池里的线程数
)
);
}
@Override
protected String run() throws Exception {
Random rand = new Random();
//模拟错误百分比(方式比较粗鲁但可以证明问题)
if(1==rand.nextInt(2)){
// System.out.println("make exception");
throw new Exception("make exception");
}
return "running: ";
}
@Override
protected String getFallback() {
// System.out.println("FAILBACK");
return "fallback: ";
}
public static class UnitTest{
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<35;i++){
Thread.sleep(500);
HystrixCommand<String> command = new GetOrderCircuitBreakerCommand("testCircuitBreaker");
String result = command.execute();
//本例子中从第11次,熔断器开始打开
System.out.println("call times:"+(i+1)+" result:"+result +" isCircuitBreakerOpen: "+command.isCircuitBreakerOpen());
//本例子中5s以后,熔断器尝试关闭,放开新的请求进来
}
}
}
}
初始化
HystrixCommandKey
Hystrix使用单例模式存储HystrixCommand,熔断机制就是根据单实例上的调用情况统计实现的
,所以每个HystrixCommand要有自己的名字,用于区分,同时用于依赖调用的隔离。HystrixCommandKey就是用于定义这个名字,如果没有定义这个名字,Hystrix会使用其类名作为其名字,可以使用HystrixCommandKey.Factory.asKey(String name)方法定义一个名称。
HystrixThreadPoolKey
HystrixThreadPoolKey是HystrixCommand所在的线程池,如果该参数不设置则使用HystrixCommandGroupKey作为HystrixThreadPoolKey,这种情况下同一个HystrixCommandGroupKey下的依赖调用共用同一个线程池内
,如果不想共用同一个线程池,则需要设置该参数。可以使用HystrixThreadPoolKey.Factory.asKey(String name)方法设置。
HystrixCommandGroupKey
Hystrix需要对HystrixCommand进行分组,便于统计、管理,所以需要一个分组名称,HystrixCommandGroupKey就是用于定义分组名称
,可以使用HystrixCommandGroupKey.Factory.asKey(String name)方法定义一个分组名。每个HystrixCommand必须要配置一个分组名,一个是用于分组,还有如果没有配置HystrixThreadPoolKey,这个分组名将会用于线程池名。
HystrixCommandProperties
这个就是HystrixCommand的属性配置,它可以设置熔断器是否可用、熔断器熔断的错误百分比、依赖调用超时时间等
,它有一些默认的配置参数,如熔断器熔断的错误百分比默认值是50%、依赖调用超时时间默认值是1000毫秒。
HystrixThreadPoolProperties
从名称上可以看出这是线程池的属性配置
,可以通过它设置核心线程数大小、最大线程数、任务队列大小等,当然它也又一些默认的配置参数。
命令执行
execute():以同步堵塞方式执行run()。调用execute()后,hystrix先创建一个新线程运行run(),接着调用程序要在execute()调用处一直堵塞着,直到run()运行完成。
queue():以异步非堵塞方式执行run()。调用queue()就直接返回一个Future对象,同时hystrix创建一个新线程运行run(),调用程序通过Future.get()拿到run()的返回结果,而Future.get()是堵塞执行的。
observe():不研究
toObservable():不研究
降级
使用fallback机制很简单,继承HystrixCommand只需重写getFallback()
Dubbo 的服务降级策略与 Hystrix 的熔断机制的简单对比
一开始我有个疑问:Dubbo提供了服务降级策略,为什么说Hystrix对RPC(对网络开消)的服务容错呢?
Dubbo 提供的降级策略比较简单,Dubbo 的降级可以通过代码设置或者通过管理控制台进行设置,并且一旦设置为了降级,那么所有消费者调用降级接口时候都是用本地 mock 值,并没有自动恢复策略,并且必须让管理员手动进行降级或者取消降级
Hystrix 除了提供 Dubbo 类似的降级策略外,还提供了自适应的降级恢复策略,当调用服务异常次数超过指定阈值后会自动进行降级,并且降级后不是一直都是用降级方案,它可以在降级后时间窗口过后再次进行尝试远程调用,如果目前服务已经 OK,那么会自动关闭降级,这种自反馈的降级,降级恢复策略在整个过程中都自动完成的,不需要人工干涉。