metaq原理簡介

_吹雪_發表於2018-10-09

1. 前言

本文件旨在描述RocketMQ的多個關鍵特性的實現原理,並對訊息中介軟體遇到的各種問題進行總結,闡述RocketMQ如何解決這些問題。文中主要引用了JMS規範與CORBA Notification規範,規範為我們設計系統指明瞭方向,但是仍有不少問題規範沒有提及,對於訊息中介軟體又至關重要。RocketMQ並不遵循任何規範,但是參考了各種規範與同類產品的設計思想。

產品發展歷史

大約經歷了三個主要版本迭代
一、Metaq(Metamorphosis)1.x
由開源社群killme2008維護,開源社群非常活躍。
https://github.com/killme2008/Metamorphosis
二、Metaq 2.x
於2012年10月份上線,在淘寶內部被廣泛使用。
三、RocketMQ 3.x
基於公司內部開源共建原則,RocketMQ專案只維護核心功能,且去除了所有其他執行時依賴,核心功能最簡化。 每個BU的個性化需求都在RocketMQ專案之上進行深度定製。RocketMQ向其他BU提供的僅僅是Jar包,例如要定製一個Broker,那麼只需要依賴rocketmq-broker這個jar包即可,可通過API進行互動,如果定製client,則依賴rocketmq-client這個jar包,對其提供的api進行再封裝。開源社群地址:
https://github.com/alibaba/RocketMQ在RocketMQ專案基礎上衍生的專案如下

  • com.taobao.metaq v3.0 = RocketMQ + 淘寶個性化需求
    為淘寶應用提供訊息服務
  • com.alipay.zpullmsg v1.0 = RocketMQ + 支付寶個性化需求
    為支付寶應用提供訊息服務
  • com.alibaba.commonmq v1.0 = Notify + RocketMQ + B2B個性化需求
    為B2B應用提供訊息服務

3. 專業術語

  • Producer
    訊息生產者,負責產生訊息,一般由業務系統負責產生訊息。
  • Consumer
    訊息消費者,負責消費訊息,一般是後臺系統負責非同步消費。
  • Push Consumer
    Consumer的一種,應用通常向Consumer物件註冊一個Listener介面,一旦收到訊息,Consumer物件立刻回撥Listener介面方法。
  • Pull Consumer
    Consumer的一種,應用通常主動呼叫Consumer的拉訊息方法從Broker拉訊息,主動權由應用控制。
  • ProducerGroup
    一類Producer的集合名稱,這類Producer通常傳送一類訊息,且傳送邏輯一致。
  • Consumer Group
    一類Consumer的集合名稱,這類Consumer通常消費一類訊息,且消費邏輯一致。
  • Broker
    訊息中轉角色,負責儲存訊息,轉發訊息,一般也稱為Server。 在JMS規範中稱為Provider。
  • 廣播消費
    一條訊息被多個Consumer消費,即使這些Consumer屬於同一個Consumer Group,訊息也會被Consumer Group中的每個Consumer都消費一次,廣播消費中的Consumer Group概念可以認為在訊息劃分方面無意義。
    在CORBA Notification規範中,消費方式都屬於廣播消費。
    在JMS規範中,相當於JMS publish/subscribe model
  • 叢集消費
    一個Consumer Group中的Consumer例項平均分攤消費訊息。例如某個Topic有9條訊息,其中一個Consumer Group有3個例項(可能是3個程式,或者3臺機器),那麼每個例項只消費其中的3條訊息。
    在CORBA Notification規範中,無此消費方式。
    在JMS規範中,JMS point-to-point model與之類似,但是RocketMQ的叢集消費功能大等於PTP模型。因為RocketMQ單個Consumer Group內的消費者類似於PTP,但是一個Topic/Queue可以被多個Consumer Group消費。
  • 順序訊息
    消費訊息的順序要同傳送訊息的順序一致,在RocketMQ中,主要指的是區域性順序,即一類訊息為滿足順序性,必須Producer單執行緒順序傳送,且傳送到同一個佇列(這就是原理),這樣Consumer就可以按照Producer傳送的順序去消費訊息。
  • 普通順序訊息
    順序訊息的一種,正常情況下可以保證完全的順序訊息,但是一旦發生通訊異常, Broker重啟,由於佇列總數發生變化,雜湊取模後定位的佇列會變化,產生短暫的訊息順序不一致。 如果業務能容忍在叢集異常情況(如某個Broker當機或者重啟)下,訊息短暫的亂序,使用普通順序方式比較合適。
  • 嚴格順序訊息
    順序訊息的一種, 無論正常異常情況都能保證順序,但是犧牲了分散式Failover特性,即Broker叢集中只要有一臺機器不可用,則整個叢集都不可用,服務可用性大大降低。
    如果伺服器部署為同步雙寫模式,此缺陷可通過備機自動切換為主避免,不過仍然會存在幾分鐘的服務不可用。(依賴同步雙寫,主備自動切換,自動切換功能目前還未實現)
    目前已知的應用只有資料庫binlog同步強依賴嚴格順序訊息,其他應用絕大部分都可以容忍短暫亂序,推薦使用普通的順序訊息。
  • Message Queue
    在RocketMQ中,所有訊息佇列都是持久化,長度無限的資料結構,所謂長度無限是指 佇列中的每個儲存單元都是定長,訪問其中的儲存單元使用Offset來訪問,offset為java long型別,64位,理論上在100年內不會溢位,所以認為是長度無限, 另外佇列中 只儲存最近幾天的資料, 之前的資料會按照過期時間來刪除。
    也可以認為Message Queue是一個長度無限的陣列,offset就是下標。

4. 訊息中介軟體需要解決哪些問題?

本節闡述訊息中介軟體通常需要解決哪些問題,在解決這些問題當中會遇到什麼困難,RocketMQ是否可以解決,規範中如何定義這些問題。

4.1 Publish/Subscribe

釋出訂閱是訊息中介軟體的最基本功能,也是相對於傳統RPC通訊而言。在此不再詳述。

4.2 Message Priority

規範中描述的優先順序是指在一個訊息佇列中,每條訊息都有不同的優先順序,一般用整數來描述,優先順序高的訊息先投遞,如果訊息完全在一個記憶體佇列中,那麼在投遞前可以按照優先順序排序,令優先順序高的先投遞。
由於RocketMQ所有訊息都是持久化的,所以如果按照優先順序來排序,開銷會非常大,因此RocketMQ沒有特意支援訊息優先順序,但是可以通過變通的方式實現類似功能, 即單獨配置一個優先順序高的佇列,和一個普通優先順序的佇列,將不同優先順序傳送到不同佇列即可。
對於優先順序問題,可以歸納為2類
1)只要達到優先順序目的即可,不是嚴格意義上的優先順序,通常將優先順序劃分為高、中、低,或者再多幾個級別。每個優先順序可以用不同的topic表示,發訊息時,指定不同的topic來表示優先順序,這種方式可以解決絕大部分的優先順序問題,但是對業務的優先順序精確性做了妥協。
2)嚴格的優先順序,優先順序用整數表示,例如0 ~ 65535,這種優先順序問題一般使用不同topic解決就非常不合適。如果要讓MQ解決此問題,會對MQ的效能造成非常大的影響。這裡要確保一點,業務上是否確實需要這種嚴格的優先順序,如果將優先順序壓縮成幾個,對業務的影響有多大?

4.3 Message Order

訊息有序指的是一類訊息消費時,能按照傳送的順序來消費。例如:一個訂單產生了3條訊息,分別是訂單建立,訂單付款,訂單完成。消費時,要按照這個順序消費才能有意義。但是同時訂單之間是可以並行消費的。
RocketMQ可以嚴格的保證訊息有序。原理是?

4.4 Message Filter

  • Broker端訊息過濾
    在Broker中,按照Consumer的要求做過濾,優點是減少了對於Consumer無用訊息的網路傳輸。缺點是增加了Broker的負擔,實現相對複雜。
    (1).淘寶Notify支援多種過濾方式,包含直接按照訊息型別過濾,靈活的語法表示式過濾,幾乎可以滿足最苛刻的過濾需求。
    (2). 淘寶RocketMQ支援按照簡單的Message Tag過濾,也支援按照Message Header、body進行過濾。
    (3).CORBA Notification規範中也支援靈活的語法表示式過濾。
  • Consumer端訊息過濾
    這種過濾方式可由應用完全自定義實現,但是缺點是很多無用的訊息要傳輸到Consumer端。

4.5 Message Persistence

訊息中介軟體通常採用的幾種持久化方式:
(1).持久化到資料庫,例如Mysql。
(2).持久化到KV儲存,例如levelDB、伯克利DB等KV儲存系統。
(3). 檔案記錄形式持久化,例如Kafka,RocketMQ
(4).對記憶體資料做一個持久化映象,例如beanstalkd,VisiNotify
(1)、(2)、(3)三種持久化方式都具有將記憶體佇列Buffer進行擴充套件的能力,(4)只是一個記憶體的映象,作用是當Broker掛掉重啟後仍然能將之前記憶體的資料恢復出來。
JMS與CORBA Notification規範沒有明確說明如何持久化,但是持久化部分的效能直接決定了整個訊息中介軟體的效能。
RocketMQ參考了Kafka的持久化方式,充分利用Linux檔案系統記憶體cache來提高效能。

4.6 Message Reliablity

影響訊息可靠性的幾種情況:
(1).Broker正常關閉
(2).Broker異常Crash
(3).OS Crash
(4).機器掉電,但是能立即恢復供電情況。
(5).機器無法開機(可能是cpu、主機板、記憶體等關鍵裝置損壞)
(6).磁碟裝置損壞。
(1)、(2)、(3)、(4)四種情況都屬於硬體資源可立即恢復情況,RocketMQ在這四種情況下能保證訊息不丟,或者丟失少量資料(依賴刷盤方式是同步還是非同步)。
(5)、(6)屬於單點故障,且無法恢復,一旦發生,在此單點上的訊息全部丟失。RocketMQ在這兩種情況下,通過非同步複製,可保證99%的訊息不丟,但是仍然會有極少量的訊息可能丟失。通過同步雙寫技術可以完全避免單點,同步雙寫勢必會影響效能,適合對訊息可靠性要求極高的場合,例如與Money相關的應用。
RocketMQ從3.0版本開始支援同步雙寫。

4.7 Low Latency Messaging

在訊息不堆積情況下,訊息到達Broker後,能立刻到達Consumer。
RocketMQ使用長輪詢Pull方式,可保證訊息非常實時,訊息實時性不低於Push。

4.8 At least Once

是指每個訊息必須投遞一次
RocketMQ Consumer 先 pull 訊息到本地,消費完成後,才向伺服器返回 ack,如果沒有消費一定不會 ack 訊息, 所以 RocketMQ 可以很好的支援此特性。

4.9 Exactly Only Once

(1). 傳送訊息階段,不允許傳送重複的訊息。
(2). 消費訊息階段,不允許消費重複的訊息。

只有以上兩個條件都滿足情況下,才能認為訊息是“Exactly Only Once”,而要實現以上兩點,在分散式系統環 境下,不可避免要產生巨大的開銷。所以 RocketMQ 為了追求高效能,並不保證此特性,要求在業務上進行去重, 也就是說消費訊息要做到冪等性。RocketMQ 雖然不能嚴格保證不重複,但是正常情況下很少會出現重複傳送、消費情況,只有網路異常,Consumer 啟停等異常情況下會出現訊息重複。

此問題的本質原因是網路呼叫存在不確定性,即既不成功也不失敗的第三種狀態,所以才產生了訊息重複性問題。

4.10 Broker 的 Buffer 滿了怎麼辦?

Broker 的 Buffer 通常指的是 Broker 中一個佇列的記憶體 Buffer 大小,這類 Buffer 通常大小有限,如果 Buffer 滿 了以後怎麼辦?

下面是 CORBA Notification 規範中處理方式:
(1). RejectNewEvents
拒絕新來的訊息,向 Producer 返回 RejectNewEvents 錯誤碼。

(2). 按照特定策略丟棄已有訊息
a) AnyOrder - Any event may be discarded on overflow. This is the default setting for this property.
b) FifoOrder - The first event received will be the first discarded.
c) LifoOrder - The last event received will be the first discarded.
d) PriorityOrder - Events should be discarded in priority order, such that lower priority

RocketMQ 沒有記憶體 Buffer 概念,RocketMQ 的佇列都是持久化磁碟,資料定期清除。

對於此問題的解決思路,RocketMQ 同其他 MQ 有非常顯著的區別,RocketMQ 的記憶體 Buffer 抽象成一個無限長度的佇列,不管有多少資料進來都能裝得下,這個無限是有前提的, Broker 會定期刪除過期的資料,例如 Broker 只儲存 3 天的訊息,那麼這個 Buffer 雖然長度無限,但是 3 天前的資料會被從隊尾刪除。

4.11 回溯消費

回溯消費是指 Consumer 已經消費成功的訊息,由於業務上需求需要重新消費, 要支援此功能,Broker 在向 Consumer 投遞成功訊息後,訊息仍然需要保留。並且重新消費一般是按照時間維度,例如由於 Consumer 系統故障, 恢復後需要重新消費 1 小時前的資料,那麼 Broker 要提供一種機制,可以按照時間維度來回退消費進度。

RocketMQ 支援按照時間回溯消費,時間維度精確到毫秒,可以向前回溯,也可以向後回溯。

4.12 訊息堆積

訊息中介軟體的主要功能是非同步解耦,還有個重要功能是擋住前端的資料洪峰,保證後端系統的穩定性, 這就要求訊息中介軟體具有一定的訊息堆積能力,訊息堆積分以下兩種情況:

(1). 訊息堆積在記憶體 Buffer,一旦超過記憶體 Buffer,可以根據一定的丟棄策略來丟棄訊息,如 CORBA Notification 規範中描述。適合能容忍丟棄訊息的業務,這種情況訊息的堆積能力主要在於記憶體 Buffer 大小,而且訊息 堆積後,效能下降不會太大,因為記憶體中資料多少對於對外提供的訪問能力影響有限。

(2). 訊息堆積到持久化儲存系統中,例如 DB,KV 儲存,檔案記錄形式。

當訊息不能在記憶體 Cache 命中時,要不可避免的訪問磁碟,會產生大量讀 IO,讀 IO 的吞吐量直接決定了 訊息堆積後的訪問能力。

評估訊息堆積能力主要有以下四點:
(1). 訊息能堆積多少條,多少位元組?即訊息的堆積容量。
(2). 訊息堆積後,發訊息的吞吐量大小,是否會受堆積影響?
(3). 訊息堆積後,正常消費的 Consumer 是否會受影響?
(4). 訊息堆積後,訪問堆積在磁碟的訊息時,吞吐量有多大?

4.13 分散式事務

已知的幾個分散式事務規範,如 XA,JTA 等。其中 XA 規範被各大資料庫廠商廣泛支援,如 Oracle,Mysql 等。 其中 XA 的 TM 實現佼佼者如 Oracle Tuxedo,在金融、電信等領域被廣泛應用。

分散式事務涉及到兩階段提交問題,在資料儲存方面的方面必然需要 KV 儲存的支援,因為第二階段的提交回滾需要修改訊息狀態,一定涉及到根據 Key 去查詢 Message 的動作。RocketMQ 在第二階段繞過了根據 Key 去查詢 Message 的問題, 採用第一階段傳送 Prepared 訊息時,拿到了訊息的 Offset,第二階段通過 Offset 去訪問訊息, 並修改狀態,Offset 就是資料的地址。

RocketMQ 這種實現事務方式,沒有通過 KV 儲存做,而是通過 Offset 方式,存在一個顯著缺陷,即通過 Offset 更改資料,會令系統的髒頁過多,需要特別關注。

4.14 定時訊息

定時訊息是指訊息發到 Broker 後,不能立刻被 Consumer 消費,要到特定的時間點或者等待特定的時間後才能 被消費。

如果要支援任意的時間精度,在 Broker 層面,必須要做訊息排序, 如果再涉及到持久化,那麼訊息排序要不可避免的產生巨大效能開銷。

RocketMQ 支援定時訊息,但是不支援任意時間精度,支援特定的 level,例如定時 5s,10s,1m 等。

4.15 訊息重試

Consumer 消費訊息失敗後,要提供一種重試機制,令訊息再消費一次。Consumer 消費訊息失敗通常可以認為 有以下幾種情況

  1. 由於訊息本身的原因,例如反序列化失敗,訊息資料本身無法處理(例如話費充值,當前訊息的手機號被登出,無法充值)等。 這種錯誤通常需要跳過這條訊息,再消費其他訊息,而這條失敗的訊息即使立刻重試消費,99%也不成功, 所以最好提供一種定時重試機制,即過 10s 秒後再重試。

  2. 由於依賴的下游應用服務不可用,例如 db 連線不可用,外系統網路不可達等。 遇到這種錯誤,即使跳過當前失敗的訊息,消費其他訊息同樣也會報錯。這種情況建議應用 sleep 30s,再消費下一條訊息,這樣可以減輕 Broker 重試訊息的壓力。

5 RocketMQ Overview

5.1 RocketMQ 是什麼?

圖5-1

  • 是一個佇列模型的訊息中介軟體,具有高效能、高可靠、高實時、分散式特點。
  • Producer、Consumer、佇列都可以分散式。
  • Producer 向一些佇列輪流傳送訊息,佇列集合稱為 Topic, Consumer 如果做廣播消費,則一個 consumer 例項消費這個 Topic 對應的所有佇列,如果做叢集消費,則多個 Consumer 例項平均消費這個 topic 對應的佇列集合。
  • 能夠保證嚴格的訊息順序
  • 提供豐富的訊息拉取模式
  • 高效的訂閱者水平擴充套件能力
  • 實時的訊息訂閱機制
  • 億級訊息堆積能力
  • 較少的依賴

5.2 RocketMQ 物理部署結構

圖5-2
RocketMQ 網路部署特點

  • Name Server 是一個幾乎無狀態節點,可叢集部署,節點之間無任何資訊同步。
  • Broker 部署相對複雜,Broker 分為 Master 與 Slave,一個 Master 可以對應多個 Slave,但是一個 Slave 只能 對應一個 Master,Master 與 Slave 的對應關係通過指定相同的 BrokerName,不同的 BrokerId 來定義,BrokerId為 0 表示 Master,非 0 表示 Slave。Master 也可以部署多個。每個 Broker 與 Name Server 叢集中的所有節 點建立長連線,定時註冊 Topic 資訊到所有 Name Server。
  • Producer 與 Name Server 叢集中的其中一個節點(隨機選擇)建立長連線,定期從 Name Server 取 Topic 路由資訊,並向提供 Topic 服務的 Master 建立長連線,且定時向 Master 傳送心跳。 Producer 完全無狀態,可 叢集部署。
  • Consumer 與 Name Server 叢集中的其中一個節點(隨機選擇)建立長連線,定期從 Name Server 取 Topic 路由資訊,並向提供 Topic 服務的 Master、Slave 建立長連線,且定時向 Master、Slave 傳送心跳。 Consumer 既可以從 Master 訂閱訊息,也可以從 Slave 訂閱訊息,訂閱規則由 Broker 配置決定。

5.3 RocketMQ 邏輯部署結構

  • Producer Group
    用來表示一個傳送訊息應用,一個 Producer Group 下包含多個 Producer 例項,可以是多臺機器,也可以 是一臺機器的多個程式,或者一個程式的多個 Producer 物件。 一個 Producer Group 可以傳送多個 Topic 訊息,Producer Group 作用如下:
    1.標識一類 Producer
    2.可以通過運維工具查詢這個傳送訊息應用下有多個 Producer 例項
    3.傳送分散式事務訊息時,如果 Producer 中途意外當機,Broker 會主動回撥 Producer Group 內的任意一臺機器來確認事務狀態。

  • Consumer Group
    用來表示一個消費訊息應用,一個 Consumer Group 下包含多個 Consumer 例項,可以是多臺機器,也可 以是多個程式,或者是一個程式的多個 Consumer 物件。一個 Consumer Group 下的多個 Consumer 以均攤 方式消費訊息,如果設定為廣播方式,那麼這個 Consumer Group 下的每個例項都消費全量資料。

6 RocketMQ 儲存特點

6.1 零拷貝原理

Consumer 消費訊息過程,使用了零拷貝,零拷貝包含以下兩種方式

  1. 使用 mmap + write 方式
    優點:即使頻繁呼叫,使用小塊檔案傳輸,效率也很高
    缺點:不能很好的利用 DMA 方式,會比 sendfile 多消耗 CPU,記憶體安全性控制複雜,需要避免 JVM Crash 問題。

  2. 使用 sendfile 方式
    優點:可以利用 DMA 方式,消耗 CPU 較少,大塊檔案傳輸效率高,無記憶體安全新問題。
    缺點:小塊檔案效率低於 mmap 方式,只能是 BIO 方式傳輸,不能使用 NIO。

RocketMQ 選擇了第一種方式,mmap+write 方式,因為有小塊資料傳輸的需求,效果會比 sendfile 更好。

關於 Zero Copy 的更詳細介紹,請參考以下文章

http://www.linuxjournal.com/article/6345

6.2 檔案系統

RocketMQ 選擇 Linux Ext4 檔案系統,原因如下:

Ext4 檔案系統刪除 1G 大小的檔案通常耗時小於 50ms,而 Ext3 檔案系統耗時約 1s 左右,且刪除檔案時,磁碟 IO 壓力極大,會導致 IO 寫入超時。

檔案系統層面需要做以下調優措施

檔案系統 IO 排程演算法需要調整為 deadline,因為 deadline 演算法在隨機讀情況下,可以合併讀請求為順序跳躍 方式,從而提高讀 IO 吞吐量。

Ext4 檔案系統有以下 Bug,請注意

http://blog.donghao.org/2013/03/20/修復ext4日誌(jbd2)bug/

6.3 資料儲存結構

圖6-1

6.4 儲存目錄結構

6.5 資料可靠性

7 RocketMQ 關鍵特性

7.1 單機支援 1 萬以上持久化佇列

圖7-1
(1). 所有資料單獨儲存到一個 Commit Log,完全順序寫,隨機讀。

(2). 對終端使用者展現的佇列實際只儲存訊息在 Commit Log 的位置資訊,並且序列方式刷盤。

這樣做的好處如下:

(1). 佇列輕量化,單個佇列資料量非常少。

(2). 對磁碟的訪問序列化,避免磁碟竟爭,不會因為佇列增加導致 IOWAIT 增高。

每個方案都有缺點,它的缺點如下:

(1). 寫雖然完全是順序寫,但是讀卻變成了完全的隨機讀。

(2). 讀一條訊息,會先讀 Consume Queue,再讀 Commit Log,增加了開銷。

(3). 要保證 Commit Log 與 Consume Queue 完全的一致,增加了程式設計的複雜度。

以上缺點如何克服:

(1). 隨機讀,儘可能讓讀命中 PAGECACHE,減少 IO 讀操作,所以記憶體越大越好。如果系統中堆積的訊息過多, 讀資料要訪問磁碟會不會由於隨機讀導致系統效能急劇下降,答案是否定的。

a) 訪問 PAGECACHE 時,即使只訪問 1k 的訊息,系統也會提前預讀出更多資料,在下次讀時,就可能命 中記憶體。

b) 隨機訪問 Commit Log 磁碟資料,系統 IO 排程演算法設定為 NOOP 方式,會在一定程度上將完全的隨機 讀變成順序跳躍方式,而順序跳躍方式讀較完全的隨機讀效能會高 5 倍以上,可參見以下針對各種 IO 方式的效能資料。

http://stblog.baidu-tech.com/?p=851

另外 4k 的訊息在完全隨機訪問情況下,仍然可以達到 8K 次每秒以上的讀效能。

(2). 由於 Consume Queue 儲存資料量極少,而且是順序讀,在 PAGECACHE 預讀作用下,Consume Queue 的讀效能幾乎與記憶體一致,即使堆積情況下。 所以可認為 Consume Queue 完全不會阻礙讀效能。

(3). Commit Log 中儲存了所有的元資訊,包含訊息體,類似於 Mysql、Oracle 的 redolog,所以只要有 Commit Log 在,Consume Queue 即使資料丟失,仍然可以恢復出來。

7.2 刷盤策略

RocketMQ 的所有訊息都是持久化的,先寫入系統 PAGECACHE,然後刷盤, 可以保證記憶體與磁碟都有一份資料, 訪問時,直接從記憶體讀取。

7.2.1 非同步刷盤

圖7-2-1
在有 RAID 卡,SAS 15000 轉磁碟測試順序寫檔案,速度可以達到 300M 每秒左右,而線上的網路卡一般都為千兆 網路卡,寫磁碟速度明顯快於資料網路入口速度, 那麼是否可以做到寫完記憶體就向使用者返回,由後臺執行緒刷盤呢?

(1). 由於磁碟速度大於網路卡速度,那麼刷盤的進度肯定可以跟上訊息的寫入速度。

(2). 萬一由於此時系統壓力過大,可能堆積訊息,除了寫入 IO,還有讀取 IO,萬一出現磁碟讀取落後情況, 會不會導致系統記憶體溢位,答案是否定的,原因如下:

a) 寫入訊息到 PAGECACHE 時,如果記憶體不足,則嘗試丟棄乾淨的 PAGE,騰出記憶體供新訊息使用,策略 是 LRU 方式。

b) 如果幹淨頁不足,此時寫入 PAGECACHE 會被阻塞,系統嘗試刷盤部分資料,大約每次嘗試 32 個 PAGE,來找出更多幹淨 PAGE。

綜上,記憶體溢位的情況不會出現。

7.2.2 同步刷盤

圖7-2-2
同步刷盤與非同步刷盤的唯一區別是非同步刷盤寫完 PAGECACHE 直接返回,而同步刷盤需要等待刷盤完成才返回, 同步刷盤流程如下:

(1). 寫入 PAGECACHE 後,執行緒等待,通知刷盤執行緒刷盤。

(2). 刷盤執行緒刷盤後,喚醒前端等待執行緒,可能是一批執行緒。

(3). 前端等待執行緒向使用者返回成功。

7.3 訊息查詢

7.3.1 按照 Message Id 查詢訊息

圖7-2
MsgId 總共 16 位元組,包含訊息儲存主機地址,訊息 Commit Log offset。 從 MsgId 中解析出 Broker 的地址和 Commit Log 的偏移地址,然後按照儲存格式所在位置訊息 buffer 解析成一個完整的訊息。

7.3.2 按照 Message Key 查詢訊息

圖7-3

  1. 根據查詢的 key 的 hashcode%slotNum 得到具體的槽的位置(slotNum 是一個索引檔案裡面包含的最大槽的數目, 例如圖中所示 slotNum=5000000)。

  2. 根據 slotValue(slot 位置對應的值)查詢到索引項列表的最後一項(倒序排列,slotValue 總是指向最新的一個索引項)。

  3. 遍歷索引項列表返回查詢時間範圍內的結果集(預設一次最大返回的 32 條記錄)

  4. Hash 衝突;尋找 key 的 slot 位置時相當於執行了兩次雜湊函式,一次 key 的 hash,一次 key 的 hash 值取模, 因此這裡存在兩次衝突的情況;第一種,key 的 hash 值不同但模數相同(不同key的hash值不同但模數相同),此時查詢的時候會在比較一次 key 的 hash 值(每個索引項儲存了 key 的 hash 值),過濾掉 hash 值不相等的項。第二種,hash 值相等但 key 不等(不同key的hash值相同,模數當然相同), 出於效能的考慮衝突的檢測放到客戶端處理(key 的原始值是儲存在訊息檔案中的,避免對資料檔案的解析), 客戶端比較一次訊息體的 key 是否相同。

  5. 儲存;為了節省空間索引項中儲存的時間是時間差值(儲存時間-開始時間,開始時間儲存在索引檔案頭中), 整個索引檔案是定長的,結構也是固定的。索引檔案儲存結構參見圖 7.4.3-3 。

7.4 伺服器訊息過濾

RocketMQ 的訊息過濾方式有別於其他訊息中介軟體,是在訂閱時,再做過濾,先來看下 Consume Queue 的儲存結構。
圖7-4

(1). 在Broker端進行Message Tag比對,先遍歷 Consume Queue,如果儲存的 Message Tag 與訂閱的 Message Tag 不符合,則跳過,繼續比對下一個,符合則傳輸給 Consumer。 注意:Message Tag 是字串形式,Consume Queue 中儲存的是其對應的 hashcode,比對時也是比對 hashcode。

(2). Consumer 收到過濾後的訊息後,同樣也要執行在 Broker 端的操作,但是比對的是真實的 Message Tag 字串,而不是 Hashcode。

為什麼過濾要這樣做?

(1). Message Tag 儲存 Hashcode,是為了在 Consume Queue 定長方式儲存,節約空間。
(2). 過濾過程中不會訪問 Commit Log 資料,可以保證堆積情況下也能高效過濾。
(3). 即使存在 Hash 衝突,也可以在 Consumer 端進行修正,保證萬無一失。

7.5 長輪詢 Pull

RocketMQ的Consumer都是從Broker拉訊息來消費,但是為了能做到實時收訊息,RocketMQ 使用長輪詢方式,可以保證訊息實時性同 Push 方式一致。 這種長輪詢方式類似於 Web QQ 收發訊息機制。請參考以下資訊瞭解 更多

http://www.ibm.com/developerworks/cn/web/wa-lo-comet/

7.6 順序訊息

7.6.1 順序訊息原理

圖7-6-1
在RocketMQ中,主要指的是區域性順序,即一類訊息為滿足順序性,必須Producer單執行緒順序傳送,且傳送到同一個佇列(這就是原理), 這樣Consumer就可以按照Producer傳送的順序去消費訊息。

7.6.2 順序訊息缺陷

  • 傳送順序訊息無法利用叢集 FailOver 特性

  • 消費順序訊息的並行度依賴於佇列數量

  • 佇列熱點問題,個別佇列由於雜湊不均導致訊息過多,消費速度跟不上,產生訊息堆積問題

  • 遇到訊息失敗的訊息,無法跳過,當前佇列消費暫停

7.7 事務訊息

圖7-7-1

7.8 傳送訊息負載均衡

圖7-5

如圖所示,5 個佇列可以部署在一臺機器上,也可以分別部署在 5 臺不同的機器上, 傳送訊息通過輪詢佇列的方式 傳送,每個佇列接收平均的訊息量。通過增加機器,可以水平擴充套件佇列容量。

另外也可以自定義方式選擇發往哪個佇列。

7.9 訂閱訊息負載均衡

圖7-6

如圖所示,如果有 5 個佇列,2 個 consumer,那麼第一個 Consumer 消費 3 個佇列,第二 consumer 消費 2 個佇列。

這樣即可達到平均消費的目的,可以水平擴充套件 Consumer 來提高消費能力。 但是 Consumer 數量要小於等於佇列數 量,如果 Consumer 超過佇列數量,那麼多餘的 Consumer 將不能消費訊息(不是佇列內也可以並行?)。
表1-1

7.10 單佇列並行消費

圖7-10
單佇列並行消費採用滑動視窗方式並行消費, 如圖所示,3~7 的訊息在一個滑動視窗區間,可以有多個執行緒並行消 費,但是每次提交的 Offset 都是最小 Offset,例如 3

7.11 傳送定時訊息

7.12 訊息消費失敗,定時重試

7.13 HA,同步雙寫/非同步複製

非同步複製的實現思路非常簡單,Slave 啟動一個執行緒,不斷從 Master 拉取 Commit Log 中的資料,然後在非同步 build 出 Consume Queue 資料結構。整個實現過程基本同 Mysql 主從同步類似。

7.14 單個 JVM 程式也能利用機器超大記憶體

圖7-7

(1). Producer 傳送訊息,訊息從 socket 進入 java 堆。

(2). Producer 傳送訊息,訊息從 java 堆轉入 PAGACACHE,實體記憶體。

(3). Producer 傳送訊息,由非同步執行緒刷盤,訊息從 PAGECACHE 刷入磁碟。

(4). Consumer 拉訊息(正常消費),訊息直接從 PAGECACHE(資料在實體記憶體)轉入 socket,到達 consumer, 不經過 java 堆。這種消費場景最多,線上 96G 實體記憶體,按照 1K 訊息算,可以在實體記憶體快取 1 億條消 息。

(5). Consumer 拉訊息(異常消費),訊息直接從 PAGECACHE(資料在虛擬記憶體)轉入 socket。

(6). Consumer 拉訊息(異常消費),由於 Socket 訪問了虛擬記憶體,產生缺頁中斷,此時會產生磁碟 IO,從磁

盤 Load 訊息到 PAGECACHE,然後直接從 socket 發出去。

(7). 同 5 一致。

(8). 同 6 一致。

7.15 訊息堆積問題解決辦法

前面提到衡量訊息中介軟體堆積能力的幾個指標,現將 RocketMQ 的堆積能力整理如下
表7-1

在有 Slave 情況下,Master 一旦發現 Consumer 訪問堆積在磁碟的資料時,會向 Consumer 下達一個重定向指 令,令 Consumer 從 Slave 拉取資料,這樣正常的發訊息與正常消費的 Consumer 都不會因為訊息堆積受影響,因為 系統將堆積場景與非堆積場景分割在了兩個不同的節點處理。這裡會產生另一個問題,Slave 會不會寫效能下降, 答案是否定的。因為 Slave 的訊息寫入只追求吞吐量,不追求實時性,只要整體的吞吐量高就可以,而 Slave 每次 都是從 Master 拉取一批資料,如 1M,這種批量順序寫入方式即使堆積情況,整體吞吐量影響相對較小,只是寫入 RT 會變長。

8 RocketMQ 訊息過濾

/**

* 訂閱指定topic下tags分別等於TagA或TagC或TagD
*/

consumer.subscribe("TopicTest1", "TagA || TagC || TagD");

如以上程式碼所示,簡單訊息過濾通過指定多個 Tag 來過濾訊息,過濾動作在伺服器進行。實現原理參照第 7.4 節

8.2 高階訊息過濾

圖8-2

  1. Broker 所在的機器會啟動多個 FilterServer 過濾程式

  2. Consumer 啟動後,會向 FilterServer 上傳一個過濾的 Java 類

  3. Consumer 從 FilterServer 拉訊息,FilterServer 將請求轉發給 Broker,FilterServer 從 Broker 收到訊息後,按照 Consumer 上傳的 Java 過濾程式做過濾,過濾完成後返回給 Consumer。

總結:

  1. 使用 CPU 資源來換取網路卡流量資源

  2. FilterServer 與 Broker 部署在同一臺機器,資料通過本地迴環通訊,不走網路卡

  3. 一臺 Broker 部署多個 FilterServer,充分利用 CPU 資源,因為單個 Jvm 難以全面利用高配的物理機 Cpu 資源

  4. 因為過濾程式碼使用 Java 語言來編寫,應用幾乎可以做任意形式的伺服器端訊息過濾,例如通過 Message Header 進行過濾,甚至可以按照 Message Body 進行過濾。

  5. 使用 Java 語言進行作為過濾表示式是一個雙刃劍,方便了應用的過濾操作,但是帶來了伺服器端的安全風險。 需要應用來保證過濾程式碼安全,例如在過濾程式裡儘可能不做申請大記憶體,建立執行緒等操作。避免 Broker 服 務器發生資源洩漏。

使用方式參見 Github 例子

https://github.com/alibaba/RocketMQ/blob/develop/rocketmq-example/src/main/java/com/alibaba/rocketmq/example/filter/Consumer.java

9 RocketMQ 通訊元件

RocketMQ 通訊元件使用了 Netty-4.0.9.Final,在之上做了簡單的協議封裝。

10 RocketMQ 服務發現(Name Server)

Name Server 是專為 RocketMQ 設計的輕量級名稱服務,程式碼小於 1000 行,具有簡單、可叢集橫向擴充套件、無狀 態等特點。將要支援的主備自動切換功能會強依賴 Name Server。

參考:
http://jm.taobao.org/2017/01/12/rocketmq-quick-start-in-10-minutes/
https://www.jianshu.com/p/453c6e7ff81c
http://valleylord.github.io/post/201607-mq-rocketmq/

相關文章