本文目录导读:

- 案例一:基于 Spring Boot 的优雅关闭(最常见)
- 案例二:手动实现优雅关闭(不使用Spring Boot)
- 案例三:结合负载均衡的健康检查(K8s / Nginx 场景)
- 案例四:数据库连接池与HTTP客户端优雅关闭
- 实现优雅的4个步骤
在Java开发中,服务优雅(Graceful Service)主要体现在服务关闭时的平滑处理、资源释放、任务完整执行,以及整体系统的稳定性,要实现这一点,通常涉及以下几个方面:
- 优雅关闭(Graceful Shutdown):当服务收到停止信号时,不立即中断正在处理的请求,而是等待它们完成后再关闭。
- 资源管理:确保数据库连接、线程池、文件流等资源被正确释放。
- 请求完整性:防止在服务关闭过程中,新请求被导入到正在关闭的节点(通常配合负载均衡的健康检查)。
- 超时与重试机制:避免因单点故障导致整个服务雪崩。
下面通过几个具体的Java案例来展示如何实现这些优雅特性。
基于 Spring Boot 的优雅关闭(最常见)
Spring Boot 2.3+ 内置了优雅关闭支持,这是最直接的方式。
核心配置与实现:
# application.yml
server:
shutdown: graceful # 开启优雅关闭
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 给正在处理的请求最多30秒完成
核心代码示例(模拟耗时请求):
@RestController
public class GracefulController {
@GetMapping("/delay")
public String delay() throws InterruptedException {
// 模拟一个耗时10秒的业务处理
TimeUnit.SECONDS.sleep(10);
return "任务完成";
}
@Bean
public GracefulShutdownTomcat gracefulShutdownTomcat() {
// 如果使用Tomcat且版本较低,可以自定义销毁逻辑
return new GracefulShutdownTomcat();
}
}
原理:
当发送 kill -15(SIGTERM)给Java进程时,Spring Boot 会停止接收新请求,并等待正在处理的请求在 timeout-per-shutdown-phase 内完成。
手动实现优雅关闭(不使用Spring Boot)
如果你的项目是原生Java应用或使用其他框架,需要手动实现 Shutdown Hook。
核心代码示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class GracefulShutdownExample {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws InterruptedException {
// 1. 注册 JVM 关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("【优雅关闭】收到关闭信号,开始清理...");
// 2. 关闭线程池,拒绝新任务,等待已有任务完成
executorService.shutdown();
try {
// 3. 等待最多10秒,让现有任务完成
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
// 4. 超时后强制关闭
executorService.shutdownNow();
System.out.println("【优雅关闭】部分任务未完成,已被强制中断。");
} else {
System.out.println("【优雅关闭】所有任务已平稳完成。");
}
} catch (InterruptedException e) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
System.out.println("【优雅关闭】资源释放完毕,再见。");
}));
// 2. 模拟持续接收任务
for (int i = 0; i < 5; i++) {
final int taskId = i;
executorService.submit(() -> {
try {
System.out.println("任务 " + taskId + " 开始执行...");
TimeUnit.SECONDS.sleep(5); // 模拟耗时5秒
System.out.println("任务 " + taskId + " 执行完毕。");
} catch (InterruptedException e) {
System.out.println("任务 " + taskId + " 被中断。");
Thread.currentThread().interrupt();
}
});
}
System.out.println("【主线程】服务已启动,按 Ctrl+C 发送停止信号。");
// 让主线程保持运行
Thread.currentThread().join();
}
}
关键点:
addShutdownHook:在JVM退出时自动执行。shutdown()vsshutdownNow():前者等待现有任务完成,后者直接中断。
结合负载均衡的健康检查(K8s / Nginx 场景)
要实现真正的“无感”优雅关闭,不仅应用本身要处理,还要通知上游的负载均衡器“我快要不可用了”。
Spring Boot + Actuator + K8s 的案例:
# application.yml
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: always
probes:
enabled: true # 开启就绪和存活探针支持
自定义健康检查(在关闭前标记为非健康):
@Component
public class CustomHealthIndicator implements HealthIndicator {
private boolean isReady = true;
@Override
public Health health() {
if (!isReady) {
return Health.down().withDetail("ready", false).build();
}
return Health.up().withDetail("ready", true).build();
}
// 这个方法在关闭钩子中调用,先将服务标记为不健康
public void setNotReady() {
this.isReady = false;
}
}
结合关闭钩子:
@Component
public class ShutdownListener implements ApplicationListener<ContextClosedEvent> {
@Autowired
private CustomHealthIndicator healthIndicator;
@Override
public void onApplicationEvent(ContextClosedEvent event) {
// 1. 先标记为不健康,让K8s停止路由新流量
healthIndicator.setNotReady();
System.out.println("【优雅关闭】已通知K8s停止路由新请求。");
// 2. 等待现有连接耗尽(模拟)
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 3. 真正的资源清理由Spring Boot自动完成
}
}
数据库连接池与HTTP客户端优雅关闭
资源管理是优雅关闭的核心,常见的资源包括:
@Configuration
public class ResourceConfig {
@Bean(destroyMethod = "shutdown")
public ExecutorService taskExecutor() {
return Executors.newFixedThreadPool(10);
}
// 数据库连接池(以HikariCP为例,Spring Boot默认已经优雅关闭)
// HTTP客户端(如OkHttp)
@Bean
public OkHttpClient okHttpClient() {
return new OkHttpClient.Builder()
.connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES))
.build();
// 注意:OkHttpClient需要手动调用dispatcher().executorService().shutdown()
// 可以结合@PreDestroy来处理
}
@PreDestroy
public void cleanup() {
System.out.println("【优雅关闭】正在关闭HTTP连接池...");
// 具体关闭逻辑
}
}
实现优雅的4个步骤
| 步骤 | 实现方式 | 作用 |
|---|---|---|
| 捕获停止信号 | JVM Shutdown Hook、Spring的ContextClosedEvent |
知道何时要关闭 |
| 通知上游 | 健康检查改为DOWN、从注册中心摘除 |
防止新请求进入 |
| 等待任务完成 | 线程池的shutdown() + awaitTermination() |
完成正在处理的任务 |
| 释放资源 | @PreDestroy、手动关闭连接池/文件流 |
避免资源泄露 |
最终效果:
当执行 kill -15 或 K8s 滚动更新时,服务不会突然中断用户的请求,而是先拒绝新请求,等待当前请求处理完毕(或超时),再平稳退出,这就是 Java 服务优雅的核心。