背景
服務(wù)能被優(yōu)雅停機(jī)辙喂,指的是:在接收到停機(jī)指令后摹菠,服務(wù)程序應(yīng)該能拒絕新的請(qǐng)求, 但應(yīng)該繼續(xù)完成已經(jīng)接收請(qǐng)求囊扳。
解決方案
Graceful Shutdown Spring Boot Applications 這篇文章提供了一個(gè)經(jīng)典的實(shí)現(xiàn)優(yōu)雅停機(jī)的實(shí)現(xiàn)尿贫。
import org.apache.catalina.connector.Connector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
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;
public class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
private volatile Connector connector;
@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(30, TimeUnit.SECONDS)) {
log.warn("Tomcat thread pool did not shut down gracefully within "
+ "30 seconds. Proceeding with forceful shutdown");
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
以上代碼實(shí)現(xiàn)了兩個(gè)功能:
- 實(shí)現(xiàn)
TomcatConnectorCustomizer
往毡,作用是獲取tomcat的connector个少,方便后續(xù)對(duì)tomcat的連接做操作洪乍。 - 實(shí)現(xiàn)
ApplicationListener
,監(jiān)聽ApplicationContext發(fā)布的事件夜焦,該例中監(jiān)聽的是ContextClosedEvent
事件壳澳,監(jiān)聽到此類事件后把tomcat中connector的線程池shutdown并等待30s,如果所有線程都結(jié)束則正常退出茫经,否則發(fā)出報(bào)警巷波。這里也體現(xiàn)了設(shè)計(jì)模式中的觀察者模式,實(shí)現(xiàn)交互對(duì)象之間的松耦合卸伞。
設(shè)計(jì)好listener之后還需要將該listener注冊(cè)到ApplicationContext中,方便其接受ApplicationContext發(fā)布的事件荤傲。同時(shí)還需將該listener注冊(cè)到tomcat中垮耳,方便獲取該Spring應(yīng)用集成的tomcat的connector终佛。注冊(cè)的代碼如下:
@Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
@Bean
public ConfigurableServletWebServerFactory webServerFactory(final GracefulShutdown gracefulShutdown) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(gracefulShutdown);
return factory;
}