Skip to content
Nalan Shao edited this page Jun 19, 2018 · 2 revisions

分布式服务架构下的数据一致性解决方案

在分布式服务架构下,数据往往分散在不同的节点,节点之间数据的一致性是我们非常需要关心的问题。例如在微服务架构中,不同业务往往拆分解耦,每个服务都有独立的数据库做数据存储。而这些服务又相互关联,必须保证分布式事务中数据的一致性。

常用的解决方案为二阶段提交协议。二阶段提交提供了一套完整的分布式事务的解决方案,遵循事务严格的 ACID 特性。但是其本身是一个阻塞协议,当一个节点在等待回复消息时进入阻塞状态。其他需要这些资源的处理事务需要等待。如果协调者挂掉,参与者将永远不能结束它们的事务。

二阶段提交协议从第一阶段准备阶段开始就要将所涉及的业务数据锁定,并且锁定过程跨越整个提交流程。在高并发涉及业务模块较多的情况下,对数据库的性能影响较大。导致分布式系统下无法直接使用此方案来解决数据一致性问题,但它提供了解决分布式系统下数据一致性问题的思路。

使用二阶段提交协议来保证事务一致性还需要SQL类型数据库来支持,对于NoSQL内存型数据库无法直接实现。而除了二阶段提交协议及其变种协议外,传统的解决方案还有依靠本地关系数据库的强一致性,或者通过设计回滚操作来实现。而对于不满足上述条件的情形(没有关系型数据库、结点分散独立不支持本地事务,或者不支持回滚操作等)或者对并发要求更高的场景,则需要作出相应的改变和调整,来实现分布式事务的一致性。

这里我们考虑结合BASE理论提出一个解决方案。系统在处理业务时,记录每一步的临时状态当出现异常时,根据状态判断是否继续处理请求或者退回原始状态,从而达到数据的最终一致。

可靠消息最终一致性

这里的解决方案为利用MQ来实现二阶段提交(事务性)。
方案涉及三个模块:

  • 上游应用,执行业务并发送 MQ 消息。
  • 可靠消息服务(下称RAS)和 MQ 消息组件,协调上下游消息的传递,并确保上下游数据的一致性。
  • 下游应用,监听 MQ 的消息并执行自身业务。

上游应用执行业务并发送 MQ 消息(第一阶段)

上游应用将本地业务执行和消息发送绑定在同一个本地事务中,保证要么本地操作成功并发送 MQ 消息,要么两步操作都失败并回滚

  1. 上游应用发送待确认消息到RAS。
  2. RAS持久化待确认消息并返回。
  3. 上游应用执行本地业务。
  4. 上游应用通知RAS确认业务已执行并发送消息。
  5. RAS修改消息状态为发送状态(已发送)将消息投递到 MQ 中间件
出错步骤 原因 影响
1 RAS未收到消息 上游应用回退事务,不影响一致性
2 RAS处理消息过程中出错
或处理完成后出错未返回消息
回复超时,上游应用回退事务,不影响一致性
但RAS可能保存了待确认状态的无效消息
3 上游应用执行事务失败,回退事务 不影响一致性,但RAS一定保存了待确认状态的无效消息
4 RAS未收到通知消息 产生不一致性
5 RAS修改消息状态失败 产生不一致性,未投递到MQ

本地事务包含步骤1-3,如果在步骤4、5中,上游应用也加入返回确认等待,那就变成和二阶段提交一样的阻塞协议了。所以这里直接将通知消息发给RAS后就进入异步状态。

RAS一开始为待确认状态,当步骤1-3出错时,上游应用整个事务回退,不影响一致性。而当4、5发生错误时,会出现不一致性,需要做如下处理:

  1. RAS定时监听消息的状态,如果存在状态为待确认并且超时的消息,则表示上游应用和RAS交互中的步骤 4 或者 5 出现异常。
  2. 向上游应用查询业务执行的情况。
  3. 业务未执行,则删除该消息,保证业务和RAS的一致性。业务已执行,则修改消息状态为已发送,并发送消息到 MQ 组件。

RAS需要将消息内容,消息uuid,消息超时时间、from、topic都序列化。

下游应用监听 MQ 消息并执行业务(第二阶段)

下游应用监听 MQ 消息并执行业务,并且将消息的消费结果通知RAS。
RAS的状态需要和下游应用的业务执行保持一致,RAS状态不是已完成时,确保下游应用未执行,RAS状态是已完成时,确保下游应用已执行。

  1. 下游应用监听 MQ 消息组件并获取消息。
  2. 下游应用根据 MQ 消息体信息处理本地业务。
  3. 下游应用向 MQ 组件自动发送 ACK 确认消息被消费。
  4. 下游应用通知RAS消息被成功消费。
  5. RAS查询该消息是否已经被所有订阅者消费完成,如果已完成,就更新消息持久化存储部分MQ(将已经消费的消息删除),RAS将该消息状态更改为已完成
出错步骤 原因 影响
1 下游应用未从MQ获得消息 下游应用不执行本地业务,下游数据产生不一致
2 下游应用执行本地业务失败 下游数据产生不一致
3 下游应用更新MQ时崩溃 数据已经一致,但MQ和RAS中的消息信息与状态未更新
4 RAS未收到成功消费通知 同3
5 RAS删除消息或者更新MQ失败
RAS更新消息状态失败
部分订阅者未处理完成消息
同3
数据已经一致,但RAS中的消息状态未更新
下游部分数据不一致

步骤1-5发生错误时,会对一致性有不同的影响,此时RAS中存在消息状态为已发送并且超时的消息,执行以下操作:

  1. RAS定时查询状态为已发送并超时的消息,查询消息是否存在。
  2. 如果不存在,那么更新消息持久化存储部分和MQ,并将消息状态更改为已完成,保持数据一致性。(4-5失败)
  3. 如果消息存在,那么RAS将消息修改为死亡状态,并通知上层应用
    说明有部分或者全部下游业务未完成处理,这里的处理方法一种是给所有下游应用发送回滚,类似于二阶段提交的第二阶段失败处理。但是这里RAS并不直接和下游应用通信,并且处理顺利的下游应用可能已经在处理后续消息,若直接在当前消息回滚,对已经执行的后续消息也有影响。
    所以这里采用第二种方法,将其纠正流程交托给下游应用,发生错误的下游应用在恢复后,应该重新执行获取消息,执行本地业务等流程。由于其后续消息还没消费,所以这里就可以通过本地业务执行的幂等性来保证数据的一致性。而那些没有发生错误的应用已经在消费后续消息。

实际应用过程中,应该引入重发次数限制,超过重发次数限制时,RAS将消息修改为死亡消息,通知上层应用。

除了上述流程之外,RAS和上下游应用都应该能够在发生错误重启后向redis访问恢复之前的数据。

在区块链中的应用

传统银行IT如何落地区块链技术?-- 解决事务一致性