分布式事务

在分布式系统中,如果一个业务需要多个服务合作完成,而且每一个服务都有事务,多个事务必须同时成功或失败,这样的事务就是分布式事务。其中的每个服务的事务就是一个分支事务。整个业务称为全局事务。

安装 seata

docker 安装 seata

配置参考 https://blog.csdn.net/weixin_42633509/article/details/145204282
添加数据库

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
-- 1. 执行语句创建名为 seata 的数据库
CREATE DATABASE seata DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;

-- 2.执行脚本完成 Seata 表结构的创建
use seata;

-- https://github.com/seata/seata/blob/1.7.1/script/server/db/mysql.sql
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- 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_status_gmt_modified` (`status` , `gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

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

-- the table to store lock data
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` (`xid`)
) 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);

拉取 docker 镜像

1
docker pull seataio/seata-server:2.0.0

启动临时容器

1
docker run -d -p 8091:8091 -p 7091:7091  --name seata-server seataio/seata-server:2.0.0

拷贝临时容器的配置至宿主机

1
docker cp seata-server:/seata-server/resources/. F:/docker/data/seata/config/

配置 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
124
125
126
127
128
129
130
131
132
133
134
#  Copyright 1999-2019 Seata.io Group.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

server:
port: 7091

spring:
application:
name: seata-server

logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
console:
user:
username: seata #名字和password随意起
password: seata

seata:
config:
# support: nacos 、 consul 、 apollo 、 zk 、 etcd3
type: file
nacos:
server-addr: 127.0.0.1:8848
namespace:
group: DEFAULT_GROUP
username:
password:
context-path:
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key:
#secret-key:
data-id: seataServer.properties
registry:
# support: nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa
type: nacos
nacos:
application: seata-server
server-addr: 172.17.0.5:8848
group: DEFAULT_GROUP
namespace:
cluster: default
username: nacos
password: nacos
context-path:
security:
secretKey: "seata" #key随便起名
tokenValidityInMilliseconds: 1000000000

server:
raft:
group: default
cluster:
snapshot-interval: 600
apply-batch: 32
max-append-bufferSize: 262144
max-replicator-inflight-msgs: 256
disruptor-buffer-size: 16384
election-timeout-ms: 1000
reporter-enabled: false
reporter-initial-delay: 60
serialization: jackson
compressor: none
sync: true # sync log&snapshot to disk
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
enable-parallel-handle-branch: false
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 、 raft
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://172.17.0.3:3306/seata?rewriteBatchedStatements=true
user: mysql
password: password
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
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

启动docker容器, 启动前对容器内部互联docker 网络 查看配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
docker run --name seata-server \
--privileged=true \
--restart=always \
-p 8091:8091 \
-p 7091:7091 \
-e STORE_MODE=db \
-v /mydata/seata/config/application.yml:/seata-server/resources/application.yml \
-d seataio/seata-server:2.0.0

docker run --name seata-server \
--privileged=true \
--restart=always \
-p 8091:8091 \
-p 7091:7091 \
-e STORE_MODE=db \
-e SEATA_IP=192.168.52.131 \
-e SEATA_PORT=8091 \
-v /mydata/seata/config:/seata-server/resources \
-d seataio/seata-server:2.0.0

# windows
docker run --name seata-server --privileged=true --restart=always -p 8091:8091 -p 7091:7091 -e SEATA_IP=192.168.5.18 -e SEATA_PORT=8091 -v F:/docker/data/seata/config/:/seata-server/resources -d seataio/seata-server:2.0.0

至此 seata 启动起来

使用Seata

seata
Seata事务管理中有三个重要的角色:

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

实现XA模式

XA模式的优点是什么?

  • 事务的强一致性,满足ACID原则。
  • 常用数据库都支持,实现简单,并且没有代码侵入
    XA模式的缺点是什么?
  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
  • 依赖关系型数据库实现事务

Seata的starter已经完成了XA模式的自动装配,实现非常简单,步骤如下:
修改application.yml文件(每个参与事务的微服务),开启XA模式:

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

在需要使用分布式事务的方法上面使用 @GlobalTransactional

1
2
@GlobalTransactional
public Long createOrder(OrderFormDTO orderFormDTO) {}

实现AT模式

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

  • XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
  • XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
  • XA模式强一致;AT模式最终一致
    添加 数据表到每个 微服务中去
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    -- 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';
    sql
    修改 对应 application.yaml 或者nacos 统一配置
    1
    2
    seata:
    data-source-proxy-mode: AT # 开启数据源代理的AT模式

Seata 表介绍

在 Seata 中,分布式事务被分成全局事务和分支事务。全局事务是由业务发起方启动的事务,而分支事务则是指全局事务在一个服务内的局部事务。为了协调这些事务,Seata 使用了 AT(Automatic Transaction)、TCC(Try-Confirm-Cancel)、Saga 和 XA 模式等几种不同的事务模式。

branch_table
branch_table 是 Seata 框架中的一个重要组成部分,用于分布式事务管理。Seata 是一种开源的分布式事务解决方案,旨在提供高性能和易用性的同时保证数据的一致性。

  • 分支ID(branch_id):唯一标识一个分支事务。
  • 全局事务ID(xid):关联到全局事务。
  • 分支类型(branch_type):指示该分支事务的类型,比如AT、TCC等。
  • 资源描述(resource_desc):描述了资源的位置或相关信息,例如数据库连接信息。
  • 状态(status):表示分支事务的状态,如正在进行、已提交、已回滚等。
  • 这些信息对于协调分布式事务的提交和回滚至关重要。当一个全局事务需要提交或回滚时,Seata 服务器会根据 branch_table 中的信息来决定如何操作每个分支事务,从而确保整个分布式事务的一致性。

global_table
global_table 主要记录了全局事务相关的元数据,这些信息对于协调和管理分布式事务的生命周期至关重要。

  • Transaction ID (xid): 全局事务的唯一标识符。每个分布式事务都有一个唯一的 xid,它被用来关联该事务下的所有分支事务。
  • Transaction Name: 事务的名称或描述,有时可以用来标识事务的目的或类型。
  • Status: 全局事务的状态,例如初始化、正在进行、提交、回滚等状态。这个字段帮助跟踪事务的进度和结果。
  • Application ID: 发起事务的应用程序ID,有助于识别哪个应用启动了该事务。
  • Transaction Service Group: 服务组名,与配置相关,有助于定位具体的事务协调器。
  • Timeout: 事务的超时时间设置,如果事务在这个时间内没有完成(提交或回滚),则会被认为是超时。
  • Begin Time: 事务开始的时间戳,记录了事务创建的具体时间。
  • Application Data: 可选字段,有时会用来保存一些业务相关的额外信息或者上下文。

lock_table:
lock_table 在 Seata 分布式事务框架中扮演着关键角色,主要用于解决分布式事务中的数据锁定问题。在分布式事务处理过程中,为了保证数据的一致性和隔离性,Seata 需要对涉及的资源进行锁定,以防止并发事务造成的数据不一致问题。

  • RowKey: 这是一个唯一标识符,用来标识被锁定的记录。它通常由表名和主键值组成,确保在整个数据库范围内是唯一的。
  • XID: 全局事务ID,用于关联到具体的全局事务。这帮助 Seata 确定哪个全局事务持有了特定的锁。
  • Transaction ID (transaction_id): 与 XID 相关,有时用于内部引用或当需要直接关联到一个事务管理器时使用。
  • Branch ID (branch_id): 分支事务ID,表示持有该锁的分支事务。这对于分布式事务中的细粒度控制非常重要。
  • Lock Name: 锁的名称或描述,虽然不是所有实现都必需,但它可能用于提供更详细的锁信息或便于管理和调试。

lock_table 的主要功能是记录哪些数据项被哪些事务所锁定,以便其他事务尝试访问这些数据项时可以识别出存在冲突,并采取相应的措施(例如等待当前事务释放锁或者抛出异常)。通过这种方式,Seata 能够有效地避免脏读、不可重复读等并发问题,同时保证了数据的一致性和完整性。

异常问题

  1. 以下 可能是 jdk17 的问题 通过添加
    1
    --add-opens=java.base/java.lang=ALL-UNNAMED
    解决如下问题
    1
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'globalTransactionScanner' defined in class path resource [io/seata/spring/boot/autoconfigure/SeataAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [io.seata.spring.annotation.GlobalTransactionScanner]: Factory method 'globalTransactionScanner' threw exception; nested exception is java.lang.ExceptionInInitializerError
  2. 集成seata报错:can not register RM,err:can not connect to services-server.
    通过启动时docker 添加 对应的 -e SEATA_IP=192.168.5.18 -e SEATA_PORT=8091