【spring-boot】如何优雅关闭服务

借助 spring-boot

开启优雅关闭终端:endpoints.shutdown.enabled: true

调用接口: POST /shutdown

用docker实现

  • docker stop/restart [-t seconds] [container_id]

    发送中断信号,不会接收新的请求,之前的请求会继续运行

    • 新请求无法创建连接:因为服务不再接收新的请求,所以会报503:Service Unavailable,
      建议Client设置一个合适的 连接超时时间

    • 超时时间[-t]:到达超时时间后,服务仍会立即中断,所以最好 设置一个合适的超时时间

    • 日志:接收到中断信号后,log不会再打印,但 log.isXXXEnabled() 仍返回 true
      所以不要用log作为判断请求中断的标记,而是用System.out.print

代码解决

该方法只是手动关闭服务线程,前几种方法已经可以做到,不建议使用

  • 实现

    自定义 GracefulShutdownConfiguration

    通过 @ImportAutoConfiguration(GracefulShutdownConfiguration.class) 启用配置

  • 问题:停止后服务仍未关闭,用jstack看到仍然有线程在执行(基于我当前的服务):

    • org.springframework.cloud.consul.config.ConfigWatch

      解决:spring.cloud.consul.config.watch.enabled: false

      但是有些服务是需要开启的

      查看源码,该线程默认要等待55s来判断结束状态,时间较长

    • ch.qos.logback.classic.AsyncAppender

      解决:logback-spring 里加上

      <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>

      ShutdownHookBase#close 里会调用 Context.stop() 进行关闭

参考

GracefulShutdownConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Slf4j
public class GracefulShutdownConfiguration {

@Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}

@Bean
public TomcatEmbeddedServletContainerFactory webServerFactory(final GracefulShutdown gracefulShutdown) {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.addConnectorCustomizers(gracefulShutdown);
return factory;
}

class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {

private volatile Connector connector;

@Override
public void customize(Connector connector) {
this.connector = connector;
}

@Override
public void onApplicationEvent(ContextClosedEvent event) {
log.info("signal: shutdown");
log.info(event.toString());
this.connector.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
log.info(threadPoolExecutor.getActiveCount() + "");
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();
}

}
}
}
}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注