弹性指的是在复杂网络环境下,面对各种故障和挑战,仍能提供和维持一个可以接受的服务水平,并正常运作。
-来自Wikipedia
自从长期服务和最近的微服务被大家熟知和使用,很多应用程序开发人员已经将整体式的API,转换成简单的、功能单一的微服务。然而,这样的转换,导致为了保证一致的响应时间和弹性,依赖关系变得不可用时,造成额外的损耗。例如,一个单体式的web应用程序,执行一次重试,在一定程度上是弹性的,因为它可以在某些依赖关系(如数据库或其他服务)不可用时恢复。这种恢复能力没有任何附加的网络损耗或代码复杂度。
对于一个需要编排依赖的服务,每次调用都是昂贵的,失败会导致降低用户体验,特别是当试图从失败中恢复时。将对后端服务造成更大的压力
断路器模式
考虑一个典型的使用案例:一个电子商务网站,在黑色星期五时服务器超载,由于压力过大,供应商提供的付款系统脱机了几秒钟。由于高并发请求,用户开始看到结帐时长时间的无响应。这些条件也导致了所有的应用程序服务器都被阻塞,而这些阻塞线程正在等待接收来自供应商的响应。经过漫长的等待时间,最终的结果是失败。
这些事件导致了无效的购物车,用户试图更新或重新下订单,进一步扩大了应用服务器的负载,应用服务器上已经堆积了大量等待线程,导致网络拥塞。
断路器是一种简单的设计结构,它时常保持警惕,对故障进行监控。在上述情况下,当断路器发现在调用供应商接口时,发生了长等待时间,那么使用fail fast策略,向用户返回一个错误响应,而不是使线程长时间等待。因此,断路器可以防止用户等待时间过长。
断路器背后的基本思想是很简单的。在断路器对象中,包含一个受保护的函数调用,并由该断路器对象进行故障监测。一旦故障出现,并达到一定的阈值,断路器跳闸,断路器中剩下的调用都将返回一个错误,而不是将所有步骤继续执行下去。通常,如果断路器跳闸,你还需要进一步的监控和告警。
– Martin Fowler
恢复时间对底层资源至关重要,有了一个快速失败的断路器,保护了超载的系统,使下游服务能快速恢复。
断路器是一直活跃在系统中,时刻监视系统的依赖调用。为了防止高故障率,断路器在很短的时间内便能停止失败调用的扩散,而不是仅仅返回一个标准的错误。
eBay与断路器
在很早之前,我们使用一个简单的设置方案名为AUTO_MARK_DOWN,用于防止漫长的依赖调用等待问题。通过将失败的调用短路,直到他们通过MARK_UP标记恢复。自动检查系统定期检查各种机器每一个依赖的AUTO_MARK_DOWN状态,并执行MARK_UP。
然而,自动检查系统和MARK_UP设施并不是内嵌到应用系统,而是位于外部。由于没有关于请求量和故障率的持续不断的反馈,会出现一个未经验证就被标记为MARK_UP的系统依赖异常。依托这个设置也导致了误报,由于自动检查系统是客户端之外,无法评估失败的连续性。
另一个该设计的主要缺陷是,没办法对所有应用程序的依赖做全面的,实时的监控。这个旧系统是缓慢和不稳定的,没有持续的遥测,盲目标记所有系统的auto_mark_down,假定应用程序的依赖将进一步产生故障。其结果是不可预测的,难以正确评估。
断路器的恢复
断路器在适当的时候需要注意跳闸了的依赖服务。一个更复杂的系统需要持续保持警惕,以确定依赖调用是否可用,如果没问题,则让依赖调用继续下去。
这种行为可以用2种方式实现:
1. 允许所有调用执行,在一个正常的时间间隔内执行并检查错误。
2. 允许一个单一的调用执行,更频繁的速度来衡量可用性。
AUTO_MARK_DOWN是第一种方式,其中,电路在没有任何恢复的情况下被关闭,并依靠错误识别问题。
第二种方式是一个更复杂的机制,因为它不允许多个调用同时执行,因为调用可能需要很长的时间来执行但是仍然失败。然而,只允许一个单一的调用执行,需确保更快的执行,从而实现了系统电路的恢复和更快的收敛。
理想的断路器
一个和谐的系统,应有一个理想的断路器,实时监测,并能快速恢复故障,使应用程序达到真正的弹性可容错。
断路器 + 实时监测 + 恢复 = 弹性可容错
– 匿名
使用上面的电子商务网站为例,在一个弹性系统中,断路器持续对系统进行故障评估,在付款处理器发生故障时,发现由于供应商而造成的长时间等待。在这种情况下,它打破了电路,并快速失败。其结果是,用户被告知系统故障,供应商有足够的时间来恢复。
同时,断路器也不断地发送一个请求,以确认供应商系统是否恢复。如果是这样的话,断路器将闭合电路,允许其余的调用正常执行,从而有效地消除网络拥塞和长时间等待的问题。
Netflix Hystrix
Hystrix是一个能够为延迟和故障提供更强大的容错能力的库,通过隔离访问远程系统、服务和第三方库的节点,阻止级联故障,从而使复杂的分布式系统更具弹性。
– Netflix
自从2012成立以来,Hystrix成为许多试图为系统提高处理能力和解决故障的解决方法。它有一个相当成熟的接口和一个高度可调的配置系统,使应用程序开发人员能够提供最佳的服务依赖调用。
Hystrix断路器的状态
下面的状态图,描述了中断路器生命周期中不同状态下弹性可容错系统的运作情况。
正常运行(Closed)
当一个系统运行平稳,成功状态计数器用于测量弹性系统的稳定性,而故障表用于跟踪任何故障。该设计确保当达到故障的阈值时,断路器断开电路,以防止进一步的资源请求。
失败状态(Open)
在这个时刻,每一个依赖调用是短路的,并抛出HystrixRuntimeException异常,伴随SHORTCIRCUIT失败类型,给出异常明确的原因。一旦等待时间过后,Hystrix断路器移到半开放状态。
半开放状态
在这种状态下,由Hystrix负责发送第一个请求,检查系统的可用性,让其他的请求快速失败,直到得到依赖的响应。如果调用是成功的,断路器被重置为Closed状态;如果发生故障,系统返回Open状态,并且整个过程继续循环。
如何使用Hystrix
Hystrix Github上有一个全面的文档介绍如何使用Hystrix。这很简单,只要使用Hystrix库创建类并调用服务。
public class CommandHelloWorld extends HystrixCommand<String> {
private final String name;
public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
@Override
protected String run() {
// a real example would do work like a network call here
return "Hello " + name + "!";
}
}
Reference: https://github.com/Netflix/Hystrix/wiki/Getting-Started
在内部,这个类利用rxjava库异步执行服务依赖的调用。此设计使用应用程序的线程,最大化程序性能,并智能管理服务资源调用。对于使用延迟调用的方式执行并行处理管理依赖关系的应用程序开发人员,Hystrix也提供Future
eBay如何使用Hystrix
在eBay,许多应用已经开始使用Hystrix,要么作为一个独立的库或使用我们的平台包装。我们平台包装的版本,通过JMX beans方式暴露Hystrix配置,方便集中管理。对于关键系统,我们包装的版本还注入自定义Hystric插件实现捕获实时被发布的metrics,并feed到我们的监控系统。
Hystric的dashboard作为核心服务器监控系统的一部分,使团队能够查看他们的应用程序不同时期的依赖情况。
Hystrix提供的execution hook是系统整合的一个关键组成部分,因为它有助于实时监测/预警,尤其是错误和回退失败的各种故障,从而帮助我们更迅速的调查和解决问题,几乎没有造成任何对用户的影响。
eBay使用实例: Secure Token service
eBay有一系列的内部和外部的API服务。所有这些服务都是通过令牌认证,安全令牌服务作为令牌的发行人和验证。所有的令牌服务现在都升级并使用基于Hytrix断路器,它使安全令牌服务高可用。当有一个服务在繁忙时,该服务的断路器打开,不会对令牌服务造成压力,同时允许其他服务功能正常。
断路器是Hystrix库默认提供的一个功能。断路器的功能可以概括如下:
1. 断路器对所有调用状态进行验证。
2. 电路的Closed状态允许请求通过。
3. 一个Open状态失败所有请求。
4. 一个Half-Open状态(当sleep等待时间完成),允许一个请求通过,并在成功或失败时,转换成Closed的或Open的状态。
总结
Hystrix不仅仅是一个断路器,也是一个具有丰富监控功能的完整的库,可以很容易地植入到现有系统。我们已经开始为未来的使用情况探索,使用该库的请求崩溃和请求缓存的功能。当然,还有一些其他的Java实现,如Akka和Spring断路器,然而,根据我们弹性环境中,关键应用运行情况,Hystrix已经被证明是一个成熟的库,提供任何时间段内的高可用性。