seata tcc模式下的一个性能问题

  • 2020 年 1 月 13 日
  • 笔记

本文解释Seata中,AT模式和MT模式下的一个一阶段的区别。

根据两阶段行为模式的不同,Seata将分支事务划分为2种:

  • Automatic (Branch) Transaction Mode
  • Manual (Branch) Transaction Mode

1.AT模式

AT 模式基于 支持本地 ACID 事务 的 关系型数据库:

一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。 二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。 二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。

2. MT模式

相应的,MT 模式,不依赖于底层数据资源的事务支持: 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。 二阶段 commit 行为:调用 自定义 的 commit 逻辑。 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。 所谓 MT 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。

3.一阶段解读

在AT模式下,一阶段会做如下几个操作:

1.解析业务sql; 2.获取sql执行前的镜像,前镜像; 3.执行业务sql; 4.获取sql执行后的镜像,后镜像; 5.添加undo_log日志,把前后镜像数据和业务sql相关的信息组成回滚日志,添加到undo_log中; 6.向TC注册分支事务,并申请相关目标数据的全局锁; 7.事务提交,将业务操作和undo_log一起提交; 8.上报分支事务提交结果给TC; 9.释放本地锁; 10.释放数据库连接;

在AT模式下,一阶段,会有如上的多个步骤,以及解析存储undo_log等操作;那么,在MT模式中,由于prepare逻辑有对应的rollback逻辑,显然这里是不用再添加回滚信息的。那么,这MT模式下一阶段的处理逻辑,是如何避免上述操作带来的性能损耗呢?

4.源码分析

在方法拦截器MethodInterceptor接口下,有一个TCC拦截器实现类TccActionInterceptor,这个实现类有一个invoke方法:

   @Override      public Object invoke(final MethodInvocation invocation) throws Throwable {          if(!RootContext.inGlobalTransaction()){              //not in transaction              return invocation.proceed();          }          Method method = getActionInterfaceMethod(invocation);          TwoPhaseBusinessAction businessAction = method.getAnnotation(TwoPhaseBusinessAction.class);          //try method          if (businessAction != null) {              //save the xid              String xid = RootContext.getXID();              //clear the context              RootContext.unbind();              try {                  Object[] methodArgs = invocation.getArguments();                  //Handler the TCC Aspect                  Map<String, Object> ret = actionInterceptorHandler.proceed(method, methodArgs, xid, businessAction,                          new Callback<Object>() {                              @Override                              public Object execute() throws Throwable {                                  return invocation.proceed();                              }                          });                  //return the final result                  return ret.get(Constants.TCC_METHOD_RESULT);              } finally {                  //recovery the context                  RootContext.bind(xid);              }          }          return invocation.proceed();      }

可以看到,在切面的切入点执行之前,和之后,有2个关键操作: 把xid解绑

//save the xid  String xid = RootContext.getXID();  //clear the context  RootContext.unbind();

恢复xid绑定

//recovery the context  RootContext.bind(xid);

这么做的目的是什么呢?

当把xid解绑后,tcc的这个prepare分支事务执行时,框架不会拦截业务sql进行解析,也不会存储前后镜像和生成undo_log日志,(即使使用了代理数据源,也不会)这样,tcc模式下,就避免了一阶段的上述操作带来的损耗。