Spring Boot优雅关闭服务

在之前,我们要重新部署Spring Boot应用时,都是用简单粗暴的kill -9 PID命令,这种方式的弊端很明显,假如应用内还有未处理完的业务,突然把应用给kill掉,这显然是出问题的。

现在好了,Spring Boot提供了优雅关闭服务的功能,它支持目前Jetty, Reactor Netty, Tomcat, Undertow这四种容器,使用该功能很简单,只需要在配置文件中配置server.shutdown(关闭方式)spring.lifecycle.timeout-per-shutdown-phase(超时时间)这两个参数即可,server.shutdown有两个可选值:immediate(立即停止,默认值)graceful(优雅停止)spring.lifecycle.timeout-per-shutdown-phase的默认值为30s,我们可以根据自己的业务耗时情况来设置时间。我们先到yml配置好参数:

1
2
3
4
5
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 20s

然后再写个简单的http接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@SpringBootApplication
public class GracefulApplication {

public static void main(String[] args) {
SpringApplication.run(GracefulApplication.class, args);
}

@RequestMapping("health")
public String health() {
return "health";
}

}

这里我用的是最新的Spring Boot 2.6.1,其默认的tomcat版本为Apache Tomcat/9.0.55,此时,我们打包到Linux环境下,启动后执行kill -2 PID可以看到以下日志

1
2
3
2021-12-03 11:37:25.282  INFO 638081 --- [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown        : Commencing graceful shutdown. Waiting for active requests to complete
2021-12-03 11:37:25.290 INFO 638081 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete

因为我们服务没有需要处理的请求,所以关闭得很快,我们在接口中等待10s

1
2
3
4
5
6
7
8
public String health() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "health";
}

我们再次执行kill -2 PID会看到有一定的时间间隔

1
2
3
2021-12-03 11:44:44.239  INFO 647083 --- [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown        : Commencing graceful shutdown. Waiting for active requests to complete
2021-12-03 11:44:51.911 INFO 647083 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete

在官网有提到,在kill -2之后,服务端不会再接受新请求,但是Undertow比较例外,它会接受请求,但是会返回503错误 ,我们把容器改为Undertow来看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

然后启动起来

1
2
3
4
5
6
7
2021-12-03 11:58:46.767  INFO 673474 --- [           main] io.undertow                              : starting server: Undertow - 2.2.12.Final
2021-12-03 11:58:46.783 INFO 673474 --- [ main] org.xnio : XNIO version 3.8.4.Final
2021-12-03 11:58:46.795 INFO 673474 --- [ main] org.xnio.nio : XNIO NIO Implementation Version 3.8.4.Final
2021-12-03 11:58:46.979 INFO 673474 --- [ main] org.jboss.threads : JBoss Threads version 3.1.0.Final
2021-12-03 11:58:47.089 INFO 673474 --- [ main] o.s.b.w.e.undertow.UndertowWebServer : Undertow started on port(s) 8888 (http)
2021-12-03 11:58:47.108 INFO 673474 --- [ main] c.example.graceful.GracefulApplication : Started GracefulApplication in 3.076 seconds (JVM running for 3.652)

但是在我们测试过程中并没返回503的错误,而是直接拒绝了请求

经过一番测试后,我们发现,如果在接收到kill -2 的信号时,会先打印Commencing graceful shutdown. Waiting for active requests to complete,如果在spring.lifecycle.timeout-per-shutdown-phase设置的时间内处理完所有的请求,那么最后会成功打印Graceful shutdown complete,如果超过spring.lifecycle.timeout-per-shutdown-phase的时间则会作出以下提示

1
2
3
4
5
6
2021-12-03 13:07:15.215  INFO 796937 --- [ionShutdownHook] o.s.b.w.e.undertow.UndertowWebServer     : Commencing graceful shutdown. Waiting for active requests to complete
2021-12-03 13:07:20.216 INFO 796937 --- [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Failed to shut down 1 bean with phase value 2147483647 within timeout of 5000ms: [webServerGracefulShutdown]
2021-12-03 13:07:20.217 INFO 796937 --- [ionShutdownHook] o.s.b.w.e.undertow.UndertowWebServer : Graceful shutdown aborted with one or more requests still active
2021-12-03 13:07:20.217 INFO 796937 --- [ionShutdownHook] io.undertow : stopping server: Undertow - 2.2.12.Final


好了,接下我们再大胆尝试,在等待服务处理完毕的这段时间内,我们的还能重新启动应用吗?这里,先写一段简单的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
set -m

APP_NAME="graceful-0.0.1-SNAPSHOT.jar"

PID=$(ps -ef |grep $APP_NAME | grep -v $0 |grep -v grep |awk '{print $2}')
echo "PID=${PID}"
if [ "${PID}" != "" ]; then
echo "ok"
kill -15 ${PID}
echo ${PID}
fi
sleep 1
nohup java -jar $APP_NAME 2>&1 &
PID=$(echo $!)
echo "NEW PID=$!"

运行该脚本,先找到原有进程->发送中断请求->启动新程序

可以看到,再程序未退出前,端口依然占用,此时无法启动该端口的新应用。

Spring Boot优雅关闭服务

https://blogs.52fx.biz/posts/116197676.html

作者

eyiadmin

发布于

2021-12-03

更新于

2024-05-31

许可协议

评论