Spring Boot 系列:最新版優雅停機詳解
阿新 • • 發佈:2020-10-15
> 愛生活,愛編碼,本文已收錄[架構技術專欄](http://www.jiagoujishu.com/)關注這個喜歡分享的地方。
開源專案:
- 分散式監控(Gitee GVP最有價值開源專案 ):https://gitee.com/sanjiankethree/cubic
- 攝像頭視訊流採集:https://gitee.com/sanjiankethree/cubic-video
## 優雅停機
目前Spring Boot已經發展到了2.3.4.RELEASE,伴隨著2.3版本的到來,優雅停機機制也更加完善了。
目前版本的Spring Boot 優雅停機支援Jetty, Reactor Netty, Tomcat和 Undertow 以及反應式和基於 Servlet 的 web 應用程式都支援優雅停機功能。
**優雅停機的目的:**
如果沒有優雅停機,伺服器此時直接直接關閉(kill -9),那麼就會導致當前正在容器內執行的業務直接失敗,在某些特殊的場景下產生髒資料。
**增加了優雅停機配置後:**
在伺服器執行關閉(kill -2)時,會預留一點時間使容器內部業務執行緒執行完畢,此時容器也不允許新的請求進入。新請求的處理方式跟web伺服器有關,Reactor Netty、 Tomcat將停止接入請求,Undertow的處理方式是返回503.
## 新版配置
#### YAML配置
新版本配置非常簡單,server.shutdown=graceful 就搞定了(注意,優雅停機配置需要配合Tomcat 9.0.33(含)以上版本)
```yaml
server:
port: 6080
shutdown: graceful #開啟優雅停機
spring:
lifecycle:
timeout-per-shutdown-phase: 20s #設定緩衝時間 預設30s
```
在設定了緩衝引數timeout-per-shutdown-phase 後,在規定時間內如果執行緒無法執行完畢則會被強制停機。
下面我們來看下停機時,加了優雅停日誌和不加的區別:
****
```java
//未加優雅停機配置
Disconnected from the target VM, address: '127.0.0.1:49754', transport: 'socket'
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
```
加了優雅停機配置後,可明顯發現的日誌 **Waiting for active requests to cpmplete**,此時容器將在ShutdownHook執行完畢後停止。
![](https://imgkr2.cn-bj.ufileos.com/6bba6a13-beb4-466e-942d-76828d74e99c.jpg?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=VjHXDoKrahydmHRzhuUw05JFkjU%253D&Expires=1602773654)
#### 關閉方式
1、 一定不要使用kill -9 操作,使用kill -2 來關閉容器。這樣才會觸發java內部ShutdownHook操作,kill -9不會觸發ShutdownHook。
2、可以使用端點監控 POST 請求 **/actuator/shutdown** 來執行優雅關機。
#### 新增ShutdownHook
通過上面的日誌我們發現Druid執行了自己的ShutdownHook,那麼我們也來新增下ShutdownHook,有幾種簡單的方式:
1、實現DisposableBean介面,實現destroy方法
```java
@Slf4j
@Service
public class DefaultDataStore implements DisposableBean {
private final ExecutorService executorService = new ThreadPoolExecutor(OSUtil.getAvailableProcessors(), OSUtil.getAvailableProcessors() + 1, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(200), new DefaultThreadFactory("UploadVideo"));
@Override
public void destroy() throws Exception {
log.info("準備優雅停止應用使用 DisposableBean");
executorService.shutdown();
}
}
```
2、使用@PreDestroy註解
```java
@Slf4j
@Service
public class DefaultDataStore {
private final ExecutorService executorService = new ThreadPoolExecutor(OSUtil.getAvailableProcessors(), OSUtil.getAvailableProcessors() + 1, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(200), new DefaultThreadFactory("UploadVideo"));
@PreDestroy
public void shutdown() {
log.info("準備優雅停止應用 @PreDestroy");
executorService.shutdown();
}
}
```
這裡注意,@PreDestroy 比 DisposableBean 先執行
#### 關閉原理
1、使用kill pid關閉,原始碼很簡單,大家可以看下GracefulShutdown
```java
private void doShutdown(GracefulShutdownCallback callback)