本文將深入剖析rocketmq為什么選擇自己開發(fā)NameServer,而不是選擇類似于ZK這樣的開源組件。同時對rocketmq的路由注冊、路由發(fā)現(xiàn)、路由剔除進行剖析。并通過結(jié)合核心源碼,對筆者的觀點進行驗證。同時對不同類型消息的重試機制,以及客戶端選擇nameserver的策略進行深入講解。
文章第一部分是name server在rocketmq整體架構(gòu)中的作用,熟悉的同學可以直接跳過。
Name Server 是專為 RocketMQ 設(shè)計的輕量級名稱服務(wù),具有簡單、可集群橫吐擴展、無狀態(tài),節(jié)點之間互不通信等特點。整個Rocketmq集群的工作原理如下圖所示:
可以看到,Broker集群、Producer集群、Consumer集群都需要與NameServer集群進行通信:
Broker集群
Broker用于接收生產(chǎn)者發(fā)送消息,或者消費者消費消息的請求。一個Broker集群由多組Master/Slave組成,Master可寫可讀,Slave只可以讀,Master將寫入的數(shù)據(jù)同步給Slave。每個Broker節(jié)點,在啟動時,都會遍歷NameServer列表,與每個NameServer建立長連接,注冊自己的信息,之后定時上報。
Producer集群
消息的生產(chǎn)者,通過NameServer集群獲得Topic的路由信息,包括Topic下面有哪些Queue,這些Queue分布在哪些Broker上等。Producer只會將消息發(fā)送到Master節(jié)點上,因此只需要與Master節(jié)點建立連接。
Consumer集群
消息的消費者,通過NameServer集群獲得Topic的路由信息,連接到對應(yīng)的Broker上消費消息。注意,由于Master和Slave都可以讀取消息,因此Consumer會與Master和Slave都建立連接。
目前可以作為服務(wù)發(fā)現(xiàn)組件有很多,如etcd、consul,zookeeper等:
那么為什么rocketmq選擇自己開發(fā)一個NameServer,而不是使用這些開源組件呢?特別的,Zookeeper其提供了Master選舉、分布式鎖、數(shù)據(jù)的發(fā)布和訂閱等諸多功能RocketMQ設(shè)計之初時參考的另一款消息中間件Kafka就使用了Zookeeper。
事實上,在RocketMQ的早期版本,即MetaQ 1.x和MetaQ 2.x階段,也是依賴Zookeeper的。但MetaQ 3.x(即RocketMQ)卻去掉了ZooKeeper依賴,轉(zhuǎn)而采用自己的NameServer。
而RocketMQ的架構(gòu)設(shè)計決定了只需要一個輕量級的元數(shù)據(jù)服務(wù)器就足夠了,只需要保持最終一致,而不需要Zookeeper這樣的強一致性解決方案,不需要再依賴另一個中間件,從而減少整體維護成本。
敏銳的同學肯定已經(jīng)意識到了,根據(jù)CAP理論,RocketMQ在名稱服務(wù)這個模塊的設(shè)計上選擇了AP,而不是CP:
一致性(Consistency):Name Server 集群中的多個實例,彼此之間是不通信的,這意味著某一時刻,不同實例上維護的元數(shù)據(jù)可能是不同的,客戶端獲取到的數(shù)據(jù)也可能是不一致的。
可用性(Availability):只要不是所有NameServer節(jié)點都掛掉,且某個節(jié)點可以在指定之間內(nèi)響應(yīng)客戶端即可。
事實上,除了RocketMQ開發(fā)了自己的NameServer,最近 Kafka 社區(qū)也在 Wiki 空間上提交了一項新的改進提案“KIP-500: Replace ZooKeeper with a Self-Managed Metadata Quorum”,其目的是為了消除 Kafka 對 ZooKeeper 的依賴,該提案建議用自管理的元數(shù)據(jù)仲裁機制替換原來的 ZooKeeper 組件。感興趣的讀者可以自行查閱相關(guān)資料。
NameServer作為一個名稱服務(wù),需要提供服務(wù)注冊、服務(wù)剔除、服務(wù)發(fā)現(xiàn)這些基本功能,但是NameServer節(jié)點之間并不通信,在某個時刻各個節(jié)點數(shù)據(jù)可能不一致的情況下,如何保證客戶端可以最終拿到正確的數(shù)據(jù)。下面分別從路由注冊、路由剔除,路由發(fā)現(xiàn)三個角度進行介紹。
3.1?路由注冊
對于Zookeeper、Etcd這樣強一致性組件,數(shù)據(jù)只要寫到主節(jié)點,內(nèi)部會通過狀態(tài)機將數(shù)據(jù)復(fù)制到其他節(jié)點,Zookeeper使用的是Zab協(xié)議,etcd使用的是raft協(xié)議。
但是NameServer節(jié)點之間是互不通信的,無法進行數(shù)據(jù)復(fù)制。RocketMQ采取的策略是,在Broker節(jié)點在啟動的時候,輪訓NameServer列表,與每個NameServer節(jié)點建立長連接,發(fā)起注冊請求。NameServer內(nèi)部會維護一個Broker表,用來動態(tài)存儲Broker的信息。
同時,Broker節(jié)點為了證明自己是存活的,會將最新的信息上報給NameServer,然后每隔30秒向NameServer發(fā)送心跳包,心跳包中包含 BrokerId、Broker地址、Broker名稱、Broker所屬集群名稱等等,然后NameServer接收到心跳包后,會更新時間戳,記錄這個Broker的最新存活時間。
NameServer在處理心跳包的時候,存在多個Broker同時操作一張Broker表,為了防止并發(fā)修改Broker表導致不安全,路由注冊操作引入了ReadWriteLock讀寫鎖,這個設(shè)計亮點允許多個消息生產(chǎn)者并發(fā)讀,保證了消息發(fā)送時的高并發(fā),但是同一時刻NameServer只能處理一個Broker心跳包,多個心跳包串行處理。這也是讀寫鎖的經(jīng)典使用場景,即讀多寫少。
3.2?路由剔除
正常情況下,如果Broker關(guān)閉,則會與NameServer斷開長連接,Netty的通道關(guān)閉監(jiān)聽器會監(jiān)聽到連接斷開事件,然后會將這個Broker信息剔除掉。
異常情況下,NameServer中有一個定時任務(wù),每隔10秒掃描一下Broker表,如果某個Broker的心跳包最新時間戳距離當前時間超多120秒,也會判定Broker失效并將其移除。
特別的,對于一些日常運維工作,例如:Broker升級,RocketMQ提供了一種優(yōu)雅剔除路由信息的方式。如在升級一個節(jié)Master點之前,可以先通過命令行工具禁止這個Broker的寫權(quán)限,生產(chǎn)者發(fā)送到這個Broker的請求,都會收到一個NO_PERMISSION響應(yīng),之后會自動重試其他的Broker。當觀察到這個broker沒有流量后,再將這個broker移除。
3.3?路由發(fā)現(xiàn)
路由發(fā)現(xiàn)是客戶端的行為,這里的客戶端主要說的是生產(chǎn)者和消費者。具體來說:
對于生產(chǎn)者,可以發(fā)送消息到多個Topic,因此一般是在發(fā)送第一條消息時,才會根據(jù)Topic獲取從NameServer獲取路由信息。
那么生產(chǎn)者/消費者在工作的過程中,如果路由信息發(fā)生了變化怎么處理呢?如:Broker集群新增了節(jié)點,節(jié)點宕機或者Queue的數(shù)量發(fā)生了變化。細心的讀者注意到,前面講解NameServer在路由注冊或者路由剔除過程中,并不會主動推送會客戶端的,這意味著,需要由客戶端拉取主題的最新路由信息。
事實上,RocketMQ客戶端提供了定時拉取Topic最新路由信息的機制,這里我們直接結(jié)合源碼來講解。?? ??? ?
DefaultMQProducer和DefaultMQConsumer有一個pollNameServerInterval配置項,用于定時從NameServer并獲取最新的路由表,默認是30秒,它們底層都依賴一個MQClientInstance類。
MQClientInstance類中有一個updateTopicRouteInfoFromNameServer方法,用于根據(jù)指定的拉取時間間隔,周期性的的從NameServer拉取路由信息。?在拉取時,會把當前啟動的Producer和Consumer需要使用到的Topic列表放到一個集合中,逐個從NameServer進行更新。以下源碼截圖展示了這個過程:
然而定時拉取,還不能解決所有的問題。因為客戶端默認是每隔30秒會定時請求NameServer并獲取最新的路由表,意味著客戶端獲取路由信息總是會有30秒的延時。這就帶來一個嚴重的問題,客戶端無法實時感知Broker服務(wù)器的宕機。如果生產(chǎn)者和消費者在這30秒內(nèi),依然會向這個宕機的broker發(fā)送或消費消息呢?
這個問題,可以通過客戶端重試機制來解決。
在講解生產(chǎn)者重試機制之前,我們必須先對三種消息類型:普通消息、普通有序消息、嚴格有序消息進行介紹。因為RocketMQ客戶端的生產(chǎn)者重試機制,只會普通消息有作用。對于普通有序消息、嚴格有序消息是沒有作用。目前網(wǎng)上絕大部分文章對此并沒有進行區(qū)分,導致參考了這些文章的同學誤以為自己的消息發(fā)送失敗會自動進行重試,然而事實上可能根本沒有進行重試。三種消息的類型介紹如下:
普通消息:消息是無序的,任意發(fā)送發(fā)送哪一個隊列都可以。
普通有序消息:同一類消息(例如某個用戶的消息)總是發(fā)送到同一個隊列,在異常情況下,也可以發(fā)送到其他隊列。
對于這三種類型的消息,RocketMQ對應(yīng)的提供了對應(yīng)的方法來分別消息,例如同步發(fā)送(異步/批量/oneway也是類似):
需要注意的是:這些方法重載形式,本意是為了支持以上三種不同的消息類型。但是你不按套路出牌,例如:對于一個用戶的多條消息,在調(diào)用第一種send方法形式時,依然在對于同一個用戶每次發(fā)送消息時,選擇了不同的隊列(MessageQueue),那么也沒有人能阻止。我只能說,你忽略了RocketMQ團隊設(shè)計這三個方法的意圖。
對于普通消息,消息發(fā)送默認采用round-robin機制來選擇發(fā)送到哪一個隊列,如果發(fā)送失敗,默認重試2次。由于之前發(fā)送失敗的Queue必然位于某個Broker上,在重試過程中,這個失敗的Broker上的Queue都不會選擇,這里主要是考慮,既然發(fā)送到這個Broker上某個Queue失敗了,那么發(fā)送到這個Broker上的Queue失敗的可能性依然很大,所以選擇其他Broker。
但是一定會這樣嗎?例如Broker集群只是由一組Master/Slave組成,發(fā)送消息只會選擇Master,如果這個Master失敗了,沒有其他Master可選,此時已然會選擇這個Master上的其他Queue。
在實際生產(chǎn)環(huán)境中,通常Broker集群至少由2組Master/Slave組成,甚至更多,例如我司就是3主3從。這樣就可以很好的利用RocketMQ對于普通消息發(fā)送的重試機制,每次重試到不同的Broker上。
從源碼層面來看,對于普通消息,RocketMQ選擇隊列默認是通過MQFaultStrategy#selectOneMessageQueue來選擇一個的隊列,在未開啟延遲容錯的情況下,內(nèi)部會調(diào)用TopicPublishInfo#selectOneMessageQueue方法,這個方法源碼體了前面說的重試邏輯:
事情到這里并沒有結(jié)束,這段代碼只是單次發(fā)送消息失敗重試選擇隊列的邏輯。實際情況可能是,在Broker宕機期間,可能會發(fā)送多條消息,那么每次都可能會選擇到失敗的Broker上的Queue,然后再重試,盡管重試可能會成功,但是每次發(fā)送消息的耗時會增加。因此,MQFaultStrategy實際上還提供了以下兩個功能(超出本文范疇,將會后續(xù)其他文章中講解):
失敗隔離:即發(fā)送消息到某個broker失敗之后,將其進行隔離,優(yōu)先從其他正常的broker中進行選擇
? ?對于無序消息,通過這種異常重試機制,就可以保證消息發(fā)送的高可用了。同時由于不需要NameServer通知眾多不固定的生產(chǎn)者,也降低了NameServer實現(xiàn)的復(fù)雜性。
既然重試機制有這么明顯的好處,那么對于普通有序消息,和嚴格有序消息,rocketmq為什么默認不進行重試呢?答案很簡單,這些消息只能發(fā)送某個特定的Broker上的某個特定的Queue中,如果發(fā)送失敗,重試失敗的可能依然很大,所以默認不進行重試。如果需要重試,需要業(yè)務(wù)方自己來做。
首先說明,對于普通有序消息,RocketMQ是不會進行重試的。如果需要重試,那么業(yè)務(wù)RD同學需要自己編寫重試代碼,例如通過一個for循環(huán),最多重試幾次。這里主要說明:對于普通有序消息,在異常情況下,如何經(jīng)歷短暫無序之后再恢復(fù)有序。從MessageQueueSelector源碼來尋找答案:
可以看到,這個接口到的select方法接收一個List<MessageQueue>類型參數(shù),也就是當前Topic下的隊列集合。這個接口由業(yè)務(wù)RD實現(xiàn),生產(chǎn)者客戶端在發(fā)送消息之前會回調(diào)這個接口。
正常情況下的有序
業(yè)務(wù)RD在實現(xiàn)這個接口時,為了保證消息的有序??梢圆扇∫恍┎呗?,例如:發(fā)送的是一個用戶的消息,先計算pos=user_id%mqs.size(),之后mqs.get(pos)獲得對應(yīng)的隊列。因此在正常情況下,一個用戶的消息總是有序的。
異常情況下的短暫無序
在異常情況下,例如一個Broker宕機,路由信息刷新后,這個Broker上隊列就會從List集合中移除。此時按照相同的方式選擇隊列,就會選擇到其他隊列上,造成了無序。但是這個無序是很短暫的,因為之后同一個用戶的信息,都會發(fā)送到同一個新的隊列上。如果宕機的broker恢復(fù)了,那么再次經(jīng)歷一下短暫無序,之后又變得有序了。
對于嚴格有序消息,由于直接指定了一個MessageQueue。如果這個MessageQueue所在的Broker宕機了,那么之后的重試必然都失敗,只有無限重試,直到成功。因此,非必要的情況下,是不建議使用嚴格有序消息的。
前面講解了客戶端在獲取路由信息時,每次都會嘗試先從緩存的路由表中查找Topic路由信息,如果找不到,那么就去NameServer更新嘗試。下面介紹一下客戶端NameServer節(jié)點的選擇策略。
RocketMQ會將用戶設(shè)置的NameServer列表會設(shè)置到NettyRemotingClient類的namesrvAddrList字段中,NettyRemotingClient是RocketMQ對Netty進行了封裝,如下:
private final AtomicReference<List<String>> namesrvAddrList = new AtomicReference<List<String>>();
具體選擇哪個NameServer,也是使用round-robin的策略。需要注意的是,盡管使用round-robin策略,但是在選擇了一個NameServer節(jié)點之后,后面總是會優(yōu)先選擇這個NameServer,除非與這個NameServer節(jié)點通信出現(xiàn)異常的情況下,才會選擇其他節(jié)點。
為什么客戶端不與所有NameServer節(jié)點建立連接呢,而是只選擇其中一個?筆者考慮,通常NameServer節(jié)點是固定的幾個,但是客戶端的數(shù)量可能是成百上千,為了減少每個NameServer節(jié)點的壓力,所以每個客戶端節(jié)點只隨機與其中一個NameServer節(jié)點建立連接。
為了盡可能保證NameServer集群每個節(jié)點的負載均衡,在round-robin策略選擇時,每個客戶端的初始隨機位置都不同,如下:
private?final?AtomicInteger?namesrvIndex?=?new?AtomicInteger(initValueIndex());
其中initValueIndex()就是計算一個隨機值,之后每次選擇NameServer時,namesrvIndex+1之后,對namesrvAddrList取模,計算在數(shù)據(jù)下標的位置,嘗試創(chuàng)建連接,一旦創(chuàng)建成功,會將當前選擇的NameServer地址記錄到namesrvAddrChoosed字段中:
private final AtomicReference<String> namesrvAddrChoosed = new AtomicReference<String>();
如果某個NameServer節(jié)點創(chuàng)建連接失敗是,會自動重試其他節(jié)點。具體可參見:getAndCreateNameserverChannel最后留一個筆者沒有想明白的問題,選擇NameServer的初始隨機位置是 ?initValueIndex()方法,這個方法的實現(xiàn)如下:
private static int initValueIndex() {
Random r = new Random();
return Math.abs(r.nextInt() % 999) % 999;
}
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
新聞名稱:RocketMQNameServer深入剖析-創(chuàng)新互聯(lián)
網(wǎng)址分享:http://jinyejixie.com/article30/dhdsso.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營銷型網(wǎng)站建設(shè)、搜索引擎優(yōu)化、微信公眾號、網(wǎng)站策劃、ChatGPT、手機網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容