Seata的几种事务模式

目录

XA模式

实现XA模式

 AT模式

 AT模式的脏写问题

 其他事务不获取全局锁的一个情况(AT模式写隔离的实现)

实现AT模式 

TCC模式

 TCC实现

 我们怎么样去判断是否空回滚和业务悬挂?

业务分析:

 Saga模式

总结


XA模式

提交成功:

Seata的几种事务模式_第1张图片

提交失败:

Seata的几种事务模式_第2张图片

第一阶段:RM资源管理器向TC事务协调者注册分支事务,然后发送执行sql(不提交)后的状态(失败/就绪),有利于保证ACID——>第二阶段:TC根据状态来判断是否要回滚,如果都成功就让RM事务管理器提交事务,如果失败就回滚;

具有强一致性seata相当于是在RM上做了一层封装;Seata的几种事务模式_第3张图片

XA模式优点:

1.事务的强一致性,只要有失败的,TC事务协调者就会发送信息让RM回滚——>满足ACID原则

2.没有代码侵入(*),常用数据库都支持

缺点:

1.第一阶段就要锁定数据库资源,但是却不提交,从而导致数据库所占用的资源不能释放(占数据库锁),性能较差

2.依赖关系型数据库实现事务

实现XA模式

步骤:

1.在yaml文件中开启XA代理模式

2.添加@GlobalTransactional注解开启全局事务 

Seata的几种事务模式_第4张图片

Seata的几种事务模式_第5张图片 

 当出现异常时,进行回滚

Seata的几种事务模式_第6张图片


 AT模式

  Seata的几种事务模式_第7张图片

 利用快照来保证事务的一致性,来进行数据回滚;

Seata的几种事务模式_第8张图片

 AT模式是一种最终一致的模式:因为RM资源管理器执行sql后会直接提交,那么此时如果是数据不一致的情况下,那么说明肯定是软一致,但是在阶段二时,AT模式RM资源管理器会利用快照进行数据回滚,从而保证最终一致;

AT模式直接提交,有利于提高效率Seata的几种事务模式_第9张图片

 AT模式的脏写问题(对同数据并发写的问题)

脏写问题:造成数据空转现象

什么是脏写:

简而言之,就是两个事务并发执行,修改同一条数据,我第一个事务修改并且提交之后,释放DB锁资源,第二个事务想要进行回滚,那么就会导致脏写——>前一个事务修改无效

Seata的几种事务模式_第10张图片

 解决:对事务2造成数据空转现象进行处理

1.事务1先获取DB锁,并且保存快照——>2.然后执行业务sql,我们在提交事务前获取全局锁(防止一提交事务,释放DB锁后,其他事务立马插入获取DB锁更改sql)——>3.此时全局锁会记录操作当前数据的事务,让该事务持有全局锁,然后提交事务释放DB锁——>4.此时其他事务可以争夺DB锁,执行业务sql——>5.然后和之前一样,它也要获取全局锁,但是全局锁此时已经被事务1拿了,所以它会进行自旋(300ms)——>6.然后事务1如果此时要根据快照恢复数据,那么就需要DB锁,但是DB锁此时被其他事务拿了——>7.死锁现象发生——>8.还好其他事务重试失败后会释放锁资源,因为获取全局锁失败,那么后面的事务提交也进行不了——>9.事务1再次拿到DB锁,可以进行快照恢复数据了;

Seata的几种事务模式_第11张图片

 其他事务不获取全局锁的一个情况(AT模式写隔离的实现)

利用了CAS的思想 :

实际上是有两份快照的:before-image、after-image

跟cas一样,before是我们要回滚目标的状态,而after是相当于验证的一个状态,如果满足after的内容,就可以设置为before;

如果不一样不满足的话,就会判断不能恢复回滚,那么我们可以记录异常发送警告;

Seata的几种事务模式_第12张图片

 总结:

AT:在第一阶段RM直接提交事务,释放数据库资源,不需要像XA模式那样,还需要将状态返回给TC事务协调者,还利用了全局锁实现读写分离:将表执行的事务储存起来,相当于一个标识;

并且没有代码侵入,seata自动完成回滚和提交——>seata相当于RM资源管理器的一个代理

Seata的几种事务模式_第13张图片 


实现AT模式 

lock_table:全局锁,undo_log:放的是快照信息 

Seata的几种事务模式_第14张图片

 Seata的几种事务模式_第15张图片

可以发现仓库服务失败

Seata的几种事务模式_第16张图片 来看看下单服务:

 branchType事务类型:AT模式;

 Rollback回滚事务

Seata的几种事务模式_第17张图片


TCC模式

Seata的几种事务模式_第18张图片

 对预留资源进行处理:Seata的几种事务模式_第19张图片

  那我们TCC模式是怎么保证一致性?

首先我们想想AT模式,在第一阶段,通过对数据库锁的获取完成事务,事务都是隔离的,所以有人成功有人失败,只有在二阶段完成回滚才能够保证数据的最终一致性,中间还是出现了软状态;

TCC模式,为什么就不需要锁了呢?我们AT模式是利用全局锁来保证一致性的——>执行sql后提交前上一道全局锁,那么其他事务的sql就执行不了进行自旋,超时就释放DB锁,而TCC解决:利用了每个事务都是预留资源进行处理——> 第一个事务冻结的金额和第二个事务冻结的金额是不一样的,跟其他事务是没有关系的,那么回滚事务也是跟其他事务不影响的,不需要加锁(类似Semaphore)

TCC工作模型:

Seata的几种事务模式_第20张图片

  TCC模式的关键在于有代码侵入:需要考虑Comfirm成功提交和Cancel数据回滚的编写

  优点:1.TCC第一阶段直接提交事务,提交完直接释放数据库资源,AT的话也是直接执行,但是使用了全局锁来保存事务操作的一个状态,保证其他事务争夺不了,XA的话第一阶段就垃圾了,不会提交sql业务,需要把状态给到TC事务协调者进行判断是否回滚还是提交(是一提交或者回滚就是全局那种);

2.无需生成快照与全局锁,依赖的是一个补偿操作,因为事务直接提交的原则,所以其他事务是操作不到自己的,可用于非关系型数据库

Seata的几种事务模式_第21张图片

 TCC实现

具体模式还得根据场景来,比如TCC,就很像Semaphore,一般来说是对一个共享资源进行操作,比如停车场的停车位,库存....,像下单服务就不适合了,因为你每次调用都是一个新的订单;

一个事务是可以有多个模式实现的

Seata的几种事务模式_第22张图片

 Seata的几种事务模式_第23张图片

 Seata的几种事务模式_第24张图片

 Seata的几种事务模式_第25张图片

 我们怎么样去判断是否空回滚和业务悬挂?

利用两者相互判断

业务分析:

Seata的几种事务模式_第26张图片

Seata的几种事务模式_第27张图片

我们可以在BusinessActionContext中获取里面的参数

事务表:表示事务冻结金额,冻结金额状态发生改变——>表示那部分被锁定

事务id,用户id,冻结金额和状态

Seata的几种事务模式_第28张图片

数据库中设计了金额不能<0,所以可以业务不用判断金额<0了 

 Seata的几种事务模式_第29张图片

package cn.itcast.account.service;

import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

/**
 * @author diao 2022/6/11
 */
@LocalTCC
public interface AccountTCCService {
    /**
     * 扣除存储数量
     */
    @TwoPhaseBusinessAction(name="confirm",commitMethod = "confirm",rollbackMethod = "cancel")
    void deduct(@BusinessActionContextParameter(paramName = "commodityCode") String commodityCode, @BusinessActionContextParameter(paramName = "count") int count);

    /**
     * 提交
     * @param ctx
     * @return
     */
    boolean confirm(BusinessActionContext ctx);

    /**
     * 进行回滚
     * @param ctx
     * @return
     */
    boolean cancel(BusinessActionContext ctx);

}

 业务实现类:

package cn.itcast.account.service.impl;

import cn.itcast.account.entity.AccountFreeze;
import cn.itcast.account.mapper.AccountFreezeMapper;
import cn.itcast.account.mapper.AccountMapper;
import cn.itcast.account.service.AccountTCCService;
import io.seata.core.context.RootContext;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author diao 2022/6/11
 */
@Slf4j
@Service
public class AccountTCCServiceImpl implements AccountTCCService {
   
    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private AccountFreezeMapper accountFreezeMapper;
    
     @Override
    public void deduct(String userid, int money) {
         //0.获取事务id
         String xid = RootContext.getXID();

         //业务悬挂的判断
         AccountFreeze oldFreeze = accountFreezeMapper.selectById(xid);
         if(oldFreeze!=null){
             //CANCEl执行过,拒绝业务
             return;
         }


         //1.扣减可用余额
         accountMapper.deduct(userid,money);
         
         //2.冻结,记录事务状态
         AccountFreeze freeze = new AccountFreeze();
         freeze.setUserId(userid);
         freeze.setFreezeMoney(money);
         freeze.setState(AccountFreeze.State.TRY);
         freeze.setXid(xid);
         accountFreezeMapper.insert(freeze);
     }
     
    /**
     * 将冻结金额去除,那么总金额就是扣减冻结金额之后的金额
     * @param ctx
     * @return:相当于之前的提交
     */
    @Override
    public boolean confirm(BusinessActionContext ctx) {
        //0.查询冻结记录,得到事务xid
        String xid = ctx.getXid();

        //1.根据事务xid删除冻结记录
        int count = accountFreezeMapper.deleteById(xid);
        return count==1;
    }

    /**
     * 回滚冻结金额
     * 会在AccountFreeze中插入行数据
     * @param ctx:里面是事务内容
     * @return
     */
    @Override
    public boolean cancel(BusinessActionContext ctx) {
       
        //0.根据事务id查询冻结记录
        String xid = ctx.getXid();
        AccountFreeze freeze = accountFreezeMapper.selectById(xid);
        String userId = ctx.getActionContext("userId").toString();

        //补:空回滚判断,若account_freeze表中记录为null代表try没执行,在freeze中插入空记录
        if(freeze==null){
            freeze=new AccountFreeze();
            freeze.setUserId(userId);
            freeze.setFreezeMoney(0);
            freeze.setState(AccountFreeze.State.CANCEL);
            freeze.setXid(xid);
            accountFreezeMapper.insert(freeze);
            return true;
        }
       
        
        //判断幂等:因为我们是不能反复进入cancel这个方法中,
        // 下面的恢复金额方法会造成数据不一致
        if (freeze.getState()==AccountFreeze.State.CANCEL){
            return true;
        }
        
        
       //1.恢复可用金额
       accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney()); 

       //2.对冻结金额表进行操作,将冻结金额清空,状态改变为CANCEL
        freeze.setFreezeMoney(0);
        freeze.setState(AccountFreeze.State.CANCEL);
        int count = accountFreezeMapper.updateById(freeze);
        return count==1;
    }
}

Seata的几种事务模式_第30张图片

 Saga模式

与TCC模式类似,但是TCC第一阶段只是将资源进行冻结,真正的去除还是在第二阶段的,而Saga模式是直接提交本地事务,第二阶段直接操作事务本身:成功则什么都不做,失败则通过编写补偿业务来进行回滚;

与AT相比没有用锁,与TCC比没有冻结资源,性能较好;

失败用自定义的补偿来写;

缺点:

没有保证隔离性,既没有隔离预留资源又没有上锁,容易出现脏写  

Seata的几种事务模式_第31张图片

Seata的几种事务模式_第32张图片

总结

Seata的几种事务模式_第33张图片

 (26条消息) SEATA是什么?它的四种分布式事务模式_m0_46396722的博客-CSDN博客_seata模式

你可能感兴趣的:(微服务,seata)