准备工作
@PreDestroy 会在系统关闭前执行
package cn.netkiller;import javax.annotation.PreDestroy;import org.springframework.context.annotation.Configuration;@Configurationpublic class ShutdownConfiguration {public ShutdownConfiguration() {// TODO Auto-generated constructor stub}@PreDestroypublic void preDestroy() {System.out.println("==============================");System.out.println("Destroying Spring");System.out.println("==============================");}}
kill 命令演示
kill 命令本质是给进程发送终止信号,进程接收到终止信号后退出运行。
可以看到 Springboot 启动后,进程 PID 44559,现在使用 kill 命令杀死这个进程
当执行 kill 44559 你会看到下面的输出
neo@MacBook-Pro-Neo ~/workspace/microservice/test % java -jar target/test-0.0.1-SNAPSHOT.jarStarting.... ____ _ __ _ _/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot :: (v2.5.3)2021-07-29 11:05:09.862 INFO 44559 --- [ main] cn.netkiller.Application : Starting Application v0.0.1-SNAPSHOT using Java 16.0.1 on MacBook-Pro-Neo.local with PID 44559 (/Users/neo/workspace/microservice/test/target/test-0.0.1-SNAPSHOT.jar started by neo in /Users/neo/workspace/microservice/test)2021-07-29 11:05:09.865 INFO 44559 --- [ main] cn.netkiller.Application : No active profile set, falling back to default profiles: default2021-07-29 11:05:11.363 WARN 44559 --- [ main] io.undertow.websockets.jsr : UT026010: Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used2021-07-29 11:05:11.399 INFO 44559 --- [ main] io.undertow.servlet : Initializing Spring embedded WebApplicationContext2021-07-29 11:05:11.400 INFO 44559 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1451 ms2021-07-29 11:05:12.041 INFO 44559 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint(s) beneath base path '/actuator'2021-07-29 11:05:12.073 INFO 44559 --- [ main] io.undertow : starting server: Undertow - 2.2.9.Final2021-07-29 11:05:12.085 INFO 44559 --- [ main] org.xnio : XNIO version 3.8.4.Final2021-07-29 11:05:12.099 INFO 44559 --- [ main] org.xnio.nio : XNIO NIO Implementation Version 3.8.4.Final2021-07-29 11:05:12.197 INFO 44559 --- [ main] org.jboss.threads : JBoss Threads version 3.1.0.Final2021-07-29 11:05:12.263 INFO 44559 --- [ main] o.s.b.w.e.undertow.UndertowWebServer : Undertow started on port(s) 8080 (http)2021-07-29 11:05:12.278 INFO 44559 --- [ main] cn.netkiller.Application : Started Application in 2.989 seconds (JVM running for 3.582)2021-07-29 11:05:20.577 INFO 44559 --- [ionShutdownHook] io.undertow : stopping server: Undertow - 2.2.9.Final==============================Destroying Spring==============================
而是用 kill -9 PID 就不会出现下面提示。
neo@MacBook-Pro-Neo ~/workspace/microservice/test % java -jar target/test-0.0.1-SNAPSHOT.jarStarting.... ____ _ __ _ _/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot :: (v2.5.3)2021-07-29 11:08:10.857 INFO 44613 --- [ main] cn.netkiller.Application : Starting Application v0.0.1-SNAPSHOT using Java 16.0.1 on MacBook-Pro-Neo.local with PID 44613 (/Users/neo/workspace/microservice/test/target/test-0.0.1-SNAPSHOT.jar started by neo in /Users/neo/workspace/microservice/test)2021-07-29 11:08:10.860 INFO 44613 --- [ main] cn.netkiller.Application : No active profile set, falling back to default profiles: default2021-07-29 11:08:12.377 WARN 44613 --- [ main] io.undertow.websockets.jsr : UT026010: Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used2021-07-29 11:08:12.411 INFO 44613 --- [ main] io.undertow.servlet : Initializing Spring embedded WebApplicationContext2021-07-29 11:08:12.411 INFO 44613 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1466 ms2021-07-29 11:08:13.046 INFO 44613 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint(s) beneath base path '/actuator'2021-07-29 11:08:13.081 INFO 44613 --- [ main] io.undertow : starting server: Undertow - 2.2.9.Final2021-07-29 11:08:13.100 INFO 44613 --- [ main] org.xnio : XNIO version 3.8.4.Final2021-07-29 11:08:13.114 INFO 44613 --- [ main] org.xnio.nio : XNIO NIO Implementation Version 3.8.4.Final2021-07-29 11:08:13.206 INFO 44613 --- [ main] org.jboss.threads : JBoss Threads version 3.1.0.Final2021-07-29 11:08:13.275 INFO 44613 --- [ main] o.s.b.w.e.undertow.UndertowWebServer : Undertow started on port(s) 8080 (http)2021-07-29 11:08:13.290 INFO 44613 --- [ main] cn.netkiller.Application : Started Application in 3.195 seconds (JVM running for 3.808)[1] 44613 killed java -jar target/test-0.0.1-SNAPSHOT.jar
这是因为 kill 命令会给进程发送终止信号,进程会正常退出.
什么是正常退出呢?例如:
完成为运行的逻辑
将为写入磁盘的文件后写入后退出
执行完SQL并关闭数据库
写入缓存,并关闭 redis
完成用户请求,并关闭链接
这就是为什么当我们正常关闭程序需要等待很长时间,如果我们此时没有运行状态显示,也没有通过日志反应执行状态,就会认为程序死了。其实此时程序可能尽职尽责的在工作,将未完成的工作完成,然后一步步正常退出。
尤其是多线程的程序,退出时需要等待每个线程完成请求,需要很长时间,我们常常因为升级时间紧迫而使用 kill -9 强行杀死进程,这会带来很多问题。
kill -9 的弊端:
程序执行一半被强行退出,用户端会出现 Timeout 超时
文件写入一半被终止,如果是文本文件只有一半内容;如果是二进制文件会造成损坏
数据库操作一组SQL,只执行了一半,会产生脏数据;如果使用事务处理会引起回滚;
Ctrl + C 与 kill 没有区别,也是给进程发送终止信号,现在我们来演示一下。
neo@MacBook-Pro-Neo ~/workspace/microservice/test % java -jar target/test-0.0.1-SNAPSHOT.jarStarting.... ____ _ __ _ _/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot :: (v2.5.3)2021-07-29 11:04:42.657 INFO 44546 --- [ main] cn.netkiller.Application : Starting Application v0.0.1-SNAPSHOT using Java 16.0.1 on MacBook-Pro-Neo.local with PID 44546 (/Users/neo/workspace/microservice/test/target/test-0.0.1-SNAPSHOT.jar started by neo in /Users/neo/workspace/microservice/test)2021-07-29 11:04:42.660 INFO 44546 --- [ main] cn.netkiller.Application : No active profile set, falling back to default profiles: default2021-07-29 11:04:44.212 WARN 44546 --- [ main] io.undertow.websockets.jsr : UT026010: Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used2021-07-29 11:04:44.246 INFO 44546 --- [ main] io.undertow.servlet : Initializing Spring embedded WebApplicationContext2021-07-29 11:04:44.246 INFO 44546 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1502 ms2021-07-29 11:04:44.857 INFO 44546 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint(s) beneath base path '/actuator'2021-07-29 11:04:44.889 INFO 44546 --- [ main] io.undertow : starting server: Undertow - 2.2.9.Final2021-07-29 11:04:44.902 INFO 44546 --- [ main] org.xnio : XNIO version 3.8.4.Final2021-07-29 11:04:44.916 INFO 44546 --- [ main] org.xnio.nio : XNIO NIO Implementation Version 3.8.4.Final2021-07-29 11:04:45.002 INFO 44546 --- [ main] org.jboss.threads : JBoss Threads version 3.1.0.Final2021-07-29 11:04:45.068 INFO 44546 --- [ main] o.s.b.w.e.undertow.UndertowWebServer : Undertow started on port(s) 8080 (http)2021-07-29 11:04:45.084 INFO 44546 --- [ main] cn.netkiller.Application : Started Application in 3.149 seconds (JVM running for 3.748)^C2021-07-29 11:04:47.082 INFO 44546 --- [ionShutdownHook] io.undertow : stopping server: Undertow - 2.2.9.Final==============================Destroying Spring==============================
容器中如何优雅关闭 Springboot
容器与进程模式并没有什么区别,我们给容器发送终止信号,容器会转发给 Springboot。
理论归理论,我们还是需要亲自实践,这样才能理解更深刻。
准备实验环境和素材,下面是 docker-compose.yaml 编排文件
version: '3.9'services:spring:image: openjdk:latestcontainer_name: springrestart: alwayshostname: www.netkiller.cnenvironment:TZ: Asia/ShanghaiJAVA_OPTS: -Xms256m -Xmx512m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512mports:- 8099:8080volumes:- ./test-0.0.1-SNAPSHOT.jar:/app/test-0.0.1-SNAPSHOT.jarentrypoint: java -jar /app/test-0.0.1-SNAPSHOT.jarcommand:--spring.profiles.active=dev--server.port=8080
实验步骤
运行容器:docker-compose up
观察容器:docker-compose logs -f
停止容器:
运行容器
[root@localhost netkiller.cn]# docker-compose up -dStarting spring ... done
观察容器日志
[root@localhost netkiller.cn]# docker-compose logs -fspring | Starting...spring |spring | . ____ _ __ _ _spring | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \spring | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \spring | \\/ ___)| |_)| | | | | || (_| | ) ) ) )spring | ' |____| .__|_| |_|_| |_\__, | / / / /spring | =========|_|==============|___/=/_/_/_/spring | :: Spring Boot :: (v2.5.3)spring |spring | 2021-07-29 11:29:34.556 INFO 1 --- [ main] cn.netkiller.Application : Starting Application v0.0.1-SNAPSHOT using Java 16.0.2 on www.netkiller.cn with PID 1 (/app/test-0.0.1-SNAPSHOT.jar started by root in /)spring | 2021-07-29 11:29:34.559 INFO 1 --- [ main] cn.netkiller.Application : The following profiles are active: devspring | 2021-07-29 11:29:35.903 WARN 1 --- [ main] io.undertow.websockets.jsr : UT026010: Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be usedspring | 2021-07-29 11:29:35.921 INFO 1 --- [ main] io.undertow.servlet : Initializing Spring embedded WebApplicationContextspring | 2021-07-29 11:29:35.921 INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1274 msspring | 2021-07-29 11:29:36.411 INFO 1 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint(s) beneath base path '/actuator'spring | 2021-07-29 11:29:36.437 INFO 1 --- [ main] io.undertow : starting server: Undertow - 2.2.9.Finalspring | 2021-07-29 11:29:36.444 INFO 1 --- [ main] org.xnio : XNIO version 3.8.4.Finalspring | 2021-07-29 11:29:36.451 INFO 1 --- [ main] org.xnio.nio : XNIO NIO Implementation Version 3.8.4.Finalspring | 2021-07-29 11:29:36.511 INFO 1 --- [ main] org.jboss.threads : JBoss Threads version 3.1.0.Finalspring | 2021-07-29 11:29:36.547 INFO 1 --- [ main] o.s.b.w.e.undertow.UndertowWebServer : Undertow started on port(s) 8080 (http)spring | 2021-07-29 11:29:36.560 INFO 1 --- [ main] cn.netkiller.Application : Started Application in 2.48 seconds (JVM running for 2.923)
停止容器
[root@localhost netkiller.cn]# docker ps | grep spring8901384d1973 openjdk:latest "java -jar /app/test…" 3 minutes ago Up About a minute 0.0.0.0:8099->8080/tcp, :::8099->8080/tcp spring[root@localhost netkiller.cn]# docker stop springspring[root@localhost netkiller.cn]# docker ps | grep spring
在观察日志
spring | 2021-07-29 11:31:31.807 INFO 1 --- [ionShutdownHook] io.undertow : stopping server: Undertow - 2.2.9.Finalspring | ==============================spring | Destroying Springspring | ==============================spring exited with code 143
现在可以看到 Springboot 是正常退出的
下面我们再做一个实验 docker kill
[root@localhost netkiller.cn]# docker-compose startStarting spring ... done[root@localhost netkiller.cn]# docker-compose logs -f[root@localhost netkiller.cn]# docker kill springspring
此时再观察日志,只输出了一行。
spring exited with code 137
结论,docker kill = kill -9
现在你应该明白什么时候该使用什么命令终止程序了吧,同时我们在写程序的时候,也应该将程序的运行状态反应出来,在我们停止程序运行的时候,可以去观察进程的状态,而不是半天没有反应,只能怀疑进程死了,必须执行B计划(kill -9)这会造成很多数据丢失的问题。
写入PID文件
我们明白了 kill 的原理后,常常需要与 pid 打交道,使用 ps 命令是可以查看 pid 的,但是当我们运行多个实例的时候会常常搞混,所以最好的方式是让 springboot 把PID写入到文件中。
package cn.netkiller;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.context.ApplicationPidFileWriter;@SpringBootApplicationpublic class Application {public static void main(String[] args) {System.out.println("Starting...");// SpringApplication.run(Application.class, args);SpringApplication springApplication = new SpringApplication(Application.class);springApplication.addListeners(new ApplicationPidFileWriter());springApplication.run(args);}}
程序运行后会在当前目录下产生一个 PID 文件
neo@MacBook-Pro-Neo ~/workspace/microservice/test % cat application.pid44027
修改 pid 文件位置可以配置 application.properties
server.port=8080spring.pid.file=/tmp/spring.pid
最后说说容器,容器的进程ID永远是 1 所以配置与否自己斟酌。
[root@localhost netkiller.cn]# docker exec -it spring cat /tmp/spring.pid1




