目录
XA模式
实现XA模式
AT模式
AT模式的脏写问题
其他事务不获取全局锁的一个情况(AT模式写隔离的实现)
实现AT模式
TCC模式
TCC实现
我们怎么样去判断是否空回滚和业务悬挂?
业务分析:
Saga模式
总结
XA模式
提交成功:
提交失败:
第一阶段:RM资源管理器向TC事务协调者注册分支事务,然后发送执行sql(不提交)后的状态(失败/就绪),有利于保证ACID——>第二阶段:TC根据状态来判断是否要回滚,如果都成功就让RM事务管理器提交事务,如果失败就回滚;
XA模式优点:
1.事务的强一致性,只要有失败的,TC事务协调者就会发送信息让RM回滚——>满足ACID原则
2.没有代码侵入(*),常用数据库都支持
缺点:
1.第一阶段就要锁定数据库资源,但是却不提交,从而导致数据库所占用的资源不能释放(占数据库锁),性能较差
2.依赖关系型数据库实现事务
实现XA模式
步骤:
1.在yaml文件中开启XA代理模式
2.添加@GlobalTransactional注解开启全局事务
当出现异常时,进行回滚
AT模式
利用快照来保证事务的一致性,来进行数据回滚;
AT模式是一种最终一致的模式:因为RM资源管理器执行sql后会直接提交,那么此时如果是数据不一致的情况下,那么说明肯定是软一致,但是在阶段二时,AT模式RM资源管理器会利用快照进行数据回滚,从而保证最终一致;
AT模式的脏写问题(对同数据并发写的问题)
脏写问题:造成数据空转现象
什么是脏写:
简而言之,就是两个事务并发执行,修改同一条数据,我第一个事务修改并且提交之后,释放DB锁资源,第二个事务想要进行回滚,那么就会导致脏写——>前一个事务修改无效
解决:对事务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锁,可以进行快照恢复数据了;
其他事务不获取全局锁的一个情况(AT模式写隔离的实现)
利用了CAS的思想 :
实际上是有两份快照的:before-image、after-image
跟cas一样,before是我们要回滚目标的状态,而after是相当于验证的一个状态,如果满足after的内容,就可以设置为before;
如果不一样不满足的话,就会判断不能恢复回滚,那么我们可以记录异常发送警告;
总结:
AT:在第一阶段RM直接提交事务,释放数据库资源,不需要像XA模式那样,还需要将状态返回给TC事务协调者,还利用了全局锁实现读写分离:将表执行的事务储存起来,相当于一个标识;
并且没有代码侵入,seata自动完成回滚和提交——>seata相当于RM资源管理器的一个代理
实现AT模式
lock_table:全局锁,undo_log:放的是快照信息
可以发现仓库服务失败
branchType事务类型:AT模式;
Rollback回滚事务
TCC模式
那我们TCC模式是怎么保证一致性?
首先我们想想AT模式,在第一阶段,通过对数据库锁的获取完成事务,事务都是隔离的,所以有人成功有人失败,只有在二阶段完成回滚才能够保证数据的最终一致性,中间还是出现了软状态;
而TCC模式,为什么就不需要锁了呢?我们AT模式是利用全局锁来保证一致性的——>执行sql后提交前上一道全局锁,那么其他事务的sql就执行不了进行自旋,超时就释放DB锁,而TCC解决:利用了每个事务都是预留资源进行处理——> 第一个事务冻结的金额和第二个事务冻结的金额是不一样的,跟其他事务是没有关系的,那么回滚事务也是跟其他事务不影响的,不需要加锁(类似Semaphore)
TCC工作模型:
TCC模式的关键在于有代码侵入:需要考虑Comfirm成功提交和Cancel数据回滚的编写
优点:1.TCC第一阶段直接提交事务,提交完直接释放数据库资源,AT的话也是直接执行,但是使用了全局锁来保存事务操作的一个状态,保证其他事务争夺不了,XA的话第一阶段就垃圾了,不会提交sql业务,需要把状态给到TC事务协调者进行判断是否回滚还是提交(是一提交或者回滚就是全局那种);
2.无需生成快照与全局锁,依赖的是一个补偿操作,因为事务直接提交的原则,所以其他事务是操作不到自己的,可用于非关系型数据库
TCC实现
具体模式还得根据场景来,比如TCC,就很像Semaphore,一般来说是对一个共享资源进行操作,比如停车场的停车位,库存....,像下单服务就不适合了,因为你每次调用都是一个新的订单;
一个事务是可以有多个模式实现的
我们怎么样去判断是否空回滚和业务悬挂?
利用两者相互判断
业务分析:
我们可以在BusinessActionContext中获取里面的参数
事务表:表示事务冻结金额,冻结金额状态发生改变——>表示那部分被锁定
事务id,用户id,冻结金额和状态
数据库中设计了金额不能<0,所以可以业务不用判断金额<0了
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;
}
}
Saga模式
与TCC模式类似,但是TCC第一阶段只是将资源进行冻结,真正的去除还是在第二阶段的,而Saga模式是直接提交本地事务,第二阶段直接操作事务本身:成功则什么都不做,失败则通过编写补偿业务来进行回滚;
与AT相比没有用锁,与TCC比没有冻结资源,性能较好;
失败用自定义的补偿来写;
缺点:
没有保证隔离性,既没有隔离预留资源又没有上锁,容易出现脏写
总结
(26条消息) SEATA是什么?它的四种分布式事务模式_m0_46396722的博客-CSDN博客_seata模式