分布式事务的Java实践
文章目录
- 概要
- 整体架构流程
- 技术名词解释
- 微服务
- 分布式事务
- Seata
- 技术细节
- 前提
- 整体机制
- 写隔离
- 理解
- 基于spingcloud框架的seata导入
- 部署seata环境
- Seata AT模式
- Seata配置文件
- nacos配置示例
- maven项目配置,
- 测试验证
- 参考
概要
随着市场发展,微服务架构已成为不可或缺的产物,单体架构已无法满足市场需求,比如一个软件公司,财务,考勤,人事管理,招聘面试,项目管理,资材管理等,是需要多个系统共同经营的,那就需要多个不同类型的数据库数据管理。不可避免的出现系统之间的数据传输和一致性保障,分布式事务应运而生。
整体架构流程
- 这是一个微服务架构图
事务管理这块,除了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构建容器
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项目数据库新建如下表
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容器建表
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配置文件
# 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: 27091spring:application:name: seata-serverlogging:config: classpath:logback-spring.xmlfile:path: ${log.home:${user.home}/logs/seata} #日志路径extend:logstash-appender:destination: 127.0.0.1:4560kafka-appender:bootstrap-servers: 127.0.0.1:9092topic: logback_to_logstash
## 账号密码
console:user:username: seata123456password: seata123456
seata:config:# support: nacos, consul, apollo, zk, etcd3type: nacosnacos:server-addr: 192.168.x.xxx:8848 # nacos的ip端口group: SEATA2_GROUP # 对应的组,默认为DEFAULT_GROUPusername: nacospassword: nacosdata-id: seataServer.properties # nacos中存放seata的配置文件,后面会提该文件的使用方式,相当于seata服务启动的时候需要注册到nacos,并使用nacos中的配置文件registry:# support: nacos, eureka, redis, zk, consul, etcd3, sofatype: nacosnacos:application: ${spring.application.name}server-addr: 192.168.x.xxx:8848group: SEATA2_GROUPcluster: defaultusername: nacospassword: nacosstore:# support: file 、 db 、 redis 、 raftmode: dbserver:service-port: 28091 #If not configured, the default is '${server.port} + 1000'security:secretKey: SeataSecretKeyxxxxxxxxtokenValidityInMilliseconds: 1800000ignore:urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**
nacos配置示例
# 数据存储方式,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可以共用如下配置
seata:enabled: trueenable-auto-data-source-proxy: false # 多数据源需要设置enable-auto-data-source-proxy为falseapplication-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
测试验证
@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();
}
- 通过测试,查看对应表,验证事务生效。
参考
- Seate官网AT模式