服务优雅停机

article/2025/8/29 5:20:55

优雅停机

什么是优雅停机

​ 优雅停机指的是Java项目在停机时需要做好断后工作。如果直接使用kill -9 方式暴力的将项目停掉,可能会导致正常处理的请求、定时任务、RMI、注销注册中心等出现数据不一致问题。

​ 如何解决优雅停机呢?大致需要解决如下问题:

  1. 首先要确保不会再有新的请求进来,所以需要设置一个流量挡板
  2. 保证正常处理已进来的请求线程,可以通过计数方式记录项目中的请求数量
  3. 如果涉及到注册中心,则需要在第一步结束后注销注册中心
  4. 停止项目中的定时任务
  5. 停止线程池
  6. 关闭其他需要关闭资源等等等

​ SpringBoot优雅停机出现之前,一般需要通过自研方式来保证优雅停机。我也见过有项目组使用 kill -9 或者执行 shutdown脚本直接停止运行的项目,当然这种方式不够优雅。SpringBoot在最新的2.X.X版本中新增了优雅停机功能,该功能解决了之前 kill -9的暴力停机问题。我们一起来剖析一下SpringBoot提供的优雅停机

SpringBoot优雅停机使用方式

以SpringBoot2.3.4-RELEASE为例

创建好项目后引入 :;

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

SpringBoot优雅停机有两种使用方式:

方式一: spring-boot-starter-actuator 模块提供了一个 restful 接口 /actuator/shutdown (POST) 用于优雅停机。一般需要限制内网关IP访问权限,而且最好使用Secrety进行登录验证。

#### 使用endpoints方式需要在配置文件中添加如下配置 server.shutdown=graceful ## 开启优雅停机
spring.lifecycle.timeout-per-shutdown-phase=20s ##设置优雅停机关闭流量挡板后最多等待时间management.server.port=9090  ## 指定endpoints的访问端口,最好不与server.port一致
management.endpoint.shutdown.enabled=true ## 开启/actuator/shutdown路由
management.endpoints.web.exposure.include=shutdown  ## 暴露/actuator/shutdown路由

发出一个需要30秒才能完成的请求,然后另一个线程执行 ip:port/actuator/shutdown,可以发现项目会等待20秒之后关闭容器。如果没有正在处理的请求则会立即停机。如果请求处理时间超过配置的20秒则会丢弃处理,进行关机。

方式二: 使用 kill -15 pid 发送停机通知进行优雅停机

	kill -9 pid 可以理解为操作系统从内核级别强行杀死某个进程,直接模拟了一次系统宕机,系统断电,这对于应用来说太不友好.kill -15 pid 则可以理解为发送一个通知,告知应用主动关闭。

SpringBoot优雅停机源码分析

在这里插入图片描述

上图中出现了两个重要的Bean:WebServerGracefulShutdownLifecycle、WebServerStartStopLifecycle

两个Bean都实现了SmartLifecycle接口,该接口在SpringBoot3.0出现。用于定义与关闭有关的生命周期方法。

WebServerStartStopLifecycle:@Overridepublic void start() {this.webServer.start();this.running = true;this.applicationContext.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));}@Overridepublic void stop() {this.webServer.stop();}WebServerGracefulShutdownLifecycle : @Overridepublic void start() {this.running = true;}@Overridepublic void stop(Runnable callback) {this.running = false;this.webServer.shutDownGracefully((result) -> callback.run());}

优雅停机最关键的类是GracefulShutdown。WebServerGracefulShutdownLifecycle的stop方法最终会委托给GracefulShutdown。

final class GracefulShutdown {private static final Log logger = LogFactory.getLog(GracefulShutdown.class);private final Tomcat tomcat;private volatile boolean aborted = false;GracefulShutdown(Tomcat tomcat) {this.tomcat = tomcat;}//优雅停机核心方法void shutDownGracefully(GracefulShutdownCallback callback) {logger.info("Commencing graceful shutdown. Waiting for active requests to complete");new Thread(() -> doShutdown(callback), "tomcat-shutdown").start();}private void doShutdown(GracefulShutdownCallback callback) {List<Connector> connectors = getConnectors();connectors.forEach(this::close);try {for (Container host : this.tomcat.getEngine().findChildren()) {for (Container context : host.findChildren()) {while (isActive(context)) {if (this.aborted) {logger.info("Graceful shutdown aborted with one or more requests still active");callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);return;}Thread.sleep(50);}}}}catch (InterruptedException ex) {Thread.currentThread().interrupt();}logger.info("Graceful shutdown complete");callback.shutdownComplete(GracefulShutdownResult.IDLE);}private List<Connector> getConnectors() {List<Connector> connectors = new ArrayList<>();for (Service service : this.tomcat.getServer().findServices()) {Collections.addAll(connectors, service.findConnectors());}return connectors;}private void close(Connector connector) {connector.pause();connector.getProtocolHandler().closeServerSocketGraceful();}private boolean isActive(Container context) {try {//判断关闭挡板后剩余请求数if (((StandardContext) context).getInProgressAsyncCount() > 0) {return true;}for (Container wrapper : context.findChildren()) {if (((StandardWrapper) wrapper).getCountAllocated() > 0) {return true;}}return false;}catch (Exception ex) {throw new RuntimeException(ex);}}void abort() {this.aborted = true;}}

以客户端发出 /actuator/shutdown请求后,SpringBoot接受到请求会进入ShutdownEndpoint的shutdown方法

该方法最终调用了IOC容器的AbstractApplicationContext.close方法,该方法又会委托到它的子类ServletWebServerApplicationContext中的doClose方法

@Override
protected void doClose() {//判断IOC容器是否是运行状态if (isActive()) {//发布一个AvailabilityChangeEvent事件,用于通知Tomcat关闭请求挡板//tomcat中有一个定时任务会维护一个状态,该状态决定了是否接受请求,Tomcat收到时间后会关闭挡板AvailabilityChangeEvent.publish(this, ReadinessState.REFUSING_TRAFFIC);}//调用父类AbstractApplicationContext的doClose方法关闭IOCsuper.doClose();
}
protected void doClose() {//启动IOC关闭状态if (this.active.get() && this.closed.compareAndSet(false, true)) {if (logger.isDebugEnabled()) {logger.debug("Closing " + this);}//注销JMXLiveBeansView.unregisterApplicationContext(this);try {//发布shutdown事件publishEvent(new ContextClosedEvent(this));}catch (Throwable ex) {logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);}//调用WebServerGracefulShutdownLifecycle和WebServerStartStopLifecycle两个Bean生命周期stop方法进行优雅停机if (this.lifecycleProcessor != null) {try {this.lifecycleProcessor.onClose();}catch (Throwable ex) {logger.warn("Exception thrown from LifecycleProcessor on context close", ex);}}//调用Bean的destroy生命销毁方法destroyBeans();// 关闭Bean工厂closeBeanFactory();// 关闭IOConClose();// Reset local application listeners to pre-refresh state.if (this.earlyApplicationListeners != null) {this.applicationListeners.clear();this.applicationListeners.addAll(this.earlyApplicationListeners);}//关闭IOC状态this.active.set(false);}}

自研优雅停机

在这里插入图片描述

​ 目前团队内部Devops流程: 上传代码分支----> gitalb合并master分支 ------> jenkins打包版本 -------> 自研管理台拉取nexus中打包的最新版本 --------> 自研管理台选择要升级的版本

​ 团队自研了一套管理台部署系统,本质上是调用shell脚本和提供界面操作。服务要使用自研平台的功能,需要使用封装好的通用的jar包: app-health.jar. 该jar主要包含(省略代码) :

 //HealthStatus : 维护一个状态,该状态主要控制流量挡板//started 来源于app启动后的状态,当after_start后,该值设置为true。//closing 来源于servlet的请求,当触发closing时,需要确保started状态不能被设置,并且将started状态设置为false。
 //HealthListener主要用于监听tomcat信号,用于开启流量挡板
 // HealthHttpFilter会拦截所有请求,用于记录当前接受的请求数量、当流量挡板关闭后还可以起到拒绝请求目的
//HealthServlet用于接受自研系统发出的shutdown请求,该类只是关闭了挡板,并未做注销注册中心、停止线程池等操作。HealthServlet是jar默认提供的,不同的项目可以自行覆盖并定制服务的shutdown请求。通常shutdown请求会包括注销注册中心、等待剩余请求处理、休眠指定秒数、停止线程池、停止定时任务等

​ 团队内部的优雅停机本质上是借助了自研的部署平台。当在管理台上停止某服务时,管理台会向服务发出一个shutodown请求通知服务下线。该shutodown请求可以在管理台上进行配置。既然暴露了shutdown请求那是不是会遭到有心人乱调用呢? 肯定不会的,shutdown请求会限制指定ip等。服务接受到shutdown请求后首先会关闭流量挡板、然后注销注册中心、等待剩余请求处理、休眠指定秒数、停止线程池、停止定时任务等。shutdown结束之后会返回响应给管理台系统。管理台收到响应后会调用shell脚本关闭Tomcat容器从而实现优雅停机。

​ 服务的部署也是调用shell脚本启动tomcat容器,容器启动好后,通用jar中的HealthListener会监听到Tomcat的发出的Lifecycle中不同的sign信号,当HealthListener收到Lifecycle.AFTER_START_EVENT信号之后说明容器部署成功。然后会将流量挡板打开正常运行服务。

总结


SprungBoot2.3.版本提供的新特性皆在融合docker/k8s.比如actuator新增的两个地址:/actuator/health/liveness和/actuator/health/readiness,前者用作kubernetes的存活探针,后者用作kubernetes的就绪探针;以及maven-plugin-starter支持打包docker镜像、提供spring-boot-jarmode-layertools工具提供镜像分层功能。
本质上SpringBoot的优雅停机与团队自研的优雅停机没有太大区别。都是先关闭流量挡板再处理剩余请求。但是两者都需要通过定制关闭挡板后的操作。SpringBoot并没有提供关闭线程池、定时任务、注册中心下线等操作。所以还是需要封装一个通用的starter进行后置处理。


http://chatgpt.dhexx.cn/article/ArqSefg4.shtml

相关文章

停机问题的理解

关于停机问题维基百科给出的定义是&#xff1a; 停机问题&#xff08;halting problem&#xff09;是逻辑数学中可计算性理论的一个问题。通俗的说&#xff0c;停机问题就是判断任意一个程序是否会在有限的时间之内结束运行的问题。该问题等价于如下的判定问题&#xff1a;给…

静态变量的使用

静态变量&#xff1a;即类中的静态变量 类变量被其他方法使用 不管是被自己类的方法使用&#xff0c;还是被其他类的方法使用 都可以直接使用&#xff0c;不需要实例化对象即可使用。通过类名调用。 (遵守访问修饰符) 类变量只能通过类方法初始化。 普通变量被其他方法使用 …

类变量/静态变量

类变量 引入类变量 案例引出类变量&#xff1a; 有几个小孩在玩游戏&#xff0c;不时会有其他的小孩加入一起玩。怎么统计一共有多少小孩在玩&#xff1f; 按照现有的知识&#xff0c;可以设计一个小孩类&#xff0c;定义一个加入的方法。然后在主程序类的main方法中&#xf…

变量、常量、静态变量、静态常量

1、变量 在JAVA中我们通过三个元素来描述变量&#xff1a;变量类型&#xff0c;变量名以及变量值。 String love“imooc”; 变量类型 变量名 值&#xff08;其中String具有不可变性&#xff0c;重新赋值后会生成新的String对象&#xff0c;love变量名这实际是指向对象地址的引用…

类中静态变量

不能在类声明中 初始化静态变量&#xff0c;这是因为声明描述了如何分配内存&#xff0c;但并未实际分配内存。 对于静态类成员&#xff0c;无论这个类的对象有多少个&#xff0c;静态成员都只有一个 对于静态类成员&#xff0c;可以在类声明之外使用单独的语句来进行初始化。…

静态变量和实例变量的区别

静态变量和实例变量的区别 大家好&#xff0c;我是酷酷的韩~ 1.在语法定义上的区别: 静态变量前要加static关键字&#xff0c;而实例变量前则不加。 2.在程序运行时的区别&#xff1a; (1)实例变量属于某个对象的属性&#xff0c;必须创建了实例对象&#xff0c;其中的实例…

JAVA静态变量是什么

java静态变量是什么-Java基础-PHP中文网 在java中&#xff0c;静态变量指的是被static修饰的类的变量&#xff1b;静态变量被所有类实例对象所共享&#xff0c;在内存中只有一个副本&#xff0c;当且仅当在类初次加载时会被初始化。 本教程操作环境&#xff1a;windows7系统、j…

静态变量与动态变量

0.静态存储与动态存储 1&#xff09;静态存储变量通常是在变量定义时就分定存储单元并一直保持不变&#xff0c;直至整个程序结束。静态变量&#xff0c;全局动态变量都是静态存储 2&#xff09;动态存储变量是在程序执行过程中&#xff0c;使用它时才分配存储单元&#xff0…

C++之static,静态变量

目录 1.为什么要用静态变量 2.全局变量 3.静态局部变量 4.静态数据成员的空间开辟 5.静态数据成员 6.释放 7.总结 1.内存&#xff1a; 2.初始化&#xff1a; 3.最大的优点&#xff1a; 4.指针&#xff1a; 5.释放时机&#xff1a; 1.为什么要用静态变量 前面我们定义…

C++静态变量

静态变量&#xff08;Static Variables&#xff09;是在程序运行期间保持其存在和值的变量&#xff0c;不会随着函数的调用而销毁和重新创建。静态变量在内存中分配一次&#xff0c;并且在整个程序的生命周期中保持存在。 在 C 中&#xff0c;静态变量可以声明在函数内部、类内…

win10显示rpc服务器不可用,win10系统RpC服务器不可用的详细办法

win10系统使用久了&#xff0c;好多网友反馈说win10系统RpC服务器不可用的问题&#xff0c;非常不方便。有什么办法可以永久解决win10系统RpC服务器不可用的问题&#xff0c;面对win10系统RpC服务器不可用的图文步骤非常简单&#xff0c;只需要1、使用netsh interface ip add 添…

w7系统显示rpc服务器不可用,教你win7系统rpc服务器不可用怎么办

用户在使用电脑进行时间同步&#xff0c;安装打印机或者其它的操作的时候可能会遇到同样一个问题&#xff0c;那就是提示“RPC服务器不可用”&#xff0c;很多朋友可能对于RPC并不了解&#xff0c;更不知道如何解决&#xff0c;下面&#xff0c;小编就来跟大家讲解rpc服务器不可…

计算机无法登陆提示rpc服务器不可用,电脑rpc服务器不可用,教你电脑rpc服务器不可用怎么解决...

有网友表示进入磁盘管理对磁盘进行分区、更改盘符或压缩卷等操作的时候出现“RPC服务器不可用”的报错&#xff0c;rpc服务器不可用怎么办?很多朋友可能对于RPC并不了解&#xff0c;下面小编教你电脑rpc服务器不可用怎么解决吧。 rpc服务器不可用怎么办 打开“运行”窗口&…

rpc服务器不可用自动重启,rpc服务器不可用_详细解决方法,彻底修复

通过测试证明,“rpc服务器不可用”可能是由于中了冲击波和震荡波导致。 虽然这个是很老的病毒,但还是有小部分用户没有对系统没有进行升级导致出现“rpc服务不可用”情况。 电脑遭到冲击波可能会出现以下症状: 1、系统资源紧张,应用程序运行速度异常。 2、Word、Excel、Pow…

rpc服务器不可用桌面图标消失,rpc服务器不可用,教您rpc服务器不可用怎么办

有网友表示进入磁盘管理对磁盘进行分区、更改盘符或压缩卷等操作的时候出现“RPC服务器不可用”的报错&#xff0c;通常我们在安装打印机或者虚拟磁盘时&#xff0c;将出现此提示。下面&#xff0c;小编给大家介绍rpc服务器不可用的处理技巧。 用户在使用电脑进行时间同步&…

计算机无法登陆提示rpc服务器不可用,电脑提示RPC服务器不可用的解决方法

最近有Win7用户反映在使用打印机或使用电脑进行时间同步的时候&#xff0c;突然弹出“RPC服务器不可用”的提示&#xff0c;很多用户可能对于RPC并不了解&#xff0c;更不知道如何解决&#xff0c;现在小编就和大家分享Win7系统RPC服务器不可用的解决方法。 RPC服务器&#xff…

为什么我的电脑显示rpc服务器不可用,电脑提示RPC服务器不可用解决办法

电脑提示"RPC服务器不可用"解决办法 腾讯视频/爱奇艺/优酷/外卖 充值4折起 在使用电脑的过程中&#xff0c;有些小伙伴遇到了电脑提示“RPC服务器不可用”的情况。那么&#xff0c; 电脑提示“RPC服务器不可用”怎么办呢?下面&#xff0c;就和小编一起来看看吧。 原…

xp显示rpc服务器不可用,WinXP系统rpc服务器不可用怎么解决?

最近有WinXP系统用户反映&#xff0c;使用数据线直接将手机照片向电脑复制的时候&#xff0c;出现提示“rpc服务器不可用”&#xff0c;这让用户非常苦恼。那么&#xff0c;WinXP系统rpc服务器不可用怎么解决呢&#xff1f;下面&#xff0c;我们就一起往下看看WinXP系统rpc服务…

虚拟盘rpc服务器不可用,rpc服务器不可用,手把手教你rpc服务器不可用怎么办

有网友表示进入磁盘管理对磁盘进行分区、更改盘符或压缩卷等操作的时候出现“RPC服务器不可用”的报错&#xff0c;通常我们在安装打印机或者虚拟磁盘时&#xff0c;将出现此提示。下面&#xff0c;小编给大家介绍rpc服务器不可用的处理技巧。 网上介绍关于&#xff02;RPC服务…

rpc服务器不可用处于启用状态,电脑提示RPC服务器不可用怎么办?

电脑提示"RPC服务器不可用"怎么办? 腾讯视频/爱奇艺/优酷/外卖 充值4折起 原因分析: 根据报错信息观察RPC服务是已启动的状态,说明此报错还和其他的服务有关。 从而定位到Virtual Disk服务,该服务的作用:提供用于磁盘、卷、文件系统和存储阵列的管理服务。 解决方…