微服务进阶场景实战:最终一致性与实时一致性解决方案如何设计?

数据一致性
前面总结了微服务的9个痛点,有些痛点没有好的解决方案,而有些痛点是有对策的,从本章开始,就来讲解某些痛点对应的解决方案 。
这一章先解决数据一致性的问题,先来看一个实际的业务场景 。
业务场景:下游服务失败后上游服务如何独善其身
前面讲过,使用微服务时,很多时候需要跨多个服务去更新多个数据库的数据,架构如图13-1所示 。

微服务进阶场景实战:最终一致性与实时一致性解决方案如何设计?

文章插图
• 图13-1 微服务上下游示意图
如图13-1所示,如果业务正常运转,3个服务的数据应该分别变为a2、b2、c2,此时数据才一致 。但是如果出现网络抖动、服务超负荷或者数据库超负荷等情况,整个处理链条有可能在步骤2失败,这时数据就会变成a2、b1、c1;当然也有可能在步骤3失败,最终数据就会变成a2、b2、c1 。这样数据就出错了,即数据不一致 。
在本章所讨论的项目开始之前,因为之前的改造项目时间很紧,所以开发人员完全没有精力处理系统数据一致性的问题,最终业务系统出现了很多错误数据,业务部门发工单告知IT部门数据有问题,经过一番检查后,IT部门发现是因为分布式更新的原因导致了数据不一致 。
此时,IT部门不得不抽出时间针对数据一致性问题给出一个可靠的解决方案 。通过讨论,IT部门把数据一致性的问题归类为以下两种情况 。
1.实时数据不一致可以接受,但要保证数据的最终一致性
因为一些服务出现错误,导致图13-1中的步骤3失败,此时处理完请求后,数据就变成了a2、b2、c1,不过没关系,只需保证最终数据是a2、b2、c2即可 。
在以往的一个项目中,业务场景是这样的(示例有所简化):零售下单时,一般需要实现在商品服务中扣除商品的库存、在订单服务中生成一个订单、在交易服务中生成一个交易单这3个步骤 。假设交易单生成失败,就会出现库存扣除、订单生成,但交易单没有生成的情况,此时只需保证最终交易单成功生成即可,这就是最终一致性 。
2.必须保证实时一致性
如果图13-1中的步骤2和步骤3成功了,数据就会变成b2、c2,但是如果步骤3失败,那么步骤1和步骤2会立即回滚,保证数据变回a1、b1 。
在以往的一个项目中,业务场景类似这样:用户使用积分兑换折扣券时,需要实现扣除用户积分、生成一张折扣券给用户这两个步骤 。如果还是使用最终一致性方案的话,有可能出现用户积分扣除而折扣券还未生成的情况,此时用户进入账户发现积分没有了,也没有折扣券,就会马上投诉 。
那怎么办呢?直接将前面的步骤回滚,并告知用户处理失败请继续重试即可,这就是实时一致性 。
针对以上两种情况,具体解决方案是什么呢?下面一起来看看 。
最终一致性方案
对于数据要求最终一致性的场景,实现思路是这样的 。
1)每个步骤完成后,生产一条消息给MQ,告知下一步处理接下来的数据 。
2)消费者收到这条消息,将数据处理完成后,与步骤1)一样触发下一步 。
3)消费者收到这条消息后,如果数据处理失败,这条消息应该保留,直到消费者下次重试 。
将3个服务的整个调用流程走下来,逻辑还是比较复杂的,整体流程如图13-2所示 。
微服务进阶场景实战:最终一致性与实时一致性解决方案如何设计?

文章插图
• 图13-2 服务调用流程
详细的实现逻辑如下 。
1)调用端调用Service A 。
2)Service A将数据库中的a1改为a2 。
3)Service A生成一条步骤2(暂且命名为Step2)的消息给MQ 。
4)Service A返回成功信息给调用端 。
5)Service B监听Step2的消息,获得一条消息 。
6)Service B将数据库中的b1改为b2 。
7)Service B生成一条步骤3(暂且命名为Step3)的消息给MQ 。
8)Service B将Step2的消息设置为已消费 。
9)Service C监听Step3的消息,获得一条消息 。
10)Service C将数据库中的c1改为c2 。
11)Service C将Step3的消息设置为已消费 。
接下来要考虑,如果每个步骤失败了该怎么办?
1)调用端调用Service A 。
解决方案:直接返回失败信息给用户,用户数据不受影响 。
2)Service A将数据库中的a1改为a2 。
解决方案:如果这一步失败,就利用本地事务数据直接回滚,用户数据不受影响 。
3)Service A生成一条步骤2)(Step2)的消息给MQ 。
解决方案:如果这一步失败,就利用本地事务数据将步骤2)直接回滚,用户数据不受影响 。


推荐阅读