RocketMQ關鍵特性

塵央發表於2016-12-09

Apache RocketMQ之所以能在眾多的訊息中介軟體中脫穎而出,能吸引數千企業使用者與RocketMQ的關鍵特性是分不開的,本文詳細介紹RocketMQ中的關鍵特性。

一、過萬的單機佇列數

諸如Kafka之類的訊息中介軟體,在佇列數上升時效能會產生巨大的損失,RocketMQ之所以能單機支援上萬的持久化佇列與其獨特的儲存結構分不開。

screenshot.png

如上圖所示,所有的訊息資料單獨儲存到一個Commit Log,完全順序寫,隨機讀。對終端使用者展現的佇列實際只儲存訊息在Commit Log的位置資訊,並且序列方式刷盤。

這樣做的好處如下:

  1. 佇列輕量化,單個佇列資料量非常少。
  2. 對磁碟的訪問序列化,避免磁碟竟爭,不會因為佇列增加導致IOWAIT增高。

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

  1. 寫雖然完全是順序寫,但是讀卻變成了完全的隨機讀。
  2. 讀一條訊息,會先讀Consume Queue,再讀Commit Log,增加了開銷。
  3. 要保證Commit Log與Consume Queue完全的一致,增加了程式設計的複雜度。

以上缺點如何克服:

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

    • 訪問PAGECACHE時,即使只訪問1k的訊息,系統也會提前預讀出更多資料,在下次讀時,就可能命中記憶體。
    • 隨機訪問Commit Log磁碟資料,系統IO排程演算法設定為NOOP方式,會在一定程度上將完全的隨機讀變成順序跳躍方式,而順序跳躍方式讀較完全的隨機讀效能會高5倍以上。
    • 另外4k的訊息在完全隨機訪問情況下,仍然可以達到8K次每秒以上的讀效能。
  2. 由於Consume Queue儲存資料量極少,而且是順序讀,在PAGECACHE預讀作用下,Consume Queue的讀效能幾乎與記憶體一致,即使堆積情況下。所以可認為Consume Queue完全不會阻礙讀效能。
  3. Commit Log中儲存了所有的元資訊,包含訊息體,類似於Mysql、Oracle的redolog,所以只要有Commit Log在,Consume Queue即使資料丟失,仍然可以恢復出來。

二、兩種刷盤策略

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

非同步刷盤

screenshot.png

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

  1. 由於磁碟速度大於網路卡速度,那麼刷盤的進度肯定可以跟上訊息的寫入速度。
  2. 萬一由於此時系統壓力過大,可能堆積訊息,除了寫入IO,還有讀取IO,萬一出現磁碟讀取落後情況,會不會導致系統記憶體溢位,答案是否定的,原因如下:

    • 寫入訊息到PAGECACHE時,如果記憶體不足,則嘗試丟棄乾淨的PAGE,騰出記憶體供新訊息使用,策略是LRU方式。
    • 如果幹淨頁不足,此時寫入PAGECACHE會被阻塞,系統嘗試刷盤部分資料,大約每次嘗試32個PAGE,來找出更多幹淨PAGE。
    • 綜上,記憶體溢位的情況不會出現。

同步刷盤

screenshot.png

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

  1. 寫入PAGECACHE後,執行緒等待,通知刷盤執行緒刷盤。
  2. 刷盤執行緒刷盤後,喚醒前端等待執行緒,可能是一批執行緒。
  3. 前端等待執行緒向使用者返回成功。

三、多種訊息查詢手段

豐富的訊息查詢手段,幫助使用者快速定位訊息,排查問題,RocketMQ支援按Message Id查詢、按Message Key查詢等。

按照Message Id查詢訊息

screenshot.png

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

按照Message Key查詢訊息

screenshot.png

RocketMQ可以為每條訊息指定Key,並根據建立高效的訊息索引,索引邏輯結果如上圖所示,查詢過程如下:

  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值),過濾掉hash值不相等的項。第二種,hash值相等但key不等,出於效能的考慮衝突的檢測放到客戶端處理(key的原始值是儲存在訊息檔案中的,避免對資料檔案的解析),客戶端比較一次訊息體的key是否相同。
  5. 儲存;為了節省空間索引項中儲存的時間是時間差值(儲存時間-開始時間,開始時間儲存在索引檔案頭中),整個索引檔案是定長的,結構也是固定的 。

四、訊息過濾機制

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

screenshot.png

  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端進行修正,保證萬無一失。

五、順序訊息

很多業務有順序訊息的需求,RocketMQ支援全域性和區域性的順序,一般推薦使用區域性順序,將具有順序要求的一類訊息hash到同一個佇列中便可保持有序,如下圖所示。

screenshot.png

但順序訊息,有自己的缺陷:

  • 傳送順序訊息無法利用叢集FailOver特性
  • 消費順序訊息的並行度依賴於佇列數量
  • 佇列熱點問題,個別佇列由於雜湊不均導致訊息過多,消費速度跟不上,產生訊息堆積問題
  • 遇到訊息失敗的訊息,無法跳過,當前佇列消費暫停

目前,中介軟體團隊正在攻克這些缺陷,很快將出現在新特性當中。

六、事務訊息

事務訊息特性介紹參考Aliware MQ的文件介紹

七、定時訊息

日常業務中有很多定時訊息的場景,比如在電商交易中超時未支付關閉訂單的場景,在訂單建立時會傳送一條 MQ 延時訊息,這條訊息將會在30分鐘以後投遞給消費者,消費者收到此訊息後需要判斷對應的訂單是否已完成支付。如支付未完成,則關閉訂單,如已完成支付則忽略。

RocketMQ為了實現定時訊息,引入延時級別,犧牲部分靈活性,事實上很少有業務需要隨意指定定時時間的靈活性。定時訊息內容被儲存在資料檔案中,索引按延時級別堆積在定時訊息佇列中,具有跟普通訊息一致的堆積能力,如下圖所示。

screenshot.png

八、總結

以上為使用者比較關注的RocketMQ關鍵特性,RocketMQ中更多的技術將有專門的章節介紹,比如低延遲技術、高可用以及高可靠技術等。


相關文章