日常上线重新发布项目时需要考虑服务是否还在对外提供服务,处理请求中,尤其是一些需要调用外部服务的项目尤其需要注意(如果下游服务没有做好幂等处理时会出现重复数据等脏数据问题),Java通常是通过钩子函数处理这个问题(Runtime.getRuntime().addShutdownHook())。
一、关闭场景
1.正常关闭
1.1 非守护线程执行完毕
1.2 kill -2 \ -15
1.3 System.exit(0)
1.4 ctl + c
2.异常关闭
2.1 RuntimeException
2.2 oom
3.强制关闭
3.1 断电
3.2 系统关机
3.3 kill -9
3.4 Runtime.halt()
二、SpringBoot优雅停机
1.使用spring-boot-starter-actuator
1.1)添加依赖【pom.xml】
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
1.2)添加配置文件【application.yml】
management:
server:
port: 12345 # 端口默认=server.port 增加安全性
endpoints:
web:
exposure:
include: "*" # 开放节点 * 开放所有 beans,.....
endpoint:
shutdown:
enabled: true # 打开shutdown端点 POST请求
1.3)CURL调用关闭接口
curl -X POST http://{ip}:{port}/actuator/shutdown
2.自己实现关闭请求接口
使用ConfigurableApplicationContext 的close方法。
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class ShutdownContext implements ApplicationContextAware {
private ConfigurableApplicationContext context;
public void showdown() {
if (null != context) {
context.close();
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (applicationContext instanceof ConfigurableApplicationContext) {
this.context = (ConfigurableApplicationContext) applicationContext;
}
}
}
3.Springboot2.3开启Graceful Shutdown配置
3.1)添加配置文件【application.yml】
server:
shutdown: graceful #开启优雅停机,默认是立即停机IMMEDIATE
spring:
lifecycle:
timeout-per-shutdown-phase: 20s #缓冲器即最大等待时间
3.2)kill -2 \ ctl + c
三、linux kill 命令
kill -2 pid 向指定 pid 发送 SIGINT 中断信号, 等同于 ctrl+c。
kill -9 pid 向指定 pid 发送 SIGKILL 立即终止信号。
kill -15 pid 向指定 pid 发送 SIGTERM 终止信号,等同于 kill pid。
SIGINT信号【-2】:
会被当前进程树接收到,当前进程与它的子进程都会收到该信号。
SIGKILL信号【-9】:
程序不能捕获该信号,最粗暴最快速结束程序的方法。
SIGTERM信号【-15】:
会被当前进程接收到, 它的子进程不会收到, 如果当前进程被kill掉, 它的的子进程的父进程将变成init进程。