首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >滚动发布大量 RPC 调用失败!藏了3年的 Spring Cloud 历史遗留大坑,90%Nacos集群全踩

滚动发布大量 RPC 调用失败!藏了3年的 Spring Cloud 历史遗留大坑,90%Nacos集群全踩

原创
作者头像
用户12591013
修改2026-07-01 16:08:12
修改2026-07-01 16:08:12
860
举报
文章被收录于专栏:踩坑集踩坑集

前言

K8s 滚动发布、Nacos 主动下线实例,自以为做到了平滑无感升级,结果每次发版都批量报 RPC 调用异常。

我们做了完整优雅下线逻辑:新 Pod 就绪再停旧 Pod、进程销毁主动调用 Nacos 接口摘除自身,理论上调用方应当实时感知实例下线。排查 3 年无人定位根因,直到开发灰度路由才揪出元凶:

代码语言:txt
复制
spring:cloud.loadbalancer.cache.enabled=true 默认开启的本地缓存。

这套缓存是为老旧 Eureka 设计的冗余产物,适配 Nacos2.x 长连接实时推送完全多余,本文拆解历史设计缺陷、线上故障根源、一键根治配置。


一、业务发布方案:我们以为完美的平滑下线流程

1. 集群环境

  • 微服务框架:Spring Cloud + Spring Cloud Alibaba
  • 注册中心:Nacos 2.x(gRPC 长连接、服务变更实时推送)
  • 部署载体:K8s,标准滚动更新策略

2. 完整下线保障逻辑

  1. K8s 滚动发布:先启动新 Pod,健康探针就绪后,再销毁旧 Pod;
  2. 应用内置销毁钩子:收到停机信号后,主动调用 Nacos OpenAPI,将当前实例从注册中心摘除;
  3. 预期效果:Nacos 服务端瞬间剔除下线实例,所有消费端通过长连接收到变更推送,负载均衡不再路由下线节点,RPC 报错几乎为 0。

3. 诡异线上现象

每轮滚动发布,都会持续数十秒出现大量 RPC 连接失败、超时报错;

Nacos 控制台已经看不到下线 Pod 实例,但调用方依旧持续路由到已销毁节点,报错峰值持续 30s+。

我们反复核对下线代码、Nacos 推送日志、K8s 发布流程,全部无异常,故障持续近 3 年无人定位。


二、根因定位:Spring Cloud LoadBalancer 内置缓存拦死实时推送

核心元凶配置

yaml

代码语言:javascript
复制
spring:
  cloud:
    loadbalancer:
      cache:
        enabled: true # 默认开启,全公司项目无一人主动关闭

底层机制拆解

Spring Cloud LoadBalancer 默认包装一层 CachingServiceInstanceListSupplier,逻辑流程:

  1. 每次 RPC 发起负载均衡时,优先读取本地内存缓存中的服务实例列表;
  2. 缓存默认 TTL=35 秒,35 秒内不会主动向 Nacos 客户端拉取最新实例;
  3. Nacos2.x 虽然通过 gRPC 长连接实时推送实例变更,但无法主动清除 LoadBalancer 本地缓存

就算 Nacos 客户端秒级收到下线推送,消费端 LoadBalancer 依旧拿着 35 秒前的旧实例列表分发流量,这就是发布期批量报错的直接原因。

源码实锤:默认 35 秒 TTL 的硬编码

从 Spring Cloud LoadBalancer 源码可以直接看到,缓存 TTL 被硬编码为 35 秒,这就是我们线上 30 + 秒报错窗口的直接来源:

代码语言:javascript
复制
// 源码片段:LoadBalancerCacheProperties.java
private Duration ttl = Duration.ofSeconds(35);

注释明确标注:Time To Live - time counted from writing of the record, after which cache entries are expired. (TTL:从记录写入开始计算,缓存条目过期的时间,默认 35 秒)


三、深挖历史设计:一套缓存兼容全注册中心,却给 Nacos 埋致命坑

1. 缓存诞生的历史背景(适配 Eureka 1.x)

早期 Spring Cloud 主流注册中心是 Eureka,Eureka Client 采用HTTP 定时轮询拉取服务列表(默认 30s 一次),每次拉取都要建立 HTTP 请求,网络开销大。

为了减少注册中心压力、降低 HTTP 请求耗时,Spring Cloud 官方设计了 LoadBalancer 本地缓存:

  • 缓存复用实例列表,避免每次 RPC 都发起网络请求;
  • 适配 Eureka 轮询架构,是当年性能优化的合理方案。

2. Nacos2.x 时代,缓存彻底沦为冗余累赘

Nacos 2.x 重构底层通信模型,抛弃 HTTP 轮询,改用gRPC 长连接双向推送

  1. 服务上下线变更,Nacos 服务端毫秒级推送全部在线消费端;
  2. Nacos 客户端内存实时维护最新实例清单,无需定时拉取; 此时 LoadBalancer 的本地缓存完全多余,相当于在实时数据流外层套了一层 35 秒延迟屏障。

3. 官方无法删除缓存的妥协设计

Spring Cloud Commons 要统一抽象层,兼容 Eureka、Consul、Zookeeper、Nacos 全注册中心,不能为 Nacos 单独移除缓存逻辑。为了 API 一致性,只能保留缓存开关,默认开启,导致所有使用 Nacos 的团队,都被动吃下 35 秒流量延迟的大坑。


四、故障放大的关键细节:我们的下线流程完全失效

我们实现了进程主动摘除 Nacos 实例,但只能修改 Nacos 服务端数据,无法穿透清除消费端 LoadBalancer 缓存:

  1. 旧 Pod 主动摘除 → Nacos 服务端删除实例 → Nacos 客户端收到推送更新本地列表;
  2. 消费端发起 RPC,LoadBalancer 优先读取 35 秒未过期的缓存旧列表;
  3. 流量持续打向已销毁 Pod,直到缓存 TTL 到期刷新,报错持续数十秒。

这套为平滑发布设计的优雅下线机制,被一层默认开启的缓存彻底废掉,近 3 年无人发现该配置。


五、极简根治方案,一行配置彻底解决发布报错

修复配置(全局关闭 LoadBalancer 实例缓存)

yaml

代码语言:javascript
复制
spring:
  cloud:
    loadbalancer:
      cache:
        enabled: false

关闭缓存后的执行逻辑

  1. 每次负载均衡获取实例,直接调用 Nacos Client 读取实时更新的本地实例清单;
  2. 完全消除 35 秒缓存延迟,Nacos 推送的变更立刻生效;
  3. 滚动发布期间,下线实例瞬间从负载均衡候选列表剔除,RPC 失败率几乎归零。

性能顾虑解答

很多人担心关闭缓存会频繁查询 Nacos,压测验证结论:

Nacos Client 自身已经维护实时实例内存缓存,调用getInstances纯内存操作,无网络 IO,关闭 LoadBalancer 缓存不会增加注册中心压力,无性能损耗。

六、线上落地规范 & 避坑总结

  1. 所有使用 Nacos 2.x 的微服务,统一关闭 LoadBalancer 缓存,新增项目直接默认配置cache.enabled=false
  2. 不要死记 Spring Cloud 通用模板,区分 Eureka 与 Nacos 底层通信差异,通用兼容设计不一定适配你的注册中心;
  3. 滚动发布、灰度、动态扩缩容场景,本地缓存是隐形杀手,凡是依赖实时服务感知的业务,必须禁用负载均衡缓存;
  4. 框架通用兼容层存在大量历史遗留设计,不能无脑使用默认配置,要结合中间件底层通信模型判断配置合理性。


结束语

绝大多数线上长期疑难故障,都不是业务代码 Bug,而是框架为兼容旧组件留下的历史遗留设计陷阱。

如果只会复制粘贴通用配置、不深究底层通信原理,就算做了完整的平滑发布方案,依旧会持续忍受发布期批量报错。

吃透 Spring Cloud LoadBalancer 缓存的历史成因、Nacos 长连接推送机制,才能真正实现零故障无感滚动发布。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 一、业务发布方案:我们以为完美的平滑下线流程
      • 1. 集群环境
      • 2. 完整下线保障逻辑
      • 3. 诡异线上现象
    • 二、根因定位:Spring Cloud LoadBalancer 内置缓存拦死实时推送
      • 核心元凶配置
      • 底层机制拆解
      • 源码实锤:默认 35 秒 TTL 的硬编码
    • 三、深挖历史设计:一套缓存兼容全注册中心,却给 Nacos 埋致命坑
      • 1. 缓存诞生的历史背景(适配 Eureka 1.x)
      • 2. Nacos2.x 时代,缓存彻底沦为冗余累赘
      • 3. 官方无法删除缓存的妥协设计
    • 四、故障放大的关键细节:我们的下线流程完全失效
    • 五、极简根治方案,一行配置彻底解决发布报错
      • 修复配置(全局关闭 LoadBalancer 实例缓存)
      • 关闭缓存后的执行逻辑
      • 性能顾虑解答
    • 六、线上落地规范 & 避坑总结
    • 结束语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档