架构系列二十二(服务容错设计思考)

208 阅读10分钟

写这篇文章的一个原因,正好是最近项目中有这样的业务场景需要,理论和实践相互结合,才是最佳涨知识的机会。下面我简单交代一下,我们的业务场景。

我们是供应链业务系统,在其中仓储服务的出入库审核,反审核业务场景,有一个需要记录审核反审核his记录的需求。产生这个历史his记录,有这么两个特征

  • 历史his记录,不属于出入库核心业务流程中的内容,它属于旁路逻辑,潜台词,对它的操作,不能影响出入库业务操作
  • 但是它显得又挺重要,原因是在进销存报表中,我们会依靠这个his记录来计算进销存报表,众所周知,在ERP中,进销存是非常重要的一个报表

业务场景交代清楚,你肯定也想到了,这个地方,我们需要去考虑这个his记录,在设计实现上的容错性设计。最终我们的实现方案,我暂且卖一个关子。

接下来,我们先来聊一聊服务容错,需要去考虑的东西。服务容错也是微服务架构众多关注点中的一个,而且是很重要的一个。

关于微服务架构的十大关注点,我曾经分享过一篇文章,就在这个系列的第十七篇文章:架构系列十七(云原生微服务架构架构体系实践思考) 。在这篇文中中,有系统化的去分享微服务架构公共关注点,以及中台化分层架构,推荐有兴趣的朋友可以去看一看。

在这篇文章中,我们只关注一个点,即服务容错性的设计。为了更直观的体现服务容错的重要性,我还是贴出微服务架构公共关注点的图

image.png

什么是服务容错设计?

简单的说,服务容错就是当你的系统,服务在请求中发生故障以后,系统或者服务该如何响应调用者。在服务容错设计中,我们需要考虑服务容错的策略,以及通过什么服务容错设计模式来实现。

这里,有两个概念

  • 服务容错策略
  • 服务容错设计模式

服务容错策略,是指面对故障,我们该做些什么。服务容错设计模式,是指面对容错策略,我们该如何去实现它

服务容错策略有哪些?

常见的服务容错策略,有

  • 故障转移
  • 快速失败
  • 安全失败
  • 沉默失败
  • 故障恢复
  • 并行请求
  • 广播请求

你看到了,一共7种容错策略,你可能在想,为什么会需要这么多的容错策略?这也是我们常说的,在软件应用领域,没有银弹,任何解决方案都需要去结合实际的业务场景,来做权衡取舍

当然,这也是架构师工作乐趣的一部分。

下面我们分别来探讨,每一种容错策略的具体含义,以及它的适用场景。

故障转移

故障转移是一种对调用方透明的容错方案。在分布式系统架构中,可用性是我们追求的一个目标,如果连可用性都不能保障,那么分布式在某种程度上看来意义就没那么大了。

因此到具体的服务上,往往我们都会选择多副本,集群的部署方案,来解决单点的问题。

这就非常满足故障转移的条件了。比如说订单服务有实例A/B/C,用户下单的时候,请求首先打到实例A,实例A发生故障,不能处理请求和做出响应。

这个时候我们可以通过负载均衡,重试让请求打到实例B,最终由B处理请求,并做出响应。

你看到了,在整个故障转移过程中,调用方其实是不知道的,是透明的。当然,调用方本身并不关心最终请求,是实例A做出的响应,还是B或者C做出的响应。

这里,你应该看到了,故障转移有一个关键点:重试

因此当我们选择故障转移策略的时候,你不得不去考虑

  • 幂等性
  • 调用方对请求响应的时间不敏感

如果实际的业务,我们实现了业务操作的幂等性,以及对请求响应时间不敏感的业务场景,那么故障转移是合适的选择

快速失败

在有些业务场景中,我们是不能去做重试操作的。比如说用户支付银行扣款的场景,你不能说扣款不成功,那就重试再扣一次。扣多了,那就等着用户投诉你吧!

这个时候,我们必须要让调用方(用户)来决定是是否需要发起一次新的扣款请求。比如就是扣款这个场景,发生故障后,系统直接响应扣款失败,然后用户可以进一步查询余额,或者支付结果,来决定是否需要发起新的支付扣款请求。

你看到了,这即是快速失败容错策略的适用场景。

安全失败

安全失败,适合于这样的业务场景。比如我开篇说的出入库审核记录his的案例中。业务上有这样的特点

  • 业务核心流程是完成出入库操作
  • 至于记录审核反审核his日志,哪怕失败了,它也不应该影响到主业务流程

那么这个时候,我们非常适合选择安全失败容错策略。

当然你猜目前的实现中,我们选择的是什么样的策略呢?很遗憾!我们目前线上的容错方案是快速失败。

你可能会有疑问,明明知道安全失败的策略更合适,怎么还非要选择快速失败呢?

关于这个实现,有一个背景。我们其实一开始是做了安全失败的容错策略,在完成出入库核心业务流程后,通过异步的方式去写his记录日志,并配套了相关的业务监控,当异步写入his失败,可以及时通过告警走补偿方案,可以说这是个完美的方案选择。

但是中间我们有一个紧急需求,需要配套改造his日志的写入,在改造中提出了两个点

  • 将异步开线程写入方案,改造走MQ对接的方案,这个点主要是考虑需要写入his对接的单据口子有点多(出入库只是其中的一部分),比如像库存调整,调拨,借出归还,赠送,剪板等众多业务场景,都需要对接写入his记录。因此改造走MQ方案,一来方便对接,另外还可以统一收归his写入的口子,达到解耦的目的。
  • 考虑到his记录对进销存报表的重要性,将发送MQ消息与主业务流程做了耦合绑定,要求强一致性

这即是我们目前线上的一个方案。其实这里是否有必要将发送MQ消息与主业务流程进行强一致绑定,是值得商榷的。对于进销存报表,业务上其实没有强一致性的需求,哪怕his记录晚一点写入,或者补偿写入都可以。只需要做到最终一致性即可。

当然,这个点等线上运行一段时间后,一定会根据业务需要做出实际的方案调整。

沉默失败

沉默失败主要考虑这样一种场景。当大量的请求,需要等到超时或者长时间的处理,才能做出结果响应。那么这个时候很容易因为远程服务调用,造成请求对接,进而占用过多的线程,内存,CPU资源影响系统稳定性。

这个时候,我们可以选择沉默失败容错策略。默认远程服务提供者暂时没有能力正常提供服务,不将新的流量打向它,完成故障隔离。

故障恢复

故障恢复这一容错策略,需要和其它容错策略配套使用。跟它一起搭配使用的,通常是快速失败。即当服务故障发生以后,通过快速失败告知服务调用者,此次请求处理失败了。

但是没有关系,请你放心,我们已经把该故障记录下来,并且会去重试处理它。你只要知道一下就好了。

你看到了,故障恢复适合于非主体业务流程的处理。当核心的业务流程处理完成,进一步处理非核心业务流程失败,我们可以选择故障恢复的策略,及时响应调用方,并通过重试做出补偿。

当然这里,需要重试操作,那么幂等性一定要做好。

并行调用

并行调用容错策略,适合于请求对响应时间非常敏感的场景。它的本质是通过请求的并行执行处理,换取响应时间的一种策略,这也就意味着要消耗更多的资源,做跟多的无用功。

因为在并行执行中,只要有一个处理完成,其它都是无用功。

这也就注定了,并行调用适合用在一些关键业务场景,通过并行执行成本换取执行时间,以及执行成功的概率

广播调用

广播调用,有点类似并行调用。但有着本质的区别,并行调用是只要有一个成功即可,而广播调用是都要成功

因此,广播调用适合于这样的场景,比如说分布式缓存的更新。20年我们给政府完成一个大型系统的重构中,当时应用了多级缓存方案

  • 一级缓存,通过redis实现分布式缓存
  • 因为系统业务复杂,参数配置项非常多,为了提升处理响应性能,同时做了进程内的缓存,即二级缓存

一旦参数配置发生变化,需要更新一级缓存,并进一步更新二级缓存,达到缓存数据一致性的目的。这个时候,就非常适合广播调用这个策略。实际实现方案,我们借助了redis的发布订阅机制,来实现进程内缓存的更新。

有哪些服务容错设计模式?

到这里,你大概都知道有哪些服务容错策略,以及它们各自的适用场景。当然问题也来了,这众多的容错策略,该如何去实现呢?

这个话题,我将在下一篇文章中继续给你分享,文章的收尾,先简单列一下有哪些服务容错设计模式

  • 断路器模式
  • 舱壁模式
  • 超时重试