SpringBoot如何優雅關閉(SpringBoot2.3&Spring Boot2.2)

柳先開發表於2022-05-07

SpringBoot如何優雅關閉(SpringBoot2.3&Spring Boot2.2)

優雅停止&暴力停止

  • 暴力停止:像日常開發過程中,測試區或者本地開發時,我們並不會考慮專案關閉帶來的影響,只是想最快速的關掉重啟,所以用的最多的就是kill -9進行暴力停止服務;kill -9的結果就是強制關閉,不會等待服務釋放資源等操作,這也造成了,服務中很多程式無法正常結束。
  • 優雅停止:何謂優雅停止,就是等待已有的程式結束之後關閉服務,那麼如何實現優雅停止SpringBoot服務?

實現優雅停止

SpringBoot要實現優雅停止,分兩種情況一個是SpringBoot版本為2.3.0之前,一種是2.3.0及往後的版本。

  • SpringBoot 2.3.0及後續版本

    在SpringBoot的ReleaseNotes中我們可以看到,在2.3.0版本,SpringBoot新特性中有一個叫GraceFul shutdown的字樣。

    意思就是,可以通過配置server.shutdown來實現優雅關閉SpriingBoot服務,支援內嵌的Jetty,Reactor,Netty,Undertow伺服器,在配置了優雅停止的情況下關閉服務,服務將不會接收後續請求, 並且會等待寬限期,以便完成當前已有請求。

    配置解釋:

    1. server.shutdown : graceful : 表示開啟優雅停止
    2. spring.lifecycle.timeout-per-shutdown-phase : 60 :表示最大等待的寬限時間,超過這個時間還有請求沒結束的話,會強制關閉服務。

    注意:這裡需要注意的是,不能使用kill -9的命令停止服務,不然優雅停止的配置不會生效。

  • SpringBoot2.3.0之前的版本

    在上述的ReleaseNotes看到,配置的方式實現優雅停止時2.3.0之後才有的功能,那往前的版本怎麼辦?自己手動寫。

    @Slf4j
    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    public class NginxTestApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(NginxTestApplication.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 static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
            private volatile Connector connector;
    
    
            //獲取tomcat的connector
            @Override
            public void customize(Connector connector) {
                this.connector = connector;
            }
    
            //監聽事件
            @Override
            public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
                //拒絕停機操作的後續請求
                this.connector.pause();
                log.info("停止Tomcat connector, 拒絕接收後續請求");
                //獲取對應執行緒池並做對應型別判斷,true則開始優雅關閉
                Executor executor = this.connector.getProtocolHandler().getExecutor();
                if (executor instanceof ThreadPoolExecutor) {
                    try {
                        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                        //開始關閉執行緒池
                        threadPoolExecutor.shutdown();
                        log.info("開始關閉執行緒池");
                        //最大寬限時間
                        int waitTime = 30;
                        //若執行緒池中有未完成事件,等待完成後關閉,若超過最大寬限時間未完成,強制關閉;
                        //若沒有未完成事件,直接關閉
                        if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
                            log.info("Tomcat執行緒池無法在"+waitTime+"s 內優雅關閉. 強制結束");
                        }
                    } catch (InterruptedException ex) {
                        Thread.currentThread().interrupt();
                    }
                }
            }
        }
    
    }
    

    在啟動類中加上這些東西就行了,寫了很多註釋,就不詳細解釋了。

    注意:這裡同樣需要注意的是,不能使用kill -9的命令停止服務,不然優雅停止不會生效。

相關文章