分布式事务-Seata简单实践

当涉及多个不同的计算机或系统之间的操作时,分布式事务是确保这些操作在一组相关的事务中保持一致性和隔离性的机制。在传统的单机事务中,可以使用数据库的事务来确保操作的一致性和隔离性。然而,在分布式系统中,数据分布在多个节点上,可能涉及网络问题、节点故障等,使得保持一致性变得更加复杂。分布式事务需要解决以下挑战:

  1. 原子性(Atomicity):分布式事务要么全部成功,要么全部失败,不能只完成部分操作。这意味着如果一个操作失败,所有涉及的操作都必须回滚。
  2. 一致性(Consistency):事务开始前和结束后,系统必须保持一致状态。即使在分布式环境中,事务也必须确保数据不会进入不一致状态。
  3. 隔离性(Isolation):在事务执行过程中,各个事务之间应该相互隔离,以避免并发执行导致的问题,例如脏读、不可重复读和幻读。
  4. 持久性(Durability):一旦事务提交,其结果应该持久保存,即使发生系统故障也不应丢失。

分布式事务模式

  1. XA模式: XA是一种标准的分布式事务协议,它在两阶段提交(2PC)的基础上实现了分布式事务的ACID属性。XA涉及两个角色:事务管理器(TM)和资源管理器(RM)。在第一阶段,TM要求所有RM预提交事务。在第二阶段,TM发送全局提交或回滚命令,RM执行对应操作。虽然XA确保了一致性,但它的缺点是可能会导致阻塞,尤其是在网络故障或参与者故障的情况下。
  2. AT模式(Automatic Transaction): AT模式是一种基于数据库的分布式事务解决方案,通过自动化事务的提交和回滚来实现一致性。每个参与者节点在执行事务之前会先尝试执行操作,然后记录操作产生的影响。如果所有参与者的操作都成功,那么事务就会被自动提交。如果有任何一个参与者的操作失败,整个事务会被回滚。AT模式避免了2PC的阻塞问题,但可能导致部分失败问题。
  3. TCC模式(Try-Confirm-Cancel): TCC模式通过将一个大事务分解为三个阶段来实现分布式事务:尝试(Try)、确认(Confirm)和取消(Cancel)。在尝试阶段,所有参与者都会尝试执行操作。在确认阶段,如果所有参与者的操作都成功,就会进行确认操作。如果任何操作失败,就会进行取消操作。TCC模式提供了更细粒度的控制,但需要在代码中显式编写确认和取消逻辑。
  4. Saga模式: Saga模式将一个大事务拆分为一系列小步骤,每个步骤都是一个原子操作。每个步骤都有一个与之关联的补偿操作,用于撤销该步骤的影响。如果一个步骤失败,就会触发其关联的补偿操作,将系统状态回滚到一致状态。Saga模式适用于分布式环境中的长时间事务,但需要开发人员仔细设计补偿逻辑。

Seata

安装配置

  • 下载

    点击链接进去下载:https://seata.io/zh-cn/blog/download.html

    本文的seata版本为1.7.0

  • 配置

    • 参考conf/application.example.yml修改conf/application.yml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      server:
      port: 7091

      spring:
      application:
      name: seata-server

      logging:
      config: classpath:logback-spring.xml
      file:
      path: ${user.home}/logs/seata

      console:
      user:
      username: seata
      password: seata
      seata:
      config:
      # support: nacos, consul, apollo, zk, etcd3
      type: nacos
      nacos:
      server-addr: 127.0.0.1:8848
      namespace:
      group: SEATA_GROUP
      username: nacos
      password: nacos
      context-path:
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key:
      #secret-key:
      data-id: seataServer.properties
      consul:
      registry:
      # support: nacos, eureka, redis, zk, consul, etcd3, sofa
      type: nacos
      nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace:
      cluster: default
      username: nacos
      password: nacos
      context-path:
      store:
      # support: file 、 db 、 redis
      mode: db
      db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true
      user: root
      password: root
      min-conn: 10
      max-conn: 100
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
      distributed-lock-table: distributed_lock
      query-limit: 1000
      max-wait: 5000
      # server:
      # service-port: 8091 #If not configured, the default is '${server.port} + 1000'
      security:
      secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
      tokenValidityInMilliseconds: 1800000
      ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login
    • mysql数据库创建seata库并执行/seata/script/server/db/mysql.sql

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      -- -------------------------------- 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` (`xid`)
      ) 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);
  • 启动

    在seata目录下执行seata-server.sh 文件

    1
    bin/seata-server.sh 

    访问http://localhost:7091/,出现以下画面代表成功账号密码就是在application.yml配置的console.user.username和console.user.password

    image-20230828204423604

    也可以在nacos页面中查看服务是否注册成功

    image-20230828205059592

微服务集成Seata

  • 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>2.2.5.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.7.0</version>
    </dependency>

XA

前提

支持XA 事务的数据库。
Java 应用,通过 JDBC 访问数据库。

整体机制

在 Seata 定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式。

执行阶段:

可回滚:业务 SQL 操作放在 XA 分支中进行,由资源对 XA 协议的支持来保证 可回滚
持久化:XA 分支完成后,执行 XA prepare,同样,由资源对 XA 协议的支持来保证 持久化(即,之后任何意外都不会造成无法回滚的情况)

完成阶段:

分支提交:执行 XA 分支的 commit
分支回滚:执行 XA 分支的 rollback

  • 配置application.yml 使用XA模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    seata:
    data-source-proxy-mode: XA
    registry:
    type: nacos
    nacos:
    application: seata-server
    server-addr: localhost:8848
    namespace: ""
    group: DEFAULT_GROUP
    username: nacos
    password: nacos
    #事务组的名称
    tx-service-group: seata-demo
    service:
    #事务组和cluster的映射关系
    vgroup-mapping:
    #你配置的seata/conf/application.yml的 cluster
    seata-demo: default
  • Seata通过封装数据库的XA接口,实现了分布式事务处理的流程,降低了使用者的复杂度。用户只要在Seata客户端进行简单的开启XA模式的配置,即可成功使用Seata的XA模式。XA模式的性能也较差,只适用于一些对事务一致性要求非常高的场景。

    直接在service对用方法上添加注解@GlobalTransactional,即可生效

    image-20230831183911000

    xa模式下的日志

    image-20230831182630307

AT

前提
  • 基于支持本地 ACID 事务的关系型数据库。
  • Java 应用,通过 JDBC 访问数据库。
整体机制

两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。
  • mysql数据新增undo_log表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    CREATE TABLE `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=utf8;
  • data-source-proxy-mode修改成AT模式

    1
    2
    seata:
    data-source-proxy-mode: AT
  • AT下的日志

    image-20230901101219676


分布式事务-Seata简单实践
https://cason.work/2023/08/28/分布式事务-Seata简单实践/
作者
Cason Mo
发布于
2023年8月28日
许可协议