SpringCloud Alibaba 2021微服务实战二十九 Seata 整合Nacos实战

场景说明

订单服务order-service需要对外提供创建订单的接口,创建订单的业务逻辑如下:

 

 

先调用本地的orderService保存订单操作,然后通过feign调用远程的accout-service进行账户余额扣减,最后再通过feign调用远程的product-service进行库存扣减操作。

关键的逻辑代码如下:

  • OrderController对外提供创建订单的接口
@PostMapping("/order/create")
public ResultData<OrderDTO> create(@RequestBody OrderDTO orderDTO){
	log.info("create order:{}",orderDTO);
	orderDTO.setOrderNo(UUID.randomUUID().toString());
	orderDTO.setAmount(orderDTO.getPrice().multiply(new BigDecimal(orderDTO.getCount())));
	orderService.createOrder(orderDTO);
	return ResultData.success("create order success");
}
  • OrderServiceImpl负责处理创建订单的业务逻辑
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void createOrder(OrderDTO orderDTO) {
	Order order = new Order();
	BeanUtils.copyProperties(orderDTO,order);
	//本地存储Order
	this.saveOrder(order);
	//库存扣减
	productFeign.deduct(orderDTO.getProductCode(),order.getCount());
	//账户余额扣减
	accountFeign.reduce(orderDTO.getAccountCode(), orderDTO.getAmount());
}

@Transactional(rollbackFor = RuntimeException.class)
void saveOrder(Order order) {
	orderMapper.insert(order);
}

本地先保存订单,然后调用两个远程服务进行扣减操作。

  • AccountServiceImpl扣减账户余额
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void reduceAccount(String accountCode, BigDecimal amount) {
	Account account = accountMapper.selectByCode(accountCode);
	if(null == account){
		throw new RuntimeException("can't reduce amount,account is null");
	}
	BigDecimal subAmount = account.getAmount().subtract(amount);
	if(subAmount.compareTo(BigDecimal.ZERO) < 0){
		throw new RuntimeException("can't reduce amount,account'amount is less than reduce amount");
	}
	account.setAmount(subAmount);
	accountMapper.updateById(account);
}

做些简单的校验,当账户余额不足的时候不允许扣减操作。

  • ProductServiceImpl扣减产品库存
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void deduct(String productCode, Integer deductCount) {
	Product product = productMapper.selectByCode(productCode);
	if(null == product){
		throw new RuntimeException("can't deduct product,product is null");
	}
	int surplus = product.getCount() - deductCount;
	if(surplus < 0){
		throw new RuntimeException("can't deduct product,product's count is less than deduct count");
	}
	product.setCount(surplus);
	productMapper.updateById(product);
}

做些简单的校验,当产品库存不足时不允许扣减操作。

order-serviceproduct-serviceaccount-service分属不同的服务,当其中一个服务抛出异常无法提交时就会导致分布式事务,如当使用feign调用account-service执行扣减账户余额时,account-service校验账户余额不足抛出异常,但是order-service的保存操作不会回滚;或者是前两步执行成功但是product-service校验不通过前面的操作也不会回滚,这就导致了数据不一致,也就是分布式事务问题!

Seata解决方案

 

引入seata组件

<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-alibaba-seata</artifactId>
</dependency>

在配置文件中增加seata配置

spring:
  cloud:
    alibaba:
      seata:
        tx-service-group: ${spring.application.name}-seata

Seata Client 配置修改

  • 将Seata Server 配置目录下的registry.conf文件拷贝到微服务中的resources文件夹下

 

  • 修改拷贝后的registry.conf
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    cluster = "default"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"

  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
  }
}

修改\seata\conf\nacos-config.txt文件,作如下两处修改:

  • 修改tx-service-group


如上图所示,修改的格式为service.vgroup_mapping.${spring.alibaba.seata.tx-service-group}=default,结合我们的系统服务修改后的结果如下:

service.vgroup_mapping.account-service-seata=default
service.vgroup_mapping.product-service-seata=default
service.vgroup_mapping.order-service-seata=default
  • 开启数据库自动代理
support.spring.datasource.autoproxy=true
  • 将配置推送到Nacos

生成undo_log表

在微服务的业务库下执行如下语句,生成undo_log表

-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
drop table `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

开启全局事务

在分布式事务方法入口添加注解@GlobalTransactional,这里只需要在createOrder方法上添加此注解即可!

@GlobalTransactional(name = "TX_ORDER_CREATE")
@Override
public void createOrder(OrderDTO orderDTO) {
	Order order = new Order();
	BeanUtils.copyProperties(orderDTO,order);
	//本地存储Order
	this.saveOrder(order);
	log.info("ORDER XID is: {}", RootContext.getXID());
	//账户余额扣减
	accountFeign.reduce(orderDTO.getAccountCode(), orderDTO.getAmount());
	//库存扣减
	productFeign.deduct(orderDTO.getProductCode(),orderDTO.getCount());
}

在代码中可以使用RootContext.getXID()获取全局xid

启动服务

服务正常启动后在Seata Server控制台可以看到注册信息

 

 

接口测试

改造完成后对接口进行测试,如果其他服务抛出异常会看到如下错误日志,再结合数据库数据观察是否正常回滚

 

 

执行过程中我们通过debug可以发现undo_log表会不断插入数据,在执行后又会被删除。

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值