Skip to content
DAILY QUOTE

“ ”

在微服务远程调用过程中,还会继续出现两个比较重要的问题:

  1. 服务健壮性问题 比如购物车服务查询购物车列表时,需要远程调用商品服务获取最新商品信息。如果商品服务故障,购物车服务也可能跟着失败。但从业务角度看,即使商品信息查询失败,购物车列表也应该尽量展示出来,而不是整个页面不可用。

  2. 级联失败 / 雪崩问题 如果商品服务响应变慢或阻塞,购物车服务在调用商品服务时也会被拖慢。购物车服务请求变多后,又可能占满自身线程资源,最终影响整个服务。继续扩散下去,可能导致多个微服务一起不可用,这就是雪崩问题。

  3. 跨服务事务问题 下单业务通常会涉及多个微服务:

    • 商品服务:扣减库存
    • 交易服务:保存订单
    • 购物车服务:清理购物车

这些操作都涉及数据库写操作,而且分布在不同服务、不同数据库中。如何保证它们要么全部成功,要么全部失败,就是分布式事务要解决的问题。

本章主要内容:

  • 微服务保护
    • 服务保护方案
    • 请求限流
    • 线程隔离
    • 服务熔断
  • 分布式事务
    • 初识分布式事务
    • Seata
    • XA模式
    • AT模式

1.微服务保护

微服务保护的目标是:

text
提高服务健壮性,避免一个服务故障拖垮其它服务,最终导致雪崩。

在微服务调用链中,如果某个服务响应慢、异常率高或并发过大,调用它的服务也会被影响。为了控制影响范围,需要引入服务保护机制。

1.1.服务保护方案

常见方案包括:

  • 请求限流 控制某个接口单位时间内允许通过的请求数量,避免瞬间高并发压垮服务。
  • 线程隔离 限制某个业务或远程调用可以使用的线程数量,避免它占满整个服务的线程资源。
  • 服务熔断 当某个远程服务异常比例或慢调用比例过高时,暂时停止调用它,直接走降级逻辑。
  • 服务降级 当远程调用失败、限流、熔断时,不直接让业务崩掉,而是返回默认值、空数据或友好提示。

这些方案本质上都是为了牺牲一部分完整体验,换取系统整体稳定性。比如请求限流降低了并发上线,线程隔离降低了可用资源数量,服务熔断降低了服务完整度。

1.1.1.请求限流

请求限流就是限制接口访问流量,防止瞬时高并发把服务打垮。

比如某个接口最多能承受每秒100个请求,如果瞬间来了1000个请求,就可能导致服务响应变慢、线程耗尽,甚至崩溃。

限流的做法是:

text
只允许一部分请求通过,超出的请求直接拒绝或降级处理。

数量高低起伏的并发请求曲线,经过限流器就会变得平稳,保持在一个平稳的水平。

1.1.2.线程隔离

线程隔离解决的是:

text
某个接口或远程调用占满线程资源,导致服务内其它接口也不可用的问题。

比如购物车服务中有多个接口:

  • 查询购物车
  • 添加购物车
  • 修改购物车数量
  • 删除购物车商品

如果查询购物车接口需要调用商品服务,而商品服务很慢,那么大量查询购物车请求会长时间占用购物车服务的Tomcat线程。

购物车服务中的大量Tomcat工作线程,被“查询购物车请求”占用并阻塞,最终该服务线程池耗尽,导致它的其他接口也无法及时处理。

线程隔离的思路是:

text
给某个接口或远程调用分配固定线程资源,超出后直接拒绝,不允许它占满整个服务。

1.1.3.服务熔断

线程隔离可以避免故障扩散,但不能解决两个问题:

  1. 故障服务依然会拖慢调用方。
  2. 调用失败后业务体验不好。

比如商品服务响应很慢,购物车服务每次查询都要等商品服务,虽然线程数量被限制了,但查询购物车本身还是很慢。

所以还需要服务熔断和降级。

服务熔断包含两个核心动作:

  1. 降级逻辑 当远程调用失败时,不直接报错,而是返回默认数据、空集合或友好提示。
  2. 熔断判断 统计远程服务的异常比例或慢调用比例。如果超过阈值,就暂时停止调用该服务,直接走降级逻辑。

1.2.Sentinel

Sentinel是阿里巴巴开源的服务保护框架,已经集成到Spring Cloud Alibaba中。

它主要用于:

  • 请求限流
  • 线程隔离
  • 服务熔断
  • 服务降级
  • 实时监控

本课程使用Sentinel来实现微服务保护。

1.2.1.介绍和安装

官方网站: home | Sentinel

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

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

为了方便监控微服务,我们先把Sentinel的控制台搭建出来。

安装控制台步骤:

1. 下载Sentinel Dashboard

下载地址: Releases · alibaba/Sentinel

也可以直接使用课前资料提供的版本:

2. 启动控制台

将jar包放在任意非中文、不包含特殊字符的目录下。

启动命令:

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

参数说明:

参数说明
server.port=8090Sentinel控制台端口
csp.sentinel.dashboard.server控制台地址
project.name应用名称
其它启动时可配置参数可参考官方文档:
启动配置项 · alibaba/Sentinel Wiki
3. 访问控制台

访问:http://localhost:8090就可以看到页面:

账号和密码,默认都是:sentinel

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

1.2.2.微服务整合

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

1. 引入Sentinel依赖
xml
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2. 配置控制台地址

在bootstrap.yaml中配置:

yaml
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8090
3. 访问接口,触发监控

重启cart-service,访问购物车接口:

然后打开Sentinel控制台,可以看到服务和接口监控数据。

4. 簇点链路

点击簇点链路菜单,会看到下面的页面:

Sentinel控制台中的“簇点链路”,可以理解为:

text
当前服务中被Sentinel监控的资源列表。

默认情况下,Spring MVC的每个接口都会成为一个资源。

例如:

text
/carts

但是黑马商城接口是RESTful风格,查询、删除、修改购物车可能都是/carts,只是 请求方式不同,无法区分路径相同但请求方式不同的接口,查询、删除、修改等都被识别为一个簇点资源,这显然是不合适的。

解决办法:开启请求方式前缀。

yaml
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8090
      http-method-specify: true # 开启请求方式前缀

开启后,资源名称会变成:

text
GET:/carts
POST:/carts
DELETE:/carts

重启服务,通过页面访问购物车相关接口,控制台簇点链路发生变化,这样就可以区分不同请求方式的接口:

1.3.请求限流

在簇点链路后面点击流控按钮,即可对其做限流配置:

在弹出的菜单中这样填写: 这样就把查询购物车列表这个簇点资源的流量限制在了每秒6个,也就是最大QPS为6.

我们利用Jemeter做限流测试,我们每秒发出10个请求:

直接使用微服务端口进行访问,就不需要经过网关进行token校验了,但还需要user-info信息:

1000个用户在100秒内陆续启动,每个用户只执行一次请求。 因此总请求数是1000次,平均请求发起速率约为10次/秒。

最终监控结果如下:

接口通过的QPS不超过6,符合预期。

1.4.线程隔离

限流可以降低突发流量带来的压力,但如果远程服务已经变慢或故障,仍然可能拖慢调用方。

比如查询购物车时,需要调用商品服务查询商品信息。如果商品服务阻塞,购物车服务的线程也会被阻塞。

为了避免这种情况,需要对远程调用做线程隔离。

1.4.1.OpenFeign整合Sentienl

修改cart-service模块的application.yml文件,开启Feign的sentinel功能:

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

需要注意的是,默认情况下SpringBoot项目的tomcat最大线程数是200,允许的最大连接是8492,单机测试很难打满。

所以我们需要配置一下cart-service模块的application.yml文件,修改tomcat连接:

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

上面配置代表的含义为:

text
100个连接进入cart-service
  →50个请求由50个线程同时处理
  →另外50个等待
  →继续来的请求可能被拒绝

然后重启cart-service服务,可以看到查询商品的FeignClient自动变成了一个簇点资源: 流程为:

text
购物车业务调用itemClient.queryItemByIds()
  →Feign代理准备发HTTP请求
  →SentinelFeign拦截调用
  →把本次Feign调用登记为一个簇点资源
  →执行请求
  →记录调用次数、响应时间、异常数等指标

1.4.2.配置线程隔离

接下来,点击查询商品的FeignClient对应的簇点资源后面的流控按钮:

在弹出的表单中填写下面内容:

这里勾选的是并发线程数限制,也就是这个查询功能最多使用5个线程,而不是5QPS。如果查询商品接口每秒处理两个请求,则五个现成的实际QPS在10左右,超出的请求会被拒绝。

利用Jemeter测试,每秒发送100个请求:

完整过程:

 JMeter大量发请求
 →最多约50个请求由Tomcat线程执行
 →其中最多5个同时调用item-service
 →其余到达该资源时被Sentinel拒绝

进入查询购物车的请求每秒大概在100,而在查询商品时却只剩下每秒10左右,符合我们的预期。

1.5.服务熔断

线程隔离解决了资源占用问题,但还没有解决业务体验问题。

比如商品服务响应很慢,购物车服务虽然不会被拖垮,但查询购物车时仍然可能变慢或失败。

所以需要两件事:

  1. 编写降级逻辑 远程调用失败、限流、熔断时,返回默认数据或友好结果。
  2. 配置服务熔断 当远程服务慢调用比例或异常比例过高时,直接熔断,不再真正调用远程服务。

1.5.1.编写降级逻辑

FeignClient的降级处理有两种方式:

方式说明
FallbackClass只能写固定降级逻辑,拿不到异常原因
FallbackFactory可以拿到异常原因,更常用

课程中使用FallbackFactory

步骤一:在hm-api模块中给ItemClient定义降级处理类,实现FallbackFactory

代码如下:

java
package com.hmall.api.client.fallback;

import com.hmall.api.client.ItemClient;
import com.hmall.api.dto.ItemDTO;
import com.hmall.api.dto.OrderDetailDTO;
import com.hmall.common.exception.BizIllegalException;
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 ItemClientFallback implements FallbackFactory<ItemClient> {
    @Override
    public ItemClient create(Throwable cause) {
        return new ItemClient() {
            @Override
            public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
                log.error("远程调用ItemClient#queryItemByIds方法出现异常,参数:{}", ids, cause);
                // 查询购物车允许失败,查询失败,返回空集合
                return CollUtils.emptyList();
            }

            @Override
            public void deductStock(List<OrderDetailDTO> items) {
                // 库存扣减业务需要触发事务回滚,查询失败,抛出异常
                throw new BizIllegalException(cause);
            }
        };
    }
}

步骤二:在hm-api模块中的com.hmall.api.config.DefaultFeignConfig类中将ItemClientFallback注册为一个Bean

步骤三:在hm-api模块中的ItemClient接口中使用ItemClientFallbackFactory

重启后,清空汇总报告记录,再次测试,发现被限流的请求不再报错,走了降级逻辑:

但是未被限流的请求延时依然很高:

1.5.2.服务熔断

查询商品响应时间较高,导致查询购物车的响应时间也变得很长。这样不仅拖慢了购物车服务,消耗了购物车服务的更多资源,用户体验也很差。

对于商品服务这种不健康的接口,应该停止调用,直接走降级逻辑,避免影响当前服务。也就是将商品查询接口熔断。当商品服务接口恢复正常后,再允许调用。这就是断路器的工作模式了。

Sentinel断路器不仅可以统计某个接口的慢请求比例,还可以统计异常请求比例。当这些比例超出阈值时,就会熔断该接口。即拦截访问接口的一切请求,降级处理;当接口恢复正常时,再放行对于接口的请求。

断路器的工作状态切换有一个状态机来控制:

Sentinel熔断器有三个状态:

  1. closed:关闭状态
    • 正常放行请求。
    • 统计异常比例和慢调用比例。
    • 超过阈值后进入open。
  2. open:打开状态
    • 服务被熔断。
    • 请求不再真正调用远程服务。
    • 直接走降级逻辑。
    • 持续一段时间后进入half-open。
  3. half-open:半开状态
    • 放行一次请求试探服务是否恢复。
    • 如果成功,回到closed。
    • 如果失败,重新进入open。

我们可以在控制台通过点击簇点后的熔断按钮来配置熔断策略:

在弹出的表格中这样填写:

含义:

text
最近1秒内至少有5次请求;
如果响应时间超过200ms的慢调用比例达到50%;
则触发熔断;
接下来20秒内不再真正调用该接口,直接走降级逻辑。

配置完成后,再次利用Jemeter测试,可以发现:

在一开始一段时间是允许访问的,后来触发熔断后,查询商品服务的接口通过QPS直接为0,所有请求都被熔断了。而查询购物车的本身并没有受到影响。

2.分布式事务

下单业务通常涉及多个微服务:

text
交易服务:保存订单
商品服务:扣减库存
购物车服务:清理购物车

这些操作分布在不同服务、不同数据库中。

如果没有分布式事务,可能出现:

text
订单创建失败,但购物车已经清空
库存扣减失败,但订单已经保存
某个服务成功,另一个服务失败

这会导致数据不一致。

在单体项目中,一个本地事务可以保证ACID。但在微服务中,每个服务有自己的数据库,本地事务只能保证自己服务内部一致,无法保证多个服务整体一致。

这就产生了分布式事务问题。

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

  • 一个业务跨多个微服务。
  • 一个业务跨多个数据库。
  • 一个业务同时操作多个数据源。

2.1.认识Seata

Seata是阿里巴巴开源的分布式事务框架,用来解决微服务中的分布式事务问题。

官方网站:Seata是什么? | Apache Seata

分布式事务的核心思想是:

text
找一个统一的事务协调者,协调各个分支事务,要么一起提交,要么一起回滚。

Seata中有三个重要角色:

角色全称作用
TCTransaction Coordinator事务协调者,维护全局事务和分支事务状态,决定提交或回滚
TMTransaction Manager事务管理器,定义全局事务范围,发起全局事务,提交或回滚全局事务
RMResource Manager资源管理器,管理分支事务,注册分支事务,汇报状态,执行提交或回滚

可以理解为:

text
TM:告诉TC,我要开启一个全局事务。
RM:告诉TC,我这个分支事务执行得怎么样。
TC:根据所有分支状态,决定大家一起提交还是一起回滚。

工作框架如图所示:

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

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

2.2.部署TC服务

2.2.1.准备数据库表

Seata Server需要保存全局事务、分支事务等状态数据。

因此要先准备数据库表。

执行课程资料中的:

text
seata-tc.sql

导入Seata TC需要的表。

2.2.2.准备配置文件

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

将整个seata文件夹拷贝到服务器的目录:

2.2.3.Docker部署

部署前要保证:nacos、mysql、seata在同一个Docker网络中,例如hm-net。

如果容器不在同一个网络,可以执行:

bash
docker network connect hm-net <容器>

启动Seata Server:

bash
docker run -d \
    --name seata \
    --network hm-net \
    -p 127.0.0.1:8099:8099 \
    -p 127.0.0.1:7099:7099 \
    -e SEATA_IP=127.0.0.1 \
    -e SEATA_PORT=8099 \
    -v /home/ubuntu/seata:/seata-server/resources \
    --restart=always \
    seataio/seata-server:1.5.2

2.3.微服务集成Seata

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

2.3.1.引入依赖

为了方便各个微服务集成seata,我们需要把seata配置共享到nacos,因此trade-service模块不仅仅要引入seata依赖,还要引入nacos依赖:

xml
<!--统一配置管理-->
  <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.3.2.改造配置

首先在nacos上添加一个共享的seata配置,命名为shared-seata.yaml

内容如下:

yaml
seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型nacos
    nacos:
      server-addr: 虚拟机地址: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"

如果是远程服务器需要使用文件形式. Seata使用Nacos注册中心时是两段连接:

text
  1.微服务→127.0.0.1:8848→SSH隧道→Nacos
  2.Nacos返回Seata注册地址172.22.0.4:8099
  3.微服务→172.22.0.4:8099

SSH的8848隧道只转发Nacos连接,不会自动转发Nacos返回的172.22.0.4:8099。

yaml
seata:
  registry:
    #不再通过Nacos查找Seata Server,直接连接指定地址
    type: file
  tx-service-group: hmall
  service:
    #事务组hmall映射到default集群
    vgroup-mapping:
      hmall: default
    #default集群的Seata Server地址
    grouplist:
      default: 127.0.0.1:18099

然后改造trade-service模块,添加bootstrap.yaml

内容如下:

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-jdbc.yaml # 共享mybatis配置
          - dataId: shared-log.yaml # 共享日志配置
          - dataId: shared-swagger.yaml # 共享日志配置
          - dataId: shared-seata.yaml # 共享seata配置

然后改造application.yaml文件,内容如下:

yaml
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

item-servicecart-service也需要按类似方式改造。

2.3.3.添加数据库表

seata的客户端在解决分布式事务的时候需要记录一些中间数据,保存在数据库中。因此我们要先准备一个这样的表。

将课前资料的seata-at.sql分别文件导入hm-trade、hm-cart、hm-item三个数据库中:

2.3.4.测试

找到trade-service模块下的com.hmall.trade.service.impl.OrderServiceImpl类中的createOrder方法,也就是下单业务方法。

将其上的@Transactional注解改为Seata提供的@GlobalTransactional

@GlobalTransactional注解就是在标记事务的起点,将来TM就会基于这个方法判断全局事务范围,初始化全局事务。

重启trade-serviceitem-servicecart-service三个服务,进行测试,进入购物车页面,结算下单,进入订单结算页面,然后在数据库中将商品库存修改为0,然后提交订单,最终因库存不足导致下单失败, 没有全局事务购物车数据会被清空,而现在没有。

2.4.XA模式

Seata支持多种分布式事务模式:

  • XA
  • TCC
  • AT
  • SAGA

本课程重点理解:

text
XA模式
AT模式

XA是一种分布式事务规范,XA规范描述了全局的TM与局部的RM之间的接口,很多主流关系型数据库都支持XA。

2.4.1.两阶段提交

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

正常情况:

异常情况:

一阶段:

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

二阶段:

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

2.4.2.Seate的XA模型

Seata对原始的XA模式做了简单的封装和改造,以适应自己的事务模型,基本架构如图: RM一阶段的工作:

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

TC二阶段的工作:

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

RM二阶段的工作:

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

2.4.3.优缺点

XA模式优点:

  1. 强一致性,满足ACID。
  2. 主流关系型数据库普遍支持。
  3. 对业务代码侵入较少。
  4. 原理清晰,实现简单。

XA模式缺点:

  1. 一阶段不提交,数据库锁持有时间长。
  2. 性能较差。
  3. 依赖数据库XA能力。
  4. 不适合高并发、长事务场景。

2.4.4.实现步骤

首先,我们要在配置文件中指定要采用的分布式事务模式。我们可以在Nacos中的共享shared-seata.yaml配置文件中设置: 具体内容如下:

yaml
seata:
  registry:
    #不再通过Nacos查找Seata Server,直接连接指定地址
    type: file
  tx-service-group: hmall
  service:
    #事务组hmall映射到default集群
    vgroup-mapping:
      hmall: default
    #default集群的Seata Server地址
    grouplist:
      default: 127.0.0.1:18099
  data-source-proxy-mode: XA

其次,我们要利用@GlobalTransactional标记分布式事务的入口方法:

2.5.AT模式

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

2.5.1.Seata的AT模型

基本流程图: 阶段一RM的工作:

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

阶段二提交时RM的工作:

  • 删除undo-log即可

阶段二回滚时RM的工作:

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

2.5.2.流程梳理

以扣减余额为例。

原始数据:

idmoney
1100

业务SQL:

sql
update tb_account set money = money - 10 where id = 1;

AT模式流程:

一阶段

  1. TM向TC发起全局事务。
  2. TM调用分支事务。
  3. RM拦截业务SQL。
  4. RM根据where条件查询修改前数据,生成前置快照:
json
{
  "id": 1,
  "money": 100
}
  1. RM执行业务SQL:
sql
update tb_account set money = money - 10 where id = 1;
  1. 数据变为:
json
{
  "id": 1,
  "money": 90
}
  1. RM记录undo_log。
  2. RM提交本地事务,释放数据库锁。
  3. RM向TC报告分支事务成功。
  • 二阶段提交

如果所有分支事务都成功:

text
TC通知RM提交全局事务;
RM删除undo_log;
事务结束。
  • 二阶段回滚

如果某个分支事务失败:

text
TC通知RM回滚;
RM读取undo_log;
把money从90恢复到100;
删除undo_log。

流程图:

2.5.3.AT与XA区别

对比项XA模式AT模式
一阶段是否提交不提交直接提交
是否长期持有数据库锁
回滚方式依赖数据库XA机制依赖undo_log数据快照
一致性强一致最终一致
性能较低较高
业务侵入
使用场景强一致要求高大多数普通业务场景

核心区别:

  1. XA一阶段不提交,AT一阶段直接提交。
  2. XA依赖数据库事务机制,AT依赖undo_log回滚。
  3. XA强一致但性能差,AT性能更好但属于最终一致。
  4. AT使用简单、侵入低,企业中使用更常见。