Seata源码(2)AT如何实现异常回滚和全局锁

240 阅读3分钟

还是从上一章的GlobalTransactionalInterceptor类开始说起,找到抛出异常的地方。回滚都是通过catch异常来进行分布式回滚操作。

异常时会调用completeTransactionAfterThrowing方法。追踪方法后很容易找到最后执行的是DefaultTransactionManager中的rollback方法。

通过TM发送GlobalRollBack请求到TC。后续TC会通知各个分支,根据本地undoLog日志进行本地回滚操作。

回滚

查看官网描述可得知

收到TC的分支回滚请求。这个时候我们需要找到分支的回滚请求如何触发。TM只存在开启GlobalTransaction中,因此执行回滚的操作是在RM中进行执行的。RM负责本地分支事务的回滚操作。

查看调用路径后很容易知道是通过Netty监听从TC传送过来的消息数据。进一步同步匹配对应的RMHandler,本文是AT模式的实现类。进而读取undoLog中的数据开始进行回滚操作。后续我们需要知道是怎么获取undoLog日志,如何进行SQL分析,在本地进行回滚。然后通知TC控制器,等所有分支都回滚完成后,通知TM回滚完成,事物结束。

BranchStatus status = getResourceManager().branchRollback(request.getBranchType(), xid, branchId, resourceId,
    applicationData);

我们打开DataSourceManager可以发现设置的分支类型是AT。因此我们需要关注下这个资源处理类中的如何处理分支回滚操作。

回滚操作实现在undo方法中。

我们这个时候需要关注一下这个undo执行器。里面包装增加,删除,修改的数据回滚时都是如何处理。

插入

插入数据其实很好处理,得到afterImage。通过PK来删除对应的数据即可。seata同步也考虑了联合主键的情况。

删除

删除操作是通过找到保存的SQL数据的主键,同步删除之前插入的数据。

更新

也是通过主键更新其他变化的数据。这里使用的?来防止sql注入。

每个分支进行数据库回滚后。同步通知TC,TC在回馈给TM告诉回滚整体结果。

有个特殊点:回滚完成后,也会抛出原始异常。通知系统由于什么错误导致业务请求回滚。回滚后同步也会删除undoLog日志。

// 3. The needed business exception to rollback.
completeTransactionAfterThrowing(txInfo, tx, ex);
throw ex;

上面这个ex,在事物回滚时,其实不会throw 出来。而是有包装类TransactionalExecutor.ExecutionException来进行包装处理。当异常不需要回滚时,才会直接抛出业务中的异常。至此,AT模式下整个业务都处理完毕。

AT机制基于两个阶段提交进行处理来完成分布式事务的提交和回滚。官网对整个流程有一个详细的流程定义。

看上述时序图可以得知,有本地锁和全局锁之分。

本地锁

本地锁在发起本地提交时,是有sql中的for update来进行本地锁绑定。connect进行commit后,自动释放本地锁。

全局锁

加锁

同步逐步调用,我们可以知道是在SelectForUpdate中进行实现的锁的相关处理。

执行sql后,判断是否加锁成功,如果没有成功则抛出异常,回滚掉当前的提交。适配全局事务或者加了GlobalLock注解的方法。这里有一个知识点,如果不想开启全局事务,只是需要在不同服务中互斥数据库操作。可以添加GlobalLock注解即可。

提交全局事务,发起全局事务提交操作。

发起同步请求到TC,TC会同步删除全局锁。便于后续其他服务操作该数据。