Spring Boot 1.X和2.X優雅彩38平臺出租重啟實戰
Spring Boot 1.X
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.catalina.connector.Connector;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent;
- Spring Boot1.X Tomcat容器優雅停機
- @author yinjihuan
-
*/@Configuration
br/>@Configuration
/**- 用於接受shutdown事件
- @return*/
@Bean
br/>*/
@Bean
return new GracefulShutdown();
}
/** - 用於註入 connector
- @return*/
@Bean
br/>*/
@Bean
return new EmbeddedServletContainerCustomizer() {@Override
br/>@Override
if (container instanceof TomcatEmbeddedServletContainerFactory) {
((TomcatEmbeddedServletContainerFactory) container).addConnectorCustomizers(gracefulShutdown());
}
}
};
}
private static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
private volatile Connector connector;
private final int waitTime = 120;@Override
br/>@Override
this.connector = connector;}
@Override
br/>}
@Override
this.connector.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
log.info("shutdown start");
threadPoolExecutor.shutdown();
log.info("shutdown end");
if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
log.info("Tomcat 進程在" + waitTime + "秒內無法結束,嘗試強制結束");
}
log.info("shutdown success");
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
}
Spring Boot 2.X
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
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.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent;
/**
- Spring Boot2.X Tomcat容器優雅停機
- @author yinjihuan
-
*/@Configuration
br/>@Configuration
/**- 用於接受shutdown事件
- @return*/
@Bean
br/>*/
@Bean
return new GracefulShutdown();}
@Bean
br/>}
@Bean
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addConnectorCustomizers(gracefulShutdown());
return tomcat;
}
private static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
private volatile Connector connector;
private final int waitTime = 120;@Override
br/>@Override
this.connector = connector;}
@Override
br/>}
@Override
this.connector.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
log.info("shutdown start");
threadPoolExecutor.shutdown();
log.info("shutdown end");
if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
log.info("Tomcat 進程在" + waitTime + "秒內無法結束,嘗試強制結束");
}
log.info("shutdown success");
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
}
重啟服務腳本:
NG="zh_CN.UTF-8"
pid=ps ax | grep fangjia-youfang-web | grep java | head -1 | awk ‘{print $1}‘
echo $pid
#kill $pid
curl -X POST http://127.0.0.1:8086/shutdown?token=認證信息
while [[ $pid != "" ]]; do
echo ‘服務停止中...‘
sleep 1
pid=ps ax | grep fangjia-youfang-web | grep java | head -1 | awk ‘{print $1}‘
done
echo ‘服務停止成功,開始重啟服務...‘
java -jar xxx.jar
在重啟之前首先發送重啟命令到endpoint,或者用kill 進程ID的方式,千萬不要用kill -9。
然後循環檢測進程是否存在,如果服務正常停止了,進程也就不存在了,如果進程還在,證明還有未處理完的請求,停止1秒,繼續檢測。
關於重啟服務,建議用kill方式,這樣就不用依賴spring-boot-starter-actuator,如果用endpoint方式,則需要控制好權限,不然隨時都有可能被人重啟了,可以用security來控制權限,我這邊是自己用過濾器來控制的。
如果用actuator方式重啟的話需要配置啟用重啟功能:
1.x配置如下:
endpoints.shutdown.enabled=true
2.x配置就比較多了,默認只暴露了幾個常用的,而且訪問地址也有變化,比如health, 以前是直接訪問/health,現在需要 /actuator/health才能訪問。我們可以通過配置來兼容之前的訪問地址。
shutdown默認是不暴露的,可以通過配置暴露並開始,配置如下:
#訪問路徑,配置後就和1.x版本路徑一樣
management.endpoints.web.base-path=/
暴露所有,也可以暴露單個或多個
management.endpoints.web.exposure.include=*
開啟shutdown
management.endpoint.shutdown.enabled=true
文檔請參考:https://docs.spring.io/spring-boot/docs/2.0.2.RELEASE/reference/htmlsingle/#production-ready
如何測試
測試的話我們可以寫一個簡單的接口,在接口中等待,然後執行腳本停止項目,如果正常的話會輸出服務停止中,等到你的接口執行完成,進程才會消失掉,但是如果超過了你配置的等待時間就會強行退出。
@GetMapping("/hello")
public String hello() {
System.out.println("req.........");
try {
Thread.sleep(1000 60 3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello";
}
需要註意的問題
如果你的項目中有用到其他的線程池,比如Spring的ThreadPoolTaskExecutor,不熟悉的同學可以參考我的這篇文章《Spring Boot Async異步執行》
在發送停止命令後如果ThreadPoolTaskExecutor有線程還沒處理完的話,這個時候進程是不會自動關閉的。這個時候我們需要對線程池進行關閉處理,增加代碼如下:
AsyncTaskExecutePool asyncTaskExecutePool = event.getApplicationContext().getBean(AsyncTaskExecutePool.class);
Executor executors = asyncTaskExecutePool.getAsyncExecutor();
try {
if (executors instanceof ThreadPoolTaskExecutor) {
ThreadPoolTaskExecutor threadPoolExecutor = (ThreadPoolTaskExecutor) executors;
log.info("Async shutdown start");
threadPoolExecutor.setWaitForTasksToCompleteOnShutdown(true);
threadPoolExecutor.setAwaitTerminationSeconds(waitTime);
threadPoolExecutor.shutdown();
}
} catch (Exception ex) {
Thread.currentThread().interrupt();
}
ThreadPoolTaskExecutor只有shutdown方法,沒有awaitTermination方法,通過查看源碼,在shutdown之前設置setWaitForTasksToCompleteOnShutdown和setAwaitTerminationSeconds同樣能實現awaitTermination。
源碼如下:
public void shutdown() {
if (logger.isInfoEnabled()) {
logger.info("Shutting down ExecutorService" + (this.beanName != null ? " ‘" + this.beanName + "‘" : ""));
}
if (this.executor != null) {
if (this.waitForTasksToCompleteOnShutdown) {
this.executor.shutdown();
}
else {
for (Runnable remainingTask : this.executor.shutdownNow()) {
cancelRemainingTask(remainingTask);
}
}
awaitTerminationIfNecessary(this.executor);
}
}
當waitForTasksToCompleteOnShutdown為true的時候就直接調用executor.shutdown();,最後執行awaitTerminationIfNecessary方法。
private void awaitTerminationIfNecessary(ExecutorService executor) {
if (this.awaitTerminationSeconds > 0) {
try {
if (!executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)) {
if (logger.isWarnEnabled()) {
logger.warn("Timed out while waiting for executor" +
(this.beanName != null ? " ‘" + this.beanName + "‘" : "") + " to terminate");
}
}
}
catch (InterruptedException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Interrupted while waiting for executor" +
(this.beanName != null ? " ‘" + this.beanName + "‘" : "") + " to terminate");
}
Thread.currentThread().interrupt();
}
}
}
awaitTerminationIfNecessary中會判斷屬性awaitTerminationSeconds 如果與值的話就執行關閉等待檢測邏輯,跟我們處理tomcat關閉的代碼是一樣的。
發現這樣做之後好像沒什麽效果,於是我換了一種寫法,直接通過獲取ThreadPoolTaskExecutor中的ThreadPoolExecutor來執行關閉邏輯:
AsyncTaskExecutePool asyncTaskExecutePool = event.getApplicationContext().getBean(AsyncTaskExecutePool.class);
Executor executors = asyncTaskExecutePool.getAsyncExecutor();
try {
if (executors instanceof ThreadPoolTaskExecutor) {
ThreadPoolTaskExecutor threadPoolExecutor = (ThreadPoolTaskExecutor) executors;
log.info("Async shutdown start");
threadPoolExecutor.getThreadPoolExecutor().shutdown();
log.info("Async shutdown end"+threadPoolExecutor.getThreadPoolExecutor().isTerminated());
if (!threadPoolExecutor.getThreadPoolExecutor().awaitTermination(waitTime, TimeUnit.SECONDS)) {
log.info("Tomcat 進程在" + waitTime + "秒內無法結束,嘗試強制結束");
}
log.info("Async shutdown success");
}
} catch (Exception ex) {
Thread.currentThread().interrupt();
}
這是方式也沒用達到我想要的效果,當我發出kill命令之後,直接就退出了,其實我有一個後臺線程在ThreadPoolTaskExecutor中運行,通過輸出的日誌看到,只要調用了shutdown,isTerminated方法返回的就是true,說已經關閉了,這塊還沒找到原因,有研究出來的小夥伴還請分享出來。
Spring Boot 1.X和2.X優雅彩38平臺出租重啟實戰