集成分布式事务解决方案 - Seata
1. 概述
Seata
是一款开源的分布式事务解决方案框架,用于提供各种模式(包括无业务代码侵入性)的事务模式,其全称是Simple Extensible Autonomous Transaction Architecture
,Seata
框架于2019年由阿里巴巴集团正式开源,同时于2023年10月将该项目捐赠于Apache基金会,该项目也广泛应用于阿里巴巴集团内部交易 / 订单的分布式事务一致性
Seata
主要解决的是在“微服务时代”散落各地不同数据源的事务问题,直接理解起来有些许抽象,参考Seata
官网提供的例子,架构图如下所示:
上图展示了3个核心微服务:
Stock
:负责仓储信息,用于对指定库存信息进行扣减Order
:负责订单信息,用于根据需求进行采购并创建订单Account
:负责账户信息,用于从用户账户中扣除余额
其中Business
作为业务触发点进行扣减库存 - 创建订单 - 扣减账户余额等一系列操作,在常见的微服务架构中,各个服务之间的操作均是不可逆的,意思是当调用Stock
服务进行库存扣减后,该行为不支持回滚,因此在后续的扣减账户余额
这一步骤中,如果出现了任何的异常(比方说账户余额不足以进行扣减),库存的扣减补偿动作无疑只能重新通过Stock
服务提供的增加库存API进行操作
而理想的情况下(可以将这三个服务想象成均存在于一个单体服务中),这几个动作应当在同一个事务中,并且在任意一步出现了异常,都可以进行数据回滚,而不需要基于API能力的形式进行补偿,而跨服务的全局事务,则是Seata
框架所提供的解决方案
Seata
框架提供了一个@GlobalTransactional
注解用于标识开启跨服务的全局事务,使用方式如下所示:
1 |
|
其中,Seata
解决方案中涉及到的专有名词为以下:
- TC:全称为
Tranaction Coordinator
,中文为事务协调者,主要负责维护全局事务和分支事务的状态,决策最终是做事务提交还是事务回滚,也就是下述即将提到的Seata Server
- TM:全称为
Transaction Manager
,中文为事务管理者,主要负责开启全局事务,提交或回滚全局事务,用于定义全局事务的范围 - RM:全称为
Resource Manager
,中文为资源管理器,主要负责与TC通讯分支事务状态以及注册分支事务,并且用于驱动事务回滚以及事务提交
2. 架构
下述将基于Seata
官网的订单 - 账户 - 库存样例进行搭建实践全局事务管理,其中涉及到的服务套件包含以下:
- Nacos:使用其作为3个核心微服务与
seata-server
的服务注册中心与配置中心 - Seata-Server:
Seata
提到的事务协调者角色,就由该服务充当,所有的业务微服务都将与seata-sever
进行通讯以便形成全局事务控制 - 业务服务:用于塑造下单 - 扣减库存 - 扣减余额的典型业务场景,并验证
Seata
分布式事务解决方案的有效性
整体架构如下所示:
3. Seata Server部署
Tips: 可参考官网提供的Seata Server部署方式,以下所述的部署方式包括业务代码均已上传至GitHub中,可通过
https://github.com/zchengb/seata-demo
进行代码拉取
到了部署seata-server
这一步,其实Seata
官网提供了不同的部署方式,但基于笔者本地环境,选择了基于Nacos
官网提供的docker-compose
部署方式(https://github.com/nacos-group/nacos-docker)与 Seata
官网提供的Docker
镜像进行结合部署,整体结合后的docker-compose.yml
如下所示:
1 | version: "3.8" |
Tips:
MySQL
容器的初始化账号密码均为root
其中涉及到的seata-server
启动配置项文件application.yaml
则是通过docker cp seata:/seata-server/resources ./seata-server
的方式拷贝后进行持久化挂载(参考seata.volumes
参数)
其中需要注意的是seata-server
容器中环境的配置项SEATA_IP
,该IP值不可以设置为127.0.0.1
或localhost
,该配置项主要用于服务注册时,默认在Docker容器环境中,服务注册上送至Nacos
的IP地址将会是容器IP地址,因此需要进行变更,以便后续业务服务能够从Nacos
中进行服务发现,获取seata-server
的IP地址,如下所示:
seata-server
涉及的完整application.yaml
内容如下所示:
1 | server: |
需要注意的是seata-server
所用到的命名空间(seata.config.nacos.namespace
和seata.registry.nacos.namespace
)不建议使用默认的public
命名空间,会导致循环拉取配置,日志无限输出的情况,另外在配置命名空间的时候,应当配置的是命名空间的ID,具体可参考Seata集成nacos疯狂打印日志bug解决
有关seata-server
所需要的配置信息,存放至nacos
对应命名空间下,其中data-id
为seataServer.properties
,整体配置信息如下所示:
1 | #For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html |
上述配置跟seata
官方提供的默认配置中主要的差异点在于,将相关的事务相关的存储介质从文件变更为数据库(MySQL),主要参考上述的store.mode
配置项,对应需要将数据库连接信息进行同步更新store.db.url
,store.db.user
与store.db.password
既然上述提到了采用MySQL数据库的方式来存储相关的事务信息,那么seata-server
所涉及的数据表是需要初始化载入到数据库中的,以下是相关的初始化表SQL语句(摘选于Seata官方):
1 | -- the table to store GlobalSession data |
至此,seata-server
部署所需要的配置均已完成,可通过http://localhost:7091/#/login
访问seata-server
的后台进行观察,初始化账号密码均为seata
,页面效果如下所示:
4. 业务服务
Tips: 涉及到的业务服务代码可从
https://github.com/zchengb/seata-demo
进行拉取
业务服务主要用于塑造下单 - 扣减库存 - 扣减余额的典型业务场景,并验证Seata
分布式事务解决方案的有效性
业务服务主要涉及:订单服务、账户服务和库存服务,其中服务注册发现以及配置获取主要结合了nacos
服务,订单服务相关的application.yaml
配置如下所示:
1 | seata: |
此处需要注意的是涉及到的nacos.xxx.namespace
需要与seata-server
服务的命名空间保持一致(因为业务服务作为RM
角色需要向TC
角色也就是seata-server
进行注册,因此这一过程涉及到服务间的交互,也就意味着需要使用到nacos
的服务发现)
上述application.yaml
中缺失的datasource
相关配置放置于nacos
配置中心中
需要引入的相关gradle
依赖如下所示:
1 | plugins { |
在集成seata
前,需要为每个业务服务对应的数据库创建seata
所需的undo-log
数据表用于存储过程中涉及到的事务信息,undo-log
数据表的初始化SQL如下所示(参考Seata官方)
1 | -- for AT mode you must to init this sql for you business database. the seata server not need it. |
关联的业务核心代码如下所示:
- 订单服务 - 创建订单
1 |
|
- 账户服务 - 扣减余额
1 |
|
- 库存服务 - 扣减库存
1 |
|
其中seata
所体现的分布式事务主要作用于订单服务中的@GlobalTransactional
注解,当出现扣减库存 / 余额失败 / 创建订单失败时,将会对各个业务服务涉及到的数据库操作进行SQL回滚,如下所示: