分布式系列--分布式事务(TCC)

概述

简介

TCC编程模式本质上也是一种二阶段协议,不同在于TCC编程模式需要与具体业务耦合。

TCC事务补偿是基于2PC实现的业务层事务控制方案,它是Try、Confirm和Cancel三个单词的首字母,含义如下:

  • Try        :检查及预留业务资源(完成提交事务前的检查,并预留好资源)。
  • Confirm:确定执行业务操作:对try阶段预留的资源正式执行。
  • Cancel :取消执行业务操作:对try阶段预留的资源释放。

TCC事务机制相比于上面介绍的XA,解决了其几个缺点:

  1. 解决了协调者单点:由主业务方发起并完成这个业务活动。业务活动管理器也变成多点,引入集群。
  2. 同步阻塞:引入超时,超时后进行补偿,并且不会锁定整个资源,将资源转换为业务逻辑形式,粒度变小。XA两阶段提交是资源层面,而TCC把资源层面2PC提到了业务层面来实现,有效避免了XA两阶段提交占用资源锁时间过长导致的性能低下问题。
  3. 数据一致性:有了补偿机制之后,由业务活动管理器控制一致性(最终一致性)。

流程示例

Try

下单业务由订单服务和库存服务协同完成,在try阶段订单服务和库存服务完成检查和预留资源。
订单服务:检查当前是否满足提交订单的条件(比如:当前存在未完成订单的不允许提交新订单)。
库存服务:检查当前是否有充足的库存,并锁定资源。

Confirm

订单服务和库存服务成功完成Try后开始正式执行资源操作。
订单服务:向订单写一条订单信息。
库存服务:减去库存。

Cancel

如果订单服务和库存服务有一方出现失败则全部取消操作。
订单服务:删除新增的订单信息。
库存服务:减去的库存再还原。

举个简单的例子:

  1. 如果你用100元买了一瓶水, Try阶段:你需要向你的钱包检查是否够100元并锁住这100元,水也是一样的。
  2. confirm:写订单信息、减库存
  3. 若有一个失败,则cancel(释放这100元和这一瓶水)。若cancel失败不论什么失败都进行重试cancel,所以要幂等。
  4. 如果都成功,则进行confirm:确认这100元被扣、这一瓶水被卖,如果confirm失败无论什么失败则重试(会依靠活动日志进行重试)

优缺点

优点:

  • 解决了跨服务的业务操作原子性问题,例如组合支付,订单减库存等场景非常实用。
  • 在业务层实现事务控制,灵活性好。
  • 性能好(因为短事务、异步)

缺点:

  • 对微服务的侵入性强,微服务每个事务都必须实现try,confirm,cancel等3个方法,开发成本高,维护改造成本高
  • 为了达到事务的一致性要求,try,confirm、cancel接口必须实现等幂性操作。(定时器+重试(try、confirm、cancel失败后要不断重试)
  • 事务管理器要记录事务日志,会损耗一定的性能,并使得整个TCC事务时间拉长,建议采用Redis的方式来记录事务日志。

适用场景

  • 强隔离性,严格一致性要求的活动业务。
  • 执行时间较短的业务

注意事项

其他网址

分布式事务之-TCC服务设计与实现一_Alen-CSDN博客
TCC和两阶段分布式事务处理的区别_轰鸣-CSDN博客

伪代码

参考网址

终于有人把“TCC分布式事务”实现原理讲明白了! - JaJian - 博客园

TCC实现阶段1:try

订单服务

public class OrderService {

    // 库存服务
    @Autowired
    private InventoryService inventoryService;

    // 积分服务
    @Autowired
    private CreditService creditService;

    // 仓储服务
    @Autowired
    private WmsService wmsService;

    // 对这个订单完成支付
    public void pay(){
        //对本地的的订单数据库修改订单状态为"已支付"
        orderDAO.updateStatus(OrderStatus.PAYED);

        //调用库存服务扣减库存
        inventoryService.reduceStock();

        //调用积分服务增加积分
        creditService.addCredit();

        //调用仓储服务通知发货
        wmsService.saleDelivery();
    }
}

        其实就是订单服务完成本地数据库操作之后,通过 Spring Cloud 的 Feign 来调用其他的各个服务罢了。
        但是光是凭借这段代码,是不足以实现 TCC 分布式事务的啊?!兄弟们,别着急,我们对这个订单服务修改点儿代码好不好。

1.修改订单状态

        首先,上面那个订单服务先把自己的状态修改为:OrderStatus.UPDATING。
        这是啥意思呢?也就是说,在 pay() 那个方法里,你别直接把订单状态修改为已支付啊!你先把订单状态修改为 UPDATING,也就是修改中的意思。
        这个状态是个没有任何含义的这么一个状态,代表有人正在修改这个状态罢了。

2.冻结库存

        库存服务直接提供的那个 reduceStock() 接口里,也别直接扣减库存啊,你可以是冻结掉库存。
        举个例子,本来你的库存数量是 100,你别直接 100 - 2 = 98,扣减这个库存!
        你可以把可销售的库存:100 - 2 = 98,设置为 98 没问题,然后在一个单独的冻结库存的字段里,设置一个 2。也就是说,有 2 个库存是给冻结了。

3.预增积分

        积分服务的 addCredit() 接口也是同理,别直接给用户增加会员积分。你可以先在积分表里的一个预增加积分字段加入积分。
        比如:用户积分原本是 1190,现在要增加 10 个积分,别直接 1190 + 10 = 1200 个积分啊!
        你可以保持积分为 1190 不变,在一个预增加字段里,比如说 prepare_add_credit 字段,设置一个 10,表示有 10 个积分准备增加。
        仓储服务的 saleDelivery() 接口也是同理啊,你可以先创建一个销售出库单,但是这个销售出库单的状态是“UNKNOWN”。
        也就是说,刚刚创建这个销售出库单,此时还不确定它的状态是什么呢!

        上面这套改造接口的过程,其实就是所谓的 TCC 分布式事务中的第一个 T 字母代表的阶段,也就是 Try 阶段。
        总结上述过程,如果你要实现一个 TCC 分布式事务,首先你的业务的主流程以及各个接口提供的业务含义,不是说直接完成那个业务操作,而是完成一个 Try 的操作。
        这个操作,一般都是锁定某个资源,设置一个预备类的状态,冻结部分数据,等等,大概都是这类操作。
        咱们来一起看看下面这张图,结合上面的文字,再来捋一捋整个过程:

分布式系列--分布式事务(TCC)_第1张图片

TCC实现阶段2:confirm

        这时,就要依靠 TCC 分布式事务框架来推动后续的执行了。

        如果你在各个服务里引入了一个 TCC 分布式事务的框架,订单服务里内嵌的那个 TCC 分布式事务框架可以感知到,各个服务的 Try 操作都成功了。此时,TCC 分布式事务框架会控制进入 TCC 下一个阶段,第一个 C 阶段,也就是 Confirm 阶段。
        为了实现这个阶段,你需要在各个服务里再加入一些代码。比如说,订单服务里,你可以加入一个 Confirm 的逻辑,就是正式把订单的状态设置为“已支付”了,大概是类似下面这样子

public class OrderServiceConfirm {

    public void pay(){
        orderDao.updateStatus(OrderStatus.PAYED);
    }
}

1.库存服务

        库存服务也是类似的,你可以有一个 InventoryServiceConfirm 类,里面提供一个 reduceStock() 接口的 Confirm 逻辑,这里就是将之前冻结库存字段的 2 个库存扣掉变为 0。
        这样的话,可销售库存之前就已经变为 98 了,现在冻结的 2 个库存也没了,那就正式完成了库存的扣减。

2.积分服务

        积分服务也是类似的,可以在积分服务里提供一个 CreditServiceConfirm 类,里面有一个 addCredit() 接口的 Confirm 逻辑,就是将预增加字段的 10 个积分扣掉,然后加入实际的会员积分字段中,从 1190 变为 1120。

3.仓储服务

        仓储服务也是类似,可以在仓储服务中提供一个 WmsServiceConfirm 类,提供一个 saleDelivery() 接口的 Confirm 逻辑,将销售出库单的状态正式修改为“已创建”,可以供仓储管理人员查看和使用,而不是停留在之前的中间状态“UNKNOWN”了。

        上面各种服务的 Confirm 的逻辑都实现好了,一旦订单服务里面的 TCC 分布式事务框架感知到各个服务的 Try 阶段都成功了以后,就会执行各个服务的 Confirm 逻辑。
        订单服务内的 TCC 事务框架会负责跟其他各个服务内的 TCC 事务框架进行通信,依次调用各个服务的 Confirm 逻辑。然后,正式完成各个服务的所有业务逻辑的执行。
        同样,给大家来一张图,顺着图一起来看看整个过程:

TCC实现阶段3:cancel        

        举个例子:在 Try 阶段,比如积分服务吧,它执行出错了,此时会怎么样?
        那订单服务内的 TCC 事务框架是可以感知到的,然后它会决定对整个 TCC 分布式事务进行回滚。
        也就是说,会执行各个服务的第二个 C 阶段,Cancel 阶段。同样,为了实现这个 Cancel 阶段,各个服务还得加一些代码。

1.订单服务

        首先订单服务,它得提供一个 OrderServiceCancel 的类,在里面有一个 pay() 接口的 Cancel 逻辑,就是可以将订单的状态设置为“CANCELED”,也就是这个订单的状态是已取消。

2.库存服务

        库存服务也同理,可以提供 reduceStock() 的 Cancel 逻辑,就是将冻结库存扣减掉 2,加回到可销售库存里去,98 + 2 = 100。

3.积分服务

        积分服务也需要提供 addCredit() 接口的 Cancel 逻辑,将预增加积分字段的 10 个积分扣减掉。

4.仓储服务

        仓储服务也需要提供一个 saleDelivery() 接口的 Cancel 逻辑,将销售出库单的状态修改为“CANCELED”设置为已取消。

        然后这个时候,订单服务的 TCC 分布式事务框架只要感知到了任何一个服务的 Try 逻辑失败了,就会跟各个服务内的 TCC 分布式事务框架进行通信,然后调用各个服务的 Cancel 逻辑。
        大家看看下面的图,直观的感受一下:

分布式系列--分布式事务(TCC)_第2张图片

TCC中间件

其他网址

分布式事物框架TCC-Transaction使用教程_varyall的专栏-CSDN博客

        要使用TCC 分布式事务框架,比如国内开源的 TCC-transaction、ByteTCC、Himly、EasyTransaction。否则的话,感知各个阶段的执行情况以及推进执行下一个阶段的这些事情,不太可能自己手写实现,太复杂了。

框架名称

Github地址

star数量

tcc-transaction

https://github.com/changmingxie/tcc-transaction

2446

Hmily

https://github.com/yu199195/hmily

1381

ByteTCC

https://github.com/liuyangming/ByteTCC

1300

EasyTransaction

https://github.com/QNJR-GROUP/EasyTransaction

904

 

框架名称

幂等性

嵌套调用

RPC框架支持

默认支持事务日志

可靠性验证

tcc-transaction

不支持

不支持

不耦合RPC框架。即:底层使用任何RPC都可以。

DB、redis、zk、file

通过

Hmily

不支持

不支持

Dubbo、motan、springcloud

DB、redis、mongodb、zk,file

通过

ByteTCC

不支持

不支持

Dubbo、springcloud

file

通过

EasyTransaction

支持

支持

Dubbo、SpringCloud、Ribbon/Eureka

DB、Redis

通过

tcc-transaction

tcc-transaction-1.2.x项目指南

dubbo

其他网址

分布式事务之——tcc-transaction分布式TCC型事务框架搭建与实战案例(基于Dubbo/Dubbox)_冰河的专栏-CSDN博客
终于跑通分布式事务框架tcc-transaction的示例项目 - JackieZheng - 博客园

SpringCloud

其他网址

分布式事务框架tcc_transaction第一篇——使用(RestTemplate模式)_Hello_noby的博客-CSDN博客

你可能感兴趣的:(分布式)