美文网首页
分布式事务之Seata讲解

分布式事务之Seata讲解

作者: 上善若泪 | 来源:发表于2023-05-13 22:10 被阅读0次

    1 Seata

    学习此文章前需要先 点击了解CAP,2PC,3PC,TCC

    1.1 简介

    Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 ATTCCSAGAXA 事务模式,为用户打造一站式的分布式解决方案。

    点击了解Seata搭建

    1.2 架构

    seata 事务管理中有三个重要的角色:

    • TC(Transaction Coordinator):事务协调者:维护全局和分支事务状态,协调全局事务提交或回滚。
    • TM(Transaction Manager):事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
    • RM(Resource Manager):资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
    image.png

    1.3 四种事务模式

    在继续学习使用SEATA之前,对seata介绍中提到的分布式事务 AT、TCC、SAGA 和 XA 事务模式这些名词有必要介绍一下

    1.3.1 XA

    1.3.1.1 定义

    XA规范是X/open组织定义的分布式事务处理(DTP)标准,XA规范描述了全局的TM与局部之间的接口,几乎所有的主流的数据库都对XA规范提供了支持。

    image.png

    seata的XA模式


    image.png

    RM一阶段工作:

    • 注册分支事务到TC
    • 执行分支业务sql但不提交
    • 报告执行状态到 TC

    TC二阶段的工作:
    TC检测各分支事务执行状态

    • 如果都成功,通知所有RM提交事务
    • 如果有失败,通知所有RM回滚事务

    RM 二阶段的工作:
    接收 TC 的指令,提交或回滚事务

    1.3.1.2 优缺点

    优点:

    • 事务的强一致性,满足ACID原则。
    • 常用的数据库都支持,实现简单,并且没有代码侵入

    缺点:

    • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能极差。
    • 依赖关系型数据库实现事务

    1.3.1.3 代码中实现

    seatastarter已经完成了XA模式的自动装配,实现非常简单,步骤如下
    修改yml文件(每个参与事务的微服务)

    image.png
    seata:
      data-source-proxy-mode: XA # 开启数据库源代理的XA模式
    

    application启动类上添加: @EnableAutoDataSourceProxy
    需要事务的方法或类上添加:@GlobalTransactional(rollbackFor = Exception.class)

    @GlobalTransactional
    public void saveAll(){
        User user=new User();
        user.setName("test");
        user.setAge(18);
        baseMapper.insert(user);
    
        //feign接口
        orderFeignService.saveFeigh();
        //手动控制异常回滚
        //throw new RuntimeException("测试回滚");
    }
    

    1.3.2 AT

    1.3.2.1 定义

    AT 模式是一种无侵入的分布式事务解决方案。在 AT 模式下,用户只需关注自己的 业务 SQL,用户的 业务 SQL 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作
    AT模式同样是分阶段提交的事务模型,不过弥补了XA模型中资源锁定周期过长的缺陷

    image.png

    阶段一RM的工作:

    • 注册分支事务
    • 记录undo-log(数据快照)
    • 执行业务sql并提交
    • 报告事务状态

    阶段二提交时RM的工作:

    • 删除undo-log即可

    阶段二回滚时RM的工作:

    • 根据undo-log恢复数据到更新前
    image.png

    1.3.2.2 全局锁

    1.3.2.2.1 AT模式脏写问题

    两个事务同时进行时,没有做到事务隔离性,如下事务1已经减10,事务二也减10了,但是事务1异常回滚了,恢复快照,也只能恢复事务1的恢复成100。


    image.png
    1.3.2.2.2 全局锁

    AT 模式用全局锁来解决上方问题。两个事务同时处理时,先获取全局锁的事务1会等待事务2释放DB锁,事务2获取不到全局锁,会有300ms的时间重试,一直获取不到就释放DB锁,事务1就能获取到DB锁,恢复数据了。

    image.png

    极端模式:
    保存快照时会保存修改前和修改后的快照,万一事务一恢复数据时,发现现在的值和快照不一样,已经被动过,就会记录异常发送警告,由人工来处理


    image.png

    1.3.2.3 AT模式优缺点

    AT模式的优点:

    • 一阶段完成直接提交事务,释放资源数据库资源,性能比较好
    • 利用全局锁实现读写隔离
    • 没有代码侵入,框架自动完成回滚和提交

    AT模式缺点:

    • 两阶段之间属于软状态,属于最终一致
    • 框架的快照功能会影响性能,但比XA模式要好很多

    1.3.2.4 与XA模式区别

    与XA模式的最大区别:

    • XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
    • XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
    • XA模式强一致;AT模式最终一致

    1.3.2.5 代码中实现

    application启动类上添加: @EnableAutoDataSourceProxy
    需要事务的方法或类上添加:@GlobalTransactional(rollbackFor = Exception.class)

    在XA模式配置基础下,只需修改data-source-proxy-mode: AT

    seata:
      data-source-proxy-mode: AT# 开启数据库源代理的XA模式
    

    业务表中增加undo_log` 表

    DROP TABLE IF EXISTS `undo_log`;
    CREATE TABLE `undo_log`  (
      `id`  bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
      `branch_id` bigint(20) NOT NULL COMMENT '分支事务ID',
      `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '全局事务ID',
      `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '上下文',
      `rollback_info` longblob NOT NULL COMMENT '回滚信息',
      `log_status` int(11) NOT NULL COMMENT '状态,0正常,1全局已完成',
      `log_created` datetime(6) NOT NULL COMMENT '创建时间',
      `log_modified` datetime(6) NOT NULL COMMENT '修改时间',
       PRIMARY KEY (`id`),
      UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) 
    ) ENGINE = InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    

    1.3.3 TCC模式

    1.3.3.1 定义

    TCC 模式与 AC 模式非常相似,每阶段都是独立事务,不同的是 TCC 通过人工编码来实现数据恢复。需要实现三个方法:

    • Try:资源的检测和预留
    • Confirm:完成资源操作业务;要求Try成功Confirm一定要能成功。
    • Cancel:预留资源释放,可以理解为try的反向操作
      image.png

    seata中TCC工作模型:


    image.png

    1.3.3.2 TCC模式优缺点

    TCC 模式优点:

    • 一阶段完成直接提交事务,释放数据库资源,性能好
    • 相比 AT 模型,无需生产快照,无需使用全局锁,性能最强
    • 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库

    TCC模式缺点:

    • 有代码侵入,需要人为编写tryConfirmCancel接口,太麻烦
    • 软状态,事务是最终一致
    • 需要考虑 ConfirmCancel 的失败情况,失败seata会重试,可能重复处理,需要做好幂等处理

    1.3.3.3 代码中实现

    1.3.3.3.1 业务需求

    需修改如下:

    • 修改方法,编写try、confim、cancel逻辑

    • 保证confirm、cancel接口的幂等性,幂等处理就是防止重复处理

    • 允许空回滚
      当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先先执行了cancel操作,这时cancel不能做回滚,这就是空回滚

      image.png
    • 拒绝业务悬挂
      对于已空回滚的业务,如果以后继续执行try,就永远不可能confirmcancel,这就是业务悬挂。应当阻止执行空回滚后的 try 操作,避免悬挂。

    1.3.3.3.2 增加表

    为了实现空回滚,防止业务悬挂,以及幂等性要求。我们必须在数据库操作的同时,记录当前事务id和执行状态,为此我们设计了一张表(自己根据业务设计,主要为了存事务状态,和业务表相关字段)

    DROP TABLE IF EXISTS `tcc`;
    CREATE TABLE `tcc`  (
      `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
      `business_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '业务id',
      `state` tinyint(1) NULL DEFAULT NULL COMMENT '事务状态,0:try   , 1:confirm ,   2:cancel',
      PRIMARY KEY (`xid`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
    
    1.3.3.3.3 新建接口编写try、confirm、cancel接口
    @LocalTCC
    public interface InsertTCCService {
        /**
         * try逻辑,@TwoPhaseBusinessAction中的name属性要与当前方法名一致,用于指定try逻辑对应的方法
         */
        @TwoPhaseBusinessAction(name = "insert",commitMethod = "confirm",rollbackMethod = "cancel")
        void insert(@BusinessActionContextParameter(paramName = "name") String name,
                    @BusinessActionContextParameter(paramName = "age")Integer age);
    
        /**
         * 二阶段confirm确认方法,可以另命名,倒要保证与commitMethod一致
         * @param context 上下文,可与i传递try方法的参数
         * @return
         */
        boolean confirm(BusinessActionContext context);
    
        /**
         *  二阶段cancel回滚方法,可以另命名,倒要保证与rollbackMethod一致
         * @param context
         * @return
         */
        boolean cancel(BusinessActionContext context);
    
    }
    

    新建实现类,实现上方接口,编写业务

    @Service
    public class InsertTCCServiceImpl extends ServiceImpl<InsertTCCMapper, Tcc>  implements InsertTCCService {
        @Autowired
        CouponService couponService;
    
    
        @Override
        public void insert(String name, Integer age) {
            // 防止业务悬挂,先判断有没有tcc表中有没有记录,如果有,一定是cancel执行过,要拒绝业务
            // 1、获取事务id
            String xid = RootContext.getXID();
            // 2、判断是否有记录
            Tcc byId = this.getById(xid);
            if (byId!=null){
                // cancel执行过,拒绝执行业务
                return;
            }
    
            // 业务
            TCoupon tCoupon = new TCoupon();
            tCoupon.setName(name);
            tCoupon.setAge(age);
            couponService.save(tCoupon);
    
            // 记录事务状态和业务id
            Tcc tcc = new Tcc();
            tcc.setBusinessId(tCoupon.getId());
            tcc.setState(0); // 0是try
            this.save(tcc);
        }
    
        @Override // 成功的方法
        public boolean confirm(BusinessActionContext context) {
            // 获取事务id
            String xid = context.getXid();
            // 根据id删除tcc记录
            boolean b = this.removeById(xid);
            return b;
        }
    
        @Override // try的反向,恢复业务修改的数据
        public boolean cancel(BusinessActionContext context) {
            // 获取事务id
            String xid = context.getXid();
            // 查询tcc表,获取我们寸的businessId
            Tcc tcc = this.getById(xid);
    
            // -----空回滚的判断,为null证明try没执行,需要空回滚,就是再添加一条tcc记录,state存cancel-----
            if (tcc == null){
                // 记录事务状态和业务id
                tcc = new Tcc();
                tcc.setBusinessId(context.getActionContext("name").toString()); // 从try传的参数获取,这个字段值要唯一
                tcc.setState(2); // 2是cancel
                this.save(tcc);
                return true;
            }
    
            // ----幂等处理,状态判断,防止重复处理-------
            if (tcc.getState() == 2){ // 已经提交过了
                return true;
            }
    
    
            // 根据businessId删除我们业务里新增的数据
            boolean b = couponService.removeById(tcc.getBusinessId());
            return b;
        }
    }
    

    需要事务的方法或类上(事务发起方TM)添加 @GlobalTransactional 注解
    接口上添加 @LocalTCC 注解,注:该注解添加在接口上,而不是实现类上

    方法上添加@TwoPhaseBusinessAction定义两阶段提交
    该注解属性:

    • name:定义一个全局唯一值,一般为该方法名
    • commitMethod:自定义提交方法名(二阶段提交),需和提交方法名保持一致
    • rollbackMethod:自定义回滚方法名(二阶段回滚),需和

    @BusinessActionContextParameter 加上该注解可传递参数到二阶段方法BusinessActionContext 上下文 可取到@BusinessActionContextParameter定义的参数

    1.3.4 Saga模式

    1.3.4.1 定义

    Saga 模式是 SEATA 提供的长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。

    Saga 模式适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁、长流程情况下可以保证性能。
    事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,可以使用 Saga 模式。

    saga 模式是 seata 提供的长事务解决方案,也分为两个阶段:

    • 一阶段:直接提交本地事务
    • 二阶段:成功则什么都不做;失败则通过编写补偿业务来回滚。


      image.png

    1.3.4.2 Saga模型优缺点

    Saga模式优点:

    • 事务参与者可以基于事件驱动实现异步调用,吞吐高
    • 一阶段可以直接提交事务,无锁,性能好
    • 不用编写 TCC 中的三个阶段,实现简单
    • 补偿服务即正向服务的“反向”,易于理解,易于实现;

    Saga缺点:

    • 软状态持续时间不长,失效性差
    • 没有锁,没有事务隔离,会有脏写
    • Saga 模式由于一阶段已经提交本地数据库事务,且没有进行 预留 动作,所以不能保证隔离性

    1.4 模式对比

    1.4.1 4种模式对比

    XA AT TCC SAGA
    一致性 强一致性 弱一致(也是最终一致) 弱一致(也是最终一致) 最终一致
    隔离性 完全隔离 基于全局锁隔离 基于资源预留隔离 无隔离
    代码侵入 有,要编写三个接口 有,要编写状态机和补偿业务
    性能 非常好 非常好
    场景 对一致性、隔离性有高业务的需求 基于关系型数据库的大多数分布式事务场景都可以 1、对性能要求较高的业务。2、有非关系型数据库要参与的业务 1、业务流程长、业务流程多。2、参与者包含其他公司或遗留系统服务,无法提供TCC模式要求的三个接口

    1.4.2 AT和TCC模式区别

    一个分布式的全局事务,整体式两阶段提交(Try-[Comfirm/Cancel])的模型,在seata中,AT模式与TCC模式事实上都是基于两阶段提交实现的,它们的区别在于:

    • AT模式基于支持本地ACID事务的关系型数据库:
      • 一阶段prepare行为:在本地事务中,一并提交“业务数据更新”和“相应回滚日志记录”;
      • 二阶段 commit 行为:马上成功结束,自动异步批量清理回滚日志;
      • 二阶段 rollback 行为:通过回滚日志,自动生成补偿操作,完成数据回滚;
    • 相应的,TCC模式需要我们认为编写代码实现提交和回滚:
      • 一键端 prepare 行为:调用自定义的prepare逻辑(如插入、删除、修改等操作);
      • 二阶段 commit 行为:调用自定义的 commit 逻辑;
      • 二阶段 rollback 行为:调用自定义的 rollback 逻辑;

    所以TCC模式就是把自定义的分支事务(提交回滚)纳入到全局事务管理中,seata中的TCC模式就是手动版的AT模式,它允许自定义两阶段的处理逻辑而不需要依赖AT模式的undo_log回滚表

    参考链接:
    https://blog.csdn.net/qq_48721706/article/details/122656490
    https://blog.csdn.net/weixin_44152047/article/details/118110391
    https://baijiahao.baidu.com/s?id=1712163514749816361&wfr=spider&for=pc

    相关文章

      网友评论

          本文标题:分布式事务之Seata讲解

          本文链接:https://www.haomeiwen.com/subject/zeuusdtx.html