Skip to main content

分布式事务

一个微服务同时调用多个微服务,每个微服务的本地事务,也可以称为分支事务。多个有关联的分支事务一起就组成了全局事务

每个微服务有自己独立的数据库,只能实现自身的ACID,无法实现整体的ACID

Seata

阿里巴巴的开源的分布式事务框架,保证全局事务下的所有分支事务同时成功或失败。

事务管理中的三个重要的角色:

  • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
  • TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager) - 资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

其中,TMRM可以理解为Seata的客户端部分,引入到参与事务的微服务依赖中即可。将来TMRM就会协助微服务,实现本地分支事务与TC之间交互,实现事务的提交或回滚。

TC服务则是事务协调中心,是一个独立的微服务,需要单独部署。

部署TC服务

  1. 在虚拟机root目录下创建seata目录,包含application.yaml文件

  2. 在虚拟机的/root目录执行下面的命令:

    docker run --name seata \
    -p 8099:8099 \
    -p 7099:7099 \ # HTTP访问端口
    -e SEATA_IP=IP \
    -v ./seata:/seata-server/resources \
    --privileged=true \
    --network 网络名 \
    -d \
    seataio/seata-server
  3. 创建seata数据库

    CREATE DATABASE IF NOT EXISTS `seata`;
    USE `seata`;


    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;


    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;


    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_and_branch_id` (`xid` , `branch_id`)
    ) 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);

微服务集成

例子:

  1. 引入依赖

    <!--统一配置管理-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <!--读取bootstrap文件-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    <!--seata-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    </dependency>
  2. Nacos中添加一个共享的seata配置,命名为shared-seata.yaml

    seata:
    registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
    server-addr: 192.168.150.101:8848 # nacos地址
    namespace: "" # namespace,默认为空
    group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
    application: seata-server # seata服务名称
    username: nacos
    password: nacos
    tx-service-group: hmall # 事务组名称
    service:
    vgroup-mapping: # 事务组与tc集群的映射关系
    hmall: "default"
  3. 在需要使用的微服务的bootstrap.yaml中添加:

    spring:
    application:
    name: trade-service # 服务名称
    profiles:
    active: dev
    cloud:
    nacos:
    server-addr: 192.168.150.101 # nacos地址
    config:
    file-extension: yaml # 文件后缀名
    shared-configs: # 共享配置
    - dataId: shared-seata.yaml # 共享seata配置
    ……

Seata支持四种不同的分布式事务解决方案:

  • XA
  • TCC
  • AT
  • SAGA

两阶段提交

A是规范,目前主流数据库都实现了这种规范,实现的原理都是基于两阶段提交。

一阶段:

  • 事务协调者通知每个事务参与者执行本地事务
  • 本地事务执行完成后报告事务执行状态给事务协调者,此时事务不提交,继续持有数据库锁

二阶段:

  • 事务协调者基于一阶段的报告来判断下一步操作
  • 如果一阶段都成功,则通知所有事务参与者,提交事务
  • 如果一阶段任意一个参与者失败,则通知所有事务参与者回滚事务

XA模式

XA 规范 是 X/Open 组织定义的分布式事务处理(DTPDistributed Transaction Processing)标准,XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。

RM一阶段的工作:

  1. 注册分支事务到TC
  2. 执行分支业务sql但不提交
  3. 报告执行状态到TC

TC二阶段的工作:

  1. TC检测各分支事务执行状态
  2. 如果都成功,通知所有RM提交事务
  3. 如果有失败,通知所有RM回滚事务

RM二阶段的工作:

  • 接收TC指令,提交或回滚事务

优点

  • 事务的强一致性,满足ACID原则
  • 常用数据库都支持,实现简单,并且没有代码侵入

缺点

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
  • 依赖关系型数据库实现事务

例子:

  1. Nacos中的共享shared-seata.yaml配置文件中加入设置:

    seata:
    data-source-proxy-mode: XA
  2. 在需要实现分布式事务的微服务的Service方法中:

    @Transactional更换为@GlobalTransactional

    @Service
    @RequiredArgsConstructor
    public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
    @Override
    // @Transactional 不再使用这个注解
    @GlobalTransactional
    public Long createOrder(OrderFormDTO orderFormDTO) {……}
    }

AT模式

同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。

阶段一RM的工作:

  • 注册分支事务
  • 记录undo-log(数据快照)
  • 执行业务sql并提交
  • 报告事务状态

阶段二提交时RM的工作:

  • 删除undo-log即可

阶段二回滚时RM的工作:

  • 根据undo-log恢复数据到更新前

AT模式与XA模式的区别

  • XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
  • XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
  • XA模式强一致;AT模式最终一致

实现:

  1. 在对应的微服务的数据库中创建undo_log表:

    如在提交订单时需要用到cart,item,trade的微服务,在这三个微服务的数据库中创建该表

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

  2. application.yaml文件修改为:

    seata:
    # data-source-proxy-mode: XA
    data-source-proxy-mode: AT