Spring Boot 2.0 实现优雅停机

前期踩的坑 (Spring Boot 1.x)

1. 添加mavne依赖

<!-- springboot监控 -->  
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-actuator</artifactId>  
</dependency>  

2. 启用shutdown

在配置文件里添加下面的配置

#启用shutdown endpoint的HTTP访问
endpoints.shutdown.enabled=true
#不需要验证 
endpoints.shutdown.sensitive=false

启动的时候可以看到下面的日志,就说明成功了
启动日志

3. 优雅停机

发送POST请求 http://localhost:8080/shutdown
如果响应码是404 可以尝试POST http://localhost:8080/actuator/shutdown

Spring Boot 2.0

如果你使用的spring boot版本是2.x的就会发现,这些POST请求都会出现404的结果。

下面是Spring Boot 2.0 优雅停机的实现方式。

1.修改Application启动类

tomcat容器

@SpringBootApplication
public class ShutdownApplication {

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

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

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

    /**
     * 优雅关闭 Spring Boot。容器必须是 tomcat
     */
    private class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
        private final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
        private volatile Connector connector;
        private final int waitTime = 10;

        @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 进程在" + waitTime + " 秒内无法结束,尝试强制结束");
                    }
                } 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;
        }

    }
}

2. 使用 kill 命令杀死进程

使用下面的命令杀死进程。该命令是向 某个进程发送终止信号。

kill -15 [PID]

参考链接

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
本文链接:https://zdran.com/20180504.html