最近上司要求我處理公司目前的一個痛點,他們每次發版都是直接将一個程序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();
}
}
}
}