java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot优雅停机

SpringBoot实现优雅停机的多种方式

作者:后端出路在何方

优雅停机(Graceful Shutdown)在现代微服务架构中是非常重要的,它帮助我们确保在应用程序停止时,不会中断正在进行的请求或导致数据丢失,让我们以通俗易懂的方式来讲解这个概念以及如何在 Spring Boot 中实现它,需要的朋友可以参考下

优雅停机在微服务架构中的角色

想象一下,你在一家餐厅用餐,突然服务员走过来说:“很抱歉,我得下班了,请您尽快结账离开。”你可能刚刚点了主菜,或许正在等着甜点,结果这个突如其来的通知让你感到非常不满。你的就餐体验被打断了,甚至你的账单也可能出现混乱。与此同时,餐厅的运营也可能因此变得不顺畅,服务员离开后,未完成的订单和客户的需求都可能无法及时得到处理,产生了不必要的麻烦。

在微服务架构中,这种情况的对应场景是应用程序的突发关闭。假设一个用户正在发起一个请求,这个请求正在进行中时,某个微服务却突然被强制关闭,或者在维护过程中突然停止。这就会导致用户的操作被中断,未处理的请求可能丢失,甚至产生数据不一致或错误的状态。

这种场景不仅会让用户体验变差,还可能给后端系统带来极大的问题。例如,用户信息可能未能成功保存到数据库中,或者订单状态没有被正确更新,最终造成数据的“脏”或不一致。为了避免这种问题,优雅停机机制在现代微服务系统中变得至关重要。它确保在服务关闭时,所有进行中的请求能够得到适当的处理,相关资源得以顺利释放,避免因应用程序的突然关闭而导致的问题。

1、什么是优雅停机

简单来说,优雅停机就是指当我们需要关闭一个服务时,服务能够有序地完成当前的工作并停止。 具体来说,优雅停机包括以下几个步骤:

2、为什么需要优雅停机

3、优雅停机的实际应用场景

4、优雅停机可能失效的情况

5、如何在 Spring Boot 中实现优雅停机

Spring Boot 优雅停机的基础实现

立即停机模式

server:
  shutdown: immediate

虽然简单高效,但这种方式通常只适用于测试或无状态服务。

优雅停机模式

server:
  shutdown: graceful

注意: 该模式下的默认等待时间为 30 秒,可通过 spring.lifecycle.timeout-per-shutdown-phase 进行配置。

添加 spring-boot-starter-actuator 依赖

首先,需要确保你的项目中包含了 spring-boot-starter-actuator 依赖,这是启用 Spring Boot 内置监控和管理端点的工具包。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

启用 shutdown 端点

默认情况下,Spring Bootshutdown 端点是禁用的。我们需要在 application.propertiesapplication.yml 中显式启用它。

对于 application.properties

management.endpoint.shutdown.enabled=true
management.endpoints.web.exposure.include=shutdown

或者,如果你使用 application.yml

management:
  endpoint:
    shutdown:
      enabled: true
  endpoints:
    web:
      exposure:
        include: "shutdown"

触发优雅停机

配置好后,你可以通过发送 HTTP 请求来触发优雅停机。例如,使用 curl 命令:

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

当你调用这个端点时,Spring Boot 应用会停止接收新的请求,继续处理已经收到的请求,直到所有请求处理完毕后,应用才会退出。

6、通过 ApplicationListener 接口实现清理逻辑

为了在应用关闭时执行特定的清理操作(例如关闭数据库连接、释放资源等),你可以实现 ApplicationListener<ContextClosedEvent> 接口。

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;

@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        // 执行必要的清理操作
        System.out.println("Starting graceful shutdown...");
        try {
            Thread.sleep(5000); // 模拟清理任务
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Graceful shutdown completed.");
    }
}

在这个例子中,我们模拟了一个清理操作的过程(通过 Thread.sleep() 来等待)。实际上,你可以替换这部分逻辑,比如关闭数据库连接、释放文件句柄等。

7、使用 JVM 的钩子函数

Java 提供了 Runtime.addShutdownHook() 方法,可以注册一个线程,在 JVM 终止时执行清理任务。这个方法适用于一些更底层的清理操作,尤其是在某些情况下,ApplicationListener 可能没有机会被触发时。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MainApp {

    public static void main(String[] args) {
        // 注册关闭钩子
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("JVM is shutting down, executing cleanup...");
            try {
                Thread.sleep(5000); // 模拟清理任务
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("Cleanup completed.");
        }));

        SpringApplication.run(MainApp.class, args);
    }
}

需要注意的是,JVM 关闭钩子并不总是能执行,尤其是在遇到强制停止(如 kill -9)时。因此,它应该作为一种补充机制,而不是唯一的保证。

8、触发优雅停机的方式

除了通过 /actuator/shutdown 端点来触发优雅停机外,还有一些常见的方法:

kill <pid>

这里的 <pid> 是应用的进程 ID。

kill -9 pid可以模拟了一次系统宕机,系统断电等极端情况,而kill -15 pid则是等待应用关闭,执行阻塞操作,有时候也会出现无法关闭应用的情况

#查看jvm进程pid
jps
#列出所有信号名称
kill -l

# Windows下信号常量值
# 简称  全称    数值 
# INT   SIGINT     2       Ctrl+C中断
# ILL   SIGILL     4       非法指令
# FPE   SIGFPE     8       floating point exception(浮点异常)
# SEGV  SIGSEGV    11      segment violation(段错误)
# TERM  SIGTERM    5       Software termination signal from kill(Kill发出的软件终止)
# BREAK SIGBREAK   21      Ctrl-Break sequence(Ctrl+Break中断)
# ABRT  SIGABRT    22      abnormal termination triggered by abort call(Abort)

#linux信号常量值
# 简称  全称  数值  
# HUP   SIGHUP      1    终端断线  
# INT   SIGINT      2    中断(同 Ctrl + C)        
# QUIT  SIGQUIT     3    退出(同 Ctrl + \)         
# KILL  SIGKILL     9    强制终止         
# TERM  SIGTERM     15    终止         
# CONT  SIGCONT     18    继续(与STOP相反, fg/bg命令)         
# STOP  SIGSTOP     19    暂停(同 Ctrl + Z)        
#....

#可以理解为操作系统从内核级别强行杀死某个进程
kill -9 pid 
#理解为发送一个通知,等待应用主动关闭
kill -15 pid
#也支持信号常量值全称或简写(就是去掉SIG后)
kill -l KILL

JVM 工具:Java 提供了一些工具(如 jcmd 和 jconsole)可以用来控制 JVM 的生命周期。例如,使用 jcmd 来发送退出命令:

jcmd <pid> VM.exit

其他方法

在 Spring Boot 中实现优雅停机(Graceful Shutdown)除了通过 spring-boot-actuatorApplicationListener<ContextClosedEvent>JVM 钩子等方式外,实际上还有一些其他方法可以帮助我们实现优雅停机。以下是几种不同的实现方式,并配合实际应用场景和代码示例。

1. 使用 @PreDestroy 注解

@PreDestroy 注解是 Java EE 中的一种注解,用来在 Bean 销毁之前执行清理任务。Spring 也支持这个注解,当 Spring 容器关闭时,所有带有 @PreDestroy 注解的方法都会被调用。它通常用于执行资源的释放操作,如关闭数据库连接池、清理缓存等。

代码示例:

import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;

@Component
public class GracefulShutdownService {

    @PreDestroy
    public void onShutdown() {
        System.out.println("Performing cleanup before shutdown...");
        // 在此处执行资源清理,如关闭数据库连接、释放线程池等
    }
}

适用场景:

2. 使用 DisposableBean 接口

Spring 提供了 DisposableBean 接口,用于定义 Bean 在销毁时需要执行的清理操作。它的 destroy() 方法会在 Spring 容器关闭时被自动调用。

代码示例:

启用 Shutdown Hook

Spring Boot 默认会通过 JVM 的 Shutdown Hook 触发优雅停机。确保以下配置启用:

spring:
  main:
    register-shutdown-hook: true

自定义资源释放逻辑

如果需要在停机时执行特定的清理操作,比如关闭数据库连接或停止线程池,可以通过添加 Shutdown Hook 或实现 DisposableBean 接口。

import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;

@Component
public class GracefulShutdownService implements DisposableBean {

    @Override
    public void destroy() throws Exception {
        System.out.println("Graceful shutdown - Cleaning up resources...");
        // 执行清理操作
    }
}

或者直接通过 JVM 钩子实现:

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    System.out.println("执行自定义的资源清理逻辑");
}));

超时机制

避免因某些请求耗时过长导致系统停机过程被阻塞,可以通过以下配置设置超时时间:

spring:
  lifecycle:
    timeout-per-shutdown-phase: 20s # 默认30秒

适用场景:

3. 配合自定义线程池实现优雅停机

如果你的应用使用了自定义线程池,想确保所有线程在应用关闭时能够有序停止,可以通过设置线程池的 shutdownshutdownNow 方法来实现。

Spring Boot 提供了多种方式来创建和配置线程池,如 TaskExecutor@Async 等。如果应用在停止时需要等待线程池中的任务完成,可以通过以下方式进行配置。

代码示例:

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {

    private final ThreadPoolTaskExecutor taskExecutor;

    public GracefulShutdownListener(ThreadPoolTaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        System.out.println("Shutting down thread pool...");
        
        // 给当前线程池中的任务一些时间来完成
        taskExecutor.shutdown();
        
        try {
            // 等待任务完成
            if (!taskExecutor.getThreadPoolExecutor().awaitTermination(60, TimeUnit.SECONDS)) {
                System.out.println("Timeout reached, forcing shutdown...");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

适用场景:

4. 在 Kubernetes 中实现优雅停机

如果你将 Spring Boot 应用部署在容器编排平台(如 Kubernetes)上,Kubernetes 会自动帮助你实现优雅停机。Kubernetes 发送 SIGTERM 信号,并等待容器停止一定时间(通常是 30 秒),在这段时间内,应用应当完成正在进行的请求并清理资源。

你可以通过设置 terminationGracePeriodSeconds 来配置应用在收到 SIGTERM 信号后的最大优雅停机时间。

配置示例(Kubernetes):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: my-app
          image: my-app-image
          ports:
            - containerPort: 8080
      terminationGracePeriodSeconds: 60  # 等待 60 秒

适用场景:

5. 使用自定义 shutdown 信号处理器

如果你不想完全依赖 Spring 提供的机制,可以实现一个自定义的信号处理器来捕获和响应关闭信号。通过这种方式,你可以更精细地控制停机流程。

代码示例:

import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class ShutdownSignalHandler {

    public ShutdownSignalHandler() throws IOException {
        // 注册 SIGTERM 信号处理器
        Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
    }

    private void shutdown() {
        System.out.println("Received shutdown signal. Performing graceful shutdown...");
        // 执行清理操作,如关闭连接池、停止后台服务等
    }
}

适用场景:

9、小结

优雅停机是 Spring Boot 应用的重要特性,它帮助我们确保在关闭应用时能够平稳地释放资源,处理完正在进行的请求,从而提高系统的稳定性和可靠性。通过 Spring Boot Actuator、ApplicationListener 接口和 JVM 钩子函数等多种方式,我们可以确保应用程序能够安全、顺利地关闭,而不会影响用户体验或导致数据丢失。

除了 Spring Boot Actuator 和 ApplicationListener 外,还有多种方式可以实现优雅停机。每种方式有不同的适用场景:

根据应用的需求,你可以选择合适的方式实现优雅停机,从而提高系统的可靠性和用户体验。

以上就是SpringBoot实现优雅停机的多种方式的详细内容,更多关于SpringBoot优雅停机的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文