前言:前几天(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的配置和启动
-
下载上传服务器并解压,我是将安装包和源码包都下载下来了
-
运行mysql.sql脚本
在源码包 script\server\db 目录下找到 mysql.sql脚本并执行,库名最好就直接叫seata,总共三张表
-
修改 file.conf
在安装包解压目录下 conf/file.conf,注意红框部分,因为我的seata和mysql在同一个阿里云服务器上,所以这里mysql的url地址就没必要配置外网地址了,直接配127.0.0.1
-
修改registry.conf
同样在安装包解压目录下 conf/registry.conf,指定注册中心为nacos,配置中心也是nacos。
我的nacos和seata也都是部署在同一个阿里云服务器上的,所以nacos的地址直接127.0.0.1即可,没 必要配置外网绕一圈。如果你们是在不同的节点上,就配置外网地址。
-
修改 config.txt
在源码包下 script/config-center 有一个 config.txt文件,将它复制到seata安装包的根目录下,然后修改,注意红框部分的改动即可。
-
运行nacos-config.sh 脚本
在seata源码包下 script/config-center/nacos 有个 nacos-config.sh 脚本,将它复制到seata安装包目录下的bin目录下,注意这个脚本的目录需要在 config.txt的下一级目录,因为运行时会去它所在的上一级目录去找 config.txt 文件。
运行完之后就把config.txt的所有配置都写到nacos配置中心去了,可以看出来一行配置就是一个配置文件,有点不太友好哈。
-
启动
首先在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作为注册中心和配置中心。