TCC型分布式事务原理和实现之:Transaction与Participant
黑客画家 发表于2个月前
TCC型分布式事务原理和实现之:Transaction与Participant
  1. 发表于 2个月前
  2. 阅读 945
  3. 收藏 47
  4. 点赞 2
  5. 评论 4
330元/年抢阿里云香港云服务器,节省80%出海成本>>>   

前言

      在TCC型分布式事务原理和实现之:TransactionManager一文中,介绍了TCC事务管理器的主要功能和实现原理。相较于事务管理器,事务则包含了更多的属性状态,下面的UML图中可以清晰的体现Transaction与Participant的关系。             

事务

      事务具有很多的属性状态。首先,事务必须具有一个唯一ID来标识自己(保证进程内唯一即可),这样不同的事务就可以进行隔离控制,常见的事务ID生成方法就是使用uuid了;TCC事务一共有try、confirm和cancel三个阶段,因此,事务必须有一个事务状态字段来标识事物当前的状态:TRYING, CONFIRMING, CANCELLING;在TransactionManager一文中,多次提到根事务和分支事务,此处再重新提一下,所谓根事务,就是指事务的主动发起方,而分支事务,就是事务的被动发起方,也就是谁先开始谁就是老大,剩下的都是追随者、参与者。那么事务当然需要一个类型字段来标识当前事务的类型了,根事务用ROOT标识,分支事务用BRANCH标识;事务不一定总是成功,否则的话分布式事务也就不再是什么难题和秘密了,事务失败了怎么办呢?很多人第一想法就是回滚啊,其实,可以完成回滚的事务我将其理解为“正常事务”,也就是事务回滚成功,事务的一致性依然保持。然后,真正的异常事务是指在commit和cancel阶段失败的事务,那么这个时候怎么办呢?业界常用的手段就是:补偿。很多人在第一次听说事务补偿的时候,都觉得这是一个很高大上的技术,恰恰相反,补偿甚至是事务处理中最笨的办法。补偿也可以理解为弥补,就是一件事情做错了,尽可能的通过各种方法去弥补,使之尽可能的变得正确。的确,补偿也不代表就一定能够成功,因此通常会给这个补偿动作加一个时长或者次数限制,实在不行,就需要人工介入了,这是最后一道防线了。这里的retriedCount就表示一个事务在异常之后又被补偿重试的次数统计,通常都会有专门的监控系统来监控该字段的变化。由于补偿通常意味着多次重试,因此需要补偿方法是幂等的;createTime、lastUpdateTime表示事务的创建时间和最近更新时间,这在处理事务的超时、事务统计和事务补偿时非常有用。version表示事务的版本;participants就表示事务的所有参与者了,这里的参与者包括事务发起方本身(我将其称为根事务参与者)和分支事务参与者。通常,分支事务参与者都代表了一个远程服务;attachments可以用于暂存事务的附加参数,该附加参数可以被事务上下文携带着传到分支事务(远程服务),也相当于dubbo中的隐式传参了。                            事务本身仅含有很少的方法属性,首先来看其构造方法。 public Transaction(TransactionContext transactionContext) { this.xid = transactionContext.getXid(); // 从transactionContext中获取事务id this.status = TransactionStatus.TRYING; // 事务状态为TRYING(因为是首次嘛) this.transactionType = TransactionType.BRANCH; // 事务类型为分支事务 }      该构造方法需要一个事务上下文作为参数,事务上下文和一次事务活动是一一对应的,它包含了事务ID、事务的当前状态、以及事务的附加参数,事务上下文必须是可序列化的,因为它会被序列化传送到远端(分支事务)。总而言之,整个分布式事务就是靠事务上下文串接起来的。该构造方法一般会在分支事务端被调用,用于根据从根事务端传递过来的事务上下文中创建一个分支事务。       下面还有一个重载的构造方法版本。它需要一个事务类型作为参数,该构造方法通常会在根事务端被调用。 public Transaction(TransactionType transactionType) { this.xid = new TransactionXid(); // 获取新的事务id this.status = TransactionStatus.TRYING; this.transactionType = transactionType; }       下面是事务中最核心的两个方法了:commit和rollback。原理很简单,遍历调用每一个参与者(Participant)的commit或rollback方法。如果你去对比JTA的实现,会发现代码如出一辙。  public void commit() { // 遍历所有参与者 for (Participant participant : participants) { // 调用所有参与者的commit,这个参与者有本地参与者也有远程参与者 participant.commit(); } } // 回滚这个事物 public void rollback() { for (Participant participant : participants) { // 遍历所有参与者,并调用其rollback participant.rollback(); } }  

事务参与者

      事务参与者(Participant)表示事务的一个参与方,通常,一次事务活动中会有多个事务参与者,否则也就没有必要使用分布式事务了。在TCC分布式事务中,通常会有多个远程服务作为分支事务参与者。      下图为Participant的UML,先看属性字段。Participant需要一个事务ID来标识自己所属的事务。confirmInvocationContext和cancelInvocationContext都为InvocationContext类型,InvocationContext标识一个调用上下文,它非常像dubbo中的Invocation,封装了一个方法调用中的:目标对象、方法名、参数类型、参数列表等,不了解的可以先去看一下dubbo。其中,confirmInvocationContext标识参与者的confirm方法调用上下文,cancelInvocationContext标识cancel方法的调用上下文,这些上下文通过构造器注入的,下文将会提到。Terminator表示终结的意思,他表示执行最终的方法调用,暂时不做细说,后文再论。最后一个属性transactionContextEditorClass标识事务上下文的获取工厂,因为TCC框架本身要做到与具体的SOA框架无关,因此默认情况下,TCC的事务上下文都会作为事务方法的第一个参数显示传递,这样做的好处是通用性比较好,缺点就是对业务代码造成了侵入。实际上,某些SOA框架(比如dubbo)提供了非常良好的隐式传参的特性,因此事务上下文无需作为方法第一个参数了。为了TCC框架本身在保持框架无关性的同时,又能保证针对特定SOA的优化,所以对事务上下文抽象出了工厂,目前工厂的主要实现由:DubboTransactionContextEditor和MethodTransactionContextEditor,该属性可以在Compensable注解中指定,也就是在一次事务活动中,不同类型的事务参与者(比如基于dubbo的、基于http等)可以使用不同的事务上下文传递方式。                                    下面是Participant的构造方法,该构造方法会在ResourceCoordinatorInterceptor切面中被调用。 public Participant(TransactionXid xid, InvocationContext confirmInvocationContext, InvocationContext cancelInvocationContext, Class<? extends TransactionContextEditor> transactionContextEditorClass) { this.xid = xid; this.confirmInvocationContext = confirmInvocationContext; this.cancelInvocationContext = cancelInvocationContext; this.transactionContextEditorClass = transactionContextEditorClass; }       由于Participant的commit和rollback方法执行逻辑基本相同,因此此处只以commit方法为例。 public void commit() { // 会调用真正的commit方法(业务提供的) terminator.invoke(new TransactionContext(xid, TransactionStatus.CONFIRMING.getId()), confirmInvocationContext, transactionContextEditorClass); }        这里用到了上文提到的Terminator,看一下invoke方法源码: public Object invoke(TransactionContext transactionContext, InvocationContext invocationContext, Class<? extends TransactionContextEditor> transactionContextEditorClass) { if (StringUtils.isNotEmpty(invocationContext.getMethodName())) { try { // 获取targetClass单例 Object target = FactoryBuilder.factoryOf(invocationContext.getTargetClass()).getInstance(); Method method = null; // 反射拿到真正方法 method = target.getClass().getMethod(invocationContext.getMethodName(), invocationContext.getParameterTypes()); // dubbo隐士传参或者方法显示传参 FactoryBuilder.factoryOf(transactionContextEditorClass).getInstance().set(transactionContext, target, method, invocationContext.getArgs()); // 反射调用真正的方法(本地或者远程) return method.invoke(target, invocationContext.getArgs()); } catch (Exception e) { throw new SystemException(e); } } return null; }       invoke需要使用事务上下文、调用上下文以及事务上下文工厂作为参数,具体的调用原理非常简单,使用调用上下文携带的信息,先反射拿到要调用的方法,然后执行调用。同时,这里也可以清晰看到使用事务上下文工厂的好处。

系列文章

TCC型分布式事务原理和实现之:原理介绍 TCC型分布式事务原理和实现之:TransactionManager TCC型分布式事务原理和实现之:Transaction与Participant TCC型分布式事务原理和实现之:事务切面 TCC型分布式事务原理和实现之:事务recovery TCC型分布式事务原理和实现之:兼容dubbo
  1. 打赏
  2. 点赞
  3. 收藏
  4. 分享
共有 人打赏支持
粉丝 66
博文 56
码字总数 149650
评论 (4)
小遥yao
写的非常好
什么都会点
求更新
黑客画家

引用来自“小遥yao”的评论

写的非常好
谢谢
黑客画家

引用来自“什么都会点”的评论

求更新
最近比较忙,晚点更新
×
黑客画家
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额:
利发国际官方网