Spring Cloud Alibaba (四) :整合Seata 分布式事务

前言:前几天(12-22) SpringCloud发布了最新的 Spring Cloud 2020.0 Release Notes,改动还是挺大的。大家感兴趣自行去看看。
在这里插入图片描述

不过不影响我继续整合 SpringCloudAlibaba,今天我们整合 Seata 分布式事务。
之前我就写了一篇关于Seata的文章,是用Eureka+seata整合的。
SpringCloud+Eureka+Seata:整合分布式事务

在SpringCloudAlibaba中,我们就用nacos作为seata的注册中心和配置中心,所以今天再次整合下Seata。

概念

  • Transaction Coordinator:事务协调者。维护全局和分支事务的状态(执行结果),驱动全局事务提交或回滚。
  • TM:Transaction Manager,事务管理器。定义全局事务的范围:开始全局事务、提交或回滚全局事务。它实际是全局事务的发起者。
  • RM:Resource Manager,资源管理器。管理分支事务处理的资源,与 TC 交谈(通信)以注册分支事务和报告分支事务的状态(执行结果),并驱动分支事务提交或回滚。
    常见的 DBMS 在分布式系统中都是以 RM 的角色出现的。

分布式事务模型

Seata 提供了 AT、TCC、SAGA 与 XA 事务模式。

  • XA模型:
    1)TM 向 TC 注册并开启一个全局事务。
    2)根据业务要求,各个 RM 会逐个向 TC 注册分支事务,然后 TC 会逐个向 RM 发出预执行指令(第一阶段提交)。
    3)各个RM 在接收到指令后会进行本地事务预执行。
    4)RM 将预执行结果 Report 给 TC。当然,这个结果可能是成功,也可能是失败。
    5)TC 在接收到各个 RM 的 Report 后 TM 会获取到汇总结果,根据汇总结果 TM 会向 TC 发出确认指令。
    若所有结果全部是成功响应,则 TM 会向 TC 发送 Global Commit 指令;
    若存在有一个不成功的响应,则 TM 会向 TC 发送 Global Rollback 指令;
    6)TC 在接收到指令后再次向RM 发送确认指令(第二阶段提交)。
  • AT模型:
    它是Seata中默认的事务模型,有XA演变而来,对XA做了一些优化。
  • TCC模型:
    TCC 同样也是 2PC 的,支持将自定义的分支事务纳入到全局事务管理中。
  • SAGA模型:
    它不是 2PC 的,其是一种长事务解决方案,暂不多做介绍。

Seata的配置和启动

  1. 下载上传服务器并解压,我是将安装包和源码包都下载下来了
    在这里插入图片描述

  2. 运行mysql.sql脚本
    在源码包 script\server\db 目录下找到 mysql.sql脚本并执行,库名最好就直接叫seata,总共三张表
    在这里插入图片描述

  3. 修改 file.conf
    在安装包解压目录下 conf/file.conf,注意红框部分,因为我的seata和mysql在同一个阿里云服务器上,所以这里mysql的url地址就没必要配置外网地址了,直接配127.0.0.1
    在这里插入图片描述

  4. 修改registry.conf
    同样在安装包解压目录下 conf/registry.conf,指定注册中心为nacos,配置中心也是nacos。
    我的nacos和seata也都是部署在同一个阿里云服务器上的,所以nacos的地址直接127.0.0.1即可,没 必要配置外网绕一圈。如果你们是在不同的节点上,就配置外网地址。
    在这里插入图片描述
    在这里插入图片描述

  5. 修改 config.txt
    在源码包下 script/config-center 有一个 config.txt文件,将它复制到seata安装包的根目录下,然后修改,注意红框部分的改动即可。
    在这里插入图片描述

  6. 运行nacos-config.sh 脚本
    在seata源码包下 script/config-center/nacos 有个 nacos-config.sh 脚本,将它复制到seata安装包目录下的bin目录下,注意这个脚本的目录需要在 config.txt的下一级目录,因为运行时会去它所在的上一级目录去找 config.txt 文件。
    运行完之后就把config.txt的所有配置都写到nacos配置中心去了,可以看出来一行配置就是一个配置文件,有点不太友好哈。
    在这里插入图片描述

  7. 启动
    首先在seata安装包根目录下新建一个log目录用来存放日志。
    nohup ./seata-server.sh -h xxx.xxx.xxx.xxx -m db &
    **注意:**因为我的两个服务是在本地启动的,如果启动seata的时候没有 -h 指定seata的服务地址的话,默认是我阿里云的内网地址,红框部分就是阿里云内网地址。
    在这里插入图片描述
    然后本地启动的服务是连接不上seata服务的,报错如下:
    在这里插入图片描述
    所以,如果你是跟我一样的情况,或者seata服务和其他服务不在一个内网环境的话,需要 -h 指定seata地址,也就是节点的外网ip。
    正常启动后,可以在nacos中看到seata服务注册上了nacos,默认的分组就是SEATA_GROUP。
    在这里插入图片描述

业务代码测试

首先我们需要两个服务,环境的搭建很简单,就是一个服务去调用另一个服务,在另一个服务中手动抛出异常,看数据有没有都插入成功。

  • 添加依赖

    <!-- seata依赖 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    </dependency>
    
  • 业务代码
    1)drp-purchase-dubbo ,采购模块,它调用库存模块,通过dubbo调用(dubbo的调用见上一篇文章)。
    关键的一个注解就是 @GlobalTransactional

    @DubboService
    public class PurchaseServiceImpl implements PurchaseService {
    
        @Autowired
        private PurchaseRepository repository;
    
        @DubboReference(check = false)
        private StockService stockService;
    
        @GlobalTransactional   // 开启全局事务
        @Override
        public boolean addPurchase(Purchase purchase, int deno) {
            // 新增采购表
            Purchase p = this.getPurchaseByName(purchase.getName());
            p.setCount(p.getCount() + purchase.getCount());
            Purchase obj = repository.save(p);
    
            int i = 3 / deno;
    
            // 调用库存服务,新增库存表
            Stock stock = new Stock();
            stock.setName(purchase.getName());
            stock.setTotal(purchase.getCount());
            boolean b = stockService.addStock(stock);
    
    
            if(obj != null && b) {
                return true;
            }
            return false;
        }
    
        @Override
        public Purchase getPurchaseByName(String name) {
            Purchase purchase = repository.findByName(name);
            if (purchase == null) {
                purchase = new Purchase(null, name, 0);
            }
            return purchase;
        }
    }
    
    @RestController
    public class PurchaseController {
        @Autowired
        private PurchaseService purchaseService;
    
        @PostMapping("/purchase/add/{deno}")
        public boolean addPurchaseHandle(@RequestBody Purchase purchase, @PathVariable("deno") int deno) {
            return purchaseService.addPurchase(purchase, deno);
        }
    }
    

    配置文件如下:注意这样要指定seata的注册中心和配置中心,不然默认的就是127.0.0.1,所以会报连接不上seata服务,因为我的seata服务对外暴露的是阿里云外网地址;
    还需要一些默认配置,不然也会报错;
    这里的 seata.tx-service-group 配置取的是 config.txt文件中的值。在这里插入图片描述

    spring:
      application:
        # 服务应用名称,该值在 Dubbo Spring Cloud 加持下被视作 dubbo.application.name,因此,无需再显示地配置 dubbo.application.name
        name: drp-purchase-dubbo
      cloud:
        nacos:
          config:
            server-addr: 47.114.154.140:8848
            file-extension: yml
    seata:
      enabled: true
      application-id: seata-server
      tx-service-group: my_test_tx_group
      config:
        type: nacos
        nacos:
          server-addr: xxx.xxx.xxx.xxx:8848
          group: SEATA_GROUP
          namespace: ""
          username: nacos
          password: nacos
      registry:
        type: nacos
        nacos:
          server-addr: xxx.xxx.xxx.xxx:8848
          group: SEATA_GROUP
          namespace: ""
          cluster: default
          username: nacos
          password: nacos
    

    在这里插入图片描述
    在这里插入图片描述

    2)drp-stock-dubbo,库存模块
    配置文件同上,业务代码很简单

    @DubboService
    public class StockServiceImpl implements StockService {
    
        @Autowired
        private StockRepository repository;
    
        @Override
        public boolean addStock(Stock stock) {
            Stock s = this.getStockByName(stock.getName());
            s.setTotal(s.getTotal() + stock.getTotal());
            Stock obj = repository.save(s);
            if(obj != null) {
                return true;
            }
            return false;
        }
    
        @Override
        public Stock getStockByName(String name) {
            Stock stock = repository.findByName(name);
            if (stock == null) {
                stock = new Stock(null, name, 0);
            }
            return stock;
        }
    }
    

    3)最后,创建两张表 purchase 和 stock 然后在每个库中都需要创建 一个 undo_log 表,我这里为了方便就放在同一个库中了。
    在这里插入图片描述
    4)启动两个服务做测试:
    注意传入的参数 deno=0 这样就会报异常,可以观察到两张表中数据都没有增加,分布式事务成功。
    然后可以去掉 @GlobalTransactional 注解,发现数据能插入到 purchase 表中,插入 stock 表失败,说明没做到分布式事务。
    至于 undo_log 表的作用,之前关于seata的文章说的很清楚了,这里就不多说了。

总结

至此,整合seata分布式事务ok了,采用的是nacos作为注册中心和配置中心。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值