Version: Next

案例代码实现

三个微服务

  • Order-Module:seata-order-service2001 订单模块
  • Storage-Module: seata-storage-service2002 库存模块
  • Account-Module: seata-account-service2003 账户模块

Order-Module

seata-order-service2001

<dependencies>
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--hutool 测试雪花算法-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-captcha</artifactId>
<version>5.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.errorprone/error_prone_core -->
<!--<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>0.92</version>
</dependency>-->
</dependencies>
提示
  • 如果后续调用报超时异常,那么参考 OpenFeign 的只是,修改默认超时时间即可
    • feign.client.config.default.connect-timeout=300000
    • feign.client.config.default.read-timeout=300000
  • 控制台 Netty 持续报错属于正常情况,觉得烦可以手动关闭 Netty 日志打印
    • logging.level.io.seata.core.rpc.netty.NettyClientChannelManager=off

Storage-Module

seata-storage-service2002

<dependencies>
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--hutool 测试雪花算法-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-captcha</artifactId>
<version>5.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.errorprone/error_prone_core -->
<!--<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>0.92</version>
</dependency>-->
</dependencies>

Account-Module

seata-account-service2003

<dependencies>
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--hutool 测试雪花算法-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-captcha</artifactId>
<version>5.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.errorprone/error_prone_core -->
<!--<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>0.92</version>
</dependency>-->
</dependencies>

使用

默认三个数据库的三张表数据情况

  • seata_account -> t_account
iduser_idtotalusedresidue
11100001000
  • seata_order -> t_order
iduser_idproduct_idcountmoneystatus
------
  • seata_storage -> t_storage
idproduct_idtotalusedresidue
111000100

正常下单

  • 访问 http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
    • 1 号用户,买 1 号产品,买了 10 个,花费 100 块钱
  • 查看数据库变化
  • seata_account -> t_account
iduser_idtotalusedresidue
111000100900
  • seata_order -> t_order
iduser_idproduct_idcountmoneystatus
711101001
  • seata_storage -> t_storage
idproduct_idtotalusedresidue
111001090

正常完成业务,没毛病!

@GlobalTransactional 注解

超时异常 + 没加 @GlobalTransactional

故意造异常

Account-Module

  • 在 Account-service2003 的 Service 层 Impl 代码中,故意添加一个 20 秒睡眠,来制造 超时异常
@Service
public class AccountServiceImpl implements AccountService {
private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
@Resource
private AccountDao accountDao;
@Override
public void decrease(Long userId, BigDecimal money) {
LOGGER.info("----> account-service中扣减用户余额开始");
try {
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
accountDao.decrease(userId, money);
LOGGER.info("----> account-service中扣减用户余额开始");
}
}

执行业务

  • 访问 http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
    • 1 号用户,买 1 号产品,买了 10 个,花费 100 块钱
  • 此处会触发 OpenFeign 超时异常
  • 查看数据库变化
  • seata_account -> t_account
iduser_idtotalusedresidue
111000200800
  • seata_order -> t_order
iduser_idproduct_idcountmoneystatus
711101001
811101000
  • seata_storage -> t_storage
idproduct_idtotalusedresidue
111002080
出错

出问题了!由于超时异常,订单的最终状态为 未完成,但:

  • 商品库存被扣除了
  • 账户余额也被扣除了

故障情况:

  • 当库存和账户金额扣减后,订单状态没有设置成已完成,没有从 0 变为 1
  • 由于 OpenFeign 重试机制,账户余额还有可能被多次扣减

超时异常 + 添加 @GlobalTransacitonal

只需要在业务发起微服务的 Service 层方法处添加 @GlobalTransactional 注解即可开启分布式事务支持

添加注解

  • 依然在 AccountServiceImpl 添加超时
  • 在业务发起微服务的 Service 层添加注解,即在 OrderServiceImpl 的 Service 层添加 @GlobalTransacitonal 注解
    • name 属性:随便,保证唯一即可
    • rollbackFor:指定遇到什么类型的异常触发回滚
OderServiceImpl
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Resource
private OrderDao orderDao;
@Resource
private AccountService accountService;
@Resource
private StorageService storageService;
/**
* 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
* 简单说:
* 下订单->减库存->减余额->改状态
* GlobalTransactional seata开启分布式事务,异常时回滚,name保证唯一即可
*
* @param order 订单对象
*/
@Override
@GlobalTransactional(name = "fsp-create-order", rollbackFor = Exception.class)
public void create(Order order) {
// 1 新建订单
log.info("----->开始新建订单");
orderDao.create(order);
// 2 扣减库存
log.info("----->订单微服务开始调用库存,做扣减Count");
storageService.decrease(order.getProductId(), order.getCount());
log.info("----->订单微服务开始调用库存,做扣减End");
// 3 扣减账户
log.info("----->订单微服务开始调用账户,做扣减Money");
accountService.decrease(order.getUserId(), order.getMoney());
log.info("----->订单微服务开始调用账户,做扣减End");
// 4 修改订单状态,从0到1,1代表已完成
log.info("----->修改订单状态开始");
orderDao.update(order.getUserId(), 0);
log.info("----->下订单结束了,O(∩_∩)O哈哈~");
}
}

执行业务

  • 访问 http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
    • 1 号用户,买 1 号产品,买了 10 个,花费 100 块钱
  • 此处会触发 OpenFeign 超时异常
  • 查看数据库变化
  • seata_account -> t_account
iduser_idtotalusedresidue
111000200800
  • seata_order -> t_order
iduser_idproduct_idcountmoneystatus
711101001
811101000
  • seata_storage -> t_storage
idproduct_idtotalusedresidue
111002080
结果

数据没有任何变化,说明触发超时异常后,所有一阶段提交的分支事务都统一回滚了