Seata
1 事务
数据库事务(transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。【引自百度百科】
事务应满足4个特性,原子性(atomicity)、隔离性(consistency)、一致性(isolation)和持久性(durability)。
1.1 原子性
事务的操作是一个整体,要么全部执行,要么全部不执行。(如执行一部分操作后出现异常导致后续操作无法执行,已执行的操作要回滚。)
1.2 隔离性
事务与事务之间的操作是相互隔离,互不影响。
隔离级别
对于数据库来说又分为4个隔离级别,分别是读未提交(Read uncommitted),读已提交(Read committed),可重复读(Repeatable read),串行化(Serializable)。
-
读未提交 读未提交可以读取对其它事务未提交的数据,可能引发脏读、不可重复读和幻读。(数据不一致是脏读,数据集不一致是幻读)
- 脏读 如果事务A修改了某一数据,随后事务B读取了该数据,然后事务A执行了回滚,那么数据B读取的数据就是脏数据,这就是脏读。
- 不可重复读 如果事务A读取了某一个数据,随后事务B修改了该数据并提交,然后事务A再读取该数据并发现数据和之前读取的不一致,这就是不可重复读。
- 幻读 如果事务A读取某一范围的数据,得到N条数据,随后事务B增加了M条数据,之后事务A再次读取该数据的时候发现读取到了N+M条数据,即在同一事务中多次读取到的数据集不一致,这就是幻读。
-
读已提交 读已提交就是只能读取其它事务已经提交的结果。可以解决脏读问题,无法解决不可重复读和幻读问题。
-
可重复读 锁定该事务期间引用的所有行,保证在一个事务内,多次读取同一个数据是相同的。可以解决脏读和不可重复读问题,无法解决幻读问题。
-
串行化 读加共享锁(读锁),写加排它锁,保证涉及写相关事务串行执行。可解决脏读、不可重复读和幻读,但是效率低。
1.3 一致性
ensuring the consistency is the responsibility of user, not DBMS.
DBMS assumes that consistency holds for each transaction
用户应该确保一致性,数据库假定事务是一致性的。
一致性应该由用户的业务决定的。比如在转账操作时,转账方和接受方的资金流转应该是一致的,不应该是转账方接收钱而接收方未收到钱(如果业务有这样的要求另说),一致性保证事务中的数据由一个一致状态流转到另一个一致状态。
例子:
1. A有1000元,B有0元,A给B转100元且成功,A剩900元,B剩100元,这就是两个一致性状态的转变。
2. A有0元,购买商品100元,支付失败,生成订单,订单为待支付,这也是两个一致性状态的转变。
3. A有0元,购买商品100元,支付失败,生成订单,订单状态已完成,这就是非一致性的。
注:一致性是由用户(业务)决定。
1.4 持久性
事务的操作结果要持久化,能永久的存储,非事务期间发生故障、错误,不影响持久化的结果。
2 Seata事务模式
Seata 是开源的分布式事务框架。它提供了AT、TCC、SAGA 和 XA 事务模式。
2.1 AT
2.1.1 机制
一个事务分为两阶段提交。
- 一阶段 业务数据和回滚日志在本地事务提交,释放本地资源和连接资源
- 二阶段
- 提交异步化
- 回滚一阶段回滚日志进行反向补偿
2.1.2 写隔离
一阶段提交事务前需要拿到全局锁,全局锁获取失败回滚本地事务,释放本地锁。
假设事务A、B操作同一资源。
-
正常流程:
一阶段事务A提交:事务A执行,获取本地锁,事务A准备提交本地事务,获取到全局锁,本地事务提交,释放本地锁。 一阶段事务B提交:事务B开始执行,获取本地锁,事务B尝试获取全局锁,事务A未释放全局锁,重试。
二阶段事务A提交:事务A提交全局事务,释放全局锁。 一阶段事务B提交:事务B获取到事务A全局锁,事务B提交本地事务。
二阶段事务B提交:事务B提交全局事务,释放全局锁。
-
异常
一阶段事务A提交:事务A执行,获取本地锁,事务A准备提交本地事务,获取到全局锁,本地事务提交,释放本地锁。 一阶段事务B提交:事务B开始执行,获取本地锁,事务B尝试获取全局锁,事务A未释放全局锁,重试。
二阶段事务A提交:事务A全局回滚,尝试获取本地锁,等待事务B释放本地锁,未释放全局锁。 一阶段事务B提交:事务B尝试获取全局锁失败,事务B回滚,事务B释放本地锁。
二阶段事务A提交:事务A全局回滚,获取本地锁成功,回滚一阶段回滚日志,释放全局锁。
2.1.3 Seata 术语
TC(Transaction Coordinate)-事务协调者。
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM(Transaction Manager)事务管理器
定义全局事务范围:开始全局事务、提交或回滚全局事务。
RM(Resource Manager)资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务的提交或回滚。
3 Seata 服务端
3.1 下载安装
下载地址。
3.2 高可用部署
Seata的高可用依赖注册中心、配置中心和数据库来实现
3.2.1 配置注册中心、配置中心
使用Nacos作为注册中心和配置中心,启动Nacos。并添加SEATA命名空间。
打开config/registry.conf
文件,修改下面的配置:
registry {
# 注册中心修改为nacos
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = "SEATA"
cluster = "default"
username = ""
password = ""
}
}
config {
# 配置中心修改为nacos
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = "SEATA"
group = "SEATA_GROUP"
username = ""
password = ""
dataId = "seataServer.properties"
}
}
3.2.2 配置数据库
在Mysql中新建一个库,名称自定义即可,这里以seata为例。
在db下载Mysql表,并导入Mysql库中执行。
-- -------------------------------- 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_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);
3.2.3 配置文件
因为使用Nacos作为配置中心,所以需要把一些配置信息导入nacos。
完整配置参考Seata配置参考config.txt
根据配置中心配置的dataId在配置中心创建创建配置文件(seataServer.properties),并导入下面配置(下面是部分依赖,完整依赖参考完整依赖):
#Transaction storage configuration, only for the server. The file, DB, and redis configuration values are optional.
store.mode=db
#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=1234
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
3.2.4 启动服务
sh ./bin/seata-server.sh -h 127.0.0.1 -p 8091
参数:
-h 指定在注册中心注册的 IP,默认本机IP
-p 服务启动端口,默认8091
程序启动后,在nacos-server下服务管理-服务列表中可以看到seata-server。
由于配置了连接DB,所以启动成功且配置正确的情况下可以看到下面的日志:
INFO --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
启动多个seata-server实现高可用。
4 Seata 客户端
4.1 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>seata-demo</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-demo-client</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
</dependencies>
</project>
4.2 启动类
@SpringBootApplication
public class SeataDemoClientApp {
public static void main(String[] args) {
SpringApplication.run(SeataDemoClientApp.class, args);
}
}
4.3 配置
server:
port: 9000
spring:
application:
name: seata-demo-client
seata:
# 事务分组是seata的资源逻辑,类似于服务实例
tx-service-group: my_tx_group
# 程序去配置中心去寻找service.vgroupMapping.事务分组配置项,取得配置项的值就是TC集群的名称。拿到集群名称程序通过一定的前后缀+集群名称去构造服务名,各配置中心的服务名实现不同。拿到服务名去相应的注册中心去拉取相应服务名的服务列表
service:
vgroup-mapping:
my_tx_group: default
# seata-server 注册中心
registry:
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
namespace: SEATA
group: SEATA_GROUP
# seata-server 配置中心
config:
nacos:
server-addr: 127.0.0.1:8848
namespace: SEATA
group: SEATA_GROUP
data-id: seataServer.properties
4.4 添加测试接口
@RestController
@RequestMapping("/test")
public class TestController {
@GlobalTransactional
@GetMapping("/transaction")
public boolean transaction() throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
return true;
}
}
4.5 项目测试
启动项目,访问添加的接口,接口返回前会在数据库global_table
表中会添加一条记录事务记录,接口返回后,事务被提交,这条记录会被清除。