Skip to content
🔴🟠🟡🟢🔵🟣🟤⚫⚪

spring-cloud-alibaba-demo

十三、分布式事务解决方案seata

版本说明:

https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明

官方文档*:

https://seata.io/zh-cn/docs/overview/what-is-seata.html

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了** AT、TCC、SAGA 和 XA **事务模式,为用户打造一站式的分布式解决方案。seata推荐 AT 模式

TC (Transaction Coordinator) - 事务协调者(单独部署服务)

维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器

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

常见分布式事务解决方案 1、seata阿里分布式事务框架 2、消息队列 3、saga 4、XA 他们有一个共同点,都是两阶段(2PC)。“两阶段“是指完成整个分布式事务,划分成两个步骤完成。 这四种常见的分布式事务解决方案, 分别对应着分布式事务的四种模式:AT、TCC、Saga、XA

四种分布式事务模式,都有各自的理论基础,分别在不同的时间被提出;每种模式都有它的适用场景,同样每个模式也都诞生有各自的代表产品;而这些代表产品, 可能就是我们常见的(全局事务、基于可靠消息、最大努力通知、TCC) 。

写隔离

  • 一阶段本地事务提交前,需要确保先拿到 全局锁
  • 拿不到 全局锁 ,不能提交本地事务。
  • 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

读隔离

AT模式

TCC模式

AT 模式(参考链接 TBD)基于 支持本地 ACID 事务关系型数据库

  • 一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
  • 二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。
  • 二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。

相应的,TCC 模式,不依赖于底层数据资源的事务支持:

  • 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
  • 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
  • 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。

所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。

国内主要的开源TCC分布式事务框架包括 框架名称 Github地址 star数量

tcc-transaction https://github.com/changmingxie/tcc-transaction

Hmily https://github.com/yu199195/hmily

ByteTCC https://github.com/liuyangming/ByteTCC

EasyTransaction https://github.com/QNJR-GROUP/EasyTransaction

2PC的问题

1.同步阻塞参与者在等待协调者的指令时,其实是在等待其他参与者的响应,在此过程中,参与者是无法进行其他操作的,也就是阻塞了其运行,倘若参与者与协调者之间网络异常导致参与者一直收不到协调者信息,那么会导致参与者一直阻塞下去

2.单点在2PC中,一切请求都来自协调者,所以协调者的地位是至关重要的,如果协调者宕机,那么就会使参与者一直阻塞并一直占用事务资源如果协调者也是分布式,使用选主方式提供服务,那么在一个协调者挂掉后,可以选取另一个协调者继续后续的服务,可以解决单点问题。但是,新协调者无法知道上一个事务的 全部状态信息(例如已等待Prepare响应的时长等) , 所以也无法顺利处理上一个事务

3.数据不一致Commit事务过程中Commit请求/Rollback请求可能因为协调者宕机或协调者与参与者网络问题丢失, 那么就导致了部分参与者没有收到Commit/Rollback请求, 而其他参与者则正常收到执行了Commit/Rollback操作, 没有收到请求的参与者则继续阻塞。这时, 参与者之间的数据就不再一致了当参与者执行Commit/Rollback后会向协调者发送Ack, 然而协调者不论是否收到所有的参与者的Ack, 该事务也不会再有其他补救措施了, 协调者能做的也就是等待超时后像事务发起者返回一个*我不确定该事务是否成功

4, 环境可靠性依赖协调者Prepare请求发出后, 等待响应, 然而如果有参与者宕机或与协调者之间的网络中断, 都会导致协调者无法收到所有参与者的响应, 那么在2PC中,协调者会等待一定时间,然后超时后,会触发事务中断,在这个过程中,协调者和所有其他参与者都是出于阻塞的,这种机制对网络问题常见的现实环境来说太苛刻了

可靠事务的解决方案最终一致(消息队列)

部署Seata(Server端)

Seata分TC、TM和RM三个角色,TC(Server端) 为单独服务端部署,TM和RM(Client端) 由业务系统集成。

https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html

资源目录

https://github.com/seata/seata/tree/1.3.0/script

  • client

存放client端sql脚本 (包含 undo_log表) ,参数配置

  • config-center

各个配置中心参数导入脚本,config.txt(包含server和client,原名nacos-config.txt)为通用参数文件

  • server

server端数据库脚本 (包含 lock_table、branch_table 与 global_table) 及各个容器配置

启动并安装Server

Server端存储模式(store.mode)现有file、db、redis三种(后续将引入raft,mongodb),

file模式无需改动,直接启动即可

注: file模式为单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高;

db (db version 5.7+) 模式为高可用模式,全局事务会话信息通过db共享,相应性能差些;

redis模式Seata-Server 1.3及以上版本支持,性能较高,存在事务信息丢失风险,请提前配置合适当前场景的redis持久化配置.

下载地址:

https://github.com/seata/seata/releases

已经配置好的服务端包:

[seata-1.3.0-server.zip]

服务端表:

java
-- -------------------------------- 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(96),
    `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;

启动包: seata-->conf-->file.conf,修改store.mode="db或者redis"

启动包: seata-->conf-->file.conf,修改store.db或store.redis相关属性。

修改注册中心

修改配置中心

将配置推送到nacos

script文件夹是从源码拉下来的

java
sh nacos-config.sh -h 81.69.43.78 -p 8848 -g SEATA_GROUP

检查已经成功推送到nacos

需要config.txt的数据源改为db 然后通过nacos-config.sh注册到配置中心

启动Seata Server

客户端整合seata

涉及到分布式事务的服务引入依赖:

java
<!--seata依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>

各个微服务】对应的数据库添加 undo_log 表

sql
-- 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(20)   NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(100) 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';

涉及到分布式事务的客户端配置:

yaml
# 分布式事务的分组 和 配置文件中要一致
spring:
  cloud:    
    alibaba:
      seata:
        tx-service-group: chengdu
# 分布式事务seata相关配置      
seata:
  registry:
    # 配置 seata的注册中心,告诉 seata client 怎么去访问 seata server(TC)
    type: nacos
    nacos:
      server-addr: 81.69.43.78:8848 # nacos地址
      application: seata-server # seata server的服务名
      username: nacos
      password: nacos
      group: SEATA_GROUP

seata遇到的异常错误

seata 1.4.x版本在MySQL8.0下DATETIME类型转换错误的问题

java
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `java.time.LocalDateTime`

客户端引入jar包

java
<!--seata 1.4.x版本在MySQL8.0下DATETIME类型转换错误的问题-->
<dependency>
            <groupId>com.esotericsoftware</groupId>
            <artifactId>kryo</artifactId>
            <version>4.0.2</version>
        </dependency>

        <dependency>
            <groupId>de.javakaffee</groupId>
            <artifactId>kryo-serializers</artifactId>
            <version>0.44</version>
        </dependency>

seata测试结果

服务端feign端服务端是否手动抛出异常服务端全局事务feign全局事务是否全部都回滚
正确出错回滚
正确出错不回滚
错误正确回滚
正确出错否(单体事务注解)feign端回滚,服务端不回滚

综上所述:

服务端必须检查 feign 是否调用正确,若feign调用失败则抛出异常,让两边都回滚