如何以正确的方式关闭一个 Spring 引导应用程序?

在 Spring Boot Document 中,他们说“每个 SpringApplication 都会在 JVM 中注册一个关闭钩子,以确保 ApplicationContext 在退出时被优雅地关闭。”。'

当我在 shell 命令上单击 ctrl+c时,应用程序可以优雅地关闭。如果在生产机器中运行应用程序,则必须使用以下命令 但是我不能关闭 shell 终端,否则它将关闭进程。

如果我运行像 nohup java -jar ProApplicaton.jar &这样的命令,我就不能使用 ctrl+c优雅地关闭它。

在生产环境中启动和停止 Spring 引导应用程序的正确方法是什么?

249542 次浏览

如果使用执行器模块,则可以通过启用端点时的 JMXHTTP关闭应用程序。

application.properties中加入:

启用 = 真

网址如下:

/actuator/shutdown-允许优雅地关闭应用程序(默认情况下不启用)。

根据端点公开的方式,敏感参数可用作安全提示。

例如,敏感端点在通过 HTTP访问时需要一个用户名/密码(或者在没有启用 Web 安全性时仅仅禁用)。

来自 Spring 启动文档

如果您正在使用 maven,则可以使用 Maven 应用程序汇编程序插件

守护进程 mojo (嵌入了 JSW)将输出一个带 start/stop 参数的 shell 脚本。stop将优雅地关闭/终止 Spring 应用程序。

可以使用相同的脚本将 Maven 应用程序用作 linux 服务。

您可以让 springboot 应用程序将 PID 写入文件,并使用 PID 文件停止或重新启动或使用 bash 脚本获取状态。要将 PID 写入文件,请使用 ApplicationPidFileWriter 将侦听器注册到 SpringApplication,如下所示:

SpringApplication application = new SpringApplication(Application.class);
application.addListeners(new ApplicationPidFileWriter("./bin/app.pid"));
application.run();

然后编写一个 bash 脚本来运行春季启动应用程序。

现在您可以使用该脚本来启动、停止或重新启动。

至于“让-菲利普 · 邦德”的回答,

下面是一个 maven 快速示例,让 maven 用户配置 HTTP 端点,使用 spring-boot-starter-actiator 关闭一个 Spring 启动 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

保安注意事项:

如果您需要关闭方法的认证保护,您可能还需要

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

配置详情 :

下面是另一个不要求您更改代码或公开关闭端点的选项。创建以下脚本并使用它们启动和停止应用程序。

开始

#!/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 &

如果您需要从远程机器或 CI 管道使用 ssh 启动应用程序,那么使用这个脚本来启动您的应用程序。直接使用 start.sh 可以让 shell 挂起。

在重新部署你的应用程序之后,你可以使用以下方法重新启动它:

sshpass -p password ssh -oStrictHostKeyChecking=no userName@www.domain.com 'cd /home/user/pathToApp; ./stop.sh; ./start_silent.sh'

SpringBoot 在尝试创建应用程序上下文时提供了几个应用程序侦听器,其中一个是 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);

在 Spring Boot 2.3 和更高版本中,有一个内置的 优雅的关闭机制。

前春天启动2.3 ,没有开箱即用的优雅关机机制。 一些弹簧启动器提供了这样的功能:

  1. Https://github.com/jihor/hiatus-spring-boot
  2. Https://github.com/gesellix/graceful-shutdown-spring-boot
  3. Https://github.com/corentin59/spring-boot-graceful-shutdown

我是 nr 的作者。1.启动器名为“ Hiatus for Spring Boot”。它在负载平衡器级别上工作,即仅仅将服务标记为 OUT _ OF _ SERVICE,不以任何方式干扰应用程序上下文。这允许进行优雅的关闭,并意味着,如果需要,服务可以停止服务一段时间,然后恢复生命。缺点是它不能停止 JVM,必须使用 kill命令才能停止。当我在容器中运行所有东西时,这对我来说没什么大不了的,因为无论如何我将不得不停下来移除容器。

2号和3号或多或少是基于安迪 · 威尔金森的 这篇文章。它们是单向工作的——一旦触发,它们最终会关闭上下文。

SpringApplication 隐式地向 JVM 注册一个关闭钩子,以确保 ApplicationContext 在退出时被优雅地关闭。它还将调用用 @PreDestroy注释的所有 bean 方法。这意味着我们不必像在 Spring Core 应用程序中那样,在引导应用程序中显式地使用 ConfigurableApplicationContextregisterShutdownHook()方法。

@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");
}
}
}

所有的答案似乎都忽略了这样一个事实: 在优雅的关闭期间(例如,在企业应用程序中) ,您可能需要以协调的方式完成某些工作。

@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();
}
}

如果您处于 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 命令优雅地关闭。

我不公开任何端点和启动(后台有 nohup,没有通过 nohup 创建的 out 文件) ,也不使用 shell 脚本(使用 优雅地杀死 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

有很多方法可以关闭一个弹簧应用程序,其中之一就是在 ApplicationContext上调用 close () :

ApplicationContext ctx =
SpringApplication.run(HelloWorldApplication.class, args);
// ...
ctx.close()

您的问题表明您希望通过执行 Ctrl+C来关闭应用程序,Ctrl+C通常用于终止命令。在这种情况下。

使用 endpoints.shutdown.enabled=true不是最好的食谱。这意味着要公开终止应用程序的端点。因此,根据您的用例和环境,您必须保护它..。

SpringApplicationContext 可能在 JVM 运行时中注册了一个关闭钩子。

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 -9kill -KILL是不好的,因为应用程序(这里是 JVM)无法捕获它以优雅地终止。

另一个解决方案是将应用程序放回前台。那么 Ctrl+C就可以了。看看 Bash Job 控件,更准确地说是 fg

使用 SpringApplication 类中的静态 exit()方法来优雅地关闭您的 Spring 启动应用程序。

public class SomeClass {
@Autowired
private ApplicationContext context


public void close() {
SpringApplication.exit(context);
}
}

Spring Boot 现在支持优雅的关闭(目前在预发布版本中,2.3.0.BUILD-SNAPSHOT)

如果启用,应用程序的关闭将包括一个宽限期 在这段宽限期内,现有的请求 将被允许完成,但没有新的请求将被允许

你可透过以下方法启用:

server.shutdown.grace-period=30s

Https://docs.spring.io/spring-boot/docs/2.3.0.build-snapshot/reference/html/spring-boot-features.html#boot-features-graceful-shutdown

对于 Spring 启动 Web 应用程序,Spring 启动提供了从 2.3.0.RELEASE版本优雅关闭的开箱即用解决方案。

节选自 Spring Doc

请参考 < strong > 代码段 的答案

如果您正在使用春季启动版本2.3 n 向上,有一个内置的方式来关闭应用程序优雅。在 application.properties 中添加以下内容

关闭 = 优雅 每次关机超时-阶段 = 20秒

如果您正在使用低弹簧启动版本,您可以编写一个自定义关闭钩子和处理不同的 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);
}


}




}

许多执行机构的回答大部分是正确的。不幸的是,配置和端点信息已经更改,因此它们不是100% 正确。要启用执行器,请为 Maven 添加

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

或者是为了格拉德尔

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-only POST 关闭端点是不可用的:

curl -X POST localhost:8080/actuator/shutdown

我能够使用这些步骤在 SpringBootVersion > = 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

尝试在运行 cmd 或 bash 终端的服务器下使用以下命令。

kill $(jobs -p)

Microservices With Spring Boot And Spring Cloud - Build Resilient And Scalable Microservices书中得到的建议。

试试这个: 按 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