当前位置: 首页 > news >正文

分布式事务seata入门

一、分布式事务

1.1 分布式事务问题

在分布式系统,跨越多个服务和数据源,每个服务都是分支事务,要保证所有分支事务的最终一致性。

在这里插入图片描述

1.2 CAP理论

  • 一致性(consistency)
    分布式的数据一致性。
  • 可用性(availability)
    微服务里的一个服务是否可以提供服务。
  • 分区容错(partition)
    当集群节点出现网络连接失败,数据无法同步,如果保证可用性,就无法保证数据一致性,反之则无法保证可用性。

1.3 AP和CP

  • AP
    AP保证可用性,可能会出现临时数据不一致的情况,但会通过补偿数据来弥补,达到最终一致性。
  • CP
    保证数据一致性,但是服务可能会临时不可用,但是基本可用。

二、Seata架构

2.1 Seata中的三个角色

  • 事务协调者(TC)
    维护全局和分支事务的状态,协调全局事务的提交与回滚。
  • 事务管理器(TM)
    定义全局事务的范围,开启全局事务、提交和回滚事务。
  • 资源管理器(RM)
    管理分支事务的资源,与TC交谈以注册和报告分支事务的状态,以驱动分支事务提交或回滚。

2.2 配置Seata

2.2.1 修改Seata-server下的conf的registry.conf

  • 修改type为nacos,为注册中心,删除其余注册中心的配置
    在这里插入图片描述

  • 用户名和密码配置为nacos

2.2.2 在nacos下配置seataServer.properties

#数据存储方式,db代表数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
#事务、日志等配置
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
#客户端与服务端传输方式
transport.serialization=seata
transport.compressor=none
#关闭metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

2.2.2 创建seata表,执行sql

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for branch_table
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table`  (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `transaction_id` bigint(20) NULL DEFAULT NULL,
  `resource_group_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `branch_type` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` tinyint(4) NULL DEFAULT NULL,
  `client_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime(6) NULL DEFAULT NULL,
  `gmt_modified` datetime(6) NULL DEFAULT NULL,
  PRIMARY KEY (`branch_id`) USING BTREE,
  INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of branch_table
-- ----------------------------

-- ----------------------------
-- Table structure for global_table
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table`  (
  `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `transaction_id` bigint(20) NULL DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_service_group` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `timeout` int(11) NULL DEFAULT NULL,
  `begin_time` bigint(20) NULL DEFAULT NULL,
  `application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime NULL DEFAULT NULL,
  `gmt_modified` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`xid`) USING BTREE,
  INDEX `idx_gmt_modified_status`(`gmt_modified`, `status`) USING BTREE,
  INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of global_table
-- ----------------------------


-- ----------------------------
-- Records of lock_table
-- ----------------------------

SET FOREIGN_KEY_CHECKS = 1;

2.2.3 进入到bin目录执行命令seata-server.bat

2.3 微服务集成seata

2.3.1 引入seata相关依赖

 <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-spring-boot-starter</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>${seata.version}</version>
        </dependency> //1.4.2

2.3.2 配置yml,对应信息在之前的conf文件中

seata:
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: ""
      group: DEFAULT_GROUP
      application: seata-tc-server
      username: nacos
      password: nacos
  tx-service-group: seata-demo # 事务组名称
  service:
    vgroup-mapping:
      seata-demo: SH
#  data-source-proxy-mode: XA
#  config:
#    type: nacos
#    nacos:
#      server-addr: 127.0.0.1:8848
#      username: nacos
#      password: nacos
#      group: SEATA_GROUP
#      data-id: seataServer.properties

确认一个TC是要根据namespace、group、service(applicaitonname)、cluster。
在这里插入图片描述

三、 实践

3.1 XA模式(强一致)

RM在XA中为数据库,第一阶段事务协调者让RM准备,但不提交,如果都就绪了,那就成功了,第二阶段进行统一提交。
在这里插入图片描述

seata中的XA模式
在这里插入图片描述
在这里插入图片描述
3.2 seata实现XA模式

3.2.1修改applicaiton.yml文件

seata:
 data-source-proxy-mode: XA #数据源代理

3.2.2 给全局事务的入口方法添加@GlobalTransactionl注解

3.2.3 库存不足实现回滚

在这里插入图片描述

3.2 AT模式(最终一致)

AT模式的TM调用事务分支后,RM会先保存数据快照undo-log后执行sql并提交,然后报告事务的状态,如果TC判断全部提交,就可以删除log,如果失败,就可以基于log恢复数据,再删除log

3.2.1 AT模式的脏写问题

如果在第一个事务进入并提交,第二个事务进入也提交,并且成功。而此时第一个事务需要回滚,使得更新数据丢失。

在这里插入图片描述
3.2.2 AT模式写隔离

AT模式采用全局锁来解决写隔离,通过线程idxid和表以及行数确定这行数据的锁被谁持有。需要在事务提交之前获取全局锁。
但是如果两个线程同时进来,一个需要db锁进行回滚,一个需要全局锁,造成死锁,通过获取全局锁设置时间间隔30ms,重试10次,获取不到就释放db锁进行数据库事务回滚,第一个事务就可以获取db锁就行数据恢复。
在这里插入图片描述

3.3 Seata实现AT模式

3.3.1 数据库导入

将lock_table导入TC服务关联的数据库,将undo_log表导入微服务关联的数据库。

DROP TABLE IF EXISTS undo_log;
CREATE TABLE undo_log (
branch_id bigint(20) NOT NULL COMMENT ‘branch transaction id’,
xid varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT ‘global transaction id’,
context varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci 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 INDEX ux_undo_log(xid, branch_id) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = ‘AT transaction mode undo table’ ROW_FORMAT = Compact;


DROP TABLE IF EXISTS lock_table;
CREATE TABLE lock_table (
row_key varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
xid varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
transaction_id bigint(20) NULL DEFAULT NULL,
branch_id bigint(20) NOT NULL,
resource_id varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
table_name varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
pk varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
gmt_create datetime NULL DEFAULT NULL,
gmt_modified datetime NULL DEFAULT NULL,
PRIMARY KEY (row_key) USING BTREE,
INDEX idx_branch_id(branch_id) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

3.3.2 yml配置的模式改为AT

3.3.3 AT和XT的区别

XT是在一阶段不提交,利用事务本身特性来进行隔离,AT是一阶段提交,利用全局锁来实现事务的隔离。

3.4 TCC模式

TCC模式与AT类似,每阶段都是独立事务。但是需要实现三个方法

  • Try
    资源的检测和预留
  • Confirm
    完成资源的操作业务;要求Try成功,Confirm一定能成功
  • Cancel
    预留资源的释放,为try的反向操作。

通过使用冻结金额进行资源预留
在这里插入图片描述
3.4.1 TCC模式原理

在这里插入图片描述
3.4.2 TCC的问题

  • 幂等性
    如果Confirm或者Concel失败了,seata会帮我们做重试,但是我们要保证结果的幂等性。
  • 空回滚和业务悬挂
    当有两个线程调用事务分支,第一个线程锁定资源,第二个事务超时了,这个业务出错了造成全局事务超时需要回滚,第一个事务回滚了,而第二个事务没有锁定资源就回回滚(会报错,然后一直重试),这时候不能进行回滚,就需要进行空回滚。
    当执行了空回滚后,如果之后执行try,就永远不可能confirm和cancel,出现业务悬挂,应当阻止空回滚后的try操作。
  • 空回滚和业务悬挂问题可以通过记录当前的状态state,如果事务没有try锁定资源进行cancel空回滚,我们可以通过一张表记录状态,如果try锁定资源就增加一条数据,状态为try,并且锁定对应的资源freeze_money ,如果进行空回归,在try中查不到数据就直接退出。
  • 幂等性可以对冻结资源进行判断,如果状态是CANCEL,那么就已经回滚过了,不需要在进行回滚,保证幂等性。
    在这里插入图片描述

3.4.3 Seata实现TCC模式

  • TCC的声明
    使用@BusinessActionContextParameter声明的变量能在BusinessActionContext中获取。在这里插入图片描述
  @Override
    @Transactional
    public void deduct(String userId, int money) {

        // 0.获取事务id
        String xid = RootContext.getXID();
        //解决业务悬挂
        if(freezeMapper.selectById("id") != null){
            return;
        }
        // 1.扣减可用余额
        accountMapper.deduct(userId, money);
        // 2.记录冻结金额,事务状态
        AccountFreeze freeze = new AccountFreeze();

        freeze.setUserId(userId);
        freeze.setFreezeMoney(money);
        freeze.setState(AccountFreeze.State.TRY);
        freeze.setXid(xid);
        freezeMapper.insert(freeze);
    }

    @Override
    public boolean confirm(BusinessActionContext ctx) {
        // 1.获取事务id
        String xid = ctx.getXid();
        // 2.根据id删除冻结记录
        int count = freezeMapper.deleteById(xid);
        return count == 1;
    }

    @Override
    public boolean cancel(BusinessActionContext ctx) {
        // 0.查询冻结记录
        String xid = ctx.getXid();
        AccountFreeze freeze = freezeMapper.selectById(xid);
     
        String userId = ctx.getActionContext("userId").toString();
        //判断freeze是否为null,如果为null,则证明try没有执行,需要空回滚
        if (freeze == null) {
            freeze = new AccountFreeze();
            freeze.setUserId(userId);
            freeze.setFreezeMoney(0);
            freeze.setState(AccountFreeze.State.CANCEL);
            freeze.setXid(xid);
            freezeMapper.insert(freeze);
            return true;
        }
   //幂等性
        if(freeze.getState() == AccountFreeze.State.CANCEL)
            return true;
        // 1.恢复可用余额
        accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());
        // 2.将冻结金额清零,状态改为CANCEL
        freeze.setFreezeMoney(0);
        freeze.setState(AccountFreeze.State.CANCEL);
        int count = freezeMapper.updateById(freeze);
        return count == 1;
    }

4、四种模式的区别

在这里插入图片描述

相关文章:

  • 深度神经网络训练
  • 盒模型小知识点
  • Hbase-9-HBase操作-过滤器
  • matlab gui编程教程,matlab如何使用gui
  • win10如何禁止CDR软件访问网络的设置方法教程
  • u2 尚硅谷--Vue 脚手架
  • STM32使用库函数点灯实验
  • C# 学习笔记1 - 输入输出
  • 替代STM32的GD32,替代KEIL的Eclipse配置---连载3
  • 贪心算法及其简单习题
  • java特殊数据结构:栈Stack
  • 基于APB与I2C的多主多从架构设计
  • Visual Studio Code 自动编译 TypeScript
  • 【智能合约】——智能合约安全指南
  • 三、git分支操作
  • 【附node操作实例】redis简明入门系列—字符串类型
  • canvas 高仿 Apple Watch 表盘
  • co.js - 让异步代码同步化
  • go append函数以及写入
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • JS专题之继承
  • Lsb图片隐写
  • node 版本过低
  • python大佬养成计划----difflib模块
  • Wamp集成环境 添加PHP的新版本
  • 初识MongoDB分片
  • 前端之React实战:创建跨平台的项目架构
  • 7行Python代码的人脸识别
  • kubernetes资源对象--ingress
  • Salesforce和SAP Netweaver里数据库表的元数据设计
  • #LLM入门|Prompt#3.3_存储_Memory
  • (层次遍历)104. 二叉树的最大深度
  • (第27天)Oracle 数据泵转换分区表
  • (顶刊)一个基于分类代理模型的超多目标优化算法
  • (附源码)计算机毕业设计ssm电影分享网站
  • (亲测)设​置​m​y​e​c​l​i​p​s​e​打​开​默​认​工​作​空​间...
  • (十八)SpringBoot之发送QQ邮件
  • (四)模仿学习-完成后台管理页面查询
  • (转)PlayerPrefs在Windows下存到哪里去了?
  • (转)四层和七层负载均衡的区别
  • .axf 转化 .bin文件 的方法
  • .NET Standard / dotnet-core / net472 —— .NET 究竟应该如何大小写?
  • .NET 中使用 TaskCompletionSource 作为线程同步互斥或异步操作的事件
  • .Net程序猿乐Android发展---(10)框架布局FrameLayout
  • .NET企业级应用架构设计系列之应用服务器
  • @Resource和@Autowired的区别
  • [ vulhub漏洞复现篇 ] GhostScript 沙箱绕过(任意命令执行)漏洞CVE-2019-6116
  • [ vulhub漏洞复现篇 ] JBOSS AS 5.x/6.x反序列化远程代码执行漏洞CVE-2017-12149
  • [] 与 [[]], -gt 与 > 的比较
  • [2010-8-30]
  • [AI]ChatGPT4 与 ChatGPT3.5 区别有多大
  • [Android]创建TabBar
  • [C#7] 1.Tuples(元组)
  • [C++参考]拷贝构造函数的参数必须是引用类型
  • [CareerCup] 17.8 Contiguous Sequence with Largest Sum 连续子序列之和最大