问题场景
元数据
- 库存 100
- 订单记录为空
下单操作
@Autowired
RestTemplate restTemplate;
/**
* 下单
*
* @return
*/
@Transactional // 开启事务 异常后触发数据库回滚操作
@Override
public Order create(Order order) {
// 插入订单
orderMapper.insert(order);
// 扣减库存
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();paramMap.add("productId", order.getProductId());
String msg = restTemplate.postForObject("http://localhost:8071/stock/reduct", paramMap, String.class);
// 制造异常
int a = 1 / 0;
return order;
}
结果
>> 由于制造异常, 数据库事务回滚,订单没有入库;但库存确减少了1. 100 -> 99
问题解决
- 上述问题是 A服务 调用 B服务,因此A服务的事务无法回滚B服务的操作
- 微服务架构下,类似场景频繁遇到,因此就需要引入分布式事务组件 Seata
SeataServer
>> SeataServer的搭建,上一篇已详述,这里就不多介绍了。
引入依赖
<!--nacos-服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--1. 添加openfeign依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--seata的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
增加配置
- 订单、库存服务均增加 seata 、nacos 配置
spring:
datasource:
username: root
password: xxxxx
url: jdbc:mysql://localhost:3307/seata_stock?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#初始化时运行sql脚本
schema: classpath:sql/schema.sql
initialization-mode: never
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8847
username: nacos
password: nacos
alibaba:
seata:
tx-service-group: my_test_tx_group
application:
name: stock-seata
seata:
registry:
# 配置seata的注册中心, 告诉seata client 怎么去访问seata server(TC) type: nacos
nacos:
server-addr: 127.0.0.1:8847 # seata server 所在的nacos服务地址
application: seata-server # seata server 的服务名seata-server ,如果没有修改可以不配
username: nacos
password: nacos
group: SEATA_GROUP # seata server 所在的组,默认就是SEATA_GROUP,没有改也可以不配
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8847
username: nacos
password: nacos
group: SEATA_GROUP
编写OpenFeign接口
>> 启动类增加 @EnableFeignClients 注解
@FeignClient(value = "stock-seata", path = "/stock")
public interface StockFeignService {
// @RequestMapping("/reduct")
// public String reduct(@RequestParam(value="productId") Integer productId){
// stockService.reduct(productId);
// return "扣减库存";
// }
@RequestMapping("/reduct")
String reduct(@RequestParam(value = "productId") Integer productId);
}
修改调用类
>> @Transactional 变更为 @GlobalTransactional
@Autowired
StockFeignService stockFeignService;
/**
* 下单
* @return
*/
// @Transactional
// @Override
// public Order create(Order order) {
// // 插入订单
// orderMapper.insert(order);
//
//
// // 扣减库存
// MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
// paramMap.add("productId", order.getProductId());
//
// String msg = restTemplate.postForObject("http://localhost:8071/stock/reduct", paramMap,String.class );
//
// // 制造异常
// int a=1/0;
//
// return order;
// }
/**
* 下单
* @return
*/
@GlobalTransactional
@Override
public Order create(Order order) {
// 插入订单
orderMapper.insert(order);
// 扣减库存
stockFeignService.reduct(order.getProductId());
// 制造异常
int a=1/0;
return order;
}
启动服务运行测试
>> 库存表数量为 99 ,订单表记录为空
- 订单失败场景
- 订单成功场景 (注释掉制造异常的代码, 重启订单服务)
@GlobalTransactional
@Override
public Order create(Order order) {
// 插入订单
orderMapper.insert(order);
// 扣减库存
stockFeignService.reduct(order.getProductId());
// 制造异常
// int a=1/0;
return order;
}
- 下单成功, 库存 - 1
总结
微服务框架下,分布式事务的控制已多见不怪了,Seata组件就是其中的一种解决方案;本文介绍了Seata客户端使用,希望对你有所帮助!!!