天翼云代理,天翼云代理商,北京代理商
天翼云折扣专线:400-150-1900(全国市话)

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

2022-02-09 11:26:57

前言

参考资料
《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 形式的作业流程

  • TM 向 TC 恳求敞开一个大局业务,大局业务创立成功并生成一个大局仅有的 XID;
  • XID 在微服务调用链路的上下文中传播;
  • RM 向 TC 注册分支业务,将其归入 XID 对应大局业务的统辖;
  • TM 向 TC 建议针对 XID 的大局提交或回滚抉择;
  • TC 调度 XID 下统辖的悉数分支业务完结提交或回滚恳求;

Seata AT 形式的作业流程

1.3 Seata 服务端的存储形式

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

    • file:默许,单机形式,大局业务会话信息耐久化在本地文件 ${SEATA_HOME}\bin\sessionStore\root.data 中,功用较高(file 类型不支持注册中心的动态发现和动态装备功用);
    • db:需求修正装备,高可用形式,Seata 大局业务会话信息由大局业务、分支业务、大局锁构成,对应表:globaltable、branchtable、lock_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.cloudgroupId> <artifactId>spring-cloud-starter-alibaba-seataartifactId> 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-group 和 seata.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 住的,直到大局锁拿到,即读取的相关数据是已提交的,才回来;
12年经验 · 提供上云保障

服务热线:132-6161-6125(手机) 400-150-1900(全国市话)

站内导航: 天翼云服务器价格| 天翼云购买流程| 天翼云代理| 北京天翼云代理商| 杭州天翼云代理| 深圳天翼云代理商| 钉钉代理商| 阿里云代理| 公司官网

我公司收款账号| 天翼云备案系统

CopyRight © 2019 天翼云代理商. All Rights Reserved 京ICP备11011846号-15 管理-北京志远天辰科技有限公司