线上服务如何优雅停机?

大家好,我是苏三,又跟大家见面了。

前言

最近星球中有位小伙伴问了我一个问题:如何优雅的停机?

我觉得这个问题挺有代表性的。

今天这篇文章跟大家一下优雅停机的一些常见方案,希望对你会有所帮助。

最近建了一些工作内推群,各大城市都有,欢迎各位HR和找工作的小伙伴进群交流,群里目前已经收集了不少的工作内推岗位。

扫码加苏三的微信:li_su223,备注:所在城市,即可进群。

1.什么是优雅停机?

优雅停机(Graceful Shutdown) 指在服务终止前,系统能:

拒绝新请求进入

完成存量请求处理

释放所有资源

通知上下游服务

非优雅停机的惨痛代价 :

真实案例:支付回调丢失。

// 支付回调处理

@PostMapping("/callback")

public void handleCallback(Payment payment) {

// 1. 更新订单状态

orderService.updateStatus(payment.getOrderId(), PAID);

// 2. 发放权益(kill发生时此处未执行)

benefitService.grantVip(payment.getUserId());

}

当kill发生在步骤1和2之间时,导致订单状态已更新但权益未发放,引发用户投诉。

2.优雅停机三大核心流程

2.1 信号捕获层

2.2 流量控制层

2.3 资源释放层

3.Spring Boot优雅停机的实现

3.1 基础配置

在SpringBoot项目的application.yml文件中增加如下配置:

server:

shutdown: graceful # 开启优雅停机

spring:

lifecycle:

timeout-per-shutdown-phase: 30s # 最长等待时间

3.2 线程池优雅关闭

在线程池中实现优雅关闭功能:

@Bean

public ExecutorService threadPool() {

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

executor.setWaitForTasksToCompleteOnShutdown(true); // 等待任务完成

executor.setAwaitTerminationSeconds(60); // 最大等待时间

return executor.getThreadPoolExecutor();

}

在shutdown之前,先等待任务完成。

3.3 分布式锁释放拦截器

@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")

public Object handleRequest(ProceedingJoinPoint pjp) {

Lock lock = redisson.getLock("order\_lock");

try {

lock.lock();

return pjp.proceed();

} finally {

if (!isShuttingDown()) {

lock.unlock(); // 非停机时正常释放

}

// 停机时由锁管理器统一释放

}

}

使用统一的拦截器释放分布式锁,防止出现异常有释放遗漏的地方。

4.Kubernetes环境下的优雅停机

4.1 关键配置

STOPSIGNAL SIGTERM # 使用SIGTERM替代SIGKILL

# Deployment配置

spec:

terminationGracePeriodSeconds:60# 宽限期

containers:

-lifecycle:

preStop:

exec:

command:["/bin/sh","-c","sleep 20;"]# 预留缓冲时间

在部署配置中增加预留缓冲时间。

4.2 就绪探针自动摘流

5.中间件连接优雅关闭

5.1 数据库连接池

@PreDestroy

public void close() {

HikariPool pool = dataSource.getHikariPoolMXBean();

pool.suspendPool(); // 停止借出连接

pool.softEvictConnections(); // 驱逐空闲连接

while (pool.getActiveConnections() > 0) {

Thread.sleep(500); // 等待活动连接完成

}

pool.shutdown(); // 彻底关闭

}

使用@PreDestroy在服务销毁之前关闭数据库连接池。

5.2 RabbitMQ消费者

@PreDestroy

public void stop() {

channel.basicCancel(consumerTag); // 取消订阅

while (unackedMessages.get() > 0) {

Thread.sleep(100); // 等待ACK完成

}

connection.close();

}

@PreDestroy在服务销毁之前取消订阅,需要先等待ACK完成。

3. Redis分布式锁

public class LockManager implements DisposableBean {

@Override

public void destroy() {

lockMap.forEach((key, lock) -> {

if (lock.isHeldByCurrentThread()) {

lock.unlock(); // 强制释放未解锁的锁

}

});

}

}

实现DisposableBean接口,在服务销毁之前强制释放未解锁的锁。

6.全链路优雅停机

6.1 停机事件传播机制

6.2 状态机管理

public enum ShutdownState {

RUNNING, // 正常运行

PRE\_SHUTDOWN, // 拒绝新请求

DRAINING, // 排空存量请求

TERMINATED // 完全终止

}

6.3 停机监控面板

7.生产环境避坑指南

7.1 必须避免的四大陷阱

|

陷阱

|

后果

|

解决方案

|

| --- | --- | --- |

|

死锁等待

|

无法完成停机

|

设置锁超时时间

|

|

第三方服务不可用

|

资源无法释放

|

添加熔断机制

|

|

长周期任务

|

超过宽限期被强杀

|

拆分任务+保存中间状态

|

|

文件写入未完成

|

数据损坏

|

使用原子文件替换

|

7.2 停机检查清单

# 停机前执行

curl -X POST http://localhost:8080/actuator/shutdown-prepare

# 验证项:

1. 新请求返回503

2. 活动线程数持续下降

3. 数据库连接数归零

4. MQ无未ACK消息

7.3 黄金法则:二段式停机

总结

基础层 :处理HTTP请求

Spring Boot Graceful Shutdown + 线程池等待

进阶层 :管理中间件连接

数据库连接池排空 + MQ消费者取消订阅

高级层 :分布式协同

停机事件广播 + 分布式锁释放

终极层 :全链路状态管理

停机状态机 + 智能超时控制

停机策略对比表

|

策略

|

实现难度

|

停机时间

|

数据安全

|

适用场景

|

| --- | --- | --- | --- | --- |

|

直接kill -9

|

|

秒级

|

极低

|

开发环境

|

|

Spring Boot

|

☆☆

|

10-30s

|

|

常规Web应用

|

|

容器化方案

|

☆☆☆

|

可配置

|

|

K8S环境

|

|

全链路管理

|

☆☆☆☆

|

分钟级

|

极高

|

金融核心系统

|

最后欢迎加入苏三的星球,你将获得:100万QPS短链系统、复杂的商城微服务系统、苏三AI项目、刷题吧小程序、秒杀系统、商城系统、秒杀系统、代码生成工具等8个项目的源代码、开发教程和技术答疑。

系统设计、性能优化、技术选型、底层原理、Spring源码解读、工作经验分享、痛点问题、面试八股文等多个优质专栏。

我的技术成长之路

这7个项目,yyds

我被官方推荐了!

还有1V1免费修改简历、技术答疑、职业规划、送书活动、技术交流。

目前星球已经更新了5800+篇优质内容,还在持续爆肝中.....

星球已经被官方推荐了3次,收到了小伙伴们的一致好评。戳我加入学习,已有1800+小伙伴加入学习。

2026-07-01 20:29:48
家用电热水器该配多少平方电线?一文带你搞清楚
阅阅赚(好好赚旗下)赚钱速度快吗?收益周期分析