从零搭建微服务项目Pro(第9-1章——分布式事务管理Seata环境配置)
前言:
在传统单体应用中,所有业务逻辑和数据库集中在单一服务中,事务通过本地数据库的ACID(原子性、一致性、隔离性、持久性)特性即可保证。在Web后端开发中,当某个功能需要多个mapper协作时,如果其中一个mapper执行sql时需要对所有的mapper进行相应的回滚,否则即会出现数据不一致问题,SpringBoot中的@Transactional已经支持上述做法,将注解函数中所有的sql操作放在同一事务下进而保证事务能正常回滚。
但随着业务复杂度增长,单体架构面临性能瓶颈和扩展性问题,促使系统向分布式架构演进。微服务与分布式架构下,业务被拆分为多个独立服务,每个服务拥有私有数据库(如订单库、支付库)。一个业务操作(如电商下单)可能跨多个服务,此时本地事务无法覆盖跨服务的操作,需引入分布式事务确保数据一致性。本章将介绍现有的开源的分布式事务管理中间件Seata环境配置以及几种分布式事务模式。
如果想看具体整合代码,可查看下面链接,后续会逐步迭代并最终完成完整的后端代码,(可能还会包括Vue网页端和OpenHarmony手机端的前端代码),以及详尽的配置文档方便快速入手以及设计文档方便快速了解业务,欢迎Star和Follow。
wlf728050719/BitGoPlushttps://github.com/wlf728050719/BitGoPlus如果没有接触过微服务项目或者对上述功能模块感兴趣的小伙伴可收藏下面专栏目录链接,专栏主要内容为笔者从0接触微服务的全部学习心得和记录。以及建议按时间顺序看循序渐进。
从零搭建微服务项目(全)-CSDN博客https://blog.csdn.net/wlf2030/article/details/145799620?spm=1001.2014.3001.5501
XA模式:
XA模式是较早的事务标准,因此几乎所有数据库均支持XA模式。XA模式具体结构如下,当某个功能需要多个服务进行协调时,这些服务(RM)会先准备执行一次SQL,并将执行结果返回,但不提交Sql,当所有的RM均无误时,事务协调者决定这些RM提交事务。
当其中一个RM执行出现错误时,事务协调者则让其余所有的RM回滚事务。
Seata中XA模式结构:
Seata对XA模式进行简单封装,具体结构如下:
Seata XA 模式中的角色分工
TC(Transaction Coordinator,事务协调器)
由 Seata Server 担任,负责全局事务的协调(如开启、提交、回滚)。管理全局事务状态,协调各个 RM(Resource Manager)执行 XA 协议的提交或回滚。
TM(Transaction Manager,事务管理器)
向 TC 注册全局事务,并决定最终提交或回滚。由业务应用中的 @GlobalTransactional
注解触发,负责全局事务的边界定义(如 begin
、commit
、rollback
)。
RM(Resource Manager,资源管理器)
由集成了 Seata XA 模式的数据库(如 MySQL、Oracle)担任,负责分支事务的执行。在 XA 模式下,RM 需要支持 XA 协议(如 XA PREPARE
、XA COMMIT
、XA ROLLBACK
)。
Seata XA 模式的交互流程
(1)TM(业务代码) 开启全局事务,向 TC(Seata Server) 注册。
(2)TC 生成全局事务 ID(XID),并协调各个 RM(数据库) 执行 XA 分支事务。
(3)RM 执行本地事务,并进入 XA PREPARE
状态(数据未真正提交)。
(4)TM 决定提交或回滚,通知 TC。
(5)TC 向所有 RM 发送 XA COMMIT
或 XA ROLLBACK
指令,完成全局事务。
AT模式:
XA模式存在资源锁定周期过长的缺陷,AT模式则使用undo_log表存放sql执行前的快照,直接提交事务,当某个rm执行错误时,使用快照中的数据进行恢复,如果均执行成功则将快照中数据删除即可。
需要了解更多Seata模式的同学可以参考下方链接:
Seata XA 模式 | Apache Seatahttps://seata.apache.org/zh-cn/docs/user/mode/xa
环境配置:
1.Seata下载
Seata的Release链接如下:
Releases · apache/incubator-seatahttps://github.com/apache/incubator-seata/releases?page=2这里使用的Seata版本是1.4.2。
在第二页
windows系统下载zip文件即可
关于Seata版本和SpringCloud版本对应关系可以参考下面链接或者看下面表格,(不过实际我使用的2.2.5Release搭配1.4.2也没有遇到冲突):
版本说明 · alibaba/spring-cloud-alibaba Wikihttps://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
Spring Cloud Alibaba Version | Sentinel Version | Nacos Version | RocketMQ Version | Dubbo Version | Seata Version |
---|---|---|---|---|---|
2022.0.0.0 | 1.8.6 | 2.2.1 | 4.9.4 | ~ | 1.7.0 |
2022.0.0.0-RC2 | 1.8.6 | 2.2.1 | 4.9.4 | ~ | 1.7.0-native-rc2 |
2021.0.5.0 | 1.8.6 | 2.2.0 | 4.9.4 | ~ | 1.6.1 |
2.2.10-RC1 | 1.8.6 | 2.2.0 | 4.9.4 | ~ | 1.6.1 |
2022.0.0.0-RC1 | 1.8.6 | 2.2.1-RC | 4.9.4 | ~ | 1.6.1 |
2.2.9.RELEASE | 1.8.5 | 2.1.0 | 4.9.4 | ~ | 1.5.2 |
2021.0.4.0 | 1.8.5 | 2.0.4 | 4.9.4 | ~ | 1.5.2 |
2.2.8.RELEASE | 1.8.4 | 2.1.0 | 4.9.3 | ~ | 1.5.1 |
2021.0.1.0 | 1.8.3 | 1.4.2 | 4.9.2 | ~ | 1.4.2 |
2.2.7.RELEASE | 1.8.1 | 2.0.3 | 4.6.1 | 2.7.13 | 1.3.0 |
2.2.6.RELEASE | 1.8.1 | 1.4.2 | 4.4.0 | 2.7.8 | 1.3.0 |
2021.1 or 2.2.5.RELEASE or 2.1.4.RELEASE or 2.0.4.RELEASE | 1.8.0 | 1.4.1 | 4.4.0 | 2.7.8 | 1.3.0 |
2.2.3.RELEASE or 2.1.3.RELEASE or 2.0.3.RELEASE | 1.8.0 | 1.3.3 | 4.4.0 | 2.7.8 | 1.3.0 |
2.2.1.RELEASE or 2.1.2.RELEASE or 2.0.2.RELEASE | 1.7.1 | 1.2.1 | 4.4.0 | 2.7.6 | 1.2.0 |
2.2.0.RELEASE | 1.7.1 | 1.1.4 | 4.4.0 | 2.7.4.1 | 1.0.0 |
2.1.1.RELEASE or 2.0.1.RELEASE or 1.5.1.RELEASE | 1.7.0 | 1.1.4 | 4.4.0 | 2.7.3 | 0.9.0 |
2.1.0.RELEASE or 2.0.0.RELEASE or 1.5.0.RELEASE | 1.6.3 | 1.1.1 | 4.4.0 | 2.7.3 | 0.7.1 |
下载1.4.2解压后应该如下:
2.Seata配置
由于mysql数据库5.0和8.0在特性上有很大改变,seata提供了两种jar包,需要根据自己的数据库版本选择
这里我的是8.0版本,将lib/jdbc下的8.0版本的jar包复制粘贴到它的上级目录即lib下面。
然后修改conf下的registry.conf文件,这个文件主要作用为将Seata服务注册和配置。这里选择的是使用nacos,服务注册部分需要配置服务名称(必需),nacos地址(必需),group(默认default_group),namespace(默认public)以及cluster(默认default),nacos用户名和密码(默认nacos,nacos)。根据个人需要更改即可,配置则需要额外配置dataId这里使用seataServer.properties不变即可。
registry {# file 、nacos 、eureka、redis、zk、consul、etcd3、sofatype = "nacos"nacos {application = "seata-server"serverAddr = "127.0.0.1:8848"group = "dev"namespace = "5dadfe94-237d-4367-998e-b13d172ddcfc"cluster = "BJ"username = ""password = ""}}config {# file、nacos 、apollo、zk、consul、etcd3type = "nacos"nacos {serverAddr = "127.0.0.1:8848"namespace = "5dadfe94-237d-4367-998e-b13d172ddcfc"group = "dev"username = ""password = ""dataId = "seataServer.properties"}
}
在nacos上配置新增配置seataServer.properties,注意与前面配置一致
内容如下:(这里的db_seata数据库在后面创建)
# 数据存储方式,db代表数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
## mysql 8.0
store.db.driverClassName=com.mysql.cj.jdbc.Driver
# 对应导入的数据库
store.db.url=jdbc:mysql://127.0.0.1:3306/db_seata?useUnicode=true&rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC
store.db.user=root
store.db.password=15947035212
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.distributedLockTable=distributed_lock
store.db.maxWait=5000
# 事务、日志等配置
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000# 客户端与服务端传输方式
transport.serialization=seata
transport.compressor=none
# 关闭metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
根据个人配置只需要改动下面数据库部分内容即可,其余部分不动。
同时在需要使用全局事务的服务对应的yml文件下添加seata的配置,具体内容如下:(这里使用的AT模式)
seata:client:undo:log-serialization: jacksonregistry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址type: nacos # 注册中心类型 nacosnacos:server-addr: 127.0.0.1:8848 # nacos地址namespace: "5dadfe94-237d-4367-998e-b13d172ddcfc" # namespace,默认为空group: "dev" # 分组,默认是DEFAULT_GROUPapplication: seata-server # seata服务名称username: nacospassword: nacostx-service-group: seata-demo # 事务组名称data-source-proxy-mode: ATservice:vgroup-mapping: # 事务组与cluster的映射关系seata-demo: "BJ"
这里由于之前使用application.yml为所有服务的共享配置,所以就放在application.yml下。
3.数据库创建:
创建db_seata数据库:
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession dataCREATE SCHEMA db_schema;
USE db_seata;
CREATE TABLE IF NOT EXISTS `global_table`
(`xid` VARCHAR(128) NOT NULL,`transaction_id` BIGINT,`status` TINYINT NOT NULL,`application_id` VARCHAR(32),`transaction_service_group` VARCHAR(32),`transaction_name` VARCHAR(128),`timeout` INT,`begin_time` BIGINT,`application_data` VARCHAR(2000),`gmt_create` DATETIME,`gmt_modified` DATETIME,PRIMARY KEY (`xid`),KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(`branch_id` BIGINT NOT NULL,`xid` VARCHAR(128) NOT NULL,`transaction_id` BIGINT,`resource_group_id` VARCHAR(32),`resource_id` VARCHAR(256),`branch_type` VARCHAR(8),`status` TINYINT,`client_id` VARCHAR(64),`application_data` VARCHAR(2000),`gmt_create` DATETIME(6),`gmt_modified` DATETIME(6),PRIMARY KEY (`branch_id`),KEY `idx_xid` (`xid`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(`row_key` VARCHAR(128) NOT NULL,`xid` VARCHAR(128),`transaction_id` BIGINT,`branch_id` BIGINT NOT NULL,`resource_id` VARCHAR(256),`table_name` VARCHAR(32),`pk` VARCHAR(36),`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',`gmt_create` DATETIME,`gmt_modified` DATETIME,PRIMARY KEY (`row_key`),KEY `idx_status` (`status`),KEY `idx_branch_id` (`branch_id`),KEY `idx_xid` (`xid`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;CREATE TABLE IF NOT EXISTS `distributed_lock`
(`lock_key` CHAR(20) NOT NULL,`lock_value` VARCHAR(20) NOT NULL,`expire` BIGINT,primary key (`lock_key`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
由于使用的是AT模式,所以需要创建undo_log表,注意这个表不是在db_seata下创建,而是开启全局事务的所有涉及的服务使用的数据库。
CREATE TABLE `undo_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`branch_id` bigint(20) NOT NULL,`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int(11) NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,`ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=63 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
比如后文中使用创建店铺的例子,需要在db_product和db_user中均创建一个undo_log。
4.maven导入
在需要被全局服务管理的服务pom文件下导入下面的依赖即可,(虽然用的2.0版本和1.4.2对应不上但实际使用测试无误),下文案例使用了两个服务,所以两个服务均要导入依赖。
<!--seata--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><exclusions><exclusion><artifactId>seata-spring-boot-starter</artifactId><groupId>io.seata</groupId></exclusion></exclusions></dependency><dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>2.0.0</version></dependency>
5.全局事务开启
在需要使用全局事务的函数上使用@GlobalTransactional注解即可,这里是先创建了一个店铺再通过feign调用修改用户的权限。
测试:
先启动nacos后再启动seata服务,在bin目录下,cmd启动
虽然可能会报一堆警告,不过最后内容如下即可:
再启动服务,在服务的日志能看到下面的输出
在seata服务的日志也能看到两个服务被事务管理了
手动在feign部分添加异常:
访问接口
查看控制台:
user这边报除0异常
product这边检测到异常并成功回滚
店铺无新增
再演示正确操作:
product成功将事务提交
同时数据库新增:
最后:
又花了大约一个半星期做这章,虽然中间也抽空发了两篇关于深度学习的博客,不过关于seata环境配置的内容确实现有博客或教程都比较老了,导致中间出现各种各样的问题,好在最终顺利解决,如果对你有帮助欢迎关注,后续还会更新更多关于微服务,分布式,深度学习或者杂七杂八的计算机知识的学习和个人思考。