【深入理解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"语句查询涉及到的记录进行写操作)。

整体流程

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

相关推荐
wheelmouse77888 小时前
一个优雅、通用、零侵入的 CSV 导出工具类(Java 实战)
java·开发语言
cike_y9 小时前
JavaWeb-Request应用与Cookie&[特殊字符]️Session
java·开发语言·安全·java安全
hashiqimiya9 小时前
两个步骤,打包war,tomcat使用war包
java·服务器·前端
大筒木老辈子9 小时前
C++笔记---并发支持库(atomic)
java·c++·笔记
Cricyta Sevina9 小时前
Java Collection 集合进阶知识笔记
java·笔记·python·collection集合
BD_Marathon9 小时前
【JavaWeb】Servlet_url-pattern的一些特殊写法问题
java·开发语言·servlet
黄俊懿9 小时前
【深入理解SpringCloud微服务】Seata(AT模式)源码解析——开启全局事务
java·数据库·spring·spring cloud·微服务·架构·架构师
零度@10 小时前
Java中Map的多种用法
java·前端·python
中文很快乐10 小时前
java开发--开发工具全面介绍--新手养成记
java·开发语言·java开发·开发工具介绍·idea开发工具