当前位置: 首页 > news >正文

在 Nacos 点了下线,为什么流量还是打到了停机的机器上?

前两天面试问了一个非常日常的问题:

你们线上发版的时候,怎么保证旧服务下线时,用户的请求不报错?

他挺自信地回答:这很简单啊,去 Nacos 控制台找到那个实例,点一下下线按钮。或者在发布脚本里直接kill -15杀进程,Nacos 收不到心跳自然就把节点剔除了,网关就不会往这台机器发流量了。

额~

在真实的生产环境下,如果发布流水线真的只是这么干,那每次发版的一两分钟里,你们的网关日志里一定会刷出一堆Connection Refused或者Read Timeout的报错。

如果刚好赶上晚高峰,用户的直观体验就是:点了一下按钮,界面弹出一个醒目的网络异常

1. 流量为什么停不住?

很多同学对注册中心有一个误解,觉得它是一个绝对实时的系统:只要 Nacos 知道某个节点下线了,所有依赖的微服务就瞬间都知道了。

但在分布式系统里,信息的传递是需要时间的。

服务调用方比如 Gateway 网关在发起 HTTP 请求前,需要知道目标服务的 IP。但为了性能,网关不可能每来一个请求都去 Nacos 查一次网络。

会用像 Ribbon 或者 SpringCloud LoadBalancer 这样的客户端负载均衡组件,会在自己的内存里维护一个ServerList 服务 IP 列表缓存

坑就踩在这个缓存的更新机制上:

  1. 调用方默认会启动一个后台定时任务,每隔一段时间去 Nacos 拉取最新的实例列表。

  2. 虽然 Nacos 服务端也有 UDP 实时推送变更的机制,但在复杂的生产网络里,UDP 推送是有概率丢失的。

  3. 这就导致了一个长达几十秒的时间差。

在这几十秒里,虽然 Nacos 服务端已经把那个节点标记为下线了,但网关本地的缓存还没刷新,网关手里拿着的依然是旧名单。

现在回想一下你在控制台点下线后的操作:

你调用了下线,紧接着就触发了重启(进程被杀掉)。但此时网关还没更新缓存,依然把用户的请求往这台已经死掉的机器上送。

操作系统一看端口都没人监听了,直接回一个 RST 包,网关马上就报Connection Refused了。

2. 中小团队的解法

知道了原因,解决思路也就有了。很多中小团队的做法是:在 CI/CD 发布流水线里加一行sleep 40

流程变成了这样:

  1. 主动摘流,脚本先调 Nacos 的 OpenAPI,把机器标记为下线。

  2. 脚本强行sleep 40秒。在这 40 秒里,机器还在正常运行,依然在处理请求,只是我们在这 40 秒里等待全网所有的网关更新完缓存。

  3. 优雅停机40 秒一过,没人往这发请求了,再发送kill -15杀进程。

这套方案确实管用。但如果你拿着这个方案去大厂面试,依然是不及格的。原因很简单:太慢了且不具备普适性。

如果一个核心服务有 500 个节点,滚动发布时每个节点都要干等 40 秒,发一次版要好几个小时。

而且单纯靠等,根本无法应对物理机突然宕机、网络抖动等突发情况,因为机器意外宕机时,你根本没机会去 sleep。

3. 大厂的无损下线方案

第一步编排层摘流

正规一点的公司下线动作不该由 Jenkins 脚本控制,而应该与 K8s 的 Pod 生命周期深度绑定。

当 K8s 决定缩容或更新一个 Pod 时,它不会立刻发送kill -15,而是会先触发一个生命周期前置钩子PreStop Hook。在 K8s 的 yaml 里这么写:

lifecycle: preStop: exec: command: - /bin/sh - -c - | # 1. 立即向 Nacos 发送下线请求,主动摘除自己 curl -X PUT "http://nacos-server:8848/nacos/v1/ns/instance?serviceName=my-service&ip=${POD_IP}&port=8080&enabled=false" # 2. 象征性地短暂停顿 3~5 秒,给 Nacos 的 UDP 主动推送一点时间 sleep 5

这一步把主动下线的逻辑封装在了容器内部,只要 Pod 要死,临死前第一件事就是去注册中心销户

第二步客户端重试

即使有 PreStop,依然无法 100% 避免在那几秒钟里,有残余的请求顺着网关的旧缓存打过来。

当这台机器停止接收新请求时,网关打过来的请求会立刻收到操作系统的Connection Refused。注意这个极其关键的细节:ConnectException发生在 TCP 三次握手阶段,此时 HTTP 业务数据根本还没有发出去!

既然业务没执行,那这个请求就是绝对安全、绝对幂等的。

所以我们在网关或上游微服务里,必须开启底层的重试机制。以 SpringCloud 为例,只要加上:

spring: cloud: loadbalancer: retry: enabled: true # 开启客户端重试

此时发生的奇迹是:

网关拿着旧 IP 发请求 -> 碰壁报错Connection Refused->网关底层的 LoadBalancer 捕获到连接异常,默默把它掐掉,然后自动从缓存里换另外一个健康的 IP 重新发起请求。

整个过程在几毫秒内完成,最终用户在手机上看到的,就是一个正常的 200 OK,用户是无感知的。

第三步应用的优雅停机

有了前面两步,进入这台机器的新请求已经被完美挡住并重试了。最后是处理那些已经接进来了,正在工作的老请求。

在 Spring Boot 项目的application.yml里,加上这两行配置:

server: shutdown: graceful # 开启优雅停机 spring: lifecycle: timeout-per-shutdown-phase: 30s # 最多等老请求 30 秒

配合 K8s 的停机机制,Java 进程在等待一段时间后,然后彻底释放资源。

4. 无损下线全流程

为了让你看得更直观,我画了这张涵盖了K8s + Nacos + 客户端重试的联动时序图。

http://www.jsqmd.com/news/1246361/

相关文章:

  • 独立游戏美术全流程:AI辅助从概念到3D资产的实战指南
  • NVIDIA Vera CPU技术详解:88核Olympus、176线程与1.2TB/s内存
  • 2026龙岩新罗黄金回收全维度深度测评 六大片区正规门店综合对比解析 - 不晚生活号
  • AI产品A/B测试的实验设计:分流策略、指标选择与统计显著性的工程化实现
  • 2026年7月最新劳力士长沙开福天街维修保养服务电话 - 劳力士官方服务中心
  • BOM管理实战:制造业成本控制与效率提升指南
  • 亲身到店体验泉州亨得利名表服务中心|最新电话和维修地址(2026年7月更新) - 亨得利官方
  • AMD提出Agent Computer概念,PC与AC未来能否共存?
  • 深夜卡文破防?2026年全网最全10款写小说ai工具(内含避坑经验)
  • Lua与C/C++交互实战:从动态库编译到性能优化全解析
  • 石墨乳环保型厂家实力测评,零套路不踩坑,选购避坑指南 - myqiye
  • C++银行账户管理系统:从控制台项目掌握面向对象与文件操作
  • HybridSim:融合物理仿真与数据驱动的毫米波人体感知数字孪生平台
  • AI流量争夺战必看!选错损失百万:2026国内专业靠谱GEO管理系统权威排行榜
  • 亲身到店探访重庆欧米茄售后服务中心|完整电话和网点地址(2026年7月最新) - 欧米茄服务中心
  • 有人让Qwen3.6写了一套ERP,外包公司的活还能做多久?
  • URP下Shader Graph淡入淡出效果:从原理到实战的完整指南
  • 火车采集器|网页表格数据批量采集 + 导出完整方案
  • Tiva™ C系列MCU I2C主模式寄存器配置与调试实战指南
  • 2026年7月烟台欧米茄地址与热线电话最新服务通知 - 欧米茄服务中心
  • typedef与define的本质区别
  • 雷达宁波服务热线与网点地址2026年7月最新版——客户售后无忧 - 亨得利官方服务中心
  • 2026年7月最新积家天津滨海万达广场维修保养服务电话 - 积家官方售后服务中心
  • CDN技术演进:从MPLS管道到智能边缘计算
  • 2026年7月最新泰州海陵区城西街道亨得利名表服务中心电话公示 - 亨得利官方博客
  • 2026年7月最新欧米茄龙湖北京丽泽天街维修保养服务电话 - 欧米茄官方服务中心
  • 2026 年 7 月新发布:黄岛口碑好的泄爆墙板制造商哪家强,揭秘墙面惊人秘密:这泄爆墙板到底藏着什么?-柏舟抗爆墙 - 行业推荐官[官方】--
  • Gemini 3.0:智能开发工具链与React组件生成实战
  • Unity游戏模组开发实战:基于MelonLoader的代码注入与Harmony补丁技术
  • XXL-JOB时间轮机制解析与优化实践