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
公众号:服务端技术精选
评论