Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。

Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

官网地址:Seata

Seata术语:

TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

 

AT 模式
1.前提
基于支持本地 ACID 事务的关系型数据库。
Java 应用,通过 JDBC 访问数据库。


2.整体机制
两阶段提交协议的演变:

一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。

二阶段:提交异步化,非常快速地完成。回滚通过一阶段的回滚日志进行反向补偿。

 

3.流程:

3.1 全局事务开启:TM向TC发起全局事务,TC生成全局事务id(xid),全局事务id可以在调用链中进行传播。

3.2 一阶段提交:

过程:

对于如下sql语句:

update product set name = 'GTS' where name = 'TXC';

(1)解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = 'TXC')等相关的信息。

(2)查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。

(3)执行业务 SQL:更新这条记录的 name 为 'GTS'。

(4)查询后镜像:根据前镜像的结果,通过 主键 定位数据。

(5)插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到 UNDO_LOG 表中。

(6)提交前,向 TC 注册分支:申请 product 表中,主键值等于 1 的记录的 全局锁

(7)本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。

(8)将本地事务提交的结果上报给 TC。

 

3.3.1 二阶段提交:

收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。

异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。

 

3.3.2 二阶段回滚:

收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。

通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。

数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理。

根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:

update product set name = 'TXC' where id = 1;

提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。

 

搭建Seata Server

下载地址:Tags · seata/seata (github.com)

配置conf/file.conf

 

 

 

创建seata数据库:

脚本地址:seata/script/server/db at develop · seata/seata (github.com)

创建seata数据库,并执行如下(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_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

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

-- 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),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

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);

执行完毕后,见如下四个表:

 

 

 

Seata使用Nacos注册中心,配置文件为conf/registry.conf:

 

 

 

 

 

Seata的配置,参考:Seata 参数配置

在Nacos上创建一个配置文件:

 

 

 

启动Seata,在bin目录运行如下命令:

cd bin
seata-server.bat -h 127.0.0.1 -p 8098

启动后,可以在Nacos控制台看到seata服务已经注册成功。

 

 

在业务数据库创建UNDO_LOG表,脚本见github:seata/mysql.sql at develop · seata/seata (github.com)

-- 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 = utf8 COMMENT ='AT transaction mode undo table';

 

代码示例:

1、在cloud-goods中添加pom依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-seata</artifactId>
    <version>2.2.0.RELEASE</version>
    <exclusions>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.4.2</version>
</dependency>    

 

2、添加配置:

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/goods?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: false
seata:
  enabled: true
  tx-service-group: fengmi_tx_group
  enable-auto-data-source-proxy: true
  service:
    vgroupMapping:
      my_test_tx_group: default
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: DEFAULT_GROUP
      namespace: public
      username: nacos
      password: nacos
      data-id: seataServer.properties
  registry: #从Nacos中发现seata server服务
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      namespace: public
      group: DEFAULT_GROUP
      username: nacos
      password: nacos

 

在代码中可以使用@GlobalTransitional注解来实现分布式事务。

package com.yas.service;

import com.yas.Goods;
import com.yas.mapper.GoodsMapper;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class GoodsService implements IGoodsService {
    @Autowired
    GoodsMapper goodsMapper;

    @Override
    @GlobalTransactional
    public String buy(){
        Integer result = goodsMapper.insert(new Goods("苹果",3999));
        System.out.println(result);

        int x = 1 / 0;
        System.out.println(x);
        return result+"";
    }
}

 

posted on 2021-11-13 08:16  Sempron2800+  阅读(259)  评论(0编辑  收藏  举报