0%

Spring Boot安全停止服务的方案

在日常的开发和运维工作中,我们经常需要停止正在运行的Spring Boot应用。然而,如果我们不正确地停止应用,可能会导致数据丢失、资源泄露等问题。本文将介绍如何安全优雅地停止Spring Boot服务。

1. kill -9的风险

kill -9命令可以立即终止一个进程的运行,这使得它成为了强制停止进程的常用手段。然而,kill -9命令有一个重要的缺点:它不会给进程任何准备停止的时间,而是立即终止进程。这可能会导致以下问题:

  • 数据丢失:如果进程在处理数据时被终止,那么正在处理的数据可能会丢失。
  • 资源泄露:如果进程在被终止时没有释放资源,那么这些资源可能会被泄露。
  • 事务不一致:如果进程在执行一个事务时被终止,那么这个事务可能会处于不一致的状态。

因此,我们应该尽量避免使用kill -9命令,除非我们没有其他选择。

2. kill命令的差异性

kill命令用于向进程发送信号,不同的信号有不同的含义。以下是几个常用的信号:

  • SIGINT(-2):这是“中断”信号,通常由用户通过按下Ctrl+C来发送。大多数程序会在接收到这个信号后结束运行,但是也可以选择忽略这个信号或者处理这个信号。
  • SIGKILL(-9):这是“终止”信号,用于立即结束进程的运行。这个信号不能被忽略或者处理,所以通常用于强制结束一个不响应的进程。
  • SIGTERM(-15):这是“终止”信号,通常用于优雅地结束进程的运行。大多数程序会在接收到这个信号后结束运行,但是也可以选择忽略这个信号或者处理这个信号(没有指定信号,所以默认是SIGTERM)。

在实际应用中,我们通常首先发送SIGTERM信号来优雅地结束进程的运行。如果进程没有响应SIGTERM信号,我们再发送SIGKILL信号来强制结束进程的运行。

kill图

3. Spring Boot安全停止服务的方法

在Spring Boot中,我们有多种方法可以安全优雅地停止服务。

Spring Boot的graceful配置

从Spring Boot 2.3.0开始,Spring Boot支持优雅地关闭应用。你可以通过设置server.shutdown=graceful来启用这个功能。

当你设置了server.shutdown=graceful,Spring Boot会在接收到关闭信号后,首先停止接受新的请求,然后等待所有当前正在处理的请求处理完成,最后再关闭应用。这样可以确保不会中断正在处理的请求,从而实现优雅地关闭应用。

你可以在你的application.propertiesapplication.yml文件中设置这个配置:

1
server.shutdown=graceful

然后,你可以使用以下的Shell脚本来优雅地重启你的Spring Boot应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
APP_NAME=myapp.jar
PID=$(cat pid.file)

echo "Stopping $APP_NAME..."
kill $PID
while kill -0 $PID > /dev/null 2>&1; do
sleep 1
done
echo "$APP_NAME stopped."

echo "Starting $APP_NAME..."
nohup java -jar $APP_NAME > /dev/null 2>&1 & echo $! > pid.file
echo "$APP_NAME started."

在这个脚本中,我们首先发送SIGTERM信号(默认的kill信号)来优雅地关闭应用,然后等待应用关闭,最后再启动应用。

Actuator的/shutdown端点配置

这种方案不限于2.3以上版本,Spring Boot Actuator提供了一个/shutdown端点,可以用来关闭应用。要使用这个功能,你需要在你的application.propertiesapplication.yml文件中启用它:

1
management.endpoint.shutdown.enabled=true

然后,你可以使用curl或其他HTTP客户端发送POST请求到/actuator/shutdown端点来关闭应用。

但是仅仅是spring层面上的,不和tomcat等容器挂钩,也就是说2.3版本之前Spring Boot本身无法实现优雅关闭服务,还需要使用代码实现容器的优雅关闭逻辑,以Tomcat为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
* 优雅关闭
*/
@Slf4j
public class GracefulShutdown implements TomcatConnectorCustomizer,
ApplicationListener<ContextClosedEvent> {

private volatile Connector connector;

/**
* 30s强制关闭
*/
private static final int TIMEOUT = 30;

/**
* 自定义链接
*
* @param connector
*/
@Override
public void customize(Connector connector) {
this.connector = connector;
}

/**
* 关闭时触发
*
* @param event
*/
@Override
public void onApplicationEvent(ContextClosedEvent event) {
if (this.connector == null) {
return;
}
this.connector.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(TIMEOUT, TimeUnit.SECONDS)) {
log.warn("Tomcat thread pool did not shut down gracefully within "
+ "30 seconds. Proceeding with forceful shutdown");
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}

4. 总结

在Spring Boot中,我们有多种方法可以安全优雅地停止服务。我们应该尽量避免使用kill -9命令,除非我们没有其他选择。我们可以使用kill -15kill -2命令,并结合使用Spring Boot的server.shutdown=graceful配置,或者使用Spring Boot Actuator的/shutdown端点来优雅地关闭服务。在实际应用中,我们需要根据我们的具体需求来选择最适合我们的方法。

坚持原创技术分享,您的支持将鼓励我继续创作!