11、若依分布式事务

本文详细介绍了Seata分布式事务的使用,包括其基本概念、下载方式、配置步骤以及测试验证。通过创建数据库和表、引入依赖、配置服务、集成Nacos、实现测试用例等步骤,展示了Seata在分布式事务处理中的应用。同时,文章还涉及了分布式事务的ACID特性以及Seata的AT、TCC、Saga和XA四种事务模式。
摘要由CSDN通过智能技术生成

数据库事务的基本概念(ACID)

原子性(Atomicity):操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。要么执行,要么不执行

一致性(Consistency):事务的执行使数据从一个状态转换为另一个状态,数据库的完整性约束没有被破坏。能量守恒,总量不变

隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。信息彼此独立,互不干扰

持久性(Durability):当事务正确完成后,它对于数据的改变是永久性的。不会轻易丢失

一、基本介绍

  • 什么是分布式事务

​ 指一次大的操作由不同的小操作组成的,这些小的操作分布在不同的服务器上,分布式事务需要保证这些小操作要么全部成功,要么全部失败。从本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

  • 为什么要使用分布式事务

​ 在微服务独立数据源的思想,每一个微服务都有一个或者多个数据源,虽然单机单库事务已经非常成熟,但是由于网路延迟和不可靠的客观因素,分布式事务到现在也还没有成熟的方案,对于中大型网站,特别是涉及到交易的网站,一旦将服务拆分微服务,分布式事务一定是绕不开的一个组件,通常解决分布式事务问题。

  • seata 分布式事务

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

Seata目标打造一站式的分布事务的解决方案,最终会提供四种事务模式:

AT 模式:参见(《Seata AT 模式》 (opens new window))文档

TCC 模式:参见(《Seata TCC 模式》 (opens new window))文档

Saga 模式:参见(《SEATA Saga 模式》 (opens new window))文档

XA 模式:正在开发中… 目前使用的流行度情况是:AT > TCC > Saga。因此,我们在学习Seata的时候,可以花更多精力在AT模式上,最好搞懂背后的实现原理,毕竟分布式事务涉及到数据的正确性,出问题需要快速排查定位并解决。

二、下载方式

  • Windows平台安装包下载

可以从https://github.com/seata/seata/releases下载seata-server-$version.zip包。

Windows下载解压后(.zip)

直接点击bin/seata-server.bat就可以了。(我使用的是1.4.0版本)

img

提示

如果觉得官网下载慢,可以使用我分享的网盘地址: 百度网盘 请输入提取码 提取码: vneh

三、如何使用

1、创建相关测试数据库和表。

# 订单数据库信息 seata_order
DROP DATABASE IF EXISTS seata_order;
CREATE DATABASE seata_order;

DROP TABLE IF EXISTS seata_order.p_order;
CREATE TABLE seata_order.p_order
(
    id               INT(11) NOT NULL AUTO_INCREMENT,
    user_id          INT(11) DEFAULT NULL,
    product_id       INT(11) DEFAULT NULL,
    amount           INT(11) DEFAULT NULL,
    total_price      DOUBLE       DEFAULT NULL,
    status           VARCHAR(100) DEFAULT NULL,
    add_time         DATETIME     DEFAULT CURRENT_TIMESTAMP,
    last_update_time DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4;

DROP TABLE IF EXISTS seata_order.undo_log;
CREATE TABLE seata_order.undo_log
(
    id            BIGINT(20) NOT NULL AUTO_INCREMENT,
    branch_id     BIGINT(20) NOT NULL,
    xid           VARCHAR(100) NOT NULL,
    context       VARCHAR(128) NOT NULL,
    rollback_info LONGBLOB     NOT NULL,
    log_status    INT(11) NOT NULL,
    log_created   DATETIME     NOT NULL,
    log_modified  DATETIME     NOT NULL,
    PRIMARY KEY (id),
    UNIQUE KEY ux_undo_log (xid, branch_id)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4;
  
# 产品数据库信息 seata_product
DROP DATABASE IF EXISTS seata_product;
CREATE DATABASE seata_product;

DROP TABLE IF EXISTS seata_product.product;
CREATE TABLE seata_product.product
(
    id               INT(11) NOT NULL AUTO_INCREMENT,
    price            DOUBLE   DEFAULT NULL,
    stock            INT(11) DEFAULT NULL,
    last_update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4;

DROP TABLE IF EXISTS seata_product.undo_log;
CREATE TABLE seata_product.undo_log
(
    id            BIGINT(20) NOT NULL AUTO_INCREMENT,
    branch_id     BIGINT(20) NOT NULL,
    xid           VARCHAR(100) NOT NULL,
    context       VARCHAR(128) NOT NULL,
    rollback_info LONGBLOB     NOT NULL,
    log_status    INT(11) NOT NULL,
    log_created   DATETIME     NOT NULL,
    log_modified  DATETIME     NOT NULL,
    PRIMARY KEY (id),
    UNIQUE KEY ux_undo_log (xid, branch_id)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4;

INSERT INTO seata_product.product (id, price, stock)
VALUES (1, 10, 20);


# 账户数据库信息 seata_account
DROP DATABASE IF EXISTS seata_account;
CREATE DATABASE seata_account;

DROP TABLE IF EXISTS seata_account.account;
CREATE TABLE seata_account.account
(
    id               INT(11) NOT NULL AUTO_INCREMENT,
    balance          DOUBLE   DEFAULT NULL,
    last_update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4;

DROP TABLE IF EXISTS seata_account.undo_log;
CREATE TABLE seata_account.undo_log
(
    id            BIGINT(20) NOT NULL AUTO_INCREMENT,
    branch_id     BIGINT(20) NOT NULL,
    xid           VARCHAR(100) NOT NULL,
    context       VARCHAR(128) NOT NULL,
    rollback_info LONGBLOB     NOT NULL,
    log_status    INT(11) NOT NULL,
    log_created   DATETIME     NOT NULL,
    log_modified  DATETIME     NOT NULL,
    PRIMARY KEY (id),
    UNIQUE KEY ux_undo_log (xid, branch_id)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4;
INSERT INTO seata_account.account (id, balance)
VALUES (1, 50);

​ 其中,每个库中的undo_log表,是Seata AT模式必须创建的表,主要用于分支事务的回滚。 另外,考虑到测试方便,我们插入了一条id = 1account记录,和一条id = 1product记录。

2、创建新的test模块

具体参见新建子模块

3、引入依赖

        <!-- RuoYi Common Datasource -->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-common-datasource</artifactId>
        </dependency>

        <!-- RuoYi Common Seata -->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-common-seata</artifactId>
        </dependency>

4、服务配置文件

# spring配置
spring:
  redis:
    host: localhost
    port: 6379
    password:
  datasource:
    druid:
      stat-view-servlet:
        enabled: true
        loginUsername: admin
        loginPassword: 123456
    dynamic:
      druid:
        initial-size: 5
        min-idle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall,slf4j
        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
      datasource:
        # 主库数据源
        master:
          username: root
          password: admin0625
          url: jdbc:mysql://localhost:3306/ry-cloud?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
          driver-class-name: com.mysql.cj.jdbc.Driver
        # seata_order数据源
        order:
          username: root
          password: admin0625
          url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
          driver-class-name: com.mysql.cj.jdbc.Driver
        # seata_account数据源
        account:
          username: root
          password: admin0625
          url: jdbc:mysql://localhost:3306/seata_account?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
          driver-class-name: com.mysql.cj.jdbc.Driver
        # seata_product数据源
        product:
          username: root
          password: admin0625
          url: jdbc:mysql://localhost:3306/seata_product?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
          driver-class-name: com.mysql.cj.jdbc.Driver
      #开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭
      seata: true    


# seata配置
seata:
  enabled: true
  # Seata 应用编号,默认为 ${spring.application.name}
  application-id: ${
   spring.application.name}
  # Seata 事务组编号,用于 TC 集群名
  tx-service-group: ${
   spring.application.name}-group
  # 关闭自动代理
  enable-auto-data-source-proxy: false
  # 服务配置项
  service:
    # 虚拟组和分组的映射
    vgroup-mapping:
      ruoyi-system-group: default
  config:
    type: nacos
    nacos:
      serverAddr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace:
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      namespace:

# mybatis配置
mybatis:
  # 搜索指定包别名
  typeAliasesPackage: com.ruoyi.test.domain
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath:mapper/**/*.xml

# swagger配置
swagger:
  title: 测试模块接口文档
  license: Powered By ruoyi
  licenseUrl: https://ruoyi.vip

提示

​ 注意,一定要设置spring.datasource.dynamic.seata配置项为true,开启对Seata的集成,否则会导致Seata全局事务回滚失败。

5、示例代码

Domain

Account.java

package com.ruoyi.test.domain;

import java.util.Date;

public class Account
{
   
    private Long id;

    /**
     * 余额
     */
    private Double balance;

    private Date lastUpdateTime;

    public Long getId()
    {
   
        return id;
    }

    public void setId(Long id)
    {
   
        this.id = id;
    }

    public Double getBalance()
    {
   
        return balance;
    }

    public void setBalance(Double balance)
    {
   
        this.balance = balance;
    }

    public Date getLastUpdateTime()
    {
   
        return lastUpdateTime;
    }

    public void setLastUpdateTime(Date lastUpdateTime)
    {
   
        this.lastUpdateTime = lastUpdateTime;
    }
}

Order.java

package com.ruoyi.test.domain;
public class Order
{
   
    private Integer id;

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 商品ID
     */
    private Long productId;

    /**
     * 订单状态
     */
    private int status;

    /**
     * 数量
     */
    private Integer amount;

    /**
     * 总金额
     */
    private Double totalPrice;

    public Order()
    {
   
    }

    public Order(Long userId, Long productId, int status, Integer amount)
    {
   
        this.userId = userId;
        this.productId = productId;
        this.status = status;
        this.amount = amount;
    }

    public Integer getId()
    {
   
        return id;
    }

    public void setId(Integer id)
    {
   
        this.id = id;
    }

    public Long getUserId()
    {
   
        return userId;
    }

    public void setUserId(Long userId)
    {
   
        this.userId = userId;
    }

    public Long getProductId()
    {
   
        return productId;
    }

    public void setProductId(Long productId)
    {
   
        this.productId = productId;
    }

    public int getStatus()
    {
   
        return status;
    }

    public void setStatus(int status)
    {
   
        this.status = status;
    }

    public Integer getAmount()
    {
   
        return amount;
    }

    public void setAmount(Integer amount)
    {
   
        this.amount = amount;
    }

    public Double getTotalPrice()
    {
   
        return totalPrice;
    }

    public void setTotalPrice(Double totalPrice)
    {
   
        this.totalPrice = totalPrice;
    }
}

Product.java

package com.ruoyi.test.domain;

import java.util.Date;

public class Product
{
   

    private Integer id;
    /**
     * 价格
     */
    private Double price;
    /**
     * 库存
     */
    private Integer stock;

    private Date lastUpdateTime;

    public Integer getId()
    {
   
        return id;
    }

    public void setId(Integer id)
    {
   
        this.id = id;
    }

    public Double getPrice()
    {
   
        return price;
    }

    public void setPrice(Double price)
    {
   
        this.price = price;
    }

    public Integer getStock()
    {
   
        return stock;
    }

    public void setStock
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值