稍微涉及技術細節,留以我設計中間件時參考,未來整理深度文檔時會抽取走,入門人員能夠無視。java
如下RocketMQ簡稱爲RQ,理論部分採用版本爲3.2.4,測試部分採用版本爲3.2.6。mysql
MQ的需求
咱們對MQ的需求,相比JMS標準有幾點要求更高:linux
1. 必須優美靈活地支持集羣消費。sql
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管理,須要先調查客戶端集羣機器的數目,合理設置隊列數量以後,再上線。