springCloud alibaba:Seata--分布式事务

超、凢脫俗 2023-10-11 17:22 83阅读 0赞

文章目录

  • 1.搭建服务
  • 2.建表
    • 2.1 order服务下的表
    • 2.2 stock服务下的表
  • 3.实现服务
    • 3.1 新建下单服务(OrderServer模块)
    • 3.2 添加商品信息获取以及库存消减服务(stockServer模块)
  • 4.启动Seata
    • 4.1 安装 Seata
  • 5.使用Seata实现事务控制
    • 5.1 初始化数据表
    • 5.2 添加配置
      • 5.2.1 添加依赖
      • 5.2.2 DataSourceProxyConfig
      • 5.2.3 registry.conf
      • 5.2.4 修改application.properties文件
    • 5.3 在服务上开启全局事务
  • 6.测试

理论知识,在 《分布式事务解决方案》已经讲解的十分通透了,本文主要讲解如何通过Seata实现分布式事务控制
我们以订单微服务模块进行演示,然后由订单微服务调用商品微服务扣除库存
实现流程如下:
在这里插入图片描述

1.搭建服务

我们创建两个微服务模块,服务模块:

  • orderServer
  • stockServer

同时将两个服务集成fegin,mybatis,rabbitMq并且保证可以注册到nacos,具体如何创建和集成大家可以参考:

  • 《SpringCloud alibaba实战》

2.建表

2.1 order服务下的表

  • tb_order(订单表)

    DROP TABLE IF EXISTS tb_order;
    CREATE TABLE tb_order (
    order_id int NOT NULL AUTO_INCREMENT,
    user_id int DEFAULT NULL,
    product_id int DEFAULT NULL COMMENT ‘产品ID’,
    order_time datetime DEFAULT NULL COMMENT ‘下单时间 ‘,
    transfer_money double DEFAULT NULL COMMENT ‘交易金额’,
    product_num int DEFAULT NULL COMMENT ‘购买商品数量’,
    PRIMARY KEY (order_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2.2 stock服务下的表

  • tb_product(产品信息表)

    DROP TABLE IF EXISTS tb_product;
    CREATE TABLE tb_product (
    product_id int NOT NULL AUTO_INCREMENT,
    product_name varchar(255) DEFAULT NULL COMMENT ‘产品名称’,
    price double(10,2) DEFAULT NULL COMMENT ‘价格’,
    rest_num int DEFAULT NULL COMMENT ‘产品剩余数量’,
    PRIMARY KEY (product_id)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;


    — Records of tb_product


    INSERT INTO tb_product VALUES (‘1’, ‘Nike Air’, ‘500.00’, ‘15’);

3.实现服务

3.1 新建下单服务(OrderServer模块)

  1. @RestController
  2. @Slf4j
  3. public class TbOrderController {
  4. @Autowired
  5. ITbOrderService tbOrderService;
  6. @PostMapping("/order/prod/createOrder")
  7. public TbOrder createOrder(@RequestBody TbOrder tbOrder) {
  8. log.info("接收到{}号商品的下单请求", tbOrder.getProductId());
  9. return tbOrderService.createOrder(tbOrder.getProductId(), tbOrder.getProductNum());
  10. }
  11. }
  12. @Service
  13. @Slf4j
  14. public class TbOrderServiceImpl extends ServiceImpl<TbOrderMapper, TbOrder> implements ITbOrderService {
  15. @Autowired
  16. ProductService productService;
  17. @Autowired
  18. Sender_Direct sender_direct;
  19. @Override
  20. public TbOrder createOrder(Integer productId, Integer productNum) {
  21. //1.调用商品微服务获取商品信息
  22. Map<String, Object> productInfo = productService.getProductById(productId);
  23. log.info("查询到{}号商品,商品名称为:{},剩余数量:{}", productId, productInfo.get("productName"), productInfo.get("restNum"));
  24. //2.下单
  25. TbOrder tbOrder = new TbOrder();
  26. tbOrder.setUserId(1);
  27. tbOrder.setProductId(productId);
  28. tbOrder.setOrderTime(new Date());
  29. tbOrder.setTransferMoney(Double.valueOf(productInfo.get("transferMoney").toString()));
  30. baseMapper.insert(tbOrder);
  31. log.info("创建订单成功 ,订单信息为:{}", JSON.toJSONString(tbOrder));
  32. //3.调用商品微服务--扣减库存
  33. productService.reduceInventory(productId, productNum);
  34. //4.消息队列中投递一个下单成功的消息
  35. //此处调用消息队列服务
  36. return null;
  37. }

对应的fegin如下:

  1. @FeignClient(value = "seata-stock")
  2. public interface ProductService {
  3. /**
  4. * 通过商品id获取商品信息
  5. *
  6. * @param productId
  7. * @return
  8. */
  9. @RequestMapping(value = "getProductById", method = RequestMethod.POST)
  10. public Map<String, Object> getProductById(@RequestParam(value = "productId") Integer productId);
  11. /**
  12. * 通过商品id扣减库存
  13. *
  14. * @param productId
  15. * @return
  16. */
  17. @RequestMapping(value = "reduceInventory", method = RequestMethod.POST)
  18. public Integer reduceInventory(@RequestParam(value = "productId") Integer productId,
  19. @RequestParam(value = "num") Integer num);
  20. }

3.2 添加商品信息获取以及库存消减服务(stockServer模块)

  1. @RestController
  2. public class TbProductController {
  3. @Autowired
  4. ITbProductService productService;
  5. /**
  6. * 通过商品id获取商品信息
  7. *
  8. * @param productId
  9. * @return
  10. */
  11. @RequestMapping(value = "getProductById", method = RequestMethod.POST)
  12. public TbProduct getProductById(@RequestParam(value = "productId") Integer productId) {
  13. return productService.getById(productId);
  14. }
  15. /**
  16. * 通过商品id扣减库存
  17. *
  18. * @param productId
  19. * @return
  20. */
  21. @RequestMapping(value = "reduceInventory", method = RequestMethod.POST)
  22. public Integer reduceInventory(@RequestParam(value = "productId") Integer productId,
  23. @RequestParam(value = "num") Integer num) {
  24. return productService.reduceInventory(productId, num);
  25. }
  26. }
  27. /**
  28. * <p>
  29. * 服务实现类
  30. * </p>
  31. *
  32. * @author astupidcoder
  33. * @since 2022-04-21
  34. */
  35. @Service
  36. public class TbProductServiceImpl extends ServiceImpl<TbProductMapper, TbProduct> implements ITbProductService {
  37. @Override
  38. public Integer reduceInventory(Integer productId, Integer num) {
  39. TbProduct tbProduct = baseMapper.selectById(productId);
  40. if (tbProduct.getRestNum() < num) {
  41. throw new RuntimeException("库存不足");
  42. }
  43. int i = 1 / 0;//此处用于模拟异常
  44. tbProduct.setRestNum(tbProduct.getRestNum() - num);
  45. return baseMapper.update(tbProduct, null);
  46. }
  47. }

4.启动Seata

4.1 安装 Seata

  • 《docker部署Seata》
  • 《windows安装seata》

5.使用Seata实现事务控制

5.1 初始化数据表

在我们的数据库中加入一张undo_log表,这是Seata记录事务日志要用到的表

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

5.2 添加配置

在需要进行分布式控制的微服务中进行下面几项配置:

5.2.1 添加依赖

  1. <!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-seata -->
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  5. <version>2021.1</version>
  6. </dependency>
  7. <!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-config -->
  8. <dependency>
  9. <groupId>com.alibaba.cloud</groupId>
  10. <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  11. <version>2021.1</version>
  12. </dependency>
  13. <dependency>
  14. <groupId>org.springframework.cloud</groupId>
  15. <artifactId>spring-cloud-starter-bootstrap</artifactId>
  16. </dependency>

5.2.2 DataSourceProxyConfig

Seata 是通过代理数据源实现事务分支的,所以需要配置 io.seata.rm.datasource.DataSourceProxy 的Bean,且是 @Primary默认的数据源,否则事务不会回滚,无法实现分布式事务

  1. import com.alibaba.druid.pool.DruidDataSource;
  2. import io.seata.rm.datasource.DataSourceProxy;
  3. import org.springframework.boot.context.properties.ConfigurationProperties;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.context.annotation.Primary;
  7. @Configuration
  8. public class DataSourceProxyConfig {
  9. @Bean
  10. @ConfigurationProperties(prefix = "spring.datasource")
  11. public DruidDataSource druidDataSource() {
  12. return new DruidDataSource();
  13. }
  14. @Primary
  15. @Bean
  16. public DataSourceProxy dataSource(DruidDataSource druidDataSource) {
  17. return new DataSourceProxy(druidDataSource);
  18. }
  19. }

5.2.3 registry.conf

在resources下添加Seata的配置文件 registry.conf

  1. registry {
  2. type = "nacos"
  3. nacos {
  4. serverAddr = "localhost"
  5. namespace = "public"
  6. cluster = "default"
  7. }
  8. }
  9. config {
  10. type = "nacos"
  11. nacos {
  12. serverAddr = "localhost"
  13. namespace = "public"
  14. cluster = "default"
  15. }
  16. }

注意:serverAddr按照你自己的naocs地址进行修改

5.2.4 修改application.properties文件

  1. nacos.ip=192.168.28.133
  2. #------------------------------------------------------------------------------------------------------------------------
  3. server.port=8082
  4. spring.application.name=seata-order
  5. spring.cloud.nacos.discovery.server-addr=${
  6. nacos.ip}:8848
  7. spring.cloud.nacos.discovery.namespace=public
  8. spring.cloud.nacos.discovery.group=SEATA_GROUP
  9. spring.cloud.nacos.discovery.username=nacos
  10. spring.cloud.nacos.discovery.password=nacos
  11. spring.main.allow-circular-references=true
  12. #-----------------------------------------------------------------------------------------------------------------------
  13. # 开启自动装配
  14. seata.enabled=true
  15. # 是否开启数据源自动代理,一定要为 false,否则会和mp冲突
  16. seata.enable-auto-data-source-proxy=false
  17. # 事务分组,程序会通过用户的配置中心去寻找service.vgroupMapping
  18. seata.tx-service-group=my_test_tx_group
  19. seata.config.type=nacos
  20. seata.config.nacos.server-addr=${
  21. nacos.ip}:8848
  22. seata.config.nacos.namespace=public
  23. seata.config.nacos.group=SEATA_GROUP
  24. seata.config.nacos.username=nacos
  25. seata.config.nacos.password=nacos
  26. seata.registry.type=nacos
  27. seata.registry.nacos.server-addr=${
  28. nacos.ip}:8848
  29. seata.registry.nacos.namespace=public
  30. seata.registry.nacos.username=nacos
  31. seata.registry.nacos.password=nacos
  32. seata.registry.nacos.application=seata-server
  33. seata.registry.nacos.group=SEATA_GROUP
  34. # 事务群组,my_test_tx_group为分组,配置项值为TC集群名
  35. seata.service.vgroup-mapping.my_test_tx_group=default
  36. seata.service.disable-global-transaction=false
  37. seata.client.rm.report-success-enable=false

在这里插入图片描述

如下所示:
在这里插入图片描述

5.3 在服务上开启全局事务

  1. @GlobalTransactional

在这里插入图片描述

注意:我们需要在所有需要控制的服务上都需要开启此注解,即:
在这里插入图片描述

在这里插入图片描述

6.测试

首先我们先不加入注解@GlobalTransactional,进行测试:
在这里插入图片描述

在这里插入图片描述

打开我们的订单表,可以看到已经生成了一个订单信息:
在这里插入图片描述

同时我们再看看库存数据,会发现未发生任何变化
在这里插入图片描述

我来解释一下原因:
每一个服务下都会遵循自己独立的ACID原则,一旦发生异常数据库就会回滚,即保证数据的一致性,而我们的分布式事务就是要解决微服务之间数据一致性的问题


现在我们在两个服务上面加入注解@GlobalTransactional再次进行测试:
会发现order表中未添加任何的数据,同时库存也没有变化,完全符合我们所需要的功能。

发表评论

表情:
评论列表 (有 0 条评论,83人围观)

还没有评论,来说两句吧...

相关阅读

    相关 SpringCloud分布式事务Seata

    1.什么是分布式事务 分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,简单的说,就是一次大的操作由不同的小