使用Hystrix提高系统可用性

标签: 架构与性能 Hystrix 可用性 | 发表时间:2014-03-19 11:28 | 作者:juvenxu
出处:http://www.juvenxu.com

今天稍微复杂点的互联网应用,服务端基本都是分布式的,大量的服务支撑起整个系统,服务之间也难免有大量的依赖关系,依赖都是通过网络连接起来。

hystrix-healthy

(图片来源:https://github.com/Netflix/Hystrix/wiki)

然而任何一个服务的可用性都不是 100% 的,网络亦是脆弱的。当我依赖的某个服务不可用的时候,我自身是否会被拖死?当网络不稳定的时候,我自身是否会被拖死?这些在单机环境下不太需要考虑的问题,在分布式环境下就不得不考虑了。假设我有5个依赖的服务,他们的可用性都是99.95%,即一年不可用时间约为4个多小时,那么是否意味着我的可用性最多就是 99.95% 的5次方,99.75%(近乎一天),再加上网络不稳定因素、依赖服务可能更多,可用性会更低。考虑到所依赖的服务必定会在某些时间不可用,考虑到网络必定会不稳定,我们应该怎么设计自身服务?即,怎么为出错设计?

Michael T. Nygard 在在精彩的 《Release It!》一书中总结了很多提高系统可用性的模式,其中我认为非常重要的两条是:

  1. 使用超时
  2. 使用断路器

第一条,通过网络调用外部依赖服务的时候,都必须应该设置超时。在健康的情况下,一般局域往的一次远程调用在几十毫秒内就返回了,但是当网络拥堵的时候,或者所依赖服务不可用的时候,这个时间可能是好多秒,或者压根就僵死了。通常情况下,一次远程调用对应了一个线程或者进程,如果响应太慢,或者僵死了,那一个进程/线程,就被拖死,短时间内得不到释放,而进程/线程都对应了系统资源,这就等于说我自身服务资源会被耗尽,导致自身服务不可用。假设我的服务依赖于很多服务,其中一个非核心的依赖如果不可用,而且没有超时机制,那么这个非核心依赖就能拖死我的服务,尽管理论上即使没有它我在大部分情况还能健康运转的。

断路器其实我们大家都不陌生(你会换保险丝么?),如果你家没有断路器,当电流过载,或者短路的时候,电路不断开,电线就会升温,造成火灾,烧掉房子。有了断路器之后,电流过载的时候,保险丝就会首先烧掉,断开电路,不至于引起更大的灾难(只不过这个时候你得换保险丝)。

cb

当我们的服务访问某项依赖有大量超时的时候,再让新的请求去访问已经没有太大意义,那只会无谓的消耗现有资源。即使你已经设置超时1秒了,那明知依赖不可用的情况下再让更多的请求,比如100个,去访问这个依赖,也会导致100个线程1秒的资源浪费。这个时候,断路器就能帮助我们避免这种资源浪费,在自身服务和依赖之间放一个断路器,实时统计访问的状态,当访问超时或者失败达到某个阈值的时候(如50%请求超时,或者连续20次请失败),就打开断路器,那么后续的请求就直接返回失败,不至于浪费资源。断路器再根据一个时间间隔(如5分钟)尝试关闭断路器(或者更换保险丝),看依赖是否恢复服务了。

超时机制和断路器能够很好的保护我们的服务,不受依赖服务不可用的影响太大。然而具体实现这两个模式还是有一定的复杂度的,所幸 Netflix 开源的 Hystrix框架 帮我们大大简化了超时机制和断路器的实现。

hystrix-logo

先上POM依赖:

  com.netflix.hystrix
  hystrix-core
  1.3.13

使用Hystrix,需要通过Command封装对远程依赖的调用:

public class CommandHelloWorld extends HystrixCommand {    

    public CommandHelloWorld() {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));        
    }

    @Override
    protected String run() {
        return dependencyService.call(); //this call may timeout or block
    }
}

然后在需要的时候调用这个Command:

String s = new CommandHelloWorld().execute();

上述是同步调用,当然如果业务逻辑允许且更追求性能,或许可以选择异步调用:

Observable observable = new CommandHelloWorld().observe();
observable.subscribe(new Observer() {
    @Override
    public void onCompleted() {
        // do nothing
    }

    @Override
    public void onError(Throwable e) {
         e.printStackTrace();
    }

    @Override
    public void onNext(String v) {
        // do nothing
    }
});

该例中,不论 dependencyService.call() 自身有没有超时机制(可能你会发现很多远程调用接口自身并没有给你提供超时机制),用 HystrixCommand 封装过后,超时是强制的,默认超时时间是1秒,当然你可以根据需要自己在构造函数中调节 Command 的超时时间,例如说2秒:

super(Setter
    .withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
    .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationThreadTimeoutInMilliseconds(2000))
  );

当Hystrix执行命令超时后,它会抛出如下的异常:

com.netflix.hystrix.exception.HystrixRuntimeException: MyCommand timed-out and no fallback available.
	at com.netflix.hystrix.HystrixCommand.getFallbackOrThrowException(HystrixCommand.java:1644)
	at com.netflix.hystrix.HystrixCommand.access$1900(HystrixCommand.java:98)
	at com.netflix.hystrix.HystrixCommand$TimeoutObservable$1$1.run(HystrixCommand.java:1019)
	at com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable$1.call(HystrixContextRunnable.java:41)
	at com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable$1.call(HystrixContextRunnable.java:37)
	at com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable.run(HystrixContextRunnable.java:57)
	at com.netflix.hystrix.HystrixCommand$TimeoutObservable$1$2.tick(HystrixCommand.java:1043)
	at com.netflix.hystrix.util.HystrixTimer$1.run(HystrixTimer.java:101)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:439)
	...
Caused by: java.util.concurrent.TimeoutException
	... 15 more

注意异常信息中包含“MyCommand timed-out and no fallback available.”,也就是说 Hystrix 执行命令超时或者失败之后,是会尝试去调用一个 fallback 的,这个 fallback 即一个备用方案,要为 HystrixCommand 提供 fallback,只要重写 protected String getFallback() 方法即可。

一般情况下,Hystrix 会为 Command 分配专门的线程池,池中的线程数量是固定的,这也是一个保护机制,假设你依赖很多个服务,你不希望对其中一个服务的调用消耗过多的线程以致于其他服务都没线程调用了。默认这个线程池的大小是10,即并发执行的命令最多只能有是个了,超过这个数量的调用就得排队,如果队伍太长了(默认超过5),Hystrix就立刻走 fallback 或者抛异常。

根据你的具体需要,你可能会想要调整某个Command的线程池大小,例如你对某个依赖的调用平均响应时间为200ms,而峰值的QPS是200,那么这个并发至少就是 0.2 x 200 = 40 ( Little’s Law),考虑到一定的宽松度,这个线程池的大小设置为60可能比较合适:

super(Setter
    .withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
    .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(60))
  );

说了这么多,还没提到Hystrix的断路器,其实对于使用者来说,断路器机制默认是启用的,但是编程接口默认几乎不需要关心这个,机制和前面讲的也差不多,Hystrix会统计命令调用,看其中失败的比例,默认当超过50%失败后,开启断路器,那之后一段时间的命令调用直接返回失败(或者走fallback),5秒之后,Hystrix再尝试关闭断路器,看看请求是否能正常响应。下面的几行Hystrix源码展示了它如何统计失败率的:

long totalCount = failure + success + timeout + threadPoolRejected + shortCircuited + semaphoreRejected;
  long errorCount = failure + timeout + threadPoolRejected + shortCircuited + semaphoreRejected;
  int errorPercentage = 0;

  if (totalCount > 0) {
      errorPercentage = (int) ((double) errorCount / totalCount * 100);
  }

其中 failure 表示命令本身发生错误、success 自然不必说,timeout 是超时、threadPoolRejected 表示当线程池满后拒绝的命令调用、shortCircuited 表示断路器打开后拒绝的命令调用,semaphoreRejected 使用信号量机制(而不是线程池)拒绝的命令调用。

本文并不打算完整地介绍 Hystrix,这里只是介绍了为什么要用 Hystrix 以及使用它需要关心的一些基本核心概念,Hystrix 是 Netflix 的核心中间件,在保证他们系统可用性上起到了非常核心的作用,它还有更多的功能都在文档完整地介绍了: https://github.com/Netflix/Hystrix/wiki,其中最重要的而且本文没有介绍的,可能就是监控功能了,当系统足够复杂,相互依赖错综发杂的时候,快速定位到故障点,是运维非常关心的问题。

相关 [hystrix 系统 可用性] 推荐:

使用Hystrix提高系统可用性

- - Juven Xu
今天稍微复杂点的互联网应用,服务端基本都是分布式的,大量的服务支撑起整个系统,服务之间也难免有大量的依赖关系,依赖都是通过网络连接起来. (图片来源:https://github.com/Netflix/Hystrix/wiki). 然而任何一个服务的可用性都不是 100% 的,网络亦是脆弱的. 当我依赖的某个服务不可用的时候,我自身是否会被拖死.

Hystrix 使用与分析

- - ITeye博客
 转载请注明出处哈:http://hot66hot.iteye.com/admin/blogs/2155036. 一:为什么需要Hystrix?. 在大中型分布式系统中,通常系统很多依赖(HTTP,hession,Netty,Dubbo等),如下图:. 在高并发访问下,这些依赖的稳定性与否对系统的影响非常大,但是依赖有很多不可控问题:如网络连接缓慢,资源繁忙,暂时不可用,服务脱机等..

[译]Hystrix的简单介绍

- - 上善若水 厚德载物
最近几天我直在探索Netflix Hystrix library,领会到了这个优秀类库提供的特性. 引用Hystrix网站的原话:.    Hystrix通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力,阻止故障的连锁反应. ,并允许你快速失败并迅速恢复. 这里有很多要分析的关键词,然而体验Hystrix的最佳方式就是亲手做一个例子来试试.

隔离术之使用 Hystrix 实现隔离

- - IT瘾-dev
本篇摘自《亿级流量网站架构核心技术》第三章 隔离术 部分内容,全文. 商品详情页系统的Servlet3异步化实践. Hystrix是Netflix开源的一款针对分布式系统的延迟和容错库,目的是用来隔离分布式服务故障. 它提供线程和信号量隔离,以减少不同服务之间资源竞争带来的相互影响;提供优雅降级机制;提供熔断机制使得服务可以快速失败,而不是一直阻塞等待服务响应,并能从中快速恢复.

使用hystrix保护你的应用 - Kris的博客 | Kris' Blog

- -
凡是可能出错的事必定会出错. hystrix([hɪst'rɪks])是豪猪的意思. 豪猪是一种哺乳动物,全身是刺用以更好的保护自己. netflix使用这畜生来命名这框架实在是非常的贴切,意味着hystrix能够像豪猪的刺一样保护着你的应用. 本文专门探讨netflix的hystrix框架. 首先会说明在一次请求中调用多个远程服务时可能会出现的雪崩问题,然后提出几个解决这些问题的办法,从而引出了hystrix框架;之后我们会给出一个简单的例子试图说明hystrix是如何解决上述问题的;文章主要探讨了线程池隔离技术、信号量隔离技术、优雅降级、熔断器机制.

使用Hystrix完成熔断、限流和降级 - Blog of Kami Wan

- -
2.2 雪崩效应形成的原因. 2.2.1 服务提供者不可用原因. 2.2.3 服务调用者不可用. 3.2 Hystrix在spring boot中的使用. 为了保证系统不被突发流量击垮,进行容量保障是十分有必要的. 从架构的稳定性角度看,在有限资源的情况下,所能提供的单位时间服务能力也是有限的. 假如超过承受能力,可能会带来整个服务的停顿,应用的Crash,进而可能将风险传递给服务调用方造成整个系统的服务能力丧失,进而引发雪崩.

Hystrix熔断器技术解析-HystrixCircuitBreaker - 简书

- -
一、熔断器(Circuit Breaker)介绍. 熔断器,现实生活中有一个很好的类比,就是家庭电路中都会安装一个保险盒,当电流过大的时候保险盒里面的保险丝会自动断掉,来保护家里的各种电器及电路. Hystrix中的熔断器(Circuit Breaker)也是起到这样的作用,Hystrix在运行过程中会向每个commandKey对应的熔断器报告.

Hystrix使用入门手册(中文) - 简书

- -
导语:网上资料(尤其中文文档)对hystrix基础功能的解释比较笼统,看了往往一头雾水. 为此,本文将通过若干demo,加入对. 官网How-it-Works的理解和翻译,力求更清晰解释hystrix的基础功能. 官网How-To-Use进行了二次修改,见. Hystrix是Netflix开源的一款容错系统,能帮助使用者码出具备强大的容错能力和鲁棒性的程序.

战狼项目:美团点评金融核心交易系统可用性7个9是这样炼成的

- - 美团点评技术团队
2017年8月25日,我怀着“再也不要在下班时间收到报警”的美好期待,加入美团金融智能支付负责核心交易,结果入职后收到的报警一天紧似一天. 核心交易是整个智能支付的核心链路,承担着智能支付百分之百的流量,不敢有丝毫的懈怠. 从图中可以看到从17年下半年开始,我们的日单量增长迅速,而且压力和流量在午、晚高峰时段非常集中.

可用性案例分析

- BTK 4eVeR - 时光立方
以下就自己的理解,写写对尼尔森《十大可用性原则》的感想. 本文对各项原则进行了再理解与意译,与常见翻译的版本不同;这也正是我在鼓励和推动互联网中文普及与标准化所做的重要努力之一. 用户在网页上的任何操作,不论是单击、滚动还是按下键盘,页面应即时给出反馈. “即时”是指,页面响应时间小于用户能忍受的等待时间.