Spring Boot 内嵌容器 Tomcat / Undertow / Jetty 优雅停机实现

Spring Boot 在关闭时,如果有请求没有响应完,在不同的容器会出现不同的结果,例如,在 Tomcat 和 Undertow 中会出现中断异常,那么就有可能对业务造成影响。所以,优雅停机非常有必要性,目前官方是没有提供很好的策略来实现。

Each SpringApplication registers a shutdown hook with the JVM to ensure that the ApplicationContext closes gracefully on exit. All the standard Spring lifecycle callbacks (such as the DisposableBean interface or the @PreDestroy annotation) can be used.

Spring Boot Application 在接收到停机信号后,可以通过 DisposableBean 接口 、 @PreDestroy 注解 或者 ContextClosedEvent 事件来处理优雅停机的相关逻辑。

版本信息

Spring Boot 版本: 2.0.0.RELEASE

Tomcat 优雅停机

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    /**
     * 用于接受 shutdown 事件
     */
    @Bean
    public GracefulShutdown gracefulShutdown() {
        return new GracefulShutdown();
    }

    @Bean
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        tomcat.addConnectorCustomizers(gracefulShutdown());
        return tomcat;
    }

    /**
     * 优雅关闭 Spring Boot
     */
    private class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
        private final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
        private volatile Connector connector;
        private final int waitTime = 30;
        @Override
        public void customize(Connector connector) {
            this.connector = connector;
        }
        @Override
        public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
            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();
                }
            }
        }
    }

}

Undertow 优雅停机

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    /**
     * 优雅关闭 Spring Boot
     */
    @Component
    public class GracefulShutdown implements ApplicationListener<ContextClosedEvent> {

        @Autowired
        private GracefulShutdownWrapper gracefulShutdownWrapper;

        @Autowired
        private ServletWebServerApplicationContext context;

        @Override
        public void onApplicationEvent(ContextClosedEvent contextClosedEvent){
            gracefulShutdownWrapper.getGracefulShutdownHandler().shutdown();
            try {
                UndertowServletWebServer webServer = (UndertowServletWebServer)context.getWebServer();
                Field field = webServer.getClass().getDeclaredField("undertow");
                field.setAccessible(true);
                Undertow undertow = (Undertow) field.get(webServer);
                List<Undertow.ListenerInfo> listenerInfo = undertow.getListenerInfo();
                Undertow.ListenerInfo listener = listenerInfo.get(0);
                ConnectorStatistics connectorStatistics = listener.getConnectorStatistics();
                while (connectorStatistics.getActiveConnections() > 0){}
            }catch (Exception e){
                // Application Shutdown
            }
        }
    }
}
@Component
public class GracefulShutdownWrapper implements HandlerWrapper{

    private GracefulShutdownHandler gracefulShutdownHandler;

    @Override
    public HttpHandler wrap(HttpHandler handler) {
        if(gracefulShutdownHandler == null) {
            this.gracefulShutdownHandler = new GracefulShutdownHandler(handler);
        }
        return gracefulShutdownHandler;
    }

    public GracefulShutdownHandler getGracefulShutdownHandler() {
        return gracefulShutdownHandler;
    }

}
@Component
@AllArgsConstructor
public class UndertowExtraConfiguration {

    private final GracefulShutdownWrapper gracefulShutdownWrapper;

    @Bean
    public UndertowServletWebServerFactory servletWebServerFactory() {
        UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
        factory.addDeploymentInfoCustomizers(deploymentInfo -> deploymentInfo.addOuterHandlerChainWrapper(gracefulShutdownWrapper));
        factory.addBuilderCustomizers(builder -> builder.setServerOption(UndertowOptions.ENABLE_STATISTICS, true));
        return factory;
    }

}

Jetty 优雅停机

默认支持所有请求完毕后再关闭,缺点:客户端接收不到响应,有待改进!

转载声明:本文转载自「精讲JAVA」。

原文发布于微信公众号 - 平凡文摘(tooooooozi)

原文发表时间:2018-05-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏IT笔记

SpringBoot开发案例之整合Dubbo消费者

有人卖就有人买,显然是亘古不变的真理,前两篇讲解了SpringBoot+Dubbo的提供者的几种暴露方式,这篇跟大家分享一下消费者如何去订阅属于自己的服务。 相...

2635
来自专栏温安适的blog

2个小bug,有点小门道

2017年的某日,小辉(我的同事)遇到了一个bug,解决了一下午还是没有找到,气的摔键盘,骂人,我看在眼里,急在心中。

4207
来自专栏咸鱼不闲

简单才是美! SpringBoot+JPA

SpringBoot 急速构建项目,真的是用了才知道,搭配JPA作为持久层,一简到底! 下面记录项目的搭建,后续会添加NOSQL redis,搜索引擎elast...

863
来自专栏杂烩

kafka连接异常 原

722
来自专栏青枫的专栏

day35_Spring学习回顾_03

511
来自专栏JAVA技术站

SpringCloud之zuul搭建

Zuul的主要功能是路由和过滤器。路由功能是微服务的一部分,比如/api/user映射到user服务,/api/shop映射到shop服务。zuul实现了负载均...

822
来自专栏一个会写诗的程序员的博客

《Spring Boot极简教程》第7章 Spring Boot集成模板引擎

其实,没有任何一个模板引擎(jsp,velocity,thymeleaf,freemarker,etc)可以完全实现MVC绝对的分层,只有“自由度”上的界定罢了...

663
来自专栏dalaoyang

SpringBoot集成Druid监控

druid是开源的数据库连接池,提供了优秀的对数据库操作的监控功能,本文要讲解一下springboot项目怎么集成druid。 本文在基于jpa的项目下开发,首...

3657
来自专栏Java帮帮-微信公众号-技术文章全总结

高级框架-SpringBoot【悟空教程】

Spring 诞生时是 Java 企业版(Java Enterprise Edition,JEE,也称 J2EE)的轻量级代替品。无需开发重量级的 Enterp...

1032
来自专栏后端之路

spring定时任务相关

目前环境中定时任务使用比较常见,对使用quartz的用法不做太多说明,简述一下分布式环境下quartz的做法(基于没有单独的quartz server) ma...

1776

扫码关注云+社区