Seata

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表中会添加一条记录事务记录,接口返回后,事务被提交,这条记录会被清除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值