如何以正确的方式关闭 Spring Boot 应用程序?

2024-10-10 09:28:00
admin
原创
80
摘要:问题描述:在 Spring Boot 文档中,他们说“每个 SpringApplication 都会向 JVM 注册一个关闭钩子,以确保 ApplicationContext 在退出时正常关闭。”当我点击ctrl+cshell 命令时,应用程序可以正常关闭。如果我在生产机器中运行该应用程序,我必须使用该命令 ...

问题描述:

在 Spring Boot 文档中,他们说“每个 SpringApplication 都会向 JVM 注册一个关闭钩子,以确保 ApplicationContext 在退出时正常关闭。”

当我点击ctrl+cshell 命令时,应用程序可以正常关闭。如果我在生产机器中运行该应用程序,我必须使用该命令
java -jar ProApplicaton.jar。但我不能关闭 shell 终端,否则它会关闭进程。

如果我运行类似的命令nohup java -jar ProApplicaton.jar &,我无法使用它ctrl+c来正常关闭它。

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


解决方案 1:

如果您正在使用执行器模块,则可以通过JMXHTTP在端点启用的情况下关闭应用程序。

添加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. 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

我是第 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

https://docs.spring.io/spring-boot/docs/2.3.0.BUILD-SNAPSHOT/reference/html/spring-boot-features.html#boot-features-graceful-shutdown

解决方案 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 上阻塞。不忽略nohupINT 信号。所以它不会阻止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
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用