最近领导要求我处理公司目前的一个痛点,他们每次发版都是直接将一个进程kill -9 结束进程,然后将新包替换上去,这样会导致一两秒内某些请求服务不可用,而且正在工作的线程会被立即中断,我心里想,那么暴力,还好是家小公司,业务量不会那么大,不然很多用户会奔溃的。
我一开始跟领导提出使用灰度方案A/B切换来做,就是准备两台环境,然后两套环境进行来回切换,从而实现无缝替换新包,可惜领导不太愿意接受这套方案,很尴尬!
但是,领导跟我讲
springboot
有提供优雅停机配置,你去研究一下。然后我就去查了相关的资料,确实有这么一个东西。突然,我想到
Dubbo
也有提供优雅停机的配置,于是,我就去看了官网的配置,
Dubbo
优雅停机有个缺陷:
Dubbo
无法做到等待
Dubbo
中的用户线程以外的线程处理完才停机,需要自己对Dubbo的优雅停机功能进行扩展。
现在我们是基于springboot来实现优雅停机,但是springboot 1.x和2.x配置还是有点区别的,下面来看下springboot 1.x的配置:
springboot 1.x 配置
/**
* 用于接受shutdown事件
*
* @return
*/
@Bean
public MyShutdown myShutdown() {
return new MyShutdown();
}
/**
* 用于注入 connector
*
* @return
*/
@Bean
public EmbeddedServletContainerCustomizer tomcatCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
((TomcatEmbeddedServletContainerFactory) container).addConnectorCustomizers(myShutdown());
}
}
};
}
private static class MyShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private static final Logger log = LoggerFactory.getLogger(MyShutdown.class);
private volatile Connector connector;
private final int waitTime = 100;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
this.connector.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
log.warn("Tomcat thread pool did not shut down gracefully within "
+ waitTime + " seconds. Proceeding with forceful shutdown");
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
springboot 2.x 配置
/**
* 用于接受shutdown事件
*
* @return
*/
@Bean
public MyShutdown myShutdown() {
return new MyShutdown();
}
/**
* 用于注入 connector
*
* @return
*/
@Bean
public WebServerFactoryCustomizer tomcatCustomizer() {
return (WebServerFactoryCustomizer<ConfigurableWebServerFactory>) container -> {
if (container instanceof TomcatServletWebServerFactory) {
((TomcatServletWebServerFactory) container).addConnectorCustomizers(myShutdown());
}
};
}
private static class MyShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private static final Logger log = LoggerFactory.getLogger(MyShutdown.class);
private volatile Connector connector;
private final int waitTime = 100;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
this.connector.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
log.warn("Tomcat thread pool did not shut down gracefully within "
+ waitTime + " seconds. Proceeding with forceful shutdown");
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}