环境:SpringBoot3.4.2
1. 为什么要优雅停机
- 数据完整性:在停机过程中,如果暴力终止进程,可能会导致事务执行中断或业务处理不完整,从而在数据库或系统中留下脏数据或残留文件。优雅停机能够确保在停机前完成所有正在处理的请求,并保存相关数据,保证数据的完整性和一致性。
- 用户体验:优雅停机能够在终止服务前,不再接受新的请求,但会完成已接收的请求,从而避免用户请求被中断或超时,提升用户体验。
- 资源释放:优雅停机能够确保在停机过程中,系统资源得到正确的释放,避免资源泄露或浪费。
2. 准备测试环境
准测试的环境,模拟耗时操作及资源销毁动作。
2.1 环境准备
@Component
public class PersonService {
@PreDestroy
public void destroy() {
try {
TimeUnit.SECONDS.sleep(5) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("资源释放完成...") ;
}
}
该Bean定义了销毁方法,容器在关闭时会执行。
@GetMapping("/index")
public Object index() {
try {
System.out.println("执行任务...") ;
TimeUnit.SECONDS.sleep(30) ;
System.out.println("任务执行完成...") ;
} catch (InterruptedException e) {
e.printStackTrace();
}
return "demo index" ;
}
该接口模拟耗时任务。主要用来观察发出停机信号后该接口是否会执行完成后再停机。
2.2 暴露停机
我们先来观察下暴露停机会是什么情况。
启动服务
请求/index接口
[root@pack ~]# curl http://localhost:8080/demos/index
强制终止进程
通过kill -9 强制终止进程
[root@pack ~]# jps
31713 Jps
26037 jar
[root@pack ~]# kill -9 26037
[root@pack ~]#
此时查看服务输出情况
请求的/index接口并没有执行完成,并且PersonService的销毁方法并没有执行。
3. 优雅停机方式
3.1 方式1
通过linux命令kill,但是这里我们不要添加-9 这样的参数。而是不是使用参数或者使用-15参数,示例如下:
[root@pack ~]# kill xxx 或者 kill -15 xxx
这里测试还是按照上面的方式进行,我们只需要观察服务控制台输出结果即可。
执行任务...
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
资源释放完成...
当通过上面的方式终止进程后,PersonService的销毁方法执行完成以后程序才终止。确保资源的释放。为什么SpringBoot程序能正确的关闭呢?这是因为SpringBoot程序默认为JVM注册了一个关闭钩子,也就是添加了下面这样的代码
Runtime.getRuntime().addShutdownHook(...)
有兴趣可以查看下SpringApplication源码。
当我们配置了如下参数后,SpringBoot程序就算你使用kill xxx或者kill -15 xxx也会立即终止。
spring:
main:
register-shutdown-hook: false
关闭注册钩子。
提示:你也可以直接通过Ctrl + C的方式来终止效果与kill - 15 是一样的。
3.2 方式2
通过如下配置,开启优雅关闭
server:
shutdown: graceful
---
spring:
main:
register-shutdown-hook: true
再次通过上面的方式进行测试,服务输出结果
当发出kill xxx信号后,我们请求的/index接口并没有直接终止,程序也没有立即终止而是等到所有的请求都结束以后才终止。这种优雅停机方式要比上一种优雅的太多了。
该种停机方式首先停止接收新的请求,然后等待所有正在执行的请求完成,最后关闭应用。
3.3 方式3
通过Actuator,Actuator提供了/shutdown接口,我们可以调用该接口进行程序的关闭。
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
默认情况下并没有开启shutdown接口,需要进行如下的配置
management:
endpoints:
web:
base-path: /ac
exposure:
include: '*'
endpoint:
shutdown:
enabled: true
接下来测试方式与上面相同,只不过停止服务需要通过如下接口
http://localhost:8080/ac/shutdown
当执行上面接口后
服务输出结果
服务与上面一样优雅的停机等待当前正处理的请求结束后再停机。