不就是分布式事务,这下彻底清楚了

Posted 90后小伙追梦之路

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了不就是分布式事务,这下彻底清楚了相关的知识,希望对你有一定的参考价值。

从本地事务到分布式事务

事务大家应该都知道,事务将一组操作纳入到一个不可分割的执行单元,这个执行单元里的操作都成功时才能提交成功。

简单地说,事务提供一种要么不做,要么全做机制。

ACID

我们先简单了解一下事务的四大特性:

ACID

  • A 原子性(Atomicity)

一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会出现部分成功部分失败的情况。。

  • C 一致性(Consistency)

事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。

  • I 隔离性(Isolation)

指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。

  • D 持久性(Durability)

指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。

单体事务

在单体架构时代,所有的业务只用一个数据库。

单体服务

单体架构时代事务的是实现很简单,我们操作的是同一个数据库,利用数据库本身提供的事务机制支持就可以了。

例如我们比较熟悉的mysql数据库:

  • 事务的隔离性是通过数据库锁的机制实现的。

  • 事务的一致性由undo log来保证:undo log是逻辑日志,记录了事务的insertupdatedeltete操作,回滚的时候做相反的deleteupdateinsert操作来恢复数据。

  • 事务的原子性和一持久性由redo log来保证:redolog被称作重做日志,是物理日志,事务提交的时候,必须先将事务的所有日志写入redo log持久化,到事务的提交操作才算完成。

ACID实现

详细了解建议阅读《MySQL技术内幕  InnoDB存储引擎》7.2节。

分布式事务

随着业务发展,单体架构顶不住了,慢慢进入分布式时代——SOA或者粒度更细的微服务

当然伴随而来的就是分库分表。

  • 我们可能会根据业务服务拆分的方式,对应地垂直拆分大库,例如原始大库拆分成订单库、商品库、支付库。

  • 同时由于业务数据可能会高速增加,很快就成了亿级,我们不得不又水平分库,来减轻单个数据库的压力。

分布式情况下数据库

不管是怎么分库的,最后的结果就是我们一个操作可能要横跨多个数据库。

数据库本身的事务机制只能保证它自己这个库的事务,但是没法保证到其它的库。我们要保证跨多个库的操作还具备事务的特性,就不得不上分布式事务了。

在前面 分布式必备理论基础:CAP和BASE  里,讲了分布式的理论基础——CAPBASE,这里就不再多讲。

我们只需要知道,BASE理论是对CAP中AP的一个延申,在没法保证强一致性的前提下,尽可能达到最终的一致性。

我们的分布式事务通常也做不到本地事务那么强的一致性,一般都是对一致性(Consistency)适当做了一些放宽,只需要达到最终的一致性。

分布式事务解决方案

XA /2PC两阶段提交

XA

XA是一个分布式事务协议,由Tuxedo提出。

在这个协议里,有三个角色:

  • AP(Application):应用系统(服务)

  • TM(Transaction Manager):事务管理器(全局事务管理)

  • RM(Resource Manager):资源管理器(数据库)

XA规范主要定义了 事务管理器(Transaction  Manager)和资源管理器(Resource Manager)之间的接口。

XA规范

XA协议采用两阶段提交方式来管理分布式事务。XA接口提供资源管理器与事务管理器之间进行通信的标准接口。

2PC 两阶段提交

两阶段提交的思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情况决定各参与者是否要提交操作还是中止操作。

两阶段提交的两个阶段:第一阶段:准备阶段,第二阶段:提交阶段

两阶段-参考[2]

准备阶段 Prepares

协调者向所有参与者询问是否可以执行提交操作,所有参与者执行事务,将结果返回给协调者。

第一阶段

提交阶段 commit

  • 如果第一阶段中所有参与者都返回yes响应,协调者向所有参与者发出提交请求,所有参与者提交事务

  • 如果第一阶段中有一个或者多个参与者返回no响应,协调者向所有参与者发出回滚请求,所有参与者进行回滚操作

第二阶段

两阶段提交优点:尽量保证了数据的强一致,但不是100%一致

两阶段提交同样有一些缺点:

  • 单点故障

    由于协调者的重要性,一旦协调者发生故障,参与者会一直阻塞,尤其是在第二阶段,协调者发生故障,那么所有的参与者都处于锁定事务资源的状态中,而无法继续完成事务操作。

  • 同步阻塞

    它是一个强一致性的同步阻塞协议,也就是所谓刚性事务,事务执⾏过程中需要将所需资源全部锁定,会比较影响性能。

  • 数据不一致

    在第二阶段中,当协调者向参与者发送提交事务请求之后,由于网络抖动,如果第二阶段只有部分参与者收到提交请求,那么就会导致数据不一致。

3PC 三阶段提交

三阶段提交(3PC)是二阶段提交(2PC)的一种改进版本 ,为解决两阶段提交协议的单点故障和同步阻塞问题。上边提到两阶段提交,当协调者崩溃时,参与者不能做出最后的选择,就会一直保持阻塞锁定资源。

2PC 中只有协调者有超时机制,3PC 在协调者和参与者中都引入了超时机制,协调者出现故障后,参与者就不会一直阻塞。而且在第一阶段和第二阶段中又插入了一个预提交阶段,保证了在最后提交阶段之前各参与节点的状态是一致的。

三阶段提交的三个阶段:CanCommitPreCommitDoCommit三个阶段

三阶段协议-参考[2]

准备阶段 CanCommit

协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

预提交阶段 PreCommit

协调者根据参与者在准备阶段的响应判断是否执行事务还是中断事务

  • 如果所有参与者都返回Yes,则执行事务

  • 如果参与者有一个或多个参与者返回No或者超时,则中断事务

参与者执行完操作之后返回ACK响应,同时开始等待最终指令。

提交阶段 DoCommit

协调者根据参与者在准备阶段的响应判断是否执行事务还是中断事务

  • 如果所有参与者都返回正确的ACK响应,则提交事务

  • 如果参与者有一个或多个参与者收到错误的ACK响应或者超时,则中断事务

  • 如果参与者无法及时接收到来自协调者的提交或者中断事务请求时,在等待超时之后,会继续进行事务提交

协调者收到所有参与者的ACK响应,完成事务。

3PC

可以看出,三阶段提交解决的只是两阶段提交中 单体故障和同步阻塞的问题,因为加入了超时机制,这里的超时的机制作用于 预提交阶段 和 提交阶段。如果等待 预提交请求 超时,参与者直接回到准备阶段之前。如果等到提交请求超时,那参与者就会提交事务了。

无论是2PC还是3PC都不能保证分布式系统中的数据100%一致

TCC补偿事务

TCC  Try-Confirm-Cancel 的简称,是两阶段提交的一个变种,针对每个操作,都需要有一个其对应的确认和取消操作,当操作成功时调用确认操作,当操作失败时调用取消操作,类似于二阶段提交,只不过是这里的提交和回滚是针对业务上的,所以基于TCC实现的分布式事务也可以看做是对业务的一种补偿机制。

TCC的三阶段:

  1. Try 阶段:对业务系统做检测及资源预留

  2. Confirm 阶段:对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功

  3. Cancel 阶段:在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放

在Try阶段,是对业务系统进行检查及资源预览,比如订单和库存操作,需要检查库存剩余数量是否够用,并进行预留,预留操作的话就是新建一个可用库存数量字段,Try阶段操作是对这个可用库存数量进行操作。

例如下单减库存的操作:

TCC下单减库存

执行流程:

  1. Try阶段:订单系统将当前订单状态设置为支付中,库存系统校验当前剩余库存数量是否大于1,然后将可用库存数量设置为库存剩余数量-1,

  2. 如果Try阶段执行成功,执行Confirm 阶段,将订单状态修改为支付成功,库存剩余数量修改为可用库存数量

  3. 如果Try阶段执行失败,执行Cancel 阶段,将订单状态修改为支付失败,可用库存数量修改为库存剩余数量

TCC 不存在资源阻塞的问题,因为每个方法都直接进行事务的提交,一旦出现异常通过则 Cancel 来进行回滚补偿,这也就是常说的补偿性事务。

但是,使用TCC,原本一个方法,现在却需要三个方法来支持,可以看到 TCC 对业务的侵入性很强,而且这种模式并不能很好地被复用,会导致开发量激增。还要考虑到网络波动等原因,为保证请求一定送达都会有重试机制,所以还需要考虑接口的幂等性。

本地消息表

本地消息表的核心思想是将分布式事务拆分成本地事务进行处理。

例如,可以在订单库新增一个消息表,将新增订单和新增消息放到一个事务里完成,然后通过轮询的方式去查询消息表,将消息推送到MQ,库存系统去消费MQ。

本地消息表

执行流程:

  1. 订单服务,添加一条订单和一条消息,在一个事务里提交

  2. 订单服务,使用定时任务轮询查询状态为未同步的消息表,发送到MQ,如果发送失败,就重试发送

  3. 库存服务,接收MQ消息,修改库存表,需要保证幂等操作

  4. 如果修改成功,调用rpc接口修改订单系统消息表的状态为已完成或者直接删除这条消息

  5. 如果修改失败,可以不做处理,等待重试

订单服务中的消息有可能由于业务问题会一直重复发送,所以为了避免这种情况可以记录一下发送次数,当达到次数限制之后报警,人工接入处理;库存服务需要保证幂等,避免同一条消息被多次消费造成数据不一致。

本地消息表这种方案实现了最终一致性,需要在业务系统里增加消息表,业务逻辑中多一次插入的DB操作,所以性能会有损耗,而且最终一致性的间隔主要由定时任务的间隔时间决定。

MQ消息事务

消息事务的原理是将两个事务通过消息中间件进行异步解耦。

订单服务执行自己的本地事务,并发送MQ消息,库存服务接收消息,执行自己的本地事务,乍一看,好像跟本地消息表的实现方案类似,只是省去 了对本地消息表的操作和轮询发送MQ的操作,但实际上两种方案的实现是不一样的。

消息事务一定要保证业务操作与消息发送的一致性,如果业务操作成功,这条消息也一定投递成功。

MQ消息事务

执行流程:

  1. 发送prepare消息到消息中间件

  2. 发送成功后,执行本地事务

  3. 如果事务执行成功,则commit,消息中间件将消息下发至消费端

  4. 如果事务执行失败,则回滚,消息中间件将这条prepare消息删除

  5. 消费端接收到消息进行消费,如果消费失败,则不断重试

消息事务依赖于消息中间件的事务消息,例如我们熟悉的RocketMQ就支持事务消息(半消息),也就是只有收到发送方确定才会正常投递的消息。

这种方案也是实现了最终一致性,对比本地消息表实现方案,不需要再建消息表,对性能的损耗和业务的入侵更小。

最大努力通知

最大努力通知相比实现会简单一些,适用于一些最终一致性要求较低的业务,比如支付通知,短信通知这种业务。

以支付通知为例,业务系统调用支付平台进行支付,支付平台进行支付,进行操作支付之后支付平台会去同步通知业务系统支付操作是否成功,如果不成功,会一直异步重试,但是会有一个最大通知次数,如果超过这个次数后还是通知失败,就不再通知,业务系统自行调用支付平台提供一个查询接口,供业务系统进行查询支付操作是否成功

最大努力通知

执行流程:

  1. 业务系统调用支付平台支付接口, 并在本地进行记录,支付状态为支付中

  2. 支付平台进行支付操作之后,无论成功还是失败,同步给业务系统一个结果通知

  3. 如果通知一直失败则根据重试规则异步进行重试,达到最大通知次数后,不再通知

  4. 支付平台提供查询订单支付操作结果接口

  5. 业务系统根据一定业务规则去支付平台查询支付结果

Saga事务

Saga事务,核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。

和本地事务undo log有点像,出问题了,逆向操作来挽救。

Sega简介:

  • Saga = Long Live Transaction (LLT,长活事务)

  • LLT = T1 + T2 + T3 + ... + Ti(Ti为本地短事务)

  • 每个本地事务Ti 有对应的补偿 Ci

Sega的执行顺序:

  • 正常情况:T1 T2 T3 ... Tn

  • 异常情况:T1 T2 T3 C3 C2 C1

Saga两种恢复策略

  • 向后恢复,如果任意本地子事务失败,补偿已完成的事务。如异常情况的执行顺序T1 T2 Ti Ci C2 C1.

  • 向前恢复,即重试失败的事务,假设最后每个子事务都会成功。执行顺序:T1, T2, ..., Tj(失败), Tj(重试),..., Tn。

举个例子,假设用户下订单,花50块钱购买了10瓶可乐,则有这么一些短事务和回滚操作:

T1=下订单  => T2=用户扣50块钱 => T3=用户加10瓶可乐= > T4=库存减10瓶可乐

C1=取消订单 => C2= 给用户加50块钱 => C3 =用户减10朵玫瑰 = > C4=库存加10朵玫瑰

Sega事务

Seata

看了这么些事务的方案,介绍了相关的原理,但是这些原理怎么落地呢?各种各样的坑怎么处理呢?

—— 人生苦短,我用开源。

阿里巴巴开源了一套开源分布式事务解决方案——Seata。Seata可能并不称之为完美,但对代码入侵性非常小,基本环境搭建完成的话,使用的时候在只需要方法上添加一个注解@GlobalTransactional就可以开启全局事务。

Seata 也是从两段提交演变而来的一种分布式事务解决方案,提供了 ATTCCSAGA 和 XA 等事务模式,我们来看一下AT模式。

Seata 中主要有这么几种角色:

TC(Transaction Coordinator):事务协调者。管理全局的分支事务的状态,用于全局性事务的提交和回滚。

TM(Transaction Manager):事务管理者。用于开启、提交或回滚事务。

RM(Resource Manager):资源管理器。用于分支事务上的资源管理,向 TC 注册分支事务,上报分支事务的状态,接收 TC 的命令来提交或者回滚分支事务。

我们看一下Seata大概的一个工作流程:

Seata

执行流程:

  1. 服务A中的 TM 向 TC 申请开启一个全局事务,TC 就会创建一个全局事务并返回一个唯一的 XID

  2. 服务A中的 RM 向 TC 注册分支事务,然后将这个分支事务纳入 XID 对应的全局事务管辖中

  3. 服务A开始执行分支事务

  4. 服务A开始远程调用B服务,此时 XID 会根据调用链传播

  5. 服务B中的 RM 也向 TC 注册分支事务,然后将这个分支事务纳入 XID 对应的全局事务管辖中

  6. 服务B开始执行分支事务

  7. 全局事务调用处理结束后,TM 会根据有误异常情况,向 TC 发起全局事务的提交或回滚

  8. TC 协调其管辖之下的所有分支事务,决定是提交还是回滚

关于Seata的使用,和更详细的原理,这里挖个坑,以后有时间再细讲。

总结

上边简单介绍了 2PC3PCTCC本地消息表最大努力通知MQSegaSeata 这8种分布式事务解决方案,但不管我们选哪一种方案,我们可以看到真要落地要考虑的点都很多,一个不慎,可能踩坑。

即使是看起来很省心的Seata,我之前的项目花了不少w买了它的商业化版本GTS,但是支持方仍然列出了一些“禁忌”,像长事务、大量数据、热点数据、异步调用等等,都可能会出现问题。

所以在项目中应用分布式事务要谨慎再谨慎,除非真的有一致性要求比较强的场景,能不用就尽量不用。

需要完整资料的朋友可以:点赞+转发+关注!后台小信封:回复【444】即可获取完整笔记资料!

尽量别用分布式事务

如果觉得文章有帮助, 点赞在看关注转发  素质四连,抱拳!

一文详解Spring事务注解的解析,这下你总懂了吧?

前言

事务我们都知道是什么,而Spring事务就是在数据库之上利用AOP提供声明式事务和编程式事务帮助我们简化开发,解耦业务逻辑和系统逻辑。但是Spring事务原理是怎样?事务在方法间是如何传播的?为什么有时候事务会失效?接下来几篇文章将重点分析Spring事务源码,让我们彻底搞懂Spring事务的原理。

XML标签的解析

<tx:annotation-driven transaction-manager="transactionManager"/>

配置过事务的应该都不陌生,上面这个配置就是Spring开启事务注解(@Transactional)支持的配置,而看过我之前文章的应该知道,这个带前缀的标签叫自定义标签,我在之前的文章也分析过自定义标签的解析过程,所以这里我直接找到对应的handler:

public class TxNamespaceHandler extends NamespaceHandlerSupport 
    static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";
    static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";
    static String getTransactionManagerName(Element element) 
        return (element.hasAttribute(TRANSACTION_MANAGER_ATTRIBUTE) ?
                element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME);
    
    @Override
    public void init() 
        registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
        registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
    

可以看到对应的注解解析器就是AnnotationDrivenBeanDefinitionParser类,在该类中一定会有一个parse方法:

    public BeanDefinition parse(Element element, ParserContext parserContext) 
        registerTransactionalEventListenerFactory(parserContext);
        String mode = element.getAttribute("mode");
        if ("aspectj".equals(mode)) 
            // mode="aspectj"
            registerTransactionAspect(element, parserContext);
            if (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader())) 
                registerJtaTransactionAspect(element, parserContext);
            
        
        else 
            // mode="proxy"
            AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
        
        return null;
    

首先拿到mode属性的值判断是使用AspectJ生成代理还是JDK生成代理,这里我们主要看proxy模式,进入configureAutoProxyCreator方法:

        public static void configureAutoProxyCreator(Element element, ParserContext parserContext) 
            // 注册AOP的入口类
            AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);

            String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;
            if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) 
                Object eleSource = parserContext.extractSource(element);

                // Create the TransactionAttributeSource definition.
                // @Transactional注解的属性封装
                RootBeanDefinition sourceDef = new RootBeanDefinition(
                        "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
                sourceDef.setSource(eleSource);
                sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);

                // Create the TransactionInterceptor definition.
                // AOP执行链
                RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
                interceptorDef.setSource(eleSource);
                interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                // 拿到transaction-manager属性的值
                registerTransactionManager(element, interceptorDef);
                interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
                String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);

                // Create the TransactionAttributeSourceAdvisor definition.
                RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
                advisorDef.setSource(eleSource);
                advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
                advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
                if (element.hasAttribute("order")) 
                    advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
                
                parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);

                CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
                compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
                compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
                compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName));
                parserContext.registerComponent(compositeDef);
            
        

这里的流程比较长,但逻辑很简单。首先来看注册事务AOP入口类是哪个:

    public static void registerAutoProxyCreatorIfNecessary(
            ParserContext parserContext, Element sourceElement) 

        // 将优先级更高的AOP入口类放入到IOC容器中
        BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(
                parserContext.getRegistry(), parserContext.extractSource(sourceElement));
        // 设置代理生成的方式以及是否缓存代理类到当前线程
        useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
        registerComponentIfNecessary(beanDefinition, parserContext);
    

主要看registerAutoProxyCreatorIfNecessary方法:

    public static BeanDefinition registerAutoProxyCreatorIfNecessary(
            BeanDefinitionRegistry registry, @Nullable Object source) 

        return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
    

    private static BeanDefinition registerOrEscalateApcAsRequired(
            Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) 

        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

        // 判断传进来的类和ICO中当前存在的类哪个优先级更高,将更高的放入IOC中
        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) 
            BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
            if (!cls.getName().equals(apcDefinition.getBeanClassName())) 
                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
                int requiredPriority = findPriorityForClass(cls);
                if (currentPriority < requiredPriority) 
                    apcDefinition.setBeanClassName(cls.getName());
                
            
            return null;
        

        //把AOP入口类封装成beanDefinition对象,要实例化
        RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
        beanDefinition.setSource(source);
        beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        //注解aop入口类的beanName名称 AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME
        registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
        return beanDefinition;
    

首先判断容器中是否已经存在AOP入口类,如果不存在则直接创建InfrastructureAdvisorAutoProxyCreator的BeanDefinition对象注册到容器中,这个类也是AOP入口类AbstractAutoProxyCreator的子类,再来看看其继承关系:

你会不会疑惑,这么多子类,到底会使用哪一个呢?回到刚刚的代码中,可以看到如果已经存在一个入口类了,就会通过findPriorityForClass获取两个类的优先级,最终就会使用优先级更大的那个,那么它们的优先级顺序是怎样的呢?

    private static final List<Class<?>> APC_PRIORITY_LIST = new ArrayList<>(3);

    static 
        // Set up the escalation list...
        APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
        APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
        APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
    

    private static int findPriorityForClass(@Nullable String className) 
        // 索引即是优先级,越大优先级越高,IOC中只会存在一个事务AOP入口类
        for (int i = 0; i < APC_PRIORITY_LIST.size(); i++) 
            Class<?> clazz = APC_PRIORITY_LIST.get(i);
            if (clazz.getName().equals(className)) 
                return i;
            
        
        throw new IllegalArgumentException(
                "Class name [" + className + "] is not a known auto-proxy creator class");
    

可以看到,InfrastructureAdvisorAutoProxyCreator是优先级最低的,基本上不会起作用;AspectJAwareAdvisorAutoProxyCreator是当我们配置了<aop:config>标签时会注册,也就是xml配置的AOP的入口类;而AnnotationAwareAspectJAutoProxyCreator是当我们配置了<aop:aspectj-autoproxy>或使用@EnableAspectJAutoProxy注解时注册,因此大部分情况下都是使用的AnnotationAwareAspectJAutoProxyCreator。

注册完AOP的入口类后,回到configureAutoProxyCreator方法:

RootBeanDefinition sourceDef = new RootBeanDefinition(
        "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
sourceDef.setSource(eleSource);
sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String sourceName =parserContext.getReaderContext().registerWithGeneratedName(sourceDef);

AnnotationTransactionAttributeSource类的作用就是封装事务注解@Transactional的属性,这里需要记住其继承体系以及熟悉该类和其父类的属性和方法,对后面分析事物切面执行原理有帮助:

紧接着就是创建了TransactionInterceptor对象,专门的事务拦截器,并且该类是MethodInterceptor的子类,看到这个应该不陌生了,我们知道AOP调用链在执行过程中主要就是调用该类的invoke的方法,因此它是事务切面执行的入口。既然有了Interceptor,那么必不可少的还应该有Advisor,而Advisor又是由Advice和Poincut组成的,这样才能构成一个完整的切面,所以该方法后面就是创建这两个对象。以上就是xml配置AOP注解支持的原理,很简单,下面再来看看零配置又是如何实现的。

AOP零配置原理

使用过SpringBoot的都知道,如果需要开启事务注解的支持,只需要一个注解就能搞定:@EnableTransactionManagement,不用再配置xml文件,这个又是怎么做到的呢?不多说,我们直接来看其源码:

@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement 
    boolean proxyTargetClass() default false;
    AdviceMode mode() default AdviceMode.PROXY;
    int order() default Ordered.LOWEST_PRECEDENCE;

在该注解下使用@Import导入了一个类TransactionManagementConfigurationSelector,首先该注解的作用就是导入一个类的实例到IOC容器中,你可能会说不是在类上加@Component注解就行了么,但是有些类它并不在你扫描的路径下,而该注解依然可以将其导入进来,所以我么主要看TransactionManagementConfigurationSelector类中做了些啥:

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> 

    @Override
    protected String[] selectImports(AdviceMode adviceMode) 
        switch (adviceMode) 
            case PROXY:
                return new String[] AutoProxyRegistrar.class.getName(),
                        ProxyTransactionManagementConfiguration.class.getName();
            case ASPECTJ:
                return new String[] determineTransactionAspectClass();
            default:
                return null;
        
    

    private String determineTransactionAspectClass() 
        return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ?
                TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME :
                TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME);
    

可以看到在selectImports方法中返回了AutoProxyRegistrar和ProxyTransactionManagementConfiguration类,返回后会被封装为BeanDefinition对象,那这个方法是在哪里调用的呢?这个在之前的文章中也分析过,ConfigurationClassPostProcessor类中会调用ConfigurationClassParser类的parse方法解析@Configuration、@Import、@ImportSource等注解,具体过程这里就不再赘述了。我们继续来分别看看AutoProxyRegistrar和ProxyTransactionManagementConfiguration类:

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar 

    private final Log logger = LogFactory.getLog(getClass());

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) 
        boolean candidateFound = false;
        Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
        for (String annoType : annoTypes) 
            AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
            if (candidate == null) 
                continue;
            
            Object mode = candidate.get("mode");
            Object proxyTargetClass = candidate.get("proxyTargetClass");
            if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
                    Boolean.class == proxyTargetClass.getClass()) 
                candidateFound = true;
                if (mode == AdviceMode.PROXY) 
                    //注册事务AOP的入口类InfrastructureAdvisorAutoProxyCreator,实际上这个AOP入口类起不了作用
                    AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                    if ((Boolean) proxyTargetClass) 
                        AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                        return;
                    
                
            
        
    


public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration 

    /*
    * 明显是创建事务切面实例
    * BeanFactoryTransactionAttributeSourceAdvisor
    *
    * */
    @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() 
        BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
        advisor.setTransactionAttributeSource(transactionAttributeSource());
        //设置通知类
        advisor.setAdvice(transactionInterceptor());
        if (this.enableTx != null) 
            advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
        
        return advisor;
    

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public TransactionAttributeSource transactionAttributeSource() 
        return new AnnotationTransactionAttributeSource();
    

    /*
    * 创建事务advice
    * TransactionInterceptor
    * */
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public TransactionInterceptor transactionInterceptor() 
        TransactionInterceptor interceptor = new TransactionInterceptor();
        interceptor.setTransactionAttributeSource(transactionAttributeSource());
        //事务管理器要跟数据源挂钩,所以需要自己定义
        if (this.txManager != null) 
            interceptor.setTransactionManager(this.txManager);
        
        return interceptor;
    

看到这就很清楚了,前者是注册AOP的入口类(这里注册的入口类依然是InfrastructureAdvisorAutoProxyCreator),后者则是创建事务AOP的组件的实例到IOC中,到这里相信不仅仅是对于事务的零配置,而是整个SpringBoot的零配置实现原理都心中有数了。

以上是关于不就是分布式事务,这下彻底清楚了的主要内容,如果未能解决你的问题,请参考以下文章

不就是分布式事务,这下彻底清楚了

一文详解Spring事务注解的解析,这下你总懂了吧?

12张图带你彻底理解分布式事务产生的场景和解决方案!!

面试题 java啥叫事务,事务有啥用

写给 Java 工程师的数据库事务!

最新JAVA面试合集:2021Java高级进阶学习资料,先收藏了