0%

分布式一致性解决方案

何为一致性问题?简单而言,一致性问题就是相互独立的节点之间如何达成一项决议的问题。分布式系统中,进行数据库事务提交(commit transaction)、Leader选举、序列号生成等都会遇到一致性问题。

假设一个具有N个节点的分布式系统,当其满足以下条件时,我们说这个系统满足一致性:

  • 全认同(agreement): 所有N个节点都认同一个结果。
  • 值合法(validity): 该结果必须由N个节点中的节点提出。
  • 可结束(termination): 决议过程在一定时间内结束,不会无休止地进行下去。

分布式系统实现起来并不轻松,因为它面临着这些问题:

  • 消息传递异步无序(asynchronous): 现实网络不是一个可靠的信道,存在消息延时、丢失,节点间消息传递做不到同步有序(synchronous)
  • 节点宕机(fail-stop): 节点持续宕机,不会恢复
  • 节点宕机恢复(fail-recover): 节点宕机一段时间后恢复,在分布式系统中最常见
  • 网络分化(network partition): 网络链路出现问题,将N个节点隔离成多个部分
  • 拜占庭将军问题(byzantine failure): 节点或宕机或逻辑失败,甚至不按套路出牌抛出干扰决议的信息

一致性还具备两个属性,一个是强一致(safety),它要求所有节点状态一致、共进退;一个是可用(liveness),它要求分布式系统24x7无间断对外服务。

FLP定理是分布式系统理论中的基础理论,正如物理学中的能量守恒定律彻底否定了永动机的存在,FLP定理否定了同时满足safety 和 liveness 的一致性协议的存在。

1. 2PC 和 3PC

1.1 2PC(tow phase commit)

2PC(tow phase commit)两阶段提交[5]顾名思义它分成两个阶段,先由一方进行提议(propose)并收集其他节点的反馈(vote),再根据反馈决定提交(commit)或中止(abort)事务。我们将提议的节点称为协调者(coordinator),其他参与决议节点称为参与者(participants)。

img

第一个阶段是「投票阶段」

  • 1.协调者首先将命令「写入日志」
    1. 「发一个prepare命令」给B和C节点这两个参与者
  • 3.B和C收到消息后,根据自己的实际情况,「判断自己的实际情况是否可以提交」
  • 4.将处理结果「记录到日志」系统
  • 5.将结果「返回」给协调者
img

第二个阶段是「决定阶段」

当A节点收到B和C参与者所有的确认消息后

  • 「判断」所有协调者「是否都可以提交」
    • 如果可以则「写入日志」并且发起commit命令
    • 有一个不可以则「写入日志」并且发起abort命令
  • 参与者收到协调者发起的命令,「执行命令」
  • 将执行命令及结果「写入日志」
  • 「返回结果」给协调者

coordinator根据participant的反馈,提交或中止事务,如果participant全部同意则提交,只要有一个participant不同意就中止。

在异步环境(asynchronous)并且没有节点宕机(fail-stop)的模型下,2PC可以满足全认同、值合法、可结束,是解决一致性问题的一种协议。

可能会存在哪些问题?

  • 「单点故障」:一旦事务管理器出现故障,整个系统不可用
  • 「数据不一致」:在阶段二,如果事务管理器只发送了部分 commit 消息,此时网络发生异常,那么只有部分参与者接收到 commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
  • 「响应时间较长」:整个消息链路是串行的,要等待响应结果,不适合高并发的场景
  • 「不确定性」:当事务管理器发送 commit 之后,并且此时只有一个参与者收到了 commit,那么当该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功。

1.2 3PC(three phase commit)

3PC(three phase commit)即三阶段提交。相对于2PC来说增加了CanCommit阶段和超时机制。如果段时间内没有收到协调者的commit请求,那么就会自动进行commit,解决了2PC单点故障的问题。

f2f443bf6560482a3a4dd3eeb002bedb
  • 第一阶段:「CanCommit阶段」

    这个阶段所做的事很简单,就是协调者询问事务参与者,你是否有能力完成此次事务。

    • 如果都返回yes,则进入第二阶段
    • 有一个返回no或等待响应超时,则中断事务,并向所有参与者发送abort请求
  • 第二阶段:「PreCommit阶段」

    此时协调者会向所有的参与者发送PreCommit请求,参与者收到后开始执行事务操作,并将Undo和Redo信息记录到事务日志中。参与者执行完事务操作后(此时属于未提交事务的状态),就会向协调者反馈“Ack”表示我已经准备好提交了,并等待协调者的下一步指令。

  • 第三阶段:「DoCommit阶段」

    在阶段二中如果所有的参与者节点都可以进行PreCommit提交,那么协调者就会从“预提交状态”转变为“提交状态”。然后向所有的参与者节点发送”doCommit”请求,参与者节点在收到提交请求后就会各自执行事务提交操作,并向协调者节点反馈“Ack”消息,协调者收到所有参与者的Ack消息后完成事务。相反,如果有一个参与者节点未完成PreCommit的反馈或者反馈超时,那么协调者都会向所有的参与者节点发送abort请求,从而中断事务。

participant如果在不同阶段宕机,我们来看看3PC如何应对:

  • 阶段1: coordinator或watchdog未收到宕机participant的vote,直接中止事务;宕机的participant恢复后,读取logging发现未发出赞成vote,自行中止该次事务
  • 阶段2: coordinator未收到宕机participant的precommit ACK,但因为之前已经收到了宕机participant的赞成反馈(不然也不会进入到阶段2),coordinator进行commit;watchdog可以通过问询其他participant获得这些信息,过程同理;宕机的participant恢复后发现收到precommit或已经发出赞成vote,则自行commit该次事务
  • 阶段3: 即便coordinator或watchdog未收到宕机participant的commit ACK,也结束该次事务;宕机的participant恢复后发现收到commit或者precommit,也将自行commit该次事务

因为有了准备提交(prepare to commit)阶段,3PC的事务处理延时也增加了1个RTT,变为3个RTT(propose+precommit+commit),但是它防止participant宕机后整个系统进入阻塞态,增强了系统的可用性,对一些现实业务场景是非常值得的。

2. 柔性事务方案

2.1 TCC 补偿

TCC(Try-Confirm-Cancel)又被称补偿事务,TCC与2PC的思想很相似,事务处理流程也很相似,但2PC是应用于在DB层面,TCC则可以理解为在应用层面的2PC,是需要我们编写业务逻辑来实现

TCC它的核心思想是:”针对每个操作都要注册一个与其对应的确认(Try)和补偿(Cancel)”。它分为三个阶段:Try,Confirm,Cancel

Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。

img

TCC 事务机制相比于上面介绍的2PC,解决了其几个缺点:

  • 1.「解决了协调者单点」,由主业务方发起并完成这个业务活动。业务活动管理器也变成多点,引入集群。
  • 2.「同步阻塞」:引入超时,超时后进行补偿,并且不会锁定整个资源,将资源转换为业务逻辑形式,粒度变小。
  • 3.「数据一致性」,有了补偿机制之后,由业务活动管理器控制一致性。

总之,TCC 就是通过代码人为实现了两阶段提交,不同的业务场景所写的代码都不一样,并且很大程度的「增加」了业务代码的「复杂度」,因此,这种模式并不能很好地被复用。

2.2 本地消息表

image.png

写本地消息和业务操作放在一个事务里,保证了业务和发消息的原子性,要么他们全都成功,要么全都失败。

容错机制:

  • 扣减余额事务 失败时,事务直接回滚,无后续步骤
  • 轮序生产消息失败, 增加余额事务失败都会进行重试

本地消息表的特点:

  • 不支持回滚
  • 轮询生产消息难实现,如果定时轮询会延长事务总时长,如果订阅binlog则开发维护困难

适用于可异步执行的业务,且后续操作无需回滚的业务

2.3 事务消息

在上述的本地消息表方案中,生产者需要额外创建消息表,还需要对本地消息表进行轮询,业务负担较重。阿里开源的RocketMQ 4.3之后的版本正式支持事务消息,该事务消息本质上是把本地消息表放到RocketMQ上,解决生产端的消息发送与本地事务执行的原子性问题。

事务消息发送及提交:

  • 发送消息(half消息)
  • 服务端存储消息,并响应消息的写入结果
  • 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)
  • 根据本地事务状态执行Commit或者Rollback(Commit操作发布消息,消息对消费者可见)
image.png

2.4 最大努力通知

最大努力通知的方案实现比较简单,适用于一些最终一致性要求较低的业务。

执行流程:

  • 系统 A 本地事务执行完之后,发送个消息到 MQ;
  • 这里会有个专门消费 MQ 的服务,这个服务会消费 MQ 并调用系统 B 的接口;
  • 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B, 反复 N 次,最后还是不行就放弃。

2.5 Saga

将长事务拆分为多个短事务,由 Saga 事务协调器协调,如果每个短事务都成功提交完成,那么全局事务就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。

3. 参考资料

给作者打赏,可以加首页微信,咨询作者相关问题!