XA模式的缺点:
1、修改applicaiton.yml(每个参与事务的微服务)文件,开启XA模式
seata:
data-source-proxy-mode: XA
2、给发起全局事务的入口方法添加@GlobalTransactional注解,本例中是OrderServiceImpl中的create方法:
@GlobalTransactional
3、重启服务并测试
当库存不足时,会进行事务的回滚
我们的金额并没有减少,
AT模式与XA模式最大的区别:
AT模式的脏写
解决办法:引入全局锁
AT模式的优点:
一阶段完成直接提交事务,释放数据库资源,性能比较好
利用全局锁实现读写隔离
没有代码侵入,框架自动完成回滚和提交
AT模式的缺点:
实现AT模式:
lock_table导入到TC服务相关联的数据库
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table` (
`row_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`xid` varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`branch_id` bigint(20) NOT NULL,
`resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`table_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`pk` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime NULL DEFAULT NULL,
`gmt_modified` datetime NULL DEFAULT NULL,
PRIMARY KEY (`row_key`) USING BTREE,
INDEX `idx_branch_id`(`branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
导入微服务相关联的数据库
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` longblob NOT NULL COMMENT 'rollback info',
`log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` datetime(6) NOT NULL COMMENT 'create datetime',
`log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Compact;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z6jIDSEj-1662039964373)(D:\笔记\a\image-20220831212022129.png)]
2、修改配置文件为AT
data-source-proxy-mode: AT
3、重启微服务
TCC模式的每个阶段是做什么的?
TCC的优点是什么?
TCC的缺点是什么?
案例:改造acccount-service服务,利用TCC实现分布式事务
修改account-service,编写try、confirm、cancel逻辑
try业务:添加冻结金额,扣减可用金额
confirm业务:删除冻结金额
cancel业务:删除冻结金额,恢复可用金额
保证confirm、cancel接口的幂等性
允许空回滚(当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚):cancel业务中,根据xid查询account_freeze,如果为null则说明try还没做,需要空回滚
拒绝业务悬挂(对于已经空回滚的业务,如果以后继续执行try,就永远不可能confirm或cancel,这就是业务悬挂。应当阻止执行空回滚后的try操作,避免悬挂):try业务中,根据xid查询account_freeze ,如果已经存在则证明Cancel已经执行,拒绝执行try业务
为了实现空回滚、防止业务悬挂,以及幂等性要求。我们必须在数据库记录冻结金额的同时,记录当前事务id和执行状态,为此我们设计了一张表
/*
Navicat Premium Data Transfer
Source Server : local
Source Server Type : MySQL
Source Server Version : 50622
Source Host : localhost:3306
Source Schema : seata_demo
Target Server Type : MySQL
Target Server Version : 50622
File Encoding : 65001
Date: 23/06/2021 16:23:20
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for account_freeze_tbl
-- ----------------------------
DROP TABLE IF EXISTS `account_freeze_tbl`;
CREATE TABLE `account_freeze_tbl` (
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`freeze_money` int(11) UNSIGNED NULL DEFAULT 0,
`state` int(1) NULL DEFAULT NULL COMMENT '事务状态,0:try,1:confirm,2:cancel',
PRIMARY KEY (`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
-- ----------------------------
-- Records of account_freeze_tbl
-- ----------------------------
SET FOREIGN_KEY_CHECKS = 1;
两个表对应的实体类
@Data
@TableName("account_tbl")
public class Account {
@TableId
private Long id;
private String userId;
private Integer money;
}
@Data
@TableName("account_freeze_tbl")
public class AccountFreeze {
@TableId(type = IdType.INPUT)
private String xid; //事务id
private Stri ng userId; //用户id
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;
}
}
声明TCC的三个接口
@LocalTCC
public interface AccountTCCService {
@TwoPhaseBusinessAction(name = "deduct",commitMethod = "confirm",rollbackMethod = "cancel")
void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "money")int money);
boolean confirm(BusinessActionContext ctx);
boolean cancel(BusinessActionContext ctx);
}
实现TCC接口
三个基本业务实现
@Service
public class AccountServiceImpl implements AccountService {
//注入mapper
@Autowired
private AccountMapper accountMapper;
@Autowired
private AccountFreezeMapper freezeMapper;
@Override
public void deduct(String userId, int money) {
//获取事务的id
String xid = RootContext.getXID();
//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);
}
@Override
public boolean confirm(BusinessActionContext ctx) {
//获取事务id
String xid = ctx.getXid();
//删除冻结金额
int i = freezeMapper.deleteById(xid);
//判断是否删除成功
return i==1;
}
@Override
public boolean cancel(BusinessActionContext ctx) {
String xid = ctx.getXid();
//0、查询冻结记录
AccountFreeze accountFreeze = freezeMapper.selectById(xid);
//1、恢复可用金额
accountMapper.refund(accountFreeze.getUserId(),accountFreeze.getFreezeMoney());
//2、将动态金额清零,状态修改为Cancel
accountFreeze.setFreezeMoney(0);
accountFreeze.setState(AccountFreeze.State.CANCEL);
int i = freezeMapper.updateById(accountFreeze);
return i==1;
}
}
解决空回滚和业务悬挂,增加幂等判断
package cn.itcast.account.service.impl;
import cn.itcast.account.mapper.AccountFreezeMapper;
import cn.itcast.account.mapper.AccountMapper;
import cn.itcast.account.entity.AccountFreeze;
import cn.itcast.account.service.AccountTccService;
import io.seata.core.context.RootContext;
import io.seata.rm.tcc.api.BusinessActionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @ClassName AccountServiceImpl
* @Description TODO
* @Author ylh
* @Date 2022/9/1 20:54
* @Version 1.0
*/
@Service
public class AccountTccServiceImpl implements AccountTccService {
//注入mapper
@Autowired
private AccountMapper accountMapper;
@Autowired
private AccountFreezeMapper freezeMapper;
@Override
public void deduct(String userId, int money) {
//获取事务的id
String xid = RootContext.getXID();
//判断freeze中是否有冻结记录,如果有,一定是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);
}
@Override
public boolean confirm(BusinessActionContext ctx) {
//获取事务id
String xid = ctx.getXid();
//删除冻结金额
int i = freezeMapper.deleteById(xid);
//判断是否删除成功
return i==1;
}
@Override
public boolean cancel(BusinessActionContext ctx) {
String xid = ctx.getXid();
//0、查询冻结记录
AccountFreeze accountFreeze = freezeMapper.selectById(xid);
//空回滚的判断,没有执行冻结
if ( accountFreeze==null){
//需要空回滚
accountFreeze=new AccountFreeze();
accountFreeze.setUserId(ctx.getActionContext("userId").toString());
accountFreeze.setFreezeMoney(0);
accountFreeze.setState(AccountFreeze.State.CANCEL);
accountFreeze.setXid(xid);
freezeMapper.insert(accountFreeze);
return true;
}
//幂等判断--cancle超时了,又调用了一次cancel
if (accountFreeze.getState()== AccountFreeze.State.CANCEL){
//已经处理过了,无需继续处理
return true;
}
//1、恢复可用金额
accountMapper.refund(accountFreeze.getUserId(),accountFreeze.getFreezeMoney());
//2、将动态金额清零,状态修改为Cancel
accountFreeze.setFreezeMoney(0);
accountFreeze.setState(AccountFreeze.State.CANCEL);
int i = freezeMapper.updateById(accountFreeze);
return i==1;
}
}
修改web中service方法
@Autowired
private AccountTCCService accountService;
Saga模式是SEATA提供的长事务解决方案。也分为两个阶段:
一阶段:直接提交本地事务
二阶段:成功则什么都不做;失败则通过编写补偿业务来回滚
Saga模式优点:
事务参与者可以基于事件驱动实现异步调用,吞吐高
一阶段直接提交事务,无锁,性能好
不用编写TCC中的三个阶段,实现简单
缺点:
软状态持续时间不确定,时效性差
没有锁,没有事务隔离,会有脏写