likes
comments
collection
share

Mysql分库分表相关问题之分布式事务

作者站长头像
站长
· 阅读数 21

前言

什么是分布式事务?

我们先回顾一下,事务的四大特性:

  • A(Atomicity)——原子性 事务里面所有的事务都是一个整体,完成一个事务里面的事件要么全部成功要么全部失败。

  • C(consistency)——一致性 事务执行之前读取的事务一定是更新前的数据,执行之后一定是更新后的数据,不允许在事务进行中读取到过程中的数据,也就是说事务中间状态或者说完成部分事务是不可被用户读取到的,因为数据在事务执行过程中是不一致的。

  • I(isolation)——隔离性 事务与事务在执行的过程中不能串,因为事务提交后才具有持久性稳定了,串的话会带来不一致的风险。

  • D(durability)——持久性 事务执行成功之后数据将稳定不受其他原因破坏。

单体库中事务比较容易做到ACID,但是分布式库中比较难做到,因为事务里面的事件是在不同的系统中,系统之间的联动是比较复杂和消耗的。我们来看看分布式库中事务的四大特性会遇到啥问题:

  • 分布式的原子性保证 可以做到,要么全部成功要么全部失败。

  • 分布式的一致性 需要让事务之前前和执行后的数据都是一致的,中间的数据用户读不到。可以用持久日志的形式,不同数据库的子事件完成后使用日志记录,等所有的子事件都完成后再进行提交入库,需要看各系统的子事务是否都完成了,都完成了再进行入库,所有子系统都入库了事务就结束了。

    可以看到引入了日志处理;子系统之间需要交互;最后的提交阶段也会发生不可预测的失败,失败了还需要进行重试等;最后的提交子系统之间做不到完全同时,所以还是可能读到中间的数据。

  • 分布式的隔离性

    • 读已提交,上面的”分布式的一致性“大致可以做到。
    • 可重复读,也可以做到可以参考mysql的mvcc自己设计一个数据库中间件——分配trx_id、实现undolog和readview那一套。
    • 不幻读也可以参考mysql的next-key根据范围去不同水平分表中进行锁定(一般不会这么做,一个是要锁定所有分表等所有都锁定了才能保证,如果网络波动存在较大空档无法保证不幻读;一个是next-key是依赖索引的,如果没有索引也保证不了);可以在自己设计的中间件上使用分布式锁代替mysql的next-key。
  • 分布式的持久性 持久到磁盘中,肯定可以实现。

下面我们继续看看有哪些解决分布式事务的思想和方法。

CAP

对于一个分布式计算系统来说,不可能同时满足以下三个特点:

  • 一致性(Consistency):等同于所有节点访问同一份最新的数据副本。
  • 可用性(Availavlity):每次请求都能获取非常不错的响应——但是不能保证获取的数据为最新的数据。
  • 分区容错性(Partition tolerance):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。

根据定理,分布式系统只能满足三项中的两项而不可能满足全部三项。允许一个节点更新状态会导致数据不一致,即丧失了C性质。如果为了保证数据一致性,将分区一侧的节点设置为不可用,那么又丧失了A性质。除非两个节点可以互相通信,才能保证C又保证A,这个又会丧失P性质。

  • 放弃P:分布式存在网络通讯,满足不了C,所以P是一定要满足的。
  • 满足AP:允许一定时间内发生分区,C满足不了。
  • 满足CP:那就只能让用户不访问某些数据,失去了可用性。

不可用是不能容忍的,所以一般会选择AP,也就是最终一致性。

X/Open DTP

X/Open DTP(X/Open Distributed Transaction Processing Reference Model 分布式事务处理模型)。

Mysql分库分表相关问题之分布式事务

组件包括:

  • 应用程序(AP)
  • 事务管理器(TM)
  • 资源管理器(RM)

X/Open XA

在计算技术上,XA规范是开放群组关于分布式处理(DTP)的规范。规范描述了全局的事务管理器与局部的资源管理器之间的接口。XA规范的目的是允许多个资源(如数据库,应用服务器,消息队列,等等)在同一事务中访问,这样可以使ACID属性跨越应用程序而保持有效。XA使用两阶段提交来保证所有资源同时提交或回滚任何特定的事务。

XA规范描述了资源管理器要支持事务性访问所必需做的事情。遵守该规范的资源管理器被称为XA compliant。

2PC-二阶段提交

二阶段提交(Two-phase Commit),为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法。当一个事务跨多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来掌握所有节点(称为参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交。

因此,二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈觉得各参与者是否要提交操作还是终止操作。

前提

  • 该分布式系统中,存在一个节点作为协调者,其他节点作为参与者。且节点之间可以进行网络通信。
  • 所有节点都将采用预写式日志,且日志被写入后即保存在可靠的存储设备上,即使节点损坏也不会导致日志数据的消失。
  • 所有节点不会永久损坏,即使损坏仍然可以恢复。

基本算法

第一阶段(提交请求阶段)

  1. 协调者节点向所有参与者节点询问是否可以执行提交操作,并开始等待各参与者节点的响应。
  2. 参与者执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。
  3. 各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个“同意”消息;如果参与者的事务操作执行失败,则返回一个“终止”消息。

第二阶段(提交执行阶段) 成功(所有参与者都发送“同意”):

  1. 协调者节点向所有节点发出“正式提交”的请求。
  2. 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
  3. 参与者节点向协调者节点发送“完成”消息。
  4. 协调节点收到所有参与者节点反馈的“完成”消息后,完成事务。

失败(只要有一个参与者发送“终止”):

  1. 协调者向所有参与者节点发送“回滚操作”的请求。
  2. 参与者利用之前写入的Undo信息执行回滚,并释放整个事务期间内占用的资源。
  3. 参与者节点向协调者节点发送“回滚完成”消息。
  4. 协调者节点收到所有参与者节点反馈的“回滚完成”消息后,取消事务。
    协调者                                              参与者
                              QUERY TO COMMIT
                -------------------------------->
                              VOTE YES/NO           prepare*/abort*
                <-------------------------------
commit*/abort*                COMMIT/ROLLBACK
                -------------------------------->
                              ACKNOWLEDGMENT        commit*/abort*
                <--------------------------------  
end

"*" 所标记的操作意味着此类操作必须记录在稳固存储上.[1]

缺点

可以看到第二阶段需要等待所有参与者都返回消息,如果网络延迟呢?如果参与者出现故障呢?参与者持有的资源一直得不到释放。如果一个参与者一直没返回,协调者会超时后重新发送commit命令因为其他节点已经提交了事务不可能再回滚,只能利用重试实现最终一致。

协调者也可能出现故障,需要考虑高可用的情况。同时:

  1. 第一阶段发送故障,已经接收到部分(大于等于0小于全部)AP的消息,应该考虑记录当前进行事务的相关日志(持久化的),这样再选举出新的协调者之后能继续接收到剩余AP的消息拿到事务中AP的消息。

  2. 第二阶段发送故障,之前就已经给部分(大于等于0小于全部)参与者发送了消息,应该考虑记录当前进行事务的相关日志(持久化的),这样再选举出新的协调者之后能继续对参与者发送消息,让参与者提交或者回滚。(这里的继续发送也是需要依赖第一阶段的日志,这样才知道有那些参与者)

注意,上面提到的选举恢复是在超时时间内进行的,参与者应该也有超时时间。

3PC-三阶段提交

就是增加了对参与者的状态的确认,尽早的发现问题,如果发现一个参与者不可用就早点结束流程。

TCC

Mysql分库分表相关问题之分布式事务 TCC即Try-Confirm-Cancel,流程上和2PC类似实现上不一样,2PC依赖数据库进行提交回滚,这样的缺点是在一致性的时候会持有资源比如锁,一直不释放,那么我们其实可以在业务层去做提交和回滚。

比如用户在电商网站购买商品1000元,使用余额支付800元,使用红包支付200元。

Try操作

  • tryX 下单系统创建待支付订单
  • tryY 冻结账户红包 200 元
  • tryZ 冻结资金账户 800 元

Confirm操作

  • confirmX 订单更新为支付成功
  • confirmY 扣减账户红包 200 元
  • confirmZ 扣减资金账户 800 元

Cancel操作

  • cancelX 订单处理异常,资金红包退回,订单支付失败
  • cancelY 冻结红包失败,账户余额退回,订单支付失败
  • cancelZ 冻结余额失败,账户红包退回,订单支付失败

冻结红包/余额可以用数据库减操作+日志(记录订单号)实现。这里需要保留订单号,比如订单处理异常了需要回滚红包和余额就需要根据订单号去回滚。

事务消息

就是利用消息队列比如rocketmq。

Mysql分库分表相关问题之分布式事务

如图所示:

  • A处理任务成功之后需要发送“提交”消息到中间件,表示之前发布的消息可以被消费了,这里A系统需要提供一个任务状态查询的接口给mq,因为会存在A系统一直没有发送“提交”消息的情况。
  • A系统完成了之后才会投递消息到B系统,但是B系统如果没有响应消息队列那么就会重复投递(B系统需要保证幂等性)。

参考

CAP定理

X/Open 分布式事务处理模型

X/Open XA

二阶段提交

三阶段提交

CAP、Base理论和分布式事务——2PC、3PC 和 TCC