分布式事务 Seata 集群搭建

发布时间 2023-11-20 22:46:07作者: 乔京飞

Seata 是蚂蚁金服和阿里巴巴共同开源的一款分布式事务项目,致力于在微服务架构下提供高性能和简单易用的分布式事务解决方案。自诞生以来就备受国内开发人员推崇,在实际工作中使用者甚多。Seata 提供了四种不同的分布式事务解决方案:

  • XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
  • TCC模式:最终一致的分阶段事务模式,有业务侵入
  • AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
  • SAGA模式:长事务模式,有业务侵入

在实际工作中,比较常用的是前三种模式(XA、TCC、AT),在后面的博客中会进行介绍,SAGA 模式很少使用,就不再介绍了。本篇博客先介绍如何进行 Seata 集群的搭建(如果会搭建集群,那么搭建单节点也很容易)。

Seata 的官网地址是:http://seata.io


一、准备工作

本篇博客参考 Seata 官网的部署文档,采用 docker-compose 进行集群部署。

默认情况下 Seata 是采用文件进行注册、配置和存储的,但是实际工作中并不会采用这种模式。

因此我们直接使用 nacos 作为注册中心和配置中心,使用 mysql 作为 Seata 的数据存储。

由于本篇博客主要介绍如何进行 Seata 集群的搭建,因此有关 nacos 和 mysql 的搭建,这里不做介绍。

我的虚拟机操作系统为 CentOS7( ip地址为 192.168.136.128 ),已经部署好了 nacos 和 mysql,其中 nacos 无需账号密码就可以访问;虚拟机上也安装好了 docker 和 docker-compose

首先到 GitHub 下载最新版的 Seata ,本篇博客在编写时,Seata 的最新版本号是 1.8.0

Seata 的 GitHub 下载地址为:https://github.com/seata/seata/releases

image

可以把 seata-server.zip 和源码文件包都下载下来,会下载到 2 个文件:

  • seata-server-1.8.0.zip 是 Seata 的服务端文件

  • seata-1.8.0.zip 是源代码文件

之所以要下载 seata-server-1.8.0.zip 是因为:如果你想快速使用的话,可以很方便的直接在 windows 上启动 Seata 服务。

之所以下载 seata-1.8.0.zip 源码文件,是因为其内部包含部署所需要的文件。linux 部署 Seata 只需要下载源码文件即可。

解压缩 seata-1.8.0.zip 源码文件,我们在部署过程中,需要使用的文件在解压后的文件夹路径为:

  • server\src\main\resources 目录下的 application.yml 和 application.example.yml,这是 Seata 服务端的配置文件和配置样例参考文件。

  • script\server\db 目录下的 mysql.sql ,这是 Seata 服务端在 mysql 中的建表文件。

  • script\client\at\db 目录下的 mysql.sql ,这个是在使用 AT 模式时需要在客户端微服务所连接 mysql 中的建表文件。

其中 script\client\at\db 目录下的 mysql.sql 文件,这里先不用,我们在后续介绍 Seata 的 AT 事务模式时再使用。


二、集群搭建

我们部署的是 3 节点的 Seata 集群,首先在虚拟机上先创建好 docker-compose 部署的目录结构:

首先创建一个目录 /app/seata,在其下面创建 3 个子目录,每个目录下拷贝一个 application.yml 文件:

image

参照 application.example.yml,我们使用 nacos 作为注册中心,使用 mysql 作为 seata 数据存储,需要修改配置。

以 server1 为例,其修改后的 application.yml 配置内容如下:

server:
  port: 7091

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${log.home:${user.home}/logs/seata}

console:
  user:
    username: seata
    password: seata

seata:
  config:
    type: nacos
    nacos:
      server-addr: 192.168.136.128:8848
      namespace:
      #username:
      #password:
      #context-path:
      group: SEATA_GROUP
      data-id: seataServer.properties

  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 192.168.136.128:8848
      group: SEATA_GROUP
      namespace:
      cluster: jobs
      #username:
      #password:
      #context-path:

#  server:
#    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /, /**/*.css, /**/*.js, /**/*.html, /**/*.map, /**/*.svg, /**/*.png, /**/*.jpeg, /**/*.ico, /api/v1/auth/login
  • server 下配置的端口 7091 是 seata 的 web 界面访问的端口,其数据通信端口如果不配置的话,默认在此基础上加 1000,也就是 8091

  • console 下配置的用户名和密码,是登录 seata 的 web 界面的账号和密码,默认都是字符串 seata

  • setata 的 config 和 setata 的 registry 下面都是配置 nacos 的连接方式,表示使用 nacos 作为配置中心和注册中心

在 setata 的 config 下的 data-id 配置的文件名是 seataServer.properties,表示 seata 启动后会去 nacos 的配置中心上读取该文件进行服务配置,因此根据 config 下的 nacos 配置,我们需要在 nacos 的配置中心中创建出该文件:

image

其 Properties 文件的配置内容如下:(主要配置的是 Seata 服务端连接 mysql 的信息)

store.mode=db
#-----db-----
store.db.datasource=druid
store.db.dbType=mysql
# 需要根据mysql的版本调整driverClassName
# mysql8及以上版本对应的driver:com.mysql.cj.jdbc.Driver
# mysql8以下版本的driver:com.mysql.jdbc.Driver
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://192.168.136.128:3306/seata?useUnicode=true&characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false
store.db.user=root
store.db.password=root
# 数据库初始连接数
store.db.minConn=1
# 数据库最大连接数
store.db.maxConn=20
# 获取连接时最大等待时间 默认5000,单位毫秒
store.db.maxWait=5000
# 全局事务表名 默认global_table
store.db.globalTable=global_table
# 分支事务表名 默认branch_table
store.db.branchTable=branch_table
# 全局锁表名 默认lock_table
store.db.lockTable=lock_table
# 查询全局事务一次的最大条数 默认100
store.db.queryLimit=100


# undo保留天数 默认7天,log_status=1(附录3)和未正常清理的undo
server.undo.logSaveDays=7
# undo清理线程间隔时间 默认86400000,单位毫秒
server.undo.logDeletePeriod=86400000
# 二阶段提交重试超时时长 单位ms,s,m,h,d,对应毫秒,秒,分,小时,天,默认毫秒。默认值-1表示无限重试
# 公式: timeout>=now-globalTransactionBeginTime,true表示超时则不再重试
# 注: 达到超时时间后将不会做任何重试,有数据不一致风险,除非业务自行可校准数据,否者慎用
server.maxCommitRetryTimeout=-1
# 二阶段回滚重试超时时长
server.maxRollbackRetryTimeout=-1
# 二阶段提交未完成状态全局事务重试提交线程间隔时间 默认1000,单位毫秒
server.recovery.committingRetryPeriod=1000
# 二阶段异步提交状态重试提交线程间隔时间 默认1000,单位毫秒
server.recovery.asynCommittingRetryPeriod=1000
# 二阶段回滚状态重试回滚线程间隔时间  默认1000,单位毫秒
server.recovery.rollbackingRetryPeriod=1000
# 超时状态检测重试线程间隔时间 默认1000,单位毫秒,检测出超时将全局事务置入回滚会话管理器
server.recovery.timeoutRetryPeriod=1000

根据配置的数据库连接字符串,我们需要在 mysql 数据库中,运行 sql 脚本创建出 Seata 服务端所需要的表:

-- -------------------------------- 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_status_gmt_modified` (`status` , `gmt_modified`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT 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 = InnoDB
  DEFAULT 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 = InnoDB
  DEFAULT 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 = InnoDB
  DEFAULT 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);

OK,以上步骤完成后,Setata 的第一个节点就搞定了,由于剩下两个节点也是到 naocs 上读取配置,连接的 mysql 也相同,因此剩下两个节点,只需要把 server1 下面的 application.yml 拷贝到 server2 和 server3 目录下即可,最好修改一下端口号。

将 server2 下的 application.yml 中的 server 端口修改为 7092,server3 下的 yml 中端口修改为 7093

最后在 /app/seata 目录下创建的 docker-compose.yml 的文件,填写一下内容即可:

version: "3.2"
services:
  seata1:
    image: seataio/seata-server:1.8.0
    container_name: seata1
    restart: always
    ports:
      - 7091:7091
      - 8091:8091
    environment:
      - SEATA_IP=192.168.136.128
      - SEATA_PORT=8091
      # 服务节点
      - SERVER_NODE=1
    volumes:
      - /app/seata/server1/application.yml:/seata-server/resources/application.yml
    networks:
      - seata_net

  seata2:
    image: seataio/seata-server:1.8.0
    container_name: seata2
    restart: always
    ports:
      - 7092:7092
      - 8092:8092
    environment:
      - SEATA_IP=192.168.136.128
      - SEATA_PORT=8092
      # 服务节点
      - SERVER_NODE=2
    volumes:
      - /app/seata/server2/application.yml:/seata-server/resources/application.yml
    networks:
      - seata_net

  seata3:
    image: seataio/seata-server:1.8.0
    container_name: seata3
    restart: always
    ports:
      - 7093:7093
      - 8093:8093
    environment:
      - SEATA_IP=192.168.136.128
      - SEATA_PORT=8093
      # 服务节点
      - SERVER_NODE=3
    volumes:
      - /app/seata/server3/application.yml:/seata-server/resources/application.yml
    networks:
      - seata_net

 # 网络配置
networks:
  seata_net:
    driver: bridge

这里需要说明的是:由于 docker 内部的 seata 服务,注册到 nacos 时使用的是 docker 内部的 ip 和端口,这样导致我们外部的 IDEA 代码无法访问。为了能够在后续的博客中,使用 IDEA 开发的 Demo 代码顺利访问 Seata,所以在 3 个节点的 Seata 服务中,设置环境变量 SEATA_IP 和 SEATA_PORT 为虚拟机的 ip 和映射出来的数据端口。

最后在虚拟机的 /app/seata 目录下,运行 docker-compose up -d 命令启动服务即可。


三、验证部署结果

  1. 可以在 /app/seata 目录下,使用如下命令查看每个节点的日志,看是否正常启动:
docker logs -f seata1
docker logs -f seata2
docker logs -f seata3
  1. 可以访问以下地址,看是否可以访问 seata 的 web 管理界面,登录账号和密码,我们配置的都是字符串 seata
http://192.168.136.128:7091
http://192.168.136.128:7092
http://192.168.136.128:7093

image

  1. 在 nacos 中查看 seata 服务的注册信息,看看是否是 3 个节点:

image

点击详情,可以查看注册的集群名称和节点信息,我们配置的集群名称是 jobs,三个节点采用虚拟机的 ip 和端口:

image


OK,以上就是 Seata 采用 docker-compose 搭建集群的过程。想要搭建单节点,只需要将 docker-compose.yml 中的其中 2 个节点的配置删掉即可。总体搭建过程比较简单。后面的博客将使用该环境,编写 demo 介绍如何使用 Seata 三种模式的分布式事务。