SEATA框架1

一、主要用于解决分布式事务,其中定义了三个角色,分别是TC(事务协调员)、TM(事务管理员)、RM(资源管理员,也就是单个微服务的管理员)。

二、基于BASE理论,对于分布式事务提供了4种解决方式:XA 、AT、TCC、SAGA。

1.XA模式:(分阶段提交、强一致、无代码入侵)

【一阶段】tm开启全局事务给tc,tm调用微服务,rm拦截到请求并注册分支事务、执行sql但不提交、报告状态到tc。
【二阶段】tm通知tc结束业务,tc检查状态:失败回滚,成功则提交。

【实操】(1).修改每个参与事务的微服务的application.yml文件

加入

seata:

 data-source-proxy-mode:XA #开启数据源代理的某某模式(让seata代理数据源,当发起sql请求时将由RM拦截并作出处理)

(2).给入口方法添加@GlobalTransactional注解

(3).重启服务

2.AT模式:(分阶段提交、最终一致、无代码侵入、seata默认模式)

通过加全局锁实现隔离性,通过生成快照实现回滚。

【一阶段】tm开启全局事务给tc,tm调用微服务,rm拦截到请求并注册分支事务、执行sql并提交,rm拦截到执行后生成更新前后的快照:undo_log,报告状态到tc。
【二阶段】tm通知tc结束业务,tc检查分支事务状态:失败回滚,成功则删除快照。如图

【对比XA】XA在一阶段中不提交事务,锁定资源,AT模式则相反;XA依赖数据库机制实现回滚,AT利用数据快照实现回滚。

【脏写问题】由于没有资源锁定,顾当回滚前如果有一个x进程更改了数据,回滚时会出现x进程的写入失败,称作脏写问题。

【全局锁】为了解决脏写问题,引入全局锁:由TC记录当前正在操作某行数据的事务,该事务持有全局锁(持久化的记录操作的表),具备执行权。如图

 对于不在seata管理之内,但仍然需要操作相同字段的业务,这种模式仍存在脏写问题,因而,要在生成快照时需要记录seata业务的操作前后的状态,并在回滚时做一个对比,若出现记录的更新后数据与当前数据不一致,则说明有其他业务修改了数据,这时就需要发送警告,人工介入。

【实操】(1)在tc相关的数据库中创建lock_table表,用于记录全局锁;在微服务相关的数据库中创建undo_log表,用于记录快照

(2)修改每个参与事务的微服务的application.yml文件,加入

seata:

 data-source-proxy-mode: AT #开启数据源代理的某某模式(让seata代理数据源,当发起sql请求时将由RM拦截并作出处理)

(3)给入口方法添加@GlobalTransactional注解

(4)重启服务

3.TCC模式:(分阶段提交、最终一致、有代码侵入)

通过预留所要操作的字段资源实现隔离性与回滚。其中TCC分别表示资源检查和预留(try)、业务执行和提交(confirm)、资源释放(cancel)。

【一阶段】tm开启全局事务给tc,tm调用微服务,rm拦截到请求并注册分支事务、预留资源(try)、提交事务、报告状态到tc。直接释放了数据库锁。
【二阶段】tm通知tc结束业务,tc检查分支事务状态:预留资源若足够,则执行并提交(confirm),否则回滚(cancel)。其中tcc三个操作需要手动编写代码。如图

 原理例如下图

 【优】一阶段直接提交事务,释放数据库资源,性能好;相比AT,无锁无快照,性能更强,不依赖数据库事务,依赖补偿操作,可用于非事务型数据库

【缺】代码侵入,需要考虑代码的健壮性、软状态。

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

【业务悬挂】已经空回滚后的业务,当阻塞解除后,若业务继续自动执行try操作,因为事务已经结束,将不会再有第二阶段,这样的情况称业务悬挂。应组织回滚后的try操作,避免悬挂。

【实操】

try业务:记录冻结金额和事务状态到account_freeze表,扣减account表的可用金额

confirm业务:根据xid删除account_freeze表的冻结记录

cancel业务:修改冻结金额为0,state为2,修改account表,恢复可用金额

判断是否需要空回滚:cancel业务中,根据xid查询freeze表,如果为null则说明try还没做,需要空回滚

避免业务悬挂:try业务中,根据xid查询freeze表,如果已经存在证明cancel已经执行过,就要拒绝try

(1)数据库中除了已有的表外,还需要创建account_freeze_tbl服务的冻结表(以账户account服务为例)

CREATE TABLE 'account_freeze_tbl'(
 'xid' varchar(128) NOT NULL,
 'user_id' varchar(255) DEFAULT NULL COMMENT '用户id', 
 'freeze_money' int(11) unsigned DEFAULT '0' COMMENT '冻结金额',
 'state' int(1) DEFAULT NULL COMMENT '事务状态,0:try,1:confirm,2:cancel',PRIMARY KEY ('xid')  USING BTREE
)ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

(2)由于try业务需要预留的资源存储到数据库的account_freeze表中,对应的也需要创建一个冻结实体类AccountFreeze;再编写对应的mapper;声明TCC接口,编写包含try、confirm、cancel业务的实现类;

//预留的冻结资源实体类
@Data
@TableName("account_freeze_tbl")
public class AccountFreeze {
    @TableId(type = IdType.INPUT)
    private String xid;
    private String userId;
    private Integer freezeMoney;
    private Integer state;

    public static abstract class State {
        public final static int TRY = 0;
        public final static int CONFIRM = 1;
        public final static int CANCEL = 2;
    }
}
public interface AccountFreezeMapper extends BaseMapper<AccountFreeze> {
}
@LocalTCC
public interface AccountTCCService {
    //Try逻辑,@TwoPhaseBusinessAction中的name属性要与当前方法名一致,用于指定Try逻辑对应的方法

    @TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
    void deduct(@BusinessActionContextParameter(paramName = "userId") String userId
            ,@BusinessActionContextParameter(paramName = "money") int money);

    //二阶段confirm确认方法、可以另命名,但要保证与commitMethod一致
    //@param context 上下文,可以传递try方法的参数
    //@return boolean 执行是否成功
    boolean confirm(BusinessActionContext ctx);

    //二阶段回滚方法,要保证与rollbackMethod一致
    boolean cancel(BusinessActionContext ctx);
}
@Service
@Slf4j
public class AccountTCCServiceImpl implements AccountTCCService {
    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private AccountFreezeMapper freezeMapper;

    @Override
    @Transactional
    //try
    public void deduct(String userId, int money) {
        //0.用seata提供的api获取事务的xid
        String xid = RootContext.getXID();
        //【业务悬挂的判断】判断表中是否有冻结记录 有则表明cancel已经执行过了 就要拒绝业务
        AccountFreeze oldFreeze = freezeMapper.selectById(xid);
        if(oldFreeze!=null){
            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);
        freezeMapper.insert(freeze);
    }
    //confirm
    @Override
    public boolean confirm(BusinessActionContext ctx) {
        //获取事务id
        String xid = ctx.getXid();
        //删除冻结记录
        int count = freezeMapper.deleteById(xid);//count:删除的条数
        return count==1;
    }
    //cancel
    @Override
    public boolean cancel(BusinessActionContext ctx) {
        //查询冻结记录
        String xid = ctx.getXid();
        AccountFreeze freeze = freezeMapper.selectById(xid);
        //【空回滚判断】若freeze为null 为null证明try没执行 则需要空回滚
        String userId = ctx.getActionContext("userId").toString();
        if(freeze==null){
            freeze=new AccountFreeze();
            freeze.setUserId(userId);
            freeze.setFreezeMoney(0);
            freeze.setState(AccountFreeze.State.CANCEL);
            freeze.setXid(xid);
            freezeMapper.insert(freeze);
            return true;
        }
        //幂等判断
        if(freeze.getState()==AccountFreeze.State.CANCEL){
            return true;
        }
        //恢复可用金额
        accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());
        //将冻结金额清零,恢复状态
        freeze.setFreezeMoney(0);
        freeze.setState(AccountFreeze.State.CANCEL);
        int count = freezeMapper.updateById(freeze);
        return count==1;
    }
}

(3)重启服务

注:这是一个有三个微服务的boot demo实例,三个服务分别是storage、account、order。在实际开发中,根据业务的不同,tcc代码也将不同。

4.Saga模式:(分阶段提交、最终一致、有代码侵入、长事务)

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

【优】可以基于事件驱动实现异步调用,吞吐高;一阶段直接提交,无锁,性能好;不用编写tcc三个阶段,实现简单

【缺】软状态持续时间不稳定,时效性差;无锁,无隔离,会有脏写

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值