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是异步的呢?