实现XA模式,需要支持XA事务的数据库
AT模式是Seata的默认模式,满足两阶段提交协议:
创建两个SpringBoot工程,分别为storage-service
与order-service
,模拟从在order-service
服务中新增订单,然后调用storage-service
服务新增库存扣减记录;核心代码如下,完整代码参考文末github地址
;
在编码方式上,XA模式
与AT模式
保持一致,只需要将数据源代理配置修改为XA
即可,修改如下:
seata:
data-source-proxy-mode: XA
-- 数据库名称: seata-xa-demo.sql
-- 订单表
CREATE TABLE `tb_order`
(
`id` int(11) NOT NULL COMMENT '主键',
`count` int(11) NULL DEFAULT 0 COMMENT '下单数量',
`money` int(11) NULL DEFAULT 0 COMMENT '金额',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
-- 库存表
CREATE TABLE `tb_storage`
(
`id` int(11) NOT NULL COMMENT '主键',
`order_id` int(11) NOT NULL COMMENT '订单ID',
`count` int(11) NOT NULL DEFAULT 0 COMMENT '库存',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
注意: 如果使用高版本的mysql驱动可能会出现com.mysql.cj.conf.PropertySet.getBooleanReadableProperty(java.lang.String)
异常,需要将mysql版本回退到8.0.11版本
;
server:
port: 8082
spring:
application:
name: order-service
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3307/seata-at-demo?useUnicode=true&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: lhzlx
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: 64ed9ca7-d705-4655-b4e4-f824e420a12a
group: test
seata:
enabled: true
# 使用XA模式
data-source-proxy-mode: XA
application-id: ${spring.application.name}
# 事务组的名称,对应service.vgroupMapping.default_tx_group=xxx中配置的default_tx_group
tx-service-group: default_tx_group
# 配置事务组与集群的对应关系
service:
vgroup-mapping:
# default_tx_group为事务组的名称,default为集群名称
default_tx_group: default
disable-global-transaction: false
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: 64ed9ca7-d705-4655-b4e4-f824e420a12a
username: nacos
password: nacos
cluster: default
config:
type: nacos
nacos:
server-addr: 162.14.115.18:8848
group: SEATA_GROUP
namespace: 64ed9ca7-d705-4655-b4e4-f824e420a12a
username: nacos
password: nacos
data-id: seataServer.properties
上游服务通过@GlobalTransactional
注解开启全局事务,使用storageClient
进行feign调用
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Resource
private StorageClient storageClient;
@Resource
private OrderMapper orderMapper;
/**
* 创建订单
*
* @param order
* @return
*/
@Override
@GlobalTransactional
public Long create(Order order) {
// 创建订单
long id = new Random().nextInt(999999999);
order.setId(id);
orderMapper.insert(order);
try {
// 记录库存信息
storageClient.deduct(order.getId(), order.getCount());
// 模拟异常
// int a = 1 / 0;
} catch (FeignException e) {
log.error("下单失败,原因:{}", e.contentUTF8(), e);
throw new RuntimeException(e.contentUTF8(), e);
}
return order.getId();
}
}
@FeignClient("storage-service")
public interface StorageClient {
/**
* 扣减库存
*
* @param orderId
* @param count
*/
@PostMapping("/storage")
void deduct(@RequestParam("orderId") Long orderId, @RequestParam("count") Integer count);
}
server:
port: 8081
spring:
application:
name: storage-service
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3307/seata-xa-demo?useUnicode=true&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: lhzlx
cloud:
nacos:
discovery:
server-addr: 162.14.115.18:8848
namespace: 64ed9ca7-d705-4655-b4e4-f824e420a12a
group: test
# 在dev环境进行debug时,可以将时间设置长一些
#heart-beat-interval: 1000 #心跳间隔。单位为毫秒,默认5*1000
heart-beat-timeout: 300000 #心跳暂停,收不到心跳,会将实例设为不健康。单位为毫秒,默认15*1000
ip-delete-timeout: 4000000 #Ip删除超时,收不到心跳,会将实例删除。单位为毫秒,默认30*1000
seata:
enabled: true
# 开启XA模式
data-source-proxy-mode: XA
application-id: ${spring.application.name}
# 事务组的名称,对应service.vgroupMapping.default_tx_group=xxx中配置的default_tx_group
tx-service-group: default_tx_group
# 配置事务组与集群的对应关系
service:
vgroup-mapping:
# default_tx_group为事务组的名称,default为集群名称
default_tx_group: default
disable-global-transaction: false
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: 64ed9ca7-d705-4655-b4e4-f824e420a12a
username: nacos
password: nacos
cluster: default
config:
type: nacos
nacos:
server-addr: 162.14.115.18:8848
group: SEATA_GROUP
namespace: 64ed9ca7-d705-4655-b4e4-f824e420a12a
username: nacos
password: nacos
data-id: seataServer.properties
@Slf4j
@Service
public class StorageServiceImpl implements StorageService {
@Resource
private StorageMapper storageMapper;
/**
* 扣除存储数量
*
* @param orderId
* @param count
*/
@Override
public void deduct(Long orderId, int count) {
log.info("开始记录库存信息");
try {
long id = new Random().nextInt(999999999);
Storage storage = new Storage();
storage.setId(id);
storage.setOrderId(orderId);
storage.setCount(count);
storageMapper.insert(storage);
// 模拟异常
// int a = 1 / 0;
} catch (Exception e) {
throw new RuntimeException("扣减库存失败,可能是库存不足!", e);
}
log.info("库存信息记录成功");
}
}
测试时没有做截图进行演示,只说明了结果,可以运行代码设置异常进行验证
在order-service
服务中正常,在storage-service
服务的service中抛出异常,观察数据是否成功回滚;如果tb_order
与tb_storage
都不存在数据,则表示全局事务成功;
order-service
服务在执行storageClient.deduct()
方法后抛出异常,在storage-service
服务中正常,观察数据是否成功回滚;如果tb_order
与tb_storage
都不存在数据,则表示全局事务成功;
XA
模式与AT
模式不同点在于,XA是强一致性的而AT是最终一致性;
我们可以在上游服务执行完orderMapper.insert(order);
后马上进入断点,测试去观察数据库会发现tb_order
中不存在数据,再放行断点使程序正常执行,再次观察数据库会发现tb_order
中存在数据,表示实现了数据的强一致性而不是最终一致性;
Seata值AT模式代码实现:《seata-xa-demo》