从生产故障解锁RocketMQ集群部署的最佳实践
1、故障描述
RocketMQ 集群采取的部署架構為2主2從,其部署架構如下圖所示:
其部署架構中一個非常明顯的特點是一臺物理機上分別部署了 nameserver,broker 兩個進程。
其中一臺機器(192.168.3.100)的內存出現(xiàn)故障,導致機器重啟,但Linux操作系統(tǒng)由于重啟需要自檢等因素,整個重啟過程竟然持續(xù)了將近10分鐘,客戶端的發(fā)送超時持續(xù)10分鐘,這顯然是不能接受的!!!
RocketMQ的高可用設計何在?接下來我們將詳細介紹其分析過程。
2、故障分析
當?shù)弥慌_機器故障導致消息發(fā)送超時持續(xù)10分鐘,我的第一反應是不應該呀,因為 RocketMQ 集群是分布式部署架構,天然支持故障發(fā)現(xiàn)與故障恢復,消息發(fā)送客戶端能自動感知 Broker 異常的的時間絕對不會超過10分鐘,那故障又是怎么發(fā)生的呢?
首先我們來回顧一下RocketMQ的路由注冊與發(fā)現(xiàn)機制。
2.1 RocketMQ路由注冊與剔除機制
其路由注冊、剔除機制說明如下:
集群中所有Broker每隔30s向集群中所有的NameServer發(fā)送心跳包,注冊Topic路由信息。
NameServer在收到Broker端的心跳包時首先會更新路由表,并記錄收到心跳包的時間。
NameServer啟動一個定時任務每10s掃描Broker存活狀態(tài)表,如果Nameserver 連續(xù)120s未收到Broker的心跳包,將判定該Broker已下線,從路由表中將該Broker移除。
如果Nameserver與Broker端的長連接斷開,NameServer能立即感知Broker下線并從路由表中將該Broker移除。
消息客戶端(消息發(fā)送者、消息消費者)在任意時刻只會和其中一臺NameServer建立連接,并每隔30s向NameServer查詢路由信息,如果查詢到結果會更新客戶端的本地路由信息;如果查詢路由失敗,則忽略。
從上述路由注冊、剔除機制來看,當一臺Broker服務器宕機,消息發(fā)送者感知路由信息發(fā)生變化需要的時間是多長呢?
分如下兩種情況分別討論:
NameServer與Broker服務器TCP連接斷開,此時NameServer能立即感知路由信息變化,將其從路由表中移除,從而消息發(fā)送端應該在30s左右就能感知路由發(fā)送變化,在此30s內在發(fā)送端會出現(xiàn)消息發(fā)送失敗,但結合發(fā)送規(guī)避機制,并不會對發(fā)送方帶來重大故障,可接受。
如果NameServer與Broker服務器的TCP連接未斷開,但Broker已無法提供服務(例如假死),此時NameServer需要120s才能感知Broker宕機,此時消息發(fā)送端最多需要150s才能感知其路由信息的變化。
但問題來了,為什么一臺Broker由于內存故障重啟,10分鐘后業(yè)務才恢復,即客戶端才真正感知Broker宕機呢?
既然出現(xiàn)了,我們就需要對其進行分析,給出解決方案,避免不會在生產環(huán)境出現(xiàn)同類型的錯誤。
2.2 故障排查經過
查詢客戶端的日志(/home/{user}/logs/rocketmqlogs/rocketmq_client.log),從中可以看到從客戶端第一次報消息發(fā)送超時的時間是14:44,其日志輸出如下:
由于192.168.3.100機器內存故障,故首先去查看該集群中其他nameserver中的日志,看正常機器中的NameServer感知broker-a故障的時長,其日志如下所示:
從中可以看出192.138.3.101的nameserver基本在2分鐘左右才感知其宕機,即雖然機器在重啟,但可能由于操作系統(tǒng)要做硬件自檢等其他原因,TCP連接并未斷開,故nameserver在120s后才感知其宕機,從路由信息表中將該broker移除,那按照路由剔除機制,客戶端應該在150秒的時間內感知其變化,那為什么沒感知呢?
繼續(xù)查看客戶端路由信息,查看客戶端感知路由信息發(fā)生變化的時間點,如下圖所示:
從客戶端日志來看,客戶端在14:53:46才感知其變化,這又是為什么呢?
原來客戶端在更新路由信息時報超時異常,其截圖如下所示::
從發(fā)生故障到故障恢復期間,客戶端一直嘗試從已發(fā)生故障的NameServer去更新路由信息,但一直返回超時,這樣就導致了客戶端一直無法獲取最新的路由信息,故一直無法感知已宕機的Broker。
從日志分析來看,到目前來說就比較明朗了,客戶端之所有沒有在120s之內感知其路由信息的變化,是因為客戶端一直嘗試從已宕機的nameserver去更新路由信息,但由于一直無法請求成功,故客戶端的緩存路由信息一直無法得到更新,造成了上面的現(xiàn)象
那問題來了,按照我們對RocketMQ的認識,NameServer宕機,客戶端會自動去從nameserver列表中選擇下一個nameserver,那為什么這里并沒有發(fā)生nameserver切換,而是等到14:53才切換呢?
接下來我們將目光投向NameServer的切換代碼,其代碼片段如下圖所示:
上圖中的幾個關鍵分析如下:
客戶端從緩存中選用連接用于發(fā)送RPC請求的前提條件是連接的的isActive方法返回true,即底層TCP連接處于激活狀態(tài)。
在客戶端向服務端發(fā)起RPC請求時,如果出現(xiàn)非超時類異常,會執(zhí)行closeChannel方法,該方法會關閉連接并從連接緩存表中移除,這個非常關鍵,因為在切換NameServer時如果緩存中存在連接并連接處于激活狀態(tài),就不會切換nameserver。
如果發(fā)送RPC超時,rocketmq會根據clientCloseSocketIfTimeout參數(shù)來決定是否關閉連接,但遺憾的是該參數(shù)默認為false,并且并未提供修改的入口。
那問題分析到這里,已經非常明了。
由于機器內存故障觸發(fā)重啟并且需要自檢等因素,造成nameserver,broker無法再處理請求但底層TCP連接并未斷開,超時后返回,但客戶端并不會關閉與故障機器nameserver的TCP連接,不會觸發(fā)切換NameServer,等到機器重新啟動成功后,TCP連接斷開,故障機器重啟完成后感知路由信息變化,故障恢復。
根本原因:nameserver的假死導致路由信息無法更新。
3、最佳實踐
經過上面的故障,個人覺得nameserver不應該與broker部署在一起,如果nameserver 與 broker 并不部署在一起,上面的問題能得到有效避免,其部署架構如下圖所示:
這樣的部署架構如果面對上面的場景,即出現(xiàn)Broker假死的情況,能有效避免嗎?答案是可以的。
如果 192.168.3.100 的 broker 假死,那么 3.110,3.111 的 nameserver 都能在2分鐘內感知 broker-a 宕機,客戶端能從nameserver處獲得最新的路由信息,從而在消息發(fā)送時不會繼續(xù)向宕機Broker繼續(xù)發(fā)送消息,故障恢復;
如果nameserver假死,出現(xiàn)超時錯誤,只要broker不宕機,則通過緩存,還是能正常工作的,但如果nanmeserver,broker一起假死,則上述架構還是無法規(guī)避上面的問題。
故本次的最佳實踐主要包含如下兩個舉措:
1、nameserver與broker一定要分開部署,進行隔離。
2、nameserver與客戶端的連接,應該在超時后,關閉連接,觸發(fā)nameserver漂移,需要修改源碼。
有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
總結
以上是生活随笔為你收集整理的从生产故障解锁RocketMQ集群部署的最佳实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CompletableFuture 实现
- 下一篇: 垃圾邮件分类实战(SVM)