文章目录
- [Spring Boot 服务优雅停机完全指南](#Spring Boot 服务优雅停机完全指南)
-
- 一、为什么需要优雅停机
-
- [1.1 问题背景](#1.1 问题背景)
- [1.2 不优雅停机的后果](#1.2 不优雅停机的后果)
- [2. Spring Boot 默认的停机行为](#2. Spring Boot 默认的停机行为)
-
- [2.1 快速关闭(Immediate Shutdown)](#2.1 快速关闭(Immediate Shutdown))
- [2.2 启用优雅停机](#2.2 启用优雅停机)
- [3. 核心原理与源码解读](#3. 核心原理与源码解读)
-
- [3.1 Spring Boot 优雅停机架构](#3.1 Spring Boot 优雅停机架构)
- 3.2 源码解读:SpringApplication#run
- [3.3 源码解读:Tomcat 优雅停机](#3.3 源码解读:Tomcat 优雅停机)
- [3.4 源码解读:Spring LifecycleProcessor](#3.4 源码解读:Spring LifecycleProcessor)
- [3.5 停机阶段详细流程图](#3.5 停机阶段详细流程图)
- [4. 优雅停机最佳实践](#4. 优雅停机最佳实践)
-
- [4.1 服务注册中心下线](#4.1 服务注册中心下线)
- [4.2 线程池优雅关闭](#4.2 线程池优雅关闭)
- [4.3 数据库连接池配置](#4.3 数据库连接池配置)
- [4.4 自定义组件的优雅停机](#4.4 自定义组件的优雅停机)
- [4.5 异步任务优雅处理](#4.5 异步任务优雅处理)
- [5. Spring Boot 2.3+ 增强的优雅停机](#5. Spring Boot 2.3+ 增强的优雅停机)
-
- [5.1 新特性概览](#5.1 新特性概览)
- [5.2 配置对比](#5.2 配置对比)
- [5.3 Actuator 安全关闭](#5.3 Actuator 安全关闭)
- [6. 生产环境配置实战](#6. 生产环境配置实战)
-
- [6.1 完整配置示例](#6.1 完整配置示例)
- [6.2 K8s 部署配置](#6.2 K8s 部署配置)
- [6.3 健康检查配置](#6.3 健康检查配置)
- [7. 总结](#7. 总结)
-
- [7.1 核心要点回顾](#7.1 核心要点回顾)
- [7.2 避坑指南](#7.2 避坑指南)
- [7.3 监控指标](#7.3 监控指标)
Spring Boot 服务优雅停机完全指南
优雅停机是,生产环境服务治理的核心能力之一。本文从核心原理到源码解读,系统讲解 Spring Boot 优雅停机的完整技术体系。
一、为什么需要优雅停机
1.1 问题背景
在生产环境中,服务经常需要:
- 滚动更新:K8s deployments 滚动更新时,旧 Pod 被终止,新 Pod 启动
- 定时维护:凌晨的业务低峰期进行系统升级
- 灰度发布:流量切换过程中的服务实例下线
- 故障恢复:实例异常时的自动替换
1.2 不优雅停机的后果
流量损失图:
+-----------------------------------------------------------------------+
| 请求 ----------------------------------------------------------------> |
| | | |
| 正常处理 正在处理 |
| ####### ******* |
| | | |
| Terminate Connection Reset |
| | | |
| +----+-----+ +----+-----+ |
| | 500 | | 504 | |
| | Error | | Timeout |
| +---------+ +---------+ |
+-----------------------------------------------------------------------+
具体问题:
| 问题类型 | 具体表现 | 影响 |
|---|---|---|
| 请求丢失 | 正在处理的业务请求被强制中断 | 数据不一致 |
| 拒绝服务 | 新请求被直接拒绝 | 短时不可用 |
| 连接断开 | WebSocket/TCP 连接强制关闭 | 体感中断 |
| 资源泄漏 | 数据库连接未释放 | 后续启动失败 |
| 任务中断 | 异步任务/定时任务被强制终止 | 业务逻辑错误 |
2. Spring Boot 默认的停机行为
2.1 快速关闭(Immediate Shutdown)
Spring Boot 默认使用 快速关闭 模式:
properties
# 默认配置
server.shutdown=immediate # 立即关闭
行为时序:
时间线 ──────────────────────────────────────────────────►
0ms 10ms 20ms 30ms 40ms
│ │ │ │ │
│──────────│──────────│──────────│──────────│
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Tomcat │ 停止 │ │ │ │
│ Graceful│ 接受 │ 超时 │ 强制 │ │
│ Wait │ 新请求 │ 等待 │ 关闭 │ │
└─────────────────────────────────────────────────────────────┘
2.2 启用优雅停机
yaml
# application.yml
server:
shutdown: graceful # 启用优雅停机
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 停机阶段超时时间
3. 核心原理与源码解读
3.1 Spring Boot 优雅停机架构
┌────────────────────────────────────────────────────────────────┐
│ Spring Boot 优雅停机架构 │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Shutdown │──►│ Lifecycle │──►│ Tomcat │ │
│ │ Hook │ │ Manager │ │ Connector │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ @PreDestroy│ │ Bean │ │ Pool │ │
│ │ Filters │ │ Destroy │ │ Sockets │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
3.2 源码解读:SpringApplication#run
核心源码位置:Spring Boot 启动流程中的优雅退出机制。
java
// Spring Boot 2.x 核心源码
// 文件:spring-boot project's SpringApplication.java
public ConfigurableApplicationContext run(String... args) {
// ... 启动逻辑
// 1. 注册 JVM 关闭钩子
this.lifecycleResources.registerClosedHttpServer(() -> {
// 执行优雅停机
SpringApplication.this.exitCode = 0;
getShutdownHandler().handleProcessExit(0);
});
// 2. 等待优雅停机完成
// Reference: DefaultLifecycleProcessor
}
3.3 源码解读:Tomcat 优雅停机
核心源码位置 :org.apache.catalina.core.StandardService
java
// Tomcat 源码核心分析
// 文件:apache-tomcat's StandardService.java
@Override
public void stopInternal() {
// 阶段1: 停止接收新请求
this.serverSocket.close(); // 关闭监听Socket
// 阶段2: 等待活跃请求处理完成
// Reference: NioEndpoint.java
for (Connector connector : this.connectors) {
connector.pause(); // 暂停Connector
// 等待请求超时
long timeout = getStopTimoeut();
boolean timedOut = waitForActiveRequests(timeout, TimeUnit.SECONDS);
if (timedOut) {
// 超时则强制关闭
forceStopInternal();
}
}
}
private boolean waitForActiveRequests(long timeout, TimeUnit unit) {
// 核心逻辑:等待线程池中的任务完成
Executor executor = getConnector().getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
ThreadPoolExecutor threadPool = (ThreadPoolExecutor) executor;
// 拒绝新任务提交
threadPool.shutdown();
// 等待现有任务完成
return threadPool.awaitTermination(timeout, unit);
}
return true;
}
3.4 源码解读:Spring LifecycleProcessor
java
// Spring Framework 核心源码
// 文件:org.springframework.context.support.DefaultLifecycleProcessor
public class DefaultLifecycleProcessor implements LifecycleProcessor {
// 执行 Bean 的优雅停机
@Override
public void onClose() {
// 按 SmartLifeCycle.getPhase() 排序
Map<Integer, List<Lifecycle>> phases = new TreeMap<>();
// 按阶段分组 Beans
this.beanFactory.getBeansOfType(Lifecycle.class, true, false)
.forEach((name, bean) -> phases
.computeIfAbsent(getPhase(bean), k -> new ArrayList<>())
.add(bean));
// 按阶段依次停止(反向顺序:先停止依赖者)
for (List<Lifecycle> beans : phases.descendingMap().values()) {
beans.forEach(bean -> {
try {
bean.stop();
} catch (Throwable ex) {
// 记录异常但继续
}
});
}
}
private int getPhase(Lifecycle bean) {
if (bean instanceof SmartLifecycle) {
return ((SmartLifecycle) bean).getPhase();
}
// 默认相位:Integer.MAX_VALUE(最后停止)
return DEFAULT_PHASE;
}
}
3.5 停机阶段详细流程图
Spring Boot 优雅停机完整流程
═══════════════════════════════════════════════════════════════════════
╔═══════════════╗
║ SIGTERM / ║
║ /shutdown ║
╚═════╤══════════╝
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Phase 1: 停止接受新请求 │
├──────────────────────────────────────────────────────────────────┤
│ • Tomcat 关闭 ServerSocket │
│ • 设置 Connector 状态为 PAUSED │
│ • LoadBalancer 开始摘除流量 │
│ │
│ 【关键代码】connector.pause() │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────��─��───────────────────────────────┐
│ Phase 2: 等待正在处理的请求 │
├──────────────────────────────────────────────────────────────────┤
│ • 等待 ThreadPoolExecutor 中的任务完成 │
│ • 超时时间可配置:spring.lifecycle.timeout-per-shutdown-phase │
│ • 默认:30秒 │
│ │
│ 【关键代码】threadPool.awaitTermination() │
└──────────────────────────────────────────────────────────────────┘
│
├──────────────────┬──────────────────────────────────┐
│ │ │
▼ ▼ │
┌───��─────────────┐ ┌─────────────────┐ │
│ 等待超时完成 │ │ 超时强制终止 │ │
├─────────────────┤ ├─────────────────┤ │
│ 所有请求处理完 │ │ 调用 shutdownNow│ │
│ 进入下一阶段 │ │ 线程中断退出 │ │
└────────┬────────┘ └────────┬────────┘ │
│ │ │
└──────────┬──────────┘ │
▼ │
┌──────────────────────────────────────────────────────────────────┐
│ Phase 3: 执行 Bean 销毁 │
├──────────────────────────────────────────────────────────────────┤
│ @PreDestroy 标注的方法执行 │
│ @Bean(destroyMethod="xxx") 指定的方法 │
│ DisposableBean.destroy() │
│ Closeable.close() │
│ │
│ 按 SmartLifecycle.getPhase() 排序执行 │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Phase 4: 关闭基础设施 │
├──────────────────────────────────────────────────────────────────┤
│ • 关闭数据库连接池 HikariCP │
│ • 关闭 Redis 连接池 │
│ • 关闭消息队列连接 │
│ • 注销服务注册 (Nacos/Zookeeper) │
│ • 关闭 HTTP Client 连接池 │
│ │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Phase 5: JVM 退出 │
├──────────────────────────────────────────────────────────────────┤
│ • 调用 System.exit(0) │
│ • 进程终止 ��
��──────────────────────────────────────────────────────────────────┘
4. 优雅停机最佳实践
4.1 服务注册中心下线
核心要点:在服务停止前主动从注册中心注销服务。
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lifecycle.annotation.Stopped;
@Configuration
public class ServiceDeregistrationConfig {
@Autowired
private NacosServiceRegistry nacosServiceRegistry;
@Autowired
private Registration registration;
/**
* 在 Spring 容器关闭时执行
* 执行时机:在 Bean 销毁之前
*/
@Stopped
public void onServiceStop() {
String serviceId = registration.getServiceId();
String instanceId = registration.getInstanceId();
log.info("开始注销服务: serviceId={}, instanceId={}",
serviceId, instanceId);
// 立即摘除流量:不等待
nacosServiceRegistry.deregister(registration);
log.info("服务注销完成: {}", serviceId);
}
}
4.2 线程池优雅关闭
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
@Configuration
public class ThreadPoolConfig {
@Bean(name = "businessExecutor", destroyMethod = "shutdownGracefully")
public ExecutorService businessExecutor() {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("business-pool-%d")
.setDaemon(false) // 非守护线程,确保等待任务完成
.build();
return new ThreadPoolExecutor(
4, // corePoolSize
8, // maximumPoolSize
60L, TimeUnit.SECONDS, // keepAliveTime
new LinkedBlockingQueue<>(100), // 队列容量
threadFactory,
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
}
}
关键说明:
- 使用
shutdownGracefully()而非shutdownNow() - 设置
allowCoreThreadTimeOut(true)使核心线程也能超时退出
4.3 数据库连接池配置
yaml
# application.yml
spring:
datasource:
hikari:
maximum-pool-size: 10
minimum-idle: 5
# 关键:连接池关闭超时
max-lifetime: 1800000 # 30分钟
connection-timeout: 30000
# 优雅停机相关
leak-detection-threshold: 60000 # 泄漏检测
4.4 自定义组件的优雅停机
java
import javax.annotation.PreDestroy;
@Component
public class CustomComponent implements AutoCloseable {
private volatile boolean running = true;
private ExecutorService executor;
public CustomComponent() {
this.executor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "custom-component");
t.setDaemon(false);
return t;
});
}
public void process() {
// 业务处理逻辑
}
/**
* 关键:在容器关闭时会被调用
* 注意:@PreDestroy 执行在 destroyMethod 之前
*/
@PreDestroy
public void shutdown() {
log.info("收到停止信号,准备关闭...");
running = false;
// 设置 interrupt 标志
executor.shutdown();
try {
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
/**
* AutoCloseable.close()
* 通过 destroyMethod="close" 指定
*/
@Override
public void close() {
// 清理资源
}
}
4.5 异步任务优雅处理
java
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AsyncTaskProcessor {
@Autowired
private TaskExecutor taskExecutor;
private final Map<String, CompletableFuture> futures = new ConcurrentHashMap<>();
@Async("businessExecutor")
public CompletableFuture<Result> submitTask(String taskId, Task task) {
CompletableFuture<Result> future = CompletableFuture.supplyAsync(() -> {
try {
// 执行业务逻辑
return processTask(task);
} finally {
// 任务完成后移除
futures.remove(taskId);
}
}, taskExecutor);
futures.put(taskId, future);
return future;
}
/**
* 检查并等待所有任务完成
*/
public boolean awaitAllTasks(long timeout, TimeUnit unit) {
if (futures.isEmpty()) {
return true;
}
CompletableFuture<?>[] array = futures.values().toArray(new CompletableFuture[0]);
try {
CompletableFuture.allOf(array).get(timeout, unit);
return true;
} catch (Exception e) {
return false;
}
}
}
5. Spring Boot 2.3+ 增强的优雅停机
5.1 新特性概览
Spring Boot 2.3 引入了增强的优雅停机支持:
| 特性 | Spring Boot 2.3+ | 之前版本 |
|---|---|---|
| graceful shutdown | 内置支持 | 需要自定义 |
| 配置项 | server.shutdown |
无 |
| 连接池关闭 | 自动等待 | 需手动配置 |
| Actuator shutdown | 更安全 | 需额外配置 |
5.2 配置对比
yaml
# Spring Boot 2.3+
server:
shutdown: graceful # 启用优雅停机
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 停机阶段超时
# Spring Boot 2.3 之前的版本
# 需要手动实现 AbstractReactiveWebServerApplicationContext
# 或使用 Tomcat 的配置
5.3 Actuator 安全关闭
yaml
# pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application.yml
management:
endpoints:
web:
exposure:
include: health,info,shutdown
base-path: /actuator
endpoint:
shutdown:
enabled: true # 启用 shutdown 端点
# 安全配置(必须)
spring.security.user.name=admin
spring.security.user.password=${SHUTDOWN_PASSWORD}
调用方式:
bash
# POST 请求关闭服务
curl -X POST "http://localhost:8080/actuator/shutdown" \
-u admin:your_password \
-H "Content-Type: application/json"
6. 生产环境配置实战
6.1 完整配置示例
yaml
# application.yml - 生产环境完整配置
server:
shutdown: graceful
tomcat:
# 等待请求超时时间
connection-timeout: 60s
# 最大连接数
max-connections: 20000
accept-count: 100
spring:
lifecycle:
timeout-per-shutdown-phase: 60s
# 服务注册
spring.cloud.nacos:
discovery:
instance-id: ${spring.application.name}-${server.port}
ephemeral: false # 关闭后保留实例信息
ip-delete-interval: 5000 # 5秒删除过期实例
# 数据库连接池
spring.datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
max-lifetime: 1800000
leak-detection-threshold: 60000
# Redis 连接池
spring.redis:
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 4
max-wait: 3000ms
# 线程池
business:
executor:
core-pool-size: 10
max-pool-size: 50
queue-capacity: 500
keep-alive-seconds: 60
thread-name-prefix: business-
# 日志
logging:
level:
root: INFO
org.springframework.lifecycle: DEBUG
org.apache.catalina: DEBUG
6.2 K8s 部署配置
yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0 # 关键:保持全部可用
template:
spec:
containers:
- name: myapp
image: myapp:latest
ports:
- containerPort: 8080
# 优雅停机相关配置
env:
- name: JAVA_OPTS
value: "-Xms512m -Xmx512m"
# 预停机钩子(可选)
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- "sleep 10"
---
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
type: ClusterIP
关键配置说明:
yaml
# preStop 延迟原理:
# 1. K8s 发送 SIGTERM
# 2. 业务容器收到信号,开始优雅停机
# 3. preStop sleep 10s,给业务充足时间
# 4. 期间 LoadBalancer 摘除流量
# 5. sleep 结束后容器才被终止
6.3 健康检查配置
java
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class ShutdownHealthIndicator implements HealthIndicator {
private final AtomicBoolean shuttingDown = new AtomicBoolean(false);
public void setShuttingDown() {
shuttingDown.set(true);
}
@Override
public Health health() {
if (shuttingDown.get()) {
return Health.down()
.withDetail("reason", "Service is shutting down")
.build();
}
return Health.up().build();
}
}
7. 总结
7.1 核心要点回顾
┌─────────────────────────────────────────────────────────────────┐
│ 优雅停机核心要素 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1️⃣ 配置启用 │
│ server.shutdown = graceful │
│ spring.lifecycle.timeout-per-shutdown-phase = 30s │
│ │
│ 2️⃣ 基础设施关闭顺序 │
│ 服务注册 → 线程池 → 连接池 → Bean销毁 → JVM退出 │
│ │
│ 3️⃣ 关键实现点 │
│ • Nacos/Zookeeper 服务下线 │
│ • 使用 awaitTermination() 等待��步��务 │
│ • 配置连接池超时 │
│ • @PreDestroy 清理资源 │
│ │
│ 4️⃣ K8s 配合 │
│ • maxUnavailable: 0 │
│ • preStop sleep 延迟 │
│ • 添加健康探针 │
│ │
└─────────────────────────────────────────────────────────────────┘
7.2 避坑指南
| 陷阱 | 后果 | 解决方案 |
|---|---|---|
使用 shutdownNow() |
任务被强制终止 | 使用 shutdown() + awaitTermination() |
| 数据库连接池未关闭 | 连接泄漏 | 显式配置 close Method |
| 异步任务未等待 | 请求丢失 | 使用 CompletableFuture 管理 |
| K8s 探针未配置 | 流量未摘除 | 添加 /actuator/health 探针 |
| 超时时间太短 | 任务被强制中断 | 根据业务调整 timeout-per-shutdown-phase |
7.3 监控指标
推荐添加以下监控指标:
# 优雅停机相关 metrics
spring_lifecycle_shutdown_phase_duration_seconds
tomcat_connections_active
tomcat_threads_busy
hikari_connections_active
business_tasks_queued
💡 提示 :实际生产环境中,建议进行真实的优雅停机演练,验证配置是否生效。可以利用
curl -X POST localhost:8080/actuator/shutdown模拟关闭,监控系统行为。