強制停機
無論是Linux的Kill -9 pid還是windows的taskkill /f /pid強制關閉進程,都會帶來一些副作用,如:
請求丟失:內(nèi)存隊列中等待執(zhí)行的請求丟失;
數(shù)據(jù)丟失:處于內(nèi)存中的數(shù)據(jù)尚未持久化到磁盤中;
業(yè)務中斷:處理一半的業(yè)務被強行中斷,卻沒有更新到數(shù)據(jù)庫中;
文件損壞:正在進行文件的write操作中,突然退出,導致文件損壞;
鎖表:在操作數(shù)據(jù)庫多表更新時,事務方法執(zhí)行中斷,導致數(shù)據(jù)庫鎖表;
優(yōu)雅停機
Java是通過 JDK 的 ShutdownHook 來完成優(yōu)雅停機的,當程序接收到退出指令后,會標記為退出狀態(tài),此時不再接收新的消息,然后將積壓的消息處理完后回收資源,最后關閉線程。所以不能直接使用kill -9 PID 等強制關閉指令,只有通過 kill -2 PID(Ctrl + C)或 kill PID (kill -15 PID)時,才會通知程序調(diào)用ShutdownHook方法。通常優(yōu)雅停機需要有等待超時機制,如果在規(guī)定時間內(nèi)還未完成退出前的操作,則由直接調(diào)用kill -9 PID,強制退出。
ShutdownHook用法
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("關閉應用,釋放資源");
}));
優(yōu)雅停機腳本stop.sh
#!/bin/bash
cd `dirname $0`
SERVER_PORT=$1
if [ ! -n "$SERVER_PORT" ]; then
echo "用法:$0 <SERVER_PORT>"
exit 1
fi
PIDS=`ps aux | grep java | grep "--server.port=$SERVER_PORT" | awk '{print $2}'`
if [ -z "$PIDS" ]; then
echo "錯誤: 指定端口的服務進程沒有運行!"
exit 1
fi
echo -e "正在停止 ...\c"
for PID in $PIDS ; do
kill $PID > /dev/null 2>&1
done
COUNT=0
NUM=0
#等待程序處理積壓的消息
while [ $COUNT -lt 1 ]; do
echo -e ".\c"
sleep 1
NUM=$(( $NUM + 1 ))
COUNT=1
for PID in $PIDS ; do
PID_EXIST=`ps -f -p $PID | grep java`
if [ -n "$PID_EXIST" ]; then
COUNT=0
break
fi
done
#90秒超時強制退出
if [ $NUM -gt 90 ]; then
for PID in $PIDS ; do
kill -9 $PID > /dev/null 2>&1
done
break
fi
done
echo "成功關閉進程: $PIDS"
StringBoot配置
spring boot 2.3.x 版本以后,內(nèi)置了優(yōu)雅停機的機制,也就不需要自行擴展容器的線程池來處理, 目前spring boot嵌入式支持的web服務器(Jetty、Reactor Netty、Tomcat 和 Undertow)以及反應式和基于Servlet的web 應用程序都支持優(yōu)雅停機功能。只需在application.yml中添加如下配置即可。
server:
shutdown: graceful #關停方式,默認IMMEDIATE(立即關閉)
spring:
lifecycle:
timeout-per-shutdown-phase: 30s #最大等待線程結(jié)束時間,默認30s
- spring boot 2.3.x之前的版本需要自己去擴展, 具體代碼和腳本可以參考:spring-boot-graceful-shutdown
容器表現(xiàn)行為
| Web 容器 | 表現(xiàn)行為 |
|---|---|
| Tomcat 9.0.33+ | 停止接收請求,客戶端新請求等待超時。 |
| Reactor Netty | 停止接收請求,客戶端新請求等待超時。 |
| Undertow | 停止接收請求,客戶端新請求直接返回 503。 |