分布式事务
分布式事务顾名思义就是要在分布式系统中实现事务,它其实是由多个本地事务组合而成。
对于分布式事务而言几乎满足不了 ACID,其实对于单机事务而言大部分情况下也没有满足 ACID,不然怎么会有四种隔离级别呢?所以更别说分布在不同数据库或者不同应用上的分布式事务了
2PC
二阶段提交(Two-Phase Commit),是一个非常经典的强一致、中心化的原子提交协议。目前,绝大多数关系型数据库都采用二阶段提交协议来完成分布式事务处理(例如mysql的XA协议)。因此二阶段提交协议也被广泛运用到分布式系统中。
顾名思义,算法流程就是分为两个阶段提交某一操作,其分为准备阶段、提交阶段。为了更好描述算法过程,为此定义了两种角色:事务管理者(TM),资源管理者(RM)
阶段一:准备阶段
在准备阶段,全局事务管理器向每个资源管理器发送准备消息,用于确认本地事务操作成功与否。
阶段二:提交阶段
在提交阶段,若全局事务管理器收到了所有资源管理器回复的成功消息,则向每个资源管理器发送提交消息,否则发送回滚消息。资源管理器根据接收到的消息对本地事务进行提交或回滚。
- 返回y 的情况(提交事务)
- 返回n 的情况(回滚事务)
2PC 优缺点分析:
-
优点:原理简单、容易实现。并且基本只在 db 做文章,所以对代码的入侵小
-
缺点:
- 同步阻塞-每个参与者都需要等待其他参与者完成后,才能继续下一阶段,也就是说事务操作逻辑都是处于阻塞状态,极大限制了分布式系统性能
- 单点问题-事务管理者在2PC中,太过重要,当TM宕机,整个集群将不可用。更可怕的是,TM在第二阶段之前宕机,那么所有参与者将一直锁定准备阶段的事务资源
- 太过保守 任何一个节点故障,都会导致整个事务协调失败,换句话说没有完善的容错机制
XA 规范
既然说到 2PC 了那么也简单的提一下 XA 规范,XA 规范是基于两阶段提交的,它实现了两阶段提交协议
XA 在 MySQL 中是如何操作的(目前只有 InnoDB 支持)
简单的说就是要先定义一个全局唯一的 XID,然后告知每个事务分支要进行的操作
可以看到图中执行了两个操作,分别是改名字和插入日志,等于先注册下要做的事情,通过XA START XID 和 XA END
来包裹要执行的 SQL
对应2PC 的一阶段,其实就是这个PREPARE
命令
然后根据准备的情况来选择执行提交事务命令还是回滚事务命令
MySQL 的XA 基本上就是这么个流程
3PC
三段提交(3PC)是对两段提交(2PC)的一种升级优化,3PC在2PC的第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前,各参与者节点的状态都一致。同时在协调者和参与者中都引入超时机制,当参与者各种原因未收到协调者的commit请求后,会对本地事务进行commit,不会一直阻塞等待,解决了2PC的单点故障问题
3PC 的三个阶段分别是CanCommit、PreCommit、DoCommit
CanCommit:TM向所有RM发送CanCommit命令,询问是否可以执行事务提交操作。如果全部响应YES则进入下一个阶段。
PreCommit:TM向所有RM发送PreCommit命令,询问是否可以进行事务的预提交操作,RM接收到PreCommit请求后,如RM成功的执行了事务操作,则返回Yes响应,进入最终commit阶段。一旦RM中有向TM发送了No响应,或因网络造成超时,TM没有接到RM的响应,TM向所有RM发送abort请求,RM接受abort命令执行事务的中断。
DoCommit: 在前两个阶段中所有RM的响应反馈均是YES后,TM向RM发送DoCommit命令正式提交事务,如TM没有接收到RM发送的ACK响应,会向所有RM发送abort请求命令,执行事务的中断
3PC 多了一个阶段其实就是在执行事务之前来确认RM是否正常,防止个别RM不正常的情况下,其他RM都执行了事务,锁定资源。出发点是好的,但是绝大部分情况下肯定是正常的,所以每次都多了一个交互阶段就很不划算
从个人的观点出发,我觉得 3PC 的引入并没什么实际突破,而且性能更差了。。。
以至于我搜罗了好久,压根没找到实际的3PC 的落地示例,反倒是2PC,确实有不少的落地示例
TCC
不知道大家注意到没,不管是 2PC 还是 3PC 都是依赖于数据库的事务提交和回滚,但是我们在实际开发的过程中,还有一些其他的资源,比如上传一张照片或者发送某个消息等等。所以说事务的提交和回滚就得提升到业务层面而不是数据库层面了,而 TCC 就是一种业务层面或者是应用层的两阶段提交。
TCC 分为指代 Try、Confirm、Cancel ,也就是业务层面需要写对应的三个方法,主要用于跨数据库、跨服务的业务操作的数据一致性问题
Try 指的是预留,即资源的预留和锁定,注意是预留。 Confirm 指的是确认操作,这一步其实就是真正的执行了。 Cancel 指的是撤销操作,可以理解为把预留阶段的动作撤销了
其实从思想上看和 2PC 差不多,都是先试探性的执行,如果都可以那就真正的执行,如果不行就回滚。
比如说一个事务要执行A、B三个操作,那么先对两个操作执行预留动作。如果都预留成功了那么就执行确认操作,如果有一个预留失败那就都执行撤销动作
可以看到流程还是很简单的,难点在于业务上的定义,对于每一个操作你都需要定义三个动作分别对应Try - Confirm - Cancel。因此 TCC 对业务的侵入较大和业务紧耦合,需要根据特定的场景和业务逻辑来设计相应的操作(代码工作量大)
虽说对业务有侵入,但是 TCC 没有资源的阻塞,每一个方法都是直接提交事务的,如果出错是通过业务层面的 Cancel 来进行补偿,所以也称补偿性事务方法。
这里有人说那要是所有人 Try 都成功了,都执行 Comfirm 了,但是个别 Confirm 失败了怎么办?
这时候只能是不停地重试调失败了的 Confirm 直到成功为止,如果真的不行只能记录下来,到时候人工介入了