Spring Cloud Alibaba(五) -分布式事务Seata

什么是事务?(本地事务篇)

想象一下,你正在谈恋爱(执行SQL)。

  • 事务:就是“要么结婚(全部提交),要么分手(全部回滚)”,绝对不能出现“证领了但彩礼没给”这种中间状态。

事务的四大金刚(ACID):

  • 原子性:最小执行单位,不可分割。就像原子弹,要么不爆,爆了就是全爆。
  • 一致性:不管怎么折腾,数据最后得是对的。你不能凭空变出钱来。
  • 隔离性:你的事务是你的事,我的事务是我的事,咱俩别互相干扰(除非你想搞事情)。
  • 持久性:一旦提交,就是板上钉钉,天王老子来了也改不了(除非删库跑路)。

MySQL 的“后悔药”机制(Undo Log):

  • BEGIN TRANSACTION;:开始搞事,先拿个小本本(undo_log)记下来老数据长啥样。
  • SQL1; SQL2;:疯狂操作。
  • COMMIT;:搞定!把小本本撕了,不用回滚了。
  • ROLLBACK;:哎呀翻车了!赶紧掏出小本本,把数据恢复原状。

Spring 的 AOP 大法:
以前我们要自己写 try-catch 来控制事务,累得像条狗。现在有了 @Transactional 注解,Spring 就像个贴心的管家,自动帮你处理 commit 和 rollback。


分布式事务:当“本地”不够用了

场景:
以前是一个服务独大(单体应用),现在流行微服务,大家分家了。

  • 尴尬的情况:订单服务(Order)扣了库存,结果积分服务(Point)挂了。订单说“我扣了”,积分说“我还没加”,数据这就“精神分裂”了。

这就是分布式事务要解决的痛点: 怎么让这几个分家了的微服务,还能像亲兄弟一样“同生共死”?


Seata 闪亮登场(核心组件篇)

Seata 就是来解决这个“家庭纠纷”的居委会大妈。它有三个核心角色:

TM - 事务管理器:发号施令的“老板”

  • 职责:定义全局事务的范围。
  • 口头禅:“小的们,干活了!@GlobalTransactional 开启!”
  • 位置:通常在业务入口(比如订单服务)。

TC - 事务协调器:操碎了心的“管家”

  • 职责:维护全局和分支事务的状态,指挥 RM 是提交还是回滚。
  • 装备:它自己有几张表(global_table, branch_table, lock_table),专门用来记流水账。
  • 位置:独立部署的服务器(Seata Server)。

RM - 资源管理器:干苦力的“打工仔”

  • 职责:向 TC 注册分支事务,干完活汇报状态。如果 TC 说“回滚”,它就得乖乖拿出自己的 undo_log 恢复数据。
  • 装备:每个参与的服务数据库里,都得有一张 undo_log 表(这是 Seata 的“后悔药”)。

️ 实战:Seata 安装与配置(保姆级教程)

光说不练假把式,咱们来把这个“管家”请回家。

第一步:请神(下载 Seata Server)
去 GitHub 下载 Seata Server(也就是 TC)。

  • 下载地址:找最新的 release 版本,比如 seata-server-1.x.x.zip
  • 解压后,你会看到 bin, conf, lib 等目录。

第二步:装修(配置 TC)
修改 conf/registry.conf 文件。

  • 注册中心:如果你用 Nacos,就把 type 改成 nacos,填上 Nacos 的地址。

registry {
  #tc服务的注册中心类型,这里选择nacos,也可以是eureka、zookeeper等
  type = "nacos"

  nacos {
    # seata tc服务注册到nacos的服务名称,可以自定义
    application = "seata-server"
    # nacos的地址
    serverAddr = "192.168.204.129:8848"
    # seata服务所在分组
    group = "SEATA_GROUP"
    # seata服务所在的名称空间,这里不填就是使用默认的"public"
    namespace = ""
    # TC集群名,默认是"default"
    cluster = "default"
    # 这个是nacos的用户名
    username = ""
    # 这个是nacos的密码
    password = ""
  }
}
config {
  # tc服务的配置中心类型:file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"

  nacos {
    serverAddr = "192.168.204.129:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = ""
    password = ""
    dataId = "seataServer.properties"
  }
}  
  • 配置中心:让 Seata 去 Nacos 里读取配置。
# 数据存储方式,db代表数据库,在此处修改mysql信息
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://192.168.20.9:3306/seata?useUnicode=true&rewriteBatchedStatements=true&useSSL=false
store.db.user=root
store.db.password=1111
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=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

第三步:建账(数据库准备)
这一步最重要,别忘了!

  1. TC 的库:创建一个数据库(比如叫 seata),运行官方提供的 SQL 脚本,生成 global_table 等三张表。不想下载在下面复制也可以。
-- -------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
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_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- 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 = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(96),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;
  1. RM 的库:在你的业务数据库(订单库、库存库等)里,必须创建 undo_log 表!

undo_log 建表 SQL(复制拿走不谢):

CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`),
  KEY `ix_log_created` (`log_created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AT 事务模式回滚日志表';

第四步:启动(Run!)

  • 进入 bin 目录。
  • Windows 双击 seata-server.bat
  • Linux/Mac 执行 sh seata-server.shsh seata-server.sh -h ip地址 -p 端口号 (用此居多)。
  • 看到控制台打印出 “Seata Server started…” 就算成功了!

第五步:微服务接入(RM 端配置)
在你的 Spring Boot 项目里:

  1. 加依赖spring-cloud-starter-alibaba-seata
  2. 改配置 (application.yml):
seata:
  registry:
    type: nacos #查找TC服务,参考registry.conf
    nacos:
      server-addr: 192.168.204.129:8848
      namespace: ""
      group: SEATA_GROUP
      application: seata-server #TC服务名
  tx-service-group: seata-demo #事务组,根据tx-service-group名称获得TC服务cluster名称
  service:
    vgroup-mapping: #tx-service-group与TC cluster的映射关系
      seata-demo: default

知识点:Seata 的 AT 模式原理(它是咋工作的?)

Seata 最常用的是 AT 模式,主打一个“无侵入”(代码改动极小)。

第一阶段(Try):

  1. 业务 SQL 执行。
  2. Seata 拦截 SQL,生成 undo_log(记录修改前的数据和修改后的数据)。
  3. 本地事务提交(注意:这里就提交了,所以性能高,不锁表太久)。
  4. 向 TC 汇报:“老板,我这步做完了,但我还没最终确认。”

第二阶段(Confirm/Rollback):

  • 如果大家都成功:TM 通知 TC 提交,TC 异步删除 undo_log,完事。
  • 如果有人失败:TM 通知 TC 回滚,TC 让 RM 根据 undo_log 生成反向 SQL(比如把 UPDATE 变成 REVERSE UPDATE),把数据恢复回去。

总结一句话:
Seata 就是帮你自动记录“后悔药”(undo_log),出事了自动喂给你吃,让你回到过去。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐