分布式事务的Java实践

文章目录

概要

随着市场发展,微服务架构已成为不可或缺的产物,单体架构已无法满足市场需求,比如一个软件公司,财务,考勤,人事管理,招聘面试,项目管理,资材管理等,是需要多个系统共同经营的,那就需要多个不同类型的数据库数据管理。不可避免的出现系统之间的数据传输和一致性保障,分布式事务应运而生。

整体架构流程

  • 这是一个微服务架构图

    事务管理这块,除了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();
}
  • 通过测试,查看对应表,验证事务生效。

参考

相关推荐
Elastic 中国社区官方博客4 小时前
Elasticsearch 的 JVM 基础知识:指标、内存和监控
java·大数据·elasticsearch·搜索引擎·全文检索
孤独的复仇者4 小时前
RabbitMQ高级:延迟消息
分布式·rabbitmq
组合缺一4 小时前
搭建基于 Solon AI 的 Streamable MCP 服务并部署至阿里云百炼
java·人工智能·solon·mcp
毕设源码-邱学长4 小时前
【开题答辩全过程】以 智能商品数据分析系统为例,包含答辩的问题和答案
java·eclipse
Kira Skyler4 小时前
抓虫:sw架构防火墙服务启动失败 Unable to initialize Netlink socket: 不支持的协议
java·linux
AMiner:AI科研助手6 小时前
警惕!你和ChatGPT的对话,可能正在制造分布式妄想
人工智能·分布式·算法·chatgpt·deepseek
张较瘦_7 小时前
[论文阅读] 软件工程 | 告别“线程安全玄学”:基于JMM的Java类静态分析,CodeQL3分钟扫遍GitHub千仓错误
java·论文阅读·安全
A尘埃9 小时前
智慧零售全渠道业务中台系统
java·零售
码luffyliu9 小时前
消息队列 :Kafka 核心要点总结
分布式·kafka·消息队列·mq