SpringCloud (4) 分布式事务

分布式事务 :在分布式系统中,一个业务需要多个微服务合作完成,每个微服务都有事务,这多个事务必须同时成功或失败

分支事务:每个微服务的事务

全局事务:整个业务的事务

1 seata

1.1 seata中3个角色

TC(Transaction Coordinator)事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚

TM(Transaction Manager)事务管理器:定义全局事务的范围,开启、提交、回滚全局事务

RM(Resource Manager)资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态

1.2 部署TC服务

1.2.1 准备数据库表

新建seata数据库,在数据库中新建一下4个表

sql 复制代码
-- -------------------------------- 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);

在需要接入seata的微服务对应的数据库中新建undo_log表(XA模式不需要此表,AT模式需要此表)

sql 复制代码
CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `branch_id` bigint NOT NULL COMMENT '分支事务ID',
  `xid` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8_general_ci NOT NULL COMMENT '全局事务唯一标识',
  `context` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8_general_ci NOT NULL COMMENT '上下文',
  `rollback_info` longblob NOT NULL COMMENT '回滚信息',
  `log_status` int NOT NULL COMMENT '状态,0正常,1全局已完成(防悬挂)',
  `log_created` datetime NOT NULL COMMENT '创建时间',
  `log_modified` datetime NOT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AT模式回滚日志表';

1.2.2 准备配置文件(application.yml)

XML 复制代码
sever:
  port: 7099 #web控制台

spring:
  application:
    name: seata-server

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

console: #控制台帐号密码
  user:
    username: admin
    password: admin

seata:
  config: #seata配置中心,可以选择file表示直接配置在当前文件(application.yaml)中,也可以选择配置在nacos中
    type: file
#    nacos:
#      server-addr: nacos:8848
#      group: DEFAULT_GROUP
#      namespace: "" #不写默认public
#      dataId: seataServer.properties
#      username: nacos
#      password: nacos
  registry: #seata注册中心,选择nacos
    type: nacos
    nacos:
      application: seata-server
      server-addr: nacos:8848 #nacos地址,可以写成容器名(nacos)+端口,要求nacos和seata部署在同一个docker中,且彼此在同一个网络里; 也可以写成IP地址+端口
      group: DEFAULT_GROUP
      namespace: "" #不写默认public
      username: root
      password: 123456
      #cluster: default #tc集群名称

  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
#如果上面的seata.config.type写的是nacos,就将一下配置写在nacos中;  如果写的是file,就将一下配置写在本application.yml中
  server:
    max-commit-retry-timeout: -1 #二阶段提交重试超时时长(默认-1,表示无限重试)
    max-rollback-retry-timeout: -1 #二阶段回滚重试超时时长(默认-1,表示无限重试)
    rollback-retry-timeout-unlock-enable: false
    enable-check-auth: true
    enable-parallel-request-handle: true
    retry-dead-threshold: 130000
    xaer-nota-retry-timeout: 60000
    enableParallelRequestHandle: true
    recovery:
      committing-retry-period: 1000 #二阶段提交未完成状态全局事务重试提交线程间隔时间(默认1000ms)
      async-committing-retry-period: 1000 #二阶段异步提交状态重试提交线程间隔时间(默认1000ms)
      rollbacking-retry-period: 1000 #二阶段回滚状态重试回滚线程间隔时间(默认1000ms)
      timeout-retry-period: 1000 #超时状态检测重试线程间隔时间(默认1000ms),检测出超时将全局事务置入回滚会话管理器
    undo:
      log-save-days: 7 #undo保留天数(默认7天)
      log-delete-period: 86400000 #undo清理线程间隔时间ms(默认86400000ms)
    session:
      branch-async-queue-size: 5000
      enable-branch-async-remove: false
  store:
    mode: db #也可以存储在file或redis中
    session:
      mode: db
    lock:
      mode: db
    db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver #mysql8以后的驱动用这个(.cj)
      #此种写法要保证seata与mysql在同一个docker,且在同一个网络中, 否则要写成IP地址(jdbc:mysql://123.123.123.123:3306/seata......)
      url: jdbc:mysql://mysql:3306/seata?rewriteBatchedStatements=true&serverTimezone=UTC
      user: root
      password: 123456
      min-conn: 10 #数据库初始连接数
      max-conn: 100 #数据库最大连接数
      global-table: global_table #全局事务表名
      branch-table: branch_table #分支事务表名
      lock-table: lock_table #全局锁表名
      distributed-lock-table: distributed_lock
      query-limit: 1000 #查询全局事务一次的最大条数(默认100)
      max-wait: 5000 #获取连接时最大等待时间ms(默认5000ms)
  metrics:
    enabled: false
    registry-type: compact
    exporter-list: prometheus
    exporter-prometheus-port: 9898
  transport:
    rpc-tc-request-timeout: 15000
    enable-tc-server-batch-send-response: false
    shutdown:
      wait: 3
    thread-factory:
      boss-thread-prefix: NettyBoss
      worker-thread-prefix: NettyServerNIOWorker
      boss-thread-size: 1

1.2.3 Docker部署

拉取seata镜像

bash 复制代码
docker pull seataio/seata-server

创建seata容器(命令行创建)

bash 复制代码
docker run --name seata_container \
-p 8099:8099 \
-p 7099:7090 \
--restart=always \
--network sunner_network \
-e SEATA_IP=123.123.123.123 \
-e SEATA_PORT=8099 \
-v /root/docker_seata/application.yml:/seata-server/resources/application.yml \
-v /root/docker_seata/logs:/root/logs \
-d \
seataio/seata-server:latest

创建seata容器(compose文件创建)

bash 复制代码
service:
  seata:
    image: seataio/seata-server
    container_name: seata_container
    restart: always  #自动重启
    ports:
      - "8099:8099" #微服务与seata连接
      - "7099:7099" #web控制台
    environment:
      SEATA_IP: 123.123.123.123 #seata所在地址
      SEATA_PORT: 8099
    volumes:
      - "/root/docker_seata/application.yml:/seata-server/resources/application.yml"  #日志
      - "/root/docker_seata/logs:/root/logs"  #项目目录
    networks:
      - sunner_network

2 微服务继承seata

2.1 引入依赖

bash 复制代码
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

2.2 微服务配置添加seata

在nacos中新建shar-seata.yaml文件,用于保存seata配置

XML 复制代码
seata:
    registry: #注册中心的配置,微服务根据这些信息去注册中心获取TC服务地址
        type: nacos #注册中心类型-nacos
        nacos:
            server-addr: 106.13.228.161:8848
            namespace: "" #不写默认public
            group: DEFAULT_GROUP
            application: seata-server #seata服务名,在nacos注册的服务名
            #username: nacos #nacos帐号(nacos开启鉴权才填)
            #password: nacos #nacos密码(nacos开启鉴权才填)
    tx-service-group: txg #事务组名称(集群)
    service:
        vgroup-mapping: #事务组与TC集群的映射关系
            txg: "default"
            #txg2: "abc"
            #txg3: "bbb"
    data-source-proxy-mode: AT #默认AT模式

在对应的微服务bootstrp.yaml引入nacos中的seata共享配置share-seata.yaml

XML 复制代码
spring:
  cloud:
    nacos:
      config:
        file-extension: yaml #文件后缀名/配置格式
        shared-configs: #共享配置
          - data-id: share-seata.yaml

3 XA模式

3.1 XA模式流程

一阶段:

① RM注册分支事务到TC

② RM执行分支事务业务sql,但不提交(会锁定数据库)

③ RM报告执行状态到TC

二阶段:

① TC检测各分支事务执行状态

如果都成功,通知所有RM提交事务

如果有失败,通知所有RM回滚事务

② RM接收TC命令(提交或回滚)

3.2 XA模式优缺点

优点:●满足事务的强一致性

●实现简单,没有代码侵入

●利用数据库机制回滚(常用数据库都支持)

缺点:●因为一阶段需要锁定数据库,要等二阶段结束才释放,性能较差

●依赖关系型数据库

3.3 XA模式的使用

修改需要使用seata的微服务的bootstrap.yaml(或application.yaml),也可以修改nacos的shar-seata.yaml

复制代码
seata:
    data-source-proxy-mode: XA #默认AT模式

在"全局事务"入口的方法上添加@GlobalTransactional注解

在其他微服务的"分支事务"方法上添加@Transactional注解

4 AT模式(默认)

4.1 AT模式流程

一阶段:

① RM注册分支事务

② 记录undo-log(数据快照)

③ 执行业务sql并提交

④ 报告事务状态

二阶段:

RM提交,删除undo-log数据

RM回滚,根据undo-log恢复数据到更新前

4.2 AT模式优缺点

优点:●AT模式一阶段直接提交,不锁定资源,性能高

●利用数据快照undo-log回滚

缺点:●不能满足事务强一致性

4.3 AT模式的使用

修改需要使用seata的微服务的bootstrap.yaml(或application.yaml),也可以修改nacos的shar-seata.yaml

复制代码
seata:
    data-source-proxy-mode: AT #默认AT模式

在需要接入seata的微服务对应的数据库中新建undo_log表(XA模式不需要此表,AT模式需要此表) 《见1.2.1》

在"全局事务"入口的方法上添加@GlobalTransactional注解

在其他微服务的"分支事务"方法上添加@Transactional注解

4.4 AT模式注意事项

不能修改主键字段,否则报错

java 复制代码
java.sql.SQLException: org.apache.seata.common.exception.ShouldNeverHappenException: Before image size is not equaled to after image size, probably because you updated the primary keys
相关推荐
Olrookie10 分钟前
若依前后端分离版学习笔记(五)——Spring Boot简介与Spring Security
笔记·后端·学习·spring·ruoyi
小白的代码日记26 分钟前
基于 Spring Boot 的小区人脸识别与出入记录管理系统实现
java·spring boot·后端
hty622 小时前
Spring Boot 注解式大文件 Excel 导入工具:excel‑import‑spring‑boot‑starter
java
李少兄2 小时前
解决IntelliJ IDEA 项目名称后带中括号问题(模块名不一致)
java·ide·intellij-idea
dylan_QAQ2 小时前
【附录】Spring容器的启动过程是怎样的?
后端·spring
林林code2 小时前
从源码的角度解读 Nacos 是如何动态刷新配置的
spring cloud
Alt.92 小时前
SpringMVC(一)
java·mvc
干了这杯柠檬多2 小时前
使用maven-shade-plugin解决es跨版本冲突
java·elasticsearch·maven
Proxbj2 小时前
MQTT解析
java
埃泽漫笔2 小时前
Spring 的 ioc 控制反转
java·spring·ioc