【深入理解SpringCloud微服务】Seata(AT模式)源码解析——@GlobalTransactional注解与@globalLock生效的原理

Seata(AT模式)源码解析

AT模式流程复习

AT模式一阶段流程:

AT模式二阶段(提交)流程:

AT模式二阶段(回滚)流程:

AT模式原理解析

seata是基于SpringAOP实现的,通过SpringAOP的扩展点扫描@GlobalTransactional注解和@GlobalLock注解,生成代理对象,指定了自定义的代理增强器。

自定义的代理增强器的对于@GlobalTransactional注解的处理逻辑就是开启全局事务、执行目标方法,提交/回滚全局事务。

开启全局事务做的事情就是:

  1. TM请求TC开启全局事务的接口
  2. TC生成全局事务信息,存入global_table表
  3. TC返回全局事务id(xid)给TM
  4. TM把获取到的xid存入ThreadLocal

开启全局事务成功,接下来就要执行目标方,假设我们的目标方法需要执行本地sql,也需要发起rpc远程调用。

执行sql会进入PreparedStatementProxy,PreparedStatementProxy是由ConnectionProxy生成的,而ConnectionProxy又是由DataSourceProxy生成的。

PreparedStatementProxy获取前置镜像、执行sql、获取后置镜像,然后根据前置镜像和后置镜像生成undolog信息。

然后就要向TC注册分支事务,注册成功则提交本地事务,然后上报分支事务状态。

然后如果当前调用链还有后续需要调用的服务,会在请求头中携带xid,被调用方通过SpringMVC拦截器从请求头中获取xid存入ThreadLocal。然后被调用方执行sql时,也是经历"获取前置镜像、执行sql、获取后置镜像,生成undolog信息,注册分支事务,提交本地事务,上报分支事务状态"这一整套流程。

如果一切顺利,TM就会提交全局事务,然后异步TC通知每个参与该全局事务的RM提交本地事务,RM异步删除本地的undolog表记录。

如果调用链出现异常,TM会catch住然后向TC发起全局事务回滚。TC会通知各RM回滚本地事务,RM会查询当前事务对应的undolog记录,根据undolog生成反向补偿sql并执行。

源码解析

GlobalTransactionScanner

java 复制代码
public class GlobalTransactionScanner extends AbstractAutoProxyCreator {
    @Override
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        try {
            synchronized () {
                if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) {
                // TCC模式
                } else {
                	// 检查类上或方法上是否有@GlobalTransactional或@GlobalLock修饰
                    if (!existsAnnotation(new Class[]{serviceInterface})
                        && !existsAnnotation(interfacesIfJdk)) {
                        // 没有@GlobalTransactional或@GlobalLock修饰,返回原来的bean
                        return bean;
                    }

                    if (globalTransactionalInterceptor == null) {
                    	// 全局事务拦截器,
                        globalTransactionalInterceptor = new GlobalTransactionalInterceptor(failureHandlerHook);
                    }
                    // 保存GlobalTransactionalInterceptor到成员变量interceptor,
                    // 生成代理对象时放入AOP拦截器链
                    interceptor = globalTransactionalInterceptor;
                }

                if (!AopUtils.isAopProxy(bean)) {
                	// 进行AOP增强
                    bean = super.wrapIfNecessary(bean, beanName, cacheKey);
                } else {}
                return bean;
            }
        } catch () {}
    }
}

GlobalTransactionScanner继承了AbstractAutoProxyCreator,重写了wrapIfNecessary方法。wrapIfNecessary方法会扫描到当前bean的类上或方法上是否有@GlobalTransactional或@GlobalLock注解修饰,如果有@GlobalTransactional或@GlobalLock注解修饰,则会对当前bean做增强处理。如果当前bean已经是一个AOP代理bean,那么往AOP拦截器链中放入指定的拦截器GlobalTransactionalInterceptor;如果当前bean是一个普通bean,那么对其进行AOP代理增强,指定AOP拦截器为GlobalTransactionalInterceptor。

GlobalTransactionalInterceptor

该bean被代理增强后,当我们调用代理bean的方法时,会被GlobalTransactionalInterceptor拦截,进入GlobalTransactionalInterceptor 的处理逻辑。

java 复制代码
public class GlobalTransactionalInterceptor implements MethodInterceptor {
	@Override
	public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
	    // 1、获取Class对象
	    // 2、获取Method对象
	    // 3、从Method对象上获取@GlobalTransactional注解和@globalLockAnnotation注解
	    if (globalTransactionalAnnotation != null) {
	        // 方法有@GlobalTransactional注解修饰,执行handleGlobalTransaction方法
	        return handleGlobalTransaction(methodInvocation, globalTransactionalAnnotation);
	    } else if (globalLockAnnotation != null) {
	        // 方法有@globalLock注解修饰,执行handleGlobalLock方法
	        return handleGlobalLock(methodInvocation, globalLockAnnotation);
	    }
	    return methodInvocation.proceed();
	}
}

GlobalTransactionalInterceptor会判断不同的注解调用不同的方法进行处理。

GlobalTransactionalInterceptor#handleGlobalTransaction

java 复制代码
    Object handleGlobalTransaction(final MethodInvocation methodInvocation,
        final GlobalTransactional globalTrxAnno) throws Throwable {
        try {
        	// @GlobalTransaction注解的处理逻辑
        	// 调用transactionalTemplate的execute方法
            return transactionalTemplate.execute(new TransactionalExecutor() {
                @Override
                public Object execute() throws Throwable {
                    return methodInvocation.proceed();
                }
            });
        } catch () {} finally {}
    }

如果是@GlobalTransaction注解,会调用transactionalTemplate进行处理。

GlobalTransactionalInterceptor#handleGlobalLock

java 复制代码
    Object handleGlobalLock(final MethodInvocation methodInvocation,
        final GlobalLock globalLockAnno) throws Throwable {
        // @GlobalLock注解的处理逻辑
        // 调用globalLockTemplate的execute方法
        return globalLockTemplate.execute(new GlobalLockExecutor() {
            @Override
            public Object execute() throws Throwable {
                return methodInvocation.proceed();
            }
        });
    }

如果是@GlobalLock注解,会调用globalLockTemplate进行处理。

TransactionalTemplate

java 复制代码
public class TransactionalTemplate {
    public Object execute(TransactionalExecutor business) throws Throwable {
                try {
            // 请求TC开启全局事务
            beginTransaction(txInfo, tx);

            Object rs;
            try {
                // 执行业务逻辑
                rs = business.execute();
            } catch (Throwable ex) {
                // 调用链上有异常发生,请求TC回滚全局事务
                completeTransactionAfterThrowing(txInfo, tx, ex);
                throw ex;
            }

            // 一切顺利,请求TC提交全局事务
            commitTransaction(tx);

            return rs;
        } finally {}
    }
}

TransactionalTemplate的execute方法是对@GlobalTransactional注解的具体处理逻辑。

里面就会一个try-catch-finally代码块:全局事务开启,执行业务逻辑;如果调用链上发生异常,则在catch块中进行全局事务回滚;否则提交全局事务。

GlobalLockTemplate

java 复制代码
public class GlobalLockTemplate {

    public Object execute(GlobalLockExecutor executor) throws Throwable {
        boolean alreadyInGlobalLock = RootContext.requireGlobalLock();
        if (!alreadyInGlobalLock) {
        	// 当前线程绑定全局锁标记到ThreadLocal中
            RootContext.bindGlobalLockFlag();
        }

        try {
        	// 执行业务逻辑
            return executor.execute();
        } finally {
            if (!alreadyInGlobalLock) {
            	// 当前线程解绑全局锁标记
                RootContext.unbindGlobalLockFlag();
            }
        }
    }
}

GlobalLockTemplate调用了RootContext.bindGlobalLockFlag()为当前线程在ThreadLocal中绑定一个全局锁标记,然后就调用业务逻辑,完事后再解绑全局锁标记。

给当前线程绑定全局锁标记后,后续对"select for update"语句的处理,seata检查到当前线程绑定了全局锁标记,会在执行查询sql之后,返回查询结果之前,会请求TC查询是否存在全局锁(也就是全局锁已被别人获取)。如果存在全局锁,则当前本地事务回滚进行重试;全局锁不存在,则返回查询结果(之所以全局锁不存在就可以返回查询结果,是因为"select for update"语句会获取本地锁,那么其他线程就不可能再获取到同一个本地锁,而此时确认了TC上没有全局锁,就可以断定没有其他线程对"select for update"语句查询涉及到的记录进行写操作)。

整体流程

由于"开启全局事务"、"分支事务处理"、"全局事务提交/回滚"等的这些分支每一个展开都描述都需要很长的篇幅,因此就不在本篇文件进行解析了,放到后面的文件进行逐一的分析。

相关推荐
indexsunny2 小时前
互联网大厂Java求职面试实战:Spring Boot微服务与Redis缓存场景解析
java·spring boot·redis·缓存·微服务·消息队列·电商
无心水2 小时前
【分布式利器:腾讯TSF】7、TSF高级部署策略全解析:蓝绿/灰度发布落地+Jenkins CI/CD集成(Java微服务实战)
java·人工智能·分布式·ci/cd·微服务·jenkins·腾讯tsf
28岁青春痘老男孩7 小时前
JDK8+SpringBoot2.x 升级 JDK 17 + Spring Boot 3.x
java·spring boot
方璧7 小时前
限流的算法
java·开发语言
元Y亨H7 小时前
Nacos - 服务注册
java·微服务
曲莫终8 小时前
Java VarHandle全面详解:从入门到精通
java·开发语言
独自归家的兔8 小时前
Spring Cloud核心架构组件深度解析(原理+实战+面试高频)
spring cloud·面试·架构
一心赚狗粮的宇叔8 小时前
中级软件开发工程师2025年度总结
java·大数据·oracle·c#
奋进的芋圆8 小时前
DataSyncManager 详解与 Spring Boot 迁移指南
java·spring boot·后端
计算机程序设计小李同学8 小时前
个人数据管理系统
java·vue.js·spring boot·后端·web安全