dubbo教程-11-我深入研究了下tcc-transaction

本地查看代码

我们把fork后的代码下载到本地
这个不用说了

git clone https://github.com/ibywind/tcc-transaction

然后导入到我们的IDE

我们导入到 IDEA之后就需要干一件事情了
我们查看他的 提交历史
也就是 通过 git 历史 来查看这个项目的 一个 进化过程
感谢 git 大神 .

我们使用 version control –> log 面板来查看他的一个 提交历史
我们发现了下面 红圈 中的 这次提交 是他的第一次提交

dubbo教程-11-我深入研究了下tcc-transaction_第1张图片

我们这个时候不着急
我们再看下 他后续的提交

我们发现了这样一个 提交日志 update to 1.0.3
也就是 他第一次有了一个版本号的提交
我相信 打了版本号的 这次提交
学习价值来讲的话 会比 第一次提交要好很多

我们把版本回退到这次提交 并且开始学习 !!

真的和我所说的一样 , 在这个版本 作者 还贴心的 给我们加上了 db.sql
也就是 我们 存储 各个事务状态的 一张表
他的 语句 如下:

CREATE TABLE `TCC_TRANSACTION` (
  `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT,
  `GLOBAL_TX_ID` varchar(200) NOT NULL,
  `BRANCH_QUALIFIER` varchar(200) NOT NULL,
  `CONTENT` varbinary(8000) DEFAULT NULL,
  `STATUS` int(11) DEFAULT NULL,
  `TRANSACTION_TYPE` int(11) DEFAULT NULL,
  'RETRIED_COUNT' int(11) DEFAULT NULL,
  PRIMARY KEY (`TRANSACTION_ID`),
  UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这张表是用来做 事务的 一个存储的
通过这种表的一个记录 我们 知道 当前分布式事务 存在哪些本地事务 , 我们需要修复哪些失败了的事务.
这个详细的设计 我们后续会讲到!

开始看代码

首先说明一下 我本次代码的一个版本号是 ad39cabda69d2d8035322406ad9ab10f4905c9d4
提交注释是 : update to 1.0.3
这个项目 整体是一个 maven聚合项目
主要分为下面四个子项目

tcc-transaction-api , tcc-transaction-core , tcc-transaction-spring , tcc-transaction-unit-test
我们将一个一个讲到

tcc-transaction-api

这个里面 主要有三个类 其中一个还是枚举类型的

首先我们来看这个类
TransactionXid 他实现 了 Xid这个接口
也就是 JAVA 事务规范当中的 定义 全局事务唯一性的 标识
globalTransactionId 表示 全局事务的 唯一标识
branchQualifier 表示 当前本地事务在 全局事务中的一个分支号

dubbo教程-11-我深入研究了下tcc-transaction_第2张图片

在本框架中,全局事务存储的方案是 存到 mysql数据库
我们看下他的建表语句 他是通过 双列 唯一索引的方式 来确保全局事务唯一性
同时一个 全局事务下面又有 若干个 分支 (全局事务参与者–>本地事务)

dubbo教程-11-我深入研究了下tcc-transaction_第3张图片

我们来到JAVA文档看下这个类 和他所在的包 具体是干啥的

然后就是 TransactionStatus 他是用来表示当前事务的一个具体状态的

TRYING(1), CONFIRMING(2), CANCELLING(3);

也就是 TCC 的 这三种状态 的一个 前置状态 (加了ing) 标识他接下来要干啥 ..
呵呵 咱好歹也是 英语6级程序员啊 !! 基本的语法还是懂的

最后是这个 TransactionContext 他是事务的一个上下文对象

// 事务ID
private TransactionXid xid;
// 事务状态
private int status;
// 事务附属信息
private Map, String> attachments = new ConcurrentHashMap, String>();

这个包其实就是一个 API 定义
我学会了这种编码的方式
短短几行代码
三个类 就描述了 这个事 我们要怎么做 怎么开始做
这个是 长时间沉淀 以后才有的 抽象能力!!
为作者来个 赞 

tcc-transaction-core

接下来我们看到 tcc-transaction-core
很明显 他是一个很重要的项目
而且是核心
因为 他有 core
里面主要有两个包
第一个是 utils
就是一个工具类

处理序列化和反序列化
以及string的一个 empty 判断

作者自己写了两个工具类
我进去看了下代码 , 完全可以用现成的啊 !

接下来就是其他代码了

说实话 作者给类起名字还是很见名知意的 是个老人了
我们一个一个来解释下吧
重点的我就多讲讲

BeanFactory 这个接口就一个方法 通过class 获得对象 , 就是反射呗

public interface BeanFactory {
    Object getBean(Class aClass);
}

BeanFactoryAdapter 适配器设计模式
也就是对于 BeanFactory 的一个装配和增强

public class BeanFactoryAdapter {

    private static BeanFactory beanFactory;

 public static Object getBean(Class aClass) {
        return beanFactory.getBean(aClass);
  }

    public static void setBeanFactory(BeanFactory beanFactory) {
        BeanFactoryAdapter.beanFactory = beanFactory;
  }
}

InvocationContext 这是一个方法执行反射 实现的 封装 !

dubbo教程-11-我深入研究了下tcc-transaction_第4张图片

MethodType 枚举 , 当前这个方法 是什么类型的方法

public enum MethodType {
  ROOT,
  CONSUMER,
  PROVIDER,
  NORMAL;
}

这个方法的调用 我们到了这个类 CompensableMethodUtils 它里面有一个方法 用来判断 当前 方法的一个 类型

public static MethodType calculateMethodType( TransactionContext transactionContext, boolean isCompensable) {

    if (transactionContext == null && isCompensable) {
        //isRootTransactionMethod
  return MethodType.ROOT;
  } else if (transactionContext == null && !isCompensable) {
        //isSoaConsumer
  return MethodType.CONSUMER;
  } else if (transactionContext != null && isCompensable) {
        //isSoaProvider
  return MethodType.PROVIDER;
  } else {
        return MethodType.NORMAL;
  }
}

Participant 事务的参与者 一个 抽象

它里面 包含一个 全局 事务ID 以及 当前这个事务参与者的一个终结者 对象 Terminator

public class Participant implements Serializable {

 private static final long serialVersionUID = 4127729421281425247L;
 private TransactionXid xid;

 private Terminator terminator;

 public Participant(TransactionXid xid, Terminator terminator) {
        this.xid = xid;
 this.terminator = terminator;
  }

    public void rollback() {
        terminator.rollback();
  }

    public void commit() {
        terminator.commit();
  }

}

Terminator 事务的最终执行 是要靠他的
它里面 有 TCC 中的 confirm 和 cancel 方法的持有对象
同事 有 定义 事务的 commit 和 rollback 方法

public class Terminator implements Serializable {

 private static final long serialVersionUID = -164958655471605778L;
 private InvocationContext confirmInvocationContext;

 private InvocationContext cancelInvocationContext;

 public Terminator(InvocationContext confirmInvocationContext, InvocationContext cancelInvocationContext) {
        this.confirmInvocationContext = confirmInvocationContext;
 this.cancelInvocationContext = cancelInvocationContext;
  }

    public void commit() {

        try {
            invoke(confirmInvocationContext);
  } catch (Throwable throwable) {
            throw new Error(throwable);
  }
    }

    public void rollback() {
        try {
            invoke(cancelInvocationContext);
  } catch (Throwable throwable) {
            throw new Error(throwable);
  }
    }

    private Object invoke(InvocationContext invocationContext) {

        if (StringUtils.isNotEmpty(invocationContext.getMethodName())) {
            try {

                Object target = BeanFactoryAdapter.getBean(invocationContext.getTargetClass());

 if (target == null && !invocationContext.getTargetClass().isInterface()) {
                    target = invocationContext.getTargetClass().newInstance();
  }

                Method method = target.getClass().getMethod(invocationContext.getMethodName(), invocationContext.getParameterTypes());
 return method.invoke(target, invocationContext.getArgs());

  } catch (Throwable e) {
                throw new Error(e);
  }
        }
        return null;
  }
}

Transaction 对于事务的一个抽象
当然这里的事务 指代的 是 分布式事务
成员变量有这些 !

dubbo教程-11-我深入研究了下tcc-transaction_第5张图片

我们关注到了 它里面有一个 private List participants = new ArrayList(); 目的是 把所有属于当前事务的 参与者 全部装载进来
另外我们注意到了 commit he rollback 方法
里面都是for 循环 让 事务的参与者 自己去 做自己对应的操作 ! 这个设计 很不错 哦

public void commit() {

    for (Participant participant : participants) {
        participant.commit();
  }
}

public void rollback() {
    for (Participant participant : participants) {
        participant.rollback();
  }
}

同时 开始定义 全局事务的一个 修复 策略了他在这里面 加入了 private volatile int retriedCount = 0;
用来 记录 当前事务 被重试的了几次
后续开发中 作者完善了 具体的 重试逻辑, 然后 可以配置最大重试 次数 ….

TransactionConfigurator 事务配置 抽象 接口 里面有 事务管理器 以及 事务存储的操作类

public interface TransactionConfigurator {

    public TransactionManager getTransactionManager();

    public TransactionRepository getTransactionRepository();

}

我们只找到 TccTransactionConfigurator 这一个实现 在 -spring 的那个项目中

@Component
public class TccTransactionConfigurator implements TransactionConfigurator {

    @Autowired
    private TransactionRepository transactionRepository;

    private TransactionManager transactionManager = new TransactionManager(this);

    @Override
    public TransactionManager getTransactionManager() {
        return transactionManager;
    }

    @Override
    public TransactionRepository getTransactionRepository() {
        return transactionRepository;
    }

}

TransactionManager 接下来是我们整个框架的 重头戏了
这个类 是 全局分布式事务的一个 事务管理器的抽象
他的一个结构是这样的 !

有两个变量 transactionConfigurator 不用说了 这是一个聚合设计 !
transactionMap 是一个 当前线程为 Key 全局事务为 value 的map
保证线程安全 !
为啥不用 threadlocal ?

private TransactionConfigurator transactionConfigurator;
private final Map, Transaction> transactionMap = new ConcurrentHashMap, Transaction>();

我们来看到 他的这个 begin 方法 !

public void begin() {

  Transaction transaction = new Transaction();
  transaction.setTransactionType(TransactionType.ROOT);

  transaction.setStatus(TransactionStatus.TRYING);
  this.transactionMap.put(Thread.currentThread(), transaction);

  TransactionRepository transactionRepository = transactionConfigurator.getTransactionRepository();
  transactionRepository.create(transaction);
}

首先 我们 开始 事务的时候,我们需要把 我们的事务 设置成 根事务 ! 并且是一个 trying 的事务状态, 如果你重复操作了 , 对不起 transactionMap 会报错 因为 同一个线程下 只能有一个 !
重复begin的话 value 会被覆盖 ! 并且我们需要把 当前的 全局事务对象 存储起来 (缓存 或者 是持久存储) DAO 层 实现有很多 !

另外有两个方法 propagationNewBegin propagationExistBegin

propagationNewBegin 这里我们是因为 会存在多个 SOA provider的情况.

propagationExistBegin 是为了 我们 执行完 try 阶段方法之后, 执行 confirm 方法的时候.依然存在事务 并且 需要更新 外部存储中 事务的状态

TransactionRepository 定义了一个存储 全局事务的 一个 接口 他的具体实现 就好比我们 DAOImpl 一样的
可以是 缓存 也可以是 文件存储 DB存储 等等

public interface TransactionRepository {

 void create(Transaction transaction);

 void update(Transaction transaction);

 void delete(Transaction transaction);

 Transaction findByXid(TransactionXid xid);

 List findAll();

 void addErrorTransaction(Transaction transaction);

 void removeErrorTransaction(Transaction transaction);

 Collection findAllErrorTransactions();

}

TransactionType 定义了 事务的类型 是 根事务 还是分支事务 !

dubbo教程-11-我深入研究了下tcc-transaction_第6张图片

在这个项目中 我们看到了 具体的一个 分布式事务的 定义 和 描述
具体的一些细节我们还需要看
tcc-transaction-spring 这个项目 !

tcc-transaction-spring

首先我们看下 他的 一个结构
dubbo教程-11-我深入研究了下tcc-transaction_第7张图片

我们从 recovery 包开始看吧

TransactionRecoveryJob这里有一个 JOB 需要去定时执行的 . 后来的 代码 作者把这 执行频次 变得 可配置了
这个很简单 我们不做深究了

@Component("transactionRecoveryJob")
public class TransactionRecoveryJob {

    @Autowired
    private TransactionRecovery transactionRecovery;

    @Scheduled(cron = "0 */1 * * * ?")
    public void recover() {
        transactionRecovery.startRecover();
    }
}

support 包我们来看下 , 总感觉 这个包 很吊的样子 !
之前 我们 在 API 包中定义了一个 BeanFactory 这回 找到试下了
我们看spring 来给他实现 .

@Component
public class TccApplicationContext implements BeanFactory, ApplicationContextAware {

  private ApplicationContext applicationContext;

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
  }

    @Override
  public Object getBean(Class aClass) {
        return this.applicationContext.getBean(aClass);
  }
}

TccBeanPostProcessor 这个类也是一个 初始化 工具类 !

我么来看到 TccTransactionConfigurator
这个类 就是 TransactionConfigurator 的一个具体实现了

TransactionManager 直接 new 出来 !
TransactionRepository 使用 Spring 配置的 我们一会讲到

@Component
public class TccTransactionConfigurator implements TransactionConfigurator {

    @Autowired
    private TransactionRepository transactionRepository;

    private TransactionManager transactionManager = new TransactionManager(this);

    @Override
    public TransactionManager getTransactionManager() {
        return transactionManager;
    }

    @Override
    public TransactionRepository getTransactionRepository() {
        return transactionRepository;
    }

}

CompensableMethodUtils 这个了 主要是 对于那些 有 @compensable 注解的方法 提供一个 工具类
在 后面的 spring 切面中会用来 做为 当前方法 判断的一个依据 !

ReflectionUtils 这个类 是通过方法签名 找 对应的 持有类 ! 首先找 接口中是否有这个方法 然后找父类是否有这个方法!

public class ReflectionUtils {

    public static Class getDeclaringType(Class aClass, String methodName, Class[] parameterTypes) {

        Method method = null;


        Class findClass = aClass;

        do {
            Class[] clazzes = findClass.getInterfaces();

            for (Class clazz : clazzes) {

                try {
                    method = clazz.getDeclaredMethod(methodName, parameterTypes);
                } catch (NoSuchMethodException e) {
                    method = null;
                }

                if (method != null) {
                    return clazz;
                }
            }

            findClass = findClass.getSuperclass();

        } while (!findClass.equals(Object.class));

        return aClass;
    }
}

接下来 就是我们的 注解 Compensable 他会出现在需要支持分布式事务的 方法上 他是一个方法级 的注解 !

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface Compensable {

   public String confirmMethod() default "";

   public String cancelMethod() default "";
}

JdbcTransactionRepository 这个类是 全局分布式事务的一个持久化对象
JDBC 一看就是 和 数据库打交道的
同时里面 有 两个MAP 用于 缓存 结构 提高效率

private final Map, Transaction> transactionXidCompensableTransactionMap = new ConcurrentHashMap, Transaction>();

private final Map, Transaction> errorTransactionXidCompensableTransactionMap = new ConcurrentHashMap, Transaction>();

接下来 就是两个 切面了

TccCompensableAspect
TccTransactionContextAspect
这两个类 都实现了 Ordered 这个接口
也就是 他们是可排序的!

TccTransactionContextAspect 这个切面对象 在 被初始化的时候 他的自身的 这个 order 变量也会被赋值

TccCompensableAspect 的 order 是 Ordered.HIGHEST_PRECEDENCE 这个常量
而 TccTransactionContextAspect

private int order = Ordered.HIGHEST_PRECEDENCE + 1;

这样 就 实现了 切面 环绕通知的 一个 先后顺序
TccCompensableAspect 在前
TccTransactionContextAspect 在后!

我们来看一下 切面表达式 :

@Pointcut("@annotation(org.mengyun.tcctransaction.spring.Compensable)")

这个是他们都有的, 也就是 对于 带有这个 Compensable 的方法 他们都会 被 切面 打到

TccTransactionContextAspect 这个类有点变态了 他的 切面表达式 是这样的
任何第一个 参数为 TransactionContext 的 方法 都会被 打到 !

@Pointcut("execution(public * *(org.mengyun.tcctransaction.api.TransactionContext,..))||@annotation(org.mengyun.tcctransaction.spring.Compensable)")

这个就意味着 , 那些 concel confirm 方法 都会 被全局事务的 切面 处理 到

后续会 详细 解释 这个 !

tcc-transaction-unit-test

tcc-transaction-unit-test.xml 找到这个配置文件 把他改成我们自己的配置

然后在看代码吧 !




    

    
        
    

    
        
        
        
        
    
    

打开 pom.xml 我们发现了


		org.mengyun
		tcc-transaction-spring
		${project.version}

他依赖 -spring 那个项目的 而 -spring 又依赖于 -core 和 -api

我们来看他的 测试用例吧

TransferServiceTest 这个是测试入口

public class TransferServiceTest extends AbstractTestCase {

  @Autowired
  private TransferService transferService;

  @Autowired
  SubAccountRepository subAccountRepository;

  @Autowired
  AccountRecordRepository accountRecordRepository;

  @Autowired
  TransactionRecovery transactionRecovery;

我们本次的 测试 没有对数据库 进行 具体的操作 而是在 缓存在内存中 所以 大家不要 问 为啥找不到 表结构 SQL
因为这个根本不需要 和数据库交互 !
SubAccountRepository
AccountRecordRepository

他们两个都是 操作 内存数据 !

我们开始运行 一下 代码吧
第一个 测试方法

在 测试之前 如果你对于之前的 代码 有不明白的地方 都可以打断点 然后进行调试!

这段代码 我打了 断点 .
不止这个
我其他很多的代码 都打了断点的! 因为 我想通过断点 了解 他的一个执行 顺序
看看我之前 想的 一些 点 是否正确

首先 请确保 你有一个 test 数据库 并且 建立一个表 DDL 语句如下
这个是用来 存储 全局事务的

drop table if EXISTS TCC_TRANSACTION ;

CREATE TABLE `TCC_TRANSACTION` (
  `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT,
  `GLOBAL_TX_ID` varchar(200) NOT NULL,
  `BRANCH_QUALIFIER` varchar(200) NOT NULL,
  `CONTENT` varbinary(8000) DEFAULT NULL,
  `STATUS` int(11) DEFAULT NULL,
  `TRANSACTION_TYPE` int(11) DEFAULT NULL,
  `RETRIED_COUNT` int(11) DEFAULT NULL,
  PRIMARY KEY (`TRANSACTION_ID`),
  UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

我们开始运行程序吧

接着他调用service 进入了这个方法

然后 我们 进入了切面

TccCompensableAspect 这个切面 先执行
所以 那个 order 的判断 是正确的 他会 让spring 框架 去 按照 我们的设置的 顺序 去执行

TccCompensableAspect 这个切面 完成了以后
我们会来到 第二个切面 TccTransactionContextAspect

你可能感兴趣的:(dubbo)