ShutdownHook- Java 優雅停機解決方案

想象一下,如果你現在剛好在 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

歡迎關注我的公衆號:程序通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的博客:studyidea.cn

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章