降级利器-Hystrix

fisherMartyn bio photo By fisherMartyn

简介

在分布式环境下,服务之间有大量的依赖,单个依赖故障时的容灾是个很重要的话题。

相似的话题包括:SOA柔性架构、分布式系统高可用、高可用系统故障处理。

个人理解主要解决三个方面的问题:

  1. 非核心依赖故障时,系统应该提供有损的服务。
  2. 核心依赖故障时,系统不应该被拖垮。
  3. 核心依赖问题恢复后,系统应该尽快恢复。

这里介绍下Hystrix,以介绍一些主要的思想、核心的设计思路为主,具体的使用请阅读文档5遍,深入的也可以分析代码。

几个公式

99.99^30 = 99.7% uptime

如果一个服务依赖30个子服务,子服务都是4个9的可用性,那么该服务可用性为2个9.

0.3% of 1 billion requests = 3,000,000 failures

从请求数量上讲,一亿次请求中如果有0.3%的失败,失败次数是300万。

2+ hours downtime/month even if all dependencies have excellent uptime.

从时间上讲,上述服务相当于每个月有2个小时的不可用时间。

Hystrix的设计原则

  • 不允许单个依赖占满Web容器线程池。
  • 快速失败(而不是放到队列排队)。
  • 提供fallback钩子机制。
  • 使用隔离技术(例如熔断器模式)来防止对单个依赖的冲击。
  • 实时问题发现,监控、上报、报警。
  • 实时的故障恢复。
  • 策略对整个client生效,而不仅仅针对网络问题。

主要手段

  • 使用HystrixCommand 或者HystrixObservableCommand 封装所有外部系统的调用,并在单独的线程执行(命令模式)。
  • 对于超过自定应响应时间阈值的请求,执行timeout。响应时间阈值一般需要设置为略高于该依赖99.5响应时间。
  • 对于每个依赖维护一个小的线程池,如果线程池满了,响应会被立即拒绝掉。
  • 记录成功、失败(client异常)、超时、线程拒绝等事件。
  • 如果依赖错误率超过一定阈值,触发一个熔断器阻止所有请求一段时间。
  • 请求失败、拒绝、超时、或者短路时,执行fallback钩子逻辑。
  • 近乎实时的指标监控和配置修改。

请求执行流程

  1. 构建HystrixCommand 或者HystrixObservableCommand 对象。前者是用来处理依赖返回单个结果的情况,后者是用来返回Observable
  2. 执行处理动作。主要有四种方式:execute()是阻塞的方式返回单个依赖处理的结果;queue(),返回依赖调用结果的Future对象;observe()observe()返回多个(或者一个)响应结果的Observable。本质上所有请求都是Observable的实现,只不过executequeue().get(),而queue()toObservable().toBlocking().toFuture()
  3. 判断请求是否缓存。如果请求缓存开启,命中请求缓存的会直接返回Observable 中的数据。
  4. 判断是否熔断。每个请求执行时,会判断熔断是否生效,如果熔断失效,则直接走到第8步,返回fallback,否则执行第5步。
  5. 判断是否资源(线程池、队列、信号量)满了。如果资源已经占满,则不会执行请求,直接走到第8步的fallback。
  6. 请求执行。执行真正封装的外部依赖指令,发生超时会执行fallback;如果未发生异常并成功返回结果,则进行相应日志和上报。
  7. 熔断计算。Hystrix维护的熔断计数器进行统计和更新。如果满足熔断条件会进行熔断一段时间,并进行健康检查。
  8. 执行fallback。综上所述,执行短路的条件包括:运行时异常、熔断开启和线程池资源被占满。
  9. 返回成功结果。基于不同的调用方式,返回结果。

如何配置熔断

如何部署Hystrix到生成环境并调优:

  1. 超时时间保留为默认的1s,除非明确知道需要更长时间。
  2. 线程池保留为默认的10个,除非明确知道需要更多线程。
  3. 部署到灰度机器、如果正常,全量运行24小时。
  4. 依赖标准的报警和监控来发现问题。
  5. 24小时后,根据统计的数据计算熔断所需的最低配置。
  6. 根据线上dashboard监控实时调整,直到合适。
  7. 通过监控报警和dashboard监控得知依赖状态修改、继续调整。

Hystrix配置

如上图所示:

假设应用平均响应时间40ms,99%响应时间200ms,99.5%响应时间300ms。峰值QPS 30。

  • 线程池数量 = 最大QPS x 99响应时间 + 一定余量。这里是30*0.2 + 余量4 = 10。
  • 线程池Queue大小:5-10
  • 连接超时100ms,读超时250ms,重试一次。该设置远高于平均响应时间,但仍然丢掉了1%的网络抖动问题。给了一次平均响应时间去另外的机器重试。(前提要求是99.5%以上响应时间的请求没有逻辑上的原因)。
  • 线程超时时间 = 客户端超时时间 + 一次重试的平均响应时间。这里是250 + 40 约等 300。如果网络调用超过了350ms,加上重试的一次共700ms,则会导致线程池占满、开始拒绝请求。

其它

  1. Hystrix使用线程池来处理不同的请求、有利有弊,需要自己权衡。核心优点是隔离、核心缺点是会有性能损耗。
  2. Hystrix支持请求合并(Request Collapsing)和请求缓存(Request Caching)来优化请求。
  3. Hystrix支持同步调用、异步调用和响应式调用。根据不同的需求调用。

参考

  1. https://github.com/Netflix/Hystrix/wiki
  2. http://www.infoq.com/cn/news/2013/01/netflix-hystrix-fault-tolerance