springboot + nacos + k8s 优雅停机
今天来聊一个我最近刚搞过、也确实很多小伙伴踩坑的东西——Spring Boot + Nacos + Kubernetes 怎么优雅停机。说白了就是:服务怎么关得体面、关得不掉链子、关得不影响用户体验。
我跟你讲,这里面的坑啊,可不是你加一行 @PreDestroy 就能搞定的事。尤其你一旦上了 K8s,这个“优雅”两个字背后的含义直接加倍放大!我之前项目里就遇到 Pod 频繁重启,把我数据库连接、MQ消费者、线程池全搞炸了🤯……所以今天我就从程序员实战角度,给你整明白这个事!
先说结论:要想在 Spring Boot + Nacos + K8s 场景下做到真正的“优雅停机”,你得搞定下面这几件事:
1)Nacos里的服务要提前下线 2)K8s探针要合理配置,不然就会出现还没完全关,Pod已经被砍了 3)业务线程池、连接池、消息队列等资源得主动回收 4)一些长连接(WebSocket、Grpc、TCP)要主动关闭 5)如果有MQ消费者,还要搞定幂等性 + 消息回补机制
下面我就一点点来剖一下,附代码,保你看完能立马上手!
第一步,服务停之前得告诉Nacos我“要走了”
不然你服务还在Nacos注册列表里,别的服务还是往你这发请求,结果你都准备撤了还硬接,这不是找事嘛!
Spring Boot里,Nacos Client 有个 NamingService,可以手动把实例从注册中心摘除:
@Autowired
private NamingService namingService;
@Value("${spring.cloud.nacos.discovery.service}")
private String serviceName;
@Value("${spring.cloud.nacos.discovery.ip}")
private String ip;
@Value("${server.port}")
private int port;
@PreDestroy
public void deregister() throws NacosException {
namingService.deregisterInstance(serviceName, ip, port);
System.out.println("手动摘除nacos注册实例完成!");
}
注意,这段代码要在优雅停机流程的早期执行,否则就晚了,人家流量已经进来了👋
第二步,K8s 的探针配置很关键!
这里是我之前踩的一个大坑:K8s默认在你Pod收到 SIGTERM 的时候会立刻把它从Service里摘掉,然后马上开始发请求给别的Pod,而你的这个Pod其实还没关完,可能还有线程在跑。
解决办法就是——
1)加一个 preStop hook,给你服务一些“缓冲时间” 2)设置 terminationGracePeriodSeconds 比如说30秒 3)再配合 Spring Boot 的 @PreDestroy 来做资源释放
K8s配置示例👇:
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "echo 等30秒再停 && sleep 30"]
terminationGracePeriodSeconds: 35
这个 preStop 就是你Pod快关前的一次“临终遗言时间”,给你30秒窗口干净撤退😎
第三步,线程池要自己收,不然你任务就死在半路上
比如你项目里用了线程池去处理异步任务,那优雅停机时,一定要调用 shutdown() 然后 awaitTermination() 等待任务跑完再结束:
@Autowired
private ThreadPoolTaskExecutor executor;
@PreDestroy
public void shutdownExecutor() {
executor.shutdown();
try {
if (!executor.getThreadPoolExecutor().awaitTermination(30, TimeUnit.SECONDS)) {
System.out.println("线程池任务还没跑完,强制关闭");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
别以为线程池会自动关,不主动清理,你一停机全是异常堆栈💣
第四步,MQ的消费者得停干净,消息不能丢也不能重复处理
比如你用了 RabbitMQ 或 Kafka 做异步消费,那你服务停机时千万别让它还在消费!否则出现“消息来了但处理了一半”就尴尬了。
以 RabbitMQ 为例,你可以在 @PreDestroy 里手动 stop 掉 listener:
@Autowired
private SimpleMessageListenerContainer listenerContainer;
@PreDestroy
public void shutdownMQConsumer() {
listenerContainer.stop();
System.out.println("RabbitMQ 消费者已停止");
}
另外,消息要么做到幂等,要么能补偿(重试、死信、告警等都行),不然出错就不好排查了。
第五步,注意那些容易忘掉的“粘人连接”
我见过很多人忘记关 WebSocket / TCP / Grpc 的连接,结果客户端一直以为“还能用”,但服务早挂了……
你如果用的是 Netty / Spring WebSocket,就得在停机前通知客户端主动关闭,或者服务端统一踢掉连接:
@PreDestroy
public void closeWebSocketSessions() {
for (Session session : sessionList) {
if (session.isOpen()) {
session.close();
}
}
}
不然你等客户端一个个 timeout,那用户体验就像掉进黑洞一样🌑
对了,还有一个小Tip!
Spring Boot 从 2.3 开始支持 ApplicationListener<ApplicationReadyEvent> 和 ApplicationListener<ContextClosedEvent> 了,前者适合做一些“启动后注册”,后者适合做“停机前清理”。
你完全可以这么写:
@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
System.out.println("监听到Spring容器关闭,开始优雅停机...");
// 这里调用前面那几个清理动作
}
}
比单靠 @PreDestroy 更灵活,也能让整个流程更有条理,代码也更清晰。
SpringBoot + Nacos + K8s 想做到“真·优雅停机”,不能只靠“系统自动”,而是要你作为开发者主动插手,去安排服务撤退的每一步。只要你做到:
✅ 提前下线Nacos服务实例 ✅ 配置合理的探针与 preStop✅ 回收所有资源(线程池、MQ、连接) ✅ 幂等性保障+消息补偿机制 ✅ 客户端连接主动关闭
那你这服务就能优雅地告别,不留一丝痕迹!✌️
更多推荐

所有评论(0)