微服务保护和分布式事务

微服务保护

前面学过了组件Spring Cloud - 韩熙隐ario - 博客园,我们已经可以解决业务中99%的问题了。

image-20251126214603494

我们还可以拥有一些高级的能力,比如:

  • 服务保护:业务的优化、系统健壮性的处理和疑难问题的阶段等
  • 分布式事务:服务独立,各自操作数据库,如何保证ACID。

服务保护

  • 雪崩问题

雪崩问题

保证服务运行的健壮性,避免级联失败导致的雪崩问题,就属于微服务保护。

微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩。

image-20251126214943513

比如商品服务出现问题。在购物车积累了很多请求。导致资源耗尽,影响其他服务B的请求。

这样,就造成了雪崩问题

image-20251126215107348

雪崩问题产生的原因是什么?

  • 微服务相互调用,服务提供者出现故障或阻塞。
  • 服务调用者没有做好异常处理,导致自身故障。
  • 调用链中的所有服务级联失败,导致整个集群故障

解决问题的思路有哪些?

  • 尽量避免服务出现故障或阻塞。【避免出错】
    • 保证代码的健壮性;
    • 保证网络畅通;
    • 能应对较高的并发请求;
  • 服务调用者做好远程调用异常的后备方案,避免故障扩散【减少影响】

服务保护方案

  • 请求限流:限制访问微服务的请求的并发量,避免服务因流量激增出现故障。

    避免把服务压垮。限流器。但也无法确保服务不出问题

    image-20251128190806463

  • 线程隔离:也叫做舱壁模式,模拟船舱隔板的防水原理。通过限定每个业务能使用的线程数量而将故障业务隔离,避免故障扩散。

    限定每个业务使用的线程数量,将故障隔离。避免故障扩散。减少能影响的数量。

    image-20251128190849786

  • 快速失败:给业务编写一个调用失败时的处理的逻辑,称为fallback。当调用出现故障(比如无线程可用)时,按照失败处理逻辑执行业务并返回,而不是直接抛出异常。

    image-20251128191018032

  • 服务熔断:由断路器统计请求的异常比例或慢调用比例,如果超出阈值则会熔断该业务,则拦截该接口的请求。熔断期间,所有请求快速失败,全都走fallback逻辑。

    发现异常后,拒绝请求,所有请求快速失败,走fallback【fallback可以说是后续的过程,补充,兜底】。可以返回友好提示等内容。这样就少了远程调用和等待的过程了。

    image-20251128191320185

服务保护技术

方案有了,代码要自己写吗?

其实有现成的技术了。主要的是下面两个技术,分别由阿里和网飞开发的

Sentinel Hystrix
线程隔离 信号量隔离 线程池隔离/信号量隔离
熔断策略 基于慢调用比例或异常比例 基于异常比率
限流 基于 QPS,支持流量整形 有限的支持
Fallback 支持 支持
控制台 开箱即用,可配置规则、查看秒级监控、机器发现等 不完善
配置方式 基于代码。也可以基于控制台,但基于控制台重启后失效。 基于注解或配置文件,永久生效

Sentinel

初识Sentinel

Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址: https://sentinelguard.io/zh-cn/index.html

image-20251129215308164

官方提供好了一个核心库——Sentinel客户端,其实就是一个jar包。包含了服务限流、熔断等上面的功能。那微服务引用它,这些功能都不用再去写了。只需要配置规则即可。

配置方式有两种

  • Java编码实现:相对来说比较麻烦,需要学很多api

  • 控制台实现:只要服务引入客户端并配置控制台地址,那么微服务中的客户端会和控制台产生交互,控制台可以监控微服务内部的接口运行、限流等情况。在控制台中也可以实现对上面功能规则的配置。不用编码实现了 。

    综上,控制台有两个功能

    • 接口监控
    • 规则配置

综上:Sentinel 的使用可以分为两个部分:

  • 核心库(Jar包):不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。在项目中引入依赖即可实现服务限流、隔离、熔断等功能。
  • 控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等。

所以,我们用控制台的方式实现。

搭建控制台

  1. 下载jar包

    下载地址,也可以直接使用已有资料提供的版本sentinel-dashboard-1.8.6.jar

  2. 运行

    将jar包放在任意非中文、不包含特殊字符的目录下,重命名为sentinel-dashboard.jar,方便运行指令的编写。

    然后在命令行运行如下命令启动控制台:

    java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
    

    默认端口是8080,端口变了地址也配一下,项目名称

    其它启动时可配置参数可参考官方文档

  3. 访问

    访问http://localhost:8090页面,就可以看到sentinel的控制台了:

    image-20251201155447463

    需要输入账号和密码,默认都是:sentinel

    登录后,即可看到控制台,默认会监控sentinel-dashboard服务本身:

    image-20251201155547327

微服务整合

我们在cart-service模块中整合sentinel,连接sentinel-dashboard控制台,步骤如下:

  1. 引入sentinel依赖

    <!--sentinel-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId> 
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    
  2. 配置控制台

    修改application.yaml文件,添加下面内容:【这样就和控制台关联了】

    spring:
      cloud: 
        sentinel:
          transport:
            dashboard: localhost:8090 #与控制台关联
    
  3. 访问cart-service的任意端点

    重启cart-service,然后访问查询购物车接口【得有了访问才能显示】,sentinel的客户端就会将服务访问的信息提交到sentinel-dashboard控制台。并展示出统计信息:

    image-20251201162730693

我们可以在控制台可以看出常用功能

  • 实时监控

    接口限流等等情况

  • **规则

    更好配置对应的规则,推送到服务中

  • 簇点链路,

    就是单机调用链路。是一次请求进入服务后经过的每一个被Sentinel监控的资源链。默认Sentinel(只)会监控SpringMVC的每一个Endpoint(http接口)【对应controller接口】。限流、熔断等都是针对簇点链路中的资源设置的。而资源名默认就是接口的请求路径

    但这里有个问题,它是以请求路径作为簇点资源名称的。但Restful风格的API请求路径一般都相同,这会导致簇点资源名称重复。因此我们要修改配置,把请求方式+请求路径作为簇点资源名称,在Sentinel中就不会重复了

    spring:
      cloud:
        sentinel:
          transport:
            dashboard: localhost:8090 #与控制台关联
          http-method-specify: true #开启请求方式前缀
    

    然后,重启服务,通过页面访问购物车的相关接口,可以看到sentinel控制台的簇点链路发生了变化:

    image-20251201165805838

请求限流

在控制台中实现简单配置就行。访问过就会出现在簇点链路。

image-20251201171433665

点击流控,选QPS【每秒钟请求的数量】、单机阈值,限制每秒钟的并发数量是多少。然后可以用测试工具进行压力测试,验证效果。【ApiFox和Jmeter】

image-20251201173044942

线程隔离

当商品服务出现阻塞或故障时,调用商品服务的购物车服务可能因此而被拖慢,甚至资源耗尽。所以必须限制购物车服务中查询商品这个业务的可用线程数,实现线程隔离

image-20251201193740028

设置方式和上面请求限流基本一致,选择”并发线程数“即可。后面配置的就是这个资源可以用几个线程。

image-20251201185903526

要测试是否生效的话。我们可以针对性的设置一下。

  1. 可以主动延长处理时长

    public List<ItemDTO> queryItemByIds(@RequestParam("ids") List<Long> ids){
        ThreadUtil.sleep(2000);
        return itemService.queryItemByIds(ids);
    }
    
  2. 降低tomcat调低上限

    image-20251201192043332

    server:
      port: 8082
      tomcat:
        threads:
          max: 50 # 允许的最大线程数
        accept-count: 50 # 最大排队等待线程数量
        max-connections: 100 # 允许的最大连接
    

然后可以对比设置线程隔离前后压力测试访问接口的影响效果。【比如对查询购物车接口进行压力测试。然后测试本接口和其他接口的性能。观察开启前后的不同】

另外,我们可以更进一步优化线程隔离。把OpenFeign整合Sentinel

修改cart-service模块的application.yml文件,开启Feign的sentinel功能【让Feign也成为ClientSentinel的簇点资源】:

feign:
  sentinel:
    enabled: true # 开启feign对sentinel的支持

Fallback

前面的线程隔离确实对微服务起到了保护作用。但是被隔离的业务资源耗尽后直接不可用了,所以还可以优化。

image-20251201201059785

从上面的图中可以看出,我们假设出问题的地方是商品服务,因为购物车查询时用到了查询商品的服务,所以才会出错。那我们可以细化流控。对查询商品的服务进行限制。同时,为了避免查商品出错后购物车查询直接出错。我们可以添加Fallback逻辑,请求失败后执行fallback逻辑。具体步骤如下

  1. 将FeignClient作为Sentinel的簇点资源

    在cart-service配置文件配置

    feign:
      sentinel:
        enabled: true
    

    配置完成后会在控制台中以查询购物车接口的子级出现

  2. FeignClient的Fallback有两种配置方式

    • FallbackClass,无法对远程调用的异常做处理
    • FallbackFactory,可以对远程调用的异常做处理,通常都会选择这种

案例:

我们对上面提到的商品查询接口编写fallback

  1. 自定义类,实现FallbackFactory,编写对某个FeignClient的fallback逻辑

    在api包下编写

    package com.hmall.api.fallback;
    
    import com.hmall.api.client.ItemClient;
    import com.hmall.api.dto.ItemDTO;
    import com.hmall.api.dto.OrderDetailDTO;
    import com.hmall.common.utils.CollUtils;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.cloud.openfeign.FallbackFactory;
    
    import java.util.Collection;
    import java.util.List;
    
    @Slf4j
    public class ItemClientFallbackFactory implements FallbackFactory<ItemClient> {
        @Override
        public ItemClient create(Throwable cause) { //接收异常
            return new ItemClient() {
                @Override
                public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
                    log.error("查询商品失败!", cause); //打印异常信息
                    return CollUtils.emptyList(); //返回一个空集合而不报错
                }
    
                @Override
                public void deductStock(List<OrderDetailDTO> items) {
                    log.error("扣减商品库存失败!", cause);
                    throw new RuntimeException(cause); //对应不明白如何处理的异常,抛出去就行,让调用者处理
                }
            };
        }
    }
    
  2. 将刚刚定义的ItemClientFallbackFactory注册为一个Bean

    这时,我们已经有一个config类了【DefaultFeignConfig】。我们可以在原本类中添加。也可以新建一个config类。我们这还是以前者为例

    @Bean
    public ItemClientFallbackFactory itemClientFallbackFactory() {
        return new ItemClientFallbackFactory();
    }
    
  3. 在ItemClient接口中使用ItemClientFallbackFactory

    @FeignClient(value = "cart-service", fallbackFactory = ItemClientFallbackFactory.class)
    

然后就可以测试了

测试发现,及时查询商品出错,但查购物车是没问题的,只不过价格字段是空的。其他操作不受影响。

服务熔断

通过上面的处理后,我们取得了一定的效果。但是还可以优化。

商品服务出错时,我们查询购物车每次还是会对商品服务进行远程调用,而且远程调用很耗时或者直接等待。如果发现服务挂了或者性能下降不可接受,我们直接熔断就好了,直接拒绝远程调用,直接走Fallback。

当然,熔断不能一直熔断。恢复正常了要恢复请求的。

以上可以通过断路器实现。

熔断是解决雪崩问题的重要手段。思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。

image-20251201211501691

open状态不是持续的。到一定期限会放行一次请求,观察是否正常。

对于规则配置,我们可以通过控制很方便的配置

image-20251201211616185

熔断策略【可以同时设置多个策略】

  • 慢调用比例
    • 最大RT:超过多久算慢调用
    • 比例阈值:慢调用占比多少时熔断
    • 熔断时长:open状态持续多久
    • 最小请求数:能触发熔断的最小请求总数
    • 统计时长:统计的时间范围周期
  • 异常比例
  • 异常数
    当然处理上面的功能,Sentinel还有其他功能。可以参考其官方网站进行学习。

分布式事务

分布式事务问题

一直是微服务中的难点。

我们可以通过项目中的一个业务案例进行认识。

首先我们看看项目中的下单业务整体流程

image-20251202152900870

下单业务,前端请求首先进入订单服务,创建订单并写入数据库。然后订单服务调用购物车服务和库存服务:

  • 购物车服务负责清理购物车信息
  • 库存服务负责扣减商品库存

在分布式系统中,如果一个业务需要多个服务合作完成,而且每一个服务都有事务,多个事务必须同时成功或失败,这样的事务就是分布式事务。其中的每个服务的事务就是一个分支事务。整个业务称为全局事务

在案例中,我们知道每一个分支事务就是传统的单体事务,都可以满足ACID特性,但全局事务跨越多个服务、多个数据库,是否还能满足呢?

我们已有的代码中,购物车服务感知不到库存服务的错误。所以依然清理了购物车。这就使得全局事务不满足ACID特性。

这就是分布式事务问题,出现以下情况之一就可能产生分布式事务问题:

  • 业务跨多个服务实现
  • 业务跨多个数据源实现

接下来研究下如何解决分布式事务问题。

Seata

初识Seata

解决分布式事务的方案有很多,但实现起来都比较复杂,因此我们一般会使用开源的框架来解决分布式事务问题。在众多的开源分布式事务框架中,功能最完善、使用最多的就是阿里巴巴在2019年开源的Seata了。

Seata是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。

官网地址:http://seata.io/,其中的文档、播客中提供了大量的使用说明、源码分析。

其实分布式事务产生的一个重要原因,就是参与事务的多个分支事务互相无感知,不知道彼此的执行状态。因此解决分布式事务的思想非常简单:

就是找一个统一的事务协调者,与多个分支事务通信,检测每个分支事务的执行状态,保证全局事务下的每一个分支事务同时成功或失败即可。大多数的分布式事务框架都是基于这个理论来实现的。

image-20251202154937282

其实就是当局者迷,旁观者清。加一个事务协调者即可。Seata就是这个原理

但实际上更加复杂一点。

image-20251202155003666

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

  • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
  • TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。【全局事务的开始,可以理解为ceateOrder方法的范围。我们告诉它就行。】
  • RM (Resource Manager) - 资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态

TC通过TM知道什么时候开始和技术,通过RM知道微服务是否正常。

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

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

部署TC服务

  1. 准备数据库表

    Seata支持多种存储模式,但考虑到持久化的需要,我们一般选择基于数据库存储。执行课前资料提供的《seata-tc.sql》,导入数据库表:

    image-20251202163856563

  2. 准备配置文件

    已有资料一个seata目录,其中包含了seata运行时所需要的配置文件:

    image-20251202164031136

    文件示例如下:

    server:
      port: 7099 #web控制台端口
    
    spring:
      application:
        name: seata-server #其实也是一个微服务,这里指定微服务名字。
    
    logging: 
      config: classpath:logback-spring.xml
      file:
        path: ${user.home}/logs/seata
      # extend:
      #   logstash-appender:
      #     destination: 127.0.0.1:4560
      #   kafka-appender:
      #     bootstrap-servers: 127.0.0.1:9092
      #     topic: logback_to_logstash
    
    console: #控制台账号密码
      user:
        username: admin
        password: admin
    
    seata: #seata配置
      config:
        # support: nacos, consul, apollo, zk, etcd3
        type: file   #支持多种配置方式,这里指定为file直接在当前文件配置,当然也可以指定为nacos
        # nacos:
        #   server-addr: nacos:8848
        #   group : "DEFAULT_GROUP"
        #   namespace: ""
        #   dataId: "seataServer.properties"
        #   username: "nacos"
        #   password: "nacos"
      registry:
        # support: nacos, eureka, redis, zk, consul, etcd3, sofa
        type: nacos
        nacos: #配置到注册中心,让微服务知道其地址信息
          application: seata-server
          server-addr: nacos:8848  #容器名,可以部署到docker中
          group : "DEFAULT_GROUP" 
          namespace: ""  # 命名空间【具体可以按项目划分】,不填就是public,分组
          username: "nacos"
          password: "nacos"
    #  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,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
      server:
        # service-port: 8091 #If not configured, the default is '${server.port} + 1000'
        max-commit-retry-timeout: -1
        max-rollback-retry-timeout: -1
        rollback-retry-timeout-unlock-enable: false
        enable-check-auth: true
        enable-parallel-request-handle: true
        retry-dead-threshold: 130000
        xaer-nota-retry-timeout: 60000
        enableParallelRequestHandle: true
        recovery:
          committing-retry-period: 1000
          async-committing-retry-period: 1000
          rollbacking-retry-period: 1000
          timeout-retry-period: 1000
        undo:
          log-save-days: 7
          log-delete-period: 86400000
        session:
          branch-async-queue-size: 5000 #branch async remove queue size
          enable-branch-async-remove: false #enable to asynchronous remove branchSession
      store:
        # 数据存储,也有很多方式
        # support: file 、 db 、 redis
        mode: db
        session:
          mode: db
        lock:
          mode: db
        db:
          datasource: druid
          db-type: mysql
          driver-class-name: com.mysql.cj.jdbc.Driver
          # 因为后面部署到容器中,这里mysql写的是容器名
          url: jdbc:mysql://mysql:3306/seata?rewriteBatchedStatements=true&serverTimezone=UTC
          user: root
          password: 123
          # 和一些参数和表
          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
        # redis:
        #   mode: single
        #   database: 0
        #   min-conn: 10
        #   max-conn: 100
        #   password:
        #   max-total: 100
        #   query-limit: 1000
        #   single:
        #     host: 192.168.150.101
        #     port: 6379
      metrics:
        enabled: false
        registry-type: compact
        exporter-list: prometheus
        exporter-prometheus-port: 9898
      transport:
        rpc-tc-request-timeout: 15000
        enable-tc-server-batch-send-response: false
        shutdown:
          wait: 3
        thread-factory:
          boss-thread-prefix: NettyBoss
          worker-thread-prefix: NettyServerNIOWorker
          boss-thread-size: 1
    
  3. Docker部署

    我们将整个seata文件夹拷贝到虚拟机的/root目录:

    image-20251202165122737

    需要注意,要确保nacos、mysql都在hm-net网络中。如果某个容器不再hm-net网络

    在虚拟机的/root目录执行下面的命令:【两个端口,应该是与微服务,一个是控制台】

    docker run --name seata \
    -p 8099:8099 \
    -p 7099:7099 \
    -e SEATA_IP=192.168.150.101 \
    -v ./seata:/seata-server/resources \
    --privileged=true \
    --network hm-net \
    -d \
    seataio/seata-server:1.5.2
    

    当然我们应该利用已有的镜像seata-1.5.2.tar。免得下载镜像了。

    注意:

    我们一定要把配置文件自己的信息填充上。否则出错。

部署成功!!!

image-20251202170807689

image-20251202170817078

微服务集成Seata

参与分布式事务的每一个微服务都需要集成Seata,我们以trade-service为例。

注意:如果在api模块或common模块引入,那么除了这三个使用seata的微服务需要配置,其它没有使用seata的模块也要配置seata,因为引入的是starter场景启动器,否则报错。

集成步骤如下:

  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. 配置TC地址,在application.yml中添加配置,让微服务找到TC服务地址:

    image-20251202173333835
    TC很重要,我们实际工作中TC很可能是个集群,所以比较复杂,而且写死也不太好。既然我们上面已经把它注册到nacos中了。我们可以用nacos进行配置

    所以我们直接把下面配置复制到nacos中去。shared-seata.yaml

    image-20251202201121023

    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"
    

    spring cloud中已经配置了nacos地址,这里之所以还需要nacos地址,是因为Seata都不确定用到是哪个类型的服务中心,更不会去找nacos地址,所以这里直接再配一下就好了

    命名空间可以理解成数据隔离的东西,命名空间->分组->微服务->集群【具体运行微服务中的某个集群,可以理解为机房,每个机房就是一个集群】。有了这些信息,才能确定是哪个具体的服务

    事务组可以理解为集群。一个机房一个组

    然后,改造trade-service模块,添加bootstrap.yaml:【哪些微服务需要做这样的集成呢——事务参与者,比如这俩trade、item、cart服务】

    server:
      port: 8085
    feign:
      okhttp:
        enabled: true # 开启OKHttp连接池支持
      sentinel:
        enabled: true # 开启Feign对Sentinel的整合
    hm:
      swagger:
        title: 交易服务接口文档
        package: com.hmall.trade.controller
      db:
        database: hm-trade
    

    参考上述办法分别改造hm-carthm-item两个微服务模块。

上面配置基本上是死的,CV就行

接下来,我们就可以利用Seata解决分布式事务问题了

解决方案

Seata为解决分布式事务针对不同场景提供了多种模式,最有代表性的模式有两种模式

  • XA
  • TCC
  • AT
  • SAGA

这里我们以XA模式和AT模式来介绍

XA模式

XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA 规范 描述了全局的TM与局部的RM之间的接口,这套标准有数十年的历史了,几乎所有主流的关系型数据库都对 XA 规范 提供了支持。Seata的XA模式如下:

image-20251202212837112

事务执行完,不会提交,锁不会释放的。等带所有分支都执行完才提交,避免不一致。调用完,TM知道是不是执行完了

一阶段的工作:

  • RM注册分支事务到TC
  • RM执行分支业务sql但不提交
  • RM报告执行状态到TC

二阶段的工作:

  • TC检测各分支事务执行状态
    • 如果都成功,通知所有RM提交事务
    • 如果有失败,通知所有RM回滚事务
  • RM接收TC指令,提交或回滚事务

优点

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

缺点:【干等,别人也干不了】

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

Seata的starter已经完成了XA模式的自动装配,实现非常简单,步骤如下

  • 修改application.yml文件(每个参与事务的微服务),开启XA模式【因为抽取到了nacos中,可以在nacos中共享shared-seata.yaml配置文件中设置】:

    seata:
      data-source-proxy-mode: XA
    
  • 给发起全局事务的入口方法添加@GlobalTransactional注解,本例中是OrderServiceImpl中的create方法:【另外在分支事务方法中添加Transactional注解,方便事务回滚】【Transactional注解在不侵入业务代码的情况下,为方法或类添加事务控制能力,确保数据库操作ACID】

    @Override
    @GlobalTransactional
    public Long createOrder(OrderFormDTO orderFormDTO) {}
    

然后就可以测试。把购物车中某商品数改成零,提交订单,查看相应情况。

AT模式

Seata主推的是AT模式【默认AT】,AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。

image-20251202215336333

阶段一RM的工作:

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

阶段二提交时RM的工作:

  • 删除undo-log即可

阶段二回滚时RM的工作:

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

在这个过程中可以看出,会出现脏读的现象,出现数据短暂不一致的现象。

简述AT模式与XA模式最大的区别是什么?

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

这两个模式具体用哪个,看实际业务的需求了,看对性能和一致性平衡。

实现AT模式步骤如下

  1. 首先,添加资料中的seata-at.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       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.yml文件,将事务模式修改为AT模式:【和上面相同,可以在nacos中设置。其实默认就是AT】

    seata:
      data-source-proxy-mode: AT
    
posted @ 2025-11-28 19:34  韩熙隐ario  阅读(8)  评论(0)    收藏  举报