zoukankan      html  css  js  c++  java
  • seata 分布式事务 -- TCC模式

     

    基本概念:

    TCC(Try-Confirm-Cancel)分布式事务模型相对于 XA 等传统模型,其特征在于它不依赖 RM 对分布式事务的支持,

    而是通过对业务逻辑的分解来实现分布式事务。


    TCC与AT模式相同,也是二阶段提交,但是TCC对业务代码侵入性很强

    TCC模式下,所有事务都要手动实现Try,Confirm,Cancel三个方法

     


    TCC 模型认为对于业务系统中一个特定的业务逻辑 ,其对外提供服务时,必须接受一些不确定性,

    即对业务逻辑初步操作的调用仅是一个临时性操作,调用它的主业务服务保留了后续的取消权。

    如果主业务服务认为全局事务应该回滚,它会要求取消之前的临时性操作,这就对应从业务服务的取消操作。

    而当主业务服务认为全局事务应该提交时,它会放弃之前临时性操作的取消权,这对应从业务服务的确认操作。

    每一个初步操作,最终都会被确认或取消。因此,针对一个具体的业务服务,TCC 分布式事务模型需要业务系统提供

    三段业务逻辑:

    1. 初步操作 Try:完成所有业务检查,预留必须的业务资源。
    
    2. 确认操作 Confirm:真正执行的业务逻辑,不做任何业务检查,只使用 Try 阶段预留的业务资源。
    
      因此,只要 Try 操作成功,Confirm 必须能成功。另外,Confirm 操作需满足幂等性,保证一笔分布式事务能且只能成功一次。
    
    3. 取消操作 Cancel:释放 Try 阶段预留的业务资源。同样的,Cancel 操作也需要满足幂等性。

     

    工作机制:

    一阶段

    TM

    TM处理流程和AT模式下一样:

    1.开启全局事务,向TC注册全局事务并返回XID

    2.如果业务执行成功,通知TC全局事务提交

    3.如果业务执行失败,通知TC全局事务回滚

    4.清除内存中XID


    RM

    注册分支事务,获取branchId。并将方法调用时的上下文发送给TC。执行业务逻辑


    二阶段

    TM

    TM通知TC全局提交/回滚。TC通知各分支事务。RM处理消息逻辑是RMHandlerTCC里。

    RM

    提交和回滚逻辑一样:

    1.用TC发过来的ResourceId查到TCResource,找到commit/rollback方法并执行

    2.通知TC执行结果

     

    常见异常 :

    最常见的主要是这三种异常,空回滚、幂等、悬挂。


    1 空回滚:

    空回滚就是对于一个分布式事务,在没有调用 TCC 资源 Try 方法的情况下,调用了二阶段的 Cancel 方法,
    
    Cancel 方法需要识别出这是一个空回滚,然后直接返回成功。
    
    
    < try 未执行,Cancel 执行 >
    
    什么样的情形会造成空回滚呢?注册分支事务是在调用 RPC 时,Seata 框架的切面会拦截到该次调用请求,
    
    先向 TC 注册一个分支事务,然后才去执行 RPC 调用逻辑。如果 RPC 调用逻辑有问题,
    
    比如调用方机器宕机、网络异常,都会造成 RPC 调用失败,即未执行 Try 方法。但是分布式事务已经开启了,
    
    需要推进到终态,因此,TC 会回调参与者二阶段 Cancel 接口,从而形成空回滚。
    
    解决:
    
    需要一张额外的事务控制表,其中有分布式事务 ID 和分支事务 ID,第一阶段 Try 方法里会插入一条记录,
    
    表示一阶段执行了。Cancel 接口里读取该记录,如果该记录存在,则正常回滚;如果该记录不存在,则是空回滚。

     

    2 幂等:

    幂等就是对于同一个分布式事务的同一个分支事务,重复去调用该分支事务的第二阶段接口,
    
    因此,要求 TCC 的二阶段 Confirm 和 Cancel 接口保证幂等,不会重复使用或者释放资源。如果幂等控制没有做好,
    
    很有可能导致资损等严重问题。
    
    
    < 多次 执行 Confirm 或 Cancel>
    
    什么样的情形会造成重复提交或回滚?从图中可以看到,提交或回滚是一次 TC 到参与者的网络调用。因此,
    
    网络故障、参与者宕机等都有可能造成参与者 TCC 资源实际执行了二阶段防范,但是 TC 没有收到返回结果的情况,
    
    这时,TC 就会重复调用,直至调用成功,整个分布式事务结束。
    
     
    解决:
    
    事务控制表的每条记录关联一个分支事务,那我们完全可以在这张事务控制表上加一个状态字段,
    
    用来记录每个分支事务的执行状态。
    
    状态字段有三个值,分别是初始化、已提交、已回滚。Try 方法插入时,是初始化状态。
    
    二阶段 Confirm 和 Cancel 方法执行后修改为已提交或已回滚状态。当重复调用二阶段接口时,
    
    先获取该事务控制表对应记录,检查状态,如果已执行,则直接返回成功;否则正常执行。

     


    3.悬挂:

    悬挂就是对于一个分布式事务,其二阶段 Cancel 接口比 Try 接口先执行。
    
    因为允许空回滚的原因,Cancel 接口认为 Try 接口没执行,空回滚直接返回成功,对于 Seata 框架来说,
    
    认为分布式事务的二阶段接口已经执行成功,整个分布式事务就结束了。但是这之后 Try 方法才真正开始执行。
    
    
    < Confirm 在 try 之前>
    
    什么样的情况会造成悬挂呢?按照前面所讲,在 RPC 调用时,先注册分支事务,再执行 RPC 调用,
    
    如果此时 RPC 调用的网络发生拥堵,通常 RPC 调用是有超时时间的,RPC 超时以后,发起方就会通知 TC 回滚该分布式事务,
    
    可能回滚完成后,RPC 请求才到达参与者,真正执行,从而造成悬挂。
    
    解决:
    
    可以在二阶段执行时插入一条事务控制记录,状态为已回滚,这样当一阶段执行时,先读取该记录,
    
    如果记录存在,就认为二阶段已经执行,进行 空 try ,否则二阶段没执行,正常执行 try

     

    TCC模式 需要的数据库表:

    创建数据库: seata-server

    建表 Sql语句 :

     

    SET FOREIGN_KEY_CHECKS=0;
    
    
    DROP TABLE IF EXISTS `branch_table`;
    CREATE TABLE `branch_table` (
    `branch_id` bigint(20) NOT NULL,
    `xid` varchar(128) NOT NULL,
    `transaction_id` bigint(20) DEFAULT NULL,
    `resource_group_id` varchar(32) DEFAULT NULL,
    `resource_id` varchar(256) DEFAULT NULL,
    `branch_type` varchar(8) DEFAULT NULL,
    `status` tinyint(4) DEFAULT NULL,
    `client_id` varchar(64) DEFAULT NULL,
    `application_data` varchar(2000) DEFAULT NULL,
    `gmt_create` datetime(6) DEFAULT NULL,
    `gmt_modified` datetime(6) DEFAULT NULL,
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    
    DROP TABLE IF EXISTS `global_table`;
    CREATE TABLE `global_table` (
    `xid` varchar(128) NOT NULL,
    `transaction_id` bigint(20) DEFAULT NULL,
    `status` tinyint(4) NOT NULL,
    `application_id` varchar(32) DEFAULT NULL,
    `transaction_service_group` varchar(32) DEFAULT NULL,
    `transaction_name` varchar(128) DEFAULT NULL,
    `timeout` int(11) DEFAULT NULL,
    `begin_time` bigint(20) DEFAULT NULL,
    `application_data` varchar(2000) DEFAULT NULL,
    `gmt_create` datetime DEFAULT NULL,
    `gmt_modified` datetime DEFAULT NULL,
    PRIMARY KEY (`xid`),
    KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
    KEY `idx_transaction_id` (`transaction_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
     
    
    DROP TABLE IF EXISTS `lock_table`;
    CREATE TABLE `lock_table` (
    `row_key` varchar(128) NOT NULL,
    `xid` varchar(96) DEFAULT NULL,
    `transaction_id` bigint(20) DEFAULT NULL,
    `branch_id` bigint(20) NOT NULL,
    `resource_id` varchar(256) DEFAULT NULL,
    `table_name` varchar(32) DEFAULT NULL,
    `pk` varchar(36) DEFAULT NULL,
    `gmt_create` datetime DEFAULT NULL,
    `gmt_modified` datetime DEFAULT NULL,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
     
  • 相关阅读:
    9、 vector与list的区别与应用?怎么找某vector或者list的倒数第二个元素
    8、STL的两级空间配置器
    hdoj--1342--Lotto(dfs)
    FZU--2188--过河(bfs暴力条件判断)
    zzuli--1812--sort(模拟水题)
    hdoj--3123--GCC(技巧阶乘取余)
    zzulioj--1089--make pair(dfs+模拟)
    zzulioj--1815--easy problem(暴力加技巧)
    zzulioj--1801--xue姐的小动物(水题)
    HIToj--1076--Ordered Fractions(水题)
  • 原文地址:https://www.cnblogs.com/lifan12589/p/14780960.html
Copyright © 2011-2022 走看看