rocketmq優雅停機往事

捉蟲大師發表於2021-10-25

1

時間追溯到2018年12月的某一天夜晚,那天我正準備上線一個需求完就回家,剛點下發布按鈕,告警就響起,我擦,難道回不了家了?看著報錯量只有一兩個,斷定只是偶發,穩住不要慌。

把剩下的機器發完,又出現了幾個同樣的錯誤,作為一名優(鹹)秀(魚)程式設計師,這種問題必須追查到底。

image

2

嫻熟地查詢到報錯日誌

org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is com.alibaba.druid.pool.DataSourceClosedException: dataSource already closed

看著異常資訊,陷入了沉思

image

  • 表面上看報錯是因為使用了已經關閉的資料來源
  • 資料來源什麼時候會關閉呢?只有程式被殺死的時候
  • 莫非是應用關閉時不夠平滑?釋出時會先摘除流量的呀,應該不至於呀

天色已經很晚,漫無目的地拖動日誌,疲憊地尋找新線索,突然報錯日誌中一個單詞引入眼簾:rocketmq

精神抖擻,大概知道原因了,這應用中還有個兢兢業業的rocketmq consumer一直在消費訊息,在應用關閉時,外部流量被摘除了,但沒人通知rocketmq consumer,於是它拋異常了。

image

3

出於我對rocketmq不深刻甚至有點膚淺的理解,它的消費採用ack的方式,如果報錯,訊息稍後還會重試,不會丟訊息,而且如果消費程式碼是冪等的,也不會有業務上的異常,總之這不重要,因為它也不是我寫的程式碼。

瞅了一眼consumer的程式碼(這裡就不貼程式碼了,反正貼了你也不會看),consumer註冊了一個ShutdownHook,ShutdownHook裡consumer執行了shutdown來優雅地退出,並且給這個shutdownThread設定了最高優先順序,然而從實踐看來,這個執行緒最高優先順序並沒有什麼卵用。

而且從《ShutdownHook原理》這篇文章中也知道ShutdownHook是併發執行的,spring容器關閉也是一個ShutdownHook,他們之前沒有先後順序。

瞭解原因後,第一時間想到了類似dubbo摘流的方案,吭哧吭哧寫了個優雅關閉rocketmq cosnumer的介面,在應用關閉指令碼的kill之前呼叫該介面,完美解決問題,趕緊下班回家,不然要猝死了。

image

4

夜裡入睡,夢到老闆讓我把所有的系統都改造掉,嚇得我一機靈。

於是第二天又重新思考這個問題,總覺得在應用裡實現一個介面並在stop指令碼中去呼叫是一件非常不優雅的事,更重要的是這也沒法複製到其他專案,我又陷入了沉思。

既然是spring容器關閉時bean的銷燬順序導致的問題,那麼能不能利用spring的depend-on把順序理順了?說幹就幹。

起初我遇到是這樣的依賴關係:

image

手把手在xml的每個bean中把depend-on關係都配上,似乎也起到了作用。

但當我開啟第二個專案時,它的bean之間的依賴關係大致如下:

image

好傢伙,26個字母差點不夠用,當時我的心情是這樣的

image

所以我覺得以當前的速度,改造完所有專案可能都到9102年了。

5

又過了一段時間,在github交友網站上突然看到了rocketmq官方實現的spring-boot-starter,於是點進去看了它的實現。好傢伙,看完直呼666。

官方starter實現了spring的SmartLifecycle介面,它的start方法能在所有bean初始化完成後被呼叫,stop方法會在bean被銷燬前呼叫,對rocketmq consumer來說簡直完美。

順便還複習了一下spring容器的關閉,程式碼在AbstractApplicationContext的doClose方法,這裡我總結成一幅圖:

image

通過上圖能看到,銷燬bean之前,有關閉lifecycle bean和傳送ContextClosedEvent兩個動作,官方starter選擇了實現LifeCycle介面的方式。

6

到這裡我該給老闆彙報去了,之所以rocketmq consumer釋出時不平滑是我們的使用姿勢問題,雖然對業務沒影響,但不優雅,解決方案有兩個,老闆你選吧:

  • 全都換成官方starter,依賴spring-boot,官方維護,改造成本很高,
  • 監聽ContextClosedEvent來實現優雅關閉,這塊可以封裝一下,讓業務方引入依賴即可

image


都看到這了,不點個關注嗎?

image

相關文章