52.seata分布式事务

目录

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.mfbz.cn/a/178558.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

摩尔定律,梅特卡夫定律,吉尔德定律

信息系统的三大定律(摩尔定律&#xff0c;梅特卡夫定律&#xff0c;吉尔德定律)有一个清晰的视角&#xff1a; 信息系统不是左边的生产消费系统&#xff0c;而是右边的交易系统&#xff0c;交易系统与生产消费典型的区别在于信息交易过程会产生新的信息&#xff0c;就像钱一样…

jvs-智能bi(自助式数据分析)11.21更新功能上线

jvs智能bi更新功能 新增: 1.字段设置节点新增自定义时间格式功能&#xff1b; 自定义功能允许用户根据需要自定义日期和时间字段的显示格式&#xff0c;为用户提供了更大的灵活性和便利性 2.图表时间搜索条件新增向下兼容模式&#xff1b; 时间搜索条件的向下兼容模式允许用…

【wireshark】基础学习

TOC 查询tcp tcp 查询tcp握手请求的代码 tcp.flags.ack 0 确定tcp握手成功的代码 tcp.flags.ack 1 确定tcp连接请求的代码 tcp.flags.ack 0 and tcp.flags.syn 1 3次握手后确定发送成功的查询 tcp.flags.fin 1 查询某IP对外发送的数据 ip.src_host 192.168.73.134 查询某…

Ps:背景橡皮擦工具抠图实例

背景橡皮擦工具 Background Eraser Tool由于是一个破坏性的工具&#xff08;直接删除像素&#xff09;而少被人使用。 其实&#xff0c;它不仅是一个功能强大的抠图工具&#xff0c;也是可以转换为非破坏性运用的。 原图&#xff08;注&#xff1a;图片来自网络&#xff09; 效…

【尚硅谷】第06章:随堂复习与企业真题(面向对象-基础)

第06章&#xff1a;随堂复习与企业真题&#xff08;面向对象-基础&#xff09; 一、随堂复习 1. &#xff08;了解&#xff09;面向过程 vs 面向对象 不管是面向过程、面向对象&#xff0c;都是程序设计的思路。面向过程&#xff1a;以函数为基本单位&#xff0c;适合解决简单…

QGIS文章五——对遥感影像进行土地类型分类—监督分类(dzetsaka : classification tool)...

dzetsaka classification tool是QGIS的强大分类插件&#xff0c;目前主要提供了高斯混合模型分类器、Random Forest、KNN和SVM四种分类器模型&#xff0c;相比于SCP(Semi-Automatic Classification)&#xff0c;他的一个特点就是功能专一&#xff0c;操作简单。 从十一月开始一…

深度学习 loss 是nan的可能原因

1 loss 损失值非常大&#xff0c;超过了浮点数的范围&#xff0c;所以表示为overflow 状态下的男。 解决办法&#xff1a; 减小学习率&#xff0c;观察loss值是不是还是nan 在将数据输入模型前&#xff0c;进行恰当的归一化 缩放 2 loss 的计算中存在除以0&#xff0c; log(0…

ios qt开发要点

目前关于ios qt的开发资料比较少&#xff0c;这里整理了几个比较重要的开发要点&#xff0c;基于MacOS14 Xcode15 Qt15.5 cmake iphone真机。 cmake报错&#xff0c;报错信息如下 CMake Error at /Users/user/Qt/5.15.5/ios/lib/cmake/Qt5Core/Qt5CoreConfig.cmake:91 (m…

【PHP】PHP生成全年日历

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

Centos7使用rpm安装mysql 5.7.43

Centos7使用rpm安装mysql 5.7.43 1、下载rpm包 wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.43-1.el7.x86_64.rpm-bundle.tar2、解压并安装 tar xf mysql-5.7.43-1.el7.x86_64.rpm-bundle.tar yum -y install mysql-*3、按需修改mysql配置 #注意&a…

Impala VS Hive

Impala和Hive的关系 Impala是基于Hive的大数据实时分析查询引擎&#xff0c;直接使用Hive的元数据库Metadata,意味着impala元数据都存储在Hive的metastore中。并且impala兼容Hive的sql解析&#xff0c;实现了Hive的SQL语义的子集&#xff0c;功能还在不断的完善中。 与Hive的…

虹科Pico汽车示波器 | 汽车免拆检修 | 2017款东风本田XR-V车转向助力左右不一致

一、故障现象 一辆2017款东风本田XR-V车&#xff0c;搭载R18ZA发动机&#xff0c;累计行驶里程约为4万km。车主反映&#xff0c;车辆行驶或静止时&#xff0c;向右侧转向比向左侧转向沉重。 二、故障诊断 接车后试车&#xff0c;起动发动机&#xff0c;组合仪表上无故障灯点亮&…

探究Kafka原理-1.初识Kafka

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理&#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44…

Java集合拓展01

1、List&#xff0c;Set&#xff0c;Map三者的区别 List&#xff1a;一个有序&#xff08;元素存入集合的顺序和取出的顺序一致&#xff09;容器&#xff0c;元素可以重复&#xff0c;可以插入多个null元素&#xff0c;元素都有索引。常用的实现类有 ArrayList、LinkedList 和…

【Web】preg_match绕过相关例题wp

目录 ①[FBCTF 2019]rceservice ②[ctfshow]web130 ③[ctfshow]web131 ④[NISACTF 2022]middlerce 简单回顾一下基础 参考文章 p牛神文 preg_match绕过总的来讲就三块可利用 数组绕过、PCRE回溯次数限制、换行符 ①[FBCTF 2019]rceservice 先贴出附件给的源码 &l…

关于用css设置input输入框hover的时候的样式以及当input为disabled的时候,不要让hover样式生效

效果如果&#xff1a; 编辑状态下的时候&#xff1a; 只读状态下的时候&#xff1a; 代码如图&#xff1a; <input type"text" name"dataForm.exportCode" id"exportCodeItem" required :disabled"editDisabled" />input:not(…

SQL知多少?这篇文章让你从小白到入门

个人网站 本文首发公众号小肖学数据分析 SQL&#xff08;Structured Query Language&#xff09;是一种用于管理和处理关系型数据库的编程语言。 对于想要成为数据分析师、数据库管理员或者Web开发人员的小白来说&#xff0c;学习SQL是一个很好的起点。 本文将为你提供一个…

实时错误’-2147217887‘多步OLB DB 操作产生错误。如果可能,请检查OLE DB状态值

目录 背景问题问题分析问题解决 错误解决与定位技巧总结 背景 仍旧是学生信息管理系统的问题&#xff0c;当时做的时候没发现这么多问题呢&#xff0c;只能说明一件事&#xff0c;做的时候没有站在用户的角度考虑需求&#xff0c;设置了什么内容&#xff0c;就按照设置好的去测…

【libGDX】使用Mesh绘制圆形

1 前言 使用Mesh绘制三角形 中介绍了绘制三角形的方法&#xff0c;使用Mesh绘制矩形 中介绍了绘制矩形的方法&#xff0c;本文将介绍绘制圆形的方法。 libGDX 以点、线段、三角形为图元&#xff0c;没有提供绘制圆形的接口。要绘制圆形边框&#xff0c;必须通过割圆法逼近圆形&…

防止恶意攻击,服务器DDoS防御软件科普

作为一种恶意的攻击方式&#xff0c;DDoS攻击正以超出服务器承受能力的流量淹没网站&#xff0c;让网站变得不可用。近几年&#xff0c;这种攻击持续增多&#xff0c;由此优秀服务器DDoS防御软件的需求也随之增长。那么如何选择服务器DDoS防御软件&#xff0c;从根本上根除DDoS…
最新文章