什么是优雅停机
让应用退出前,通知 JVM 先不再接收新的消息,但正在处理的任务要正常完成,完成特定资源清理,之后才退出应用,这就是优雅关机(graceful shutdown)的概念
Java 语言中,我们可以通过 Runtime.getRuntime().addShutdownHook(Thread hook):这个方法的意思就是在jvm中增加一个关闭的钩子,当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。所以这些钩子可以在jvm关闭的时候进行内存清理、对象销毁等操作。
sringboot 实现
1 tomcat配置
//tomcat链接配置1
import org.apache.catalina.connector.Connector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.cloud.zookeeper.serviceregistry.ZookeeperRegistration;
import org.springframework.cloud.zookeeper.serviceregistry.ZookeeperServiceRegistry;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* springboot优雅关闭
*/
public class GracefulShutdownTomcatConnectorCustomizer implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private final Logger log = LoggerFactory.getLogger(GracefulShutdownTomcatConnectorCustomizer.class);
private volatile Connector connector;
private int timeout=30;
private int wait=30;
private boolean isShutdown;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
/**
* 服务下线,没有使用zookeeper的可以忽略
* @param contextClosedEvent
*/
/* private void offline(ContextClosedEvent contextClosedEvent){
try{
ZookeeperServiceRegistry registry = contextClosedEvent.getApplicationContext().getBean(ZookeeperServiceRegistry.class);
ZookeeperRegistration registration = contextClosedEvent.getApplicationContext().getBean(ZookeeperRegistration.class);
registry.deregister(registration);
log.info("[GracefulShutdown] service zookeeper deregister success. id={},name={},address={},port={}.",
registration.getServiceInstance().getId(),registration.getServiceInstance().getName(),
registration.getServiceInstance().getAddress(),registration.getServiceInstance().getPort());
}catch(Exception e){
log.error("[GracefulShutdown] service zookeeper deregister error.",e);
}
}*/
/**
* tomcat暂停接收
*/
private void pause(){
try{
Thread.sleep(wait*1000);
log.info("[GracefulShutdown] service pause. wait={}.",wait);
this.connector.pause();
}catch(Exception e){
log.error("[GracefulShutdown] connector pause error.",e);
}
}
@Override
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
if(isShutdown){
return;
}
this.offline(contextClosedEvent);
if (this.connector == null) {
isShutdown = true;
return;
}
this.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
log.info("[GracefulShutdown] tomcat begin shutdown. timeout={}.",timeout);
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(timeout, TimeUnit.SECONDS)) {
log.info("[GracefulShutdown] tomcat is not termination. timeout={}.",timeout);
}
log.info("[GracefulShutdown] tomcat is shutdown success. timeout={}.",timeout);
} catch (InterruptedException ex) {
log.error("[GracefulShutdown] application Event error.",ex);
Thread.currentThread().interrupt();
}
}
isShutdown = true;
}
}
//tomcat容器配置2
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
public class GracefulShutdownTomcatContainerCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
private final GracefulShutdownTomcatConnectorCustomizer connectorCustomizer;
public GracefulShutdownTomcatContainerCustomizer(GracefulShutdownTomcatConnectorCustomizer connectorCustomizer) {
this.connectorCustomizer = connectorCustomizer;
}
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers(connectorCustomizer);
}
}
2 停机配置
import shutdown.tomcat.GracefulShutdownTomcatConnectorCustomizer;
import shutdown.tomcat.GracefulShutdownTomcatContainerCustomizer;
import javax.servlet.Servlet;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.UpgradeProtocol;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@Configuration
public class GracefulShutdownAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public GracefulShutdownTomcatContainerCustomizer gracefulShutdownTomcatContainerCustomizer(
GracefulShutdownTomcatConnectorCustomizer connectorCustomizer) {
return new GracefulShutdownTomcatContainerCustomizer(connectorCustomizer);
}
@Bean
@ConditionalOnMissingBean
public GracefulShutdownTomcatConnectorCustomizer gracefulShutdownTomcatConnectorCustomizer() {
return new GracefulShutdownTomcatConnectorCustomizer();
}
}
定义钩子函数
import javax.annotation.PostConstruct;
@Component
public class ShutDownConfig {
public static boolean healthStatus = true;
@PostConstruct
public void init() {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
// 修改checkHeal状态码
healthStatus = false;
// 这里设置等待20秒,让clb检测状态后拒绝请求
Thread.sleep(20000);
close();//关闭资源
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
//把所有关闭资源的close放在这,才能做到程序全部停止前,已经进入的请求可以正常执行
public void close() {
//调用使用的资源关闭close()接口
}
}
cli接口
@RestController
public class HealthController {
@Autowired
private IUserDao userDao;
/*
@Mapper
public interface IUserDao {
@Select("SELECT now()")
String health();
}
*/
@GetMapping("/checkHealth")
public Result<String> check() {
try {
userDao.health();
if (!serviceStatus) {
throw new ServiceException("application is unhealthy");
}
return Result.ofSuccess("ok");
} catch (Exception e) {
throw new ServiceException("application is unhealthy", e);
}
}
}
测试
启动代码,jps查找当前应用的pid, 使用kill -15 pid,然后在postman 调用接口