分布式事务解决方案知多少?

一、概述

事务是恢复和并发控制的基本单位。具有ACID(原子性、一致性、隔离性、持久性)的特性。

原子性:一个事务是一个不可分割的工作单位,事务中包括的操作要成功都成功,要失败都失败。

一致性:事务必须是使数据库从一个一致性状态变到另一个一致性状态。

隔离性:一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性:一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。

而分布式事务则是用于在分布式系统中保证不同节点之间的数据一致性。分布式事务的实现有很多种,最具有代表性的是由Oracle Tuxedo系统提出的XA分布式事务协议。

二、XA二阶段提交

XA协议中包含着两个角色:事务协调者事务参与者

第一阶段Prepare

事务协调者会首先向所有的参与者节点发送Prepare请求。

在接到Prepare请求之后,每一个参与者节点会各自执行与事务有关的数据更新,写入Undo Log和Redo Log。

如果参与者执行成功,暂时不提交事务,而是向事务协调节点返回完成消息。

如果某个事务参与者反馈失败消息,说明该节点的本地事务执行不成功,必须回滚。

第二阶段Commit

如果事务协调节点在之前所收到都是成功,那么它将会向所有事务参与者发出Commit请求。接到Commit请求之后,事务参与者节点会各自进行本地的事务提交,并释放锁资源。当本地事务完成提交后,将会向事务协调者返回完成消息。当事务协调者接收到所有事务参与者的完成反馈,整个分布式事务完成。

如果某个事务参与者反馈失败消息,事务协调节点向所有的事务参与者发送Abort请求。接收到Abort请求之后,各个事务参与者节点需要在本地进行事务的回滚操作,回滚操作依照Undo Log来进行。

存在的问题:

1)XA协议遵循强一致性,因此在事务执行过程中,各个节点都要占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知提交,参与者提交后释放资源。这样的过程有着非常明显的性能问题。

2)事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,参与者收不到提交或是回滚通知,参与者会一直处于中间状态无法完成事务。

3)在XA协议的第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就导致了节点之间数据的不一致。

三、XA三阶段提交

XA三阶段提交引入超时机制,并把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。

第一阶段CanCommit:

事务协调者会首先向所有的参与者节点发送询问请求。

在接到询问请求之后,每一个参与者节点会预估自己是否能够执行。

第二阶段PreCommit:

如果事务协调节点所收到都是成功,那么它将会向所有事务参与者发出PreCommit请求。事务参与者执行事务,但是不提交,并将结果返回给事务协调者。

如果事务协调节点在之前所收到存在失败,或者协调者等待超时,事务协调节点向所有的事务参与者发送Abort请求。

第三阶段DoCommit:

如果事务协调节点在所收到都是成功,那么它将会向所有事务参与者发出Commit请求。接到Commit请求之后,事务参与者节点会各自进行本地的事务提交,并释放锁资源。

如果事务协调节点在之前所收到存在失败,或者协调者等待超时,那么它将会向所有事务参与者发出rollback请求。

2PC和3PC的区别:
相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,但是性能问题和不一致的问题仍然没有根本解决。

由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。

四、TCC(Try-Confirm-Cancel)

TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。

Try阶段:

完成所有业务检查(一致性),预留业务资源(准隔离性)

Confirm阶段:

确认执行业务操作,不做任何业务检查, 只使用Try阶段预留的业务资源。

Cancel阶段:

取消Try阶段预留的业务资源。

五、XA和TCC对比

XA是资源层面的分布式事务,具有强一致性。在两阶段提交的整个过程中,一直会持有资源的锁。基于数据库锁实现。

TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。(第一阶段的事务就已经提交了,第二阶段执行确认操作或取消第一阶段的操作)

六、本地消息表

本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。

1)在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。

2)之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。

3)在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。

七、MQ事务消息

有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交。但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。

RocketMQ 的思路大致为:

第一阶段Prepared消息,会拿到消息的地址。

第二阶段执行本地事务

第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。

也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

八、seata

Seata(Simple Extensible Autonomous Transaction Architecture) 是 阿里巴巴开源的分布式事务中间件,以高效并且对业务 0 侵入的方式,解决微服务场景下面临的分布式事务问题。

Seata AT模式是基于XA事务演进而来的一个分布式事务中间件,需要数据库支持,Mysql5.6以上版本支持XA协议

第一阶段

Seata 的 JDBC 数据源代理通过对业务 SQL 的解析,把业务数据在更新前后的数据镜像组织成回滚日志,利用本地事务 的 ACID 特性,将业务数据的更新和回滚日志的写入在同一个本地事务中提交,确保任何提交的业务数据的更新一定有相应的回滚日志存在。

有了回滚日志之后,可以在第一阶段释放对资源的锁定,降低了锁范围,提高效率。即使第二阶段发生异常需要回滚,只需找对undolog中对应数据并反解析成sql来达到回滚目的。

同时Seata通过代理数据源将业务sql的执行解析成undolog来与业务数据的更新同时入库,达到了对业务无侵入的效果。

第二阶段
如果决议是全局提交,此时分支事务此时已经完成提交,不需要同步协调处理(只需要异步清理回滚日志),第二阶段可以非常快速地完成。
如果决议是全局回滚,RM 收到协调器发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。

要点

1)全局事务开始使用 @GlobalTransactional标识 。

2)每个本地事务方案仍然使用@Transactional标识。

3)每个数据都需要创建undo_log表,此表是seata保证本地事务一致性的关键。

4)需要事务协调器的存在,用于维护全局事务的运行状态,接收TM指令发起全局事务的提交与回滚,负责与RM通信协调各各分支事务的提交或回滚。

发布了43 篇原创文章 · 获赞 0 · 访问量 3891

猜你喜欢

转载自blog.csdn.net/zhangdx001/article/details/105322703