我是3y,一年CRUD經驗用十年的markdown程式員???????常年被譽為職業八股文選手
好幾天沒更新austin的系列文章啦,主要是一直在寫austin的代碼,而這篇文章我想了很久標題,最后定為《優雅,不過時》,文章的內容主要由以下部分組成:
- 應用發布重啟了怎么辦?記憶體資料不是丟失了嗎?
- 什么是優雅停機?如何實作優雅停機?
- 如何優雅地調整執行緒池的引數?
如果你的專案遇到了類似的問題,也可以借鑒下我今天所講解的內容,讀完我相信你肯定會有些識訓,
01、應用發布重啟了怎么辦
眾所周知,如果我們系統在運行的程序中,記憶體資料沒存盤起來那就會導致丟失,對于austin專案而言,就會使訊息丟失,并且無法下發到用戶上,
這個在我講述完我是如何設計「發送訊息消費端」以及「讀取檔案」時,尤其問得比較多,為了部分沒有追更的讀者,我再簡單講述下我這邊的設計:

在austin-handler模塊,每個渠道的每種訊息型別我都用到了執行緒池進行隔離而消費:

在austin-cron模塊,我讀取檔案是把每一條記錄放至了單執行緒池做LazyPending,目的為了延遲消費做批量下發,

敏感的技術人看到記憶體佇列或執行緒池(執行緒池也需要指定對應的記憶體佇列)就很正常地想:記憶體佇列可能的size為1024,而服務器在重啟的時候可能記憶體佇列的資料還沒消費完,此時你怎么辦?資料就丟了嗎?
我們使用執行緒池/記憶體佇列在很多場景下都是為了提高吞吐量,有得就必有失,至于重啟服務器導致記憶體資料的丟失,就看你評估對自己的業務帶來多少的影響了,
針對這種問題,austin本身就開發好了相關的功能作為「補充」,通過實時計算引擎flink的能力可以實時在后臺查看訊息下發的情況:

可以在離線hive找到訊息下發失敗的userId(離線這塊暫未實作),輸入具體的receiverId 可以查看實時下發時失敗的原因

查明原因之后再通過csv檔案上傳的做補發,

不過,這是平臺提供做補發的能力,從技術上的角度,還有別的思路盡量避免執行緒池或者記憶體佇列的資料因重啟而丟失的資料嗎?有的,優雅關閉執行緒池
02、優雅停機
所謂「優雅停機」就是關閉的時候先將自己需要處理的內容處理完了,之后才關閉,如果你直接kill -9,是沒有「優雅」這一說法的,神仙都救不了,
1、在網路層:TCP有四次揮手、TCP KeepAlive、HTTP KeepAlive 讓連接 優雅地關閉,避免很多報錯,
2、在Java里邊通過Runtime.getRuntime().addShutdownHook()注冊事件,當虛擬機關閉的前呼叫該方法的具體邏輯進行善后,
3、在Spring里邊執行了ApplicationContext 的close之后,只要我們Bean配置了destroy策略,那Spring在關閉之前也會先執行我們的已實作好的destroy方法
4、在Tomcat容器提供了幾種關閉的姿勢,先暫停請求,選擇等待N秒才完全關閉容器,
5、在Java執行緒池提供了shutdown和shutdownNow供我們關閉執行緒,顯然shutdown是優雅關閉執行緒池的方法,
我們的austin專案是基于SpringBoot環境構造的,所以我們可以重度依賴SpringBoot進行優雅停機,
1、我們設定應用服務器的停機模式為graceful
server.shutdown=graceful
2、在austin已經引入動態執行緒池而非使用Spring管理下的ThreadPoolTaskExecutor,所以我們可以把自己創建出來的執行緒池在Spring關閉的時候,進行優雅shutdown(想要關閉其他的資源時,也可以類似干這種操作)

注:如果是使用Spring封裝過的執行緒池ThreadPoolTaskExecutor,默認就會優雅關閉,因為它是實作了DisposableBean介面的

03、如何優雅地調整執行緒池的引數?
austin在整個專案里邊,還是有挺多地方是用到了執行緒池,特別重要的是從MQ里消費所創建的執行緒池,

有小伙伴當時給過建議:有沒有打算引入動態執行緒池,不用發布就調整執行緒池的引數從而臨時提高消費能力,順便在這給大家推薦美團的執行緒池文章:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html,如果沒讀過這篇文章的,建議都去讀下,挺不錯的,
美團這篇文章講述了動態執行緒池的思路,但應該是未官方開源,所以有很多小伙伴基于文章的思路造了好用的輪子,比如 Hippo4J 和dynamic-tp 都是比較優秀的輪子了,
這兩個倉庫我都看了下原始碼, Hippo4J 有無依賴中間件實作動態執行緒池,也有默認實作Nacos和Apollo的版本,并有著管理后臺,而dynamic-tp 默認實作依賴Nacos或Apollo,大佬們的代碼都寫得很不錯,我推薦大家都可以去學學,
我在最初的時候接的是dynamic-tp的代碼,因為我本身austin就接入了Apollo,也感覺暫時不太需要管理后臺,后來 Hippo4J 作者找我聊了下,希望我能接入Hippo4J,
我按照我目前的使用場景對著代碼看了一把,我是需要通過在創建執行緒池后再動態調參的場景,于是跟 Hippo4J 作者反饋了下,他果斷說晚上或明天就給我實作(:恐怖如斯,太肝了


不過,周三我反饋完,周四晚上我差不多就將 dynamic-tp 快接入完了,我目前現在打算先跑著(畢竟切換API其實也是需要時間成本的),后續看有沒有遇到痛點或者空的時候再遷移到Hippo4J再體驗體驗
也不為別的,就看中龍臺大佬比我還肝(自己提出的場景,開源作者能很快地反饋并實作,太強了,絲毫不擔心有大坑要我自己搞)
04、總結
對于austin而言,正常的重啟發布我們通過優雅停機來盡可能減少系統的處理資料時的丟失,如果訊息是真的非常重要而且需要做補發,在austin中也可以通過上傳檔案的方式再做補發,且能看到實時推送的資料鏈路統計和某個用戶下發訊息失敗的原因,
我相信,這已經能覆寫線上絕大多數的場景了,
或許后續也可以針對某些場景在消費端做exactly once + 冪等 來解決kill -9的窘境,但要知道的是:想要保證資料不丟失、不重復發送給用戶,一定會帶來性能的損耗,這是需要做平衡的,
在專案很少使用執行緒池之前,一直可能認為執行緒池的相關面試題就是八股文,但當你專案系統真的遇到執行緒池優雅關閉的問題、執行緒池引數動態調整的問題,你就會發現之前看的內容其實是很有意義的,
阿,原來可以設定引數讓核心執行緒數也會回收的(之前一直都沒有注意過呢)
阿,原來都大多數框架都有提供對應的擴展介面給我們監聽關閉,默認的實作都有優雅停機的機制咯,之前一直都不知道呢,
....
austin還在持續優化和更新中,歡迎大佬們給點意見和想法一起討論,對該專案感興趣的同學也可以到我的GitHub上逛逛,或許有可能這個季度的KPI就有了咯,
動態執行緒池的倉庫地址:
- Hippo4J:https://github.com/acmenlt/dynamic-threadpool
- DynamicTp:https://gitee.com/yanhom/dynamic-tp
都看到這里了,點個贊一點都不過分吧?我是3y,下期見,

關注我的微信公眾號【Java3y】除了技術我還會聊點日常,有些話只能悄悄說~ 【對線面試官+從零撰寫Java專案】 持續高強度更新中!求star!!原創不易!!求三連!!
austin專案原始碼Gitee鏈接:gitee.com/austin
austin專案原始碼GitHub鏈接:github.com/austin
更多的文章可往:文章的目錄導航轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/463443.html
標籤:Java
下一篇:自己寫的第一個java專案!
