分布式事务.md

事务

事务是对一组操作的集合,具有以下特性 (AICD):

  • 原子性 (Atomicity) :要么全做,要么不做

  • 一致性 (Consistency) :对数据的一组特定陈述必须始终成立,这是应用程序的属性,比如账单数据库中规定必须借贷相抵,而应用操作事务中每个操作都没有破坏这个规定,则说明该陈述对数据始终成立,这不取决于数据库。

  • 隔离性 (Isolation) :同时执行的事务时相互隔离的

    四个隔离级别:

    • 读未提交 (Read Uncommitted):可以读取未提交的数据
    • 读已提交 (Read Committed):可能看到已提交的修改
    • 可重复读 (Repeatable Read):解决不可重复读的问题 (MySQL 默认)
    • 序列化 (Serializable):最高隔离

    各个隔离级别的异常:

    • RU - 脏读,因为可以看到其他事务未提交的数据,因此可能读到的数据不会最终写入数据库 (另一个事务可能会回滚,而该事务不知道)
    • RC - 不可重复读,同个事务内同一条 SQL 语句可能查询出不同的结果,因为可能执行时其他事务提交了修改
    • RR - 幻读,某一次操作得到的数据表征的数据无法支撑后续的业务,例如查询,发现不存在后准备插入,而此时可能因为数据已存在而插入失败 。(这个级别在事务开始时类似于创造了一个快照,因此多次查询语句会返回同样的结果,但可能无法支撑后续业务)
  • 持久性 (Durability) :当事务提交后,数据不会再丢失,这是数据库对事务操作的一个承诺性特征 。

分布式事务

一组对数据库的操作涉及多个微服务或者多个数据库,管理其中每个数据库的本地事务,使得一整组操作作为一个整体可以看作一个事务,具有 AICD 特性 。

有几种实现方法:

2PC (Two-phase commit protocol)

二阶段提交是一种强一致性设计,引入了一个事务协调者来协调管理,一般由一个结点来实现协调管理,分为两个阶段:

  • 准备阶段:协调者向各个需要操作的结点发送准备命令,各结点收到命令后开始进行本地事务处理,但不提交(也不回滚),操作完成后会发送操作结果给协调者,然后挂起。
  • 投票阶段:各结点将结果发回给协调者看作是否最终提交的投票,采用一票否决,当有一个结点投票回滚,则协调者会向各结点发送回滚命令,所有操作都会回滚,如果所有节点都投票提交,则协调者会向各结点发送提交命令,各结点会讲自己的事务最终提交

细节:

  • 第一阶段协调者存在超时机制,一个节点超时没回复则看作其投票回滚,直接进行回滚
  • 第二阶段因为操作已经执行了,协调者无法超时,只能不断重试,比如投票决定要提交,协调者发送给 A , A 提交了,协调者发提交指令给 B 时,B 没有回应,此时作为协调者只能不断重试,否则分布式事务的原子性将无法保证,因为存在只做一半的情况。
  • 协调者存在单点故障,并且可能会在各个阶段故障,因此具体到各个实现该协议的库都需要实现一个机制解决该问题,比如投票决定协调者,协调者宕机后重新投票等机制。

性质:

  • 强一致性
  • 存在单点故障,极端情况存在不一致的问题,引入定时扫描补偿机制后基本可以做到强一致性
  • 效率较低(但一般使用该协议就是为了强一致性,效率低不会作为不使用他的特点)

3PC (Three-phase commit protocol)

与 2PC 协议相比,在参与者中引入超时机制,并且新增了一个阶段,分为三个阶段:

  • 准备阶段 (canCommit):协调者询问各参与者是否有条件接受事务(此时什么都不做)
  • 预提交阶段 (ProCommiit):相当于 2PC 的准备阶段
  • 提交阶段 (DoCommit):相当于 2PC 的投票阶段

准备阶段中 协调者具有超时机制

预提交阶段中 协调者具有超时机制

提交阶段中,参与者也有超时机制,超时默认执行提交操作,因为经过准备阶段,所有节点都对当前事务处于乐观的态度(第一阶段时各自负载没问题,数据库交互也没问题,否则也不会进入第二阶段),因此有大概率是可以提交的 。

特性:

  • 一致性较弱,因为第三阶段超时后可能有的节点超时执行提交后才收到协调者发来的回滚命令,也需要定时扫描补偿
  • 避免 2PC 中第二阶段协调者的死等
  • 引入 第一阶段 让各个微服务状态统一一下 。
  • 当协调者单点故障后,新选出来的协调者可以执行提交指令,因为参与者超时后默认提交
  • 性能更下降,因为添加了一个阶段
  • 因为其性能不如 2PC,同时一致性也比 2PC 弱,实用性较差,因此目前只是一种理论上的协议,目前具体实现很少。

TCC (Try - Confirm - Cancel )

这是业务层面的分布式事务,前两个是数据库层面的,相当于更抽象了一层 。

  • Try 预留,资源的预留和锁定
  • Confirm 确认,真正执行操作
  • Cannel 撤销,把预留阶段的动作撤销

image20210806111755206.png

  • 对业务侵入性较大,需要手动写具体业务对应的 try,confirm 和 cancel 的逻辑。

本地消息表

主要是为了实现补偿,将业务执行的各个操作作为数据存起来,并标记是否成功,然后定时读取本地消息表,筛选出未成功的操作在调用对应服务 。

使用重试保证操作幂等,重试次数超过阈值后需要报警人工处理。

保证了最终一致性,允许暂时不一致性,暂时的不一致等到下次扫描后就会处理。

消息事务

有的消息中间件也有支持消息事务,比如 RocketMQ,一般操作如下:

image20210806112458848.png

最大努力通知

这是一种思维,表示尽最大努力保证,但允许异常情况的出错,从广义上说,本地事务表和消息事务就属于最大努力思维,本地事务表中重试多次后采用直接报警人工处理,而消息事务中如果没消费的事务会不断重试,最后进入死信队列。