前言

单体应用被拆分成各个独立的业务模块后,就不得不要去面对分布式事务,好在阿里已经开源分布式事务组件Seata,虽还在迭代中,难免会有bug产生,但随着社区发展及反馈,相信终究会越来越稳定,话不多说让我们开始吧。

项目版本

spring-boot.version:2.2.5.RELEASE

spring-cloud.version:Hoxton.SR3

seata.version:1.2.0

项目说明

项目模块说明如下:



前端请求接口经由网关服务进行路由转发后进入cloud-web模块,经cloud-web模块调用相应业务微服务模块,执行业务逻辑后响应前端请求。

Seata服务端部署

1.下载Seata服务端部署文件

https://github.com/seata/seata/releases/download/v1.2.0/seata-server-1.2.0.zip

如嫌下载慢,可关注本文下方微信公众号二维码,关注后回复“666”即可获取开发常用工具包

2.解压至本地目录后,执行seata-server.bat脚本,过程中无报错则说明部署正常,Linux环境下操作类似不做展开说明

Seata客户端集成
cloud-web

部分pom.xml,后续模块引入seata依赖一样,后续模块不再单独说明

  1. <dependency>
  2. <groupId>com.alibaba.cloud</groupId>
  3. <artifactId>spring-cloud-alibaba-seata</artifactId>
  4. <version>2.2.0.RELEASE</version>
  5. <exclusions>
  6. <exclusion>
  7. <groupId>io.seata</groupId>
  8. <artifactId>seata-spring-boot-starter</artifactId>
  9. </exclusion>
  10. </exclusions>
  11. </dependency>
  12. <dependency>
  13. <groupId>io.seata</groupId>
  14. <artifactId>seata-spring-boot-starter</artifactId>
  15. <version>1.2.0</version>
  16. </dependency>

application.properties ,后续模块引入seata配置项大致一样,根据业务模块调整3个配置项即可,后续模块不再单独说明

  1. # seata配置
  2. seata.enabled=true
  3. #seata.excludes-for-auto-proxying=firstClassNameForExclude,secondClassNameForExclude
  4. seata.application-id=cloud-web
  5. seata.tx-service-group=cloud-web_tx_group
  6. seata.enable-auto-data-source-proxy=true
  7. seata.use-jdk-proxy=false
  8. seata.client.rm.async-commit-buffer-limit=1000
  9. seata.client.rm.report-retry-count=5
  10. seata.client.rm.table-meta-check-enable=false
  11. seata.client.rm.report-success-enable=false
  12. seata.client.rm.saga-branch-register-enable=false
  13. seata.client.rm.lock.retry-interval=10
  14. seata.client.rm.lock.retry-times=30
  15. seata.client.rm.lock.retry-policy-branch-rollback-on-conflict=true
  16. seata.client.tm.commit-retry-count=5
  17. seata.client.tm.rollback-retry-count=5
  18. seata.client.tm.degrade-check=false
  19. seata.client.tm.degrade-check-allow-times=10
  20. seata.client.tm.degrade-check-period=2000
  21. seata.client.undo.data-validation=true
  22. seata.client.undo.log-serialization=jackson
  23. seata.client.undo.only-care-update-columns=true
  24. seata.client.undo.log-table=undo_log
  25. seata.client.log.exceptionRate=100
  26. seata.service.vgroup-mapping.cloud-web_tx_group=default
  27. seata.service.grouplist.default=${cloud-web.seata.service.grouplist.default}
  28. seata.service.enable-degrade=false
  29. seata.service.disable-global-transaction=false
  30. seata.transport.shutdown.wait=3
  31. seata.transport.thread-factory.boss-thread-prefix=NettyBoss
  32. seata.transport.thread-factory.worker-thread-prefix=NettyServerNIOWorker
  33. seata.transport.thread-factory.server-executor-thread-prefix=NettyServerBizHandler
  34. seata.transport.thread-factory.share-boss-worker=false
  35. seata.transport.thread-factory.client-selector-thread-prefix=NettyClientSelector
  36. seata.transport.thread-factory.client-selector-thread-size=1
  37. seata.transport.thread-factory.client-worker-thread-prefix=NettyClientWorkerThread
  38. seata.transport.thread-factory.worker-thread-size=default
  39. seata.transport.thread-factory.boss-thread-size=1
  40. seata.transport.type=TCP
  41. seata.transport.server=NIO
  42. seata.transport.heartbeat=true
  43. seata.transport.serialization=seata
  44. seata.transport.compressor=none
  45. seata.transport.enable-client-batch-send-request=true
  46. seata.config.type=file
  47. seata.registry.type=file

重点3个配置项需要调整下,其余保持默认

seata.application-id=cloud-web

seata.tx-service-group=cloud-web_tx_group

seata.service.vgroup-mapping.cloud-web_tx_group=default

OrderController.java

  1. @RestController
  2. @RequestMapping(value = "/order")
  3. public class OrderController {
  4. @Autowired
  5. OrderFacade orderFacade;
  6. @GlobalTransactional
  7. @GetMapping("/add")
  8. public String add(@RequestParam("cartId") Long cartId){
  9. orderFacade.addOrder(cartId);
  10. return "OK";
  11. }
  12. }

在需要开启分布式事务的方法上添加@GlobalTransactional注解即可

module-order

项目结构图如下



OrderService.java

  1. @RestController
  2. public class OrderService implements OrderFacade {
  3. @Autowired
  4. private TbOrderMapper tbOrderMapper;
  5. @Autowired
  6. private CartFacade cartFacade;
  7. @Autowired
  8. private GoodsFacade goodsFacade;
  9. @Autowired
  10. private WalletFacade walletFacade;
  11. /**
  12. * <p >
  13. * 功能:新增订单
  14. * </p>
  15. * @param cartId 购物车ID
  16. * @author wuyubin
  17. * @date 2020年05月22日
  18. * @return
  19. */
  20. @Override
  21. public void addOrder(Long cartId) {
  22. CartDTO cart = cartFacade.getCartById(cartId);
  23. TbOrder order = new TbOrder();
  24. order.setUserId(cart.getUserId());
  25. order.setGoodsId(cart.getGoodsId());
  26. order.setOrderNo(String.valueOf(System.currentTimeMillis()));
  27. order.setCreateTime(System.currentTimeMillis());
  28. order.setUpdateTime(order.getCreateTime());
  29. order.setIsDeleted(Byte.valueOf("0"));
  30. // 新增订单
  31. tbOrderMapper.insert(order);
  32. // 删除购物车
  33. cartFacade.deleteCartById(cartId);
  34. GoodsDTO goods = goodsFacade.getByGoodsId(cart.getGoodsId());
  35. // 扣减库存
  36. goodsFacade.substractStock(goods.getId());
  37. // 扣减金额
  38. walletFacade.substractMoney(cart.getUserId(),goods.getMoney());
  39. throw new RuntimeException();
  40. }
module-cart

项目结构图如下



CartService.java

  1. @RestController
  2. public class CartService implements CartFacade {
  3. Logger LOGGER = LoggerFactory.getLogger(CartService.class);
  4. @Autowired
  5. private TbCartMapper tbCartMapper;
  6. /**
  7. * <p >
  8. * 功能:增加商品至购物车
  9. * </p>
  10. * @param userId 用户ID
  11. * @param goodsId 商品ID
  12. * @author wuyubin
  13. * @date 2020年05月22日
  14. * @return
  15. */
  16. @Override
  17. public String addCart(Long userId,Long goodsId) {
  18. TbCart cart = new TbCart();
  19. cart.setUserId(userId);
  20. cart.setGoodsId(goodsId);
  21. cart.setCreateTime(System.currentTimeMillis());
  22. cart.setUpdateTime(cart.getCreateTime());
  23. cart.setIsDeleted(Byte.valueOf("0"));
  24. tbCartMapper.insert(cart);
  25. return null;
  26. }
  27. /**
  28. * <p >
  29. * 功能:获取购物车信息
  30. * </p>
  31. * @param cartId 购物车ID
  32. * @author wuyubin
  33. * @date 2020年05月22日
  34. * @return
  35. */
  36. @Override
  37. public CartDTO getCartById(Long cartId) {
  38. CartDTO cartDTO = null;
  39. TbCart cart = tbCartMapper.selectById(cartId);
  40. if (null != cart) {
  41. cartDTO = new CartDTO();
  42. cartDTO.setUserId(cart.getUserId());
  43. cartDTO.setGoodsId(cart.getGoodsId());
  44. }
  45. return cartDTO;
  46. }
  47. /**
  48. * <p >
  49. * 功能:删除购物车信息
  50. * </p>
  51. * @param cartId 购物车ID
  52. * @author wuyubin
  53. * @date 2020年05月22日
  54. * @return
  55. */
  56. @Override
  57. public void deleteCartById(Long cartId) {
  58. tbCartMapper.deleteById(cartId);
  59. }
  60. }
module-goods

项目结构图如下



GoodsService.java

  1. @RestController
  2. public class GoodsService implements GoodsFacade {
  3. @Autowired
  4. private TbGoodsMapper tbGoodsMapper;
  5. /**
  6. * <p >
  7. * 功能:获取商品信息
  8. * </p>
  9. * @param goodsId 商品ID
  10. * @author wuyubin
  11. * @date 2020年05月22日
  12. * @return
  13. */
  14. @Override
  15. public GoodsDTO getByGoodsId(Long goodsId) {
  16. GoodsDTO goodsDTO = null;
  17. TbGoods goods = tbGoodsMapper.selectById(goodsId);
  18. if (null != goods) {
  19. goodsDTO = new GoodsDTO();
  20. BeanUtils.copyProperties(goods,goodsDTO);
  21. }
  22. return goodsDTO;
  23. }
  24. /**
  25. * <p >
  26. * 功能:扣减商品库存
  27. * </p>
  28. * @param goodsId 商品ID
  29. * @author wuyubin
  30. * @date 2020年05月22日
  31. * @return
  32. */
  33. @Override
  34. public void substractStock(@RequestParam("goodsId") Long goodsId) {
  35. if (tbGoodsMapper.updateSubstractStockNumById(goodsId) != 1) {
  36. throw new RuntimeException("扣减库存异常");
  37. }
  38. }
  39. }
module-wallet

项目结构图如下



WalletService.java

  1. @RestController
  2. public class WalletService implements WalletFacade {
  3. @Autowired
  4. private TbWalletMapper tbWalletMapper;
  5. /**
  6. * <p >
  7. * 功能:扣减用户钱包金额
  8. * </p>
  9. * @param userId 用户ID
  10. * @param money 金额
  11. * @author wuyubin
  12. * @date 2020年05月22日
  13. * @return
  14. */
  15. @Override
  16. public void substractMoney(Long userId, BigDecimal money) {
  17. if (tbWalletMapper.updateSubstractMoney(userId,money) != 1) {
  18. throw new RuntimeException("用户金额异常");
  19. }
  20. }
  21. }
表结构说明

undo_log 表:seata依赖表

  1. CREATE TABLE `undo_log` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3. `branch_id` bigint(20) NOT NULL,
  4. `xid` varchar(100) NOT NULL,
  5. `context` varchar(128) NOT NULL,
  6. `rollback_info` longblob NOT NULL,
  7. `log_status` int(11) NOT NULL,
  8. `log_created` datetime NOT NULL,
  9. `log_modified` datetime NOT NULL,
  10. `ext` varchar(100) DEFAULT NULL,
  11. PRIMARY KEY (`id`),
  12. UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
  13. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

tb_cart 表:购物车表

  1. CREATE TABLE `tb_cart` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  3. `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
  4. `goods_id` bigint(20) NULL DEFAULT NULL COMMENT '商品ID',
  5. `create_time` bigint(20) NULL DEFAULT NULL COMMENT '创建时间戳',
  6. `update_time` bigint(20) NULL DEFAULT NULL COMMENT '更新时间戳',
  7. `is_deleted` tinyint(4) NULL DEFAULT 0 COMMENT '删除标志 0:未删除;1:已删除',
  8. PRIMARY KEY (`id`) USING BTREE
  9. ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '购物车表' ROW_FORMAT = Dynamic;
  10. -- 初始化数据
  11. INSERT INTO `tb_cart` VALUES (1, 1, 1, 1590114829756, 1590114829756, 0);

tb_goods 表:商品表

  1. CREATE TABLE `tb_goods` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  3. `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品名称',
  4. `stock_num` bigint(20) NULL DEFAULT NULL COMMENT '商品库存数量',
  5. `money` decimal(10, 2) NULL DEFAULT NULL COMMENT '商品金额',
  6. `create_time` bigint(20) NULL DEFAULT NULL COMMENT '创建时间戳',
  7. `update_time` bigint(20) NULL DEFAULT NULL COMMENT '更新时间戳',
  8. `is_deleted` tinyint(4) NULL DEFAULT NULL COMMENT '删除标志 0:未删除;1:已删除',
  9. PRIMARY KEY (`id`) USING BTREE
  10. ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '商品表' ROW_FORMAT = Dynamic;
  11. -- 初始化数据
  12. INSERT INTO `tb_goods` VALUES (1, '键盘', 100, 100.00, 1590132270000, 1590377130, 0);

tb_wallet 表:钱包表

  1. CREATE TABLE `tb_wallet` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  3. `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
  4. `money` decimal(10, 2) NULL DEFAULT NULL COMMENT '金额',
  5. `create_time` bigint(20) NULL DEFAULT NULL COMMENT '创建时间戳',
  6. `update_time` bigint(20) NULL DEFAULT NULL COMMENT '更新时间戳',
  7. `is_deleted` tinyint(4) NULL DEFAULT NULL COMMENT '删除标志 0:未删除;1:已删除',
  8. PRIMARY KEY (`id`) USING BTREE
  9. ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '钱包表' ROW_FORMAT = Dynamic;
  10. -- 初始化数据
  11. INSERT INTO `tb_wallet` VALUES (1, 1, 500.00, 1590132270000, 1590377130, 0);

tb_order 表:订单表

  1. CREATE TABLE `tb_order` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  3. `order_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '订单编号',
  4. `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
  5. `goods_id` bigint(20) NULL DEFAULT NULL COMMENT '商品ID',
  6. `create_time` bigint(20) NULL DEFAULT NULL COMMENT '创建时间',
  7. `update_time` bigint(20) NULL DEFAULT NULL COMMENT '更新时间',
  8. `is_deleted` tinyint(4) NULL DEFAULT 0 COMMENT '是否删除 0:未删除;1:已删除',
  9. PRIMARY KEY (`id`) USING BTREE
  10. ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '订单表' ROW_FORMAT = Dynamic;

所有服务启动后,请求以下接口

http://localhost:9005/cloud-web/order/add?cartId=1



查看各服务模块日志,你会发现均有如下信息输出,提示已回滚



因为在order模块中addOrder方法下,我这边人为抛出一个运行时异常,看样子事务已经生效了



我们看下数据库中数据是否已回滚正常,核实后发现数据均以回滚









接下来我们把order模块中addOrder方法下将“throw new RuntimeException();”代码块注释掉,重启订单模块服务后再次访问上述接口地址,发现访问正常



查看各服务模块日志,你会发现均有如下信息输出,提示已提交成功



我们再一次核实下数据表中的数据

购物车表已将原先记录逻辑删除



订单表新增一条订单记录



商品表库存数量已扣减1



钱包表金额已扣减100



最后我们测试其中一个服务出现异常,验证下事务是否回滚正常,我们将购物车表逻辑删除恢复正常,将商品表库存改成0,这时我们再请求上述接口地址,发现返回异常了,我们再核实下数据,发现数据表中的数据均以回滚。好啦,SpringCloud集成分布式事务Seata的示例就到这里啦,后续有深入的研究再分享出来。

参考资料

https://github.com/seata/seata

https://seata.io/zh-cn/docs/overview/what-is-seata.html

系列文章

SpringCloud系列之配置中心(Config)使用说明

SpringCloud系列之服务注册发现(Eureka)应用篇

SpringCloud系列之网关(Gateway)应用篇

SpringCloud系列之集成Dubbo应用篇

项目源码

SpringCloud系列之集成分布式事务Seata应用篇的更多相关文章

  1. 阿里分布式事务seata入门(采坑)

    1. 阿里分布式事务seata入门(采坑) 1.1. 前言 seata是feascar改名而来,这是阿里在19年年初开源出来的分布式事务框架,当初刚出来的时候就想研究下了,一直拖到了现在,目前是0.8 ...

  2. 分布式事务(Seata)原理 详解篇,建议收藏

    前言 在之前的系列中,我们讲解了关于Seata基本介绍和实际应用,今天带来的这篇,就给大家分析一下Seata的源码是如何一步一步实现的.读源码的时候我们需要俯瞰起全貌,不要去扣一个一个的细节,这样我们 ...

  3. SpringCloud系列之集成Dubbo应用篇

    目录 前言 项目版本 项目说明 集成Dubbo 2.6.x 新项目模块 老项目模块 集成Dubbo 2.7.x 新项目模块 老项目模块 参考资料 系列文章 前言 SpringCloud系列开篇文章就说 ...

  4. 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾

    https://mp.weixin.qq.com/s/67NvEVljnU-0-6rb7MWpGw 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾 原创 蚂蚁金 ...

  5. SpringBoot系列之集成logback实现日志打印(篇二)

    SpringBoot系列之集成logback实现日志打印(篇二) 基于上篇博客SpringBoot系列之集成logback实现日志打印(篇一)之后,再写一篇博客进行补充 logback是一款开源的日志 ...

  6. SpringCloud整合分布式事务Seata 1.4.1 支持微服务全局异常拦截

    项目依赖 SpringBoot 2.5.5 SpringCloud 2020.0.4 Alibaba Spring Cloud 2021.1 Mybatis Plus 3.4.0 Seata 1.4. ...

  7. 出席分布式事务Seata 1.0.0 GA典礼

    前言 图中那个红衣服的就是本人 什么是分布式事务 分布式事务就是指事务的参与者.支持事务的服务器.资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上. 简单的说,就是一次大的操作由不同的小 ...

  8. spring boot:使用分布式事务seata(druid 1.1.23 / seata 1.3.0 / mybatis / spring boot 2.3.2)

    一,什么是seata? Seata:Simpe Extensible Autonomous Transcaction Architecture, 是阿里中间件开源的分布式事务解决方案. 前身是阿里的F ...

  9. 微服务开发的最大痛点-分布式事务SEATA入门简介

    前言 在微服务开发中,存在诸多的开发痛点,例如分布式事务.全链路跟踪.限流降级和服务平滑上下线等.而在这其中,分布式事务是最让开发者头痛的.那分布式事务是什么呢? 分布式事务就是指事务的参与者.支持事 ...

随机推荐

  1. iOS-自定义 UITabBarController

    先来回顾一下UITabBarController ( 稍微详细的在在http://blog.csdn.net/yang198907/article/details/49807011) 伴随UITabB ...

  2. .NET开发者省份分布排名

    什么叫.NET开发者省份分布排名呢? 顾名思义,这几个词大家都认识,.NET开发者都集中在城市,涵盖一线城市到五线城市.排名的方法非常简单粗暴,就是根据本公众号(dotnet跨平台)的省份订阅读者数量 ...

  3. Jmeter(九) - 从入门到精通 - JMeter逻辑控制器 - 上篇(详解教程)

    1.简介 Jmeter官网对逻辑控制器的解释是:“Logic Controllers determine the order in which Samplers are processed.”. 意思 ...

  4. mail邮件操作

    目录 1. 概念 1.1. 常见的类型 1.2. 相关协议 1.3. SMTP协议 2. python::smtplib 1. 概念 1.1. 常见的类型 Mail User Agent 收发邮件用的 ...

  5. @hdu - 6426@ Problem A.Alkane

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 求包含 n 个碳的烷烃与烷基的同分异构体个数 mod 99824 ...

  6. @atcoder - AGC018F@ Two Trees

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定两棵树 A, B.现你需要构造一组值 (X1, X2, .. ...

  7. zabbix服务的部署

    1.zabbix的介绍 zabbix是一个基于WEB界面分布式系统监视以及网络监视功能的企业的开源解决方案. zabbix能监视各种网络参数,保证服务器系统的安全运营:并且提供灵活的通知机制以让系统管 ...

  8. CollectionView的cell长按事件实现

    原生cell没有长按事件,我们需要使用手势识别来绑定CollectionView.创建并绑定CollectionView如下: (void)viewDidLoad { [super viewDidLo ...

  9. IP地址和端口

    IP地址是网络中计算机的唯一标识.没有IP地址,计算机无法接入互联网. IPv4地址32bit,用点分十进制表示,如202.38.64.3 IPv6地址128bit,用冒号分割十六进制表示,如2001 ...

  10. TCP 粘包拆包

    一.什么是粘包拆包? 粘包拆包是TCP协议传输中一种现象概念.TCP是传输层协议,他传输的是“流”式数据,TCP并不知道传输是哪种业务数据,或者说,并不关心.它只是根据缓冲区状况将数据进行包划分,然后 ...