Version: Next
案例代码实现
三个微服务
- Order-Module:
seata-order-service2001
订单模块- Storage-Module:
seata-storage-service2002
库存模块- Account-Module:
seata-account-service2003
账户模块
Order-Module
seata-order-service2001
- POM
- YAML
- 主启动
- 实体类
- file.conf
- registry.conf
- 业务类
- 配置类
<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
- POM
- YAML
- 主启动
- 实体类
- file.conf
- registry.conf
- 业务类
- 配置类
<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
- POM
- YAML
- 主启动
- 实体类
- file.conf
- registry.conf
- 业务类
- 配置类
<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
表
id user_id total used residue 1 1 1000 0 1000
seata_order -> t_order
表
id user_id product_id count money status - - - - - -
seata_storage -> t_storage
表
id product_id total used residue 1 1 100 0 100
正常下单
- 访问 http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
- 1 号用户,买 1 号产品,买了 10 个,花费 100 块钱
- 查看数据库变化
seata_account -> t_account
表
id user_id total used residue 1 1 1000 100 900
seata_order -> t_order
表
id user_id product_id count money status 7 1 1 10 100 1
seata_storage -> t_storage
表
id product_id total used residue 1 1 100 10 90
正常完成业务,没毛病!
@GlobalTransactional 注解
超时异常 + 没加 @GlobalTransactional
故意造异常
Account-Module
- 在 Account-service2003 的 Service 层 Impl 代码中,故意添加一个 20 秒睡眠,来制造
超时异常
@Servicepublic class AccountServiceImpl implements AccountService {private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);@Resourceprivate AccountDao accountDao;@Overridepublic 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
表
id user_id total used residue 1 1 1000 200 800
seata_order -> t_order
表
id user_id product_id count money status 7 1 1 10 100 1 8 1 1 10 100 0
seata_storage -> t_storage
表
id product_id total used residue 1 1 100 20 80 出错
出问题了!由于超时异常,订单的最终状态为
未完成
,但:
- 商品库存被扣除了
- 账户余额也被扣除了
故障情况:
- 当库存和账户金额扣减后,订单状态没有设置成已完成,没有从 0 变为 1
- 由于 OpenFeign 重试机制,账户余额还有可能被多次扣减
超时异常 + 添加 @GlobalTransacitonal
只需要在业务发起微服务的 Service 层方法处添加
@GlobalTransactional
注解即可开启分布式事务支持
添加注解
- 依然在
AccountServiceImpl
添加超时- 在业务发起微服务的 Service 层添加注解,即在
OrderServiceImpl
的 Service 层添加@GlobalTransacitonal
注解
name
属性:随便,保证唯一即可rollbackFor
:指定遇到什么类型的异常触发回滚OderServiceImpl@Service@Slf4jpublic class OrderServiceImpl implements OrderService {@Resourceprivate OrderDao orderDao;@Resourceprivate AccountService accountService;@Resourceprivate 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
表
id user_id total used residue 1 1 1000 200 800
seata_order -> t_order
表
id user_id product_id count money status 7 1 1 10 100 1 8 1 1 10 100 0
seata_storage -> t_storage
表
id product_id total used residue 1 1 100 20 80
结果
数据没有任何变化,说明触发超时异常后,所有一阶段提交的分支事务都统一回滚了