SpringBoot优雅关机,监听关机事件,docker配置

news2025/4/21 19:38:21

Spring Boot 提供了多种方法来实现优雅停机(Graceful Shutdown),这意味着在关闭应用程序之前,它会等待当前正在处理的请求完成,并且不再接受新的请求。

一、优雅停机的基本概念

优雅停机的主要步骤如下:

  1. 停止接收新的请求:一旦收到关闭指令,服务器会停止接受新的请求。
  2. 处理当前请求:系统会继续处理已经在处理中的请求,确保这些请求能够正常完成。
  3. 释放资源:在所有请求处理完毕后,系统会释放已分配的资源,比如关闭数据库连接、断开网络连接等。
  4. 关闭服务:当所有资源都被正确释放之后,系统会安全地关闭服务。

二、实现优雅停机的方法

2.1、在 Spring Boot 2.3 及以上版本中启用优雅停机

从 Spring Boot 2.3 开始,默认集成了对优雅停机的支持,你只需要通过配置文件进行简单的设置即可。

application.ymlapplication.properties 文件中添加以下配置:

server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 60s # 设置最大等待时间为60秒

上述配置告诉 Spring Boot 使用优雅的方式关闭应用,并设置了最长等待时间。

2.2、 对于 Spring Boot 2.3 之前的版本

如果你使用的是 Spring Boot 2.3 之前的版本,则需要手动引入 spring-boot-starter-actuator 并利用其提供的 /shutdown 端点来实现优雅停机。

首先,在你的 pom.xml 或者 build.gradle 中添加依赖:

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

然后,在 application.yml 中开启 /shutdown 端点:

management:
  endpoints:
    web:
      exposure:
        include: "shutdown"
  endpoint:
    shutdown:
      enabled: true

接下来,你可以通过发送 POST 请求到 /actuator/shutdown 来触发优雅停机。

2.3、 自定义优雅停机逻辑

如果默认的优雅停机行为不能满足需求,你还可以自定义优雅停机逻辑。例如,对于不同的 Web 容器(如 Tomcat、Jetty、Undertow),可以编写相应的代码来控制线程池的行为。

以 Tomcat 为例,你可以创建一个类实现 GracefulShutdownListenerApplicationListener<ContextClosedEvent> 接口,来定制化优雅停机过程。

package cn.gxm.multiinstancetest.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2025年03月04日
 */
@Slf4j
@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {


    /**
     * 优雅关机是否已开始
     */
    private boolean isGracefulShutdownStarted = false;

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        // 执行你的业务逻辑
        log.info("Executing custom logic before graceful shutdown...");
        this.isGracefulShutdownStarted = true;
    }


    public boolean isGracefulShutdownStarted() {
        return isGracefulShutdownStarted;
    }
}

2.4、测试

下面的测试中, spring.lifecycle.timeout-per-shutdown-phase 值为30。

在这里插入图片描述

2.4.1、接口测试

1、写一个接口/sleep/{seconds}来测试。

package cn.gxm.multiinstancetest.controller;

import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2025年01月03日
 */
@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {

    @GetMapping("/say")
    public String say() throws UnknownHostException {
        InetAddress localHost = InetAddress.getLocalHost();
        log.info("localhost: {}", localHost);
        return localHost.getHostAddress() + "_" + localHost.getHostName();
    }

    @GetMapping("/sleep/{seconds}")
    public String sleep(@PathVariable(value = "seconds") Integer seconds) {
        String startTime = DateUtil.now();
        try {
            Thread.sleep(seconds * 1000);
        } catch (Exception e) {
            log.error("sleep:", e);
        }
        String endTime = DateUtil.now();
        return startTime + "      " + endTime;
    }
}

2、第一种情况:在关机之前,请求接口http://127.0.0.1:9600/multi-instance-test/test/sleep/20,会导致睡眠20秒,接着马上在Idea关机程序,触发优雅关机。接口正常返回,并相差20秒,并且查看程序日志也是发现等待接口完成后才关机

在这里插入图片描述
在这里插入图片描述

3、第二种情况:在关机之前,请求接口http://127.0.0.1:9600/multi-instance-test/test/sleep/50,会导致睡眠50秒,接着马上在Idea关机程序,触发优雅关机。接口不会正常返回,并且查看程序日志发现关机就是相差30秒,所以如果你的接口处理的逻辑超过了设置的优雅关机的时间,它是不会管你的,任然会直接关机,会导致你的接口业务没有处理完成。

在这里插入图片描述

2.4.2、GracefulShutdownListener 测试

1、在 GracefulShutdownListener 类里通过sleep来模拟业务

    @Slf4j
@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {


    /**
     * 优雅关机是否已开始
     */
    private boolean isGracefulShutdownStarted = false;

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        // 执行你的业务逻辑
        log.info("Executing custom logic before graceful shutdown...");
        this.isGracefulShutdownStarted = true;

        try {
            Thread.sleep(60 * 1000);
        } catch (Exception e) {
            log.error("sleep:", e);
        }
        log.info("Executing custom logic before graceful shutdown,end!");
    }


    public boolean isGracefulShutdownStarted() {
        return isGracefulShutdownStarted;
    }
}

2、第一种情况:设置上述代码的睡眠时间是20秒后启动程序,接着马上在Idea关机程序,触发优雅关机。日志正常打印,并相差20秒,并且查看程序日志也是发现等待睡眠完成后才关机

在这里插入图片描述

3、第二种情况:设置上述代码的睡眠时间是50秒后启动程序,接着马上在Idea关机程序,触发优雅关机。日志正常打印,并相差50秒,并且查看程序日志也是发现等待睡眠完成后才关机,所以如果你的逻辑超过了设置的优雅关机的时间,它是会等你完成,再关机。

在这里插入图片描述

3、GracefulShutdownListener 的逻辑是,要关机了,发出信号给你,你可以先处理,比如, 你在 GracefulShutdownListener 代码里面业务处理了50秒,那它就等你50秒,直到你自己的业务处理完成即50秒之后,然后再开始spring.lifecycletimeout-per-shutdown-phase30秒的计时,去执行自己的关机业务逻辑,为什么那么说呢,因为,你可以在GracefulShutdownListener 种处理的50秒内,请求接口,是可以接受返回的,如下

在这里插入图片描述

三、docker 镜像中优雅关机的配置

默认情况下,docker stop 命令会发送一个 SIGTERM 信号给容器中的主进程(PID 1),并等待一段时间(默认为 10 秒)以允许该进程正常关闭。如果这段时间内进程没有退出,Docker 会发送一个 SIGKILL 信号强制终止进程

3.1、错误示例

1、❌ 先给出一个原始的简单的"sh", "-c",这种方式,是收不到关机信号的,这是因为docker stop命令默认发送的是SIGTERM信号给容器的PID 1进程(在这个例子中是sh脚本),而不是直接给Java进程。由于sh脚本并不具备转发信号的能力,因此Java应用无法接收到终止信号,从而不能触发优雅关闭流程

FROM openjdk:17-jdk-alpine
VOLUME /tmp
ADD multi-instance-test-0.0.1-SNAPSHOT.jar run.jar
RUN sh -c 'touch /run.jar'
ENV JAVA_OPTS="-Xms512m -Xmx512m -server"
ENV PROFILE="test"
ENV APP_NAME="run.jar"

ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS  -Djava.awt.headless=true -Duser.timezone=GMT+08 -Djava.security.egd=file:/dev/./urandom -jar /$APP_NAME --spring.profiles.active=test" ]

3.2、ENTRYPOINT 示例

2、修改上述的 ENTRYPOINT ,使用exec命令来替换当前进程(sh)为Java进程,这样Java进程就会成为PID 1,如下就可以收到信号了。

FROM openjdk:17-jdk-alpine
VOLUME /tmp
ADD multi-instance-test-0.0.1-SNAPSHOT.jar run.jar
RUN sh -c 'touch /run.jar'
ENV JAVA_OPTS="-Xms512m -Xmx512m -server"
ENV PROFILE="test"
ENV APP_NAME="run.jar"

# 使用exec命令来替换当前进程(sh)为Java进程,这样Java进程就会成为PID 1
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -Djava.awt.headless=true -Duser.timezone=GMT+08 -Djava.security.egd=file:/dev/./urandom -jar /$APP_NAME --spring.profiles.active=$PROFILE"]

3.3、使用tini 作为 init 系统(推荐)

推荐理由:

  1. 信号转发与僵尸进程管理tini 不仅能确保信号(如 SIGTERM)被正确转发给子进程,还能处理僵尸进程,这使得它非常适合用于容器化应用。
  2. 简化配置:相比于直接操作 ENTRYPOINT 或编写复杂的启动脚本,使用 tini 更加简洁和易于维护。
  3. 兼容性和稳定性tini 是一个广泛使用的轻量级 init 系统,在许多 Docker 容器中都有成功应用的案例。

首先,在 Dockerfile 中添加安装 tini 的步骤,并调整 ENTRYPOINT 和 CMD 以使用 tini 启动 Java 应用。

FROM openjdk:17-jdk-alpine
VOLUME /tmp

# 安装 tini
RUN apk add --no-cache tini

ADD multi-instance-test-0.0.1-SNAPSHOT.jar run.jar
RUN sh -c 'touch /run.jar'
ENV JAVA_OPTS="-Xms512m -Xmx512m -server"
ENV PROFILE="test"
ENV APP_NAME="run.jar"

# 使用 tini 启动 Java 应用
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["sh", "-c", "java $JAVA_OPTS -Djava.awt.headless=true -Duser.timezone=GMT+08 -Djava.security.egd=file:/dev/./urandom -jar /$APP_NAME --spring.profiles.active=$PROFILE"]

3.4、其他注意事项

  • Spring Boot 版本:确保你使用的 Spring Boot 版本支持优雅停机功能(2.3 及以上版本默认支持)。如果你使用的是较早版本,请参考之前的建议来启用或自定义优雅停机逻辑。
  • 超时设置:默认情况下,docker stop 命令会等待 10 秒让应用程序关闭。如果需要更长的时间,可以通过 -t 参数指定等待时间。例如,docker stop -t 30 my-running-app 将等待 30 秒。

其中超时设置,我再详细说一下,如果你在Spring Boot应用中设置了spring.lifecycle.timeout-per-shutdown-phase: 30s,那么为了确保Docker有足够的时间让Spring Boot完成其优雅关闭过程,在使用docker stop命令时,你应该设置一个至少为30秒的超时时间(通过-t参数)。

3.4.1 、具体原因

  1. Spring Boot优雅关闭机制:当Spring Boot接收到SIGTERM信号后,它会开始执行优雅关闭流程。这个过程包括但不限于停止监听HTTP请求、等待当前正在处理的请求完成、释放资源等。你配置的timeout-per-shutdown-phase: 30s意味着Spring Boot希望在这30秒内完成所有必要的关闭操作。

  2. Docker的SIGTERM和SIGKILL行为

    • 当你执行docker stop <container_id>时,Docker会向容器内的主进程发送SIGTERM信号。
    • 默认情况下,Docker会等待10秒(可以通过-t参数自定义)来允许容器内的进程自行终止。
    • 如果在这个时间内进程没有退出,Docker将发送SIGKILL信号强制终止该进程。

因此,如果Docker的超时时间(默认或通过-t指定)小于Spring Boot所需的关闭时间(例如小于30秒),那么Spring Boot可能无法在Docker强制终止之前完成所有的关闭操作,导致部分关闭逻辑未被执行。

3.4.2、实践建议

为了确保Spring Boot应用能够顺利完成其优雅关闭流程,可以采取以下措施之一:

3.4.2.1、方法1:增加Docker的停止超时时间

当你使用docker stop命令时,指定一个至少为30秒的超时时间:

docker stop -t 30 <container_id>

这将给Spring Boot足够的时间来完成其关闭流程。

3.4.2.2、方法2:在启动容器时设置停止超时时间

你也可以在启动容器时通过--stop-timeout选项来设置超时时间:

docker run --stop-timeout 30 ...
3.4.2.3、方法3:调整Spring Boot的关闭超时时间

如果你发现30秒对于你的应用来说过长,可以考虑缩短Spring Boot的关闭超时时间。但是请注意,这需要确保在较短的时间内能够完成所有必要的关闭操作,否则可能会导致资源泄漏或其他问题。

3.4.2.4 示例

假设你已经配置了Spring Boot的关闭超时时间为30秒:

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

那么在停止容器时,你应该使用如下命令:

docker stop -t 30 my-spring-boot-container

这样,Docker会给Spring Boot足够的时间(30秒)来完成其优雅关闭流程,避免因超时导致的强制终止。

只有日志显示了完整的优雅关闭的日志才是真的没问题

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2312988.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在【k8s】中部署Jenkins的实践指南

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Kubernetes航线图&#xff1a;从船长到K8s掌舵者》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、Jenkins简介 2、k8s简介 3、什么在…

Unity DOTS从入门到精通之 C# Job System

文章目录 前言安装 DOTS 包C# 任务系统Mono 环境DOTS 环境运行作业NativeContainer 前言 作为 DOTS 教程&#xff0c;我们将创建一个旋转立方体的简单程序&#xff0c;并将传统的 Unity 设计转换为 DOTS 设计。 Unity 2022.3.52f1Entities 1.3.10 安装 DOTS 包 要安装 DOTS…

【Godot4.4】浅尝Godot中的MVC

概述 基于一个Unity的视频。学习了一下基本的MVC概念&#xff0c;并尝试在Godot中实现了一下。 原始的MVC&#xff1a; Godot中的MVC&#xff1a; Model、View和Controller各自应该实现的功能如下&#xff1a; Model: 属性(数据字段)数据存取方法数据更新信号 View: 控…

Elasticsearch为索引设置自动时间戳,ES自动时间戳

文章目录 0、思路1、配置 ingest pipeline2、在索引映射中启用_source字段的时间戳3、使用 index template 全局设置时间戳4、写入测试数据5、验证结果6、总结 在使用 Elasticsearch 进行数据存储和检索时&#xff0c;时间戳字段是一个非常重要的组成部分。它可以帮助我们追踪数…

计算机网络:计算机网络的组成和功能

计算机网络的组成&#xff1a; 计算机网络的工作方式&#xff1a; 计算机网络的逻辑功能; 总结&#xff1a; 计算机网络的功能&#xff1a; 1.数据通信 2.资源共享 3.分布式处理:计算机网络的分布式处理是指将计算任务分散到网络中的多个节点&#xff08;计算机或设备&…

FPGA设计时序约束用法大全保姆级说明

目录 一、序言 二、时序约束概览 2.1 约束五大类 2.2 约束功能简述 2.3 跨时钟域约束 三、时序约束规范 3.1 时序约束顺序 3.2 约束的优先级 四、约束示例 4.1 设计代码 4.2 时序结果 4.2.1 create_clock 4.2.2 create_generated_clock 4.2.3 Rename_Auto-Derive…

云服务运维智能时代:阿里云操作系统控制台

阿里云操作系统控制台 引言需求介绍操作系统使用实例获得的帮助与提升建议 引言 阿里云操作系统控制台是一款创新型云服务器运维工具&#xff0c;专为简化用户的运维工作而设计。它采用智能化和可视化的方式&#xff0c;让运维变得更加高效、直观。借助AI技术&#xff0c;控制…

硬件学习笔记--48 磁保持继电器相关基础知识介绍

目录 1.磁保持继电器工作原理 2.磁保持继电器内部结构及组成部分 3.磁保持继电器主要参数 4.总结 1.磁保持继电器工作原理 磁保持继电器利用永磁体的磁场和线圈通电产生的磁场相互作用&#xff0c;实现触点的切换。其特点在于线圈断电后&#xff0c;触点状态仍能保持&#…

简记_硬件系统设计之需求分析要点

目录 一、 功能需求 二、 整体性能需求 三、 用户接口需求 四、 功耗需求 五、 成本需求 六、 IP和NEMA防护等级需求 七、 认证需求 功能需求 供电方式及防护 供电方式&#xff1a;市电供电、外置直流稳压电源供电、电池供电、PoE&#xff08;Power Over Ether…

ubuntu 20.04下ZEDmini安装使用

提前安装好显卡驱动和cuda&#xff0c;如果没有安装可以参考我的这两篇文章进行安装&#xff1a; ubuntu20.04配置YOLOV5&#xff08;非虚拟机&#xff09;_ubuntu20.04安装yolov5-CSDN博客 ubuntu20.04安装显卡驱动及问题总结_乌班图里怎么备份显卡驱动-CSDN博客 还需要提前…

tauri-plugin-shell插件将_blank的a标签用浏览器打开了,,,解决办法

不要使用这个插件&#xff0c;这个插件默认会将网页中a标签为_blank的使用默认浏览器打开&#xff0c;但是这种做法在我的程序里不是很友好&#xff0c;我需要自定义这种行为&#xff0c;当我点击我自己的链接的时候&#xff0c;使用默认浏览器打开&#xff0c;当点击别的链接的…

C++ 继承(2)

Hello&#xff01;&#xff01;大家早上中午晚上好&#xff01;&#xff01;今天收尾继承剩余部分内容&#xff01;&#xff01; 一、友元不能继承 基类的友元函数不能被子类继承&#xff0c;也就是基类的友元函数访问不了子类的私有或保护成员&#xff01; 1.1解决方法在子…

解决:Word 保存文档失败,重启电脑后,Word 在试图打开文件时遇到错误

杀千刀的微软&#xff0c;设计的 Word 是个几把&#xff0c;用 LaTex 写完公式&#xff0c;然后保存&#xff0c;卡的飞起 我看文档卡了很久&#xff0c;就关闭文档&#xff0c;然后 TMD 脑抽了重启电脑 重启之后&#xff0c;文档打不开了&#xff0c;显示 杀千刀的&#xff…

基于Asp.net的零食购物商城网站

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

ESP8266UDP透传

1. 配置 WiFi 模式 ATCWMODE3 // softAPstation mode 响应 : OK 2. PC 连⼊入 ESP8266 softAP 就是连接wifi 3.查询ESP8266设备的IP地址 ATCIFSR 响应: CIFSR: APIP, "192.168.4.1" CIFSR: APMAC, "1a: fe: 34: a5:8d: c6" CIFSR: STAIP, "192.…

UE5从入门到精通之如何创建自定义插件

前言 Unreal 的Plugins插件系统中有很多的插件供大家使用,包括官方的和第三方的,这些插件不仅能帮我我们实现特定功能,还能够提升我们的工作效率。 所以我们今天就来自己创建一个自定义插件,如果我们想实现什么特定的功能,我们也可以发布到商店供大家使用了。 创建插件 …

基于python大数据的招聘数据可视化与推荐系统

博主介绍&#xff1a;资深开发工程师&#xff0c;从事互联网行业多年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了多年的设计程序开发&#xff0c;开发过上千套设计程序&#xff0c;没有什么华丽的语言&#xff0c;只有…

olmOCR:高效精准的 PDF 文本提取工具

在日常的工作和学习中&#xff0c;是否经常被 PDF 文本提取问题困扰&#xff1f;例如&#xff1a; 想从学术论文 PDF 中提取关键信息&#xff0c;却发现传统 OCR 工具识别不准确或文本格式混乱&#xff1f;需要快速提取商务合同 PDF 中的条款内容&#xff0c;却因工具不给力而…

Spring Boot使用JDBC /JPA访问达梦数据库

Spring Boot 是一个广泛使用的 Java 框架&#xff0c;用于快速构建基于 Spring 的应用程序。对于达梦数据库&#xff08;DMDB&#xff09;的支持&#xff0c;Spring Boot 本身并没有直接内置对达梦数据库的集成&#xff0c;但你可以通过一些配置和依赖来支持达梦数据库。 以下…

【五.LangChain技术与应用】【31.LangChain ReAct Agent:反应式智能代理的实现】

一、ReAct Agent是啥?为什么说它比「普通AI」聪明? 想象一下,你让ChatGPT查快递物流,它可能直接编个假单号糊弄你。但换成ReAct Agent,它会先推理(Reasoning)需要调用哪个接口,再行动(Action)查询真实数据——这就是ReAct的核心:让AI学会「动脑子」再动手。 举个真…