微服务篇-深入了解 XA 模式与 AT 模式、分布式事务(部署 TC 服务、微服务集成 Seata )

🔥博客主页: 【小扳_-CSDN博客】**
❤感谢大家点赞👍收藏⭐评论✍**

文章目录

[1.0 分布式事务](#1.0 分布式事务)

[1.1 认识 Seata](#1.1 认识 Seata)

[2.0 部署 TC 服务](#2.0 部署 TC 服务)

[2.1 准备数据库表](#2.1 准备数据库表)

[2.2 准备配置文件](#2.2 准备配置文件)

[2.3 Docker 部署](#2.3 Docker 部署)

[3.0 微服务集成 Seata](#3.0 微服务集成 Seata)

[3.1 引入依赖](#3.1 引入依赖)

[3.2 改造配置](#3.2 改造配置)

[3.3 添加数据库表](#3.3 添加数据库表)

[3.4 具体使用分布式事务](#3.4 具体使用分布式事务)

[4.0 XA 模式](#4.0 XA 模式)

[4.1 两阶段提交](#4.1 两阶段提交)

[4.2 Seata 的 XA 模型](#4.2 Seata 的 XA 模型)

[4.3 XA 模型优缺点](#4.3 XA 模型优缺点)

[4.4 实现步骤](#4.4 实现步骤)

[5.0 AT 模式](#5.0 AT 模式)

[5.1 Seata 的 AT 模型](#5.1 Seata 的 AT 模型)

[5.2 AT 与 XA 的区别](#5.2 AT 与 XA 的区别)


1.0 分布式事务

通过一个例子来直观的认识在微服务中分布式事务。

首先看看项目中的下单业务整体流程:

由于订单、购物车、商品分别在三个不同的微服务,而每个微服务都有自己独立的数据库,

因此下单过程中就会跨多个数据库完成业务,而每个微服务都会执行自己的本地事务:

1)交易服务:下单事务

2)购物车服务:清理购物车事务

3)库存服务:扣减库存事务

整个业务中,各个本地事务是由关联的。因此每个微服务的本地事务,也可以称为分支事

务。多个有关联的分支事务一起就组成了全局事务。必须要保证整个全局事务同时成功或失败。

每一个分支事务就是传统的单体事务,都可以满足 ACID 特性,但全局事务跨越多个服务,

多个数据库,是如何满足呢?

1.1 认识 Seata

解决分布式事务的方案有很多,但实现起来都比较复杂,因此我们一般会使用开源的框架来

解决分布式事务问题。在众多的开源分布式事务框架中,功能最完善、使用最多的就是阿里巴巴在

2019 年开源的 Seata 了。

Seata 官网:Seata 是什么? | Apache Seata

其实分布式事务产生的一个重要原因,就是参与事务的多个分支事务互相无感知,不知道彼

此的执行状态。

因此解决分布式事务的思想非常简单:

就是找一个统一的事务协调者,与多个分支事务通信,检测每个分支事务的执行状态,保证

全局事务下的每一个分支事务同时成功或失败即可。大多数的分布式事务框架都是基于这个理论来

实现的。

在 Seata 的事务管理中有三个重要的角色:

1)TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事

务提交或回滚。

2)TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或

回滚全局事务。

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

告分支事务的状态,并驱动分支事务提交或回滚。

Seata 的工作架构如图所示:

其中,TM 和 RM 可以理解为 Seata 的客户端部分,引入到参与事务的微服务依赖中即可。

将来 TM 和 RM 就会协助微服务,实现本地分支事务与 TC 之间交互,实现事务的提交或回滚。

而 TC 服务则是事务协调中心,是一个独立的微服务,需要单独部署。

2.0 部署 TC 服务

2.1 准备数据库表

Seata 支持多种存储模式,但考虑到持久化的需要,一般选择基于数据库存储。

sql 复制代码
CREATE DATABASE IF NOT EXISTS `seata`;
USE `seata`;


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;


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;


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_and_branch_id` (`xid` , `branch_id`)
) 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);

数据表结构分析:

1)branch_table:分支表,存放分支事务信息。

2)global_table:全局表,存放全局事务信息。

3)剩下两张 lock 表就是锁,tc 服务会基于数据表实现一个锁功能来确保分布式事务执行的

过程中的线程安全。

2.2 准备配置文件

seata 运行时所需要的配置文件:

解析 application.yml 配置文件:

bash 复制代码
server:
  port: 7099

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${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: admin
    password: admin

seata:
  config:
    # support: nacos, consul, apollo, zk, etcd3
    type: file
    # nacos:
    #   server-addr: nacos:8848
    #   group : "DEFAULT_GROUP"
    #   namespace: ""
    #   dataId: "seataServer.properties"
    #   username: "nacos"
    #   password: "nacos"
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos
    nacos:
      application: seata-server
      server-addr: nacos:8848
      group : "DEFAULT_GROUP"
      namespace: ""
      username: "nacos"
      password: "nacos"
#  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,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
  server:
    # service-port: 8091 #If not configured, the default is '${server.port} + 1000'
    max-commit-retry-timeout: -1
    max-rollback-retry-timeout: -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
      async-committing-retry-period: 1000
      rollbacking-retry-period: 1000
      timeout-retry-period: 1000
    undo:
      log-save-days: 7
      log-delete-period: 86400000
    session:
      branch-async-queue-size: 5000 #branch async remove queue size
      enable-branch-async-remove: false #enable to asynchronous remove branchSession
  store:
    # support: file 、 db 、 redis
    mode: db
    session:
      mode: db
    lock:
      mode: db
    db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://mysql:3306/seata?rewriteBatchedStatements=true&serverTimezone=UTC
      user: root
      password: 123
      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
      max-wait: 5000
    # redis:
    #   mode: single
    #   database: 0
    #   min-conn: 10
    #   max-conn: 100
    #   password:
    #   max-total: 100
    #   query-limit: 1000
    #   single:
    #     host: 192.168.150.101
    #     port: 6379
  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


由于根据 Docker 部署 Seata,因此将整个 seata 文件拷贝到 /root 目录下。

2.3 Docker 部署

需要注意,要确保 nacos、mysql 都在同一个网络中。如果某个容器不在同一个网络,可以

参考下面的命令将某容器加入指定网络:

bash 复制代码
docker network connect [网络名] [容器名]

在 /root 目录执行下面的命令:

bash 复制代码
docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.150.101 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network hm-net \
-d \
seataio/seata-server:1.5.2

3.0 微服务集成 Seata

参与分布式事务的每一个微服务都需要集成 Seata 。

3.1 引入依赖

为了方便各个微服务集成 seata,需要把 seata 配置共享到 nacos 中,因此不仅仅要引入

seata 依赖,还要引入 nacos 配置依赖:

XML 复制代码
<!--统一配置管理-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  </dependency>
  <!--读取bootstrap文件-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
  </dependency>
  <!--seata-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  </dependency>

3.2 改造配置

在 nacos 上添加一个共享的 seata 配置,命名为 shared-seata.yaml:

内容如下:

bash 复制代码
seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
      server-addr: 192.168.150.101:8848 # nacos地址
      namespace: "" # namespace,默认为空
      group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
      application: seata-server # seata服务名称
      username: nacos
      password: nacos
  tx-service-group: hmall # 事务组名称
  service:
    vgroup-mapping: # 事务组与tc集群的映射关系
      hmall: "default"

然后,改造模块,添加 bootstrap.yaml:

内容如下:

bash 复制代码
spring:
  application:
    name: trade-service # 服务名称
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.150.101 # nacos地址
      config:
        file-extension: yaml # 文件后缀名
        shared-configs: # 共享配置
          - dataId: shared-jdbc.yaml # 共享mybatis配置
          - dataId: shared-log.yaml # 共享日志配置
          - dataId: shared-swagger.yaml # 共享日志配置
          - dataId: shared-seata.yaml # 共享seata配置

可以看到这里加载了共享的 seata 配置。

3.3 添加数据库表

seata 的客户端在解决分布式事务的时候需要记录一些中间数据,保存在数据库中。

sql 复制代码
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';

导入数据库之后:

3.4 具体使用分布式事务

配置好 TC 服务,微服务集成 Seata 之后,使用起来非常简单,只需要在对应的业务中添加

一个 Seata 提供的 @GlobalTransactional 注解。

@GlobalTransactional 注解就是在标记事务的起点,将来 TM 就会基于这个方法判断全局事

务范围,初始化全局事务。

4.0 XA 模式

XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction

Processing)标准,XA 规范描述了全局的 TM 与局部的 RM 之间的接口,几乎所有主流的数据

库都对 XA 规范 提供了支持。

4.1 两阶段提交

目前主流数据库都实现了这种规范,实现的原理都是基于两阶段提交。

正常情况:

异常情况:

一阶段:

1)事务协调者通知每个事务参与者执行本地事务。

2)本地事务执行完成后报告事务执行状态给事务协调者,此时事务不提交,继续持有数据库

锁。

二阶段:

事务协调者基于一阶段的报告来判断下一步操作:

1)如果一阶段都成功,则通知所有事务参与者,提交事务。

2)如果一阶段任意一个参与者失败,则通知所有事务参与者回滚事务。

4.2 Seata 的 XA 模型

Seata 对原始的 XA 模式做了简单的封装和改造,以适应自己的事务模型,基本架构如图:

RM 一阶段的工作:

1)注册分支事务到 TC

2)执行分支业务 SQL 但不提交

3)报告执行状态到 TC

TC 二阶段的工作:

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

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

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

RM 二阶段的工作:

1)接收 TC 指令,提交或回滚事务。

具体流程:

请求进入全局事务对应的方法,TM 会向 TC 注册一个全局事务,TM 开始执行内部业务逻

辑,内部业务逻辑最终是要调用这些微服务的,其中每一个微服务都可以认为是全局事务中的分支

事务。当分支事务开始执行时候,资源管理器 RM 会拦截其对数据库的操作,会先向 TC 报告,注

册一下分支事务,属于哪个全局,然后才能去执行业务 sql,sql 执行完后,但是 XA 规范规定 sql

是不能提交的。(事务不提交,其对应的锁不会释放,事务写操作,往往会开启一个排他锁,占用

操作资源,只要锁没有释放,其他任何人无法对这些资源进行操作。)等到 TM 告诉 TC 事务结

束,TC 会检查所有分支事务的状态,若成功,则让所有事务提交,释放锁,否则回滚。

4.3 XA 模型优缺点

1)XA 模式的优点是什么?

事务的强一致性,满足 ACID 原则。

常用数据库都支持,实现简单,并且没有代码侵入。

2)XA 模式的缺点是什么?

因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差。

依赖关系型数据库实现事务。

4.4 实现步骤

首先,要在配置文件中指定要采用的分布式事务模式。可以在 Nacos 中的共享 shared-

seata.yaml 配置文件中设置:

bash 复制代码
seata:
  data-source-proxy-mode: XA

其次,要利用 @GlobalTransactional 注解标记分布式事务的入口方法:

5.0 AT 模式

AT 模式同样是分阶段提交的事务模型,不过缺弥补了 XA 模型中资源锁定周期过长的缺陷。

5.1 Seata 的 AT 模型

基本流程图:

阶段一 RM 的工作:

1)注册分支事务

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

3)执行业务 sql 并提交

4)报告事务状态

阶段二提交时 RM 的工作:

1)删除 undo-log 即可

阶段二回滚时 RM 的工作:

1)根据 undo-log 恢复数据到更新前

5.2 AT 与 XA 的区别

简述 AT 模式与 XA 模式最大的区别是什么?

1)XA 模式一阶段不提交事务,锁定资源;AT 模式一阶段直接提交,不锁定资源。

2)XA 模式依赖数据库机制实现回滚;AT 模式利用数据快照实现数据回滚。

3)XA 模式强一致;AT 模式最终一致。

可见,AT 模式使用起来更加简单,无业务侵入,性能更好。因此企业 90% 的分布式事务都可以用

AT 模式来解决。

相关推荐
大明湖的狗凯.3 小时前
Servlet、omcat服务器架构与工作原理
服务器·servlet·架构
优质&青年5 小时前
【故障处理--修改CI流水线】
linux·运维·服务器·ci/cd·云原生
Listen·Rain6 小时前
Excel拆分脚本
java·数据库·excel
一个略懂代码的程序员7 小时前
Redis02 SpringBoot整合Redis
spring boot·redis·后端
wa的一声哭了7 小时前
word中写论文的一些trick
java·数据库·python·sql·mysql·oracle·perl
m0_748236588 小时前
webrtc-java:引领Java进入实时通信新时代
java·开发语言·webrtc
老牛源码8 小时前
A6919 基于SSM的区域物流管理系统设计与实现
java·开发语言
码老白8 小时前
【老白学 Java】数字格式化
java·开发语言
vvw&8 小时前
Kubernetes 的 Web UI 仪表板部署以及使用
运维·服务器·ubuntu·ui·云原生·容器·kubernetes
十一呓语8 小时前
k8s+rancher配置滚动发布更新时服务不可用
java·kubernetes·rancher