微服务架构 | 11.1 整合 Seata AT 模式实现分布式事务

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务;它提供了 AT、TCC、Saga 和 XA 事务模式,为开发者提供了一站式的分布式事务解决方案;

前言

参考资料
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服务原理与实战》
《B站 尚硅谷 SpringCloud 框架开发教程 周阳》
《Seata 中文官网》
《Seata GitHub 官网》
《Seata 官方示例》

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务;它提供了 AT、TCC、Saga 和 XA 事务模式,为开发者提供了一站式的分布式事务解决方案;


1. Seata 基础知识

1.1 Seata 的 AT 模式

  • Seata 的 AT 模式基于 1 个全局 ID 和 3 个组件模型:

    • Transaction ID XID:全局唯一的事务 ID;
    • Transaction Coordinator TC:事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
    • Transaction Manager TM:控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
    • Resource Manager RM:控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚;
  • 为方便理解这里称 TC 为服务端;
  • 使用 AT 模式时有一个前提,RM 必须是支持本地事务的关系型数据库;

1.2 Seata AT 模式的工作流程

  • TMTC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID
  • XID 在微服务调用链路的上下文中传播;
  • RMTC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
  • TMTC 发起针对 XID 的全局提交或回滚决议;
  • TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求;

Seata AT 模式的工作流程

1.3 Seata 服务端的存储模式

  • Seata 服务端的存储模式有三种:file、db 和 redis:

    • file:默认,单机模式,全局事务会话信息持久化在本地文件 ${SEATA_HOME}\bin\sessionStore\root.data 中,性能较高(file 类型不支持注册中心的动态发现和动态配置功能);
    • db:需要修改配置,高可用模式,Seata 全局事务会话信息由全局事务、分支事务、全局锁构成,对应表:globaltablebranchtablelock_table
    • redis:需要修改配置,高可用模式;

1.4 Seata 与 Spring Cloud 整合说明

  • 由于 Spring Cloud 并没有提供分布式事务处理的标准,所以它不像配置中心那样插拔式地集成各种主流的解决方案;
  • Spring Cloud Alibaba Seata 本质上还是基于 Spring Boot 自动装配来集成的,在没有提供标准化配置的情况下只能根据不同的分布式事务框架进行配置和整合;

1.5 关于事务分组的说明

  • 在 Seata Clien 端的 file.conf 配置中有一个属性 vgroup_mapping,它表示事务分组映射,是 Seata 的资源逻辑,类似于服务实例,它的主要作用是根据分组来获取 Seata Serve r的服务实例;
  • 服务分组的工作机制

    • 首先,在应用程序中需要配置事务分组,也就是使用 GlobalTransactionScanner 构造方法中的 txServiceGroup 参数,这个参数有如下几种赋值方式:

      • 默认情况下,为 ${spring.application.name}-seata-service-group
      • 在 Spring Cloud Alibaba Seata 中,可以使用 spring cloudalibaba.seata.tx-service-group 赋值;
      • 在 Seata-Spring-Boot-Starter 中,可以使用 seata.tx-service-group 赋值;
    • 然后,Seata 客户端会根据应用程序的 txServiceGroup 去指定位置(file.conf 或者远程配置中心)查找 service.vgroup_mapping.${txServiceGroup} 对应的配置值,该值代表TC集群(Seata Server)的名称;
    • 最后,程序会根据集群名称去配置中心或者 file.conf 中获得对应的服务列表,也就是 clusterName.grouplist
  • 在客户端获取服务器地址并没有直接采用服务名称,而是增加了一层事务分组映射到集群的配置。这样做的好处在于,事务分组可以作为资源的逻辑隔离单位,当某个集群出现故障时,可以把故障缩减到服务级别,实现快速故障转移,只需要切换对应的分组即可;

事务分组的实现原理


2. Seata 服务端的安装

Seata 安装的是 AT 模型中的 TC,为方便理解这里称为服务端;
Seata 作为一个事务中间件,有很多种部署安装方式,有安装包部署、源码部署和 Docker 部署,这里介绍前两种。版本选 1.4.2;

2.1 安装包安装 Seata

2.1.1 下载 Seata

下载 Seata

2.1.2 修改存储模式为 db

  • 修改存储模式:

    • 修改 ${SEATA_HOME}\conf\file.conf 文件,store.mode="db"。如下图所示:

修改存储模式

  • 修改 MySQL 连接信息:

    • 修改 ${SEATA_HOME}\conf\file.conf 文件里的 db 模块为自己需要连接的 MySQL 地址;

修改 MySQL 连接信息

  • 在 MySQL 上新建数据库和表;

    • SQL 建表语句如下:
    • 该 SQL 文件在源码包里的 ${SEATA_HOME}\script/server/db/mysql.sql 文件;
-- 判断数据库存在,存在再删除
DROP DATABASE IF EXISTS seata;
    
-- 创建数据库,判断不存在,再创建
CREATE DATABASE IF NOT EXISTS seata;

-- 使用数据库
USE seata;

-- 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(128),
    `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;

2.1.3 指明注册中心与配置中心,上传 Seata 配置

  • 注册中心:

    • 修改 ${SEATA_HOME}\conf\registry.conf 文件里的 registry.type,以及下面的注册中心地址信息;

修改注册中心

  • 配置中心:

    • 也是在这个文件里,往下翻,如下图:

修改配置中心

  • 将 Seata 客户端和服务端的配置信息上传到 Nacos 服务器:

    • Seata 客户端和服务端的配置信息保存在 ${SEATA_HOME}/script/config-center/config.txt 文件里,该文件只在源码包里有,笔者是源码安装 Seata 时做的这步;
    • ${SEATA_HOME}\script\config-center\nacos 目录下执行以下 nacos-config.sh 脚本即可;
    • 上传完后可见下图:

Seata 配置上传进 Nacos 配置中心

2.1.4 启动 Seata 服务器

  • 先启动 Nacos,再执行 ${SEATA_HOME}\bin\seata-server.bat 文件;
  • 启动成功后能在 Nacos 服务器里能看见 Seata 服务;

在 Nacos 服务器里能看见 Seata 服务

2.2 源码安装 Seata

2.2.1 拉取代码

Seata GitHub

2.2.2 修改配置文件

  • 源码的配置文件在 seata-server 模块下的 resource 资源文件里,有 file.conf 和 registry.conf 文件;
  • 跟 2.1 安装包安装一样修改即可;

2.2.3 启动服务

  • 先启动 Nacos 服务器;
  • 执行 mvm install 将项目安装到本地;
  • 然后执行 seata-server 模块的 Server.run() 方法即可;

Seata 源码启动成功

  • 同样,在 Nacos 服务器里能看见 Seata 服务;

在 Nacos 服务器里能看见 Seata 服务


3. Spring Cloud 集成 Seata 实现分布式事务

3.1 引入 pom.xml 依赖文件

  • 需要给四个服务都引入以下依赖:
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

3.2 修改 bootstrap.yml 配置文件

  • Seata 在 1.0 后支持将 ${SEATA_HOME}/script/client/conf 目录下的两个配置文件 file.conf 和 registry.conf 写进 .yml 格式文件里了(1.0 版本前不支持);
  • .yml 格式的配置文件在 ${SEATA_HOME}script/client/spring 目录下;
  • 需要修改 seata.tx-service-groupseata.service.vgroup-mapping 一致,配置中心、注册中心等;
  • 另一种配置方法:

    • 除此之外,还可以将 file.conf 和 registry.conf 两个文件添加进 resource 目录下;

3.3 注入数据源

  • Seata 通过代理数据源的方式实现分支事务;MyBatis 和 JPA 都需要注入 io.seata.rm.datasource.DataSourceProxy, 不同的是,MyBatis 还需要额外注入 org.apache.ibatis.session.SqlSessionFactory
  • MyBatis:
@Configuration
public class DataSourceProxyConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        return sqlSessionFactoryBean.getObject();
    }
}

3.4 添加 undo_log 表

  • 在业务相关的数据库中添加 undo_log 表,用于保存需要回滚的数据;
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,
    `ext`           VARCHAR(100) DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8

3.5 使用 @GlobalTransactional 开启事务

  • 在业务的发起方的方法上使用 @GlobalTransactional 开启全局事务,Seata 会将事务的 xid 通过拦截器添加到调用其他服务的请求中,实现分布式事务;


4. Seata AT 模式的实现原理

4.1 两个阶段

  • AT 模式是基于 XA 事务模型演进而来的,所以它的整体机制也是一个改进版的两阶段提交协议;

    • 第一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源;
    • 第二阶段:提交异步化,非常快速地完成。回滚通过第一阶段的回滚日志进行反向补偿;

4.2 AT 模式第一阶段实现原理

  • 在业务流程中执行库存扣减操作的数据库操作时,Seata 会基于数据源代理对原执行的 SQL 进行解析(Seata 在 0.9.0 版本之后支持自动代理);
  • 然后将业务数据在更新前后保存到 undo_log 日志表中,利用本地事务的 ACID 特性,把业务数据的更新和回滚日志写入同一个本地事务中进行提交;

AT 模式第一阶段执行流程

  • 提交前,向TC注册分支事务:申请 tbl_repo 表中主键值等于 1 的记录的全局锁;
  • 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO_LOG 一并提交;
  • 将本地事务提交的结果上报给TC
  • AT 模式和 XA 最大的不同点:分支的本地事务可以在第一阶段提交完成后马上释放本地事务锁定的资源;AT 模式降低了锁的范围,从而提升了分布式事务的处理效率;

4.3 AT 模式第二阶段实现原理

  • TC 接收到所有事务分支的事务状态汇报之后,决定对全局事务进行提交或者回滚;

4.3.1 事务提交

  • 如果决定是全局提交,说明此时所有分支事务已经完成了提交,只需要清理 UNDO_LOG 日志即可。这也是和 XA 最大的不同点;

事务提交执行流程

  • 分支事务收到 TC 的提交请求后把请求放入一个异步任务队列中,并马上返回提交成功的结果给 TC;
  • 从异步队列中执行分支,提交请求,批量删除相应 UNDO_LOG 日志;

4.3.2 事务回滚

  • 整个全局事务链中,任何一个事务分支执行失败,全局事务都会进入事务回滚流程;
  • 也就是根据 UNDO_LOG 中记录的数据镜像进行补偿;

事务回滚执行流程

  • 通过 XID 和 branch ID 查找到相应的 UNDO_LOG 记录;
  • 数据校验:拿 UNDO_LOG 中的 afterImage 镜像数据与当前业务表中的数据进行比较,如果不同,说明数据被当前全局事务之外的动作做了修改,那么事务将不会回滚;
  • 如果 afterImage 中的数据和当前业务表中对应的数据相同,则根据 UNDO_LOG中的 beforelmage 镜像数据和业务 SQL 的相关信息生成回滚语句并执行;
  • 提交本地事务,并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC;

4.4 关于事务的隔离性保证

  • 在 AT 模式中,当多个全局事务操作同一张表时,它的事务隔离性保证是基于全局锁来实现的;

4.4.1 写隔离

  • 一阶段本地事务提交前,需要确保先拿到全局锁
  • 拿不到全局锁 ,不能提交本地事务。
  • 全局锁的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁;
  • 举例:

    • tx1 一阶段拿到全局锁,tx2 等待;

tx1 拿到全局锁,tx2 等待

  • tx1 二阶段全局提交,释放全局锁,tx2 拿到全局锁提交本地事务;

tx1 二阶段全局提交,释放全局锁

  • 如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚;

    • 此时,如果 tx2 仍在等待该数据的全局锁,同时持有本地锁,则 tx1 的分支回滚会失败;
    • 分支的回滚会一直重试,直到 tx2 的全局锁等锁超时,放弃全局锁并回滚本地事务释放本地锁,tx1 的分支回滚最终成功;
  • 因为整个过程全局锁在 tx1 结束前一直是被 tx1 持有的,所以不会发生脏写的问题;

4.4.2 读隔离

  • 在数据库本地事务隔离级别读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是读未提交(Read Uncommitted) ;

    • 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果,产生脏读。这在最终一致性事务模型中是允许存在的,并且在大部分分布式事务场景中都可以接受脏读
    • 如果应用在特定场景下,必需要求全局的读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理;

读已提交执行流程

  • SELECT FOR UPDATE 语句的执行会申请全局锁 ,如果全局锁被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试;
  • 这个过程中,查询是被 block 住的,直到全局锁拿到,即读取的相关数据是已提交的,才返回;



相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2天前
|
存储 监控 API
构建高效微服务架构:后端开发的现代实践
【5月更文挑战第9天】 在本文中,我们将深入探讨如何在后端开发中构建一个高效的微服务架构。通过分析不同的设计模式和最佳实践,我们将展示如何提升系统的可扩展性、弹性和维护性。我们还将讨论微服务架构在处理复杂业务逻辑和高并发场景下的优势。最后,我们将分享一些实用的工具和技术,以帮助开发者实现这一目标。
|
4天前
|
API 持续交付 开发者
构建高效微服务架构:后端开发的新视角
【5月更文挑战第8天】 随着现代软件开发的演变,微服务架构已经成为了企业追求敏捷、可扩展和灵活部署的重要解决方案。本文将深入探讨如何构建一个高效的微服务架构,包括关键的设计原则、技术栈选择以及持续集成与部署的最佳实践。我们还将讨论微服务带来的挑战,如数据一致性、服务发现和网络延迟,并提出相应的解决策略。通过本文,后端开发者将获得构建和维护微服务系统所需的深度知识,并了解如何在不断变化的技术环境中保持系统的健壮性和可维护性。
37 8
|
22小时前
|
存储 NoSQL MongoDB
【MongoDB 专栏】MongoDB 与微服务架构的结合
【5月更文挑战第11天】微服务架构流行趋势下,选择合适的数据库至关重要。MongoDB作为非关系型数据库,与微服务有天然契合度。其灵活的文档模型、水平扩展性、高性能及局部事务支持,满足微服务对数据模型多样性、高可用性、快速读写的需求。实践中,需注意数据划分、索引优化、监控调优和版本控制。未来,MongoDB在微服务中的应用将更广泛,新技术将提升其在微服务架构中的价值。
【MongoDB 专栏】MongoDB 与微服务架构的结合
|
1天前
|
监控 数据库 开发者
构建高效可靠的微服务架构:策略与实践
【5月更文挑战第11天】在当今软件开发的世界中,微服务架构已经成为构建可扩展、灵活且容错的系统的首选方法。本文深入探讨了设计、部署和维护微服务系统时面临的挑战,并提出了一系列实用的策略和最佳实践。我们将从服务的划分原则出发,讨论如何确保每个微服务的自治性,以及如何通过容器化和编排技术实现服务的高效运行。文章还将涉及监控、日志记录和故障恢复的策略,旨在帮助开发人员构建一个既高效又可靠的微服务环境。
|
1天前
|
Kubernetes API 开发者
构建高效微服务架构:后端开发的新范式
【5月更文挑战第11天】 在现代软件开发的快速演变中,微服务架构已成为企业追求敏捷性、可扩展性和技术多样性的关键解决方案。本文旨在探讨如何构建高效的微服务架构,并分析其对后端开发的影响。我们将通过一系列最佳实践和策略,展示如何优化服务的独立性、弹性和性能,同时确保系统的整体稳定性和安全性。文章还将介绍容器化、API网关、服务发现和分布式追踪等关键技术的应用,为后端开发者提供一份全面的微服务实施指南。
|
1天前
|
设计模式 监控 API
构建高效的微服务架构:后端开发的新范式
【5月更文挑战第11天】 在当今的软件开发领域,微服务架构已经成为一种流行的设计模式。它通过将应用程序分解为一组小型、松散耦合的服务来提供高度可扩展和灵活的解决方案。本文将探讨如何构建一个高效的微服务架构,包括选择合适的技术栈、设计原则以及应对常见挑战的策略。我们将深入讨论如何确保系统的可维护性、可靠性和性能,同时考虑到安全性和监控的需求。
|
2天前
|
监控 持续交付 Docker
使用Docker进行微服务架构的最佳实践
【5月更文挑战第10天】本文探讨了使用Docker实施微服务架构的最佳实践。首先,理解微服务架构是拆分小型独立服务的模式,借助Docker实现快速部署、高可移植性和环境一致性。Docker的优势在于服务扩展、容器编排、自动化构建与部署。最佳实践包括:定义清晰服务边界,使用Dockerfile和Docker Compose自动化构建,利用Docker Swarm或Kubernetes编排,实施服务发现和负载均衡,监控与日志记录,以及持续集成和持续部署。Docker虽重要,但需与其他技术结合以确保系统整体稳定性。
|
2天前
|
缓存 负载均衡 API
微服务架构下的API网关性能优化实践
【5月更文挑战第10天】在微服务架构中,API网关作为前端和后端服务之间的关键枢纽,其性能直接影响到整个系统的响应速度和稳定性。本文将探讨在高并发场景下,如何通过缓存策略、负载均衡、异步处理等技术手段对API网关进行性能优化,以确保用户体验和服务的可靠性。
|
2天前
|
监控 持续交付 开发者
构建高效微服务架构:后端开发的新范式
【5月更文挑战第10天】在现代软件开发领域,微服务架构已经成为一种流行的设计模式,它通过将大型应用程序拆分为一组小型、独立和松散耦合的服务来提供更高的可伸缩性和灵活性。本文深入探讨了微服务架构的设计理念、实施步骤以及面临的挑战,并提出了一套实用的策略和最佳实践,帮助后端开发者构建和维护高效的微服务系统。
|
3天前
|
负载均衡 算法 NoSQL
探索微服务架构下的服务发现与治理
【5月更文挑战第9天】 在当今的软件开发领域,微服务架构已成为构建可伸缩、灵活且容错的系统的首选模式。随着服务的增多,如何有效地进行服务发现与治理成为了关键的挑战。本文将深入探讨微服务环境中服务发现的机制和治理策略,分析不同服务发现工具的优缺点,并提出一种基于一致性哈希和健康检查相结合的服务治理方案,旨在提高系统的可用性和性能。