Hystrix

认识Hystrix

Hystrix是Netflix开源的一款容错框架。在高并发访问下,系统所依赖的服务的稳定性对系统掉影响非常大。依赖有很多的不可控因素,比如网络连接慢,资源繁忙,暂时不可用等。只要有其中一个依赖服务不稳定,就会拖跨整个服务。我们要构建稳定、可靠的分布式系统,就必须要有这样一套容错方法。
Hystrix提供了线程隔离、信号量隔离、熔断、降级回退等容错方法

Hystrix Wiki

容错流程

隔离

线程隔离

场景:
比如我们现在有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,那么会自动关闭降级,这种自反馈的降级,降级恢复策略在整个过程中都自动完成的,不需要人工干涉。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章