微服务SpringCloud Alibaba ------(九)Seata

1. 什么是Seata

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
官网:https://seata.io/zh-cn/index.html
源码: https://github.com/seata/seata
官方Demo: https://github.com/seata/seata-samples

2. 常见分布式事务解决方案

  1. seata 阿里分布式事务框架
  2. 消息队列
  3. saga
  4. XA

他们有一个共同点,都是“两阶段(2PC)”。 “两阶段”是指完成整个分布式事务,划分成两个步骤完成。
实际上,这四种常见的分布式事务解决方案,分别对应着分布式事务的四种模式: AT、 TCC、Saga、 XA
四种分布式事务模式,都有各自的理论基础,分别在不同的时间被提出;每种模式都有它的适用场景,同样每个模式也都诞生有各自的代表产品;而这些代表产品,可能就是我们常见的(全局事务、基于可靠消息、最大努力通知、TCC)。
今天,我们会分别来看4种模式(AT、 TCC、Saga、 XA)的分布式事务实现。在看具体实现之前,先讲下分布式事务的理论基础。

分布式事务理论基础
解决分布式事务,也有相应的规范和协议。分布式事务相关的协议有2PC、3PC。
由于三阶段提交协议3PC非常难实现,目前市面主流的分布式事务解决方案都是2PC协议。这就是文章开始提及的常见分布式事务解决方案里面,那些列举的都有一个共同点“两阶段”的内在原因。
有些文章分析2PC时,几乎都会用TCC两阶段的例子,第一阶段try, 第二阶段完成confirm或cancel。 其实2PC并不是专为实现TCC设计的,2PC具有普适性——协议一样的存在,目前绝大多数分布式解决方案都是以两阶段提交协议2PC为基础的。
TCC (Try-Confirm-Cancel) 实际上是服务化的两阶段提交协议。


2PC两阶段提交协议:
2PC(两阶段提交,Two-Phase Commit)
顾名思义,分为两个阶段: Prepare 和 Commit

Prepare:提交事务请求
基本流程如下图:
在这里插入图片描述

  1. 询问协调者向所有参与者发送事务请求,询问是否可执行事务操作,然后等待各个参与者的响应。
  2. 执行各个参与者接收到协调者事务请求后,执行事务操作(例如更新一个关系型数据库表中的记录), 并将Undo和Redo信息记录事务日志中。
  3. 响应如果参与者成功执行了事务并写入Undo和Redo信息,则向协调者返回YES响应,否则返回NO响应。当然,参与者也可能宕机,从而不会返回响应。

Commit:执行事务提交
执行事务提交分为两种情况,正常提交和回退。
正常提交事务
流程如下图:
在这里插入图片描述

  1. commit请求协调者向所有参与者发送Commit请求。
  2. 事务提交参与者收到Commit请求后,执行事务提交,提交完成后释放事务执行期占用的所有资源。
  3. 反馈结果参与者执行事务提交后向协调者发送Ack响应。
  4. 完成事务接收到所有参与者的Ack响应后,完成事务提交。

中断事务
在执行Prepare步骤过程中,如果某些参与者执行事务失败、宕机或与协调者之间的网络中断,那么协调者就无法收到所有参与者的YES响应,或者某个参与者返回了No响应,此时,协调者就会进入回退流程,对事务进行回退。流程如下图红色部分(将Commit请求替换为红色的Rollback请求):

在这里插入图片描述

  1. rollback请求协调者向所有参与者发送Rollback请求。
  2. 事务回滚参与者收到Rollback后,使用Prepare阶段的Undo日志执行事务回滚,完成后释放事务执行期占用的所有资源。
  3. 反馈结果参与者执行事务回滚后向协调者发送Ack响应。
  4. 中断事务接收到所有参与者的Ack响应后,完成事务中断。

2PC的问题

  1. 同步阻塞 参与者在等待协调者的指令时,其实是在等待其他参与者的响应,在此过程中,参与者是无法进行其他操作的,也就是阻塞了其运行。倘若参与者与协调者之间网络异常导致参与者一直收不到协调者信息,那么会导致参与者一直阻塞下去。
  2. 单点在2PC中,一切请求都来自协调者,所以协调者的地位是至关重要的,如果协调者宕机,那么就会使参与者一直阻塞并一直占用事务资源。如果协调者也是分布式,使用选主方式提供服务,那么在一个协调者挂掉后, 可以选取另一个协调者继续后续的服务,可以解决单点问题。但是,新协调者无法知道上一个事务的全部状态信息(例如已等待Prepare响应的时长等),所以也无法顺利处理上一个事务。
  3. 数据不一致 Commit事务过程中Commit请求/Rollback请求可能因为协调者宕机或协调者与参与者网络问题丢失,那么就导致了部分参与者没有收到Commit/Rollback请求,而其他参与者则正常收到执行了Commitl/Rollback操作,没有收到请求的参与者则继续阻塞。这时,参与者之间的数据就不再一致了。当参与者执行Commit/Rollback后会向协调者发送Ack,然而协调者不论是否收到所有的参与者的Ack。该事务也不会再有其他补救措施了,协调者能做的也就是等待超时后像事务发起者返回一个“我不确定该事务是否成功”。
  4. 环境可靠性依赖协调者Prepare请求发出后,等待响应,然而如果有参与者宕机或与协调者之间的网络中断,都会导致协调者无法收到所有参与者的响应,那么在2PC中,协调者会等待一定时间, 然后超时后,会触发事务中断,在这个过程中,协调者和所有其他参与者都是处于阻塞的。这种机制对网络问题常见的现实环境来说太苛刻了。

2.1. AT模式(auto transcation)

AT模式是一种无侵入的分布式事务解决方案。
Seata框架实现了该模式
在AT模式下,用户只需要关注自己的“业务SQL”,用户的“业务SQL”作为一阶段,Seata框架会自动生成事务的二阶段提交和回滚操作。

在这里插入图片描述

AT模式如何做到对业务的无侵入:
在一阶段, Seata 会拦截“业务SQL”,首先解析SQL语义,找到“业务SQL”要更新的业务数据,在业务数据被更新前,将其保存成"before image",然后执
行“业务SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image",最后生成行锁。以上操作全部在一个数据库事务内完成, 这样保证了一阶段操作的原子性。
在这里插入图片描述
二阶段如果是提交的话,因为“业务SQL”在一阶段已经提交至数据库, 所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
在这里插入图片描述
二阶段如果是回滚的话, Seata就需要回滚一阶段已经执行的“业务SQL”,还原业务数据。回滚方式便是用"before image’还原业务数据;但在还原前要首先要校验脏写,对比数据库当前业务数据和“after image”,如果两份数据完全一致就说明没有脏写, 可以还原业务数据,如果不一致就说明有脏写, 出现脏写就需要转人工处理。
在这里插入图片描述

2.2. TCC模式

TCC模式需要用户根据自己的业务场景实现Try、Confirm 和Cancel三个操作;事务发起方在一阶段执行Try方式,在二阶段提交执行Confirm方法,二阶段回滚执行Cancel方法。

  1. 对业务的侵入行比较强,并且需要自己实现相关事务的控制
  2. 在整个过程中基本没有锁,性能更强
    市面上常用的TCC框架:BeyeTCC、TCC-transaction、Himly
    在这里插入图片描述

在这里插入图片描述

3. 可靠消息最终一致性方案

此处使用RocketMQ做示例
在这里插入图片描述

4. Seata的三大角色

TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
其中TC为单独部署的Server服务端,TM和RM为嵌入到引用中的Client客户端

Seate中分布式事务的生命周期如下:
在这里插入图片描述

5. Seata快速开始

5.1 Seata Server (TC) 环境搭建

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

Server端存储模式(store.mode) 支持三种:

  • file:单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高(默认)
  • db:(5.7+)高可用模式,全局事务会话信息通过db共享,相应性能差些
    1. 打开conf/file.conf
    2. 修改mode=“db”
    3. 修改数据库连接信息(url/username/password)
    4. 创建数据库
    5. 新建表,下载地址,路径script\server\db\mysql.sql
  • redis:Seata-Server 1.3及以上版本支持,性能较高,存在事务信息丢失风险,请提前配置适合当前场景的redis持久化配置

资源目录: https://github.com/seata/seata/tree/1.3.0/script

  • client
    存放client端sq|脚本,参数配置
  • config-center
    各个配置中心参数导入脚本,config.txt(包含server和client,原名nacos-config.txt)为通用参数文件
  • server
    server端数据库脚本及各个容器配置

db存储模式+Nacos(注册&配置中心)部署
步骤一:下载安装包
https://github.com/seata/seata/releases

5.2. db+nacos高可用部署

  1. 修改注册中心与配置中心,路径conf/registry.conf

    # 这里是注册中心
    registry{
      # 可选项:file 、nacos 、eureka、redis、zk、consul、etcd3、sofa,此处修改为nacos 
      type = "nacos"
    
      nacos {
        application = "seata-server"
        serverAddr = "127.0.0.1:8848"
        group = "SEATA_GROUP"
        namespace = ""
        cluster = "default"
        username = "nacos"
        password = "nacos"
      }
    }
    
    #这里是配置中心
    config {
      # 可选项:ile、nacos 、apollo、zk、consul、etcd3,此处修改为nacos 
      type = "nacos"
      
      nacos {
        serverAddr = "127.0.0.1:8848"
        namespace = ""
        group = "SEATA_GROUP"
        username = "nacos"
        password = "nacos"
      }
    }
    
  2. 修改配置文件,路径script\config-center\config.txt,下载地址https://github.com/seata/seata/tree/1.3.0/script
    在这里插入图片描述
    事务分组与高可用

    事务分组说明。
    1.事务分组是什么?
    事务分组是seata的资源逻辑,类似于服务实例。在file.conf中的my_test_tx_group就是一个事务分组。
    2.通过事务分组如何找到后端集群?
    首先程序中配置了事务分组(GlobalTransactionScanner 构造方法的txServiceGroup参数),程序会通过用户配置的配置中心去寻找service.vgroupMapping.事务分组配置项,取得配置项的值就是TC集群的名称。拿到集群名称程序通过一定的前后缀+集群名称去构造服务名,各配置中心的服务名实现不同。拿到服务名去相应的注册中心去拉取相应服务名的服务列表,获得后端真实的TC服务列表。
    3.为什么这么设计,不直接取服务名?
    这里多了一层获取事务分组到映射集群的配置。这样设计后,事务分组可以作为资源的逻辑隔离单位,当发生故障时可以快速failover。

    在这里插入图片描述

    // 客户端切换分组
    seata.service.vgroup-mapping.projectA=Shanghai
    

    参考:https://seata.io/zh-cn/docs/user/txgroup/transaction-group-and-ha.html

  3. 将配置文件上传至配置中心
    参考:https://seata.io/zh-cn/docs/user/configuration/nacos.html
    windows下可以安装git来使用该命令

    sh ${SEATAPATH}/script/config-center/nacos/nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca -u username -w password

    参数说明:
    -h:HOST,默认值是本地主机。
    -p:端口,默认值为8848。
    -g:分组,默认值为’SEATA_GROUP’。
    -t:Nacos的名称空间ID字段,默认值为“”。
    -u:用户名,Nacos1.2.0+权限控制,默认值为“”。
    -w:密码,Nacos1.2.0+权限控制,默认值为“”。

  4. 启动seata服务
    参考:https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html
    源码启动: 执行Server.java的main方法
    命令启动: seata-server.sh -h 127.0.0.1 -p 8091 -m db -n 1 -e test

    -h: 注册到注册中心的ip
    -p: Server rpc 监听端口
    -m: 全局事务会话信息存储模式,file、db、redis,优先读取启动参数 (Seata-Server 1.3及以上版本支持redis)
    -n: Server node,多个Server时,需区分各自节点,用于生成不同区间的transactionId,以免冲突
    -e: 多环境配置参考 http://seata.io/en-us/docs/ops/multi-configuration-isolation.html

5.3. 客户端整合seata

  1. 添加依赖

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    </dependency>
    
  2. 每个客户端(服务)使用的数据库添加undo_log表

    -- MySql建表语句
    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';
    
    
    
    -- oracle建表语句
    CREATE TABLE undo_log
    (
        id            NUMBER(19)    NOT NULL,
        branch_id     NUMBER(19)    NOT NULL,
        xid           VARCHAR2(100) NOT NULL,
        context       VARCHAR2(128) NOT NULL,
        rollback_info BLOB          NOT NULL,
        log_status    NUMBER(10)    NOT NULL,
        log_created   TIMESTAMP(0)  NOT NULL,
        log_modified  TIMESTAMP(0)  NOT NULL,
        PRIMARY KEY (id),
        CONSTRAINT ux_undo_log UNIQUE (xid, branch_id)
    );
    
    COMMENT ON TABLE undo_log IS 'AT transaction mode undo table';
    
    -- Generate ID using sequence and trigger
    CREATE SEQUENCE UNDO_LOG_SEQ START WITH 1 INCREMENT BY 1;
    
    
    
    -- postgresql建表语句
    CREATE TABLE IF NOT EXISTS public.undo_log
    (
        id            SERIAL       NOT NULL,
        branch_id     BIGINT       NOT NULL,
        xid           VARCHAR(100) NOT NULL,
        context       VARCHAR(128) NOT NULL,
        rollback_info BYTEA        NOT NULL,
        log_status    INT          NOT NULL,
        log_created   TIMESTAMP(0) NOT NULL,
        log_modified  TIMESTAMP(0) NOT NULL,
        CONSTRAINT pk_undo_log PRIMARY KEY (id),
        CONSTRAINT ux_undo_log UNIQUE (xid, branch_id)
    );
    
    CREATE SEQUENCE IF NOT EXISTS undo_log_id_seq INCREMENT BY 1 MINVALUE 1 ;
    
  3. 配置事务分组
    要和服务端的这个配置保持一致
    在这里插入图片描述

    spring:
      cloud:
        alibaba:
          seata:
            tx-service-group: guangzhou
    
    seata:
      # application-id: order-seata # 如果配置了spring.application.name那么就不需要配置这个
      registry:
        # 配置nacos注册中心,告诉seata client怎么去访问seata server
        type: nacos
        nacos:
          server-addr: 127.0.0.1:8848 # seata server服务地址
          application: seata-server # seata server服务名称 默认值seata-server
          username: nacos
          password: nacos
          group: SEATA-GROUP
      config:
        # 配置nacos配置中心,读取关于seata client的配置
        type: nacos
        nacos:
          server-addr: 127.0.0.1:8848 # seata server服务地址
          username: nacos
          password: nacos
          group: SEATA-GROUP
    

5.4. 添加注解

添加注解@GlobalTransactional

@GlobalTransactional
public int addOrder(Order order){
    
    

    int pk = orderMapper.insertOrder(order);

    // OpenFeign 扣减库存
    String reduct = stockService.reduct(order.getProductId());
    log.info("扣减库存 => {}", reduct);

    // 异常
    int a = 1 / 0;

    return pk;
}

6. 代码下载

https://download.csdn.net/download/qq_42017523/57167846

猜你喜欢

转载自blog.csdn.net/qq_42017523/article/details/121648876