前言

在之前的系列中,我们讲解了关于Seata基本介绍和实际应用,今天带来的这篇,就给大家分析一下Seata的源码是如何一步一步实现的。读源码的时候我们需要俯瞰起全貌,不要去扣一个一个的细节,这样我们学习起来会快捷而且有效率,我们学习源码需要掌握的是整体思路和核心点。

首先 Seata 客户端启动一般分为以下几个流程:

  1. 自动加载Bean属性和配置信息
  2. 初始化TM
  3. 初始化RM
  4. 初始化分布式事务客户端完成,完成代理数据库配置
  5. 连接TC(Seata服务端),注册RM和TM
  6. 开启全局事务

在这篇源码的讲解中,我们主要以AT模式为主导,官网也是主推AT模式,我们在上篇的文章中也讲解过,感兴趣的小伙伴可以去看一看分布式事务(Seata) 四大模式详解,在官网中也提供了对应的流程地址:https://seata.io/zh-cn/docs/dev/mode/at-mode.html ,在这里我们只是做一些简单的介绍,AT模式主要分为两个阶段:

一阶段:

  • 解析SQL,获取SQL类型(CRUD)、表信息、条件(where) 等相关信息
  • 查询前镜像(改变之前的数据),根据解析得到的条件信息,生成查询语句,定位数据
  • 执行业务SQL,更新数据
  • 查询后镜像(改变后的数据),根据前镜像的结果,通过主键都给你为数据
  • 插入回滚日志,将前后镜像数据以及业务SQL等信息,组织成一条回滚日志记录,插入到undo Log表中
  • 提交前,向TC注册分支,申请全局锁
  • 本地事务提交,业务数据的更细腻和生成的undoLog一起提交
  • 将本地事务提交的结果通知给TC

二阶段:

如果TC收到的是回滚请求

  • 开启本地事务,通过XID和BranchID查找到对应的undo Log记录
  • 根据undoLog中的前镜像和业务SQL的相关信息生成并执行回滚语句
  • 提交本地事务,将本地事务的执行结果(分支事务回滚的信息)通知给TC

如果没问题,执行提交操作

  • 收到TC分支提交请求,将请求放入到一个异步任务的队列中,马上返回提交成功的结果给TC
  • 异步任务阶段的分支提交请求删除undoLog中记录

源码入口

接下来,我们就需要从官网中去下载源码,下载地址:https://seata.io/zh-cn/blog/download.html,选择 source 即可,下载完成之后,通过IDEA打开项目。

源码下载下来之后,我们应该如何去找入口呢?首先我们需要找到对应引入的 Seataspring-alibaba-seata,我们在回想一下,我们开启事务的时候,是不是添加过一个@GlobalTransactional的注解,这个注解就是我们入手的一个点,我们在 spring.factories 中看到有一个 GlobalTransactionAutoConfiguration,这个就是我们需要关注的点,也就是我们源码的入口

GlobalTransactionAutoConfiguration 中我们找到一个用Bean注入的方法 globalTransactionScanner ,这个就是全局事务扫描器,这个类型主要负责加载配置,注入相关的Bean

这里给大家展示了当前GlobalTransactionScanner的类关系图,其中我们现在继承了Aop的AbstractAutoProxyCreator类型,在这其中有一个重点方法,这个方法就是判断Bean对象是否需要代理,是否需要增强。

  1. @Configuration
  2. @EnableConfigurationProperties(SeataProperties.class)
  3. public class GlobalTransactionAutoConfiguration {
  4. //全局事务扫描器
  5. @Bean
  6. public GlobalTransactionScanner globalTransactionScanner() {
  7. String applicationName = applicationContext.getEnvironment()
  8. .getProperty("spring.application.name");
  9. String txServiceGroup = seataProperties.getTxServiceGroup();
  10. if (StringUtils.isEmpty(txServiceGroup)) {
  11. txServiceGroup = applicationName + "-fescar-service-group";
  12. seataProperties.setTxServiceGroup(txServiceGroup);
  13. }
  14. // 构建全局扫描器,传入参数:应用名、事务分组名,失败处理器
  15. return new GlobalTransactionScanner(applicationName, txServiceGroup);
  16. }
  17. }

在这其中我们要关心的是 GlobalTransactionScanner 这个类型,这个类型扫描 @GlobalTransactional 注解,并对代理方法进行拦截增强事务的功能。我们就从源码中搜索这个GlobalTransactionScanner类,看看里面具体是做了什么

  1. /**
  2. * The type Global transaction scanner.
  3. * 全局事务扫描器
  4. * @author slievrly
  5. */
  6. public class GlobalTransactionScanner
  7. //AbstractAutoProxyCreator AOP动态代理 增强Bean
  8. extends AbstractAutoProxyCreator
  9. /**
  10. * ConfigurationChangeListener: 监听器基准接口
  11. * InitializingBean: Bean初始化
  12. * ApplicationContextAware: Spring容器
  13. * DisposableBean: Spring 容器销毁
  14. */
  15. implements ConfigurationChangeListener, InitializingBean, ApplicationContextAware, DisposableBean {
  16. private final String applicationId;//服务名
  17. private final String txServiceGroup;//事务分组
  18. private void initClient() {
  19. //启动日志
  20. if (LOGGER.isInfoEnabled()) {
  21. LOGGER.info("Initializing Global Transaction Clients ... ");
  22. }
  23. //检查应用名以及事务分组名,为空抛出异常IllegalArgumentException
  24. if (DEFAULT_TX_GROUP_OLD.equals(txServiceGroup)) {
  25. LOGGER.warn("the default value of seata.tx-service-group: {} has already changed to {} since Seata 1.5, " +
  26. "please change your default configuration as soon as possible " +
  27. "and we don't recommend you to use default tx-service-group's value provided by seata",
  28. DEFAULT_TX_GROUP_OLD, DEFAULT_TX_GROUP);
  29. }
  30. if (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) {
  31. throw new IllegalArgumentException(String.format("applicationId: %s, txServiceGroup: %s", applicationId, txServiceGroup));
  32. }
  33. //init TM
  34. //初始化TM
  35. TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);
  36. if (LOGGER.isInfoEnabled()) {
  37. LOGGER.info("Transaction Manager Client is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);
  38. }
  39. //init RM
  40. //初始化RM
  41. RMClient.init(applicationId, txServiceGroup);
  42. if (LOGGER.isInfoEnabled()) {
  43. LOGGER.info("Resource Manager is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);
  44. }
  45. if (LOGGER.isInfoEnabled()) {
  46. LOGGER.info("Global Transaction Clients are initialized. ");
  47. }
  48. registerSpringShutdownHook();
  49. }
  50. @Override
  51. public void afterPropertiesSet() {
  52. if (disableGlobalTransaction) {
  53. if (LOGGER.isInfoEnabled()) {
  54. LOGGER.info("Global transaction is disabled.");
  55. }
  56. ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
  57. (ConfigurationChangeListener)this);
  58. return;
  59. }
  60. if (initialized.compareAndSet(false, true)) {
  61. initClient();
  62. }
  63. }
  64. private void initClient() {
  65. //启动日志
  66. if (LOGGER.isInfoEnabled()) {
  67. LOGGER.info("Initializing Global Transaction Clients ... ");
  68. }
  69. //检查应用名以及事务分组名,为空抛出异常IllegalArgumentException
  70. if (DEFAULT_TX_GROUP_OLD.equals(txServiceGroup)) {
  71. LOGGER.warn("the default value of seata.tx-service-group: {} has already changed to {} since Seata 1.5, " +
  72. "please change your default configuration as soon as possible " +
  73. "and we don't recommend you to use default tx-service-group's value provided by seata",
  74. DEFAULT_TX_GROUP_OLD, DEFAULT_TX_GROUP);
  75. }
  76. //检查应用名以及事务分组名,为空抛出异常IllegalArgumentException
  77. if (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) {
  78. throw new IllegalArgumentException(String.format("applicationId: %s, txServiceGroup: %s", applicationId, txServiceGroup));
  79. }
  80. //init TM
  81. //初始化TM
  82. TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);
  83. if (LOGGER.isInfoEnabled()) {
  84. LOGGER.info("Transaction Manager Client is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);
  85. }
  86. //init RM
  87. //初始化RM
  88. RMClient.init(applicationId, txServiceGroup);
  89. if (LOGGER.isInfoEnabled()) {
  90. LOGGER.info("Resource Manager is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);
  91. }
  92. if (LOGGER.isInfoEnabled()) {
  93. LOGGER.info("Global Transaction Clients are initialized. ");
  94. }
  95. registerSpringShutdownHook();
  96. }
  97. //代理增强,Spring 所有的Bean都会经过这个方法
  98. @Override
  99. protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
  100. // do checkers
  101. //检查bean和beanName
  102. if (!doCheckers(bean, beanName)) {
  103. return bean;
  104. }
  105. try {
  106. //加锁防止并发
  107. synchronized (PROXYED_SET) {
  108. if (PROXYED_SET.contains(beanName)) {
  109. return bean;
  110. }
  111. interceptor = null;
  112. //check TCC proxy
  113. //检查是否为TCC模式
  114. if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) {
  115. // init tcc fence clean task if enable useTccFence
  116. //如果启用useTccFence 失败 ,则初始化TCC清理任务
  117. TCCBeanParserUtils.initTccFenceCleanTask(TCCBeanParserUtils.getRemotingDesc(beanName), applicationContext);
  118. //TCC interceptor, proxy bean of sofa:reference/dubbo:reference, and LocalTCC
  119. //如果是,添加TCC拦截器
  120. interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));
  121. ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
  122. (ConfigurationChangeListener)interceptor);
  123. } else {
  124. //不是TCC
  125. Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);
  126. Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);
  127. //判断是否有相关事务注解,如果没有不进行代理
  128. if (!existsAnnotation(new Class[]{serviceInterface})
  129. && !existsAnnotation(interfacesIfJdk)) {
  130. return bean;
  131. }
  132. //发现存在全局事务注解标注的Bean对象,添加拦截器
  133. if (globalTransactionalInterceptor == null) {
  134. //添加拦截器
  135. globalTransactionalInterceptor = new GlobalTransactionalInterceptor(failureHandlerHook);
  136. ConfigurationCache.addConfigListener(
  137. ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
  138. (ConfigurationChangeListener)globalTransactionalInterceptor);
  139. }
  140. interceptor = globalTransactionalInterceptor;
  141. }
  142. LOGGER.info("Bean[{}] with name [{}] would use interceptor [{}]", bean.getClass().getName(), beanName, interceptor.getClass().getName());
  143. //检查是否为代理对象
  144. if (!AopUtils.isAopProxy(bean)) {
  145. //不是代理对象,调用父级
  146. bean = super.wrapIfNecessary(bean, beanName, cacheKey);
  147. } else {
  148. //是代理对象,反射获取代理类中已经存在的拦截器组合,然后添加到这个集合中
  149. AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);
  150. Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));
  151. int pos;
  152. for (Advisor avr : advisor) {
  153. // Find the position based on the advisor's order, and add to advisors by pos
  154. pos = findAddSeataAdvisorPosition(advised, avr);
  155. advised.addAdvisor(pos, avr);
  156. }
  157. }
  158. PROXYED_SET.add(beanName);
  159. return bean;
  160. }
  161. } catch (Exception exx) {
  162. throw new RuntimeException(exx);
  163. }
  164. }
  165. }

InitializingBean:中实现了一个 afterPropertiesSet()方法,在这个方法中,调用了initClient()

AbstractAutoProxyCreator:APO动态代理,在之前的的Nacos和Sentiel中都有这个代理类,AOP在我们越往深入学习,在学习源码的会见到的越来越多,越来越重要,很多相关代理,都是通过AOP进行增强,在这个类中,我们需要关注有一个wrapIfNecessary()方法, 这个方法主要是判断被代理的bean或者类是否需要代理增强,在这个方法中会调用GlobalTransactionalInterceptor.invoke()进行带来增强。

具体代码如下:

  1. public class GlobalTransactionalInterceptor implements ConfigurationChangeListener, MethodInterceptor, SeataInterceptor {
  2. public GlobalTransactionalInterceptor(FailureHandler failureHandler) {
  3. this.failureHandler = failureHandler == null ? DEFAULT_FAIL_HANDLER : failureHandler;
  4. this.disable = ConfigurationFactory.getInstance().getBoolean(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
  5. DEFAULT_DISABLE_GLOBAL_TRANSACTION);
  6. this.order =
  7. ConfigurationFactory.getInstance().getInt(ConfigurationKeys.TM_INTERCEPTOR_ORDER, TM_INTERCEPTOR_ORDER);
  8. degradeCheck = ConfigurationFactory.getInstance().getBoolean(ConfigurationKeys.CLIENT_DEGRADE_CHECK,
  9. DEFAULT_TM_DEGRADE_CHECK);
  10. if (degradeCheck) {
  11. ConfigurationCache.addConfigListener(ConfigurationKeys.CLIENT_DEGRADE_CHECK, this);
  12. degradeCheckPeriod = ConfigurationFactory.getInstance()
  13. .getInt(ConfigurationKeys.CLIENT_DEGRADE_CHECK_PERIOD, DEFAULT_TM_DEGRADE_CHECK_PERIOD);
  14. degradeCheckAllowTimes = ConfigurationFactory.getInstance()
  15. .getInt(ConfigurationKeys.CLIENT_DEGRADE_CHECK_ALLOW_TIMES, DEFAULT_TM_DEGRADE_CHECK_ALLOW_TIMES);
  16. EVENT_BUS.register(this);
  17. if (degradeCheckPeriod > 0 && degradeCheckAllowTimes > 0) {
  18. startDegradeCheck();
  19. }
  20. }
  21. this.initDefaultGlobalTransactionTimeout();
  22. }
  23. @Override
  24. public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
  25. //获取执行的方法
  26. Class<?> targetClass =
  27. methodInvocation.getThis() != null ? AopUtils.getTargetClass(methodInvocation.getThis()) : null;
  28. Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);
  29. if (specificMethod != null && !specificMethod.getDeclaringClass().equals(Object.class)) {
  30. final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);
  31. //获取GlobalTransactional(全局事务)、GlobalLock(全局锁)元数据
  32. final GlobalTransactional globalTransactionalAnnotation =
  33. getAnnotation(method, targetClass, GlobalTransactional.class);
  34. //GlobalLock会将本地事务的执行纳入Seata分布式事务的管理,共同竞争全局锁
  35. //保证全局事务在执行的时候,本地事务不可以操作全局事务的记录
  36. final GlobalLock globalLockAnnotation = getAnnotation(method, targetClass, GlobalLock.class);//获取全局锁
  37. boolean localDisable = disable || (degradeCheck && degradeNum >= degradeCheckAllowTimes);
  38. if (!localDisable) {
  39. if (globalTransactionalAnnotation != null || this.aspectTransactional != null) {
  40. AspectTransactional transactional;
  41. if (globalTransactionalAnnotation != null) {
  42. transactional = new AspectTransactional(globalTransactionalAnnotation.timeoutMills(),
  43. globalTransactionalAnnotation.name(), globalTransactionalAnnotation.rollbackFor(),
  44. globalTransactionalAnnotation.noRollbackForClassName(),
  45. globalTransactionalAnnotation.noRollbackFor(),
  46. globalTransactionalAnnotation.noRollbackForClassName(),
  47. globalTransactionalAnnotation.propagation(),
  48. globalTransactionalAnnotation.lockRetryInterval(),
  49. globalTransactionalAnnotation.lockRetryTimes());
  50. } else {
  51. transactional = this.aspectTransactional;
  52. }
  53. //执行全局事务
  54. return handleGlobalTransaction(methodInvocation, transactional);
  55. } else if (globalLockAnnotation != null) {
  56. //执行全局锁
  57. return handleGlobalLock(methodInvocation, globalLockAnnotation);
  58. }
  59. }
  60. }
  61. return methodInvocation.proceed();
  62. }
  63. }

具体流程图如下所示:

核心源码

在上面我们讲解到 GlobalTransactionalInterceptor 作为全局事务拦截器,一旦执行拦截,就会进入invoke方法,其中,我们会做 @GlobalTransactional 注解的判断,如果有这个注解的存在,会执行全局事务和全局锁,再执行全局事务的时候会调用 handleGlobalTransaction 全局事务处理器,获取事务信息,那我们接下来就来看一下 GlobalTransactionalInterceptor.handleGlobalTransaction 到底是如何执行全局事务的

  1. Object handleGlobalTransaction(final MethodInvocation methodInvocation,
  2. final AspectTransactional aspectTransactional) throws Throwable {
  3. boolean succeed = true;
  4. try {
  5. return transactionalTemplate.execute(new TransactionalExecutor() {
  6. @Override
  7. public Object execute() throws Throwable {
  8. return methodInvocation.proceed();
  9. }
  10. //获取事务名称,默认获取方法名
  11. public String name() {
  12. String name = aspectTransactional.getName();
  13. if (!StringUtils.isNullOrEmpty(name)) {
  14. return name;
  15. }
  16. return formatMethod(methodInvocation.getMethod());
  17. }
  18. /**
  19. * 解析GlobalTransation注解属性,封装对对象
  20. * @return
  21. */
  22. @Override
  23. public TransactionInfo getTransactionInfo() {
  24. // reset the value of timeout
  25. //获取超时时间,默认60秒
  26. int timeout = aspectTransactional.getTimeoutMills();
  27. if (timeout <= 0 || timeout == DEFAULT_GLOBAL_TRANSACTION_TIMEOUT) {
  28. timeout = defaultGlobalTransactionTimeout;
  29. }
  30. //构建事务信息对象
  31. TransactionInfo transactionInfo = new TransactionInfo();
  32. transactionInfo.setTimeOut(timeout);//超时时间
  33. transactionInfo.setName(name());//事务名称
  34. transactionInfo.setPropagation(aspectTransactional.getPropagation());//事务传播
  35. transactionInfo.setLockRetryInterval(aspectTransactional.getLockRetryInterval());//校验或占用全局锁重试间隔
  36. transactionInfo.setLockRetryTimes(aspectTransactional.getLockRetryTimes());//校验或占用全局锁重试次数
  37. Set<RollbackRule> rollbackRules = new LinkedHashSet<>();
  38. //其他构建信息
  39. for (Class<?> rbRule : aspectTransactional.getRollbackFor()) {
  40. rollbackRules.add(new RollbackRule(rbRule));
  41. }
  42. for (String rbRule : aspectTransactional.getRollbackForClassName()) {
  43. rollbackRules.add(new RollbackRule(rbRule));
  44. }
  45. for (Class<?> rbRule : aspectTransactional.getNoRollbackFor()) {
  46. rollbackRules.add(new NoRollbackRule(rbRule));
  47. }
  48. for (String rbRule : aspectTransactional.getNoRollbackForClassName()) {
  49. rollbackRules.add(new NoRollbackRule(rbRule));
  50. }
  51. transactionInfo.setRollbackRules(rollbackRules);
  52. return transactionInfo;
  53. }
  54. });
  55. } catch (TransactionalExecutor.ExecutionException e) {
  56. //执行异常
  57. TransactionalExecutor.Code code = e.getCode();
  58. switch (code) {
  59. case RollbackDone:
  60. throw e.getOriginalException();
  61. case BeginFailure:
  62. succeed = false;
  63. failureHandler.onBeginFailure(e.getTransaction(), e.getCause());
  64. throw e.getCause();
  65. case CommitFailure:
  66. succeed = false;
  67. failureHandler.onCommitFailure(e.getTransaction(), e.getCause());
  68. throw e.getCause();
  69. case RollbackFailure:
  70. failureHandler.onRollbackFailure(e.getTransaction(), e.getOriginalException());
  71. throw e.getOriginalException();
  72. case RollbackRetrying:
  73. failureHandler.onRollbackRetrying(e.getTransaction(), e.getOriginalException());
  74. throw e.getOriginalException();
  75. default:
  76. throw new ShouldNeverHappenException(String.format("Unknown TransactionalExecutor.Code: %s", code));
  77. }
  78. } finally {
  79. if (degradeCheck) {
  80. EVENT_BUS.post(new DegradeCheckEvent(succeed));
  81. }
  82. }
  83. }

在这里我们,主要关注一个重点方法 execute() ,这个方法主要用来执行事务的具体流程:

  • 获取事务信息
  • 执行全局事务
  • 发生异常全局回滚,各个数据通过UndoLog进行事务补偿
  • 全局事务提交
  • 清除所有资源

这个位置也是一个非常核心的一个位置,因为我们所有的业务进来以后都会去走这个位置,具体源码如下所示:

  1. public Object execute(TransactionalExecutor business) throws Throwable {
  2. // 1. Get transactionInfo
  3. //获取事务信息
  4. TransactionInfo txInfo = business.getTransactionInfo();
  5. if (txInfo == null) {
  6. throw new ShouldNeverHappenException("transactionInfo does not exist");
  7. }
  8. // 1.1 Get current transaction, if not null, the tx role is 'GlobalTransactionRole.Participant'.
  9. //获取当前事务,主要获取XID
  10. GlobalTransaction tx = GlobalTransactionContext.getCurrent();
  11. // 1.2 Handle the transaction propagation.
  12. //根据配置的不同事务传播行为,执行不同的逻辑
  13. Propagation propagation = txInfo.getPropagation();
  14. SuspendedResourcesHolder suspendedResourcesHolder = null;
  15. try {
  16. switch (propagation) {
  17. case NOT_SUPPORTED:
  18. // If transaction is existing, suspend it.
  19. if (existingTransaction(tx)) {
  20. suspendedResourcesHolder = tx.suspend();
  21. }
  22. // Execute without transaction and return.
  23. return business.execute();
  24. case REQUIRES_NEW:
  25. // If transaction is existing, suspend it, and then begin new transaction.
  26. if (existingTransaction(tx)) {
  27. suspendedResourcesHolder = tx.suspend();
  28. tx = GlobalTransactionContext.createNew();
  29. }
  30. // Continue and execute with new transaction
  31. break;
  32. case SUPPORTS:
  33. // If transaction is not existing, execute without transaction.
  34. if (notExistingTransaction(tx)) {
  35. return business.execute();
  36. }
  37. // Continue and execute with new transaction
  38. break;
  39. case REQUIRED:
  40. // If current transaction is existing, execute with current transaction,
  41. // else continue and execute with new transaction.
  42. break;
  43. case NEVER:
  44. // If transaction is existing, throw exception.
  45. if (existingTransaction(tx)) {
  46. throw new TransactionException(
  47. String.format("Existing transaction found for transaction marked with propagation 'never', xid = %s"
  48. , tx.getXid()));
  49. } else {
  50. // Execute without transaction and return.
  51. return business.execute();
  52. }
  53. case MANDATORY:
  54. // If transaction is not existing, throw exception.
  55. if (notExistingTransaction(tx)) {
  56. throw new TransactionException("No existing transaction found for transaction marked with propagation 'mandatory'");
  57. }
  58. // Continue and execute with current transaction.
  59. break;
  60. default:
  61. throw new TransactionException("Not Supported Propagation:" + propagation);
  62. }
  63. // 1.3 If null, create new transaction with role 'GlobalTransactionRole.Launcher'.
  64. //如果当前事务为空,创建一个新的事务
  65. if (tx == null) {
  66. tx = GlobalTransactionContext.createNew();
  67. }
  68. // set current tx config to holder
  69. GlobalLockConfig previousConfig = replaceGlobalLockConfig(txInfo);
  70. try {
  71. // 2. If the tx role is 'GlobalTransactionRole.Launcher', send the request of beginTransaction to TC,
  72. // else do nothing. Of course, the hooks will still be triggered.
  73. //开始执行全局事务
  74. beginTransaction(txInfo, tx);
  75. Object rs;
  76. try {
  77. // Do Your Business
  78. // 执行当前业务逻辑
  79. //1、在TC注册当前分支事务,TC会在branch_table中插入一条分支事务数据
  80. //2、执行本地update语句,并在执行前后查询数据状态,并把数据前后镜像存入到undo_log中
  81. //3、远程调用其他应用,远程应用接收到XID,也会注册分支事务,写入branch_table以及本地undo_log表
  82. //4、会在lock_table表中插入全局锁数据(一个分支一条)
  83. rs = business.execute();
  84. } catch (Throwable ex) {
  85. // 3. The needed business exception to rollback.
  86. //发生异常全局回滚,每个事务通过undo_log表进行事务补偿
  87. completeTransactionAfterThrowing(txInfo, tx, ex);
  88. throw ex;
  89. }
  90. // 4. everything is fine, commit.
  91. //全局提交
  92. commitTransaction(tx);
  93. return rs;
  94. } finally {
  95. //5. clear
  96. //清理所有资源
  97. resumeGlobalLockConfig(previousConfig);
  98. triggerAfterCompletion();
  99. cleanUp();
  100. }
  101. } finally {
  102. // If the transaction is suspended, resume it.
  103. if (suspendedResourcesHolder != null) {
  104. tx.resume(suspendedResourcesHolder);
  105. }
  106. }
  107. }

这其中的第三步和第四步其实在向 TC(Seata-Server)发起全局事务的提交或者回滚,在这里我们首先关注执行全局事务的 beginTransaction() 方法

  1. // 向TC发起请求,采用模板模式
  2. private void beginTransaction(TransactionInfo txInfo, GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
  3. try {
  4. triggerBeforeBegin();
  5. //对TC发起请求
  6. tx.begin(txInfo.getTimeOut(), txInfo.getName());
  7. triggerAfterBegin();
  8. } catch (TransactionException txe) {
  9. throw new TransactionalExecutor.ExecutionException(tx, txe,
  10. TransactionalExecutor.Code.BeginFailure);
  11. }
  12. }

在来关注其中,向TC发起请求的 tx.begin() 方法,而调用begin()方法的类为:DefaultGlobalTransaction

  1. @Override
  2. public void begin(int timeout, String name) throws TransactionException {
  3. //判断调用者是否为TM
  4. if (role != GlobalTransactionRole.Launcher) {
  5. assertXIDNotNull();
  6. if (LOGGER.isDebugEnabled()) {
  7. LOGGER.debug("Ignore Begin(): just involved in global transaction [{}]", xid);
  8. }
  9. return;
  10. }
  11. assertXIDNull();
  12. String currentXid = RootContext.getXID();
  13. if (currentXid != null) {
  14. throw new IllegalStateException("Global transaction already exists," +
  15. " can't begin a new global transaction, currentXid = " + currentXid);
  16. }
  17. //获取XID
  18. xid = transactionManager.begin(null, null, name, timeout);
  19. status = GlobalStatus.Begin;
  20. //绑定XID
  21. RootContext.bind(xid);
  22. if (LOGGER.isInfoEnabled()) {
  23. LOGGER.info("Begin new global transaction [{}]", xid);
  24. }
  25. }

再来看一下 transactionManager.begin() 方法,这个时候使用的是 DefaultTransactionManager.begin 默认的事务管理者,来获取XID,传入事务相关的信息 ,最好TC返回对应的全局事务XID,它调用的是DefaultTransactionManager.begin()方法

  1. public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
  2. throws TransactionException {
  3. GlobalBeginRequest request = new GlobalBeginRequest();
  4. request.setTransactionName(name);
  5. request.setTimeout(timeout);
  6. //发送请求得到响应
  7. GlobalBeginResponse response = (GlobalBeginResponse) syncCall(request);
  8. if (response.getResultCode() == ResultCode.Failed) {
  9. throw new TmTransactionException(TransactionExceptionCode.BeginFailed, response.getMsg());
  10. }
  11. //返回XID
  12. return response.getXid();
  13. }

在这里我们需要关注一个syncCall,在这里采用的是Netty通讯方式

  1. private AbstractTransactionResponse syncCall(AbstractTransactionRequest request) throws TransactionException {
  2. try {
  3. // 通过Netty发送请求
  4. return (AbstractTransactionResponse) TmNettyRemotingClient.getInstance().sendSyncRequest(request);
  5. } catch (TimeoutException toe) {
  6. throw new TmTransactionException(TransactionExceptionCode.IO, "RPC timeout", toe);
  7. }
  8. }

具体图解如下:

在这里我们需要重点了解 GlobalTransactionScanner 这个类型,在这个类型中继承了一些接口和抽象类,这个类主要作用就是扫描有注解的Bean,并做AOP增强。

  • ApplicationContextAware:继承这个类型以后,需要实现其方法 setApplicationContext(),当Spring启动完成以后,会自动调用这个类型,将 ApplicationContextbean,也就是说, GlobalTransactionScanner 能够很自然的使用Spring环境

  • InitializingBean: 继承这个接口,需要实现 afterPropertiesSet() ,但凡是继承这个接口的类,在初始化的时候,当所有的 properties 设置完成以后,会执行这个方法

  • DisposableBean : 这个类,实现了一个 destroy() 这个方法是在销毁的时候去调用

  • AbstractAutoProxyCreator: 这个类是Spring实现AOP的一种方式,本质上是一个 BeanPostProcessor ,在Bean初始化至去年,调用内部 createProxy() ,创建一个Bean的AOP代理Bean并返回,对Bean进行增强。

Seata数据源代理

在上面的环节中,我们讲解了Seata AT模式2PC的执行流程,那么现在我们就来带大家了解一下关于AT数据源代理的信息,这也是AT模式中非常关键的一个重要知识点,大家可以拿起小本子,记下来。

首先AT模式的核心主要分为一下两个

  • 开启全局事务,获取全局锁。
  • 解析SQL并写入undoLog中。

关于第一点我们已经分析清楚了,第二点就是关于AT模式如何解析SQL并写入undoLog中,但是在这之前,我们需要知道Seata是如何选择数据源,并进行数据源代理的。虽然全局事务拦截成功后最终还是执行了业务方法进行SQL提交和操作,但是由于Seata对数据源进行了代理,所以SQL的解析和undoLog的操作,是在数据源代理中进行完成的。

数据源代理是Seata中一个非常重要的知识点,在分布式事务运行过程中,undoLog的记录、资源的锁定,用户都是无感知的,因为这些操作都是数据源的代理中完成了,恰恰是这样,我们才要去了解,这样不仅有利于我们了解Seata的核心操作,还能对以后源码阅读有所帮助,因为其实很多底层代码都会去使用这样用户无感知的方式(代理)去实现。

同样,我们在之前的寻找源码入口的时候,通过我们项目中引入的jar找到一个 SeataAutoConfiguration 类,我们在里面找到一个SeataDataSourceBeanPostProcessor(),这个就是我们数据源代理的入口方法

我们进入SeataDataSourceBeanPostProcessor类里面,发现继承了一个 BeanPostProcessor ,这个接口我们应该很熟悉,这个是Sprng的拓展接口,所有的Bean对象,都有进入两个方法 postProcessAfterInitialization()postProcessBeforeInitialization() 这两个方法都是由 BeanPostProcessor提供的,这两个方法,一个是初始化之前执行Before。一个是在初始化之后执行After,主要用来对比我们的的Bean是否为数据源代理对象。

在这里我们需要关注到一个postProcessAfterInitialization.proxyDataSource() 方法,这个里面

  1. private Object proxyDataSource(Object originBean) {
  2. DataSourceProxy dataSourceProxy = DataSourceProxyHolder.get().putDataSource((DataSource) originBean);
  3. if (this.useJdkProxy) {
  4. return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), SpringProxyUtils.getAllInterfaces(originBean), (proxy, method, args) -> handleMethodProxy(dataSourceProxy, method, args, originBean));
  5. } else {
  6. return Enhancer.create(originBean.getClass(), (MethodInterceptor) (proxy, method, args, methodProxy) -> handleMethodProxy(dataSourceProxy, method, args, originBean));
  7. }
  8. }

这里有一个DataSourceProxy代理对象,我们需要看的就是这个类,这个就是我们数据库代理的对象,我们从我们下载的源码项目中,搜索这个代理对象,当我们打开这个类的目录时发现,除了这个,还有ConnectionProxy 连接对象、StatementProxyPreparedStatementProxy SQL执行对象,这些都被Seata进行了代理,为什么要对这些都进行代理,代理的目的其实为了执行Seata的业务逻辑,生成undoLog,全局事务的开启,事务的提交回滚等操作

DataSourceProxy 具体做了什么,主要功能有哪些,我们来看一下。他在源码中是如何体现的,我们需要关注的是init()

  1. public class DataSourceProxy extends AbstractDataSourceProxy implements Resource {
  2. private String resourceGroupId;
  3. private void init(DataSource dataSource, String resourceGroupId) {
  4. //资源组ID,默认是“default”这个默认值
  5. this.resourceGroupId = resourceGroupId;
  6. try (Connection connection = dataSource.getConnection()) {
  7. //根据原始数据源得到JDBC连接和数据库类型
  8. jdbcUrl = connection.getMetaData().getURL();
  9. dbType = JdbcUtils.getDbType(jdbcUrl);
  10. if (JdbcConstants.ORACLE.equals(dbType)) {
  11. userName = connection.getMetaData().getUserName();
  12. } else if (JdbcConstants.MARIADB.equals(dbType)) {
  13. dbType = JdbcConstants.MYSQL;
  14. }
  15. } catch (SQLException e) {
  16. throw new IllegalStateException("can not init dataSource", e);
  17. }
  18. initResourceId();
  19. DefaultResourceManager.get().registerResource(this);
  20. if (ENABLE_TABLE_META_CHECKER_ENABLE) {
  21. //如果配置开关打开,会定时在线程池不断更新表的元数据缓存信息
  22. tableMetaExecutor.scheduleAtFixedRate(() -> {
  23. try (Connection connection = dataSource.getConnection()) {
  24. TableMetaCacheFactory.getTableMetaCache(DataSourceProxy.this.getDbType())
  25. .refresh(connection, DataSourceProxy.this.getResourceId());
  26. } catch (Exception ignore) {
  27. }
  28. }, 0, TABLE_META_CHECKER_INTERVAL, TimeUnit.MILLISECONDS);
  29. }
  30. //Set the default branch type to 'AT' in the RootContext.
  31. RootContext.setDefaultBranchType(this.getBranchType());
  32. }
  33. }

从上面我们可以看出,他主要做了以下几点的增强:

  1. 给每个数据源标识资源组ID
  2. 如果打开配置,会有一个定时线程池定时更新表的元数据信息并缓存到本地
  3. 生成代理连接 ConnectionProxy 对象

在这三个增强功能里面,第三个是最重要的,在AT模式里面,会自动记录undoLog,资源锁定,都是通过ConnectionProxy完成的,除此之外 DataSrouceProxy重写了一个方法 getConnection,因为这里返回的是一个 ConnectionProxy,而不是原生的Connection

  1. @Override
  2. public ConnectionProxy getConnection() throws SQLException {
  3. Connection targetConnection = targetDataSource.getConnection();
  4. return new ConnectionProxy(this, targetConnection);
  5. }
  6. @Override
  7. public ConnectionProxy getConnection(String username, String password) throws SQLException {
  8. Connection targetConnection = targetDataSource.getConnection(username, password);
  9. return new ConnectionProxy(this, targetConnection);
  10. }

ConnectionProxy

ConnectionProxy 继承 AbstractConnectionProxy ,在这个父类中有很多公用的方法,在这个父类有 PreparedStatementProxyStatementProxyDataSourceProxy

所以我们需要先来看一下AbstractConnectionProxy,因为这里封装了需要我们用到的通用方法和逻辑,在其中我们需要关注的主要在于 PreparedStatementProxyStatementProxy ,在这里的逻辑主要是数据源连接的步骤,连接获取,创建执行对象等等

  1. @Override
  2. public Statement createStatement() throws SQLException {
  3. //调用真实连接对象获取Statement对象
  4. Statement targetStatement = getTargetConnection().createStatement();
  5. //创建Statement的代理
  6. return new StatementProxy(this, targetStatement);
  7. }
  8. @Override
  9. public PreparedStatement prepareStatement(String sql) throws SQLException {
  10. //获取数据库类型 mysql/oracle
  11. String dbType = getDbType();
  12. // support oracle 10.2+
  13. PreparedStatement targetPreparedStatement = null;
  14. //如果是AT模式且开启全局事务
  15. if (BranchType.AT == RootContext.getBranchType()) {
  16. List<SQLRecognizer> sqlRecognizers = SQLVisitorFactory.get(sql, dbType);
  17. if (sqlRecognizers != null && sqlRecognizers.size() == 1) {
  18. SQLRecognizer sqlRecognizer = sqlRecognizers.get(0);
  19. if (sqlRecognizer != null && sqlRecognizer.getSQLType() == SQLType.INSERT) {
  20. //获取表的元数据
  21. TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(dbType).getTableMeta(getTargetConnection(),
  22. sqlRecognizer.getTableName(), getDataSourceProxy().getResourceId());
  23. //得到表的主键列名
  24. String[] pkNameArray = new String[tableMeta.getPrimaryKeyOnlyName().size()];
  25. tableMeta.getPrimaryKeyOnlyName().toArray(pkNameArray);
  26. targetPreparedStatement = getTargetConnection().prepareStatement(sql,pkNameArray);
  27. }
  28. }
  29. }
  30. if (targetPreparedStatement == null) {
  31. targetPreparedStatement = getTargetConnection().prepareStatement(sql);
  32. }
  33. //创建PreparedStatementProxy代理
  34. return new PreparedStatementProxy(this, targetPreparedStatement, sql);
  35. }

在这两个代理对象中,都用到了以下几个方法:

  1. @Override
  2. public ResultSet executeQuery(String sql) throws SQLException {
  3. this.targetSQL = sql;
  4. return ExecuteTemplate.execute(this, (statement, args) -> statement.executeQuery((String) args[0]), sql);
  5. }
  6. @Override
  7. public int executeUpdate(String sql) throws SQLException {
  8. this.targetSQL = sql;
  9. return ExecuteTemplate.execute(this, (statement, args) -> statement.executeUpdate((String) args[0]), sql);
  10. }
  11. @Override
  12. public boolean execute(String sql) throws SQLException {
  13. this.targetSQL = sql;
  14. return ExecuteTemplate.execute(this, (statement, args) -> statement.execute((String) args[0]), sql);
  15. }

在这些方法中都调用了 ExecuteTemplate.execute(),所以我们就看一下在 ExecuteTemplate类中具体是做了什么操作:

  1. public class ExecuteTemplate {
  2. public static <T, S extends Statement> T execute(List<SQLRecognizer> sqlRecognizers,
  3. StatementProxy<S> statementProxy,
  4. StatementCallback<T, S> statementCallback,
  5. Object... args) throws SQLException {
  6. //如果没有全局锁,并且不是AT模式,直接执行SQL
  7. if (!RootContext.requireGlobalLock() && BranchType.AT != RootContext.getBranchType()) {
  8. // Just work as original statement
  9. return statementCallback.execute(statementProxy.getTargetStatement(), args);
  10. }
  11. //得到数据库类型- mysql/oracle
  12. String dbType = statementProxy.getConnectionProxy().getDbType();
  13. if (CollectionUtils.isEmpty(sqlRecognizers)) {
  14. //sqlRecognizers 为SQL语句的解析器,获取执行的SQL,通过它可以获得SQL语句表名、相关的列名、类型等信息,最后解析出对应的SQL表达式
  15. sqlRecognizers = SQLVisitorFactory.get(
  16. statementProxy.getTargetSQL(),
  17. dbType);
  18. }
  19. Executor<T> executor;
  20. if (CollectionUtils.isEmpty(sqlRecognizers)) {
  21. //如果seata没有找到合适的SQL语句解析器,那么便创建简单执行器PlainExecutor
  22. //PlainExecutor直接使用原生的Statment对象执行SQL
  23. executor = new PlainExecutor<>(statementProxy, statementCallback);
  24. } else {
  25. if (sqlRecognizers.size() == 1) {
  26. SQLRecognizer sqlRecognizer = sqlRecognizers.get(0);
  27. switch (sqlRecognizer.getSQLType()) {
  28. //新增
  29. case INSERT:
  30. executor = EnhancedServiceLoader.load(InsertExecutor.class, dbType,
  31. new Class[]{StatementProxy.class, StatementCallback.class, SQLRecognizer.class},
  32. new Object[]{statementProxy, statementCallback, sqlRecognizer});
  33. break;
  34. //修改
  35. case UPDATE:
  36. executor = new UpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer);
  37. break;
  38. //删除
  39. case DELETE:
  40. executor = new DeleteExecutor<>(statementProxy, statementCallback, sqlRecognizer);
  41. break;
  42. //加锁
  43. case SELECT_FOR_UPDATE:
  44. executor = new SelectForUpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer);
  45. break;
  46. //插入加锁
  47. case INSERT_ON_DUPLICATE_UPDATE:
  48. switch (dbType) {
  49. case JdbcConstants.MYSQL:
  50. case JdbcConstants.MARIADB:
  51. executor =
  52. new MySQLInsertOrUpdateExecutor(statementProxy, statementCallback, sqlRecognizer);
  53. break;
  54. default:
  55. throw new NotSupportYetException(dbType + " not support to INSERT_ON_DUPLICATE_UPDATE");
  56. }
  57. break;
  58. //原生
  59. default:
  60. executor = new PlainExecutor<>(statementProxy, statementCallback);
  61. break;
  62. }
  63. } else {
  64. //批量处理SQL语句
  65. executor = new MultiExecutor<>(statementProxy, statementCallback, sqlRecognizers);
  66. }
  67. }
  68. T rs;
  69. try {
  70. //执行
  71. rs = executor.execute(args);
  72. } catch (Throwable ex) {
  73. if (!(ex instanceof SQLException)) {
  74. // Turn other exception into SQLException
  75. ex = new SQLException(ex);
  76. }
  77. throw (SQLException) ex;
  78. }
  79. return rs;
  80. }
  81. }

ExecuteTemplate就一个 execute(),Seata将SQL执行委托给不同的执行器(模板),Seata提供了6种执行器也就是我们代码 case 中(INSERTUPDATEDELETESELECT_FOR_UPDATE,INSERT_ON_DUPLICATE_UPDATE),这些执行器的父类都是AbstractDMLBaseExecutor

  • UpdateExecutor: 执行update语句
  • InsertExecutor: 执行insert语句
  • DeleteExecutor: 执行delete语句
  • SelectForUpdateExecutor: 执行select for update语句
  • PlainExecutor: 执行普通查询语句
  • MultiExecutor: 复合执行器,在一条SQL语句中执行多条语句

关系图如下:

然后我们找到 rs = executor.execute(args); 最终执行的方法,找到最顶级的父类BaseTransactionalExecutor.execute()

  1. @Override
  2. public T execute(Object... args) throws Throwable {
  3. String xid = RootContext.getXID();
  4. if (xid != null) {
  5. //获取XID
  6. statementProxy.getConnectionProxy().bind(xid);
  7. }
  8. //设置全局锁
  9. statementProxy.getConnectionProxy().setGlobalLockRequire(RootContext.requireGlobalLock());
  10. return doExecute(args);
  11. }

在根据doExecute(args);找到其中的重写方法 AbstractDMLBaseExecutor.doExecute()

  1. @Override
  2. public T doExecute(Object... args) throws Throwable {
  3. AbstractConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
  4. //是否自动提交
  5. if (connectionProxy.getAutoCommit()) {
  6. return executeAutoCommitTrue(args);
  7. } else {
  8. return executeAutoCommitFalse(args);
  9. }
  10. }

对于数据库而言,本身都是自动提交的,所以我们进入executeAutoCommitTrue()

  1. protected T executeAutoCommitTrue(Object[] args) throws Throwable {
  2. ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
  3. try {
  4. //设置为手动提交
  5. connectionProxy.changeAutoCommit();
  6. return new LockRetryPolicy(connectionProxy).execute(() -> {
  7. //调用手动提交方法,得到分支执行的最终结果
  8. T result = executeAutoCommitFalse(args);
  9. //执行提交
  10. connectionProxy.commit();
  11. return result;
  12. });
  13. } catch (Exception e) {
  14. // when exception occur in finally,this exception will lost, so just print it here
  15. LOGGER.error("execute executeAutoCommitTrue error:{}", e.getMessage(), e);
  16. if (!LockRetryPolicy.isLockRetryPolicyBranchRollbackOnConflict()) {
  17. connectionProxy.getTargetConnection().rollback();
  18. }
  19. throw e;
  20. } finally {
  21. connectionProxy.getContext().reset();
  22. connectionProxy.setAutoCommit(true);
  23. }
  24. }

connectionProxy.changeAutoCommit()方法,修改为手动提交后,我们看来最关键的代码executeAutoCommitFalse()

  1. protected T executeAutoCommitFalse(Object[] args) throws Exception {
  2. if (!JdbcConstants.MYSQL.equalsIgnoreCase(getDbType()) && isMultiPk()) {
  3. throw new NotSupportYetException("multi pk only support mysql!");
  4. }
  5. //获取前镜像
  6. TableRecords beforeImage = beforeImage();
  7. //执行具体业务
  8. T result = statementCallback.execute(statementProxy.getTargetStatement(), args);
  9. //获取执行数量
  10. int updateCount = statementProxy.getUpdateCount();
  11. //判断如果执行数量大于0
  12. if (updateCount > 0) {
  13. //获取后镜像
  14. TableRecords afterImage = afterImage(beforeImage);
  15. //暂存到undolog中,在Commit的时候保存到数据库
  16. prepareUndoLog(beforeImage, afterImage);
  17. }
  18. return result;
  19. }

我们再回到executeAutoCommitTrue中,去看看提交做了哪些操作connectionProxy.commit();

  1. @Override
  2. public void commit() throws SQLException {
  3. try {
  4. lockRetryPolicy.execute(() -> {
  5. //具体执行
  6. doCommit();
  7. return null;
  8. });
  9. } catch (SQLException e) {
  10. if (targetConnection != null && !getAutoCommit() && !getContext().isAutoCommitChanged()) {
  11. rollback();
  12. }
  13. throw e;
  14. } catch (Exception e) {
  15. throw new SQLException(e);
  16. }
  17. }

进入到doCommit()

  1. private void doCommit() throws SQLException {
  2. //判断是否存在全局事务
  3. if (context.inGlobalTransaction()) {
  4. processGlobalTransactionCommit();
  5. } else if (context.isGlobalLockRequire()) {
  6. processLocalCommitWithGlobalLocks();
  7. } else {
  8. targetConnection.commit();
  9. }
  10. }

作为分布式事务,一定是存在全局事务的,所以我们进入 processGlobalTransactionCommit()

  1. private void processGlobalTransactionCommit() throws SQLException {
  2. try {
  3. //注册分支事务
  4. register();
  5. } catch (TransactionException e) {
  6. recognizeLockKeyConflictException(e, context.buildLockKeys());
  7. }
  8. try {
  9. //写入数据库undolog
  10. UndoLogManagerFactory.getUndoLogManager(this.getDbType()).flushUndoLogs(this);
  11. //执行原生提交 一阶段提交
  12. targetConnection.commit();
  13. } catch (Throwable ex) {
  14. LOGGER.error("process connectionProxy commit error: {}", ex.getMessage(), ex);
  15. report(false);
  16. throw new SQLException(ex);
  17. }
  18. if (IS_REPORT_SUCCESS_ENABLE) {
  19. report(true);
  20. }
  21. context.reset();
  22. }

其中register()方法就是注册分支事务的方法,同时还会将undoLog写入数据库和执行提交等操作


  1. //注册分支事务,生成分支事务ID
  2. private void register() throws TransactionException {
  3. if (!context.hasUndoLog() || !context.hasLockKey()) {
  4. return;
  5. }
  6. //注册分支事务
  7. Long branchId = DefaultResourceManager.get().branchRegister(BranchType.AT, getDataSourceProxy().getResourceId(),
  8. null, context.getXid(), context.getApplicationData(), context.buildLockKeys());
  9. context.setBranchId(branchId);
  10. }

然后我们在回到processGlobalTransactionCommit中,看看写入数据库中的flushUndoLogs()

  1. @Override
  2. public void flushUndoLogs(ConnectionProxy cp) throws SQLException {
  3. ConnectionContext connectionContext = cp.getContext();
  4. if (!connectionContext.hasUndoLog()) {
  5. return;
  6. }
  7. //获取XID
  8. String xid = connectionContext.getXid();
  9. //获取分支ID
  10. long branchId = connectionContext.getBranchId();
  11. BranchUndoLog branchUndoLog = new BranchUndoLog();
  12. branchUndoLog.setXid(xid);
  13. branchUndoLog.setBranchId(branchId);
  14. branchUndoLog.setSqlUndoLogs(connectionContext.getUndoItems());
  15. UndoLogParser parser = UndoLogParserFactory.getInstance();
  16. byte[] undoLogContent = parser.encode(branchUndoLog);
  17. if (LOGGER.isDebugEnabled()) {
  18. LOGGER.debug("Flushing UNDO LOG: {}", new String(undoLogContent, Constants.DEFAULT_CHARSET));
  19. }
  20. CompressorType compressorType = CompressorType.NONE;
  21. if (needCompress(undoLogContent)) {
  22. compressorType = ROLLBACK_INFO_COMPRESS_TYPE;
  23. undoLogContent = CompressorFactory.getCompressor(compressorType.getCode()).compress(undoLogContent);
  24. }
  25. //写入数据库具体位置
  26. insertUndoLogWithNormal(xid, branchId, buildContext(parser.getName(), compressorType), undoLogContent, cp.getTargetConnection());
  27. }

具体写入方法,此时我们使用的是MySql,所以执行的是MySql实现类MySQLUndoLogManager.insertUndoLogWithNormal()

  1. @Override
  2. protected void insertUndoLogWithNormal(String xid, long branchId, String rollbackCtx, byte[] undoLogContent,
  3. Connection conn) throws SQLException {
  4. insertUndoLog(xid, branchId, rollbackCtx, undoLogContent, State.Normal, conn);
  5. }
  6. //具体写入操作
  7. private void insertUndoLog(String xid, long branchId, String rollbackCtx, byte[] undoLogContent,
  8. State state, Connection conn) throws SQLException {
  9. try (PreparedStatement pst = conn.prepareStatement(INSERT_UNDO_LOG_SQL)) {
  10. pst.setLong(1, branchId);
  11. pst.setString(2, xid);
  12. pst.setString(3, rollbackCtx);
  13. pst.setBytes(4, undoLogContent);
  14. pst.setInt(5, state.getValue());
  15. pst.executeUpdate();
  16. } catch (Exception e) {
  17. if (!(e instanceof SQLException)) {
  18. e = new SQLException(e);
  19. }
  20. throw (SQLException) e;
  21. }
  22. }

具体流程如下所示:

Seata 服务端

我们找到Server.java 这里就是启动入口,在这个入口中找到协调者,因为TC整体的操作就是协调整体的全局事务

  1. //默认协调者
  2. DefaultCoordinator coordinator = DefaultCoordinator.getInstance(nettyRemotingServer);

DefaultCoordinator类中我们找到 一个doGlobalBegin 这个就是处理全局事务开始的方法,以及全局提交 doGlobalCommit 和全局回滚 doGlobalRollback

  1. //处理全局事务
  2. @Override
  3. protected void doGlobalBegin(GlobalBeginRequest request, GlobalBeginResponse response, RpcContext rpcContext)
  4. throws TransactionException {
  5. //响应客户端xid
  6. response.setXid(core.begin(rpcContext.getApplicationId(), rpcContext.getTransactionServiceGroup(),
  7. request.getTransactionName(), request.getTimeout()));
  8. if (LOGGER.isInfoEnabled()) {
  9. LOGGER.info("Begin new global transaction applicationId: {},transactionServiceGroup: {}, transactionName: {},timeout:{},xid:{}",
  10. rpcContext.getApplicationId(), rpcContext.getTransactionServiceGroup(), request.getTransactionName(), request.getTimeout(), response.getXid());
  11. }
  12. }
  13. //处理全局提交
  14. @Override
  15. protected void doGlobalCommit(GlobalCommitRequest request, GlobalCommitResponse response, RpcContext rpcContext)
  16. throws TransactionException {
  17. MDC.put(RootContext.MDC_KEY_XID, request.getXid());
  18. response.setGlobalStatus(core.commit(request.getXid()));
  19. }
  20. //处理全局回滚
  21. @Override
  22. protected void doGlobalRollback(GlobalRollbackRequest request, GlobalRollbackResponse response,
  23. RpcContext rpcContext) throws TransactionException {
  24. MDC.put(RootContext.MDC_KEY_XID, request.getXid());
  25. response.setGlobalStatus(core.rollback(request.getXid()));
  26. }

在这里我们首先关注 doGlobalBegincore.begin()

  1. @Override
  2. public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
  3. throws TransactionException {
  4. //创建全局事务Session
  5. GlobalSession session = GlobalSession.createGlobalSession(applicationId, transactionServiceGroup, name,
  6. timeout);
  7. MDC.put(RootContext.MDC_KEY_XID, session.getXid());
  8. //为Session重添加回调监听,SessionHolder.getRootSessionManager() 获取一个全局Session管理器DataBaseSessionManager
  9. //观察者设计模式,创建DataBaseSessionManager
  10. session.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
  11. //全局事务开始
  12. session.begin();
  13. // transaction start event
  14. MetricsPublisher.postSessionDoingEvent(session, false);
  15. return session.getXid();
  16. }

然后我们在来看一下SessionHolder.getRootSessionManager()

  1. /**
  2. * Gets root session manager.
  3. * 获取一个全局Session管理器
  4. * @return the root session manager
  5. */
  6. public static SessionManager getRootSessionManager() {
  7. if (ROOT_SESSION_MANAGER == null) {
  8. throw new ShouldNeverHappenException("SessionManager is NOT init!");
  9. }
  10. return ROOT_SESSION_MANAGER;
  11. }
  12. public static void init(String mode) {
  13. if (StringUtils.isBlank(mode)) {
  14. mode = CONFIG.getConfig(ConfigurationKeys.STORE_SESSION_MODE,
  15. CONFIG.getConfig(ConfigurationKeys.STORE_MODE, SERVER_DEFAULT_STORE_MODE));
  16. }
  17. StoreMode storeMode = StoreMode.get(mode);
  18. //判断Seata模式,当前为DB
  19. if (StoreMode.DB.equals(storeMode)) {
  20. //通过SPI机制读取SessionManager接口实现类,读取的META-INF.services目录,在通过反射机制创建对象DataBaseSessionManager
  21. ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName());
  22. ........
  23. }
  24. }

在这里他其实读取的是DB模式下 io.seata.server.session.SessionManager文件的内容

我们在回到begin方法中,去查看session.begin()

  1. @Override
  2. public void begin() throws TransactionException {
  3. //声明全局事务开始
  4. this.status = GlobalStatus.Begin;
  5. //开始时间
  6. this.beginTime = System.currentTimeMillis();
  7. //激活全局事务
  8. this.active = true;
  9. //将SessionManager放入到集合中,调用onBegin方法
  10. for (SessionLifecycleListener lifecycleListener : lifecycleListeners) {
  11. //调用父级抽象类的方法
  12. lifecycleListener.onBegin(this);
  13. }
  14. }

这里我们来看一下 onBegin() 方法,调用的是父级的方法,在这其中我们要关注 addGlobalSession() 方法,但是要注意,这里我们用的是db模式所以调用的是db模式的 DateBaseSessionManager

  1. @Override
  2. public void onBegin(GlobalSession globalSession) throws TransactionException {
  3. //这里调用的是DateBaseSessionManager
  4. addGlobalSession(globalSession);
  5. }
  6. @Override
  7. public void addGlobalSession(GlobalSession session) throws TransactionException {
  8. if (StringUtils.isBlank(taskName)) {
  9. //写入session
  10. boolean ret = transactionStoreManager.writeSession(LogOperation.GLOBAL_ADD, session);
  11. if (!ret) {
  12. throw new StoreException("addGlobalSession failed.");
  13. }
  14. } else {
  15. boolean ret = transactionStoreManager.writeSession(LogOperation.GLOBAL_UPDATE, session);
  16. if (!ret) {
  17. throw new StoreException("addGlobalSession failed.");
  18. }
  19. }
  20. }

然后在看查询其中关键的方法DataBaseTransactionStoreManager.writeSession()

  1. @Override
  2. public boolean writeSession(LogOperation logOperation, SessionStorable session) {
  3. //第一次进入是写入 会进入当前方法
  4. //全局添加
  5. if (LogOperation.GLOBAL_ADD.equals(logOperation)) {
  6. return logStore.insertGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
  7. //全局修改
  8. } else if (LogOperation.GLOBAL_UPDATE.equals(logOperation)) {
  9. return logStore.updateGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
  10. //全局删除
  11. } else if (LogOperation.GLOBAL_REMOVE.equals(logOperation)) {
  12. return logStore.deleteGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
  13. //分支添加
  14. } else if (LogOperation.BRANCH_ADD.equals(logOperation)) {
  15. return logStore.insertBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
  16. //分支更新
  17. } else if (LogOperation.BRANCH_UPDATE.equals(logOperation)) {
  18. return logStore.updateBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
  19. //分支移除
  20. } else if (LogOperation.BRANCH_REMOVE.equals(logOperation)) {
  21. return logStore.deleteBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
  22. } else {
  23. throw new StoreException("Unknown LogOperation:" + logOperation.name());
  24. }
  25. }

我们就看第一次进去的方法logStore.insertGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));

  1. @Override
  2. public boolean insertGlobalTransactionDO(GlobalTransactionDO globalTransactionDO) {
  3. String sql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getInsertGlobalTransactionSQL(globalTable);
  4. Connection conn = null;
  5. PreparedStatement ps = null;
  6. try {
  7. int index = 1;
  8. conn = logStoreDataSource.getConnection();
  9. conn.setAutoCommit(true);
  10. ps = conn.prepareStatement(sql);
  11. ps.setString(index++, globalTransactionDO.getXid());
  12. ps.setLong(index++, globalTransactionDO.getTransactionId());
  13. ps.setInt(index++, globalTransactionDO.getStatus());
  14. ps.setString(index++, globalTransactionDO.getApplicationId());
  15. ps.setString(index++, globalTransactionDO.getTransactionServiceGroup());
  16. String transactionName = globalTransactionDO.getTransactionName();
  17. transactionName = transactionName.length() > transactionNameColumnSize ?
  18. transactionName.substring(0, transactionNameColumnSize) :
  19. transactionName;
  20. ps.setString(index++, transactionName);
  21. ps.setInt(index++, globalTransactionDO.getTimeout());
  22. ps.setLong(index++, globalTransactionDO.getBeginTime());
  23. ps.setString(index++, globalTransactionDO.getApplicationData());
  24. return ps.executeUpdate() > 0;
  25. } catch (SQLException e) {
  26. throw new StoreException(e);
  27. } finally {
  28. IOUtil.close(ps, conn);
  29. }
  30. }

在这里有一个 GlobalTransactionDO 对象,里面有xid、transactionId 等等,到这里是不是就很熟悉了、

还记得我们第一次使用Seata的时候会创建三张表

  1. branch_table 分支事务表
  2. global_table 全局事务表
  3. lock_table 全局锁表

而这里就是对应我们的global_table表,其他两个也是差不多,都是一样的操作



流程图如下:

总结

完整流程图:

对于Seata源码来说主要是了解从哪里入口以及核心点在哪里,遇到有疑问的,可以Debug,对于Seata AT模式,我们主要掌握的核心点是

  • 如何获取全局锁、开启全局事务
  • 解析SQL并写入undolog

围绕这两点去看的话,会有针对性一点,到这里我们的Seata源码就讲解完了,有疑问的小伙伴记得在下方留言。

我是牧小农,怕什么真理无穷,进一步有进一步的欢喜,大家加油!

分布式事务(Seata)原理 详解篇,建议收藏的更多相关文章

  1. Linux 文件搜索神器 find 实战详解,建议收藏!

    大家好,我是肖邦,这是我的第 10 篇原创文章. 在 Linux 系统使用中,作为一个管理员,我希望能查找系统中所有的大小超过 200M 文件,查看近 7 天系统中哪些文件被修改过,找出所有子目录中的 ...

  2. CoreCLR源码探索(八) JIT的工作原理(详解篇)

    在上一篇我们对CoreCLR中的JIT有了一个基础的了解, 这一篇我们将更详细分析JIT的实现. JIT的实现代码主要在https://github.com/dotnet/coreclr/tree/m ...

  3. Unity 5 中的全局光照技术详解(建议收藏)

    本文整理自Unity全球官方网站,原文:UNITY 5 - LIGHTING AND RENDERING (文章较长,请耐心阅读)简介全局光照,简称GI,是一个用来模拟光的互动和反弹等复杂行为的算法, ...

  4. Spring Cloud Eureka 常用配置详解,建议收藏!

    前几天,栈长分享了 <Spring Cloud Eureka 注册中心集群搭建,Greenwich 最新版!>,今天来分享下 Spring Cloud Eureka 常用的一些参数配置及说 ...

  5. Swagger Annotation 详解(建议收藏)

    转载:https://www.jianshu.com/p/b0b19368e4a8 在软件开发行业,管理文档是件头疼的事.不是文档难于撰写,而是文档难于维护,因为需求与代码会经常变动,尤其在采用敏捷软 ...

  6. 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾

    https://mp.weixin.qq.com/s/67NvEVljnU-0-6rb7MWpGw 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾 原创 蚂蚁金 ...

  7. SpringCloud系列之集成分布式事务Seata应用篇

    目录 前言 项目版本 项目说明 Seata服务端部署 Seata客户端集成 cloud-web module-order module-cart module-goods module-wallet ...

  8. Storm概念、原理详解及其应用(一)BaseStorm

    本文借鉴官文,添加了一些解释和看法,其中有些理解,写的比较粗糙,有问题的地方希望大家指出.写这篇文章,是想把一些官文和资料中基础.重点拿出来,能总结出便于大家理解的话语.与大多数“wordcount” ...

  9. Spring框架系列(11) - Spring AOP实现原理详解之Cglib代理实现

    我们在前文中已经介绍了SpringAOP的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?本文主要介绍Cglib动态代理的案例和SpringAOP实现的原理.@pdai Spring框架系列 ...

随机推荐

  1. vue-cli4 vue-config.js配置及其备注

    // vue.config.js const path = require('path'); const CompressionWebpackPlugin = require("compre ...

  2. Linux命令篇 - grep 命令

    grep grep searches the named input FILEs (or standard input if no files are named, or if a single hy ...

  3. XPath语法和lxml模块

    XPath语法和lxml模块 什么是XPath? xpath(XML Path Language)是一门在XML和HTML文档中查找信息的语言,可用来在XML和HTML文档中对元素和属性进行遍历. X ...

  4. Nature | DNA甲基化测序助力人多能干细胞向胚胎全能8细胞的人工诱导|易基因项目文章

    北京时间2022年3月22日凌晨,<Nature>期刊在线刊登了由中国科学院广州生物医学与健康研究所等单位牵头,深圳市易基因科技有限公司.中国科学技术大学等单位参与,应用人多能干细胞向胚胎 ...

  5. Android添加布局和按键

    Android添加布局和按键 Android布局方式分为 1.LinearLayout (线性布局) 2.ConstraintLayout (约束布局) 3.FrameLayout (帧布局) 4.T ...

  6. srpingboot拦截器

    1.创建一个普通的web工程. 2.先创建需要的基础文件,比如一个用户类. package com.example.mode; public class User { private Integer ...

  7. OracleRAC ACFS安装与卸载

    目录 ACFS安装与卸载: 一.在RAC上手动安装ACFS/ADVM 模块的步骤如下: 1.验证内存中是否存在 ACFS/ADVM 模块: 2.用root用户重新安装ACFS/ADVM 模块: 3.A ...

  8. C语言函数调用栈

    C语言函数调用栈 栈溢出(stack overflow)是最常见的二进制漏洞,在介绍栈溢出之前,我们首先需要了解函数调用栈. 函数调用栈是一块连续的用来保存函数运行状态的内存区域,调用函数(calle ...

  9. 关于Spring中的useSuffixPatternMatch

    背景 spring-boot的版本是2.1.4.RELEASE,spring的版本是5.1.6.RELEASE 一个例子如下: @Configuration @Import(WebMvcAutoCon ...

  10. 大陆出境海缆TPE APCN NCP APG简介

    目前我国的登陆站主要设立在三个城市 山东 山东青岛登陆站(隶属中国联通) EAC-C2C TPE(美国方向) 上海 上海崇明登陆站(隶属中国电信) APCN2(亚太) NCP(长线--美国,新建,亚太 ...