36 _ 面对程序故障,我们该做些什么? 你好,我是周志明。接下来的两节课,我们一起来学习服务的容错性设计这个话题。

“容错性设计”(Design for Failure)是微服务的另一个核心原则,也是我在这门课中反复强调的开发观念的转变。

不过,虽然已经有了一定的心理准备,但在首次将微服务架构引入实际生产系统时,在服务发现网关路由等支持下,踏出了服务化的第一步以后,我们还是很可能会经历一段阵痛期。随着拆分出的服务越来越多,随之而来的,我们也会面临以下两个问题的困扰:

  • 某一个服务的崩溃,会导致所有用到这个服务的其他服务都无法正常工作,一个点的错误经过层层传递,最终波及到调用链上与此有关的所有服务,这便是雪崩效应。如何防止雪崩效应,便是微服务架构容错性设计原则的具体实践,否则服务化程度越高,整个系统反而越不稳定。
  • 服务虽然没有崩溃,但由于处理能力有限,面临超过预期的突发请求时,大部分请求直至超时都无法完成处理。这种现象产生的后果跟交通堵塞是类似的,如果一开始没有得到及时地治理,后面就会需要很长时间才能使全部服务都恢复正常。

这两个问题,就是“流量治理”这个话题要解决的了。在这个小章节,我们将围绕着如何解决这两个问题,提出服务容错、流量控制、服务质量管理等一系列解决方案。

当然了,这些措施并不是孤立的,它们相互之间存在很多联系,其中的许多功能还必须与咱们之前学习过的服务注册中心、服务网关、负载均衡器配合才能实现。理清楚了这些技术措施背后的逻辑链条,我们就找到了理解它们工作原理的捷径。

接下来,我们就从服务容错这个解决方案学起吧。

服务容错

Martin Fowler与James Lewis提出的“微服务的九个核心特征”是构建微服务系统的指导性原则,但不是技术规范,并没有严格的约束力。在实际构建系统时候,其中多数特征可能会有或多或少的妥协,比如分散治理、数据去中心化、轻量级通讯机制、演进式设计,等等。但也有一些特征是无法做出妥协的,其中典型的就是今天我们讨论的主题:容错性设计。

容错性设计不能妥协的原因在于,分布式系统的本质是不可靠的,一个大的服务集群中,程序可能崩溃、节点可能宕机、网络可能中断,这些“意外情况”其实全部都在“意料之中”。原本信息系统设计成分布式架构的主要动力之一,就是提升系统的可用性,最低限度也必须保证将原有系统重构为分布式架构之后,可用性不出现倒退才行。

如果说,服务集群中出现任何一点差错都能让系统面临“千里之堤溃于蚁穴”的风险,那么分布式恐怕就根本没有机会成为一种可用的系统架构形式了。

容错策略

那在实践中怎么落实容错性设计这条原则呢?除了思想观念上转变过来,正视程序必然是会出错的,对它进行有计划的防御之外,我们还必须了解一些常用的容错策略和容错设计模式,来指导具体的设计与编码实践。

那怎么理解容错策略和容错设计模式呢?其实,容错策略,指的是“面对故障,我们该做些什么”;而容错设计模式,指的是“要实现某种容错策略,我们该如何去做”

所以,接下来我们先一起学习7种常见的容错策略,包括故障转移、快速失败、安全失败、沉默失败、故障恢复、并行调用和广播调用,然后下一讲我们再学习几种被实践证明有效的服务容错设计模式。

第一种容错策略,是故障转移(Failover)

高可用的服务集群中,多数的服务,尤其是那些经常被其他服务依赖的关键路径上的服务,都会部署多个副本。这些副本可能部署在不同的节点(避免节点宕机)、不同的网络交换机(避免网络分区),甚至是不同的可用区(避免整个地区发生灾害或电力、骨干网故障)中。

故障转移是指,如果调用的服务器出现故障,系统不会立即向调用者返回失败结果,而是自动切换到其他服务副本,尝试其他副本能否返回成功调用的结果,从而保证了整体的高可用性。

故障转移的容错策略应该有一定的调用次数限制,比如允许最多重试三个服务,如果都发生报错,那还是会返回调用失败。引入调用次数的限制,不仅是因为重试有执行成本,更是因为过度的重试反而可能让系统处于更加不利的状况。

我们看一个例子。现在有Service A → Service B → Service C这么一条调用链。假设A的超时阈值为100毫秒,而B调用C需要60毫秒,然后不幸失败了,这时候做故障转移其实已经没有太大意义了。因为即使下一次调用能够返回正确结果,也很可能同样需要耗费60毫秒的时间,时间总和就已经超过了Service A的超时阈值。所以,在这种情况下故障转移反而对系统是不利的。

第二种容错策略,是快速失败(Failfast)。

有一些业务场景是不允许做故障转移的,因为故障转移策略能够实施的前提,是服务具有幂等性。那对于非幂等的服务,重复调用就可能产生脏数据,引起的麻烦远大于单纯的某次服务调用失败。这时候,就应该把快速失败作为首选的容错策略。

比如,在支付场景中,需要调用银行的扣款接口,如果该接口返回的结果是网络异常,那程序很难判断到底是扣款指令发送给银行时出现的网络异常,还是银行扣款后给服务返回结果时出现的网络异常。为了避免重复扣款,此时最恰当的方案就是尽快让服务报错并抛出异常,坚决避免重试,由调用者自行处理。

第三种容错策略,是安全失败(Failsafe)。

在一个调用链路中的服务,通常也有主路和旁路之分,并不见得每个服务都是不可或缺的,属于旁路逻辑的一个显著特点是,服务失败了也不影响核心业务的正确性。比如,开发基于Spring管理的应用程序时,通过扩展点、事件或者AOP注入的逻辑往往就属于旁路逻辑,典型的有审计、日志、调试信息,等等。

属于旁路逻辑的另一个显著特征是,后续处理不会依赖其返回值,或者它的返回值是什么都不会影响后续处理的结果。比如只是将返回值记录到数据库,并不使用它参与最终结果的运算。

对这类逻辑,一种理想的容错策略是,即使旁路逻辑调用失败了,也当作正确来返回,如果需要返回值的话,系统就自动返回一个符合要求的数据类型的对应零值,然后自动记录一条服务调用出错的日志备查即可。这种容错策略,被称为安全失败。

第四种容错策略,是沉默失败(Failsilent)。

如果大量的请求需要等到超时(或者长时间处理后)才宣告失败,很容易因为某个远程服务的请求堆积而消耗大量的线程、内存、网络等资源,进而影响到整个系统的稳定性。

面对这种情况,一种合理的失败策略是当请求失败后,就默认服务提供者一定时间内无法再对外提供服务,不再向它分配请求流量,并将错误隔离开来,避免对系统其他部分产生影响。这种容错策略,就被称为沉默失败。

第五种容错策略,是故障恢复(Failback)。

故障恢复一般不单独存在,而是作为其他容错策略的补充措施。在微服务管理框架中,如果设置容错策略为故障恢复的话,通常默认会采用快速失败加上故障恢复的策略组合。

故障恢复是指,当服务调用出错了以后,将该次调用失败的信息存入一个消息队列中,然后由系统自动开始异步重试调用。

从这个定义中也可以看出,故障恢复策略,一方面是尽力促使失败的调用最终能够被正常执行,另一方面也可以为服务注册中心和负载均衡器及时提供服务恢复的通知信息。很显然,故障恢复也要求服务必须具备幂等性,由于它的重试是后台异步进行,即使最后调用成功了,原来的请求也早已经响应完毕。所以,故障恢复策略一般用于对实时性要求不高的主路逻辑,也适合处理那些不需要返回值的旁路逻辑。

为了避免在内存中的异步调用任务堆积,故障恢复与故障转移一样,也应该有最大重试次数的限制。

故障转移、快速失败、安全失败、沉默失败和故障恢复这5种容错策略的英文,都是以“Fail”开头的,它们有一个共同点,都是针对调用失败时如何进行弥补的。接下来,咱们要学习的并行调用和广播调用这两种策略,则是在调用之前就开始考虑如何获得最大的成功概率。

第六种容错策略,是并行调用(Forking)。

并行调用策略,是指一开始就同时向多个服务副本发起调用,只要有其中任何一个返回成功,那调用便宣告成功。这种策略是在一些关键场景中,使用更高的执行成本换取执行时间和成功概率的策略。

这种处理思路,其实对应的就是,咱们在日常生活中,对一些重要环节采取的“双重保险”或者“多重保险”的处理思路。

第七种容错策略,是广播调用(Broadcast)。

广播调用与并行调用是相对应的,都是同时发起多个调用,但并行调用是任何一个调用结果返回成功便宣告成功,而广播调用则是要求所有的请求全部都成功,才算是成功。

也就是说,对于广播调用来说,任何一个服务提供者出现异常都算调用失败。因此,广播调用通常被用于实现“刷新分布式缓存”这类的操作。

小结

今天这一讲,我们学习了故障转移、快速失败、安全失败、沉默失败、故障恢复、并行调用和广播调用,一共7种容错策略。

其实,容错策略并不是计算机领域独有的,在交通、能源、航天等非常多的领域都有容错性设计,也会使用到上面这些策略,并在自己的行业领域中进行解读与延伸。

这里,我把今天讲到的7种容错策略进行了一次对比梳理,你可以在下面的表格中看到它们的优缺点和应用场景。

一课一思

你在实际工作中使用过哪种容错策略,是要用它来解决什么问题的呢?你能试着说说它是如何实现的吗?

欢迎在留言区分享你的答案。如果你觉得有收获,欢迎你把今天的内容分享给更多的朋友。

参考资料

https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/%e5%91%a8%e5%bf%97%e6%98%8e%e7%9a%84%e6%9e%b6%e6%9e%84%e8%af%be/36%20_%20%e9%9d%a2%e5%af%b9%e7%a8%8b%e5%ba%8f%e6%95%85%e9%9a%9c%ef%bc%8c%e6%88%91%e4%bb%ac%e8%af%a5%e5%81%9a%e4%ba%9b%e4%bb%80%e4%b9%88%ef%bc%9f.md