zoukankan      html  css  js  c++  java
  • Spring Cloud Alibaba学习08Seata基本使用

    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+"";
        }
    }
  • 相关阅读:
    类加载器
    会话机制
    数据库读写分离
    代码优化工具
    杀毒软件框架设计
    树状结构
    spring依赖注入
    never stop believe yourself
    利用callKit实现电话防骚扰
    mac上使用使用rz,sz命令
  • 原文地址:https://www.cnblogs.com/asenyang/p/15547238.html
Copyright © 2011-2022 走看看