微服务·数据一致-seata

news2024/12/24 21:11:19

微服务·数据一致-seata

概述

Seata(Simple Extensible Autonomous Transaction Architecture)是一个开源的分布式事务解决方案,旨在帮助应用程序分布式事务管理的挑战。Seata提供了一套全面的工具和框架,可用于实现跨多个数据库和服务的一致性事务管理。本报告将深入探讨Seata的核心概念、架构、特性以及使用场景。

核心概念

  • 全局事务:Seata引入了全局事务的概念,将一组分支事务(通常是数据库操作)组织在一起,以确保它们要么全部成功,要么全部回滚,以维护数据的一致性。
  • 分支事务:分支事务是全局事务中的单个数据库操作或者服务调用,它们遵循全局事务的指导并协作以实现全局事务的一致性。
  • 全局事务协调器:全局事务协调器(TC,Transaction Coordinator)负责群居事务的协调和管理,确保所有分支事务按照事务的隔离级别执行。

架构

Seata的架构包括三个核心组件:

  • 全局事务协调器(TC,Transaction Coordinator):负责全局事务的协调和管理。它记录了全局事务的状态,并根据分支事务的状态来确保全局事务的最终结果。
  • 分支事务参与者(TM, Transaction Manager):负责管理和执行分支事务、包括尝试、确认和回滚分支事务(在调用服务的方法中用注解启动事务)。
  • 分支事务资源管理器(RM,Resource Manager):负责管理分支事务所涉及的资源,入数据库、消息队列、缓存等。
    Seata实现分布式事务,设计了一个关键角色UNDO_LOG(回滚日志记录表),在每个应用分布式事务的业务库中创建了这张表,这个表的的核心作用就是将业务数据在更新前后的数据镜像组成回滚日志,备份在UNDO_LOG表中,以便业务异常能随时回滚。

特性

  • 强一致性:Seata确保全局事务的强一致性,即要么全部提交成功,要么全部回滚。
  • 高性能:Seata的设计注重性能,可以处理高并发的分布式事务。
  • 分布式事务隔离:Seata支持多种隔离级别,包括串行化、可重复读等,以适应不同的业务需求。
  • 全局事务追踪:Seata提供全局事务的追踪和监控能力,有助于故障排查和性能优化。
  • 分布式事务补偿:Seata支持Saga模式,可依通过补偿事务来处理部分失败的分布式事务。

Seata AT模式的详解

以下单扣库存、扣余额举例:
在这里插入图片描述

第一阶段

在这里插入图片描述

首先Seata的JDBC数据源代理通过对业务SQL解析,提取SQL的元数据,也就是得到SQL的类型(UPDATE),表(user),条件(where name = “天青色”)等相关信息。

  • 先查询数据更改前的信息(前镜像),根据解析得到的条件信息,生成查询语句,定位一条数据。
  • 紧接着执行业务SQL,根据前镜像数据之间查询出后镜像数据
  • 把业务数据在更新前后的数据镜像组织成回滚日志,将业务数据的更新和回滚日志在同一个本地事务中提交,分别插入到业务表和UNDO_LOG表中。
    UNDO_LOG数据格式如下,包括afterImage前镜像、beforeImage后镜像、branchId分支事务ID,xid全局事务ID
{
    "branchId":641789253,
    "xid":"xid:xxx",
    "undoItems":[
        {
            "afterImage":{
                "rows":[
                    {
                        "fields":[
                            {
                                "name":"id",
                                "type":4,
                                "value":1
                            }
                        ]
                    }
                ],
                "tableName":"product"
            },
            "beforeImage":{
                "rows":[
                    {
                        "fields":[
                            {
                                "name":"id",
                                "type":4,
                                "value":1
                            }
                        ]
                    }
                ],
                "tableName":"product"
            },
            "sqlType":"UPDATE"
        }
    ]
}

这样可以保证任何提交的业务数据的更新一定有相应的回滚日志。

在本地事务提交前,各分支事务需要向TC注册分支(Branch Id),为要修改的记录申请全局锁,要为这条数据加锁,利用Select for update语句。而如果一直拿不到锁那就需要回滚本地事务。TM开启事务后会生成全局唯一的XID,会在各个调用的服务间进行传递。

有了这样的机制,本地事务分支便可以在全局事务的第一阶段提交,并马上释放本地事务锁定的资源。相比传统的XA事务在第二阶段释放资源,Seata降低了锁范围提高效率。即使第二阶段发生异常需要回滚,也可以快速从UNDO_LOG表中找到对应回滚数据并反解析成SQL来达到回滚补偿。
最后本地事务提交,业务数据的更新和前面胜澈功能的UNDO_LOG数据一并条,并将本地事务提交的结果上报给全局事务协调者TC。

第二阶段

第二阶段是根据各分支的决议作出提交或者回滚:

提交

如果决议是全局提交,此时各分支事务已提交并成功,这是全局事务协调者(TC)会向分支发送第二阶段的请求。收到TC的分支提交请求,该请求会被放入一个异步任务队列中,并马上返回提交结果返回给TC。异步队列中会异步和批量的根据BranchID查找并删除相应的UNDO_LOG回滚记录。
在这里插入图片描述

回滚

如果决议是全局回滚,RM服务放收到TC全局协调者发来的回滚请求,通过XID和Branch ID找到相应的回滚日志记录,通过回滚记录生成反向的更新SQL并执行,以完成分支的回滚。
在这里插入图片描述

读写隔离

写隔离

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

举例:两个全局事务tx1和tx2,分别对应a表的m字段更新操作,m的初始值1000。
tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。
在这里插入图片描述
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
在这里插入图片描述
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。

此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。

因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。

读隔离

在数据库本地事务管理级别读已提交或以上的基础上,Seata AT模式的默认全局隔离级别是读未提交。如果在特定的场景下,必须要求全局的读已提交,seata的实现方式是通过SELECT FOR UPDATE语句代理。
在这里插入图片描述
SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。

XID的传递过程

  • TM注册到TC中,要发起全局事务式,先向TC发送一个通知,然后TC就会生成一个唯一的ID返还给TM,这个ID就是xid。
  • TM收到xid后放入当前线程的ThreadLocal中存储,在业务逻辑中使用OpenFeign调用其他服务的接口时,Seata重写了feign客户端;如果是Rest Template的方式,Seata也写了请求拦截器,将当前ThreadLocal中的xid放入hreader中进行传递。
  • RM收到请求后,首先在拦截器中尝试获取header中的xid,如果获取成功,就将xid再放入当前ThreadLocal中。
  • RM通知TC自己是该xid的事务参与者,也就是注册该分支服务。

实践

Seata Server

file.conf文件用于配制持久化事务日志的模式,目前提供file、db、redis三种方式。在选择db方式后,需要在对应的数据库中闯将gloableTable(持久化全局事务)、branchTable(持久化各提交分支的事务)、lockTable(持久化各分支锁定资源事务)三张表。

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

Seata Client

搭建服务,核心配置如下

spring:
    application:
        name: storage-server
    cloud:
        alibaba:
            seata:
                tx-service-group: my_test_tx_group
    datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://47.93.6.1:3306/seat-storage
        username: root
        password: root

业务大致流程:用户发起下单请求,本地 order 订单服务创建订单记录,并通过 RPC 远程调用 storage 扣减库存服务和 account 扣账户余额服务,只有三个服务同时执行成功,才是一个完整的下单流程。如果某个服执行失败,则其他服务全部回滚。
Seata 对业务代码的侵入性非常小,代码中使用只需用 @GlobalTransactional 注解开启一个全局事务即可。

@Override
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public void create(Order order) {
 
    String xid = RootContext.getXID();
 
    LOGGER.info("------->交易开始");
    //本地方法
    orderDao.create(order);
 
    //远程方法 扣减库存
    storageApi.decrease(order.getProductId(), order.getCount());
 
    //远程方法 扣减账户余额
    LOGGER.info("------->扣减账户开始order中");
    accountApi.decrease(order.getUserId(), order.getMoney());
    LOGGER.info("------->扣减账户结束order中");
 
    LOGGER.info("------->交易结束");
    LOGGER.info("全局事务 xid: {}", xid);
}

在相关的业务库中创建undo_log表来存数据回滚日志,表结构如下:

-- 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`
(
    `id`            BIGINT(20)   NOT NULL AUTO_INCREMENT COMMENT 'increment id',
    `branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(100) 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     NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME     NOT NULL COMMENT 'modify datetime',
    PRIMARY KEY (`id`),
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

结论

Seata是一个强大的分布式事务解决方案,具备高性能和强一致性的特点,适用于各种需要数据一致性的分布式应用场景。它的架构和设计使得分布式事务管理变得更加容易,有助于解决分布式系统中的一致性问题。对于需要构建高可用性和高性能的分布式应用程序的团队来说,Seata是一个值得考虑的工具和框架。

参考

https://www.cnblogs.com/chengxy-nds/p/14046856.html
https://seata.io/zh-cn/docs/next/dev/mode/at-mode

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1002336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nginx+Tomcat(多实例)实现动静分离和负载均衡

一、Tomcat 多实例部署 1.在安装好jdk环境后,添加两例tomcat服务 #解压安装包 cd /opt tar zxvf apache-tomcat-9.0.16.tar.gz#移动并复制一例 mkdir /usr/local/tomcat mv apache-tomcat-9.0.16 /usr/local/tomcat/tomcat1 cp -a /usr/local/tomcat/tomcat1 /usr…

常用JVM配置参数

在IDE的后台打印GC日志: 既然学习JVM,阅读GC日志是处理Java虚拟机内存问题的基础技能,它只是一些人为确定的规则,没有太多技术含量。 既然如此,那么在IDE的控制台打印GC日志是必不可少的了。现在就告诉你怎么打印。 …

Django03_Django基本配置

Django03_Django基本配置 3.1 整体概述 django项目创建后,在主应用中,会有一个settings.py文件,这个就是该项目的配置文件 settings文件包含Django安装的所有配置settings文件是一个包含模块级变量的python模块,所以该模块本身必…

解决nbsp;不生效的问题

代码块 {{title}} title:附 \xa0\xa0\xa0件,//或者 <span v-html"title"></span> title:附 件&#xff1a;,效果图

青骨申报|CSC管理信息平台使用指南

2023年青年骨干教师出国研修项目于9月10-25日网上报名&#xff0c;为此知识人网小编特转载最新版本的国家留学基金委&#xff08;CSC&#xff09;国家公派留学管理信息平台使用指南&#xff08;国内申请访学类&#xff09;&#xff0c;以方便申报者查阅。 提示&#xff1a;国家…

静态代理和动态代理笔记

总体分为: 1.静态代理: 代理类和被代理类需要实现同一个接口.在代理类中初始化被代理类对象.在代理类的方法中调 用被代理类的方法.可以选择性的在该方法执行前后增加功能或者控制访问 2.动态代理: 在程序执行过程中,实用JDK的反射机制,创建代理对象,并动态的指定要…

数字档案管理系统单机版功能

nhdeep数字档案管理系统&#xff0c;简化了档案库配置过程&#xff0c;内置标准著录项&#xff0c;点击创建新档案库后选择档案库类型为案卷库或一文一件库后&#xff0c;可立即使用此档案库&#xff1b; 支持添加额外的自定义著录项&#xff0c;支持批量数据导入&#xff0c;…

读高性能MySQL(第4版)笔记06_优化数据类型(上)

1. 良好的逻辑设计和物理设计是高性能的基石 1.1. 反范式的schema可以加速某些类型的查询&#xff0c;但同时可能减慢其他类型的查询 1.2. 添加计数器和汇总表是一个优化查询的好方法&#xff0c;但它们的维护成本可能很 1.3. 将修改schema作为一个常见事件来规划 2. 让事情…

JVM GC垃圾回收

一、GC垃圾回收算法 标记-清除算法 算法分为“标记”和“清除”阶段&#xff1a;标记存活的对象&#xff0c; 统一回收所有未被标记的对象(一般选择这种)&#xff1b;也可以反过来&#xff0c;标记出所有需要回收的对象&#xff0c;在标记完成后统一回收所有被标记的对象 。它…

二分搜索树节点删除(Java 实例代码)

目录 二分搜索树节点删除 src/runoob/binary/BSTRemove.java 文件代码&#xff1a; 二分搜索树节点删除 本小节介绍二分搜索树节点的删除之前&#xff0c;先介绍如何查找最小值和最大值&#xff0c;以及删除最小值和最大值。 以最小值为例&#xff08;最大值同理&#xff09…

在 Simscape Electrical 中对两区 MVDC 电动船的建模和仿真(Simulink实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Linux Centos7内网服务器离线升级openssh9.3

内网服务器需要升级openssh&#xff0c;被折磨了一整天&#xff0c;觉得有必要记录一下&#xff0c;不然对不起这差点崩溃的一天&#xff0c;主要的几个难点就是不能yum一键安装&#xff0c;需要自己找到对应的依赖版本然后通过堡垒机上传到内网&#xff0c;还有就是服务器很干…

无涯教程-JavaScript - ISPMT函数

描述 ISPMT函数计算在特定投资期间支付的利息。提供此功能是为了与Lotus 1-2-3兼容。 语法 ISPMT (rate, per, nper, pv)争论 Argument描述Required/OptionalRateThe interest rate for the investment.RequiredPerThe period for which you want to find the interest, an…

vue-puzzle-vcode完成验证码拖拽

一、 vue-puzzle-vcode插件【推荐】 GitHub地址&#xff1a;beeworkshop/vue-puzzle-vcode 1、安装vue-puzzle-vcode cnpm i -S vue-puzzle-vcode2、实现代码 <template><div><Vcode :show"isShow" success"success" close"close&…

7年经验之谈 —— Web测试是什么,有何特点?

Web测试是指对Web应用程序进行验证和评估的过程&#xff0c;以确保其功能、性能和安全性符合预期。 Web测试具体包括以下几个方面的内容&#xff1a; 功能测试&#xff1a;验证Web应用程序是否按照需求规格说明书中定义的功能正常工作。功能测试包括输入验证、表单提交、页面…

【Flutter】Flutter 使用 pull_to_refresh 实现下拉刷新和上拉加载

【Flutter】Flutter 使用 pull_to_refresh 实现下拉刷新和上拉加载 文章目录 一、前言二、pull_to_refresh 包简介三、安装与基本使用四、高级功能与配置五、实际业务中的用法六、完整示例七、总结 一、前言 你好&#xff01;在移动开发中&#xff0c;下拉刷新和上拉加载是非常…

【数据结构】双向链表详解

当我们学习完单链表后&#xff0c;双向链表就简单的多了&#xff0c;双向链表中的头插&#xff0c;尾插&#xff0c;头删&#xff0c;尾删&#xff0c;以及任意位置插&#xff0c;任意位置删除比单链表简单&#xff0c;今天就跟着小张一起学习吧&#xff01;&#xff01; 双向链…

Pytorch Advanced(一) Generative Adversarial Networks

生成对抗神经网络GAN&#xff0c;发挥神经网络的想象力&#xff0c;可以说是十分厉害了 参考 1、AI作家 2、将模糊图变清晰(去雨&#xff0c;去雾&#xff0c;去抖动&#xff0c;去马赛克等)&#xff0c;这需要AI具有“想象力”&#xff0c;能脑补情节&#xff1b; 3、进行数…

JavaScript Promise 的真正工作原理

Promise 是处理异步代码的一种技术,也称为脱离回调地狱的头等舱门票。 3 承诺状态 待定状态 已解决状态 拒绝状态 理解 JavaScript Promis 什么是承诺? 通常,承诺被定义为最终可用的值的代理。 Promise 多年来一直是 JavaScript 的一部分(在 ES2015 中标准化并引入)。最…

【数据结构】前言概况 - 树

&#x1f6a9;纸上得来终觉浅&#xff0c; 绝知此事要躬行。 &#x1f31f;主页&#xff1a;June-Frost &#x1f680;专栏&#xff1a;数据结构 &#x1f525;该文章针对树形结构作出前言&#xff0c;以保证可以对树初步认知。 目录&#xff1a; &#x1f30d;前言:&#x1f3…