目录

前言

参考资料

《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 下管辖的全部分支事务完成提交或回滚请求;

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

2.1.2 修改存储模式为 db

  • 修改存储模式:

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

  • 修改 MySQL 连接信息:
    • 修改 ${SEATA_HOME}\conf\file.conf 文件里的 db 模块为自己需要连接的 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 脚本即可;
      • 上传完后可见下图:

2.1.4 启动 Seata 服务器

  • 先启动 Nacos,再执行 ${SEATA_HOME}\bin\seata-server.bat 文件;

  • 启动成功后能在 Nacos 服务器里能看见 Seata 服务;

2.2 源码安装 Seata

2.2.1 拉取代码

2.2.2 修改配置文件

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

2.2.3 启动服务

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

  • 同样,在 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 特性,把业务数据的更新和回滚日志写入同一个本地事务中进行提交;

    • 提交前,向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 的二阶段全局回滚,则 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 住的,直到全局锁拿到,即读取的相关数据是已提交的,才返回;

最后

新人制作,如有错误,欢迎指出,感激不尽!
欢迎关注公众号,会分享一些更日常的东西!
如需转载,请标注出处!

微服务架构 | 11.1 整合 Seata AT 模式实现分布式事务的更多相关文章

  1. .Net Core with 微服务 - 使用 AgileDT 快速实现基于可靠消息的分布式事务

    前面对于分布式事务也讲了好几篇了(可靠消息最终一致性 分布式事务 - TCC 分布式事务 - 2PC.3PC),但是还没有实战过.那么本篇我们就来演示下如何在 .NET 环境下实现一个基于可靠消息的分 ...

  2. 微服务架构实践 - 你只懂docker与spring boot就够了吗?

    微服务架构实践 - 你只懂docker与spring boot就够了吗? 作者 浮云发发 已关注 2017.02.27 02:50* 字数 2613 阅读 2583评论 6喜欢 35赞赏 2 微服务并 ...

  3. 深入理解微服务架构spring的各个知识点(面试必问知识点)

    什么是spring spring是一个开源框架,spring为简化企业级开发而生,使用spring可以使简单的java bean 实现以前只有EJG才能实现的功能. Spring是一个轻量级的控制反转 ...

  4. WeText项目:一个基于.NET实现的DDD、CQRS与微服务架构的演示案例

    最近出于工作需要,了解了一下微服务架构(Microservice Architecture,MSA).我经过两周业余时间的努力,凭着自己对微服务架构的理解,从无到有,基于.NET打造了一个演示微服务架 ...

  5. NET实现的DDD、CQRS与微服务架构

    WeText项目:一个基于.NET实现的DDD.CQRS与微服务架构的演示案例 最近出于工作需要,了解了一下微服务架构(Microservice Architecture,MSA).我经过两周业余时间 ...

  6. 几种常见的微服务架构方案简述——ZeroC IceGrid、Spring Cloud、基于消息队列

    微服务架构是当前很热门的一个概念,它不是凭空产生的,是技术发展的必然结果.虽然微服务架构没有公认的技术标准和规范草案,但业界已经有一些很有影响力的开源微服务架构平台,架构师可以根据公司的技术实力并结合 ...

  7. 基于OpenResty和Node.js的微服务架构实践

    什么是微服务? 传统的单体服务架构是单独服务包,共享代码与数据,开发成本较高,可维护性.伸缩性较差,技术转型.跨语言配合相对困难.而微服务架构强调一个服务负责一项业务,服务可以单独部署,独立进行技术选 ...

  8. 几种常见的微服务架构方案——ZeroC IceGrid、Spring Cloud、基于消息队列、Docker Swarm

    微服务架构是当前很热门的一个概念,它不是凭空产生的,是技术发展的必然结果.虽然微服务架构没有公认的技术标准和规范草案,但业界已经有一些很有影响力的开源微服务架构平台,架构师可以根据公司的技术实力并结合 ...

  9. 漫谈微服务架构:什么是Spring Cloud,为何要选择Spring Cloud

        Spring Cloud是基于Spring Boot的,因此还在使用SpringMVC的同学要先了解Spring Boot.先上一段官话,Spring Cloud是一个基于Spring Boo ...

随机推荐

  1. 《HelloGitHub》第 69 期

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. https://github.com/521xueweiha ...

  2. SpringMVC 五大组件

    DispatcherServlet HandleMapping Controller ModeAndView ViewResolver 1.DispatcherServlet 这个控件是SpringM ...

  3. 第五十一个知识点:什么是基于ID的加密的安全模型,然后描述一个IBE方案

    第五十一个知识点:什么是基于ID的加密的安全模型,然后描述一个IBE方案 在公钥密码学中,如果Alice想要给Bob发送一条消息,她需要Bob的公钥,一般来说公钥都很长,就像一个随机的字符串. 假设A ...

  4. Augmentation For GAN

    目录 概 主要内容 Differentiable Augmentation Adaptive Augmentation 代码 Zhao S., Liu Z., Lin J., Zhu J. and H ...

  5. 对vector和map容器的删除元素操作

    /** * 删除头部元素 * 切割map到指定的个数 * @param map * @param i * @return */ map<int, Rect> PublicCardFrame ...

  6. 单芯片替代PS176 DP转HDMI 4K60HZ DP转HDMI2.0转换芯片CS5263

    PS176是一个显示端口 (DP)至HDMI 2.0视频接口转换器适用于需要视频协议转换的电缆适配器.电视接收器.监视器和其他应用.它将接受任何显示端口输入格式,包括DP 1.1a.dp1.2a.dp ...

  7. <数据结构>XDOJ332.二叉排序树的判定

    问题与解答 问题描述 给定一个二叉树,判断其是否是一个有效的二叉排序树. 假设一个二叉排序树具有如下特征: 结点的左子树只包含小于当前结点的树. 结点的右子树只包含大于当前结点的树. 所有左子树和右子 ...

  8. IntelliJ IDEA 2019.3 代码提示忽略大小写(IDEA 2019版本如何设置代码提示不分大小写?)

    最近在使用IDEA,发现每次只能进行完全匹配,且区分大小写,界面变了IDEA 2019.3 忽略大小写设置跟之前的版本稍微有点不同,跟之前的软件有点点区别,在此记录一下不区分大小写的方法. 1. 使用 ...

  9. JS中常见的几种控制台台报错

    Error 控制台报错 EvalError 全局错误RangeError 引用错ReferenceError 参数错误SyntaxError 语法错误TypeError 类型错误URIError 编码 ...

  10. MYSQL架构理解

    目录 一.MYSQL架构 1. 架构图 2.分层实现 3.查询组件 二.并发控制 三. 事务 四.引擎 摘自 通过对MYSQL重要的几个属性的理解,建立一个基本的MYSQL的知识框架 一.MYSQL架 ...