52.seata分布式事务

news2024/9/21 22:49:37

目录

1.事务的四大特性。 

2.分布式服务的事务问题。

3.seata。

3.1理论基础。

3.1.1CAP定理。

3.1.2BASE理论。

3.2初识Seata。

3.2.1Seata的架构。

3.2.2部署TC服务。

3.2.3微服务集成Seata。

3.3 seata提供的四种分布式事务解决方案。

3.3.1 XA模式。

3.3.1.1 XA模式原理。

3.3.1.2 实现XA模式。

3.3.2 AT模式。

3.3.2.1 AT模式原理。

3.3.2.2 AT模式的脏写问题 、AT模式的写隔离。

3.3.2.3 实现AT模式。

3.3.3 TCC模式。

3.3.3.1 TCC模式原理。

 3.3.3.2 TCC模式实现。

3.3.4 SAGA模式。

3.4 seata的四种模式对比。

3.5高可用。


1.事务的四大特性。 

ACID 是指数据库事务的四个特性,每个字母代表一个特性:

  1. 原子性(Atomicity):原子性要求数据库中的事务是一个不可分割的操作单元,要么全部成功执行,要么全部回滚,不会停留在中间状态。如果一个事务中的某个操作失败,那么整个事务都会被回滚到初始状态,不会对数据库产生任何影响。

  2. 一致性(Consistency):一致性要求数据库在进行事务处理后,必须保持数据的一致性状态。这意味着数据库在事务开始和结束时,必须遵循预定义的规则和约束条件,以确保数据的完整性和正确性。(要么全部执行,要么全部不执行,从而保持数据的一致性)(要么全部成功,要么全部失败

  3. 隔离性(Isolation):隔离性要求数据库中的每个事务都是相互隔离的,即每个事务对其他事务的操作应该是不可见的,以避免并发执行时的数据冲突和异常情况。数据库通过各种并发控制机制来实现隔离性,如锁机制、多版本并发控制(MVCC)等。(就是避免事务相互影响,比如两个事务以上对相同数据同时操作,或者其他事务还没成功提交就拿着用了)(事务在操作某些数据库记录时,把其他事务隔离在这些数据库记录外,事务之间不能相互影响。这样比较容易记

  4. 持久性(Durability):持久性要求一旦事务提交成功,数据库中的数据就应该永久保存,即使在系统故障或重启后也能够保持数据的一致性状态。数据库通过将数据写入稳定的存储介质(如磁盘)来实现持久性。

隔离性讲解:如果没有隔离性,可能会出现以下情况:

  1. 脏读:如果没有隔离性,一个事务可能会读取到另一个未提交事务的数据,导致读取到不正确的数据,从而产生误导性的结果。(我只是随便写写,你居然当真了?

  2. 不可重复读:缺乏隔离性可能导致同一个事务内多次读取同一数据时得到不一致的结果,影响事务的可靠性和一致性。(前脚读完,后脚就改了,数据还没给用户瞧,你就改了?

  3. 幻读:在缺乏隔离性的情况下,一个事务在读取某个范围内的记录时,另一个事务在该范围内插入了新的记录或删除旧的记录,导致第一个事务再次读取该范围时出现了之前不存在的记录,破坏了数据的一致性。(趁我不注意,你不是把我作业藏起来,就是改成你名字,你是要我怀疑人生?

  4. 数据完整性问题:缺乏隔离性可能导致事务之间相互干扰,从而破坏了数据库中数据的完整性和一致性,进而影响系统的可靠性和稳定性。(你改,我也改,大家一起改?

2.分布式服务的事务问题。

3.seata。

3.1理论基础。

3.1.1CAP定理。

C一致性:出现分区问题,访问独立分区节点的请求都被阻塞。

A可用性:出现分区问题,可以正常访问,该分区节点与其他分区节点的数据不一致性。

P分区容错性:在集群出现分区时,整个系统也要持续对外提供服务(如果不使用P,相当于访问所有分区节点都被阻塞)。

提醒:所以P在分布式系统中是必选的,A和C只能单选。

通过采用CP模型,Elasticsearch保证了数据的一致性和分区容错性。当有新的文档需要写入时,它会首先被写入主分片,然后主分片会将变更传播给对应的副本分片。只有当主分片和副本分片都确认写入成功后,写操作才会被认为是成功的,从而确保了数据的一致性。

P只要是分布式服务,使用了网络,就有可能网络故障出现分区的情况,P作用就是出现分区的情况下也能正常进行访问。

低可用性是因为当出现分区的时候,部分节点与其它节点失去连接,这时为了保证数据的一致性,访问该部分节点的请求都会被阻塞或拒绝。(如果是AP恰恰相反,可以访问,但是数据的一致性没了)

3.1.2BASE理论。

最终一致思想:不一致性、软状态(分别执行和提交)和最终一致的结合。

AP模式:不一致性(跟软状态很像) + 软状态 + 最终一致性。(允许暂时不一致性)

CP模式:强一致性 + 基本可用。(不允许暂时一致性)


3.2初识Seata。

Seata是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布 式事务服务,为用户打造一站式的分布式解决方案。

官网地址:http://seata.io/,其中的文档、播客中提供了大量的使用说明、源码分析。 

3.2.1Seata的架构。

3.2.2部署TC服务。

部署Seata的tc-server。

1.下载

首先我们要下载seata-server包,地址在http://seata.io/zh-cn/blog/download.html

2.解压

 在非中文目录解压缩这个zip包,其目录结构如下:

3.修改配置

修改conf目录下的registry.conf文件:

  内容如下:

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos" # 改了,用哪个注册中心就写那个的名字,然后只需要修改下面对应注册中心的参数就行

  nacos {
    application = "seata-tc-server" # 改了
    serverAddr = "127.0.0.1:80" # 改了
    group = "DEFAULT_GROUP" # 改了
    namespace = ""
    cluster = "SH" # 改了,SH代表上海,集群的名称
    username = "nacos" # 改了
    password = "nacos" # 改了
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = 0
    password = ""
    cluster = "default"
    timeout = 0
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
    aclToken = ""
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"

  nacos {
    serverAddr = "127.0.0.1:80" # 改了
    namespace = ""
    group = "SEATA_GROUP" 
    username = "nacos" # 改了
    password = "nacos" # 改了
    dataId = "seataServer.properties"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
    aclToken = ""
  }
  apollo {
    appId = "seata-server"
    ## apolloConfigService will cover apolloMeta
    apolloMeta = "http://192.168.1.204:8801"
    apolloConfigService = "http://192.168.1.204:8080"
    namespace = "application"
    apolloAccesskeySecret = ""
    cluster = "seata"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
    nodePath = "/seata/seata.properties"
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

4.在nacos添加配置

特别注意,为了让tc服务的集群可以共享配置,我们选择了nacos作为统一配置中心。因此服务端配置文件seataServer.properties文件需要在nacos中配好。

格式如下:

配置内容如下:

# 数据存储方式,db代表数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123
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

==其中的数据库地址、用户名、密码都需要修改成你自己的数据库信息。==

创建好如下:这里有两个配置文件,第一个是之前sentinel时使用的。第二个就是刚才创建的。

5.创建数据库表

特别注意:tc服务在管理分布式事务时,需要记录事务相关数据到数据库中,你需要提前创建好这些表。

新建一个名为seata的数据库,运行课前资料提供的sql文件:

这些表主要记录全局事务、分支事务、全局锁信息:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- 分支事务表
-- ----------------------------
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;
​
-- ----------------------------
-- 全局事务表
-- ----------------------------
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;
​
SET FOREIGN_KEY_CHECKS = 1;

6.启动TC服务

启动TC服务:

进入bin目录,运行其中的seata-server.bat即可: 

 打开浏览器,访问nacos地址:http://localhost:8848,我配的端口是80,默认的是8848,然后进入服务列表页面,可以看到seata-tc-server的信息:

出现异常解决方法:

 解决方法是:打开seata-server.bat文件添加java的jdk8路径。

找到修改前的这一行:

修改后结果:

 然后重新启动就能成功了。

启动成功后,seata-server应该已经注册到nacos注册中心了。

3.2.3微服务集成Seata。

添加seata坐标集成微服务出现报错: 

添加坐标后,一启动服务就报错,启动失败。

解决方法是添加虚拟机选项:

--add-opens java.base/java.lang=ALL-UNNAMED

微服务集成seata分布式事务:

seata:
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:80
      namespace: "" # 什么都不写就是public
      group: DEFAULT_GROUP
      application: seata-tc-server
      username: nacos
      password: nacos
  tx-service-group: seata-demo # 事务组名称
  service:
    vgroup-mapping: # 事务组与cluster集群的映射关系
      seata-demo: SH

3.3 seata提供的四种分布式事务解决方案。

Seata提供了四种不同的分布式事务解决方案:

XA 模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
TCC 模式:最终一致的分阶段事务模式,有业务侵入
AT 模式:最终一致的分阶段事务模式,无业务侵入,也是 Seata 的默认模式
SAGA 模式:长事务模式,有业务侵入

3.3.1 XA模式。

seata的RM也仅仅是在数据库的接口基础上做了一层简单的封装,实际上seata的RM它也是调用数据库的RM实现。(主流数据库基本都实现了RM)。这种方法依赖数据库底层的实现,如果数据库没有实现XA模式,那么就用不了。

3.3.1.1 XA模式原理。

XA模式原理

XA 规范 是 X/Open 组织定义的分布式事务处理(DTPDistributed Transaction Processing)标准,XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。

seataXA模式:

seataXA模式做了一些调整,但大体相似:

RM一阶段的工作:

注册分支事务到 TC
执行分支业务 sql 但不提交
报告执行状态到 TC

TC二阶段的工作:

TC 检测各分支事务执行状态
a. 如果都成功,通知所有 RM 提交事务
b. 如果有失败,通知所有 RM 回滚事务

RM二阶段的工作:

接收 TC 指令,提交或回滚事务

总结:

XA模式的优点是什么?

事务的强一致性,满足 ACID 原则。
常用数据库都支持,实现简单,并且没有代码侵入

XA模式的缺点是什么?

因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
依赖关系型数据库实现事务
3.3.1.2 实现XA模式。

实现XA模式

Seata的starter已经完成了XA模式的自动装配,实现非常简单,步骤如下:

1. 修改application.yml文件(每个参与事务的微服务),开启XA模式:

seata:  
  data-source-proxy-mode: XA # 开启数据源代理的XA模式

2. 给发起全局事务的入口方法添加@GlobalTransactional注解,本例中是OrderServiceImpl中的create方法:

@Override
@GlobalTransactional
public Long create(Order order) {
 // 创建订单
 orderMapper.insert(order);
 // 扣余额 ...略
 // 扣减库存 ...略
 return order.getId();
}

3. 重启服务并测试

访问的时候出现该异常解决方法:

 解决方法:

把数据库驱动类从8.0.27换成8.0.11就能成功启动。指定的url还要加上时区。

url: jdbc:mysql://localhost:3306/seata_demo?serverTimezone=UTC
    <properties>
        <mysql.version>8.0.11</mysql.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

3.3.2 AT模式。

3.3.2.1 AT模式原理。

AT模式原理:

总结:

简述AT模式与XA模式最大的区别是什么?

•  XA 模式一阶段不提交事务,锁定资源; AT 模式一阶段直接提交,不锁定资源。
•  XA 模式依赖数据库机制实现回滚; AT 模式利用数据快照实现数据回滚。
•  XA 模式强一致; AT 模式最终一致
3.3.2.2 AT模式的脏写问题 、AT模式的写隔离。

AT模式的脏写问题

AT模式的写隔离(seata管理的全局事务):

全局锁只对seata管理的全局事务有用,非seata管理的事务不需要全局锁就能直接操作。

AT模式的写隔离(非seata管理的全局事务):

全局锁只对seata管理的全局事务有用,非seata管理的事务不需要全局锁就能直接操作。

总结:

AT模式的优点:

•  一阶段完成直接提交事务,释放数据库资源,性能比较好
•  利用全局锁实现读写隔离
•  没有代码侵入,框架自动完成回滚和提交

AT模式的缺点:

•  两阶段之间属于软状态,属于最终一致
•  框架的快照功能会影响性能,但比 XA 模式要好很多
3.3.2.3 实现AT模式。

实现AT模式:

要注意这里的两个表可能不在同一个库中。

下面是seata-at.sql文件里面的两个表,注意两个表可能不在同一个库中。

-- ----------------------------
-- 这一张表是放在TC服务使用的库中,在之前我们在nacos配置的TC数据库配置指定了使用seata库,所以放在seata库
-- ----------------------------
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;


-- ----------------------------
-- 这一张表是放在集成了seata的微服务的配置文件中指定了的数据库的库,集成的seata的微服务我都是使用seata-demo库,所以就放在这个库上。
-- ----------------------------
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;

3.3.3 TCC模式。

3.3.3.1 TCC模式原理。

TCC模式原理:

总结:

TCC模式的每个阶段是做什么的?

•  Try :资源检查和预留
•  Confirm :业务执行和提交
•  Cancel :预留资源的释放

TCC的优点是什么?

•  一阶段完成直接提交事务,释放数据库资源,性能好
•  相比 AT 模型,无需生成快照,无需使用全局锁,性能最强
•  不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库

TCC的缺点是什么?

•  有代码侵入,需要人为编写 try Confirm Cancel 接口,太麻烦
•  软状态,事务是最终一致
•  需要考虑 Confirm 和Cancel的失败情况,做好幂等处理(

幂等性是指无论调用多少次,对系统的状态都只有一次改变。在 TCC 模式中,如果参与者因为各种原因执行了多次,那么整个事务的状态就会变得不确定,因此需要确保每个参与者操作具有幂等性。

例如,在一个转账事务中,如果在“try”阶段时发生了错误,导致“try”被多次调用,那么在“confirm”或“cancel”阶段也可能会被多次调用。如果这些操作不具有幂等性,就会出现重复的转账操作,导致数据不一致。

 3.3.3.2 TCC模式实现。

TCC的空回滚和业务悬挂:

空回滚:就是调用某分支事务的时候因为阻塞导致失败,然后TC通知所有分支事务做回滚,但是失败分支事务都没有执行try(没有预留资源),这时就需要做空回滚。(如果让它执行cancel方法会执行失败,然后继续尝试执行cancel)。

业务悬挂:已经空回滚的业务,继续执行try,就永远不可能confirm或cancel,这就是业务悬挂。,因为该全局事务已经结束了我们这时要阻止它执行。

TCC实现案例:

(@BusinessActionContextParameter(paramName = "param") 注解标记的参数会放到BusinessActionContext上下文对象中,到时候通过该上下文对象可以获取该参数。

根据上面的理解分析来动手实现TCC模式:

1.导入account_freeze_tbl数据库表,放在微服务使用的库中。

DROP TABLE IF EXISTS `account_freeze_tbl`;
CREATE TABLE `account_freeze_tbl`  (
  `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `freeze_money` int(11) UNSIGNED NULL DEFAULT 0,
  `state` int(1) NULL DEFAULT NULL COMMENT '事务状态,0:try,1:confirm,2:cancel',
  PRIMARY KEY (`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;

2.定义account_freeze_tbl表的实体类。

@Data
@TableName("account_freeze_tbl")
public class AccountFreeze {
    @TableId(type = IdType.INPUT)
    private String xid;
    private String userId;
    private Integer freezeMoney;
    private Integer state;
    public static abstract class State {
        public final static int TRY = 0;
        public final static int CONFIRM = 1;
        public final static int CANCEL = 2;
    }
}

3.定义操作account_freeze_tbl表的mybatis-plus的接口类。

@Mapper
public interface AccountFreezeMapper extends BaseMapper<AccountFreeze> {
}

4.定义TCC两个阶段的三个接口方法。

@LocalTCC
public interface AccountTCCService {
    @TwoPhaseBusinessAction(name = "deduct",commitMethod = "fonfirm",rollbackMethod = "cancel")
    void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
                @BusinessActionContextParameter(paramName = "money") int money);
    boolean confirm(BusinessActionContext context);
    boolean cancel(BusinessActionContext context);
}

我们要做的业务操作接口:

@Mapper
public interface AccountMapper extends BaseMapper<Account> {

    @Update("update account_tbl set money = money - ${money} where user_id = #{userId}")
    int deduct(@Param("userId") String userId, @Param("money") int money);

    @Update("update account_tbl set money = money + ${money} where user_id = #{userId}")
    int refund(@Param("userId") String userId, @Param("money") int money);
}

5.实现TCC两个阶段的三个方法。

由于 confirmcancel 方法的执行不会对数据进行修改,因此不需要使用 @Transactional 注解。这些方法通常只需要保证幂等性即可,确保多次调用不会产生副作用。

@Service
@Slf4j
public class AccountTCCServiceImpl implements AccountTCCService {
    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private AccountFreezeMapper freezeMapper;

    @Override
    @Transactional
    public void deduct(String userId, int money) {
        //0.获取全局事务id
        String xid = RootContext.getXID();
        //1.判断freeze中是否有冻结记录,如果有,一定是cancel执行过,我要拒绝业务。
        AccountFreeze oldFreeze = freezeMapper.selectById(xid);
        if (oldFreeze != null){
            //cancel执行过,我要拒绝业务
            return;
        }
        //1.扣减可用余额(数据库中的字段使用unsigned关键词修饰,如果为负会报错,然后回滚,所以可以省略余额判断)
        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 context) {
        //1.获取事务id
        String xid = context.getXid();
        //2.根据id删除冻结及记录
        int count = freezeMapper.deleteById(xid);
        return count == 0;
    }
    @Override
    public boolean cancel(BusinessActionContext context) {
        //注意:这里要保留记录,做空回滚和业务悬挂判断时需要用。
        String xid = context.getXid();
        String userId = context.getActionContext("userId").toString();
        //0.查询冻结记录
        AccountFreeze freeze = freezeMapper.selectById(xid);
        //1.空回滚的判断,判断freeze是否为null,为null证明try没执行,需要空回滚
        if (freeze == null){
            //证明try没执行,需要空回滚
            freeze = new AccountFreeze();
            freeze.setUserId(userId);
            freeze.setFreezeMoney(0);
            freeze.setState(AccountFreeze.State.CANCEL);
            freeze.setXid(xid);
            freezeMapper.insert(freeze);
            return true;
        }
        //2.幂等判断
        if (freeze.getState() == AccountFreeze.State.CANCEL){
            //已经处理过一次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;
    }
}

6.修改控制类。

@RestController
@RequestMapping("account")
public class AccountController {

    @Autowired
    private AccountTCCService accountTCCService;//原本是直接使用的AccountService接口的方法

    @PutMapping("/{userId}/{money}")
    public ResponseEntity<Void> deduct(@PathVariable("userId") String userId, @PathVariable("money") Integer money){
        accountTCCService.deduct(userId, money);
        return ResponseEntity.noContent().build();
    }
}

业务执行失败,回滚后的记录。

3.3.4 SAGA模式。

3.4 seata的四种模式对比。

3.5高可用。

TC服务的高可用和异地容灾(异地容灾就是把微服务部署到不同机房。)

1.模拟异地容灾的TC集群

计划启动两台seata的tc服务节点:

节点名称ip地址端口号集群名称
seata127.0.0.18091SH
seata2127.0.0.18092HZ

之前我们已经启动了一台seata服务,端口是8091,集群名为SH。

现在,将seata目录复制一份,起名为seata2

修改seata2/conf/registry.conf内容如下:

registry {
  # tc服务的注册中心类,这里选择nacos,也可以是eureka、zookeeper等
  type = "nacos"
​
  nacos {
    # seata tc 服务注册到 nacos的服务名称,可以自定义
    application = "seata-tc-server"
    serverAddr = "127.0.0.1:8848"
    group = "DEFAULT_GROUP"
    namespace = ""
    cluster = "HZ"
    username = "nacos"
    password = "nacos"
  }
}
​
config {
  # 读取tc服务端的配置文件的方式,这里是从nacos配置中心读取,这样如果tc是集群,可以共享配置
  type = "nacos"
  # 配置nacos地址等信息
  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "seataServer.properties"
  }
}

进入seata2/bin目录,然后运行命令:

seata-server.bat -p 8092

打开nacos控制台,查看服务列表:

点进详情查看:

2.将事务组映射配置到nacos

接下来,我们需要将tx-service-group与cluster的映射关系都配置到nacos配置中心。

新建一个配置:

配置的内容如下:

# 事务组映射关系
service.vgroupMapping.seata-demo=SH
​
service.enableDegrade=false
service.disableGlobalTransaction=false
# 与TC服务的通信配置
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
# RM配置
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=false
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
# TM配置
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
​
# undo日志配置
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
client.log.exceptionRate=100

3.微服务读取nacos配置

接下来,需要修改每一个微服务的application.yml文件,让微服务读取nacos中的client.properties文件:

seata:
  config:
    type: nacos # 必须要学这个,不然会报错
    nacos:
      server-addr: 127.0.0.1:8848
      username: nacos
      password: nacos
      group: SEATA_GROUP
      data-id: client.properties

重启微服务,现在微服务到底是连接tc的SH集群,还是tc的HZ集群,都统一由nacos的client.properties来决定了。

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

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

相关文章

vr小鼠虚拟解剖实验教学平台减少了受感染风险

家畜解剖实验教学是培养畜牧兽医专业学生实际操作能力的专业教学活动中的核心手段。采取新型教学方式与手段&#xff0c;合理设置实验教学内容&#xff0c;有助于激发学生的操作积极性&#xff0c;促进实践教学的改革。 家畜解剖VR仿真教学是一种借助VR虚拟现实制作和web3d开发…

游戏开发团队配置与协作流程

游戏开发技术图谱 - 知乎 游戏制作的流程是什么啊&#xff1f; - 知乎 系统策划&#xff1a;一张图梳理游戏系统的生产流程 - 知乎 游戏开发入门&#xff08;十一&#xff09;游戏引擎架构-CSDN博客

rabbit MQ的延迟队列处理模型示例(基于SpringBoot死信模式)

说明&#xff1a; 生产者P 往交换机X&#xff08;typedirect&#xff09;会发送两种消息&#xff1a;一、routingKeyXA的消息&#xff08;消息存活周期10s&#xff09;&#xff0c;被队列QA队列绑定入列&#xff1b;一、routingKeyXB的消息&#xff08;消息存活周期40s&#xf…

unity自制循环匀速动画

动画制作&#xff0c;有循环匀速要求时&#xff0c;需要调节Curves&#xff0c;将其节点的Tangent改为Linear

python 水质日历热力图

利用日历热力图可以方便的查看站点水质全年的变化情况。 接口获取站点数据 这一步根据自己实际情况&#xff0c;也可以读取excel、MySQL读取数据。这里把API地址已隐去。 import numpy as np import calendar import requests import json import pandas as pd import time f…

『亚马逊云科技产品测评』活动征文|通过Lightsail搭建个人笔记

提示&#xff1a;授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 文章目录 前言实践知识储备Lightsail介绍Leanote介绍实践…

如何用CHAT写励志文章?

问CHAT&#xff1a;写一篇以《过了60岁要积极面对身体疾病的坎儿》为题目&#xff0c;写一篇300字励志文章 CHAT回复&#xff1a; 标题&#xff1a;《过了60岁要积极面对身体疾病的坎儿》 人生&#xff0c;有时会像一趟不期而遇的旅程&#xff0c;各自带着乐观或悲观、阳光或…

Transmit v5.10.3(FTP客户端)

Transmit 5是一款由Panic开发的功能强大的FTP(文件传输协议)客户端软件&#xff0c;专为 macOS 平台设计。它提供了简单、直观的界面和丰富的功能&#xff0c;使用户能够轻松地管理和传输文件。 在文件传输和同步方面&#xff0c;Transmit 5提供了强大的文件同步功能&#xff…

基于Android校园交流uniAPP+vue 微信小程序v7e1

本系统结合现今XX校园交流APP的功能模块以及设计方式进行分析&#xff0c;使用Android平台和Ssm框架进行开发设计&#xff0c;具体研究内容如下&#xff1a; (1) 系统管理员主要对用户管理、类型管理、娱乐天地管理、投诉举报管理、学习平台、我的收藏管理、系统管理等功能进…

小米智能摄像机云台版pro 拆解教程

拆解原因 因为设备提示无内存卡&#xff0c;摄像头手动调整方向到最上面&#xff0c;就可以看到内存卡插槽 但是这个摄像头因为内存卡弹出来了&#xff0c;导致无法插入也无法取出&#xff0c;所以决定拆开重新安装 第一步&#xff0c;拆开后即可拔出底座&#xff0c;拔掉摄像…

2023软件测试的4个技术等级,你在哪个级别?

最近&#xff0c;我们讨论了软件测试工程的的分级&#xff0c;大家都贡献了自己的想法&#xff0c;对于大家来说&#xff0c;软件测试人的分级其实也代表了我们的进阶方向&#xff0c;职业发展。总体来说&#xff0c;测试工程师未来发展有三个方向&#xff1a; 技术精英 行业专…

RK3568开发板在工控工业物联网网关方面的应用

在数字化转型的浪潮中&#xff0c;工控物联网关产品扮演着重要的角色。这些产品通过连接工业设备和网络&#xff0c;为数据传输和分析提供了便利。而迅为RK3568核心板作为一款高性能的芯片&#xff0c;为工控物联网关产品的性能提升和功能扩展提供了强大的支持。 迅为RK3568核心…

【Java】智慧工地管理系统源代码,支持二次开发,SaaS模式

智慧工地系统围绕工程现场人、机、料、法、环及施工过程中质量、安全、进度、成本等各项数据满足工地多角色、多视角的有效监管,实现工程建设管理的降本增效。 一、行业现状 1、施工现场管理难&#xff1a;安全事故频发&#xff0c;人工巡检难度大&#xff0c;质量进度协同难等…

自动化发展趋势以及自动化测试常见问题解析

前言 ⾃动化接⼝测试会越来越受到重视 在移动互联⽹时代&#xff0c;对于质量的要求⽐PC时代⾼的多&#xff0c;⽽投⼊产出⽐最⾼的⾃动化接⼝测试&#xff0c;将会是⼤部分公司的⾸选⽅向&#xff0c;但需要严格掌握⼀门语⾔ 持续集成是⽬前⾮常流⾏的开发⽅式&#xff0c;…

在有springSecurity或者若依项目中获取当前系统登录的用户信息

方法一&#xff08;springSecurity自带的&#xff09; AuthenticationPrincipal 是 Spring Security 框架中的一个注解&#xff0c;用于获取当前已认证用户的 principal&#xff08;即用户身份信息&#xff09;。 方法二&#xff08;若依项目自带的&#xff09; &#xff08;1…

最新AIGC创作系统ChatGPT网站源码,Midjourney绘画系统,支持最新GPT-4-Turbo模型,支持DALL-E3文生图

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…

七、通过libfdk_aac编解码器实现aac音频和pcm的编解码

前言 测试环境&#xff1a; ffmpeg的4.3.2自行编译版本windows环境qt5.12 AAC编码是MP3格式的后继产品&#xff0c;通常在相同的比特率下可以获得比MP3更高的声音质量&#xff0c;是iPhone、iPod、iPad、iTunes的标准音频格式。 AAC相较于MP3的改进包含&#xff1a; 更多的采…

【Python】Vscode解决Python中制表符和空格混用导致的缩进问题

【Python】Vscode解决Python中制表符和空格混用导致的缩进问题 文章目录 【Python】Vscode解决Python中制表符和空格混用导致的缩进问题1. 问题来源2. 解决Reference 1. 问题来源 在python中使用缩进来进行代码块的分区&#xff0c;通常来说python的一个缩进包含4个空格&#…

微服务学习|Feign:快速入门、自定义配置、性能优化、最佳实践

RestTemplate方式调用存在的问题 先来看我们以前利用RestTemplate发起远程调用的代码 存在下面的问题 代码可读性差&#xff0c;编程体验不统一 参数复杂URL难以维护 Feign的介绍 Feign是一个声明式的http客户端&#xff0c;官方地址: https://github.com/OpenFeign/feign …

轻松整理文件夹,将视频文件全部归类到另一个文件夹!

如果你需要整理文件夹中的文件&#xff0c;将同一类别的文件归纳到一起&#xff0c;可以更加方便地管理和查找。现在&#xff0c;我们有一个简单而实用的方法&#xff0c;可以将文件夹中的所有视频文件归类到另一个文件夹中&#xff0c;让你的文件管理更加有序和高效。 首先&am…