今天看啥  ›  专栏  ›  克里斯朵夫李维

TCC模式在生产中的实践

克里斯朵夫李维  · 掘金  ·  · 2019-08-08 16:55
阅读 12

TCC模式在生产中的实践

一、为什么选择TCC

我们的服务调用使用的Dubbo,并且是大额资金的敏感操作,再不改变原有调用方式的情况下,TCC是最合适的。

二、业界主流的TCC框架

我们在使用时主要调研了TCC-Trancsactional,后续又调研了开源的Seata(Fescar)。最终结合实际情况实现了自己的TCC框架。

Seata TCC原理

  1. 开去全局事务,TM向TC申请开启全局事务,TC创建一个全局事务ID并返回给TM,全局事务开启,将全局事务id纳入RootContext(ThreadLocal)。
  2. 调用到要执行的业务方法时,解析TCC Bean,创建TCC Resource,注册到TC。
  3. 调用到要执行的业务方法时,向TC注册分支事务,并将分支事务纳入到全局事务的管理中。
  4. TM向TC发起全局提交或者回滚。

Seata TCC的不足之处

  1. 参与者和TC之间多次RPC调用,网络开销会影响一定的性能。
  2. 目前只支持File存储事务,事务恢复机制不完整。
  3. 异常控制需要业务方自己控制。

TCC Tracnsactional

TCC Transactional主要是两个切面,它没有单独的事务协调器,而是把事务协调器和应用绑定在一起,这样的好处是只要保证应用的可用性就能保证事务协调器的可用,而且减少了业务方和TC间的RPC的调用。提供了多种事务存储方式,拥有事务恢复能力和运维平台,并且已经有很多公司在使用。

TCC Transactional也有不足之处,它的异常控制机制不完善,在发生网络抖动、被调用方宕机等情况的时候依旧会出现问题。

三、数据库模型设计

交易服务至少要调用三个服务,每个服务都涉及到了资金的变化。原有的数据库模型在使用TCC之后并不能很好的适应TCC的设计思想,在参考了蚂蚁金服的设计之后,我们重新优化了持仓服务、费用服务的数据模型。

  • 持仓服务原来的数据库模型

    CREATE TABLE IF NOT EXISTS `position`(
    `id` INT UNSIGNED AUTO_INCREMENT,
    `amount` DECIMAL(20,8), -- 持仓金额
     PRIMARY KEY ( `id` )
    )ENGINE=InnoDB DEFAULT CHARSET=utf8;
    复制代码
  • 改造后的持仓数据库模型

    CREATE TABLE IF NOT EXISTS `position`(
     `id` INT UNSIGNED AUTO_INCREMENT,
     `amount` DECIMAL(20,8), -- 持仓金额
     `frozen` DECIMAL(20,8), -- 冻结资金
      PRIMARY KEY ( `id` )
    )ENGINE=InnoDB DEFAULT CHARSET=utf8;
    复制代码

三、并发控制

  1. Try阶段

    update position set amount=amount - #{money},frozen=frozen + #{money}
    where id = #{id} and amount > #{money};
    复制代码

    扣减持仓,增加冻结金额。

  2. Confirm阶段

    update position  set frozen= frozen - #{money} where id = #{id};
    复制代码

    释放冻结资金。

  3. Cancel阶段

    update position  set amount=amount + #{money} ,frozen= frozen - #{money}
    where id =#{id} and frozen > #{money};
    复制代码

    增加持仓,释放冻结资金。

四、异常

1. 滚-try未执行cancel执行

  1. Try调用超时
  2. 被调用方短时间内假死

2. 幂等问题

  1. 网络抖动
  2. 事务恢复多次重试

3. 悬挂

  1. try接口超时,执行cancel,try接口恢复

五、异常控制

 create table tcc_transaction_xxx(
   `id`              INT UNSIGNED AUTO_INCREMENT,
   `app`             char(20) comment '业务模块',
   `tx_id`           varchar(100) not null comment '全局事务id',
   `branch_id`       varchar(100) not null comment '分支事务id',
   `create_time`     datetime not null comment '创建时间',
   `update_time`     datetime not null comment '更新时间',
   `content`         varbinary(8000)  default null comment '事务内容',
   `status`          tinyint      not null comment '状态<>try=0=已发送&confirm=1=已提交&cancel=已回滚',
   `version`         int(11) default 0 comment '版本',
   `retried_content` int(11) default 0 comment '重试次数',
   PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
复制代码
  1. Try阶段

执行业务一阶段得时候先插事务控制表,判断status是否为二阶段,二阶段执行则直接抛出异常,终止一阶段。

  1. confirm阶段

理论上来说是不会发生空提交的,如果一阶段超时或出现异常,二阶段必然是回滚,所以confirm主要处理重复提交。

  1. cancel阶段

参考文章

分布式事务 Seata TCC 模式深度解析 | SOFAChannel#4 直播整理 - 金融级分布式架构的文章 - 知乎 zhuanlan.zhihu.com/p/63552935

Seata TCC 分布式事务源码分析 - Young Chen的文章 - 知乎 zhuanlan.zhihu.com/p/64484721

Seata TCC 分布式事务源码分析 - Young Chen的文章 - 知乎 zhuanlan.zhihu.com/p/64484721




原文地址:访问原文地址
快照地址: 访问文章快照