微服务-分布式事务

==1.分布式事务产生原因==

首先我们看看项目中的下单业务整体流程:

image-20240620154853978

由于订单、购物车、商品分别在三个不同的微服务,而每个微服务都有自己独立的数据库,因此下单过程中就会跨多个数据库完成业务。而每个微服务都会执行自己的本地事务:

  • 交易服务:下单事务
  • 购物车服务:清理购物车事务
  • 库存服务:扣减库存事务

整个业务中,各个本地事务是有关联的。因此每个微服务的本地事务,也可以称为分支事务。多个有关联的分支事务一起就组成了全局事务。我们必须保证整个全局事务同时成功或失败。

我们知道每一个分支事务就是传统的单体事务,都可以满足ACID特性,但全局事务跨越多个服务、多个数据库,不能满足!!!!!!!!!!!!

  • 产生原因

事务并未遵循ACID的原则,归其原因就是参与事务的多个子业务在不同的微服务,跨越了不同的数据库。虽然每个单独的业务都能在本地遵循ACID,但是它们互相之间没有感知,不知道有人失败了,无法保证最终结果的统一,也就无法遵循ACID的事务特性了。

这就是分布式事务问题,出现以下情况之一就可能产生分布式事务问题:

  • 业务跨多个服务实现
  • 业务跨多个数据源实现

==2.CAP定理==

1998年,加州大学的计算机科学家Eric Brewer提出,分布式系统要有三个指标:

  • Consistency(一致性):用户访问分布式系统中的任意节点,得到的数据必须一致

  • Availability(可用性):用户访问分布式系统时,读/写操作总能成功

  • Partition tolerance(分区容错性):即使系统出现网络分区,整个系统也要持续对外提供服务

他认为任何分布式系统架构方案都不能同时满足这三个目标,这个结论就是CAP定理。

2.1 一致性

Consistency(一致性):用户访问分布式系统中的任意节点,得到的数据必须一致

image-20241008165045608

2.2 可用性

Availability (可用性):用户访问分布式系统时,读或写操作总能成功。

只能读不能写,或者只能写不能读,或者两者都不能执行,就说明系统弱可用或不可用。

2.3 分区容错性

Partition tolerance(分区容错性):即使系统出现网络分区partition,整个系统也要持续对外提供服务tolerance。

其中partition(分区):当分布式系统节点之间出现网络故障导致节点之间无法通信的情况。

image-20241008170759823

如上图,node01和node02之间网关畅通,但是与node03之间网络断开。于是node03成为一个独立的网络分区;node01和node02在一个网络分区。

其中tolerance(分区容错):当系统出现网络分区,整个系统也要持续对外提供服务。

2.4 三者矛盾(P一定有)

在分布式系统中,网络不能100%保证畅通(partition网络分区的情况一定会存在)。而我们的系统必须要持续运行,对外提供服务。所以分区容错性(P)是硬性指标,所有的分布式系统都要满足。

而设计分布式系统的时候要取舍的就是一致性(C)和可用性(A)。

【P一定有,C和A不一定有】

image-20241008172328325

如果允许可用性(A):这样用户可以任意读写,但是由于node03不能同步数据,那就会出现数据不一致情况【只满足AP】

如果允许一致性(C):如果用户不允许随意读写(不允许写,允许读)一直到网络恢复,分区消失,只能满足数据一致性【只满足CP】

2.5 解决三者矛盾(BASE理论)

因为P一定有,C和A不一定有:所以要考虑到底是牺牲一致性还是可用性?—>BASE理论

  • Basically Available 基本可用:分布式系统在出现故障时,允许损失部分可用性,【保证核心可用性】
  • Soft State软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态。
  • Eventually Consistent最终一致性:虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。

总而言之,BASE理论其实就是一种取舍方案,不再追求完美,而是追求完成目标。

  • AP思想:【AT模式】各个子事务分别执行和提交,无需锁定数据。允许出现结果不一致,然后采用弥补措施恢复,实现最终一致。
  • CP思想:【XA模式】各个子事务执行后不要提交,而是等待彼此结果,然后同时提交或回滚。在这个过程中锁定资源,不允许其它人访问,数据处于不可用状态,但能保证一致性。

—————————————

==解决方案(中间人-事务协调者)==

解决分布式事务的方案有很多,但实现起来都比较复杂,因此我们一般会使用开源框架来解决分布式事务问题。在众多的开源分布式事务框架中,功能最完善、使用最多的就是阿里巴巴在2019年开源的Seata了。

1.Seata

官方地址:Seata

分布式事务产生的一个重要原因:参与事务的多个分支事务互相无感知, 不知道彼此的执行状态。

解决方案:就是找一个统一的事务协调者,与多个分支事务通信,检测每个分支事务的执行状态,保证全局事务下的每一个分支事务同时成功或失败即可。大多数的分布式事务框架都是基于这个理论来实现的。

image-20240620161226852

1.1 Seata架构

Seata也不例外,在Seata的事务管理中有三个重要的角色:

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

image-20240620162213687

  • 现来方式:直接执行全局事务,然后中途调用各个分支事务,执行结束就完成【各个分支不知道彼此是否正确】

  • 现在方式:直接执行全局事务(事务管理器TM管理开始和结束),然后中途调用各个分支事务(各个RM告知TC这个全局事务有我,我开始了,我结束了),执行结束就完成【中途有什么问题TC都知道,随时可能回滚】

1.2 代码实现思路(两个方面)

TMRM【Seata的客户端部分】,引入到参与事务的微服务依赖中即可。(将来TMRM就会协助微服务,实现本地分支事务与TC之间交互,实现事务的提交或回滚。)

TC【事务协调中心】,是一个独立的微服务,需要单独部署。

—————————————

==Seata具体操作(分两个部分)==

1.TC部署

1.1 准备数据库表

image-20240620170911535

其中seata-tc.sql内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
CREATE DATABASE IF NOT EXISTS `seata`;
USE `seata`;


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_status_gmt_modified` (`status` , `gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;


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 = utf8mb4;


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),
`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid_and_branch_id` (`xid` , `branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS `distributed_lock`
(
`lock_key` CHAR(20) NOT NULL,
`lock_value` VARCHAR(20) NOT NULL,
`expire` BIGINT,
primary key (`lock_key`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);

1.2 准备配置文件

  • 准备seata目录(包含application.yml配置文件),到时候docker容器可以挂载

image-20240620172739258

其中application.yml信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
server:
port: 7099 #控制台端口

spring:
application:
name: seata-server

logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
# extend:
# logstash-appender:
# destination: 127.0.0.1:4560
# kafka-appender:
# bootstrap-servers: 127.0.0.1:9092
# topic: logback_to_logstash

#控制台信息 ip:7099进入之后账号和密码
console:
user:
username: admin
password: admin

seata:
#配置中心
config:
# support: nacos, consul, apollo, zk, etcd3 多种配置中心
type: file
# nacos:
# server-addr: nacos:8848
# group : "DEFAULT_GROUP"
# namespace: ""
# dataId: "seataServer.properties"
# username: "nacos"
# password: "nacos"
#注册中心
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa 多种注册中心
type: nacos
nacos:
application: seata-server
server-addr: nacos:8848 #①ip地址 ②nacos就是容器名,意味着nacos和seata要在同一网络中(这样可通过容器名访问)
group : "DEFAULT_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
max-commit-retry-timeout: -1
max-rollback-retry-timeout: -1
rollback-retry-timeout-unlock-enable: false
enable-check-auth: true
enable-parallel-request-handle: true
retry-dead-threshold: 130000
xaer-nota-retry-timeout: 60000
enableParallelRequestHandle: true
recovery:
committing-retry-period: 1000
async-committing-retry-period: 1000
rollbacking-retry-period: 1000
timeout-retry-period: 1000
undo:
log-save-days: 7
log-delete-period: 86400000
session:
branch-async-queue-size: 5000 #branch async remove queue size
enable-branch-async-remove: false #enable to asynchronous remove branchSession
store:
# support: file 、 db 、 redis
mode: db
session:
mode: db
lock:
mode: db
#数据库配置
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql:3306/seata?rewriteBatchedStatements=true&serverTimezone=UTC
user: root
password: 123
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 1000
max-wait: 5000
# redis:
# mode: single
# database: 0
# min-conn: 10
# max-conn: 100
# password:
# max-total: 100
# query-limit: 1000
# single:
# host: 192.168.150.101
# port: 6379
metrics:
enabled: false
registry-type: compact
exporter-list: prometheus
exporter-prometheus-port: 9898
transport:
rpc-tc-request-timeout: 15000
enable-tc-server-batch-send-response: false
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
boss-thread-size: 1

1.3 Docker部署

  • 1.导入镜像文件和配置文件
image-20240620171216896
  • 2.加载镜像文件
image-20240620171453072
  • 3.运行docker容器
1
2
3
4
5
6
7
8
9
docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.92.129 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network heima \
-d \
seataio/seata-server:1.5.2

image-20240620172200138

  • 4.查看容器运行情况:docker logs -f seata

image-20240620172332870

  • 5.在浏览器输入IP:7099即可打开控制台

image-20240620172510841

2.微服务集成Seata

2.1 引入依赖

【所有分支事务都需要引入】为了方便各个微服务集成seata,我们需要把seata配置共享到nacos,因此trade-service模块不仅仅要引入seata依赖,还要引入nacos依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--统一配置管理,读取nacos共享配置文件-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

<!--如果只需要seata集成微服务,那就只导入这个依赖!!!!!!!!!!-->
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

image-20240621094124650

2.2 添加配置(统一配置到nacos)

【一般直接配置到apolication.yml文件】因为多个分支事务都需要,那我就可以将seata的配置放在nacos统一配置,剩下的就是改造application.yml和bootstrap.yml文件信息。

2.2.1 配置公共配置

server-addr一定要配置自己的ip:【不然容易注册不到nacos上去!!!】

image-20240621093202209

让微服务能找到TC的位置:

image-20240621093439565

这样配置之后,各个分支事务都去配置这个TC信息:

2.2.2 分支事务新建bootstrap.yml文件

这样配置之后,各个分支事务都去配置这个TC信息:

image-20240621094830123

2.2.3 分支事务调整application.yml文件

image-20240621094943628

2.3 添加数据库保存快照

seata的客户端(TM和RM)在解决分布式事务的时候需要记录一些中间数据,保存在数据库中。因此我们要先准备一个这样的表。

对三个分支事务hm-trade、hm-cart、hm-item三个数据库加入一个undo_log日志表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 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`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) 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(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';

添加完成之后:

image-20240621100417899

2.4 修改具体业务

我们重新启动项目之后,可以查看seata日志:

image-20240621112336246

然后针对出问题的方法进行修改【修改为GlobalTransactional注解】:

image-20240621102212713

@GlobalTransactional注解就是在标记事务的起点,将来TM就会基于这个方法判断全局事务范围,初始化全局事务。如果中途有分支事务出现问题,我们就可以告知TC进行回滚操作,保证全局事务要么成功/要么失败。

3.实现步骤

  • 1.准备TC所需要的数据库,准备配置文件和镜像文件 —【可以直接去服务器利用docker配置TC】
  • 2.微服务继承Seata
    • 2.1 引入seata依赖
    • 2.2 在yml配置seata信息 【因为涉及多个分支事务,所以一般配置到nacos】
    • 2.3 原有出现问题的方法替换@Tradtional注解为@GlobalTransactional注解解决分布式事务

==Seate四种底层原理-[四种]==

Seata支持四种不同的分布式事务解决方案:

  • XA
  • TCC(Try-Confirm-Cancel)
  • AT(Automatic Transaction)
  • SAGA

代码使用思路

【使用过程中,只是yml配置多一个属性】

其实就是Seata实现步骤:

①导入依赖

②yml配置(基础配置+模式配置属性) —-多了一个配置data-source-proxy-mode

③全局事务位置加注解@GlobalTransactional,分支事务加@Transactional

1.XA模式[统一控制,统一提交]

==①各事务执行完都锁住②统一判断是全部提交/全部撤回==

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

1.1 基本概念

主要分为==两个阶段==提交:

一阶段的工作:【TM通知各个RM执行本地事务,RM向TC注册和报告完成情况,但是不提交保持数据库锁】

①RM注册分支事务到TC【告知我是哪个TM的,我完成什么任务】

②RM执行分支业务sql但不提交【完成任务了】tryPayOrderByBalance

③RM报告执行状态到TC【告诉你我完成了】

二阶段的工作:【TC基于一阶段RM提交事务状态来判断下一步操作是回滚还是提交】

①TC检测各分支事务执行状态【看看各个RM完成如何】

​ a.如果都成功,通知所有RM提交事务【ok,提交吧】

​ b.如果有失败,通知所有RM回滚事务【no,回滚吧】

②RM接收TC指令,提交或回滚事务【TC告诉我其他人好了/有问题,就提交/回滚】

image-20240621140448858

1.1 我们启动服务通过注解@GlobalTranscational开启全局事务

1.2 我们操作的时候调用多个分支事务

1.3 分支事务先向TC进行注册,告知TC我的哪个TM负责的,我要完成什么【告知之后可以进行业务逻辑】

1.4 开始执行业务,进行sql语句的完成【但是不提交!!!!】

1.5 执行业务sql完成之后报告TC我已经完成我自己的任务了,报告事务状态【TC就知道分支业务完成状态(有的完成了,有的失败了)】

因为第一阶段结束我们可以进行结束全局事务,后续看看是回滚还是提交

2.1 结束全局事务

2.2 TM告知TC检查一下第一阶段各分支事务执行状态,看是不是所有都完成

2.3 因为要全局事务要么提交/要么回滚,如果都成功,通知所有RM提交事务,如果有失败,通知所有RM回滚事务

流程图如下:

image-20240621143957304

1.2 具体实现操作

1.2.1 yml配置

image-20240621131831960

1.2.2 修改具体业务

对应全局事务位置添加@GlobalTranscational:

image-20240621131953128

针对各个分支事务添加@transactional:

image-20240621132234331

1.2.3 测试

我们加入手机到购物车,然后修改手机库存stock=0下单之后trade-service会提示:

image-20240621134914617

1.3 XA使用总结

image-20240621140206477

1.4 XA优缺点

  • XA模式的优点是什么?

    • 事务的强一致性,满足ACID原则【第一阶段只完成不提交,只有第二阶段才告知一起回滚,还是一起提交】

    • 常用数据库都支持,实现简单,并且没有代码侵入【比较好理解,而且比较规整】

  • XA模式的缺点是什么?

    • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差【第一阶段只能等第二阶段指令,阻塞时间长】

    • 依赖关系型数据库实现事务【关系型数据库】

1.5 XA模式和AT模式对比

XA模式【模式强一致,一步一步来】 AT模式【模式最终一致,可以第二阶段回退】
第一阶段 只完成不提交(锁定资源,阻塞) 直接提交(不锁定资源)
第二阶段 数据库机制完成回滚 数据快照完成回滚(第一阶段执行业务之前生成快照)
性能 低(只有第二阶段才决定事务提交/回退) 高(第一阶段就提交,第二阶段可以恢复)

2.AT模式[各自提交,有问题快照恢复]

分阶段提交的事务模型,不过弥补了XA模型中资源锁定周期过长的缺陷(一直阻塞等到第二阶段TC告知RM才可以进行操作)

==①你可以自己提交,然后提交的时候搞个快照(备份),不用锁定资源②如果都成功就删除快照(备份),不成功就用快照(备份)恢复==

2.1 基本概念

主要分为==两个阶段==提交:

一阶段的工作:

  1. TM发起并注册全局事务到TC
  2. TM调用分支事务
  3. 分支事务准备执行业务SQL
  4. RM拦截业务SQL,根据where条件查询原始数据,形成快照。【在执行业务sql之前生成快照
1
2
3
{
"id": 1, "money": 100
}
  1. RM执行业务SQL,提交本地事务,释放数据库锁。此时 money = 90【我已经完成了自己的任务,并且提交了】
  2. RM报告本地事务状态给TC

二阶段的工作:

  1. TM通知TC事务结束【ok了,你判断一下吧】
  2. TC检查分支事务状态【如果都成功删除快照,如果有失败就用快照恢复数据库回滚】
    1. 如果都成功,则立即删除快照
    2. 如果有分支事务失败,需要回滚。读取快照数据({“id”: 1, “money”: 100}),将快照恢复到数据库。此时数据库再次恢复为100

image-20240621151403207

流程图如下:

image-20240621153523017

2.2 具体实现操作

2.2.1 yml配置

类似于XA,就是将属性改为AT

image-20240621151209260

2.2.2 修改具体业务

类似与XA,就是全局事务位置加注解@GlobalTransanctional,分支事务位置加注解@Transanctional

2.2.3 添加快照undo表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 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`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) 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(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';

2.2.4 测试

类似于XA测试,只不过多了快照数据进入到undo表

2.3 AT使用总结

1.yml添加配置

2.业务添加注解@GlobalTransanctional即可

3.添加快照表【比XA模式就多一个这个】

2.4 AT优缺点

  • XA模式的优点是什么?

    • 第一阶段就直接提交了,性能较好【后续如果需要就使用快照恢复】
  • XA模式的缺点是什么?

    • 第一阶段就提交,在第二阶段完成的极小时间段内可能出现数据不一致【用空间换时间】—99%没问题,极端情况下【特别是多线程并发访问AT模式的分布式事务时,有可能出现脏写问题(丢失一次更新)】

2.5 AT模式—脏写问题

这种模式在大多数情况下(99%)并不会有什么问题,不过在极端情况下,特别是多线程并发访问AT模式的分布式事务时,有可能出现脏写问题,如图:

image-20241008215009702

  • 解决思路:引入全局锁【在释放DB锁之前,先拿到全局锁】,避免同一时刻有另外一个事务来操作当前数据。

全局锁(TC管理):记录这行数据,其他事务可以CRUD】

DB锁(数据库管理):锁住这行数据,其他事务不可以CRUD】

image-20241008215937381

就是在原来基础上,增加获取全局锁部分[记录当前操作这行数据的事务];其他的事务获取全局锁(失败重试30次,间隔10ms一次)

2.6 XA模式和AT模式对比

XA模式【模式强一致,第二阶段统一处理】 AT模式【模式最终一致,可以第二阶段回退】
第一阶段 只完成不提交(锁定资源,阻塞) 直接提交(不锁定资源)
第二阶段 数据库机制完成回滚 数据快照完成回滚(第一阶段执行业务之前生成快照)
性能 低(只有第二阶段才决定事务提交/回退) 高(第一阶段就提交,第二阶段可以恢复)

3.TCC模式[各自提交,有问题人工恢复]

TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:

  • try:检测和预留资源;
  • confirm:完成资源操作业务;要求 try 成功 confirm 一定要能成功。
  • cancel:释放预留资源,【try的反向操作】

3.1 流程分析[举例]

例子:一个扣减用户余额的业务。假设账户A原来余额是100,需要余额扣减30元。

  • 阶段一(try):检查余额是否充足,如果充足则冻结金额增加30元,可用余额扣减30

image-20241009090835461

  • 阶段二(Confrim):假设要提交,之前可用金额已经扣减,并且转移到冻结金额。因此可用金额不变,直接冻结金额-30即可:

image-20241009090948444

  • 阶段三(Cancel):如果要回滚,则释放之前冻结的金额(冻结金额-30,可用金额+30)

image-20241009091156001

3.2 事务悬挂和空回滚

假如一个分布式事务中包含两个分支事务,try阶段,一个分支成功执行,另一个分支事务阻塞

img

如果阻塞时间太长,可能导致全局事务超时而触发三阶段的cancel操作。两个分支事务都会执行cancel操作:

image-20241009091343545

其中一个分支是未执行try操作的,直接执行了cancel操作,反而会导致数据错误。因此,这种情况下,尽管cancel方法要执行,但其中不能做任何回滚操作,这就是空回滚【一个分支事务没执行try操作,但要被牵连执行cancel操作,需要执行cancel操作但是不能做任何回滚操作(不应该回滚)】

image-20241009091922827

对于整个空回滚的分支事务,将来阻塞结束try方法依然会执行。但是整个全局事务其实已经结束了,因此永远不会再有confirm或cancel,也就是说这个事务执行了一半,处于悬挂状态【阻塞结束,try执行,但是整体全局事务已经结束,无后续】

3.3 TCC使用总结

CC的优点是什么?

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

TCC的缺点是什么?

  • 有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦【人工恢复,需要代码入侵】
  • 软状态,事务是最终一致【不是强一致性,BASE理论】
  • 需要考虑Confirm和Cancel的失败情况,做好幂等处理、事务悬挂和空回滚处理

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. ==1.分布式事务产生原因==
  2. 2. ==2.CAP定理==
    1. 2.1. 2.1 一致性
    2. 2.2. 2.2 可用性
    3. 2.3. 2.3 分区容错性
    4. 2.4. 2.4 三者矛盾(P一定有)
    5. 2.5. 2.5 解决三者矛盾(BASE理论)
  3. 3. —————————————
  4. 4. ==解决方案(中间人-事务协调者)==
  5. 5. 1.Seata
    1. 5.1. 1.1 Seata架构
    2. 5.2. 1.2 代码实现思路(两个方面)
  6. 6. —————————————
  7. 7. ==Seata具体操作(分两个部分)==
  8. 8. 1.TC部署
    1. 8.1. 1.1 准备数据库表
    2. 8.2. 1.2 准备配置文件
    3. 8.3. 1.3 Docker部署
  9. 9. 2.微服务集成Seata
    1. 9.1. 2.1 引入依赖
    2. 9.2. 2.2 添加配置(统一配置到nacos)
      1. 9.2.1. 2.2.1 配置公共配置
      2. 9.2.2. 2.2.2 分支事务新建bootstrap.yml文件
      3. 9.2.3. 2.2.3 分支事务调整application.yml文件
    3. 9.3. 2.3 添加数据库保存快照
    4. 9.4. 2.4 修改具体业务
  10. 10. 3.实现步骤
  11. 11. ==Seate四种底层原理-[四种]==
  12. 12. 代码使用思路
  13. 13. 1.XA模式[统一控制,统一提交]
    1. 13.1. 1.1 基本概念
    2. 13.2. 1.2 具体实现操作
      1. 13.2.1. 1.2.1 yml配置
      2. 13.2.2. 1.2.2 修改具体业务
      3. 13.2.3. 1.2.3 测试
    3. 13.3. 1.3 XA使用总结
    4. 13.4. 1.4 XA优缺点
    5. 13.5. 1.5 XA模式和AT模式对比
  14. 14. 2.AT模式[各自提交,有问题快照恢复]
    1. 14.1. 2.1 基本概念
    2. 14.2. 2.2 具体实现操作
      1. 14.2.1. 2.2.1 yml配置
      2. 14.2.2. 2.2.2 修改具体业务
      3. 14.2.3. 2.2.3 添加快照undo表
      4. 14.2.4. 2.2.4 测试
    3. 14.3. 2.3 AT使用总结
    4. 14.4. 2.4 AT优缺点
    5. 14.5. 2.5 AT模式—脏写问题
    6. 14.6. 2.6 XA模式和AT模式对比
  15. 15. 3.TCC模式[各自提交,有问题人工恢复]
    1. 15.1. 3.1 流程分析[举例]
    2. 15.2. 3.2 事务悬挂和空回滚
    3. 15.3. 3.3 TCC使用总结
,