Hystrix 那些事(上)

3,122 阅读10分钟
原文链接: mp.weixin.qq.com

    前些日子我写了一篇关于搭建Hystrix监控的文章,但是有部分同学还不清楚Hystrix是什么,今天我们就来聊一聊Hystrix的使用。

    Hystrix是什么?

    Netflix公司开源的Hystrix框架,对延迟和故障可以提供强大的容错能力,在分布式系统中对请求远程系统、服务或者第三方库产生的错误,通过熔断、线程池隔离等手段,可以及时停止系统中的级联错误从而起到自适应调节的作用。

    Hystrix为了解决什么问题?

    1.对第三方接口潜在的依赖调用的失败提供保护和控制机制。

    2.在分布式系统中隔离资源,降低耦合,防止导致级连失败,相互影响。

    3.快速失败以及迅速恢复。

    4.在合适的时机进行优雅的降级。

    5.提供近实时的监控,报警。

    在分布式系统中,外部用户发起一次请求访问服务器,在这次请求中或多或少会依赖其他一些第三方资源,如果第三方依赖都在正常运作,那么整个系统将良好地运行。

    每一个第三方依赖的资源对调用方来说都是黑盒的,由于依赖众多、网络原因、代码逻辑问题以及其它各种多元化原因,导致第三方依赖中有一个或多个不能提供服务了,就会导致调用方发起的这次请求一直阻塞,进而导致调用方发起的其它请求阻塞,不能够被快速消费。

    第三方依赖出问题是在所难免的,我们并不能避免某些因素导致的第三方依赖调用失败,但是我们可以尽可能的避免调用失败对调用方带来的影响,提前做好应急措施,遇到问题,可以及时启动应急预案,让系统进行自调节。如果不能及时有效隔离有问题的第三方依赖,所有的请求都会因为这个单点故障而阻塞,产生“雪崩效应”,整个应用服务器将不能正常对外提供服务。

    因此Hystrix设计的原则:

  1. 阻止任何单一的第三方依赖使用掉整个容器的全部用户线程

  2. 快速失败而不是在队列中积压请求

  3. 提供fallback错误回调机制

  4. 任何第三方依赖之间相互隔离

  5. 通过近实时的监控和报警及时发现系统中的问题

  6. 对第三方依赖进行故障保护

    Hystrix最终想达到的目的是:

    通过Hystrix的隔离保护和自调节机制,提供强大的容错能力,避免任何一个第三方依赖的单点故障带来的级联影响,避免阻塞整个应用服务器对外提供服务。

Hystrix核心处理逻辑是这样的:


    Hystrix支持两种方法构建Command对象,分别通过继承HystrixCommand和HystrixObservableCommand来进行构建,这两种Command对象共支持4种执行Command的方式,分别是:

  • R value = command.execute()

  • Future<R> value = command.queue()

  • Observable<R> value = command.observe()

  • Observable<R> value = command.toObservable()

前两种调用方式仅仅支持在通过继承HytrixCommand方式构造的Command对象中调用。第一种是同步阻塞性调用,第二种是异步非阻塞性调用,第三、四种是基于发布-订阅响应式的调用,本质上是观察者模式的一种具体实现。很有意思的一件事情是execute方法通过queue().get()来实现的,而queue方法是通过toObservable().toBlocking().toFuture()来实现的,observe方法是通过toObservable().subscribe(subject)来实现的,没错,每一种调用方式最后都是基于toObservable方法来实现的。Hystrix执行Command后,它本身会进行一系列的逻辑处理,首先判断当前请求的响应结果是否已经被缓存,如果命中缓存,直接从缓存中返回响应结果,如果没有命中缓存,接下来Hytrix会判断断路器的状态,如果断路器打开,那么Hytrix会直接调用HystrixCommand中的getFallback方法或者HystrixObservableCommand中的resumeWithFallback方法,采用一种fail-silently的方式调用回调方法返回响应(建议调用方重写这两个回调方法,否则Hystrix会抛出异常,产生错误),如果断路器关闭,Hytrix接下来会判断Semaphore或者Thread pool是否已经是拒绝状态,拒绝状态通常是因为消费速度落后于生产速度,如果是拒绝状态,那么会调用已实现的回调方法,如果不是拒绝状态,说明当前请求已经通过了Hystrix的保护逻辑的验证,Hystrix会在已实现的run或者construct核心方法中调用第三方依赖,如果对第三方依赖调用失败或者调用超时,默认同样会调用fallback回调方法,同时Hystrix会把当前这次请求的metrics数据收集,实时地对断路器的健康状态进行计算(默认失败率达到50%断路器自动打开),最后返回成功的响应结果。

    从整体上对Hystrix进行了把握后,我们不妨通过具体代码来看下Hystrix提供的几个具体的功能。

    引入依赖:

<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-core</artifactId>
    <version>1.5.9</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

    一:“Hello World” 镇一楼:

    1.继承HystrixCommand方式:

public class HelloHystrixCommand extends HystrixCommand<String> {
    private String somebody;
    public HelloHystrixCommand(String somebody) {

      super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("hello")));
        this.somebody = somebody;
    }
    protected String run() throws Exception {

        //生产环境中此处为第三方依赖的具体调用逻辑
        return new StringBuilder().append("Hello ").append(somebody).toString();
    }
}

    单元测试:

@Test
public void testHelloHystrix() {
    HelloHystrixCommand helloHystrix = new HelloHystrixCommand("World");
    String result = helloHystrix.execute();

    assertEquals("Hello World, Failure", result);

}

    2:继承HystrixObservableCommand方式:

public class HelloHystrixObservableCommand extends HystrixObservableCommand<String> {
    private String somebody;
    public HelloHystrixObservableCommand(String somebody) {
        super(HystrixCommandGroupKey.Factory.asKey("hello"));
        this.somebody = somebody;
    }
    protected Observable<String> construct() {

        //生产环境中此处为第三方依赖的具体调用逻辑

        return Observable.create(

                new Observable.OnSubscribe<String>() {
                    public void call(Subscriber<? super String> subscriber) {
                        if (!subscriber.isUnsubscribed()) {
                            subscriber.onNext("Hello " + somebody);
                            subscriber.onCompleted();
                        }
                    }
                }).subscribeOn(Schedulers.io());
    }
}

    单元测试:

@Test
public void testHelloHystrixObservable() {
    HelloHystrixObservableCommand helloHystrix = new HelloHystrixObservableCommand("World");
    Observable<String> observable = helloHystrix.observe();
    observable.subscribe(new Observer<String>() {
        public void onCompleted() {
            System.out.println("completed");
        }
        public void onError(Throwable e) {
            e.printStackTrace();
        }
        public void onNext(String s) {

            assertEquals("Hello World, Failure", s);

        }
    });
}

    二:Command的四种执行方式:

    1.同步阻塞调用:

@Test
public void testHystrixExecute () {
    String result = new HelloHystrixCommand("World").execute();

    assertEquals("Hello World, Failure", result);

}

    2.异步非阻塞调用:

@Test
public void testHystrixQueue () throws ExecutionException, InterruptedException {
    Future<String> future = new HelloHystrixCommand("World").queue();
    String result = future.get();

    assertEquals("Hello World, Failure", result);

}

    3.热响应式调用,不管你是否已经订阅Hystrix会立即执行Command,Hystrix会提供一种“ReplaySubject”方式进行过滤,所以不必担心已经执行但是还没被订阅的Command的结果会丢失。

@Test
public void testHystrixObserve() {
    Observable<String> observable = new HelloHystrixCommand("World").observe();
    observable.subscribe(new Observer<String>() {
        public void onCompleted() {
            System.out.println("completed");
        }
        public void onError(Throwable e) {
            e.printStackTrace();
        }
        public void onNext(String s) {
              assertEquals("Hello World, Failure", s);

        }
    });
}

    4.冷响应式调用,直到你订阅这个Command,Hystrix才会执行Command

@Test
public void testHystrixToObservable() {
    Observable<String> observable = new HelloHystrixCommand("World").toObservable();
    observable.subscribe(new Action1<String>() {
        public void call(String s) {

            assertEquals("Hello World, Failure", s);

        }
    });
}

    三:fallback回调机制:

    上文中的流程图我们看到各种错误比如run方法调用错误、调用超时、线程池或信号量拒绝、断路器处于短路状态等最终都会落脚到fallback方法,可见fallback方法的重要性。Hystrix主要提供两种fallback方法:

1.重写HystrixCommand中的getFallback方法:

public class HelloHystrixFailureCommand  extends HystrixCommand<String> {
    private String somebody;
    public HelloHystrixFailureCommand(String somebody) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("hello_failure")));
        this.somebody = somebody;
    }
    protected String run() throws Exception {
        throw new RuntimeException();
    }
    @Override
    protected String getFallback() {
        return "Hello World, Failure";
    }
}

单元测试:

@Test
public void testHelloHystrixFailure() {
    HelloHystrixFailureCommand failureCommand = new HelloHystrixFailureCommand("World");
    String result = failureCommand.execute();

    assertEquals("Hello World, Failure", result);

}

2.重写HystrixObservableCommand中的resumeWithFallback方法:

public class HelloHystrixFailureObservableCommand extends HystrixObservableCommand<String> {
    private String somebody;
    public HelloHystrixFailureObservableCommand(String somebody) {
        super(HystrixCommandGroupKey.Factory.asKey("hello"));
        this.somebody = somebody;
    }
    protected Observable<String> construct() {
        throw new RuntimeException();
    }
    @Override
    protected Observable<String> resumeWithFallback() {
        return Observable.create(
                new Observable.OnSubscribe<String>(){
                    public void call(Subscriber<? super String> subscriber) {
                        if (!subscriber.isUnsubscribed()) {
                            subscriber.onNext("Hello World, Failure");
                            subscriber.onCompleted();
                        }
                    }
                }
        ).subscribeOn(Schedulers.io());
    }
}

单元测试:

@Test
public void testHelloHystrixObservableFailure() {
    Observable<String> observable = new HelloHystrixFailureObservableCommand("World").observe();
    observable.subscribe(new Observer<String>() {
        public void onCompleted() {
            System.out.println("completed");
        }
        public void onError(Throwable e) {
            e.printStackTrace();
        }
        public void onNext(String s) {

            assertEquals("Hello World, Failure", s);

        }
    });
}

四:Command Group、Command Name和Command Thread-Pool:

我们可以通过实现父类构造函数来指定Command分组、命名、线程池,当然通过Setter对象设置是一种最通用的方式。

    Setter setter = Setter

        .withGroupKey(HystrixCommandGroupKey.Factory.asKey("hello"))
        .andCommandKey(HystrixCommandKey.Factory.asKey("hello command"))
        .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("hello thread pool"))
        .andCommandPropertiesDefaults(
                HystrixCommandProperties.Setter()
                        .withExecutionTimeoutInMilliseconds(1000)
        )
        .andThreadPoolPropertiesDefaults(
                HystrixThreadPoolProperties.Setter()
                        .withMaxQueueSize(10)
        );

    关于Command Group、Name和Thread-Pool三者的关系,在这里做一下说明,Group从业务逻辑上划分某些Command可以归为一组,每个独立的外部依赖放置于一个独立的Command中,拥有唯一的Command Name,每个独立的Command和Thread-Pool应该是一对一的关系,从而达到资源隔离的目的。

   关于Hystrix其他高级的特性,比如Request Cache以及Request Collapser等,由于篇幅原因(公众号文章不能超过20K字),本篇暂时不做介绍,后续也会介绍Hystrix结合注解的一些使用方法,最大程度上降低和业务代码的耦合性。

    总结,本篇文章从整体上介绍了Hystrix,结合流程图可以清晰地把握Hystrix的核心逻辑过程,同时结合具体的简单示例,介绍了Command的创建方式、Command的执行方式、Fallback的回调机制以及Command Group等属性的设置问题。