Skip to content

Spring Cloud:网关、熔断与限流 ​

微服务系统拆开之后,真正难的往往不是“服务能不能调通”,而是:

  1. 外部流量怎么统一接入
  2. 下游服务慢了怎么办
  3. 某个服务挂了会不会把整条调用链拖死
  4. 流量过大时怎么保护系统

这篇文章就集中讲 3 条常见治理主线:

  1. 网关
  2. 熔断与降级
  3. 限流与系统保护

1. 为什么微服务一定会遇到统一入口问题 ​

在单体应用里,外部请求通常直接进入一个应用就够了。

但在微服务架构里,系统往往会拆成:

  1. 用户服务
  2. 订单服务
  3. 库存服务
  4. 支付服务
  5. 营销服务

如果外部请求直接对接每个服务,会带来很多问题:

  1. 路由分散
  2. 鉴权分散
  3. 限流分散
  4. 日志和监控分散
  5. 对外暴露面过多

所以系统通常需要一个统一入口。


2. 什么是网关 ​

网关 可以理解成 系统外部流量进入内部服务之前的统一入口层。

它解决的是:

  1. 请求先到哪里
  2. 根据什么规则路由到哪个服务
  3. 哪些通用逻辑应该在入口统一做

例如常见的统一逻辑包括:

  1. 路由转发
  2. 身份认证
  3. 权限校验
  4. 限流
  5. 日志和链路信息透传

3. Spring Cloud Gateway 主要解决什么问题 ​

Spring Cloud Gateway 是 Spring Cloud 体系里很常见的网关方案。

它主要解决的是 如何在 Spring 生态里统一做路由、过滤和流量治理。

它更适合被理解成:一个面向微服务入口治理的网关框架。


4. 网关到底在做什么 ​

一条请求经过网关时,常见流程通常是:

  1. 请求先进入网关
  2. 网关根据路由规则判断目标服务
  3. 执行一组过滤逻辑
  4. 再把请求转发到下游服务
  5. 拿到响应后再统一返回

这里最重要的两个关键词是:

  1. 路由
  2. 过滤

4.1 路由 ​

路由解决的是:这条请求应该被转发到哪个服务。

4.2 过滤 ​

过滤解决的是:请求在进出系统时,要统一做哪些前置和后置处理。

例如:

  1. 加 token 校验
  2. 打日志
  3. 加链路追踪头
  4. 统一异常返回

4.3 一条订单路由经过网关时会发生什么 ​

看一个比较典型的 Spring Cloud Gateway 配置:

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: order-service-route
          # lb 表示通过注册中心按服务名转发,而不是写死某台机器地址
          uri: lb://order-service
          predicates:
            # 只有命中这个路径规则的请求,才会进入当前路由
            - Path=/api/orders/**
          filters:
            # /api/orders/create -> /orders/create,避免下游服务重复兼容网关前缀
            - StripPrefix=1
            # 给下游服务补一个统一请求头,方便日志和来源识别
            - AddRequestHeader=X-Gateway, order-gateway
            # 订单服务连续失败时,直接走降级地址,不再继续放大故障
            - CircuitBreaker=name=orderCircuitBreaker,fallbackUri=forward:/fallback/orders
            # 按用户维度做限流,避免某个用户把入口流量瞬间打满
            - RequestRateLimiter=redis-rate-limiter.replenishRate=20,redis-rate-limiter.burstCapacity=40,key-resolver=#{userKeyResolver}

这条路由的含义可以直接按执行顺序理解:

  1. 外部请求命中 /api/orders/**
  2. 网关把它转发到 lb://order-service
  3. 转发前先去掉 /api 这层前缀
  4. 补一个统一请求头给下游服务识别来源
  5. 如果下游持续失败,就走熔断降级地址
  6. 如果同一个用户的请求速率超阈值,就直接在网关层被限流

如果还想把用户标识、链路信息之类的统一透传给下游服务,工程上通常会补一个全局过滤器:

java
/**
 * 网关全局过滤器,负责补充统一的用户和链路透传信息。
 */
@Component
public class TraceUserGlobalFilter implements GlobalFilter, Ordered {

    /**
     * 在请求转发给下游前补全 `traceId` 和 `userId` 请求头。
     *
     * @param exchange 当前请求上下文
     * @param chain 网关过滤器链
     * @return 继续执行后的响应链路
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 用户标识优先取上游已经解析好的请求头
        String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
        // 如果调用方没带 traceId,就由网关统一补一个
        String traceId = Optional.ofNullable(exchange.getRequest().getHeaders().getFirst("X-Trace-Id"))
                .orElse(UUID.randomUUID().toString());

        // 统一把链路信息和用户信息透传给下游服务
        ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
                .header("X-Trace-Id", traceId)
                .header("X-User-Id", userId == null ? "anonymous" : userId)
                .build();

        return chain.filter(exchange.mutate().request(mutatedRequest).build());
    }

    /**
     * 指定过滤器执行顺序,值越小越早执行。
     *
     * @return 当前过滤器的顺序值
     */
    @Override
    public int getOrder() {
        return -100;
    }
}

执行结果可以这样理解:

  1. 路由规则统一决定“请求该去哪个服务”
  2. 过滤器统一决定“请求在转发前后要补什么治理动作”
  3. 下游订单服务不需要自己重复处理网关层已经做掉的公共逻辑

5. 为什么不能把所有治理逻辑都塞进业务服务 ​

因为像鉴权、限流、黑白名单、统一路由这些能力,本质上都不是单个业务服务独有的问题。

如果每个服务都各自实现一遍,就会出现:

  1. 规则重复
  2. 行为不一致
  3. 维护成本很高
  4. 风险扩散到每个服务

所以更自然的思路是:入口层能统一治理的事情,尽量不要分散到所有服务里。


6. 什么是熔断 ​

熔断 可以理解成 当系统发现某个下游服务持续失败或持续超时时,先主动切断对它的请求,避免故障继续放大。

它解决的是 下游已经明显异常时,不要让上游继续无意义地把流量砸过去。

这和电路里的保险丝思路有点像:

一旦故障持续,就先断开,避免全链路被拖坏。


7. 为什么熔断很重要 ​

微服务调用链一长,故障传播速度会非常快。

例如:

  1. 网关调用订单服务
  2. 订单服务调用库存服务
  3. 库存服务调用商品服务

如果商品服务持续变慢,那么:

  1. 库存服务线程会被占住
  2. 订单服务调用也会被拖慢
  3. 网关请求堆积
  4. 最终整个系统都变慢

熔断就是在这个时候发挥作用:不要让一个局部故障,最终演变成全链路雪崩。


8. 熔断、降级、重试分别是什么关系 ​

这几个词经常一起出现,所以要分开看。

8.1 熔断 ​

重点是:发现下游持续失败后,先主动切断调用。

8.2 降级 ​

重点是:当正常能力不可用时,给出一个更弱但可接受的兜底结果。

例如:

  1. 返回默认值
  2. 返回缓存值
  3. 暂时关闭非核心能力

8.3 重试 ​

重点是:某次调用失败后,再试一次或几次。

但要特别注意:重试不是万能的。

如果下游已经过载,盲目重试反而可能把系统打得更重。


9. 什么是限流 ​

限流 可以理解成 当请求量过大时,对进入系统或进入某个服务的流量做速率控制。

它解决的是 不要让流量无限涌入,把系统资源瞬间打穿。

限流的常见场景包括:

  1. 网关入口限流
  2. 热点接口限流
  3. 下游依赖保护
  4. 秒杀和营销活动保护

10. 为什么限流和熔断经常一起出现 ​

因为它们都属于系统保护能力,但保护角度不同。

  1. 限流更偏“事前控制”
  2. 熔断更偏“事中止损”

把这两个动作拆开看:

  1. 限流:流量太多时,先卡入口
  2. 熔断:下游持续异常时,先切调用

两者结合,系统才更容易稳住。


11. 常见弹性治理思路怎么理解 ​

在 Spring Cloud 语境里,常见弹性治理通常会围绕这些能力展开:

  1. 超时控制
  2. 重试
  3. 熔断
  4. 降级
  5. 限流
  6. 隔离

11.1 超时控制 ​

它解决的是 一个请求不能无限等下游。

11.2 隔离 ​

它解决的是 某条调用链或某类资源出问题时,不要拖垮整个系统。

11.3 降级 ​

它解决的是 服务不稳定时,系统还能不能以更弱能力继续提供核心功能。


12. Resilience4j、Sentinel 这类能力应该怎么理解 ​

这些名字经常会和 Spring Cloud 一起出现。

更稳妥的理解是:它们主要是在补弹性治理和系统保护能力。

也就是说,它们关注的不是“服务发现”,而是:

  1. 熔断
  2. 限流
  3. 降级
  4. 系统保护

一句话区分:

  1. 注册中心解决“服务在哪”
  2. 网关解决“流量怎么进”
  3. 弹性治理组件解决“故障和流量冲击怎么控”

12.1 熔断降级和限流是怎么配合的 ​

上面的路由只是把 CircuitBreaker 和 RequestRateLimiter 挂上去了,真正运行时还需要补两个小部件:

java
/**
 * 网关侧的限流相关配置。
 */
@Configuration
public class GatewayRateLimitConfig {

    /**
     * 生成限流维度的 Key。
     *
     * @return 优先按用户 ID,缺失时退化为按来源 IP 限流的 `KeyResolver`
     */
    @Bean
    public KeyResolver userKeyResolver() {
        // 优先按用户维度限流;如果没有登录态,就退化成按来源 IP 限流
        return exchange -> Mono.just(
                Optional.ofNullable(exchange.getRequest().getHeaders().getFirst("X-User-Id"))
                        .orElse(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress())
        );
    }
}

/**
 * 熔断打开后的降级返回控制器。
 */
@RestController
public class GatewayFallbackController {

    /**
     * 返回订单服务不可用时的统一降级结果。
     *
     * @return 标识当前请求已进入降级链路的响应体
     */
    @GetMapping("/fallback/orders")
    public Map<String, Object> orderFallback() {
        // 熔断打开后,网关直接返回一个可预期的降级结果
        return Map.of(
                "code", 503,
                "message", "订单服务暂时不可用,请稍后重试",
                "degraded", true
        );
    }
}

如果要把熔断器的阈值也落到配置里,通常还会这样配:

yaml
resilience4j:
  circuitbreaker:
    instances:
      orderCircuitBreaker:
        # 最近 20 次调用作为统计窗口
        slidingWindowSize: 20
        # 至少收集到 10 次调用后,才开始判断是否需要熔断
        minimumNumberOfCalls: 10
        # 失败率达到 50% 就打开熔断器
        failureRateThreshold: 50
        # 熔断打开 10 秒后,再尝试放少量流量探测服务是否恢复
        waitDurationInOpenState: 10s

把它们串起来看,请求链路通常是这样的:

  1. 正常情况下,请求通过网关并转发到订单服务
  2. 某个用户的请求速率超过阈值时,网关会优先在入口直接限流
  3. 即使通过了限流,如果订单服务持续超时或报错,熔断器会在失败率达到阈值后直接打开
  4. 熔断打开后,请求不再继续打到异常服务,而是快速返回 /fallback/orders 的降级结果

这也是为什么工程上常说:

  1. 限流更偏入口保护
  2. 熔断更偏故障止损
  3. 降级更偏用户侧兜底体验

13. 工程上最常见的几个误区 ​

13.1 误区一:有网关就等于系统入口治理已经做好 ​

网关只是入口载体。

真正关键的是:

  1. 路由规则是否清晰
  2. 鉴权是否统一
  3. 限流是否合理
  4. 观测是否到位

13.2 误区二:加了重试就更稳定 ​

不一定。

如果下游已经过载,重试很可能是在放大流量。

13.3 误区三:熔断就是返回报错 ​

熔断更重要的意义在于“快速失败和保护系统”,而不是单纯给用户看一个错误。

13.4 误区四:限流只在秒杀场景才需要 ​

不是。

只要存在热点接口、突发流量、下游承载有限的情况,限流都可能有必要。


14. 一句话总结 ​

网关解决的是“流量统一接入和治理”,熔断解决的是“故障扩散怎么止损”,限流解决的是“流量过大时怎么保护系统”,它们一起构成了 Spring Cloud 微服务治理里最关键的一层系统保护能力。

基于 VitePress 构建的个人技术笔记。