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模式下,就避免了一阶段的上述操作带来的损耗。