Java案例如何实现服务优雅?

wen python案例 2

本文目录导读:

Java案例如何实现服务优雅?

  1. 案例一:基于 Spring Boot 的优雅关闭(最常见)
  2. 案例二:手动实现优雅关闭(不使用Spring Boot)
  3. 案例三:结合负载均衡的健康检查(K8s / Nginx 场景)
  4. 案例四:数据库连接池与HTTP客户端优雅关闭
  5. 实现优雅的4个步骤

在Java开发中,服务优雅(Graceful Service)主要体现在服务关闭时的平滑处理资源释放任务完整执行,以及整体系统的稳定性,要实现这一点,通常涉及以下几个方面:

  1. 优雅关闭(Graceful Shutdown):当服务收到停止信号时,不立即中断正在处理的请求,而是等待它们完成后再关闭。
  2. 资源管理:确保数据库连接、线程池、文件流等资源被正确释放。
  3. 请求完整性:防止在服务关闭过程中,新请求被导入到正在关闭的节点(通常配合负载均衡的健康检查)。
  4. 超时与重试机制:避免因单点故障导致整个服务雪崩。

下面通过几个具体的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() vs shutdownNow():前者等待现有任务完成,后者直接中断。

结合负载均衡的健康检查(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 服务优雅的核心。

抱歉,评论功能暂时关闭!