什么是事务
数据库管理系统中的一个基本概念,是指一组操作要么全部成功,要么全部失败。包含ACID四个特性:
- 原子性(Atomicity):整个事务是不可分割的,事务的任何环节失败则整个事务失败并且回滚至事务开始之前的状态。
- 一致性(Consistency):数据始终满足一致性,满足业务规则和约束
○ 一致性确保事务将数据库从一个一致的状态转换到另一个一致的状态。在事务开始之前和事务结束之后,数据库的状态必须是有效的,并且满足所有的业务规则和约束条件。 - 隔离性(Isolation):
○ 隔离性确保并发执行的事务之间互不影响。不同的隔离级别决定了事务间共享数据的程度,如
读未提交(Read Uncommitted)、
读提交(Read Committed)、
可重复读(Repeatable Read)、
可序列化(Serializable)。 - 持久性(Durability):一旦事务提交,其结果永久保存,即使系统崩溃。
本地事务
本地事务(本地ACID事务)中,底层是由数据库提供支持的,在一次数据库连接中,我们可以使用编程式事务进行事务的开启、提交和回滚。在Spring中,如果配置了事务管理器,还可以通过声明式注解进行事务操作。
那么数据库是如何实现事务的呢?大体上可以理解为通过日志管理和两阶段提交协议来实现的,通过日志管理中的redoLog和undoLog来记录变更前数据和变更后数据,再结合两阶段提交,在两阶段提交的准备阶段,将涉及变更的数据记录到redoLog中,在提交阶段,将redoLog中的数据变更到数据库,如果某个表更新失败,则通过undoLog全部回滚。
分布式事务
从本地事务可以看出,在本地事务的整个过程中,数据库在涉及多张表的事务操作中扮演了全局事务的协调者的角色。由于分布式系统中,事务的参与者已经不再局限于一次数据库连接中,那么要想满足事务的特性,就要解决事务中各个参与者之间的协调问题。
分布式事务解决方案
分阶段提交
通过全局协调者对整个事务中所有的参与者进行协调,如果所有参与者都准备就绪,则进行提交。因此这个 过程是分阶段的。包含预提交阶段和提交阶段。由于在准备阶段,事务协调者需要与所有的事务参与者通信,以确定整体事务是否可提交。
两阶段提交 2PC
○ 阻塞问题:如果协调者崩溃,参与者会一直等待,可能会导致长时间的阻塞。
○ 单点故障:协调者故障,整个分布式事务不可用
需要考虑事务协调者的高可用
三阶段提交 3PC
相较于2PC,加入了预提交阶段(“询问\投票”阶段),增加了中间状态
- 减少阻塞时间:通过增加准备阶段,参与者在等待协调者指令时的阻塞时间减少,降低了系统的整体锁定时间,提高了系统的并发能力。
- 更高的容错性:在协调者故障时,参与者可以根据预提交阶段的状态,自行决定是否继续等待或回滚,从而减少系统的不确定性和阻塞。
TCC补偿
TCC(try-confirm-cancel)TCC模式允许开发人员在每个阶段都定义自己的业务逻辑和补偿操作,以适应不同的业务需求。
但是,TCC模式要求开发人员在每个阶段都显式定义业务逻辑和补偿操作,增加了系统的复杂性和开发成本,需要设计和维护每个阶段的逻辑,并确保它们之间的一致性。
MQ
本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。
- 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
- 之后将本地消息表中的消息转发到消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
- 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。
MQ可靠投递的方案
○ 本地消息表(数据的保存和业务操作在一个本地事务中,保证了可靠投递,然后通过定时扫描消息表数据向mq进行投递)
○ 事务消息 RocketMq半消息
为什么业务操作和 MQ消息 不能放在一个事务中
本地消息表和业务操作之所以可以在一个本地事务中,
是因为它们使用一个数据库连接,DMS保证了他们的原子性,
业务操作和mq之间属于跨系统(也属于分布式范畴),虽然在一个
@Transactional方法中,但它们不在一个事务中,无法保证原子性。
比如saveOrder()和sendMq(),如果sendMq响应超时,saveOrder
回滚,但sendMq可能已经执行成功,只不过响应超时而已,这就不一致了。
不同方案的区别
- 2PC :保证操作的原子性,达到数据的一致性;
柔性事务( TCC):直接针对数据,保证数据一致性;
2PC由于需要等待确认所有参与的服务的事务状态,在等待未超时前,各个服务中的数据相当于锁住,高并发场景不适用;
而TCC模式中并不要求强一致,当事务管理器发现某环节异常时调用cancel进行补偿,达到最终一致。这便是柔性事务。
- MQ实现事务的思想: 使用事务的目的是程序在异常情况下确保数据的一致性,
能不能通过某种方式保证链路不异常,一定执行成功呢?如果消息被可靠投递,又被消费者可靠消费,保证调用链路的可靠执行也能保证数据的最终一致性。MQ通过异步和重试,可以实现最终一致性。
2PC和TCC保证一致性的思想是处理异常时,通过回滚,比如对数据进行反向补偿
MQ的思路则是如何保证每个环节都正常执行,如果程序正确且执行完成,数据一定是一致的。
如何保证每个环节都正确执行呢?
首先要保证消息的可靠投递(投递成功才有后续的事件触发),
其次是MQ的高可用,比如消息数据持久化和多节点部署;
最后是重试(程序执行异常,要么是网络原因,要么是程序本身,网络原因完全可以通过重试解决,bug则需要解决),超出重试阈值进入死信队列由人工兜底。
分布式事务中间件
Seata
hmily
DTM