ShutdownHook – Java 優雅停機解決方案
- 2019 年 10 月 12 日
- 筆記
想像一下,如果你現在剛好在 word 上寫需求文檔,電腦突然重啟。等待開機完成,你可能會發現寫了一個小時文檔沒有保存,就這麼沒了。。。
一個正在運行 Java 應用如果突然將其停止,影響不止數據丟失,還會造成其他影響。比如:
- 請求丟失:記憶體隊列中等待執行請求丟失
- 數據丟失:處於記憶體快取中數據未持久化到磁碟
- 文件損壞:正在寫的文件沒有沒有更新完成,導致文件損壞
- 業務中斷:處理一半的業務被強行中斷,如支付成功了,卻沒有更新到資料庫中
- 服務未下線:上游服務依然往停止節點發送請求
所以在關閉服務之前,我們需要先做好善後工作,比如保存數據,清理資源,下線服務,然後才退出應用。這種有計劃平滑的關閉應用相對直接停止應用,就顯得非常『優雅』。
ps: 仔細品味,優雅停機這個詞真好~
ShutdownHook
Java 語言提供一種 ShutdownHook(鉤子)進位,當 JVM 接受到系統的關閉通知之後,調用 ShutdownHook 內的方法,用以完成清理操作,從而平滑的退出應用。
ShutdownHook程式碼如下:
Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("關閉應用,釋放資源"); }));
Runtime.getRuntime().addShutdownHook(Thread)
需要傳入一個執行緒對象,後續動作將會在該非同步執行緒內完成。除了主動關閉應用(使用 kill -15 指令),以下場景也將會觸發 ShutdownHook :
- 程式碼執行結束,JVM 正常退出
- 應用程式碼中調用
System#exit
方法 - 應用中發生 OOM 錯誤,導致 JVM 關閉
- 終端中使用
Ctrl+C
(非後台運行)
目前很多開源框架都是基於這個機制實現優雅停機,比如 Dubbo,Spring 等。
相關注意點
ShutdownHook 程式碼實現起來相對簡單,但是我們還是需要小心下面這些坑。
Runtime.getRuntime().addShutdownHook(Thread)
可以被多次調用
我們可以多次調用 Runtime.getRuntime().addShutdownHook(Thread)
方法,從而增加多個。但是需要注意的是,多個 ShutdownHook 之間並無任何順序,Java 並不會按照加入順序執行,反而將會並發執行。
所以盡量在一個 ShutdownHook 完成所有操作。
ShutdownHook 需要儘快執行結束
不要在 ShutdownHook 執行需要被阻塞程式碼,如 I/0 讀寫,這樣就會導致應用短時間不能被關閉。
Runtime.getRuntime().addShutdownHook(new Thread(() -> { while (true){ System.out.println("關閉應用,釋放資源"); } }));
上面程式碼中,我們使用 while(true)
模擬長時間阻塞這種極端情況,關閉該應用時,應用將會一直阻塞在 while
程式碼中,導致應用沒辦法被關閉。
除了阻塞之外,還需要小心其他會讓執行緒阻塞的行為,比如死鎖。
為了避免 ShutdownHook 執行緒被長時間阻塞,我們可以引入超時進位。如果等待一定時間之後,ShutdownHook 還未完成,由腳本直接調用 kill -9 強制退出或者 ShutdownHook 程式碼中引入超時進位。
文章首發於studyidea.cn/shutdownHook
歡迎關注我的公眾號:程式通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的部落格:studyidea.cn