如何以正确的方式关闭 Spring Boot 应用程序?
- 2024-10-10 09:28:00
- admin 原创
- 80
问题描述:
在 Spring Boot 文档中,他们说“每个 SpringApplication 都会向 JVM 注册一个关闭钩子,以确保 ApplicationContext 在退出时正常关闭。”
当我点击ctrl+c
shell 命令时,应用程序可以正常关闭。如果我在生产机器中运行该应用程序,我必须使用该命令 java -jar ProApplicaton.jar
。但我不能关闭 shell 终端,否则它会关闭进程。
如果我运行类似的命令nohup java -jar ProApplicaton.jar &
,我无法使用它ctrl+c
来正常关闭它。
在生产环境中启动和停止 Spring Boot 应用程序的正确方法是什么?
解决方案 1:
如果您正在使用执行器模块,则可以通过JMX
或HTTP
在端点启用的情况下关闭应用程序。
添加application.properties
:
Spring Boot 2.0 及更新版本:
管理.端点.关闭.启用=真
可以使用以下 URL:
/actuator/shutdown
- 允许应用程序正常关闭(默认情况下未启用)。
根据端点的暴露方式,敏感参数可能被用作安全提示。
例如,敏感端点在访问时将需要用户名/密码HTTP
(如果未启用网络安全,则只需禁用)。
来自Spring boot 文档
解决方案 2:
这是另一个选项,不需要您更改代码或公开关闭端点。创建以下脚本并使用它们来启动和停止您的应用程序。
启动脚本
#!/bin/bash
java -jar myapp.jar & echo $! > ./pid.file &
启动你的应用并将进程 ID 保存在文件中
停止脚本
#!/bin/bash
kill $(cat ./pid.file)
使用保存的进程 ID 停止你的应用
启动静音程序
#!/bin/bash
nohup ./start.sh > foo.out 2> foo.err < /dev/null &
如果您需要使用 ssh 从远程计算机或 CI 管道启动应用程序,请使用此脚本来启动应用程序。直接使用 start.sh 可能会导致 shell 挂起。
例如,重新部署你的应用程序后,你可以使用以下命令重新启动它:
sshpass -p password ssh -oStrictHostKeyChecking=no userName@www.domain.com 'cd /home/user/pathToApp; ./stop.sh; ./start_silent.sh'
解决方案 3:
至于@Jean-Philippe Bond 的回答,
这里有一个 maven 快速示例,供 maven 用户配置 HTTP 端点以使用 spring-boot-starter-actuator 关闭 spring boot web 应用程序,以便您可以复制和粘贴:
1.Maven pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.应用程序.属性:
#No auth protected
endpoints.shutdown.sensitive=false
#Enable shutdown endpoint
endpoints.shutdown.enabled=true
所有端点都列在这里:
3.发送post方法来关闭应用程序:
curl -X POST localhost:port/shutdown
安全说明:
如果你需要关机方法 auth protected,你可能还需要
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
配置详细信息:
解决方案 4:
所有的答案似乎都忽略了一个事实,即您可能需要在正常关机期间以协调的方式完成部分工作(例如,在企业应用程序中)。
@PreDestroy
允许你在单个 bean 中执行关闭代码。更复杂的代码如下:
@Component
public class ApplicationShutdown implements ApplicationListener<ContextClosedEvent> {
@Autowired ... //various components and services
@Override
public void onApplicationEvent(ContextClosedEvent event) {
service1.changeHeartBeatMessage(); // allows loadbalancers & clusters to prepare for the impending shutdown
service2.deregisterQueueListeners();
service3.finishProcessingTasksAtHand();
service2.reportFailedTasks();
service4.gracefullyShutdownNativeSystemProcessesThatMayHaveBeenLaunched();
service1.eventLogGracefulShutdownComplete();
}
}
解决方案 5:
您可以让 springboot 应用程序将 PID 写入文件,然后可以使用 pid 文件停止或重新启动,或者使用 bash 脚本获取状态。要将 PID 写入文件,请使用 ApplicationPidFileWriter 向 SpringApplication 注册一个侦听器,如下所示:
SpringApplication application = new SpringApplication(Application.class);
application.addListeners(new ApplicationPidFileWriter("./bin/app.pid"));
application.run();
然后编写一个bash脚本来运行spring boot应用程序。参考。
现在您可以使用脚本来启动、停止或重新启动。
解决方案 6:
exit()
使用SpringApplication 类中的静态方法来优雅地关闭你的 spring boot 应用程序。
public class SomeClass {
@Autowired
private ApplicationContext context
public void close() {
SpringApplication.exit(context);
}
}
解决方案 7:
从 Spring Boot 2.3及更高版本开始,有一个内置的正常关闭机制。
Spring Boot 2.3 之前没有现成的优雅关闭机制。一些 spring-boot starter 提供了此功能:
我是第 1 篇的作者。启动器名为“Hiatus for Spring Boot”。它在负载均衡器级别工作,即简单地将服务标记为 OUT_OF_SERVICE,不会以任何方式干扰应用程序上下文。这允许正常关闭,并且意味着,如果需要,服务可以停止服务一段时间,然后恢复运行。缺点是它不会停止 JVM,您必须使用kill
命令来执行此操作。由于我在容器中运行所有内容,这对我来说没什么大不了的,因为无论如何我都必须停止并删除容器。
第 2 和第 3 条或多或少基于Andy Wilkinson 的这篇文章。它们是单向的 - 一旦触发,它们最终会关闭上下文。
解决方案 8:
我没有公开任何端点,而是使用 shell 脚本启动(在后台使用 nohup 并且不通过 nohup 创建输出文件)和停止(正常情况下使用 KILL PID 并且如果应用程序在 3 分钟后仍在运行则强制终止)。我只是创建可执行 jar 并使用 PID 文件编写器来编写 PID 文件并将 Jar 和 Pid 存储在与应用程序名称同名的文件夹中,并且 shell 脚本在最后也与启动和停止同名。我也通过 jenkins 管道调用这些停止脚本和启动脚本。到目前为止没有问题。完美适用于 8 个应用程序(非常通用的脚本,易于应用于任何应用程序)。
主类
@SpringBootApplication
public class MyApplication {
public static final void main(String[] args) {
SpringApplicationBuilder app = new SpringApplicationBuilder(MyApplication.class);
app.build().addListeners(new ApplicationPidFileWriter());
app.run();
}
}
YML 文件
spring.pid.fail-on-write-error: true
spring.pid.file: /server-path-with-folder-as-app-name-for-ID/appName/appName.pid
这是启动脚本(start-appname.sh):
#Active Profile(YAML)
ACTIVE_PROFILE="preprod"
# JVM Parameters and Spring boot initialization parameters
JVM_PARAM="-Xms512m -Xmx1024m -Dspring.profiles.active=${ACTIVE_PROFILE} -Dcom.webmethods.jms.clientIDSharing=true"
# Base Folder Path like "/folder/packages"
CURRENT_DIR=$(readlink -f "$0")
BASE_PACKAGE="${CURRENT_DIR%/bin/*}"
# Shell Script file name after removing path like "start-yaml-validator.sh"
SHELL_SCRIPT_FILE_NAME=$(basename -- "$0")
# Shell Script file name after removing extension like "start-yaml-validator"
SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT="${SHELL_SCRIPT_FILE_NAME%.sh}"
# App name after removing start/stop strings like "yaml-validator"
APP_NAME=${SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT#start-}
PIDS=`ps aux |grep [j]ava.*-Dspring.profiles.active=$ACTIVE_PROFILE.*$APP_NAME.*jar | awk {'print $2'}`
if [ -z "$PIDS" ]; then
echo "No instances of $APP_NAME with profile:$ACTIVE_PROFILE is running..." 1>&2
else
for PROCESS_ID in $PIDS; do
echo "Please stop the process($PROCESS_ID) using the shell script: stop-$APP_NAME.sh"
done
exit 1
fi
# Preparing the java home path for execution
JAVA_EXEC='/usr/bin/java'
# Java Executable - Jar Path Obtained from latest file in directory
JAVA_APP=$(ls -t $BASE_PACKAGE/apps/$APP_NAME/$APP_NAME*.jar | head -n1)
# To execute the application.
FINAL_EXEC="$JAVA_EXEC $JVM_PARAM -jar $JAVA_APP"
# Making executable command using tilde symbol and running completely detached from terminal
`nohup $FINAL_EXEC </dev/null >/dev/null 2>&1 &`
echo "$APP_NAME start script is completed."
这是停止脚本(stop-appname.sh):
#Active Profile(YAML)
ACTIVE_PROFILE="preprod"
#Base Folder Path like "/folder/packages"
CURRENT_DIR=$(readlink -f "$0")
BASE_PACKAGE="${CURRENT_DIR%/bin/*}"
# Shell Script file name after removing path like "start-yaml-validator.sh"
SHELL_SCRIPT_FILE_NAME=$(basename -- "$0")
# Shell Script file name after removing extension like "start-yaml-validator"
SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT="${SHELL_SCRIPT_FILE_NAME%.*}"
# App name after removing start/stop strings like "yaml-validator"
APP_NAME=${SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT:5}
# Script to stop the application
PID_PATH="$BASE_PACKAGE/config/$APP_NAME/$APP_NAME.pid"
if [ ! -f "$PID_PATH" ]; then
echo "Process Id FilePath($PID_PATH) Not found"
else
PROCESS_ID=`cat $PID_PATH`
if [ ! -e /proc/$PROCESS_ID -a /proc/$PROCESS_ID/exe ]; then
echo "$APP_NAME was not running with PROCESS_ID:$PROCESS_ID.";
else
kill $PROCESS_ID;
echo "Gracefully stopping $APP_NAME with PROCESS_ID:$PROCESS_ID..."
sleep 5s
fi
fi
PIDS=`/bin/ps aux |/bin/grep [j]ava.*-Dspring.profiles.active=$ACTIVE_PROFILE.*$APP_NAME.*jar | /bin/awk {'print $2'}`
if [ -z "$PIDS" ]; then
echo "All instances of $APP_NAME with profile:$ACTIVE_PROFILE has has been successfully stopped now..." 1>&2
else
for PROCESS_ID in $PIDS; do
counter=1
until [ $counter -gt 150 ]
do
if ps -p $PROCESS_ID > /dev/null; then
echo "Waiting for the process($PROCESS_ID) to finish on it's own for $(( 300 - $(( $counter*5)) ))seconds..."
sleep 2s
((counter++))
else
echo "$APP_NAME with PROCESS_ID:$PROCESS_ID is stopped now.."
exit 0;
fi
done
echo "Forcefully Killing $APP_NAME with PROCESS_ID:$PROCESS_ID."
kill -9 $PROCESS_ID
done
fi
解决方案 9:
Spring Boot 在尝试创建应用程序上下文时提供了几个应用程序监听器,其中之一是 ApplicationFailedEvent。我们可以使用它来了解应用程序上下文是否已初始化。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.context.ApplicationListener;
public class ApplicationErrorListener implements
ApplicationListener<ApplicationFailedEvent> {
private static final Logger LOGGER =
LoggerFactory.getLogger(ApplicationErrorListener.class);
@Override
public void onApplicationEvent(ApplicationFailedEvent event) {
if (event.getException() != null) {
LOGGER.info("!!!!!!Looks like something not working as
expected so stoping application.!!!!!!");
event.getApplicationContext().close();
System.exit(-1);
}
}
}
将上述监听器类添加到SpringApplication中。
new SpringApplicationBuilder(Application.class)
.listeners(new ApplicationErrorListener())
.run(args);
解决方案 10:
SpringApplication 隐式地向 JVM 注册一个关闭钩子,以确保 ApplicationContext 在退出时正常关闭。它还将调用所有用 注释的 bean 方法@PreDestroy
。这意味着我们不必在启动应用程序中明确使用 registerShutdownHook()
的方法ConfigurableApplicationContext
,就像我们必须在 spring 核心应用程序中所做的那样。
@SpringBootConfiguration
public class ExampleMain {
@Bean
MyBean myBean() {
return new MyBean();
}
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(ExampleMain.class, args);
MyBean myBean = context.getBean(MyBean.class);
myBean.doSomething();
//no need to call context.registerShutdownHook();
}
private static class MyBean {
@PostConstruct
public void init() {
System.out.println("init");
}
public void doSomething() {
System.out.println("in doSomething()");
}
@PreDestroy
public void destroy() {
System.out.println("destroy");
}
}
}
解决方案 11:
我可以按照以下步骤在Spring Boot 版本 >=2.5.3上执行此操作。
1. 添加以下执行器依赖项
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
2. 在 application.properties 中添加这些属性以实现正常关闭
management.endpoint.shutdown.enabled=true
management.endpoints.web.exposure.include=shutdown
server.shutdown=GRACEFUL
3. 启动应用程序时,您应该在控制台中看到此信息(基于您公开的端点数量)
Exposing 1 endpoint(s) beneath base path '/actuator'
4. 要关闭应用程序,请执行以下操作:
POST: http://localhost:8080/<context-path-if-any>/actuator/shutdown
解决方案 12:
Spring Boot 现在支持优雅关闭(目前在预发布版本中,2.3.0.BUILD-SNAPSHOT)
启用后,应用程序的关闭将包括一个可配置持续时间的宽限期。在此宽限期内,现有请求将被允许完成,但不允许新的请求
您可以使用以下方式启用它:
server.shutdown.grace-period=30s
解决方案 13:
有很多方法可以关闭 Spring 应用程序。一种方法是调用 close() ApplicationContext
:
ApplicationContext ctx =
SpringApplication.run(HelloWorldApplication.class, args);
// ...
ctx.close()
您的问题表明您想通过执行 来关闭应用程序Ctrl+C
,这通常用于终止命令。在这种情况下...
使用endpoints.shutdown.enabled=true
并不是最好的方法。这意味着您暴露了一个端点来终止您的应用程序。因此,根据您的用例和环境,您必须保护它...
Spring 应用程序上下文可能已向 JVM 运行时注册了关闭钩子。请参阅ApplicationContext 文档。
Spring Boot 从 2.3 版开始自动配置此关闭钩子(请参阅 jihor 的回答)。您可能需要注册一些将在正常关闭期间执行的 @PreDestroy 方法(请参阅 Michal 的回答)。
Ctrl+C
应该对你的情况很有帮助。我假设你的问题是由 & 符号引起的。更多解释:
在 上Ctrl+C
,您的 shell 会INT
向前台应用程序发送一个信号。这意味着“请中断您的执行”。应用程序可以捕获此信号并在终止之前进行清理(Spring 注册的钩子),或者简单地忽略它(不好)。
nohup
是使用陷阱执行以下程序以忽略 HUP 信号的命令。HUP 用于在您挂断时终止程序(例如关闭 ssh 连接)。此外,它重定向输出以避免您的程序在消失的 TTY 上阻塞。不忽略nohup
INT 信号。所以它不会阻止Ctrl+C
工作。
我认为您的问题是由“&”符号引起的,而不是由 nohup 引起的。Ctrl+C
向前台进程发送信号。“&”符号导致您的应用程序在后台运行。一种解决方案:执行
kill -INT pid
使用kill -9
或是kill -KILL
不好的,因为应用程序(这里是 JVM)无法捕获它并正常终止。
另一个解决方案是将应用程序重新置于前台。然后Ctrl+C
就可以了。查看 Bash Job 控件,更准确地说是fg
。
解决方案 14:
很多执行器答案大多是正确的。不幸的是,配置和端点信息已更改,因此它们并非 100% 正确。要启用执行器,请添加 Maven
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
或者对于 Gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
对于配置,将以下内容添加到 application.properties。这将公开执行器中的所有端点:
management.endpoints.web.exposure.include=*
management.endpoint.shutdown.enabled=true
要仅公开关闭端点,请更改为:
management.endpoints.web.exposure.include=shutdown
management.endpoint.shutdown.enabled=true
最后,关闭端点无法通过 GET 获得,只能通过 POST 获得。因此,您必须使用类似以下方法:
curl -X POST localhost:8080/actuator/shutdown
解决方案 15:
如果您使用 maven,则可以使用Maven App assembler 插件。
守护进程 mojo(嵌入JSW)将输出带有启动/停止参数的 shell 脚本。它将stop
正常关闭/终止您的 Spring 应用程序。
相同的脚本可用于将您的 maven 应用程序用作 Linux 服务。
解决方案 16:
如果你在 Linux 环境中,你所要做的就是在 /etc/init.d/ 中创建到你的 .jar 文件的符号链接
sudo ln -s /path/to/your/myboot-app.jar /etc/init.d/myboot-app
然后你就可以像启动其他服务一样启动应用程序
sudo /etc/init.d/myboot-app start
关闭应用程序
sudo /etc/init.d/myboot-app stop
这样,退出终端时应用程序就不会终止。并且应用程序将使用 stop 命令正常关闭。
解决方案 17:
对于 Spring boot web 应用,Spring boot 从版本开始提供了开箱即用的优雅关闭解决方案2.3.0.RELEASE
。
Spring 文档摘录
请参阅此答案以获取代码片段
解决方案 18:
如果你正在使用 spring boot 2.3 及以上版本,有一种内置方法可以正常关闭应用程序。在 application.properties 中添加以下内容
server.shutdown = graceful spring.lifecycle.timeout-per-shutdown-phase = 20s
如果您使用的是较低的 Spring Boot 版本,您可以编写自定义关闭钩子并处理不同的 bean,它们应该如何关闭或按什么顺序关闭。示例代码如下。
@Component
public class AppShutdownHook implements ApplicationListener<ContextClosedEvent> {
private static final Logger logger = LoggerFactory.getLogger(AppShutdownHook.class);
@Override
public void onApplicationEvent(ContextClosedEvent event) {
logger.info("shutdown requested !!!");
try {
//TODO Add logic to shutdown, diff elements of your application
} catch (Exception e) {
logger.error("Exception occcured while shutting down Application:", e);
}
}
}
解决方案 19:
尝试在运行 cmd 或 bash 终端的服务器下使用以下命令。
kill $(jobs -p)
从书中得到的推荐Microservices With Spring Boot And Spring Cloud - Build Resilient And Scalable Microservices
。
解决方案 20:
尝试一下:按ctrl
+C
- [INFO]
------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO]
------------------------------------------------------------------------ [INFO] Total time: 04:48 min [INFO] Finished at:
2022-09-07T18:17:35+05:30 [INFO]
------------------------------------------------------------------------
Terminate batch job (Y/N)?
Type Y to terminate
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件