Skip to content

Hystrix

Calvin Xiao edited this page Nov 1, 2013 · 43 revisions

System Protection OverView

系统保护有几种目标:

  • 防止某一个系统内部或外部的节点出错导致雪崩效应,使整个系统挂掉,而不只是某些依赖了出错节点的功能不能使用。
  • 保护远程或本地的公共资源不会因为某个功能有海量请求或发生了异常行为而被占用,导致其他功能不能使用。
  • 保证系统在正常或非正常的海量请求发生时,能正常完成(如保持原有的响应时间)设计范围内的请求,而拒绝过量的请求。

在实现上可以有很多方式与层次,比如:

1. 在系统入口进行限流。
限流有助于目标2和3,可以控制并发如Http线程数,也可以控制TPS比如Google Guava中的RateLimiter。也应该有开关量,可以快速的关闭某个功能入口。

更高级是根据系统运行情况的反馈动态改变阀值,但如何获取与度量反馈源,阀值更改的算法,公共资源的互相影响,项目的具体需求等都制约着设计。

  • 反馈源可以是所依赖的关键资源的状态如CPU/IO/网络/内存/线程数,或远程服务/数据库的状态,也可以是请求的平均响应时间。
  • 获取方式可以是第三方监控关键资源,或入口自行计算平均响应时间/失败率(异步流程需要更特殊处理),或由后面的资源访问控制模块传递节点失效/繁忙信息。

另外,有时候服务并不知道自己所依赖的全部资源列表,或者入口处无法得知某个请求的执行路径,有的会访问服务B,有的访问服务C,服务C又访问服务D,有的命中缓存,有的要访问数据库,算出来的TPS/平均响应时间可能是很粗旷的,也可能错杀了一些其实不进入出错/繁忙节点的请求。
但入口限流的优势是在入口就拒绝服务,不会先访问了服务B,再访问服务D才发现服务不可用,一是浪费了资源,二是可能要回退操作。

2. 在有Queue的地方都要对Queue的长度进行限制保护。
Queue本身也是一个削峰填谷的对系统保护有益的做法。

3.对服务/资源访问的保护。
数据库连接池及类似的池策略是对目标2中的远程资源保护的一个常用手段。但连接池的共享与独立限制也是个两难的选择,如果功能间的访问高峰不同步,那用一个共享的大连接池显然更高效,但就没有了保护。

NetFlix的Hystrix项目则提供更多的功能,也更为通用,为目标1和目标2中的远程资源保护提供了良好支持,使得这一层保护的实现方式是最具有通用性的。

  • 提供Fail Fast的短路保护机制(时间段内频繁出错则进入保护期,不再访问资源而直接拒绝请求)
  • 提供并发控制
  • 在独立线程模式下提供超时控制
  • 记录访问延时等Metrics数据,为入口限流或自身并发控制的动态调整提供支持
  • 为包括短路在内的各种出错情况,提供抛异常以外的备选方案,如总是返回true或empty list

Hystrix运行原理

How it works的Flow Chart一节,很好的讲述了整个保护的流程。

  1. 如果在窗口时间(默认20秒)内,出错比例达到多少(默认50%)就进入短路状态(当然访问次数要达到某个最小值,默认20,不能说窗口内第1次,如果失败了就是100%失败)
  2. 短路状态下,所有访问都会立即返回,不会真正访问远程资源。保护期(默认5秒)过了之后,再尝试访问一次,如果依然错误,则继续保护状态。
  3. 如果是独立线程池模式,超时默认为1秒,超时立刻返回异常并算入异常计数。同时池大小默认为10,如果满了,同样立刻返回错误并记入异常计数。如果依然使用调用者线程来执行Command,使用信号量控制并发,同样默认为10。

因为采用了Command模式,所有对远程资源的访问都放入到T run()函数中,因此具有很强的普适性,并绑定任何框架,Hystrix自己只专注于保护的计算,这是它定位清晰聪明的地方。

SpringSide中的演示

配置

首先,Hystrix不支持Annotation式的配置。其次,在Hystrix自己的演示中,是在Command的构造函数里进行配置。实际上Command的配置可能需要从某些配置源读取,所以在SpringSide里,在Service层构建可重用的配置并在创建Command时传入。

异常控制

异常有两种,一种是远端服务的异常,一种是正常的异常。

正常的异常比如输入参数的校验错误,400 Bad Request,又或者其他的正常的业务异常,比如客户已欠费。此时,run函数里应该主动捕捉这些异常,用HystrixBadRequestException包裹并重新抛出,此时异常不算入短路计算内。

throw new HystrixBadRequestException(e.getMessage(), e);

Hystrix的HystrixRuntimeException异常,包括了run()内抛出异常、与Hystrix因为短路保护、超时、并发过大而抛出的各种错误,e.getFailureType()可以得到错误类型。如果是FailureType.COMMAND_EXCEPTION,即run()内抛出异常,还可以用e.getCause()得出真正的原因。

在SpringSide中,并没有在Service层进行错误的翻译,而是一直留到Web层的HystrixExceptionHandler进行统一处理。

另外,Command还提供了Fallback接口,对各种错误提供一个备选处理方案,如果没有实现则直接抛出异常。在实现里可以永远返回true,返回DummyUser,或者访问备用节点等。遗憾是此函数并没有提供具体的错误背景信息,所以也无法细致的处理异常。

Metrics

在Service层的getHystrixMetrics()函数,演示了各种对外报告的Metrics,比较有趣的是延时报告,可以报告最快的50%请求的平均延时,95%的延时等,剔除极端情况的影响。

另外Hystrix还能与Netflix Servo集成

另Hystrix还提供一个DashBoard页面,需要部署一个dashboard的war包,如果监控多个服务器还要安装用于聚合数据的turbine项目的war包。

FAQ

为什么要用有侵入的Command模式,而不是AOP

参见FAQ里的两个问题,作者觉得访问外部应用是件严肃的事情,值得认真的写成Command模式:

要不要用新开独立线程池模式

因为如果不是在新开的线程里运行,则无法在设定超时(比如2秒)时断开请求,只能依靠远程访问者自己的超时机制,如HttpClient并设定15秒超时。据Hystrix自己测试,使用新线程的性能差异有一点,但不很大 Cost of Threads。 而并发控制,用了线程池就用池大小,如果还是用原来的线程,就用一个信号量。

返回参考手册

Clone this wiki locally