持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情
分布式事务
ACID:原子性、一致性、隔离性、持久性
- 原子性:一个事务内所有操作要么全部执行,要么都不执行。
- 一致性:数据满足完整性约束,也就是不会存在中间状态的数据,数据将从一种状态转变为预期的状态。例:A账户有400元,B账户有100元,A账户向B账户转账200元,此时A剩余200元,B剩余300元。不存在B加钱,A不扣钱的中间态。
- 隔离性:指多个事务并发执行的时候不会互相干扰,即一个事务内的数据对其他事务来说是隔离的。
- 持久性:一个事务操作完成后数据就会被永远保存下来,后续的操作不会对事务的结果产生影响。
1.2PC(两阶段提交)
-
强一致性设计,通过引入一个事务协调者的角色来协调管理各参与者的提交和回滚,二阶段指准备和提交两个阶段。
-
准备阶段:协调者会给参与者发送准备命令(准备命令即除了提交事务之外啥事都做完了)。
-
提交阶段:同步等待所有资源的响应之后就进入第二阶段(提交阶段可能是提交事务,也可能是回滚事务)。
-
-
存在问题:
-
所有事务参与者都在等待其他参与者的响应时都处于同步阻塞状态,无法进行其他操作。
-
第二阶段执行的是事务回滚,那么就不断重试,直到所有参与者都回滚,不然第一阶段准备成功的参与者会一直阻塞。
-
第二阶段执行的是事务提交,有可能部分参与者事务提交成功了,那就只有一条路往前冲,不断重试,直到提交成功,最终真不行需要人工介入处理。
-
2.TCC(补偿事务)
针对每个操作都要注册一个与之对应的确认和撤销操作,具体分为三个阶段:
- Try阶段主要是对业务系统做检测及资源预留。
- Confirm阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行Confirm阶段时,默认Confirm是不会出错的。即:try成功Confirm一定成功。
- Cancel阶段主要是针对业务执行错误,需要回滚的状态下的业务取消,预留资源释放。
例:A向B转账流程:①在try阶段将A和B的资金冻结起来。②confirm阶段执行远程调用进行转账操作,转账成功进行解冻。③如果第二步执行成功,那么转账成功,如果第二步失败,需要远程调用冻结接口对应的解冻方法(cancel)。
优点:跟2PC对比起来,实现以及流程相对简单一些,但数据的一致性要比2PC差一些。
缺点:缺点比较明显,在步骤2、3都有可能失败。TCC事务属于一种补偿方式,所需需要开发人员实现的时候编写很多补偿代码。
3.本地消息表
本地消息表与业务数据表同处于一个数据库中,这样就能利用本地事务来保证这两个表操作满足事务特性,并使用消息队列保证最终一致性。
- 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能够保证这个消息被写入本地消息表中。
- 将本地消息表中的消息转发到消息队列中,如果转发成功则将本地消息从本地消息表中删除,否则继续重新转发。
- 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。
优点:一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点:消息表会耦合到业务系统中,如果没有封装好的解决方案,处理起来会有很多杂活。
4.MQ事务消息
有些第三方MQ支持事务消息,比如RocketMQ,他们支持事务消息的方式也类似2PC,但大部分常用的MQ都不支持事务消息,如RabbitMQ和Kafka都不支持。以RocketMQ为例:
- 第一阶段Prepared消息,会拿到消息的地址。第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。
- 业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了MQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所有生产方需要实现一个check接口,RocketMQ会根据发送方设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。
优点: 实现了最终一致性,不需要依赖本地数据库事务。
缺点:实现难度大,主流MQ都不支持,RocketMQ事务消息部分代码也未开源。