聊聊分布式事务

序言

提到事务,我们的脑海里总是充满了与ACID的快乐回忆

ACID指:

  • 原子性(Atomicity)
    原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  • 一致性(Consistency)
    事务前后数据的完整性必须保持一致。
  • 隔离性(Isolation)
    事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
  • 持久性(Durability)
    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

在传统单点架构中往往一个普通的DBMS就实现的功能,用就完事了,而在分布式系统中,由于各个服务节点的通信以及服务节点下线等等问题,我们引出了CAP定理。

  • 一致性(Consistency): 指在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
  • 可用性(Availability): 在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
  • 分区容忍性(Partition tolerance): 即当节点之间无法正常通信时,就产生了分区,而分区产生后,依然能够保证服务可用,那么我们就说系统是分区容忍的。显然如果节点越多,且备份越 ,分区容忍度就越高(因为即便是其中一个或多个节点挂了,仍然有其它节点和备份可用)。

而真正痛苦的,是我们只能满足其中的二者,为什么呢?

首先,我们拥有A和B这么两个服务

其中都存放了某个值val,当业务提交到A节点更新了val,而A向B发送更新报文阻塞时

来了个读取val的请求到B,那么我们此时有两重处理方案:

  1. 先不理会A传来的更新,返回当前val给业务请求
  2. 阻塞住,等待A传来的更新执行完成后给新的val给业务请求

可以发现,方案一放弃了部分一致性(无论如何会保证最终一致性),保证了业务可用,我们称这类系统叫AP系统;而方案二舍弃了部分可用性(暂时的阻塞),保证了数据的一致,我们称这类系统叫CP系统。

而真的的生产中,我们不太可能(特殊需求的模块,如支付可能要求CP)有完全的AP或者CP,就有了BASE理论:

  • Basically Available(基本可用) 牺牲部分可用性(延迟、部分功能),保证系统的基本可用
  • Soft State(软状态) 允许事务存在中间状态
  • Eventually Consistent(最终一致性) 允许短时间的不一致,但最终保持数据一致

以上是简单说明下理论,我们接着来看看具体实现的算法。

2PC

很具要参考性的算法,参考了2pc的代表选手有:XA、Raft、Paxos、TCC

2PC指二段提交,顾名思义:

  1. 提交事务请求 (preCommit)
    • 事务询问 协调者询问事务是否可以执行
    • 执行事务 参与者执行事务(不提交)
    • 事务反馈 反馈事务执行结果
  2. 执行事务提交 (doCommit)
    • 同意提交 协调者确认所有反馈事务成功,通知参与者可以提交
    • 事务提交 接受请求,提交已执行的事务
    • 提交反馈 反馈提交结果
    • 执行完成

当其中某个节点反馈失败时,协调者控制事务中断与回滚

  • 发送回滚请求 协调者向所有参与者发起回滚请求
  • 事务回滚 参与者回滚事务
  • 回滚反馈 参与者向协调者反馈回滚结果
  • 执行完成

看上去复杂度并不高,但是问题同样很明显:

  1. 阻塞 所有参与事务的逻辑均处于阻塞状态。
  2. 单点 协调者存在单点问题,如果协调者出现故障,参与者将一直处于锁定状态。
  3. 脑裂 如果只有部分参与者接收并执行了Commit请求,会导致节点数据不一致。

3PC

出名选手不多,不过有很多开源实现,可以自己找嘛

可以看出,2PC的大部分问题都是来自与节点的不稳定,无论是协调者还是参与者都缺少容错机制。而3PC在preCommit前添加了一个canCommit的操作,步骤如下:

  1. 提交询问 (canCommit)
    • 资源询问 协调者询问事务是否准备就绪
    • 事务反馈 反馈事务执行结果,在这一步协调者和参与者都有超时机制保护
  2. 提交事务请求 (preCommit)
    • 事务询问 协调者询问事务是否可以执行
    • 执行事务 参与者执行事务(不提交)
    • 事务反馈 反馈事务执行结果,在这一步协调者和参与者都有超时机制保护
  3. 执行事务提交 (doCommit)
    • 同意提交 协调者确认所有反馈事务成功,通知参与者可以提交
    • 事务提交 接受请求,提交已执行的事务
    • 提交反馈 反馈提交结果,在这一步协调者和参与者都有超时机制保护
    • 执行完成

可以看到3PC在三次过程中均有超时机制,其策略大概如下:

  1. canCommit阶段,事务管理器等待参与者响应超时,超时则中断事务;
  2. preCommit阶段,事务管理器等待参与者影响超时,超时则回滚事务;
  3. doCommit阶段,参与者等待事务管理器的commit或者rollback指令,超时则默认自动commit;

但是我们发现,脑裂问题依旧存在,这并不是一个很好的方案,因为数据最终一致性都没法解决。

TCC

TCC指的是Try-Confirm-Cancel,其实现在业务层面,在拥有一定的代码侵入的容忍度的前提下可以较好的通过业务代码解决2PC的一致性问题。
其三个阶段如下:

  1. 预留阶段 (Try)
    • 协调者尝试让各个节点保留资源,准备执行各自的任务
    • 协调者注册确认与取消操作
    • 协调者收集各节点,针对预留节点的回复
  2. 确认阶段 (Confirm)
    • 执行预留阶段资源满足情况下,协调者确认操作,协调者向各个节点发送事务确认执行的操作
    • 各节点向协调者返回各自任务执行情况
  3. 取消阶段 (Cancel)
    • 执行预留阶段,如果某节点不满足资源,协调者通知其他节点全部取消操作,达到一致性

TCC的本质是补偿事务,也是基于二阶段提交思想,程序应用扮演了协调者的角色,很明显的,TCC需要实现Confirm与Cancel的幂等,完成业务层面上的补偿。