凌晨2点,系统突然告警!服务重启导致大量订单请求丢失,用户投诉刷屏……
你是否也遇到过这种“半夜惊魂”?优雅停机正是解决这类问题的金钥匙!
本文将手把手教你用Spring Boot实现零宕机重启,从此告别数据丢失噩梦!
一、什么是优雅停机?
想象一下:餐厅打烊时,服务员不会直接赶走正在吃饭的顾客,而是:
1️⃣ 挂出“停止接单”牌子(拒绝新请求)
2️⃣ 等现有客人吃完结账(处理进行中请求)
3️⃣ 打扫卫生、关闭水电(释放资源)
4️⃣ 锁门下班(安全关闭)
技术版定义:
在微服务架构中,优雅停机意味着服务器能够有序地关闭,确保所有正在进行的请求都得到妥善处理,同时不再接受新的请求。
微服务关闭时,先拒绝新流量 → 完成存量请求 → 释放连接/线程 → 安全退出的完整流程。
直接后果:
-
更新版本时用户无感知
-
金融交易0丢失
-
运维不用背锅写事故报告
二、Spring Boot优雅停机四重奏
第一重:Actuator秒开优雅停机(适合快速上手)
Step 1:加个“开关”
<!-- pom.xml 加入 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Step 2:打开停机权限
properties
# application.properties
management.endpoint.shutdown.enabled=true
management.endpoints.web.exposure.include=shutdown
Step 3:优雅关机
curl -X POST http://localhost:8080/actuator/shutdown
注意:
⚠️ 一定要上锁!用Spring Security保护接口,否则黑客分分钟让你服务下线!
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/actuator/shutdown").hasRole("ADMIN")
.and().httpBasic();
}
}
第二重:Tomcat内置优雅停机(Spring Boot 2.3+专属)
配置两行代码直接生效:
# 开启优雅停机 + 30秒超时
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s
效果:
-
立刻返回Connection: close响应头
-
等待30秒处理存量请求
-
超时未完成则强制关闭
第三重:自定义资源清理(高阶必备)
场景:
-
关闭前备份缓存数据
-
通知注册中心下线服务
-
等待异步任务完成
代码示例:
@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@Override
public void onApplicationEvent(ContextClosedEvent event) {
// 1. 停止接收新任务
taskExecutor.shutdown();
// 2. 等待30秒让现有任务完成
try {
if (!taskExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
taskExecutor.shutdownNow(); // 强制终止
}
} catch (InterruptedException e) {
taskExecutor.shutdownNow();
}
// 3. 关闭数据库连接池
HikariDataSource dataSource = ...;
dataSource.close();
}
}
第四重:K8s集群部署终极方案
配置模板:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: springboot-app
lifecycle:
preStop: # 关闭前钩子
exec:
command:
- "curl"
- "-X POST"
- "http://localhost:8080/actuator/shutdown"
terminationGracePeriodSeconds: 60 # 等待1分钟
全流程:
-
K8s发送SIGTERM信号
-
执行preStop钩子通知Spring Boot停机
-
等待60秒后强制终止(SIGKILL)
三、避坑指南(血泪经验总结)
-
线程池必须配置
@Bean(destroyMethod = "shutdown")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setWaitForTasksToCompleteOnShutdown(true); // 关键!
executor.setAwaitTerminationSeconds(60);
return executor;
}
-
注册中心主动注销
@EventListener(ContextClosedEvent.class)
public void deregisterService() {
eurekaClient.shutdown(); // Eureka示例
consulClient.deregister(serviceId); // Consul示例
}
-
必须压测验证
-
用JMeter模拟高并发时突然停机
-
观察是否有:
✅ 新请求立即被拒
✅ 存量请求正常响应
✅ 日志显示资源释放记录
四、总结
优雅停机三大原则:
🔶 快准狠拒绝新流量(负载均衡/网关摘流)
🔶 死等存量请求完成(配置合理超时时间)
🔶 不留任何垃圾数据(连接/线程/文件全释放)
技术选型建议:
-
简单项目 → 直接用Actuator
-
集群部署 → K8s+preStop组合拳
-
复杂异步 → 自定义监听器+线程池管理