一起学习下Seata-AT模式原理吧

1,892 阅读5分钟

截屏2022-04-20 下午2.03.01.png

1:什么是Seata

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

Seata有三个重要的术语

  • TC:维护全局和分支事务的状态,驱动全局事务提交或回滚。,也就是我们的Seata-server服务

  • TM:定义全局事务的范围:开始全局事务、提交或回滚全局事务。通常来说就是我们添加了@GloableTrancation注解的那个服务

  • RM:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

2:AT模式整体机制

整体机制是基于两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。

  • 二阶段:

    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

3:AT模式整体流程

  • 开启全局事务

    • 首先TM会向TC注册一个全局事务,注册成功之后会返回一个全局的事务ID,也就是XID,并且会生成一个全局的事务会话GlobalSession,并且会在seata数据库的global_table中保存这个全局事务的相关信息

截屏2022-04-20 下午2.18.06.png

  • 执行自己的业务逻辑

    • 事务开启之后RM就执行自己的业务逻辑,执行完自己的业务之后就需要注册自己的分支事务,并生成一个分支事务会话BranchSession,该会话会与全局事务会话绑定在一起,其实就是全局会话GlobalSession里面有个List的变量,将这个BranchSession放到这个List中,也会将这个分支会话保存到branch_table中

    • 在生成分支会话之前还要判断这个全局事务的状体是否属于Begin的状态

    • 生成分支会话之后,就要获取该记录的全局锁了,举个例子,比如现在操作的是订单表,那么这时候订单ID=1的这条记录只能是一个事务去操作,其它事务是不能去操作的,只有当这个事务释放了这把锁,其它事务才能去操作这条记录,生成的全局锁是在seata的lock_table表中,操作的是那个数据,那张表,以及主键是多少都在row_key中保存起来了

    • 注册成功之后就会返回一个BranchId,RM收到这个BranchId之后,就会在自己本地数据库的undo_log表中保存一条回滚记录,用于后续的事务回滚,保存完之后本地事务就要提交,注意:这里是本地事务提交不是全局事务

    • lock_table: 截屏2022-04-20 下午2.33.44.png

    • branch_table

截屏2022-04-20 下午2.37.44.png

  • 全局事务提交

    • 通过XID全局事务ID去找到这个事务的全局会话,找到该全局事务的所有的分支会话,期间会判断该全局事务的状态

    • 拿到所有的分支会话之后,通知各个的分支会话去删除本地的undo_log对应的记录

  • 全局事务回滚

    • 因为在执行本地业务的时候,本地事务就已经提交了,如果这时候回滚该怎么办?还记得都在本地保存了一条undo_log的记录嘛,该记录有个很重要的字段 rollback_info,这个字段记录了记录操作前后的二个镜像,也就是beforeImage和afterImage。本地事务就可以通过回放将数据重新修改成事务之前的状态

    修改操作:记录了记录操作前的状态和操作后的状态

截屏2022-04-19 下午5.31.04.png

新增操作:记录了记录操作前的状态和操作后的状态,但是操作前的状态只是为空,因为是插入,之前是没有这条记录的

截屏2022-04-19 下午5.29.52.png

4:关键步骤源码

在底层只要有 @GlobalTransactional注解的方法都会经过GlobalTransactionalInterceptor这个拦截器的 invoke() 处理


TransactionalTemplate.class

public Object execute(TransactionalExecutor business) throws Throwable {
    // 1 get transactionInfo
    TransactionInfo txInfo = business.getTransactionInfo();
    if (txInfo == null) {
        throw new ShouldNeverHappenException("transactionInfo does not exist");
    }
    // 1.1 get or create a transaction
    GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();

    // 1.2 Handle the Transaction propatation and the branchType
    Propagation propagation = txInfo.getPropagation();
    SuspendedResourcesHolder suspendedResourcesHolder = null;
    try {
        switch (propagation) {
            case NOT_SUPPORTED:
                suspendedResourcesHolder = tx.suspend(true);
                return business.execute();
            case REQUIRES_NEW:
                suspendedResourcesHolder = tx.suspend(true);
                break;
            case SUPPORTS:
                if (!existingTransaction()) {
                    return business.execute();
                }
                break;
            case REQUIRED:
                break;
            case NEVER:
                if (existingTransaction()) {
                    throw new TransactionException(
                            String.format("Existing transaction found for transaction marked with propagation 'never',xid = %s"
                                    ,RootContext.getXID()));
                } else {
                    return business.execute();
                }
            case MANDATORY:
                if (!existingTransaction()) {
                    throw new TransactionException("No existing transaction found for transaction marked with propagation 'mandatory'");
                }
                break;
            default:
                throw new TransactionException("Not Supported Propagation:" + propagation);
        }


        try {

            // 2. 开启全局事务
            beginTransaction(txInfo, tx);

            Object rs = null;
            try {

                // 执行自己的业务逻辑
                rs = business.execute();

            } catch (Throwable ex) {

                // 3.全局事务回滚
                completeTransactionAfterThrowing(txInfo, tx, ex);
                throw ex;
            }

            // 4. 全局事务提交
            commitTransaction(tx);

            return rs;
        } finally {
            //5. clear
            triggerAfterCompletion();
            cleanUp();
        }
    } finally {
        tx.resume(suspendedResourcesHolder);
    }

}

最后附上整体的源码流程图

Seata-AT分布式事务原理.png

5:结尾

在通过debug方式去调试Seata源码的时候会特别麻烦,因为Seata底层是基于Netty进行通信的,所以首先你对Netty得熟悉,其次,就是因为基于Netty通信,所以源码的跳跃很容易让人头晕,不知道该走哪一步

其次就是Seata底层初始化了很多的定时线程池,当发现你有事务没有提交,或者没有回滚就会尝试去提交或者回滚之类的操作,此时可能你debug到后面突然断点又到之前的代码去了

事务都是有超时时间的,你debug时间太长根本就无法正常走完整个流程