Springboot之分布式事务框架Seata实现原理源码分析

开发 前端 分布式
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。

[[415757]]

环境:springboot2.2.11 + seata1.3.0

1 准备环境

  1. <dependency> 
  2.   <groupId>com.alibaba.cloud</groupId> 
  3.   <artifactId>spring-cloud-starter-alibaba-seata</artifactId> 
  4.   <exclusions> 
  5.     <exclusion> 
  6.       <groupId>io.seata</groupId> 
  7.       <artifactId>seata-all</artifactId> 
  8.     </exclusion> 
  9.   </exclusions> 
  10. </dependency> 
  11. <dependency> 
  12.   <groupId>io.seata</groupId> 
  13.   <artifactId>seata-all</artifactId> 
  14.   <version>1.3.0</version> 
  15. </dependency> 

开启全局事务

  1. seata: 
  2.   service: 
  3.     disable-global-transactiontrue   

2 代理数据源及注册代理Bean

  1. @Bean 
  2. @DependsOn({BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, BEAN_NAME_FAILURE_HANDLER}) 
  3. @ConditionalOnMissingBean(GlobalTransactionScanner.class) 
  4. public GlobalTransactionScanner globalTransactionScanner(SeataProperties seataProperties, FailureHandler failureHandler) { 
  5.   if (LOGGER.isInfoEnabled()) { 
  6.     LOGGER.info("Automatically configure Seata"); 
  7.   } 
  8.   return new GlobalTransactionScanner(seataProperties.getApplicationId(), seataProperties.getTxServiceGroup(),failureHandler); 
  9.  
  10. @Bean(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR) 
  11. @ConditionalOnProperty(prefix = StarterConstants.SEATA_PREFIX, name = {"enableAutoDataSourceProxy""enable-auto-data-source-proxy"}, havingValue = "true", matchIfMissing = true
  12. @ConditionalOnMissingBean(SeataAutoDataSourceProxyCreator.class) 
  13. public SeataAutoDataSourceProxyCreator seataAutoDataSourceProxyCreator(SeataProperties seataProperties) { 
  14.   return new SeataAutoDataSourceProxyCreator(seataProperties.isUseJdkProxy(),seataProperties.getExcludesForAutoProxying()); 

2.1 创建代理Bean

Seata通过GlobalTransactionScanner来注册我们项目中所有带有@GlobalTransactional注解的方法类。

  1. public class GlobalTransactionScanner extends AbstractAutoProxyCreator implements InitializingBean, ApplicationContextAware, DisposableBean 

AbstractAutoProxyCreator继承层次

Springboot之分布式事务框架Seata实现原理源码分析

从这里也知道GlobalTransactionScanner类其实是一个BeanPostProcessor处理器。

InstantiationAwareBeanPostProcessor类有如下3个方法是很有用的

postProcessBeforeInstantiation 实例化前执行

postProcessAfterInstantiation 实例化之后执行

postProcessProperties 属性填充时执行

当然在这里GlobalTransactionScanner类并没有覆盖这3个方法。

BeanPostProcessor相关的2个方法在父类中有实现

  1. @Override 
  2. public Object postProcessBeforeInitialization(Object bean, String beanName) { 
  3.   return bean; 
  4. @Override 
  5. public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { 
  6.   if (bean != null) { 
  7.     Object cacheKey = getCacheKey(bean.getClass(), beanName); 
  8.     if (this.earlyProxyReferences.remove(cacheKey) != bean) { 
  9.       return wrapIfNecessary(bean, beanName, cacheKey); 
  10.     } 
  11.   } 
  12.   return bean; 

在实例化Bean的时候会执行父类中

postProcessAfterInitialization方法。关键是该方法中的wrapIfNecessary方法,该方法在GlobalTransactionScanner类中被重写了。

Springboot之分布式事务框架Seata实现原理源码分析

existsAnnotation 方法判断当前的类方法上是否有@GlobalTransactional注解。如果不存在会直接返回当前Bean。

interceptor 判断当前拦截器是否为空,为空创建

GlobalTransactionalInterceptor该拦截器处理全局事务的地方。

  1. if (!AopUtils.isAopProxy(bean)) { 
  2.   bean = super.wrapIfNecessary(bean, beanName, cacheKey); 
  3. else { 
  4.   AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean); 
  5.   Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(nullnullnull)); 
  6.   for (Advisor avr : advisor) { 
  7.     advised.addAdvisor(0, avr); 
  8.   } 

该片段代码,判断当前的Bean是否是代理类(JDK或CGLIB),如果不是那么会先的执行下父类的wrapIfNecessary方法。

如果当前Bean是代理类对象,那么会获取当前代理类的AdvisedSupport(内部维护了切面类的集合)对象。这里可以看看JDK和CGLIB两种方式创建的代理类对象是否都具有AdvisedSupport对象。

  1. public static AdvisedSupport getAdvisedSupport(Object proxy) throws Exception { 
  2.   Field h; 
  3.   if (AopUtils.isJdkDynamicProxy(proxy)) { 
  4.     h = proxy.getClass().getSuperclass().getDeclaredField("h"); 
  5.   } else { 
  6.     h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0"); 
  7.   } 
  8.   h.setAccessible(true); 
  9.   Object dynamicAdvisedInterceptor = h.get(proxy); 
  10.   Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised"); 
  11.   advised.setAccessible(true); 
  12.   return (AdvisedSupport)advised.get(dynamicAdvisedInterceptor); 

jdk创建代理对象时使用的InvocationHandler

  1. final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { 
  2.  
  3.   /** We use a static Log to avoid serialization issues. */ 
  4.   private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class); 
  5.  
  6.   /** Config used to configure this proxy. */ 
  7.   private final AdvisedSupport advised; 
  8. }     

cglib 获取CGLIB$CALLBACK_0字段,该字段是MethodInterceptor对象

  1. public class PersonDAOImpl$$EnhancerBySpringCGLIB$$d4658dad extends PersonDAOImpl implements SpringProxy, Advised, Factory{ 
  2.   private boolean CGLIB$BOUND; 
  3.   public static Object CGLIB$FACTORY_DATA; 
  4.   private static final ThreadLocal CGLIB$THREAD_CALLBACKS; 
  5.   private static final Callback[] CGLIB$STATIC_CALLBACKS; 
  6.   private MethodInterceptor CGLIB$CALLBACK_0; 
  7. }     

 接下来就是将Seata的拦截器添加到AdvisedSupport中。

  1. Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(nullnullnull)); 
  2. for (Advisor avr : advisor) { 
  3.   advised.addAdvisor(0, avr); 
  4. protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource customTargetSource) throws BeansException { 
  5.   return new Object[]{interceptor}; 

到此就将Seata的方法拦截器包装成Advisor切面添加到了当前的AdvisedSupport管理的切面集合中。

2.2 创建代理数据源

对数据源上的方法调用进行代理处理通过DataSourceProxy

  1. public class SeataAutoDataSourceProxyCreator extends AbstractAutoProxyCreator { 
  2.   private static final Logger LOGGER = LoggerFactory.getLogger(SeataAutoDataSourceProxyCreator.class); 
  3.   private final String[] excludes; 
  4.   private final Advisor advisor = new DefaultIntroductionAdvisor(new SeataAutoDataSourceProxyAdvice()); 
  5.  
  6.   public SeataAutoDataSourceProxyCreator(boolean useJdkProxy, String[] excludes) { 
  7.     this.excludes = excludes; 
  8.     setProxyTargetClass(!useJdkProxy); 
  9.   } 
  10.  
  11.   @Override 
  12.   protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) throws BeansException { 
  13.     if (LOGGER.isInfoEnabled()) { 
  14.       LOGGER.info("Auto proxy of [{}]", beanName); 
  15.     } 
  16.     return new Object[]{advisor}; 
  17.   } 
  18.  
  19.   @Override 
  20.   protected boolean shouldSkip(Class<?> beanClass, String beanName) { 
  21.     return SeataProxy.class.isAssignableFrom(beanClass) || !DataSource.class.isAssignableFrom(beanClass) ||                   Arrays.asList(excludes).contains(beanClass.getName()); 
  22.     } 

shouldSkip 该方法确定了如果当前beanClass是SeataProxy的子类并且beanClass不是DataSource的子类或者当前的bean名称不再excludes集合中就会进行代理。简单说就是代理当前系统的默认数据源对象。

getAdvicesAndAdvisorsForBean 方法直接返回DefaultIntroductionAdvisor切面,通知类是SeataAutoDataSourceProxyAdvice

  1. public class SeataAutoDataSourceProxyAdvice implements MethodInterceptor, IntroductionInfo { 
  2.  
  3.   @Override 
  4.   public Object invoke(MethodInvocation invocation) throws Throwable { 
  5.     DataSourceProxy dataSourceProxy = DataSourceProxyHolder.get().putDataSource((DataSource) invocation.getThis()); 
  6.     Method method = invocation.getMethod(); 
  7.     Object[] args = invocation.getArguments(); 
  8.     Method m = BeanUtils.findDeclaredMethod(DataSourceProxy.class, method.getName(), method.getParameterTypes()); 
  9.     if (m != null) { 
  10.       return m.invoke(dataSourceProxy, args); 
  11.     } else { 
  12.       return invocation.proceed(); 
  13.     } 
  14.   } 
  15.  
  16.   @Override 
  17.   public Class<?>[] getInterfaces() { 
  18.     return new Class[]{SeataProxy.class}; 
  19.   } 
  20.  

3 全局事务拦截器

在需要进行发起全局事务的方法是被代理的 具体执行的拦截器是

GlobalTransactionalInterceptor

Springboot之分布式事务框架Seata实现原理源码分析

handleGlobalTransaction方法

通过事务模版执行,TransactionalExecutor类进行收集当前@GlobalTransactional注解上配置的相关信息封装到TransactionInfo中。

TransactionalTemplate.execute方法

  1. // 该方法中根据不同的事务传播特性进行不同的处理。 
  2. public Object execute(TransactionalExecutor business) throws Throwable { 
  3.     // 1 get transactionInfo 
  4.     TransactionInfo txInfo = business.getTransactionInfo(); 
  5.     if (txInfo == null) { 
  6.         throw new ShouldNeverHappenException("transactionInfo does not exist"); 
  7.     } 
  8.     // 1.1 get or create a transaction 
  9.     GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate(); 
  10.  
  11.     // 1.2 Handle the Transaction propatation and the branchType 
  12.     Propagation propagation = txInfo.getPropagation(); 
  13.     SuspendedResourcesHolder suspendedResourcesHolder = null
  14.     try { 
  15.         switch (propagation) { 
  16.             case NOT_SUPPORTED: 
  17.                 suspendedResourcesHolder = tx.suspend(true); 
  18.                 return business.execute(); 
  19.             case REQUIRES_NEW: 
  20.                 suspendedResourcesHolder = tx.suspend(true); 
  21.                 break; 
  22.             case SUPPORTS: 
  23.                 if (!existingTransaction()) { 
  24.                     return business.execute(); 
  25.                 } 
  26.                 break; 
  27.             case REQUIRED: 
  28.                 break; 
  29.             case NEVER: 
  30.                 // 存在事务抛出异常 
  31.                 if (existingTransaction()) { 
  32.                     throw new TransactionException( 
  33.                         String.format("Existing transaction found for transaction marked with propagation 'never',xid = %s" 
  34.                                       ,RootContext.getXID())); 
  35.                 } else { 
  36.                     // 直接执行业务代码 
  37.                     return business.execute(); 
  38.                 } 
  39.             case MANDATORY: 
  40.                 if (!existingTransaction()) { 
  41.                     throw new TransactionException("No existing transaction found for transaction marked with propagation 'mandatory'"); 
  42.                 } 
  43.                 break; 
  44.             default
  45.                 throw new TransactionException("Not Supported Propagation:" + propagation); 
  46.         } 
  47.  
  48.         try { 
  49.  
  50.             // 2. 开始事务 
  51.             beginTransaction(txInfo, tx); 
  52.  
  53.             Object rs = null
  54.             try { 
  55.  
  56.                 // 执行我们的业务代码 
  57.                 rs = business.execute(); 
  58.  
  59.             } catch (Throwable ex) { 
  60.  
  61.                 // 3.the needed business exception to rollback
  62.                 completeTransactionAfterThrowing(txInfo, tx, ex); 
  63.                 throw ex; 
  64.             } 
  65.  
  66.             // 4. 一切正常提交事务。 
  67.             commitTransaction(tx); 
  68.  
  69.             return rs; 
  70.         } finally { 
  71.             //5. clear 
  72.             triggerAfterCompletion(); 
  73.             cleanUp(); 
  74.         } 
  75.     } finally { 
  76.         tx.resume(suspendedResourcesHolder); 
  77.     } 
  78.  

3.1 获取全局事务

  1. GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate(); 
  2. public static GlobalTransaction getCurrentOrCreate() { 
  3.   // 首次这里会返回null,执行createNew方法   
  4.   GlobalTransaction tx = getCurrent(); 
  5.   if (tx == null) { 
  6.     return createNew(); 
  7.   } 
  8.   return tx; 
  9. // 获取全局事务对象 
  10. private static GlobalTransaction getCurrent() { 
  11.   String xid = RootContext.getXID(); 
  12.   if (xid == null) { 
  13.     return null
  14.   } 
  15.   return new DefaultGlobalTransaction(xid, GlobalStatus.Begin, GlobalTransactionRole.Participant); 
  16. private static GlobalTransaction createNew() { 
  17.   return new DefaultGlobalTransaction(); 

3.2 开始全局事务

  1. beginTransaction(txInfo, tx); 
  2. private void beginTransaction(TransactionInfo txInfo, GlobalTransaction tx) throws TransactionalExecutor.ExecutionException { 
  3.   try { 
  4.     triggerBeforeBegin(); 
  5.     tx.begin(txInfo.getTimeOut(), txInfo.getName()); 
  6.     triggerAfterBegin(); 
  7.   } catch (TransactionException txe) { 
  8.     throw new TransactionalExecutor.ExecutionException(tx, txe, TransactionalExecutor.Code.BeginFailure); 
  9.   } 
  10. // 开始全局事务,并且通过TC获取全局事务唯一ID  xid 
  11. public void begin(int timeout, String name) throws TransactionException { 
  12.   // 全局事务的开启必须是Launcher   
  13.   if (role != GlobalTransactionRole.Launcher) { 
  14.     assertXIDNotNull(); 
  15.     if (LOGGER.isDebugEnabled()) { 
  16.       LOGGER.debug("Ignore Begin(): just involved in global transaction [{}]", xid); 
  17.     } 
  18.     return
  19.   } 
  20.   assertXIDNull(); 
  21.   if (RootContext.getXID() != null) { 
  22.     throw new IllegalStateException(); 
  23.   } 
  24.   xid = transactionManager.begin(nullnullname, timeout); 
  25.   status = GlobalStatus.Begin
  26.   // 将当前获取到的xid绑定到当前thread上(ThreadLocal)   
  27.   RootContext.bind(xid); 
  28.   if (LOGGER.isInfoEnabled()) { 
  29.     LOGGER.info("Begin new global transaction [{}]", xid); 
  30.   } 

将xid绑定到当前执行thread(ThreadLocal)在这里seata是通过SPI技术来实现的

  1. private static ContextCore CONTEXT_HOLDER = ContextCoreLoader.load(); 
  2. public static void bind(String xid) { 
  3.   if (LOGGER.isDebugEnabled()) { 
  4.     LOGGER.debug("bind {}", xid); 
  5.   } 
  6.   CONTEXT_HOLDER.put(KEY_XID, xid); 
  7. //通过SPI加载具体的ContextCore实现 
  8. public class ContextCoreLoader { 
  9.  
  10.   private ContextCoreLoader() { 
  11.   } 
  12.  
  13.   private static class ContextCoreHolder { 
  14.     private static final ContextCore INSTANCE = Optional.ofNullable(EnhancedServiceLoader.load(ContextCore.class)).orElse(new ThreadLocalContextCore()); 
  15.   } 
  16.   public static ContextCore load() { 
  17.     return ContextCoreHolder.INSTANCE; 
  18.   } 
  19.  
  20. // META-INF/services/io.seata.core.context.ContextCore 文件内容 
  21. io.seata.core.context.ThreadLocalContextCore 
  22. io.seata.core.context.FastThreadLocalContextCore 

 3.3 执行本地业务

执行本地事务时会向TC注册分支然后提交本地事务,接下来看看本地分支事务的注册及处理。

  1. rs = business.execute(); 

3.3.1 提交本地事务

执行完业务代码后提交事务ConnectionProxy.commit()

  1. @Override 
  2. public void commit() throws SQLException { 
  3.   try { 
  4.     LOCK_RETRY_POLICY.execute(() -> { 
  5.       // 事务提交   
  6.       doCommit(); 
  7.       return null
  8.     }); 
  9.   } catch (SQLException e) { 
  10.     throw e; 
  11.   } catch (Exception e) { 
  12.     throw new SQLException(e); 
  13.   } 
  14. private void doCommit() throws SQLException { 
  15.   // 判断当前是否在全局事务中(xid != null)   
  16.   if (context.inGlobalTransaction()) { 
  17.     // 处理全局事务   
  18.     processGlobalTransactionCommit(); 
  19.   } else if (context.isGlobalLockRequire()) { 
  20.     processLocalCommitWithGlobalLocks(); 
  21.   } else { 
  22.     targetConnection.commit(); 
  23.   } 

进入

processGlobalTransactionCommit方法

3.3.2 注册本次事务分支

  1. private void processGlobalTransactionCommit() throws SQLException { 
  2.   try { 
  3.     register(); 
  4.   } catch (TransactionException e) { 
  5.     recognizeLockKeyConflictException(e, context.buildLockKeys()); 
  6.   } 
  7.   try { 
  8.     UndoLogManagerFactory.getUndoLogManager(this.getDbType()).flushUndoLogs(this); 
  9.     targetConnection.commit(); 
  10.   } catch (Throwable ex) { 
  11.     report(false); 
  12.     throw new SQLException(ex); 
  13.   } 
  14.   if (IS_REPORT_SUCCESS_ENABLE) { 
  15.     report(true); 
  16.   } 
  17.   context.reset(); 
  18.  
  19. // 注册本次事务执行RM,将返回的branchId保存到当前的上下文中 
  20. private void register() throws TransactionException { 
  21.   if (!context.hasUndoLog() || context.getLockKeysBuffer().isEmpty()) { 
  22.     return
  23.   } 
  24.   Long branchId = DefaultResourceManager.get().branchRegister(BranchType.AT, getDataSourceProxy().getResourceId(), null, context.getXid(), null, context.buildLockKeys()); 
  25.   // 将注册RM返回的branchId绑定到当前的上下文中ConnectionContext   
  26.   context.setBranchId(branchId); 

进入branchRegister方法

DefaultResourceManager.java

  1. @Override 
  2. public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String applicationData, String lockKeys) 
  3.   throws TransactionException { 
  4.   return getResourceManager(branchType).branchRegister(branchType, resourceId, clientId, xid, applicationData, lockKeys); 
  1. @Override 
  2. public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String applicationData, String lockKeys) throws TransactionException { 
  3.   try { 
  4.     BranchRegisterRequest request = new BranchRegisterRequest(); 
  5.     request.setXid(xid); 
  6.     request.setLockKey(lockKeys); 
  7.     request.setResourceId(resourceId); 
  8.     request.setBranchType(branchType); 
  9.     request.setApplicationData(applicationData); 
  10.  
  11.     BranchRegisterResponse response = (BranchRegisterResponse) RmNettyRemotingClient.getInstance().sendSyncRequest(request); 
  12.     if (response.getResultCode() == ResultCode.Failed) { 
  13.       throw new RmTransactionException(response.getTransactionExceptionCode(), String.format("Response[ %s ]", response.getMsg())); 
  14.     } 
  15.     return response.getBranchId(); 
  16.   } catch (TimeoutException toe) { 
  17.     throw new RmTransactionException(TransactionExceptionCode.IO, "RPC Timeout", toe); 
  18.   } catch (RuntimeException rex) { 
  19.     throw new RmTransactionException(TransactionExceptionCode.BranchRegisterFailed, "Runtime", rex); 
  20.   } 

3.3.3 记录undo log日志

undo log主要记录了数据的逻辑变化,比如一条 INSERT 语句,对应一条DELETE 的 undo log ,对于每个 UPDATE 语句,对应一条相反的 UPDATE 的 undo log ,这样在发生错误时,就能回滚到事务之前的数据状态。

  1. UndoLogManagerFactory.getUndoLogManager(this.getDbType()).flushUndoLogs(this); 
  2. // 记录undo log日志 
  3. public void flushUndoLogs(ConnectionProxy cp) throws SQLException { 
  4.   ConnectionContext connectionContext = cp.getContext(); 
  5.   if (!connectionContext.hasUndoLog()) { 
  6.     return
  7.   } 
  8.  
  9.   String xid = connectionContext.getXid(); 
  10.   long branchId = connectionContext.getBranchId(); 
  11.  
  12.   BranchUndoLog branchUndoLog = new BranchUndoLog(); 
  13.   branchUndoLog.setXid(xid); 
  14.   branchUndoLog.setBranchId(branchId); 
  15.   branchUndoLog.setSqlUndoLogs(connectionContext.getUndoItems()); 
  16.  
  17.   UndoLogParser parser = UndoLogParserFactory.getInstance(); 
  18.   byte[] undoLogContent = parser.encode(branchUndoLog); 
  19.  
  20.   if (LOGGER.isDebugEnabled()) { 
  21.     LOGGER.debug("Flushing UNDO LOG: {}", new String(undoLogContent, Constants.DEFAULT_CHARSET)); 
  22.   } 
  23.  
  24.   // 将undo log日志出入到undo_log表中   
  25.   insertUndoLogWithNormal(xid, branchId, buildContext(parser.getName()), undoLogContent, cp.getTargetConnection()); 
  26. // 这里会根据是Oracle或MySQL自动执行;我当前环境使用的MySQL,所以使用的是MySQLUndoLogManager 
  27. @Override 
  28. protected void insertUndoLogWithNormal(String xid, long branchId, String rollbackCtx, 
  29.                                        byte[] undoLogContent, Connection conn) throws SQLException { 
  30.     insertUndoLog(xid, branchId, rollbackCtx, undoLogContent, State.Normal, conn); 
  31. private void insertUndoLog(String xid, long branchId, String rollbackCtx, byte[] undoLogContent, State state, Connection conn) throws SQLException { 
  32.     try (PreparedStatement pst = conn.prepareStatement(INSERT_UNDO_LOG_SQL)) { 
  33.         pst.setLong(1, branchId); 
  34.         pst.setString(2, xid); 
  35.         pst.setString(3, rollbackCtx); 
  36.         pst.setBlob(4, BlobUtils.bytes2Blob(undoLogContent)); 
  37.         pst.setInt(5, state.getValue()); 
  38.         pst.executeUpdate(); 
  39.     } catch (Exception e) { 
  40.         if (!(e instanceof SQLException)) { 
  41.             e = new SQLException(e); 
  42.         } 
  43.         throw (SQLException) e; 
  44.     } 

3.3.4 本地事务提交

业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。

  1. targetConnection.commit(); 

3.3.5 重置当前上下文

将当前ConnectionProxy中的ConnectionContext重置

  1. context.reset(); 
  2. // 重置 
  3. void reset(String xid) { 
  4.   this.xid = xid; 
  5.   branchId = null
  6.   this.isGlobalLockRequire = false
  7.   lockKeysBuffer.clear(); 
  8.   sqlUndoItemsBuffer.clear(); 

到此整个全局事务的第一阶段完成了(通过feign的调用也成功返回);接下来是第二阶段的提交。

3.4 全局事物提交

  1. // TransactionalTemplate.java 
  2. commitTransaction(tx); 
  3. private void commitTransaction(GlobalTransaction tx) throws TransactionalExecutor.ExecutionException { 
  4.   try { 
  5.     triggerBeforeCommit(); 
  6.     tx.commit(); 
  7.     triggerAfterCommit(); 
  8.   } catch (TransactionException txe) { 
  9.     // 4.1 Failed to commit 
  10.     throw new TransactionalExecutor.ExecutionException(tx, txe, TransactionalExecutor.Code.CommitFailure); 
  11.   } 
  12. // DefaultGlobalTransaction.java 
  13. public void commit() throws TransactionException { 
  14.   int retry = COMMIT_RETRY_COUNT <= 0 ? DEFAULT_TM_COMMIT_RETRY_COUNT : COMMIT_RETRY_COUNT; 
  15.   try { 
  16.     while (retry > 0) { 
  17.       try { 
  18.         // 根据当前的全局唯一事务id xid提交事务。 
  19.         status = transactionManager.commit(xid); 
  20.         break; 
  21.       } catch (Throwable ex) { 
  22.         retry--; 
  23.           if (retry == 0) { 
  24.             throw new TransactionException("Failed to report global commit", ex); 
  25.           } 
  26.       } 
  27.     } 
  28.   } finally { 
  29.     if (RootContext.getXID() != null && xid.equals(RootContext.getXID())) { 
  30.       suspend(true); 
  31.     } 
  32.   } 
  33. // DefaultTransactionManager.java 
  34. @Override 
  35. public GlobalStatus commit(String xid) throws TransactionException { 
  36.   GlobalCommitRequest globalCommit = new GlobalCommitRequest(); 
  37.   globalCommit.setXid(xid); 
  38.   GlobalCommitResponse response = (GlobalCommitResponse) syncCall(globalCommit); 
  39.   return response.getGlobalStatus(); 
  40. // 通过Netty同步调用 
  41. private AbstractTransactionResponse syncCall(AbstractTransactionRequest request) throws TransactionException { 
  42.   try { 
  43.     return (AbstractTransactionResponse) TmNettyRemotingClient.getInstance().sendSyncRequest(request); 
  44.   } catch (TimeoutException toe) { 
  45.     throw new TmTransactionException(TransactionExceptionCode.IO, "RPC timeout", toe); 
  46.   } 

到此第二阶段的事务就提交完成了。

3.5 全局事务回滚

参与全局事务的任何一个分支发生异常将对整个事务进行回滚。

3.5.1 全局事务发起端异常

代码片段

  1. try { 
  2.   // Do Your Business 
  3.   rs = business.execute(); 
  4. } catch (Throwable ex) { 
  5.   // 3.the needed business exception to rollback
  6.   completeTransactionAfterThrowing(txInfo, tx, ex); 
  7.   throw ex; 

当本地业务执行时发生异常后执行

completeTransactionAfterThrowing方法。

  1. private void completeTransactionAfterThrowing(TransactionInfo txInfo, GlobalTransaction tx, Throwable originalException) throws TransactionalExecutor.ExecutionException { 
  2.   //roll back 
  3.   if (txInfo != null && txInfo.rollbackOn(originalException)) { 
  4.     try { 
  5.       // 全局事务回滚   
  6.       rollbackTransaction(tx, originalException); 
  7.     } catch (TransactionException txe) { 
  8.       // Failed to rollback 
  9.       throw new TransactionalExecutor.ExecutionException(tx, txe, TransactionalExecutor.Code.RollbackFailure, originalException); 
  10.     } 
  11.   } else { 
  12.     // not roll back on this exception, so commit 
  13.     commitTransaction(tx); 
  14.   } 

进入rollbackTransaction方法。

  1. private void rollbackTransaction(GlobalTransaction tx, Throwable originalException) throws TransactionException, TransactionalExecutor.ExecutionException { 
  2.   triggerBeforeRollback(); 
  3.   tx.rollback(); 
  4.   triggerAfterRollback(); 
  5.   // 3.1 Successfully rolled back 
  6.   throw new TransactionalExecutor.ExecutionException(tx, GlobalStatus.RollbackRetrying.equals(tx.getLocalStatus()) ? TransactionalExecutor.Code.RollbackRetrying : TransactionalExecutor.Code.RollbackDone, originalException); 

 进入rollback方法。

  1. public void rollback() throws TransactionException { 
  2.   // Participant(参与者),如果当前是参与者那么直接返回,全局事务的回滚必须是Launcher 
  3.   if (role == GlobalTransactionRole.Participant) { 
  4.     return
  5.   } 
  6.   assertXIDNotNull(); 
  7.   int retry = ROLLBACK_RETRY_COUNT <= 0 ? DEFAULT_TM_ROLLBACK_RETRY_COUNT : ROLLBACK_RETRY_COUNT; 
  8.   try { 
  9.     while (retry > 0) { 
  10.       try { 
  11.         status = transactionManager.rollback(xid); 
  12.         break; 
  13.       } catch (Throwable ex) { 
  14.         retry--; 
  15.         if (retry == 0) { 
  16.           throw new TransactionException("Failed to report global rollback", ex); 
  17.         } 
  18.       } 
  19.     } 
  20.   } finally { 
  21.     if (RootContext.getXID() != null && xid.equals(RootContext.getXID())) { 
  22.       suspend(true); 
  23.     } 
  24.   } 

进入

transactionManager.rollback方法

  1. public GlobalStatus rollback(String xid) throws TransactionException { 
  2.   GlobalRollbackRequest globalRollback = new GlobalRollbackRequest(); 
  3.   globalRollback.setXid(xid); 
  4.   GlobalRollbackResponse response = (GlobalRollbackResponse) syncCall(globalRollback); 
  5.   return response.getGlobalStatus(); 

到此全局事务就进行了回滚

3.5.2 全局事务参与者异常

4 XID的传递

4.1 RestTemplate

  1. @Configuration(proxyBeanMethods = false
  2. public class SeataRestTemplateAutoConfiguration { 
  3.  
  4.   // 该拦截器的作用就是为请求的Header中传递XID 
  5.   @Bean 
  6.   public SeataRestTemplateInterceptor seataRestTemplateInterceptor() { 
  7.     return new SeataRestTemplateInterceptor(); 
  8.   } 
  9.   // 获取当前IOC容器中所有的的RestTemplate对象 
  10.   @Autowired(required = false
  11.   private Collection<RestTemplate> restTemplates; 
  12.  
  13.   @Autowired 
  14.   private SeataRestTemplateInterceptor seataRestTemplateInterceptor; 
  15.  
  16.   // 当前这个Bean(SeataRestTemplateAutoConfiguration)被创建且相关属性被注入后执行 
  17.   @PostConstruct 
  18.   public void init() { 
  19.     if (this.restTemplates != null) { 
  20.       // 为所有的RestTemplate设置一个拦截器。SeataRestTemplateInterceptor 
  21.       for (RestTemplate restTemplate : restTemplates) { 
  22.         List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>(restTemplate.getInterceptors()); 
  23.         interceptors.add(this.seataRestTemplateInterceptor); 
  24.         restTemplate.setInterceptors(interceptors); 
  25.       } 
  26.     } 
  27.   } 
  1. public class SeataRestTemplateInterceptor implements ClientHttpRequestInterceptor { 
  2.  
  3.   @Override 
  4.   public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { 
  5.     HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpRequest); 
  6.  
  7.     String xid = RootContext.getXID(); 
  8.  
  9.     if (!StringUtils.isEmpty(xid)) { 
  10.       requestWrapper.getHeaders().add(RootContext.KEY_XID, xid); 
  11.     } 
  12.     return clientHttpRequestExecution.execute(requestWrapper, bytes); 
  13.   } 
  14.  

该拦截器的作用就是为当前的请求header中放置TX_XID头信息。(是不是有点过分了,所有的RestTemplate都添加这个Header;应该像@LoadBalanced一样只有添加有该注解的才具有负载均衡的作用)。

到此RestTemplate调用方式传递XID值信息就这么简单。

4.2 Feign

  1. @Configuration(proxyBeanMethods = false
  2. @ConditionalOnClass(Client.class) 
  3. @AutoConfigureBefore(FeignAutoConfiguration.class) 
  4. public class SeataFeignClientAutoConfiguration { 
  5.   @Bean 
  6.   @Scope("prototype"
  7.   @ConditionalOnClass(name = "com.netflix.hystrix.HystrixCommand"
  8.   @ConditionalOnProperty(name = "feign.hystrix.enabled", havingValue = "true"
  9.   Feign.Builder feignHystrixBuilder(BeanFactory beanFactory) { 
  10.     return SeataHystrixFeignBuilder.builder(beanFactory); 
  11.   } 

每一个Feign客户端是一个FeignClientFactoryBean 工厂Bean。当在调用接口的时候会执行getObject方法

  1. @Override 
  2. public Object getObject() throws Exception { 
  3.   return getTarget(); 
  4.  
  5. <T> T getTarget() { 
  6.   FeignContext context = this.applicationContext.getBean(FeignContext.class); 
  7.   // 这个方法会从当前的IOC容器中获取Feign.Builder对象   
  8.   Feign.Builder builder = feign(context); 
  9.  
  10.   if (!StringUtils.hasText(this.url)) { 
  11.     if (!this.name.startsWith("http")) { 
  12.       this.url = "http://" + this.name
  13.     } else { 
  14.       this.url = this.name
  15.     } 
  16.     this.url += cleanPath(); 
  17.     // 负载均衡调用目标服务(通过服务发现调用)   
  18.     return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, this.url)); 
  19.   } 
  20.   // 下面是通过配置的url直接调用目标服务。   
  21.   if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) { 
  22.     this.url = "http://" + this.url; 
  23.   } 
  24.   String url = this.url + cleanPath(); 
  25.   Client client = getOptional(context, Client.class); 
  26.   if (client != null) { 
  27.     if (client instanceof LoadBalancerFeignClient) { 
  28.       client = ((LoadBalancerFeignClient) client).getDelegate(); 
  29.     } 
  30.     if (client instanceof FeignBlockingLoadBalancerClient) { 
  31.       client = ((FeignBlockingLoadBalancerClient) client).getDelegate(); 
  32.     } 
  33.     builder.client(client); 
  34.   } 
  35.   Targeter targeter = get(context, Targeter.class); 
  36.   return (T) targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url)); 

feign(content)方法在调用链中最终执行如下方法从IOC容器中获取Feign.Builder

  1. public <T> T getInstance(String name, Class<T> type) { 
  2.   AnnotationConfigApplicationContext context = getContext(name); 
  3.   if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,type).length > 0) { 
  4.     return context.getBean(type); 
  5.   } 
  6.   return null

接下来进入

SeataHystrixFeignBuilder.builder(beanFactory);方法

  1. static Feign.Builder builder(BeanFactory beanFactory) { 
  2.   return HystrixFeign.builder().retryer(Retryer.NEVER_RETRY).client(new SeataFeignClient(beanFactory)); 

SeataFeignClient类

  1. // Feign的执行就通过Client接口调用。 
  2. public class SeataFeignClient implements Client { 
  3.  
  4.   private final Client delegate; 
  5.  
  6.   private final BeanFactory beanFactory; 
  7.  
  8.   private static final int MAP_SIZE = 16; 
  9.  
  10.   SeataFeignClient(BeanFactory beanFactory) { 
  11.     this.beanFactory = beanFactory; 
  12.     this.delegate = new Client.Default(nullnull); 
  13.   } 
  14.  
  15.   SeataFeignClient(BeanFactory beanFactory, Client delegate) { 
  16.     this.delegate = delegate; 
  17.     this.beanFactory = beanFactory; 
  18.   } 
  19.  
  20.   @Override 
  21.   public Response execute(Request request, Request.Options options) throws IOException { 
  22.     Request modifiedRequest = getModifyRequest(request); 
  23.     return this.delegate.execute(modifiedRequest, options); 
  24.   } 
  25.  
  26.   // 该方法给请求headers中添加TX_XID请求header信息。  
  27.   private Request getModifyRequest(Request request) { 
  28.     String xid = RootContext.getXID(); 
  29.     if (StringUtils.isEmpty(xid)) { 
  30.       return request; 
  31.     } 
  32.  
  33.     Map<String, Collection<String>> headers = new HashMap<>(MAP_SIZE); 
  34.     headers.putAll(request.headers()); 
  35.  
  36.     List<String> seataXid = new ArrayList<>(); 
  37.     seataXid.add(xid); 
  38.     headers.put(RootContext.KEY_XID, seataXid); 
  39.     return Request.create(request.method(), request.url(), headers, request.body(), request.charset()); 
  40.   } 
  41.  

5 参与者如何加入全局事务

在被调用端(通过Feign调用服务)接口服务上没有加入任何注解或是特殊的代码那它又是如何加入到整个全局事务中的呢?

在2.2中介绍了seata自动配置会为我们自动的创建数据源代理。就是通过这个代理数据源来完成的DataSourceProxy。

事务方法在执行时都会先拿到Connection对象,这里系统默认的DataSource已经被代理成DataSourceProxy。

5.1 参与者获取XID

SeataHandlerInterceptorConfiguration注册一个拦截器SeataHandlerInterceptor;SeataHandlerInterceptor拦截器对我们的所有请求进行拦截

  1. public class SeataHandlerInterceptorConfiguration implements WebMvcConfigurer { 
  2.  
  3.   @Override 
  4.   public void addInterceptors(InterceptorRegistry registry) { 
  5.     registry.addInterceptor(new SeataHandlerInterceptor()).addPathPatterns("/**"); 
  6.   } 
  7.  

拦截器从Header中获取TX_XID

  1. public class SeataHandlerInterceptor implements HandlerInterceptor { 
  2.   @Override 
  3.   public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) { 
  4.     String xid = RootContext.getXID(); 
  5.     // 从Header中获取TX_XID信息。如果存在就绑定到RootContext上下文中。   
  6.     String rpcXid = request.getHeader(RootContext.KEY_XID); 
  7.     if (StringUtils.isBlank(xid) && rpcXid != null) { 
  8.       RootContext.bind(rpcXid); 
  9.     } 
  10.     return true
  11.   } 
  12.   @Override 
  13.   public void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception e) { 
  14.     if (StringUtils.isNotBlank(RootContext.getXID())) { 
  15.       String rpcXid = request.getHeader(RootContext.KEY_XID); 
  16.       if (StringUtils.isEmpty(rpcXid)) { 
  17.         return
  18.       } 
  19.       // 执行完后解除绑定   
  20.       String unbindXid = RootContext.unbind(); 
  21.       if (!rpcXid.equalsIgnoreCase(unbindXid)) { 
  22.         if (unbindXid != null) { 
  23.           RootContext.bind(unbindXid); 
  24.         } 
  25.       } 
  26.     } 
  27.   } 

参与者通过拦截器的方式将xid拿到并且绑定到上下文中。

5.2 获取代理连接对象

一个数据库操作执行

DataSourceProxy.getConnection方法获取ConnectionProxy对象。

  1. @Override 
  2. public ConnectionProxy getConnection() throws SQLException { 
  3.   Connection targetConnection = targetDataSource.getConnection(); 
  4.   return new ConnectionProxy(this, targetConnection); 

用ConnectionProxy代理默认数据源的的Connection对象。

在ConnectionProxy对象中有个非常重要的属性

  1. public class ConnectionProxy extends AbstractConnectionProxy { 
  2.  
  3.   private ConnectionContext context = new ConnectionContext(); 
  4.      
  5. }     

在一个数据库操作做最后事务提交的时候会通过ConnectionContext对象来判断是否是全局事务xid是否为空。

5.3 绑定XID到ConnectionContext

由于事务在提交的时候需要从ConnectionContext中获取判断是否全局事务(xid是否为空);xid是在Statement执行时进行绑定的。

执行相关SQL语句是通过StatementProxy, PreparedStatementProxy,这两个对象都是通过ConnectionProxy获取。

  1. public class PreparedStatementProxy extends AbstractPreparedStatementProxy implements PreparedStatement, ParametersHolder { 
  2.  
  3.   @Override 
  4.   public Map<Integer,ArrayList<Object>> getParameters() { 
  5.     return parameters; 
  6.   } 
  7.  
  8.   public PreparedStatementProxy(AbstractConnectionProxy connectionProxy, PreparedStatement targetStatement, String targetSQL) throws SQLException { 
  9.     super(connectionProxy, targetStatement, targetSQL); 
  10.   } 
  11.  
  12.   @Override 
  13.   public boolean execute() throws SQLException { 
  14.     return ExecuteTemplate.execute(this, (statement, args) -> statement.execute()); 
  15.   } 
  16.  
  17.   @Override 
  18.   public ResultSet executeQuery() throws SQLException { 
  19.     return ExecuteTemplate.execute(this, (statement, args) -> statement.executeQuery()); 
  20.   } 
  21.  
  22.   @Override 
  23.   public int executeUpdate() throws SQLException { 
  24.     return ExecuteTemplate.execute(this, (statement, args) -> statement.executeUpdate()); 
  25.   } 

查看executeUpdate的执行

  1. public static <T, S extends Statement> T execute(List<SQLRecognizer> sqlRecognizers, 
  2.                                                      StatementProxy<S> statementProxy, 
  3.                                                      StatementCallback<T, S> statementCallback, 
  4.                                                      Object... args) throws SQLException { 
  5.     if (!RootContext.requireGlobalLock() && !StringUtils.equals(BranchType.AT.name(), RootContext.getBranchType())) { 
  6.         // Just work as original statement 
  7.         return statementCallback.execute(statementProxy.getTargetStatement(), args); 
  8.     } 
  9.  
  10.     String dbType = statementProxy.getConnectionProxy().getDbType(); 
  11.     if (CollectionUtils.isEmpty(sqlRecognizers)) { 
  12.         sqlRecognizers = SQLVisitorFactory.get( 
  13.             statementProxy.getTargetSQL(), 
  14.             dbType); 
  15.     } 
  16.     Executor<T> executor; 
  17.     if (CollectionUtils.isEmpty(sqlRecognizers)) { 
  18.         executor = new PlainExecutor<>(statementProxy, statementCallback); 
  19.     } else { 
  20.         if (sqlRecognizers.size() == 1) { 
  21.             SQLRecognizer sqlRecognizer = sqlRecognizers.get(0); 
  22.             switch (sqlRecognizer.getSQLType()) { 
  23.                 case INSERT
  24.                     executor = EnhancedServiceLoader.load(InsertExecutor.class, dbType, 
  25.                                                           new Class[]{StatementProxy.class, StatementCallback.class, SQLRecognizer.class}, 
  26.                                                           new Object[]{statementProxy, statementCallback, sqlRecognizer}); 
  27.                     break; 
  28.                 case UPDATE
  29.                     executor = new UpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer); 
  30.                     break; 
  31.                 case DELETE
  32.                     executor = new DeleteExecutor<>(statementProxy, statementCallback, sqlRecognizer); 
  33.                     break; 
  34.                 case SELECT_FOR_UPDATE: 
  35.                     executor = new SelectForUpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer); 
  36.                     break; 
  37.                 default
  38.                     executor = new PlainExecutor<>(statementProxy, statementCallback); 
  39.                     break; 
  40.             } 
  41.         } else { 
  42.             executor = new MultiExecutor<>(statementProxy, statementCallback, sqlRecognizers); 
  43.         } 
  44.     } 
  45.     T rs; 
  46.     try { 
  47.         rs = executor.execute(args); 
  48.     } catch (Throwable ex) { 
  49.         if (!(ex instanceof SQLException)) { 
  50.             // Turn other exception into SQLException 
  51.             ex = new SQLException(ex); 
  52.         } 
  53.         throw (SQLException) ex; 
  54.     } 
  55.     return rs; 

这里的操作UpdateExecutor,DeleteExecutor,InsertExecutor(通过SPI获取实现MySQL或Oracle)他们都继承自BaseTransactionalExecutor。

  1. rs = executor.execute(args); 
  2. // 上面的execute执行BaseTransactionalExecutor.execute方法。 
  3. public class BaseTransactionalExecutor ... { 
  4.     @Override 
  5.     public T execute(Object... args) throws Throwable { 
  6.         if (RootContext.inGlobalTransaction()) { 
  7.             String xid = RootContext.getXID(); 
  8.             statementProxy.getConnectionProxy().bind(xid); 
  9.         } 
  10.  
  11.         statementProxy.getConnectionProxy().setGlobalLockRequire(RootContext.requireGlobalLock()); 
  12.         return doExecute(args); 
  13.     } 
  1. statementProxy.getConnectionProxy().bind(xid) ; 
  2. // 该行代码将xid绑定到ConnectionProxy对象中的ConnectionContext上。 
  3. public void bind(String xid) { 
  4.   context.bind(xid); 

到这xid已经绑定到了ConnectionProxy中的ConnectionContext中。

 

责任编辑:姜华 来源: 今日头条
相关推荐

2022-06-27 08:21:05

Seata分布式事务微服务

2022-06-21 08:27:22

Seata分布式事务

2022-07-10 20:24:48

Seata分布式事务

2020-12-09 09:14:57

SpringCloudSeata 分布式

2023-01-06 09:19:12

Seata分布式事务

2022-03-24 07:51:27

seata分布式事务Java

2021-04-23 08:15:51

Seata XA AT

2021-01-19 05:43:33

分布式2PC3PC

2020-04-28 12:18:08

Seata模式分布式

2023-11-06 13:15:32

分布式事务Seata

2023-07-26 08:25:02

2021-12-09 10:45:19

分布式事务框架

2022-07-03 14:03:57

分布式Seata

2023-08-17 10:23:07

扩展方案

2020-12-08 11:43:03

Spring Clou分布式Seata

2022-01-12 10:02:02

TCC模式 Seata

2021-11-10 16:10:18

鸿蒙HarmonyOS应用

2024-01-26 08:18:03

2024-01-05 07:28:50

分布式事务框架

2019-08-19 10:24:33

分布式事务数据库
点赞
收藏

51CTO技术栈公众号