【问题标题】:How to shutdown a Spring Boot Application in a correct way?如何以正确的方式关闭 Spring Boot 应用程序?
【发布时间】:2014-12-20 06:27:01
【问题描述】:

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

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

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

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

【问题讨论】:

  • 根据您的配置,将应用程序的 PID 写入文件。您可以向该 PID 发送终止信号。另请参阅this issue 中的 cmets。
  • 我应该使用哪个信号,我不认为 kill -9 是个好主意,对吧?
  • 这就是为什么我把你指向那个线程......但是像kill -SIGTERM <PID> 这样的东西应该可以解决问题。
  • 用 pid 杀死,没有 -9
  • kill $(lsof -ti tcp:) -- 如果您不想使用执行器并需要快速杀死

标签: linux spring spring-boot


【解决方案1】:

如果您使用的是执行器模块,您可以通过JMXHTTP(如果启用了端点)关闭应用程序。

添加到application.properties:

endpoints.shutdown.enabled=true

以下网址将可用:

/actuator/shutdown - 允许应用程序正常关闭(默认不启用)。

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

例如,敏感端点在通过HTTP 访问时将需要用户名/密码(如果未启用网络安全,则直接禁用)。

来自Spring boot documentation

【讨论】:

  • 我不想包含执行器模块。但我发现,在我的控制台日志中,Spring Boot 在第一行打印了 PID。有什么方法可以让Spring Boot在其他文件中打印PID而不添加执行器模块(ApplicationPidListener)?
  • 请注意,这必须是指向此端点的 http 帖子。您可以使用 endpoints.shutdown.enabled=true 启用它
【解决方案2】:

如果您使用的是 maven,则可以使用 Maven App assembler plugin

The daemon mojo(嵌入JSW)将输出一个带有启动/停止参数的shell脚本。 stop 将优雅地关闭/终止您的 Spring 应用程序。

相同的脚本可用于将您的 maven 应用程序用作 linux 服务。

【讨论】:

    【解决方案3】:

    您可以让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】:

    至于@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方法:

    curl -X POST localhost:port/shutdown
    

    安全提示:

    如果你需要关闭方法auth protected,你可能还需要

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

    configure details:

    【讨论】:

    • 第二步后,尝试部署时遇到这个错误信息: 说明:org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter 方法setAuthenticationConfiguration 的参数0 required a bean of type 'org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration' 找不到。行动:考虑在你的配置中定义一个“org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration”类型的bean。
    • 请注意:如果我在 application.properties 中包含了类似server.contextPath=/appName 的内容,那么现在关闭命令将变为:curl -X POST localhost:8080/appName/shutdown 希望它可以帮助某人。因为这个错误,我不得不挣扎很多。
    • 对于 Spring Boot 1.5.8,如果没有安全性,建议在 application.properties endpoints.shutdown.enabled=true management.security.enabled=false 中添加。
    • 如果你不想暴露端点并使用 PID 文件通过 shell 脚本停止和启动,试试这个:stackoverflow.com/questions/26547532/…
    【解决方案5】:

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

    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 &
    

    如果您需要使用 ssh 从远程计算机或 CI 管道启动应用程序,请改用此脚本来启动您的应用程序。直接使用 start.sh 可以让 shell 挂起。

    在例如之后。重新/部署您的应用程序,您可以使用以下方法重新启动它:

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

    【讨论】:

    • 这应该是答案。我刚刚确认了信号 15 的关闭告诉 spring 优雅地关闭。
    • 为什么不在 start.sh 中使用 nohup 调用 java - jar 执行,而不是在 start.sh 中调用 java - jar 执行,后者在另一个外壳脚本中使用 nohup 调用??跨度>
    • @AnandVarkeyPhilips 唯一的原因是我有时会从命令行调用 start.sh 进行测试,但如果你总是需要nohup 那么你可以合并命令
    • @Jens,感谢您提供的信息。你能告诉我你这样做吗: foo.out 2> foo.err /null &
    • @jens,谢谢我已经成功完成了,我已经在下面发布了我的开始和停止脚本。 (stackoverflow.com/questions/26547532/…)
    【解决方案6】:

    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);  
    

    【讨论】:

    • 我找到的最佳答案!谢谢!
    • [@user3137438],它与在预销毁注解中登录有何不同?
    【解决方案7】:

    从 Spring Boot 2.3 及更高版本开始,有一个内置的graceful shutdown 机制。

    Pre-Spring Boot 2.3,没有开箱即用的优雅关闭机制。 一些 spring-boot 启动器提供了这个功能:

    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 或多或少基于 Andy Wilkinson 的 this post。它们单向工作 - 一旦触发,它们最终会关闭上下文。

    【讨论】:

    • 优雅关闭似乎不适用于可执行jar,并且在原始问题中他似乎使用了可执行jar。
    【解决方案8】:

    SpringApplication 向 JVM 隐式注册了一个关闭钩子,以确保 ApplicationContext 在退出时正常关闭。这也将调用所有带有@PreDestroy 注释的bean 方法。这意味着我们不必在启动应用程序中显式使用 ConfigurableApplicationContextregisterShutdownHook() 方法,就像我们在 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");
            }
        }
    }
    

    【讨论】:

    • 作为@PostConstruct@PreDestroy 的替代品,我在@Bean 注释中使用了initMethoddestroyMethod 属性。所以对于这个例子:@Bean(initMethod="init", destroyMethod="destroy").
    • 关于@PreDestroy 一些开发人员可能不知道的一个问题是,此类方法仅对具有单例范围的bean 调用。开发人员必须为其他范围管理 bean 生命周期的清理部分
    【解决方案9】:

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

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

    【讨论】:

    • 这正是我所需要的。执行应用程序后,按 ctrl-c 感谢@Michal
    【解决方案10】:

    如果您在 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 命令正常关闭。

    【讨论】:

      【解决方案11】:

      我不公开任何端点并开始(在后台使用 nohup 并且没有通过 nohup 创建的文件)并以 shell 脚本停止(使用 KILL PID 并在应用程序时强制终止3 分钟后仍在运行)。我只是创建可执行 jar 并使用 PID 文件编写器编写 PID 文件并将 Jar 和 Pid 存储在与应用程序名称同名的文件夹中,并且 shell 脚本的名称也与最后的 start 和 stop 相同。我也通过 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
      

      【讨论】:

        【解决方案12】:

        它们是关闭 Spring 应用程序的多种方法。一种是在ApplicationContext上调用close():

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

        您的问题建议您通过执行Ctrl+C 来关闭您的应用程序,这通常用于终止命令。这种情况下……

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

        Spring 应用程序上下文可能已向 JVM 运行时注册了一个关闭挂钩。见ApplicationContext documentation

        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

        【讨论】:

          【解决方案13】:

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

          public class SomeClass {
              @Autowired
              private ApplicationContext context
          
              public void close() {
                  SpringApplication.exit(context);
              }
          }
          

          【讨论】:

          • 错字,@Autowired
          【解决方案14】:

          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

          【讨论】:

          【解决方案15】:

          对于 Spring Boot Web 应用,Spring Boot 从版本 2.3.0.RELEASE 开始提供开箱即用的正常关闭解决方案。

          摘自Spring doc

          请参阅此答案以获取 Code Snippet

          【讨论】:

            【解决方案16】:

            如果你使用的是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);
                    }
            
                }
            
            
            }
            

            【讨论】:

              【解决方案17】:

              许多执行器的答案大多是正确的。不幸的是,配置和端点信息已更改,因此它们并非 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
              

              【讨论】:

                【解决方案18】:

                我可以使用这些步骤在 Spring Boot Version >=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
                

                【讨论】:

                  猜你喜欢
                  • 2014-05-21
                  • 2019-06-06
                  • 2019-06-17
                  • 1970-01-01
                  • 2017-03-06
                  • 1970-01-01
                  • 2014-12-07
                  • 2016-06-09
                  相关资源
                  最近更新 更多