您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(guān)JVM怎么實現(xiàn)安全退出,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
上線!重啟!你還在為丟失任務(wù)而煩惱么?看這里看這里,從此不再丟任務(wù),JVM可以安全退出的
在交易流程中,為了提升服務(wù)的性能,我們做了一些異步化的優(yōu)化,比如更新用戶最近使用的收貨地址、提單完成后通過MQ去發(fā)送各種通知類消息、清理用戶的購物車等等這些操作,異步化加快了應(yīng)用的響應(yīng)速度同時也帶來一個隱患,如何保障異步操作的執(zhí)行?這個場景主要發(fā)生在應(yīng)用重啟時,對于通過線程或線程池進行的異步化,JVM重啟時,后臺執(zhí)行的異步操作可能尚未完成。這時,需要通過JVM安全關(guān)閉來保證異步操作進行完成后,JVM再執(zhí)行關(guān)閉。
更廣泛的說,在Linux上很多應(yīng)用通常會通過kill -9 pid的方式強制將進程殺掉,這種方式簡單高效,因此很多應(yīng)用的停止腳本經(jīng)常會選擇使用kill -9 pid的方式。強制進程退出,會帶來一些副作用,對應(yīng)用程序而言其效果等同于突然掉電,可能會導(dǎo)致如下一些問題:
緩存中的數(shù)據(jù)尚未持久化到磁盤中,導(dǎo)致數(shù)據(jù)丟失;
正在進行文件的write操作,沒有更新完成,突然退出,導(dǎo)致文件損壞;
線程池的任務(wù)隊列中尚有接收到的任務(wù)還沒來得及處理,導(dǎo)致任務(wù)丟失;
數(shù)據(jù)庫操作已經(jīng)完成,例如賬戶余額更新,準備返回應(yīng)答消息給客戶端時,消息尚在通信線程的發(fā)送隊列中排隊等待發(fā)送,進程強制退出導(dǎo)致應(yīng)答消息沒有返回給客戶端,客戶端發(fā)起超時重試,會帶來重復(fù)更新問題;
其它問題等…
這些問題都有可能對我們的業(yè)務(wù)產(chǎn)生影響,造成不必要的損失,為了避免這些問題,我們需要在JVM關(guān)閉時做些掃尾的工作,為此JVM提供了關(guān)閉鉤子(shutdown hooks)來做這些事情。本文探討了利用關(guān)閉鉤子的相關(guān)內(nèi)容。
首先,我們了解下哪些情況會導(dǎo)致JVM關(guān)閉。
對于強制關(guān)閉的幾種情況,系統(tǒng)關(guān)機,操作系統(tǒng)會通知JVM進程關(guān)閉并等待,一旦等待超時,系統(tǒng)會強制中止JVM進程;kill -9、Runtime.halt()、斷電、系統(tǒng)crash這些種方式會直接無商量中止JVM進程,JVM完全沒有執(zhí)行掃尾工作的機會。因此對用應(yīng)用程序而言,我們強烈不建議使用kill -9 這種暴力方式退出。
而對于正常關(guān)閉、異常關(guān)閉的幾種情況,JVM關(guān)閉前,都會調(diào)用已注冊的shutdown hooks,基于這種機制,我們可以將掃尾的工作放在shutdown hooks中,進而使我們的應(yīng)用程序安全的退出?;谄脚_通用性的考慮,我們更推薦應(yīng)用程序使用System.exit(0)這種方式退出JVM。
JVM 與 shutdown hooks 交互流程如下圖所示,可以對照源碼進一步的學(xué)習(xí)shutdown hooks工作原理。
對于tomcat類Web應(yīng)用,我們可以直接通過Runtime.addShutdownHook(Thread hook)注冊自定義鉤子,在鉤子中實現(xiàn)資源的清理;而對于worker類應(yīng)用,我們可以采用如下的方式安全的退出應(yīng)用。
信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一個中斷請求可以說是一樣的。通俗來講,信號就是進程間的一種異步通信機制。信號具有平臺相關(guān)性,Linux平臺支持的一些終止進程信號如下所示:
Windows平臺存在一些差異,它的一些信號舉例如下所示:
信號選擇:為了不干擾正常信號的運作,又能模擬Java異步通知,在Linux上我們需要先選定一種特殊的信號。通過查看信號列表上的描述,發(fā)現(xiàn) SIGUSR1 和 SIGUSR2 是允許用戶自定義的信號,我們可以選擇SIGUSR2,在Windows上我們可以選擇SIGINT。
通過這種信號機制,對應(yīng)用程序JVM發(fā)送特定信號,JVM可以感知并處理該信號,進而可以接受程序退出指令。
首先看下通用的JVM安全退出的流程圖:
第一步,應(yīng)用進程啟動的時候,初始化Signal實例,它的代碼示例如下:
其中Signal構(gòu)造函數(shù)的參數(shù)為String字符串,也就上文介紹的信號量名稱。
第二步,根據(jù)操作系統(tǒng)的名稱來獲取對應(yīng)的信號名稱,代碼如下:
判斷是否是windows操作系統(tǒng),如果是則選擇SIGINT,接收Ctrl+C中斷的指令;否則選擇USR2信號,接收SIGUSR2(等價于kill -12 pid)指令。
第三步,將實例化之后的SignalHandler注冊到JVM的Signal,一旦JVM進程接收到kill -12 或者 Ctrl+C則回調(diào)handle接口,代碼示例如下:
其中shutdownHandler實現(xiàn)了SignalHandler接口的handle(Signal sgin)方法,代碼示例如下:
第四步,在接收到信號回調(diào)的handle接口中,初始化JVM的ShutdownHook線程,并將其注冊到Runtime中,示例代碼如下:
第五步,接收到進程退出信號后,在回調(diào)的handle接口中執(zhí)行虛擬機的退出操作,示例代碼如下:
JVM退出時,底層會自動檢測用戶是否注冊了ShutdownHook任務(wù),如果有,則會自動執(zhí)行注冊鉤子的Run方法,應(yīng)用只需要在ShutdownHook中執(zhí)行掃尾工作即可,示例代碼如下:
通過以上的幾個步驟,我們可以輕松實現(xiàn)JVM的安全退出,另外,通常安全退出需要有超時控制機制,例如30S,如果到達超時時間仍然沒有完成退出,則由停機腳本直接調(diào)用kill -9強制退出。
關(guān)閉鉤子本質(zhì)上是一個線程(也稱為Hook線程),對于一個JVM中注冊的多個關(guān)閉鉤子它們將會并發(fā)執(zhí)行,所以JVM并不保證它們的執(zhí)行順序;由于是并發(fā)執(zhí)行的,那么很可能因為代碼不當導(dǎo)致出現(xiàn)競態(tài)條件或死鎖等問題,為了避免該問題,強烈建議在一個鉤子中執(zhí)行一系列操作。
Hook線程會延遲JVM的關(guān)閉時間,這就要求在編寫鉤子過程中必須要盡可能的減少Hook線程的執(zhí)行時間,避免hook線程中出現(xiàn)耗時的計算、等待用戶I/O等等操作。
關(guān)閉鉤子執(zhí)行過程中可能被強制打斷,比如在操作系統(tǒng)關(guān)機時,操作系統(tǒng)會等待進程停止,等待超時,進程仍未停止,操作系統(tǒng)會強制的殺死該進程,在這類情況下,關(guān)閉鉤子在執(zhí)行過程中被強制中止。
在關(guān)閉鉤子中,不能執(zhí)行注冊、移除鉤子的操作,JVM將關(guān)閉鉤子序列初始化完畢后,不允許再次添加或者移除已經(jīng)存在的鉤子,否則JVM拋出 IllegalStateException。
不能在鉤子調(diào)用System.exit(),否則卡住JVM的關(guān)閉過程,但是可以調(diào)用Runtime.halt()。
Hook線程中同樣會拋出異常,對于未捕捉的異常,線程的默認異常處理器處理該異常,不會影響其他hook線程以及JVM正常退出。
為了保障應(yīng)用重啟過程中異步操作的執(zhí)行,避免強制退出JVM可能產(chǎn)生的各種問題,我們可以采用關(guān)閉鉤子、自定義信號的方式,主動的通知JVM退出,并在JVM關(guān)閉前,執(zhí)行應(yīng)用程序的一些掃尾工作,進一步保證應(yīng)用程序可以安全的退出。
看完上述內(nèi)容,你們對JVM怎么實現(xiàn)安全退出有進一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。