目录
分布式事务是指在分布式系统中,涉及多个节点(如数据库、服务)的一个操作序列,这些操作作为一个整体需要满足事务的ACID特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。在分布式系统中实现事务的管理比单一系统更为复杂,主要因为网络通信的不可靠性、节点间的异步操作及潜在的硬件故障等因素。
事务分类:本地事务、传统分布式事务、Seata 分布式事务。
一、本地事务
大部分传统公司的业务还是建立在单体应用的集群上,说白了,就是一种伪分布式的应用,事务在应用上下文中传播,就是在同一个 JVM 跑了各种 CRUD 操作。我们只要在服务入口处配置上 @Transactional 标签就可以开启事务管理,无外乎在配置下事务的传播性。这只是一个在本地控制的 Spring Transaction 而已。在使用 Spring 声明式事务时要注意事务失效的各种场景:
- 类未被 Spring 管理:如果包含事务的类未被 Spring 管理,那 Spring 就无法对其进行事务控制
- 方法权限不足:被@Transactional注解的方法必须是public的,因为Spring基于代理实现事务管理的,默认情况下它只能代理public方法
- 方法别final或static修饰:final方法不能被Spring AOP代理覆盖,因此事务增强无法应用于final。static方法属于类方法,不受实例影响,因此Spring的基于代理的事务管理也对static方法无效
- 异常处理不当:如果事务方法内部捕获了应该触发事务回滚的异常,并没有再次抛出,那Spring是无法感知到异常,从而事务失效
- 事务的传播行为设置不当:如果事务方法之间的调用关系涉及到不同传播行为的配置,如REQUIRED、REQUIRES_NEW等,配置错误可能会导致事务边界不符合预期。
- 未指定回滚的异常类型:@Transactional注解中的rollbackFor属性未指定或指定错误,导致在特定异常发生时事务失效
- 数据库本身的限制:数据库本身不支持事务,或者事务隔离级别、锁机制等设置不合理,导致事务失效
在复杂的分布式事务,终将回归到本地事务的原点上。
那如果不是单体应用,而是经过拆分之后的微服务,跨服务的事务该如何保证呢?下面看下传统的分布式事务。
二、传统的分布式事务
说起 XA 协议,可能没听说过,但提到 2PC 和 3PC 就懂了。这是大多数银行系统上了年纪的老系统喜欢用的分布式事务解决方案,这套方案依赖底层数据库的支持,DB 这层首先得先实现XA协议。比如 Oracle XA 和 MySQL InnoDB,都是支持 XA 协议的。可以把XA理解为一个强一致性的中心化原子提交协议。
原子性的概念比较好理解,就是把一系列操作合并成一个整体,要么都执行,要么都不执行。而所谓的 2PC 就更好理解了,它就是把一个事务分成两步来提交,第一步做准备动作,第二部做提交/回滚动作,这两步之间的协调是交由一个中心化的 Coordinator 来管理,保证多步操作的原子性。
第一步(Prepare):Coordinator 向各个分布式事务参与者下达了 Prepare 指令,各个事务分别将 SQL 语句在数据库执行但不提交,并且将就绪状态上报给 Coornator。
第二步(Commit/Rollback):如果所有节点都已就绪,那么 Coordinator 就下达 Commit 指令,各个参与者提交本地事务;如果有任何一个节点不能就绪,Coordinator 则下达 Rollback 指令进行本地回滚。
这种分布式事务有一个天然缺陷,导致 XA 特别不适合用在互联网的高并发场景里。因为每个本地事务在 Prepare 阶段,都要一直占用一个数据库连接资源,这个资源一直到二阶段Commit 或 Rollback 之后才会被释放。
但互联网场景的特性是高并发,因为并发量特别高,所以每个事务必须尽快释放掉所有的数据库连接资源。事务执行时间越短越好,这样才能让别的事务尽快被执行。
那有什么方法技能降低事务执行时间,又能保证一致性呢?有时候面对复杂问题,我们只需要回归到问题的本源,这个问题就迎刃而解了。这个就是前面提到的本地事务。
三、Seata分布式事务
Seata 是一款开源的分布式事务解决方案,最初由蚂蚁金服和阿里巴巴在2019年1月共同开源,旨在为微服务架构提供高性能和易于使用的分布式事务服务。Seata支持四种主要的事务模式:AT(Automatic Transaction)、TCC(Try-Confirm-Cancel)、Saga和XA,每种模式适用于不同的业务场景,以满足多样化的分布式事务需求。
其实面对任何一个复杂问题,都可以通过大事化小小事化了的思想,把问题一步步拆解成一个个小问题来解决掉,这个就是 Seata 分布式事务解决方法。
分布式事务所要解决的问题,不就是跨 DB 的事务一致性吗?那我们就把这个场景做一层抽象,把分布式事务的业务模型简化成"全局事务"和"分支事务",想在确保两者的一致性怎么做?很简单,分支事务全成功=全局事务成功,但凡一个分支事务失败,那么其余分支事务全部回滚。
这就是各个分布式事务框架都在采用的基础模型,我们在这个简化后的业务模型上再把问题做一番拆解,把目光聚焦在分支事务本质上。从单个事务的角度来看,不就是前面提到的本地事务吗。完成本地事务的步骤,执行SQL——> Commit/Rollback。
Seata 方案的破局之道就是这个长事务,XA 长事务的本质是为了保证一致性,但前面说过,长事务在超高并发的互联网场景中不受待见,所以要将长事务变短。
在 CAP 定理中,长事务是典型的偏向 CP 侧的强一致性方案。如果我们想打破长事务的束缚,就必须将强一致性改为最终一致性方案,也就是偏向 AP 侧的方案。最终一致性是一种兼顾一致性和可用性的策略,它允许应用产生短期的不一致性,然后再未来的某个时间达成最终一致性的状态,通过牺牲强一致性来获取高可用性。
注意:数据的不一致性在事务提交前的瞬间,不同服务或数据库间的数据可能暂时不一致。如果此时有外部系统或后续操作依赖于这些数据,可能会读取到不准确的信息,导致业务逻辑错误或数据混乱。
3.1 Seata AT模式
Seata 框架有三个重要的角色,TC、TM 和 RM。
TC:全称 Transaction Coordinator,它就是 Seata server。TC 扮演了一个中心化的事务协调者的角色,负责协调全局事务的提交和回滚,并维护全局和分支事务的状态。
TM:全称 Transaction Manager,它是事务管理器,主要作用是发起一个全局事务,对全局事务的提交和回滚做出决议。在 AT 方案中,TM 通常是由发起全局事务的那个微服务所扮演的。
RM:全称 Resource Manager,它是资源管理器,向 TC 注册分支事务并上报事务状态,同时负责对当前分支事务进行提交和回滚。每一个分支事务都是全局事务的参与者,这些分支事务的所属应用扮演了RM的角色。
Seata AT 的业务流程分为两个阶段来执行。
- 一阶段:执行核心业务逻辑(CRUD操作)。Seata 会根据 DB 操作自动生成相应的回滚日志,并将回滚日志添加到RM对应的undo_log表中。执行业务代码和添加回滚日志这两部都是在一个本地事务中提交的。
- 二阶段:如果全局事务的最终决议是Commit,则更新分支事务状态并清空回滚日志;如果最终决议是Rollback,则根据undo_log中的回滚日志进行rollback操作。二阶段是以异步的方式执行的。
从两个阶段看出,Seata AT 方案的核心在于 undo_log。正是有了这个记录回滚日志的undo_log 表,我们才能将一阶段和二阶段剥离成两个独立的本地事务来执行。而 Seata AT 之所以执行效率高,主要原因有两个。一是核心业务逻辑可以在一阶段得到快速提交,DB 资源被快速释放;二是全局事务的 Commit 和 Rollback 是异步执行的。
TCC 指 Try、Confirm 和 Cannel,这三个单词分别对应了 TCC 模式里的三个执行阶段,每一个阶段都是一个独立的本地事务。TCC 实现需要编写一定的代码,相对AT方案比较复杂。
往期经典推荐:
Kafka VS RabbitMQ,架构师教你如何选择-CSDN博客