tcc分布式事务实现原理解析(同步调用)

本文讨论同步调用保证结果一致性的的情景(tcc),当然即可采用顺序回滚的方式

=============

如 a方法 -> bcd方法  其中->代表远程调用

 

==============

tcc 代表三个英文单词 try commit cancel。

try阶段:

事务预提交。例如电商系统里的 积分、仓储、钱的计算,这些结果要最终保持结果一致性。那么就需要实现同时都成功或者同时都失败,那么这个过程就可以作为预提交数据,如积分可以预先加到预留字段或者通过先插入一条log日志作为预提交

commit:

当所有的try都success后,来提交上一步中所有的try预提交结果。即比如吧预留字段的积分加到最终的积分表上或者吧log日志中的数据加到最终积分表上。

cancel:

应当是当执行上面bcd的过程中 有一个远程调用失败了,那么就需要rollback当前已经执行的所有try

===========

如何实现一个分布式事务的框架?下面是伪代码实现

  1 /拦截A的方法
  2 doRound(MethodInvocation invocation){
  3     //获得一个tid;插入一个总事物对象 初始状态为try
  4     Long tId = insert(new TransactionClassInfo(tid, tryProcess, try));
  5     context.setTId(tId);
  6     //把包含tid的基本信息放到dubbo上下文中,方便告知被调用的远程方法 
  7     RpcContext.getContext().setAttachments(context);
  8     //原方法执行a继续执行 用来调用bcd
  9     boolean isSuccess = invocation.proceed();
 10      //插入一条代表所有
 11       //当invocation.proceed是执行成功的时候,需要发消息给各个远程调用的方法进行commit或者cancel
 12       if(isSuccess){
 13       //更新总事物对象 状态变为success
 14       update(transactionClassInfo(tid, tryProcess, commit));
 15       List<TransactionSubClassInfo> transactionSubClassInfos = queryTransactionSubClassInfosBysTid(params.geTId());
 16         transactionSubClassInfos.forEach(transactionSubClassInfo -> {
 17             type = transactionSubClassInfo.getType;
 18                 rocketMq.send(topic, transactionSubCode,success, transactionSubClassInfo);//发消息来调用commit方法
 19         });
 20       update(transactionClassInfo(tid, sucess, commit));
 21       }else{
 22           update(transactionClassInfo(tid, tryProcess, cancel));
 23           //根据tid查询当前tid下的所有的事物
 24           List<TransactionSubClassInfo> transactionSubClassInfos = queryTransactionSubClassInfosBysTid(context.geTId());
 25         transactionSubClassInfos.forEach(transactionSubClassInfo -> {
 26             type = transactionSubClassInfo.getType;
 27                 rocketMq.send(topic, transactionSubCode,fail, transactionSubClassInfo);//发消息来调用cancel方法
 28         });
 29         update(transactionClassInfo(tid, sucess, cancel));
 30       }
 31 
 32 }   
 33 
 34 //拦截远程调用的方法BCD
 35 doRound(MethodInvocation invocation){
 36     Long tid = RpcContext.getContext().getMessage().getTid();
 37     String methodTypeName = invocation.getMethodName();
 38     if("try".equals(methodTypeName)){
 39          uniCode = getTransactionSubUniCode();
 40          //执行原try方法
 41          invocation.proceed();
 42           //methodTypeName = status 即哪个阶段
 43          insert(new TransactionSubClassInfo(tid, unicode, try, tryExcuteSuccess));
 44     }
 45 
 46     if("commit".equals(methodTypeName)){
 47          //执行原commit方法
 48          invocation.proceed();
 49          update(transactionSubClassInfo(tid, unicode, commit, tryExcuteSuccess));
 50     }
 51 
 52     if("cancel".equals(methodTypeName)){
 53      //执行原cancel方法
 54          invocation.proceed();
 55           update(transactionSubClassInfo(tid, unicode, cancel, tryExcuteSuccess))
 56     }
 57 } 
 58 
 59 
 60 /*算法方法:每个服务器都去数据取一段顺序数字组放到缓存中, 本地直接用,每次服务器取到数据组就更新下一起点*/
 61 private getTransactionSubUniCode(){
 62     String date = "年月日";
 63     //从0开始
 64     Long start =getSequenceId();
 65     //插入下次开始的
 66     insertNextStartSequenceId(start + defalutSize);
 67     //static CodeCache; offSet = 0;codeCache.set(start, offset);
 68     return date.concat(codeCache.get(offset));
 69 
 70 }
 71 
 72 @TransactionInvoke
 73 A {
 74     //dubbo服务BCD;
 75     B();
 76 
 77     C();
 78 
 79     D();
 80 
 81 }
 82 
 83 
 84 @TransactionSub(transactionSubCode = "uniCode", transactionType = "tcc")
 85 B(){
 86     
 87 
 88     @TransactionSub(TransactionSubCode ="uniCode", ...)
 89     doTry{}
 90 
 91 
 92     @TransactionSub(TransactionSubCode ="uniCode", ...)
 93     doCommit(){
 94 
 95     }
 96 
 97     @TransactionSub(TransactionSubCode ="uniCode", ...)
 98     doCancel(){
 99 
100     }
101     
102 }
103 
104 
105 
106 @Retention(RetentionPolicy.RUNTIME)
107 @Target({ ElementType.METHOD })
108 public @interface TransactionInvoke {
109   ...
110 }
111 
112 @Retention(RetentionPolicy.RUNTIME)
113 @Target({ ElementType.METHOD })
114 public @interface TransactionSub {
115     String transactionSubCode();
116 
117     String transactionType();
118     ....
119 }
120 
121 
122 init(){
123         //获得当前服务器下面所有的有分布式事物注释的类方法的元数据
124         Map<transactionSubCode,TransactionSubClass> metaClassMap = AnnotationScanner.getClazzAndAnnotation(scanPackages)
125         //static  subClassContext = ...;
126         subClassContext.put(class);
127         //扫描所有的包 获得这些
128         List<String> transactionSubCodes = AnnotationScanner.getAllTransactionSubCodes(scanPackages);
129         cosumer.setUnderTake(new ConsumerProcessClass());
130         //吧扫描包里所有的包含
131         cosumer.suscribe(topic, transactionSubCodes, ...);
132         cosumer.listen();
133         ...
134     
135 }
136 
137 class ConsumerProcessClass{
138     process(TransactionSubClassInfo){
139     transactionSubCode = TransactionSubClassInfo.getTransactionSubCode();
140     TransactionSubClass  transactionSubClass= metaClassMap.get(transactionSubCode);
141         if(TransactionSubClassInfo.isExcuteTrySuccess()){
142             //利用反射调用commit方法或者cancel方法
143             Reflect.doMethod(TransactionSubClass.getCommitMethod);
144             update(transactionSubClassInfo(commit,sucess);
145         }else{
146         //利用反射调用commit方法或者cancel方法
147             Reflect.invoke(TransactionSubClass.getCancelMethod);
148             update(transactionSubClassInfo(cancel,sucess);
149         }
150     }
151 }
152 
153 class TransactionSubClass{
154     Method try;
155 
156     Method cancel;
157 
158     Method commit;
159 
160     ....
161 }

========================================
note:
//所有的上下文都放在三中类型的变量中
1static Filed;

2static ThreadLocal<>;


//可以设置在dubbo过滤器中 方便进行分布式事务的上下文传递
3Rpc.getContext().set();



========================================

note:如果发mq的过程中失败
或者在执行commit或者cancel阶段失败

那么怎么办?1 rocket重发2 定时任务扫描所有的transactionSubClassInfos 然后一个个根据tid查询transactionClassInfo判断是否执行成功 然后再次更新所有的定时任务扫描所有的transactionSubClassInfos 调用所有的commit或者cancel

========================================

note:如果在执行bcd是异步的呢?

 

posted @ 2020-06-10 07:01  saveworld_niub  阅读(630)  评论(0编辑  收藏  举报