文章目录
概要
随着市场发展,微服务架构已成为不可或缺的产物,单体架构已无法满足市场需求,比如一个软件公司,财务,考勤,人事管理,招聘面试,项目管理,资材管理等,是需要多个系统共同经营的,那就需要多个不同类型的数据库数据管理。不可避免的出现系统之间的数据传输和一致性保障,分布式事务应运而生。
整体架构流程
- 这是一个微服务架构图
事务管理这块,除了MySQL自身支持事务外,MongoDB通过主副本集来管理事务,Seata扮演更重要的角色,就是多数据源的事务控制。
技术名词解释
微服务
微服务是一种软件架构风格,将单一应用程序拆分为多个小型、独立的服务。每个服务运行在独立的进程中,通过轻量级通信机制(如HTTP/REST或消息队列)协作,可独立开发、部署和扩展。核心特点包括松耦合、按业务能力划分、技术栈灵活及容错性强。
分布式事务
分布式事务指跨多个服务或数据库的事务操作,需保证所有参与方数据的一致性(ACID特性)。挑战在于网络延迟、节点故障等导致的协调复杂度。常见解决方案包括两阶段提交(2PC)、补偿事务(TCC)、本地消息表及Saga模式。
Seata
Seata(Simple Extensible Autonomous Transaction Architecture)是阿里开源的分布式事务中间件,支持AT(自动补偿)、TCC、Saga和XA模式。其核心模块包括事务协调器(TC)、资源管理器(RM)和应用(TM),通过全局事务ID实现跨服务事务管理,简化了分布式系统的数据一致性保证。
三者关联:微服务架构中,Seata为分布式事务提供标准化解决方案,确保跨服务调用时的数据一致性。
技术细节
这里重点说一下Seata的AT模式
前提
基于支持本地 ACID 事务的关系型数据库。
Java 应用,通过 JDBC 访问数据库。
整体机制
两阶段提交协议的演变:
-
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
-
二阶段:
提交异步化,非常快速地完成。
回滚通过一阶段的回滚日志进行反向补偿。
写隔离
- 一阶段本地事务提交前,需要确保先拿到 全局锁 。
- 拿不到 全局锁 ,不能提交本地事务。
- 拿到 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
理解
这是官网给的原理,概括来说,就是执行事务之前,在数据库中存一份回滚记录,如果抛异常触发回滚,就从数据库中拿到回滚记录更新回去,若事务提交成功,则删掉,这里面还有全局锁用到全局事务ID在里面保障事务的原子性。
基于spingcloud框架的seata导入
部署seata环境
- 这里直接用最方便的docker构建容器
bash
docker run -d --restart always --name seata2-server -p 28091:28091 -p 27091:27091 -v /home/seata2/config:/seata-server/resources m.daocloud.io/docker.io/seataio/seata-server:2.0.0
Seata AT模式
- 需要的maven项目数据库新建如下表
sql
CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3520 DEFAULT CHARSET=utf8mb3;
- Seata容器建表
sql
CREATE TABLE `branch_table` (
`branch_id` bigint NOT NULL,
`xid` varchar(128) NOT NULL,
`transaction_id` bigint DEFAULT NULL,
`resource_group_id` varchar(32) DEFAULT NULL,
`resource_id` varchar(256) DEFAULT NULL,
`branch_type` varchar(8) DEFAULT NULL,
`status` tinyint DEFAULT NULL,
`client_id` varchar(64) DEFAULT NULL,
`application_data` varchar(2000) DEFAULT NULL,
`gmt_create` datetime(6) DEFAULT NULL,
`gmt_modified` datetime(6) DEFAULT NULL,
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `distributed_lock` (
`lock_key` char(20) NOT NULL,
`lock_value` varchar(20) NOT NULL,
`expire` bigint DEFAULT NULL,
PRIMARY KEY (`lock_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `global_table` (
`xid` varchar(128) NOT NULL,
`transaction_id` bigint DEFAULT NULL,
`status` tinyint NOT NULL,
`application_id` varchar(32) DEFAULT NULL,
`transaction_service_group` varchar(32) DEFAULT NULL,
`transaction_name` varchar(128) DEFAULT NULL,
`timeout` int DEFAULT NULL,
`begin_time` bigint DEFAULT NULL,
`application_data` varchar(2000) DEFAULT NULL,
`gmt_create` datetime DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status`,`gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `lock_table` (
`row_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`xid` varchar(128) DEFAULT NULL,
`transaction_id` bigint DEFAULT NULL,
`branch_id` bigint NOT NULL,
`resource_id` varchar(256) DEFAULT NULL,
`table_name` varchar(100) DEFAULT NULL,
`pk` varchar(36) DEFAULT NULL,
`status` tinyint NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
`gmt_create` datetime DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Seata配置文件
yaml
# Copyright 1999-2019 Seata.io Group.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
server:
port: 27091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${log.home:${user.home}/logs/seata} #日志路径
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
## 账号密码
console:
user:
username: seata123456
password: seata123456
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: 192.168.x.xxx:8848 # nacos的ip端口
group: SEATA2_GROUP # 对应的组,默认为DEFAULT_GROUP
username: nacos
password: nacos
data-id: seataServer.properties # nacos中存放seata的配置文件,后面会提该文件的使用方式,相当于seata服务启动的时候需要注册到nacos,并使用nacos中的配置文件
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: ${spring.application.name}
server-addr: 192.168.x.xxx:8848
group: SEATA2_GROUP
cluster: default
username: nacos
password: nacos
store:
# support: file 、 db 、 redis 、 raft
mode: db
server:
service-port: 28091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKeyxxxxxxxx
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**
nacos配置示例
properties
# 数据存储方式,db代表数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://xxx
store.db.user=xxx
store.db.password=xxx
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.maxWait=5000
# 事务、日志等配置
server.recovery.committingRetryPeriod=3000
server.recovery.asynCommittingRetryPeriod=3000
server.recovery.rollbackingRetryPeriod=3000
server.recovery.timeoutRetryPeriod=3000
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
maven项目配置,
- 多个maven可以共用如下配置
yaml
seata:
enabled: true
enable-auto-data-source-proxy: false # 多数据源需要设置enable-auto-data-source-proxy为false
application-id: ${spring.application.name} # Seata 应用编号,默认为 ${spring.application.name}
tx-service-group: group1 # Seata 事务组编号,用于 TC 集群名
# 服务配置项,对应 ServiceProperties 类
service:
# 虚拟组和分组的映射
vgroup-mapping:
group1: default
# 分组和 Seata 服务的映射
grouplist:
default: 192.168.x.xxx:28091
测试验证
java
@GetMapping("/transactionTest")
@ApiOperation("事务测试")
@Transactional
public Result transactionTest(@RequestParam Integer num, @RequestParam Integer isError){
//这里seataTestDao更新的本地MySQL数据库
SeataTest seataTest = seataTestDao.selectById(1);
seataTest.setNum(seataTest.getNum()+num);
seataTestDao.updateById(seataTest);
//这里feignService通过feigin调用接口更新另外一个容器数据库
SeataTest2 seataTest2 = feignService.getSeataTest2ById(1);
seataTest2.setNum(seataTest2.getNum()+num);
feignService.updateSeataTest2ById(seataTest2);
if(isError == 1){
throw new RunTimeException();
}
return Result.ok();
}
- 通过测试,查看对应表,验证事务生效。