從RocketMQ我們學到了什麼之NameServer

開場圖.png
序
在很久很久以前,人們之間的通訊方式就是面對面交談,你說一句,我聽一句,雖然簡單可靠,但是弊端也很大。
比如,當你成為一個軍隊的首領,每個屬下一有情況就立刻向你彙報,一個還好,但當你的屬下有幾十個幾百個的時候,他們每天不分時間不看場合,都在嘰嘰喳喳和你彙報情況,那你可能什麼都聽不到,而且腦袋都要炸掉了。這個時候,你說停,都給我停下,要彙報情況的,去門口排隊,一個一個的來,這個就叫做流量削峰,一群人不要一擁而上,都乖乖給我排隊去。
然後你就一個接一個的聽,聽了整整24個小時,實在困的不行,尋思著這樣不行呀,如此下去可能就要天妒英才了,於是你又說,來人,發筆和紙,都把要彙報的訊息寫在紙上,寫完後告訴呂秀才,然後聽呂秀才的指示,沿著屋裡右面牆根,按照指示的位置疊放整齊,彙報的人就可以退下該做啥做啥去吧,等我休息一下,再來看你們的彙報內容,這就叫做非同步處理,你終於可以由自己掌控訊息獲取的進度了,美滋滋的去睡覺了。
而彙報的人把內容寫在紙上,疊放好,就可以退下自己做自己該做的事情,而不是一直在門口等待彙報,這個就叫做解耦。
削峰,非同步,解耦。這就是訊息佇列最常用的三大場景。
故事中的下屬們,就是訊息生產者角色,屋子右面牆根那塊地就是訊息持久化,呂秀才就是訊息排程中心,而你就是訊息消費者角色。下屬們彙報的訊息,應該疊放在哪裡,這個訊息又應該在哪裡才能找到,全靠呂秀才的驚人記憶力,才可以讓訊息準確的被投放以及消費。
訊息排程中心是今天的主角
在RocketMQ裡,就有一個角色和呂秀才的作用一樣,叫做NameServer,它是整個分散式訊息排程的總控制,是RocketMQ的靈魂之所在,倘如沒有了它,RocketMQ會分崩離析無法工作。
那麼,它是怎麼工作的呢?
我們先來看一張RocketMQ屋裡架構圖:

RocketMQ物理架構圖.png
亂如蜘蛛絲?不要害怕,換句話說,先忘掉這張圖吧。
我們來類比一下現實生活,有一個人想要給另外一個人寄快件,那麼就需要先由這個人在網上查詢有哪些郵局,然後選擇其中一個郵局,把快件投遞給它,再由郵局配送到目標人。

比擬圖.png
需要完成這一整個業務流程,首先需要將郵局自身的資訊註冊到衛星網路上,衛星負責資訊的排程,這樣發件人就知道有哪些郵局可以選擇,收件人通過衛星網路知道快件到了哪個郵局,可以聯絡郵局溝通適合的配送時間,而郵局則負責接收配送儲存快件。
類比RocketMQ簡線圖就是如下:

簡線圖.png
Producer:訊息⽣產者,⽤於向訊息伺服器傳送訊息,就是圖中的寄件人。
NameServer:路由註冊中⼼,就是圖中的衛星。
Broker:訊息儲存伺服器,就是圖中的郵局。
Consumer:訊息消費者,不是今天的重點,圖中未標出,就是收件人。
由此可見,NameServer作為分散式訊息佇列的協調者,具有資訊路由註冊與發現的作用。
路由註冊
郵局在竣工後,需要與衛星聯網,將自己納入衛星網路管理中,這樣就相當於對外宣佈,我這個郵局開始運營了,可以收發郵件快遞了。
郵局併網之後,如何讓衛星持續並及時感知這個郵局線上以及郵局自身資訊的調整,使衛星可以隨時協調這個郵局呢?這個時候就需要郵局定時向衛星發一條資訊:
“嗶嗶嗶————我是郵局C,編號SHC,地址XXXXX,歸屬中國上海叢集,線上,此時此刻2019年3月15日13點21秒”
衛星接收到訊息後,拿個小本本記錄下來:
“郵局B,BJB,北京,2019年3月15日13點10秒,活著...”
“郵局A,BJA,北京,2019年3月15日13點15秒,活著...”
“郵局C,SHC,上海,2019年3月15日13點21秒,活著...”
......

路由註冊.png
上面這個故事,就講述了NameServer路由註冊的基本原理。
NameServer就相當於衛星,內部會維護一個Broker表,用來動態儲存Broker的資訊。
而Broker就相當於郵局,在啟動的時候,會先遍歷NameServer列表,依次發起註冊請求,保持長連線,然後每隔30秒向NameServer傳送心跳包,心跳包中包含BrokerId、Broker地址、Broker名稱、Broker所屬叢集名稱等等,然後NameServer接收到心跳包後,會更新時間戳,記錄這個Broker的最新存活時間。
NameServer在處理心跳包的時候,存在多個Broker同時操作一張Broker表,為了防止併發修改Broker表導致不安全,路由註冊操作引入了ReadWriteLock讀寫鎖,這個設計亮點允許多個訊息生產者併發讀,保證了訊息傳送時的高併發,但是同一時刻NameServer只能處理一個Broker心跳包,多個心跳包序列處理。這也是讀寫鎖的經典使用場景,即讀多寫少。
路由剔除

路由剔除.png
忽然有一天,郵局C的機房進老鼠了,咬斷電源線宕機了,而衛星不知道郵局C業務故障了,依舊將帶有郵局C的郵局表資訊傳給寄件人(生產者),寄件人聯絡郵局C傳送快件,但是郵局C機房宕機,業務暫停,處於癱瘓狀態,自然也就無法接收快件了。
另一方面,因為快件未能被郵局C收入,也就無法將快件轉交給收件人,顧客們久久等不到自己的快件,紛紛投訴,為此郵局C的管理層備受責難。
於是郵政總局技術部開始研究討論,怎麼讓衛星可以感知到郵局“失聯了”,從而自動排除故障郵局,將其負責的業務交付給其他正常的郵局處理,這樣就不會因為某一個郵局出現問題,而導致這個郵局所管轄的部分業務無法處理。
大家眾說紛紜,最後敲定了一個方案,讓衛星每隔一段時間掃描郵局資訊表,如果發現某個郵局上報資訊時間與當時掃描時間之間的差值超過了某個預設的閾值,就判定這個郵局“失聯了”,將此郵局資訊從郵局表中剔除。這樣寄件人查詢到的郵局表裡都是正常營業的郵局資訊。
新功能上線運營後,效果不錯,大家再也不用擔心因為某個郵局故障而導致業務停滯,又過上了泡茶報紙的生活。
這個故事同樣在RocketMQ中上演。
正常情況下,如果Broker關閉,則會與NameServer斷開長連線,Netty的通道關閉監聽器會監聽到連線斷開事件,然後會將這個Broker資訊剔除掉。
異常情況下,NameServer中有一個定時任務,每隔10秒掃描一下Broker表,如果某個Broker的心跳包最新時間戳距離當前時間超多120秒,也會判定Broker失效並將其移除。
細心的人會發現一個問題,NameServer在清除失活Broker之後,並沒有主動通知生產者,生產者每隔30秒會請求NameServer並獲取最新的路由表,那麼就意味著,訊息生產者總會有30秒的延時,無法實時感知Broker伺服器的宕機。所以在這個30秒裡,生產者依舊會向失活Broker傳送訊息,那麼訊息傳送的高可用性如何保證呢?
要解決這個問題得首先談一談Broker的負載策略,訊息傳送佇列預設採用輪詢機制,訊息傳送時預設選擇異常重試機制來保證訊息傳送的高可用。當Broker宕機後,雖然訊息傳送者無法第一時間感知Broker 宕機,但是當訊息生產者向Broker傳送訊息返回異常後,訊息生產者會選擇另外一個Broker上的訊息佇列,這樣就規避了發生故障的Broker,結合重試機制,巧妙實現訊息傳送的高可用,同時由於不需要NameServer通知眾多不固定的生產者,也降低了NameServer實現的複雜性。
在降低NameServer實現複雜性方面,還有一個設計亮點就是NameServer之間是彼此獨立無交流的,也就是說NameServer伺服器之間在某個時刻的資料並不會完全相同,但是異常重試機制使得這種差異不會造成任何影響。

NameServer之間不聯絡.png
路由發現
天上的衛星是有限的,不易變的,而地上的寄件人是繁多的,易變的。所以寄件人想要知道有哪些郵局,很明顯最適合的方式是向衛星發請求,拉取郵局表資訊,而不是等衛星給每個人推送。
所以在RocketMQ中,NameServer是不主動推送會客戶端的,而是由客戶端拉取主題的最新路由資訊。

路由發現.png
CAP理論
NameServer作為註冊和發現中心,是整個分散式訊息佇列排程的靈魂,談及到分散式,就逃不開CAP理論,C是Consistency,A是Availability,P是Partiton Tolerance,對於分散式架構,網路條件不可控,出現網路分割槽是不可避免的,因此必須具備分割槽容錯性,那麼NameServer就是在AP還是CP中選擇了,由於NameServer之間相互獨立,很明顯,是一個AP設計。
之所以替換掉Zookeeper
ZooKeeper為分散式應用程式提供協調服務。那為什麼RocketMQ要自己造輪子,開發叢集的管理程式呢?因為ZooKeeper的功能很強大,包括自動Master選舉等,RocketMQ的架構設計決定了它不需要進行Master選舉,用不到這些複雜的功能,只需要一個輕量級的元資料伺服器就足夠了。
中介軟體對穩定性要求很高,RocketMQ的NameServer只有很少的程式碼,容易維護,所以不需要再依賴另一箇中間件,從而減少整體維護成本。
學到了什麼?
1.長連線程式設計模型⾥⼼跳的實現原理
2.多執行緒程式設計中讀寫鎖的經典使⽤⽅式
3.追求簡單⾼效⼜可靠的實現⽅式
說在後面的話
想要研究NameServer原始碼的,請點選連結: https://github.com/MrChiu/RocketMQ-Study/tree/release-4.3.2/namesrv
裡面附有我標註的註釋,易於通讀程式碼

微信公眾號二維碼.png