Spring Cloud Gateway 路由节点健康检查盲区:实例假在线导致 502?主动探测 + 权重动态剔除!

公司微服务上了 Spring Cloud Gateway,一切正常直到有一天运维发现某个下游服务挂了,Gateway 还是把流量往那台死掉的实例上发,前端一阵 502。查了注册中心,Nacos 上那台实例的状态还是 UP。原来是服务进程虽然活着,但业务线程池被耗尽了,所有请求都在排队超时。Nacos 的心跳包是单独的线程处理的,业务线程池死了不影���心跳,所以注册中心一直认为它是健康的。

这就是"假在线"——健康检查过了不代表服务能用。今天聊聊怎么在 Gateway 层做主动业务探测,把假在线的节点从路由表里动态踢出去。


注册中心健康检查的盲区

Nacos、Eureka 这些注册中心判断服务是否健康,靠的是心跳。客户端定期发一个心跳包给注册中心,注册中心收到了就认为服务活着。

但心跳包只能证明网络没断、进程没死。以下场景心跳都是正常的:

  • 业务线程池打满,所有请求都在排队超时
  • 数据库连接池耗尽,每次数据库查询都失败
  • 依赖的下游全挂了,返回的全是 500
  • 死锁,只有心跳线程还在工作

这些情况下,注册中心说服务是 UP,Gateway 就把流量发过去,结果全是 502 或 500。


主动探测:Gateway 自己去判断节点能不能用

思路很直接:Gateway 定期给每个后端实例发一个探测请求,只有探测通过了才认为这个节点健康。

Spring Cloud Gateway 本身没有内置这个功能,但可以自己实现一个 HealthCheckFilter

主动探测流程:

  探测线程(每 10 秒):
    for each 后端实例:
      GET http://实例IP/actuator/health/business
        → 返回 200 且 {"business": "UP"} → 标记健康
        → 返回 500 或超时 → 标记不健康,动态剔除

  路由时:
    只从健康的实例中选择
    不健康的实例不参与负载均衡

关键点:不要依赖注册中心的心跳,而是自己发业务请求去验证。 /actuator/health 默认只检查磁盘空间、数据库连接等基础指标。你可以自定义一个 HealthIndicator,检查业务线程池是否满载、关键依赖是否可达:

@Component
public class BusinessHealthIndicator implements HealthIndicator {
    @Override
    public Health health() {
        // 检查业务线程池
        if (threadPool.getActiveCount() >= threadPool.getMaximumPoolSize()) {
            return Health.down().withDetail("reason", "线程池已满").build();
        }
        // 检查关键依赖
        if (!cacheService.ping()) {
            return Health.down().withDetail("reason", "缓存不可用").build();
        }
        return Health.up().build();
    }
}

Gateway 探测这个端点,线程池满了就返回 DOWN,Gateway 就自动把它摘掉。直到线程池恢复,探测重新通过,再把它加回来。


权重动态剔除:别一刀切,慢慢减流

直接把一个节点摘掉是个很"暴力"的操作。如果 5 个节点里 2 个被摘掉,剩下 3 个瞬间扛 100% 流量,可能跟着被打爆。

更好的做法是降权而非摘除:先降低不健康节点的流量权重,观察一段时间,持续不健康再彻底摘掉。

权重动态调整:

  探测连续 2 次不健康 → 权重降到 20%
  探测连续 3 次不健康 → 权重降到 0%(彻底剔除)
  探测恢复健康 → 权重逐步恢复到 100%

Spring Cloud Gateway 配合 Spring Cloud LoadBalancer 可以自定义负载均衡规则:

public class HealthAwareLoadBalancer implements ReactorLoadBalancer<ServiceInstance> {

    // 维护每个实例的健康状态和权重
    private final Map<String, InstanceHealth> healthMap = new ConcurrentHashMap<>();

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        List<ServiceInstance> instances = getInstances();
        List<ServiceInstance> healthy = instances.stream()
            .filter(i -> healthMap.get(i.getInstanceId()).weight > 0)
            .collect(toList());

        if (healthy.isEmpty()) {
            // 全挂了,降级返回兜底响应或全部放行
            return Mono.just(EmptyResponse.getInstance());
        }

        // 按权重随机选择
        return Mono.just(Response.just(weightedRandom(healthy)));
    }
}

兜底机制:全挂了也要死得体面

当所有节点都被标记为不健康时,有两种选择:

返回兜底响应——返回一个预设的降级 JSON,比如 {"code": -1, "message": "服务暂时不可用"}。总比让用户看 502 强。

全部放行——既然全挂了,那就全放,万一有一个能扛住呢。死马当活马医,比全军覆没强。

推荐方案一 + 方案二组合:先尝试降级响应,如果业务场景不适合降级(比如支付接口),再全部放行碰运气。

spring:
  cloud:
    gateway:
      default-filters:
        - name: CircuitBreaker
          args:
            name: fallback
            fallbackUri: forward:/fallback/busy

总结

注册中心的心跳只证明"人活着",不证明"能干活"。Gateway 层自己主动探测,才能发现假在线的节点。

三个要点:

自定义业务健康端点——Actuator 的默认 /health 粒度太粗。加上线程池状态、关键依赖可达性等业务维度,让健康检查真正反映服务能力。

主动探测 + 权重降级——Gateway 定期发探测请求,不健康的先降权后摘除,别一刀切。

兜底响应——全挂了至少给用户一个优雅的提示,别裸 502。

这套方案落地后,假在线节点从被发现到被摘除,延迟不超过两个探测周期。


有用的话转给还在被 502 困扰的网关运维。


标题:Spring Cloud Gateway 路由节点健康检查盲区:实例假在线导致 502?主动探测 + 权重动态剔除!
作者:jiangyi
地址:http://jiangyi.space/articles/2026/06/11/1780757794752.html
公众号:服务端技术精选
    评论
    0 评论
avatar

取消