稍微涉及技術細節,留以我設計中間件時參考,將來整理深度文檔時會抽取走,入門人員可以無視。
以下RocketMQ簡稱為RQ,理論部分采用版本為3.2.4,測試部分采用版本為3.2.6。
MQ的需求
我們對MQ的需求,相比JMS標准有幾點要求更高:
1. 必須優美靈活地支持集群消費。
2. 盡量支持消息堆積。
3. 服務高可用性和消息可靠性。
4. 有起碼的運維工具做集群管理和服務調整。
其他 提供順序消息、事務、回溯等面向特別場景的功能更好,目前暫不需要。
RQ架構
RQ的基本組成包括nameserver、broker、producer、consumer四種節點,前兩種構成服務端,后兩種在客戶端上。
還有其他輔助的進程,不提。
NameServer的基本概念
在沒有NameServer的中間件中,服務端集群就由干活的broker組成 ,其中的實例分主從兩種角色。那么客戶端就要知道,需要連接到哪個地址的broker上去做事情,於是客戶端就需要配置服務端機器的IP地址,如果服務端部署結構復雜,客戶端的配置結構也挺復雜,更討厭的是甚至可能需要客戶端也得更新地址配置。由於有了兩種思路的方案:
一是引入NameServer,負責提供地址。客戶端只需要知道NameServer機器的地址,需要找服務器干活的時候,先問NameServer我該去找哪個服務器。這樣,因為NameServer很簡單而不容易出故障,所以極少發生架構調整。而結構復雜的broker部分,無論怎么調整,客戶端都不用再操心。
RQ 2.x用的是Zookeeper做NameServer,3.x用的是自己搞的獨立服務,不知道為啥,不過代碼貌似不復雜。
二是引入反向代理,就是把服務端地址配置成虛擬IP或域名這種思路,這一個地址背后其實可能是一個集群,客戶端發起請求后,由網絡設施來中轉請求給具體服務器。
兩者各有優劣,綜合使用也挺正常。
NameServer的工作機制
I.NameServer自身之間的機制
可以啟動多個實例,相互獨立,不需要相互通信,可以理解為多機熱備。
II.NameServer與broker之間
Broker啟動后,向指定的一批NameServer發起長連接,此后每隔30s發送一次心跳,心跳內容中包含了所承載的topic信息;NameServer會每隔2分鍾掃描,如果2分鍾內無心跳,就主動斷開連接。
當然如果Broker掛掉,連接肯定也會斷開。
一旦連接斷開,因為是長連接,所以NameServer立刻就會感知有broker掛掉了,於是更新topic與broker的關系。但是,並不會主動通知客戶端。
III.NameServer與客戶端之間
客戶端啟動時,要指定這些NameServer的具體地址。之后隨機與其中一台NameServer保持長連接,如果該NameServer發生了不可用,那么會連接下一個。
連接后會定時查詢topic路由信息,默認間隔是30s,可配置,可編碼指定pollNameServerInteval。
(注意是定時的機制,不是即時查詢,也不是NameServer感知變更后推送,所以這里造成接收消息的實時性問題)。
NameServer的部署與應用
I. 運行NameServer
啟動: nohup mqnamesrv &
終止:
sh ./mqshutdown
Useage: mqshutdown broker | namesrv
II. Broker指定NameServer
nohup mqbroker -n
"192.168.36.53:9876;192.168.36.80:9876"
&
export NAMESRV_ADDR=
192.168.36.53
:
9876
;
192.168.36.80
:
9876
root
@rocketmq
-master1 bin]# sh mqbroker -m
namesrvAddr=
|
III.客戶端指定NameServer
有幾種方式:
1.編碼指定
producer.setNamesrvAddr(
"
192.168.36.53
:9876
;
192.168.36.80
:
9876
");
所以這里指定的並不是broker的主主或主從機器的地址,而是NameServer的地址。
2.java啟動參數
-Drocketmq.namesrv.addr=
192.168.36.53
:
9876
;
192.168.36.80
:
9876
export NAMESRV_ADDR=192.168.36.53
:
9876
;
192.168.36.80
:
9876
客戶端還可以配置這個域名jmenv.tbsite.alipay.net來尋址,就不用指定IP了,這樣NameServer集群可以做熱升級。
該接口具體地址是http://jmenv.tbsite.net:8080/rocketmq/nsaddr
(理論上,最后一種可用性最好;實際上,沒試出來。)
Broker的機制
I.消息的存儲
II.消息的清理
III.消息的消費
1.IO用的是文件內存映射方式,性能較高,只會有一個寫,其他的讀。順序寫,隨機讀。
2. 零拷貝原理:
以前使用linux 的sendfile 機制,利用DMA(優點是CPU不參與傳輸),將消息內容直接輸出到sokect 管道,大塊文件傳輸效率高。缺點是只能用BIO。
於是此版本使用的是mmap+write方式,代價是CPU多耗用一些,內存安全問題復雜一些,要避免JVM Crash。
IV.Topic管理
V.物理特性
1.CPU:Load高,但使用率低,因為大部分時間在IO Wait。
2. 內存:依舊需要大內存,否則swap會成為瓶頸。
3. 磁盤:IO密集,轉速越高、可靠性越高越好。
VI.broker之間的機制
單機的刷盤機制,雖然保障消息可靠性,但是存在單點故障影響服務可用性,於是有了HA的一些方式。
1.主從雙寫模式,在消息可靠性上依然很高,但是有小問題。
a.master宕機之后,客戶端會得到slave的地址繼續消費,但是不能發布消息。
b.客戶端在與NameServer直接網絡機制的延遲下,會發生一部分消息延遲,甚至要等到master恢復。
c.發現slave有消息堆積后,會令consumer從slave先取數據。
2 異步復制,消息可靠性上肯定小於主從雙寫
slave的線程不斷從master拉取commitLog的數據,然后異步構建出數據結構。類似mysql的機制。
VII.與consumer之間的機制
1.服務端隊列
topic的一個隊列只會被一個consumer消費,所以該consumer節點最好屬於一個集群。
那么也意味着,comsumer節點的數量>topic隊列的數量,多出來的那些comsumer會閑着沒事干。
舉簡單例子說明:
假設broker有2台機器,topic設置了4個隊列,那么一個broker機器上就承擔2個隊列。
此時消費者所屬的系統,有8台機器,那么運行之后,其中就只有4台機器連接到了MQ服務端的2台broker上,剩下的4台機器是不消費消息的。
所以,此時要想負載均衡,要把topic的分區數量設高。
2.可靠性
consumer與所有關聯的broker保持長連接(包括主從),每隔30s發送心跳,可配置,可以通過heartbeatBrokerInterval配置。
broker每隔10s掃描連接,發現2分鍾內沒有心跳,則關閉連接,並通知該consumer組內其他實例,過來繼續消費該topic。
當然,因為是長連接,所以consumer掛掉也會即時發生上述動作。所以,consumer集群的情況下,消費是可靠的。
而因為consumer與所有broker都持有連接,所以可以兩種角色都訂閱消息,規則由broker來自動決定(比如master掛了之后重啟,要先消費哪一台上的消息)。
3.本地隊列
consumer有線程不斷地從broker拉取消息到本地隊列中,消費線程異步消費。輪詢間隔可指定pullInterval參數,默認0;本地隊列大小可指定pullThresholdForQueue,默認1000。
而不論consumer消費多少個隊列,與一個broker只有一個連接,會有一個任務隊列來維護拉取隊列消息的任務。
4.消費進度上報
定時上報各個隊列的消費情況到broker上,時間間隔可設persistConsumerOffsetInterval。
上述采取的是DefaultMQPushConsumer類做的描述,可見所謂push模式還是定時拉取的,不是所猜測的服務端主動推送。不過拉取采用的是長輪詢的方式,實時性基本等同推送。
VIII.與producer的機制
1.可靠性
a.producer與broker的網絡機制,與consumer的相同。如果producer掛掉,broker會移除producer的信息,直到它重新連接。
b.producer發送消息失敗,最多可以重試3次,或者不超過10s的超時時間,此時間可通過sendMsgTimeout配置。如果發送失敗,輪轉到下一個broker。
c.producer也可以采用oneway的方式,只負責把數據寫入客戶端機器socket緩沖區。這樣可靠性較低,但是開銷大大減少。(適合采集小數據日志)
2.消息負載
發送消息給broker集群時,是輪流發送的,來保障隊列消息量平均。也可以自定義往哪一個隊列發送。
3.停用機制
當broker重啟的時候,可能導致此時消息發送失敗。於是有命令可以先停止寫權限,40s后producer便不會再把消息往這台broker上發送,從而可以重啟。
sh mqadmin wipeWritePerm -b brokerName -n namesrvAddr
IX.通信機制
1.組件采用的是Netty.4.0.9。
2.協議是他們自己定的新玩意,並不兼容JMS標准。協議具體內容有待我開發C#版客戶端時看詳情。
3.連接是可以復用的,通過header的opaque標示區分。
Broker的集群部署
一句話總結其特征就是:不支持主從自動切換、slave只能讀不能寫,所以故障后必須人工干預恢復負載。
集群方式
|
運維特點
|
消息可靠性(master宕機情況)
|
服務可用性(master宕機情況)
|
其他特點
|
備注
|
---|---|---|---|---|---|
一組主主 | 結構簡單,擴容方便,機器要求低 | 同步刷盤消息一條都不會丟 | 整體可用 未被消費的消息無法取得,影響實時性 |
性能最高 | 適合消息可靠性最高、實時性低的需求。 |
一組主從 | 異步有毫秒級丟失; 同步雙寫不丟失; |
差評,主備不能自動切換,且備機只能讀不能寫,會造成服務整體不可寫。 |
不考慮,除非自己提供主從切換的方案。 | ||
多組主從(異步復制) | 結構復雜,擴容方便 | 故障時會丟失消息; | 整體可用,實時性影響毫秒級別 該組服務只能讀不能寫 |
性能很高 | 適合消息可靠性中等,實時性中等的要求。 |
多組主從(同步雙寫) | 結構復雜,擴容方便 | 不丟消息 | 整體可用,不影響實時性 該組服務只能讀不能寫。 不能自動切換? |
性能比異步低10%,所以實時性也並不比異步方式太高。 |
適合消息可靠性略高,實時性中等、性能要求不高的需求。 |
第四種的官方介紹上,比第三種多說了一句:“不支持主從自動切換”。這句話讓我很恐慌,因為第三種也是不支持的,干嘛第四種偏偏多說這一句,難道可用性上比第三種差?
於是做了實驗,證明第三種和第四種可用性是一模一樣的。那么不支持主從切換是什么意思?推斷編寫者是這個意圖:
因為是主從雙寫的,所以數據一致性非常高,那么master掛了之后,slave本是可以立刻切換為主的,這一點與異步復制不一樣。異步復制並沒有這么高的一致性,所以這一句話並不是提醒,而是一個后續功能的備注,可以在雙寫的架構上繼續發展出自動主從切換的功能。
架構測試總結:
1.其實根本不用糾結,高要求首選同步雙寫,低要求選主主方案。
2.最好不用一個機器上部署多個broker實例。端口容易沖突,根源問題還沒掌握。
所以,建議采用多台機器,一台起一個broker,構成同步雙寫的架構。也就是官方提供的這種物理和邏輯架構。
注意幾個特征:
a.客戶端是先從NameServer尋址的,得到可用Broker的IP和端口信息,然后自己去連接broker。
b.生產者與所有的master連接,但不能向slave寫入;而消費者與master和slave都建有連接,在不同場景有不同的消費規則。
c.NameServer不去連接別的機器,不主動推消息。
客戶端的概念
1.Producer Group
Producer實例的集合。
Producer實例可以是多機器、但機器多進程、單進程中的多對象。Producer可以發送多個Topic。
處理分布式事務時,也需要Producer集群提高可靠性。
2.Consumer Group
Consumer實例 的集合。
Consumer 實例可以是多機器、但機器多進程、單進程中的多對象。
同一個Group中的實例,在集群模式下,以均攤的方式消費;在廣播模式下,每個實例都全部消費。
3.Push Consumer
應用通常向Consumer對象注冊一個Listener接口,一旦收到消息,Consumer對象立刻回調Listener接口方法。所以,所謂Push指的是客戶端內部的回調機制,並不是與服務端之間的機制。
4.Pull Consumer
應用通常主動調用Consumer從服務端拉消息,然后處理。這用的就是短輪詢方式了,在不同情況下,與長輪詢各有優點。
發布者和消費者類庫另有文檔,不提。
重要問題總結:
1.客戶端選擇推還是拉,其實考慮的是長輪詢和短輪詢的適用場景。
2.服務端首選同步雙寫架構,但依然可能造成故障后30s的消息實時性問題(客戶端機制決定的)。
3.Topic管理,需要先調查客戶端集群機器的數目,合理設置隊列數量之后,再上線。