在Spring Boot Document中,他们说每个SpringApplication都会向JVM注册一个关闭钩子,以确保在退出时正常关闭ApplicationContext。'
当我在shell命令上单击ctrl+c
时,可以正常关闭应用程序。如果我在生产机器中运行应用程序,我必须使用该命令
java -jar ProApplicaton.jar
。但我无法关闭shell终端,否则它将关闭该进程。
如果我运行nohup java -jar ProApplicaton.jar &
之类的命令,我就无法使用ctrl+c
正常关闭它。
在生产环境中启动和停止Spring Boot应用程序的正确方法是什么?
答案 0 :(得分:53)
如果您正在使用执行器模块,则可以通过JMX
或HTTP
关闭应用程序(如果端点已启用)(将endpoints.shutdown.enabled=true
添加到application.properties
文件中)。
/shutdown
- 允许应用程序正常关闭(默认情况下不启用)。
根据端点的暴露方式,敏感参数可用作安全提示。例如,敏感端点在HTTP
上访问时需要用户名/密码(如果未启用Web安全性,则只需禁用)。
答案 1 :(得分:44)
关于@ 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.application.properties:
#No auth protected
endpoints.shutdown.sensitive=false
#Enable shutdown endpoint
endpoints.shutdown.enabled=true
列出所有端点here:
3.发送一个post方法来关闭app:
curl -X POST localhost:port/shutdown
如果您需要使用auth保护的关闭方法,您可能还需要
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
答案 2 :(得分:34)
这是另一个不需要您更改代码或暴露关闭端点的选项。创建以下脚本并使用它们来启动和停止您的应用程序。
<强> start.sh 强>
#!/bin/bash
java -jar myapp.jar & echo $! > ./pid.file &
启动您的应用并将流程ID保存在文件
中<强> stop.sh 强>
#!/bin/bash
kill $(cat ./pid.file)
使用已保存的进程ID停止您的应用
<强> start_silent.sh 强>
#!/bin/bash
nohup ./start.sh > foo.out 2> foo.err < /dev/null &
如果您需要使用远程计算机或CI管道中的ssh启动应用程序,请使用此脚本来启动您的应用程序。直接使用start.sh可以让shell挂起。
例如。重新/部署您的应用程序,您可以使用以下命令重新启动它:
sshpass -p password ssh -oStrictHostKeyChecking=no userName@www.domain.com 'cd /home/user/pathToApp; ./stop.sh; ./silent_start.sh'
答案 3 :(得分:25)
您可以使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应用程序。 Reference。
现在您可以使用该脚本启动,停止或重新启动。
答案 4 :(得分:5)
我不公开任何终结点,而是启动(在后台使用nohup且不通过nohup创建文件),并使用shell脚本停止(使用 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
答案 5 :(得分:4)
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);
答案 6 :(得分:4)
SpringApplication隐式地向JVM注册一个关闭钩子,以确保在退出时正常关闭ApplicationContext。这也将调用所有使用@PreDestroy
注释的bean方法。这意味着我们不必在启动应用程序中明确使用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");
}
}
}
答案 7 :(得分: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();
}
}
答案 8 :(得分:3)
从Spring Boot 1.5开始,没有开箱即用的优雅关机机制。 一些spring-boot启动器提供此功能:
我是nr的作者。 1.首发队名为&#34; Hiatus for Spring Boot&#34;。它适用于负载均衡器级别,即只是将服务标记为OUT_OF_SERVICE,而不会以任何方式干扰应用程序上下文。这允许进行正常关闭,并且意味着,如果需要,可以将服务停止服务一段时间,然后恢复生命。缺点是它不会停止JVM,你必须使用kill
命令。当我在容器中运行所有东西时,这对我来说没什么大不了的,因为无论如何我将不得不停下来移除容器。
号。 2和3或多或少基于Andy Wilkinson的this post。他们单向工作 - 一旦被触发,他们最终会关闭背景。
答案 9 :(得分:2)
有很多方法可以关闭Spring应用程序。一种是在ApplicationContext
上调用close():
ApplicationContext ctx =
SpringApplication.run(HelloWorldApplication.class, args);
// ...
ctx.close()
您的问题建议您通过执行Ctrl+C
(通常用于终止命令)来关闭应用程序。在这种情况下...
使用endpoints.shutdown.enabled=true
并不是最好的食谱。这意味着您公开了一个终结点来终止您的应用程序。因此,根据您的用例和环境,您必须对其进行保护...
Ctrl+C
应该适合您的情况。我认为您的问题是由&符引起的(&)更多说明:
Spring Application Context可能已经向JVM运行时注册了一个关闭挂钩。参见ApplicationContext documentation。
我不知道Spring Boot是否如您所说自动配置了这个钩子。我想是这样。
在Ctrl+C
上,您的外壳程序将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
。
答案 10 :(得分:2)
Spring Boot现在支持正常关闭(当前在预发行版本2.3.0.BUILD-SNAPSHOT中)
启用后,关闭应用程序将包括宽限期 可配置的持续时间。在此宽限期内,现有请求 将被允许完成,但不允许新的请求
您可以通过以下方式启用它:
server.shutdown.grace-period=30s
答案 11 :(得分:0)
如果您处于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命令正常关闭。
答案 12 :(得分:0)
很多执行器的答案大多是正确的。不幸的是,配置和端点信息已更改,因此它们不是 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
答案 13 :(得分:0)
如果您使用的是 spring boot 2.3 n up 版本,有一种内置方式可以优雅地关闭应用程序。在 application.properties 中添加以下内容
server.shutdown=优雅 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);
}
}
}
答案 14 :(得分:0)
对于 Spring boot Web 应用程序,Spring boot 提供了开箱即用的解决方案,用于从 2.3.0.RELEASE
版本正常关闭。
参考此答案Code Snippet
答案 15 :(得分:0)
使用SpringApplication类中的静态exit()
方法来正常关闭Spring Boot应用程序。
public class SomeClass {
@Autowire
private ApplicationContext context
public void close() {
SpringApplication.exit(context);
}
}
答案 16 :(得分:0)
如果您使用的是maven,则可以使用Maven App assembler plugin。
The daemon mojo(嵌入JSW)将输出带有start / stop参数的shell脚本。 stop
将正常关闭/删除Spring应用程序。
可以使用相同的脚本将您的maven应用程序用作Linux服务。
答案 17 :(得分:-1)
使用Spring Boot Maven插件启动/运行部署
mvn spring-boot:run
并使用
停止/关闭Ctrl+C