微信公衆號:IT一刻鐘 大型現實非嚴肅主義現場,一刻鐘與你分享優質技術架構與見聞,作一個有劇情的程序員git
在好久好久之前,人們之間的通訊方式就是面對面交談,你說一句,我聽一句,雖然簡單可靠,可是弊端也很大。程序員
好比,當你成爲一個軍隊的首領,每一個屬下一有狀況就馬上向你彙報,一個還好,但當你的屬下有幾十個幾百個的時候,他們天天不分時間不看場合,都在嘰嘰喳喳和你彙報狀況,那你可能什麼都聽不到,並且腦殼都要炸掉了。這個時候,你說停,都給我停下,要彙報狀況的,去門口排隊,一個一個的來,這個就叫作流量削峯,一羣人不要蜂擁而上,都乖乖給我排隊去。github
而後你就一個接一個的聽,聽了整整24個小時,實在困的不行,尋思着這樣不行呀,如此下去可能就要天妒英才了,因而你又說,來人,發筆和紙,都把要彙報的消息寫在紙上,寫完後告訴呂秀才,而後聽呂秀才的指示,沿着屋裏右面牆根,按照指示的位置疊放整齊,彙報的人就能夠退下該作啥作啥去吧,等我休息一下,再來看大家的彙報內容,這就叫作異步處理,你終於能夠由本身掌控消息獲取的進度了,美滋滋的去睡覺了。編程
而彙報的人把內容寫在紙上,疊放好,就能夠退下本身作本身該作的事情,而不是一直在門口等待彙報,這個就叫作解耦。安全
削峯,異步,解耦。這就是消息隊列最經常使用的三大場景。服務器
故事中的下屬們,就是消息生產者角色,屋子右面牆根那塊地就是消息持久化,呂秀才就是消息調度中心,而你就是消息消費者角色。下屬們彙報的消息,應該疊放在哪裏,這個消息又應該在哪裏才能找到,全靠呂秀才的驚人記憶力,纔可讓消息準確的被投放以及消費。微信
在RocketMQ裏,就有一個角色和呂秀才的做用同樣,叫作NameServer,它是整個分佈式消息調度的總控制,是RocketMQ的靈魂之所在,倘如沒有了它,RocketMQ會分崩離析沒法工做。 那麼,它是怎麼工做的呢? 咱們先來看一張RocketMQ屋裏架構圖: 亂如蜘蛛絲?不要懼怕,換句話說,先忘掉這張圖吧。網絡
咱們來類比一下現實生活,有一我的想要給另一我的寄快件,那麼就須要先由這我的在網上查詢有哪些郵局,而後選擇其中一個郵局,把快件投遞給它,再由郵局配送到目標人。 多線程
須要完成這一整個業務流程,首先須要將郵局自身的信息註冊到衛星網絡上,衛星負責信息的調度,這樣發件人就知道有哪些郵局能夠選擇,收件人經過衛星網絡知道快件到了哪一個郵局,能夠聯繫郵局溝通適合的配送時間,而郵局則負責接收配送存儲快件。 類比RocketMQ簡線圖就是以下: 架構
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秒,活着...」
......
上面這個故事,就講述了NameServer路由註冊的基本原理。
NameServer就至關於衛星,內部會維護一個Broker表,用來動態存儲Broker的信息。 而Broker就至關於郵局,在啓動的時候,會先遍歷NameServer列表,依次發起註冊請求,保持長鏈接,而後每隔30秒向NameServer發送心跳包,心跳包中包含BrokerId、Broker地址、Broker名稱、Broker所屬集羣名稱等等,而後NameServer接收到心跳包後,會更新時間戳,記錄這個Broker的最新存活時間。
NameServer在處理心跳包的時候,存在多個Broker同時操做一張Broker表,爲了防止併發修改Broker表致使不安全,路由註冊操做引入了ReadWriteLock讀寫鎖,這個設計亮點容許多個消息生產者併發讀,保證了消息發送時的高併發,可是同一時刻NameServer只能處理一個Broker心跳包,多個心跳包串行處理。這也是讀寫鎖的經典使用場景,即讀多寫少。
突然有一天,郵局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服務器之間在某個時刻的數據並不會徹底相同,可是異常重試機制使得這種差別不會形成任何影響。
天上的衛星是有限的,不易變的,而地上的寄件人是繁多的,易變的。因此寄件人想要知道有哪些郵局,很明顯最適合的方式是向衛星發請求,拉取郵局表信息,而不是等衛星給每一個人推送。 因此在RocketMQ中,NameServer是不主動推送會客戶端的,而是由客戶端拉取主題的最新路由信息。
NameServer做爲註冊和發現中心,是整個分佈式消息隊列調度的靈魂,談及到分佈式,就逃不開CAP理論,C是Consistency,A是Availability,P是Partiton Tolerance,對於分佈式架構,網絡條件不可控,出現網絡分區是不可避免的,所以必須具有分區容錯性,那麼NameServer就是在AP仍是CP中選擇了,因爲NameServer之間相互獨立,很明顯,是一個AP設計。
ZooKeeper爲分佈式應用程序提供協調服務。那爲何RocketMQ要本身造輪子,開發集羣的管理程序呢?
由於ZooKeeper的功能很強大,包括自動Master選舉等,RocketMQ的架構設計決定了它不須要進行Master選舉,用不到這些複雜的功能,只須要一個輕量級的元數據服務器就足夠了。 中間件對穩定性要求很高,RocketMQ的NameServer只有不多的代碼,容易維護,因此不須要再依賴另外一箇中間件,從而減小總體維護成本。
1.長鏈接編程模型⾥⼼跳的實現原理
2.多線程編程中讀寫鎖的經典使⽤⽅式
3.追求簡單⾼效⼜可靠的實現⽅式
想要研究NameServer源代碼的,請點擊連接:https://github.com/MrChiu/RocketMQ-Study/tree/release-4.3.2/namesrv
裏面附有我標註的註釋,易於通讀代碼