
分布式事务
在分布式系统中,如果一个业务需要多个服务合作完成,而且每一个服务都有事务,多个事务必须同时成功或失败,这样的事务就是分布式事务。其中的每个服务的事务就是一个分支事务。整个业务称为全局事务。
安装 seata
docker 安装 seata
配置参考 https://blog.csdn.net/weixin_42633509/article/details/145204282
添加数据库
-- 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 镜像
docker pull seataio/seata-server:2.0.0
启动临时容器
docker run -d -p 8091:8091 -p 7091:7091 --name seata-server seataio/seata-server:2.0.0
拷贝临时容器的配置至宿主机
docker cp seata-server:/seata-server/resources/. F:/docker/data/seata/config/
配置 application.yml 文件信息使用
# 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 网络 查看配置
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模式:
seata:
data-source-proxy-mode: XA # 开启数据源代理的XA模式
在需要使用分布式事务的方法上面使用 @GlobalTransactional
@GlobalTransactional
public Long createOrder(OrderFormDTO orderFormDTO) {}
实现AT模式
简述AT模式与XA模式最大的区别是什么?
- XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
- XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
- XA模式强一致;AT模式最终一致
添加 数据表到每个 微服务中去
-- 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 统一配置
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 能够有效地避免脏读、不可重复读等并发问题,同时保证了数据的一致性和完整性。
异常问题
- 以下 可能是 jdk17 的问题 通过添加
解决如下问题--add-opens=java.base/java.lang=ALL-UNNAMED
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
- 集成seata报错:can not register RM,err:can not connect to services-server.
通过启动时docker 添加 对应的 -e SEATA_IP=192.168.5.18 -e SEATA_PORT=8091