該篇是由原作者 傅沖 提供
<code>metaq</code>是一款高性能的消息中間件,經過幾年的發展,已經非常成熟穩定,曆經多年雙11的零點峰值壓測,表現堪稱完美。
<code>metaq</code>目前最新最穩定的穩本是<code>3.x</code>系統,<code>metaq 3.x</code>重新設計和實作,比之前的版本更優秀。雖然<code>metaq</code>借鑒了<code>linkedin</code> 的消息中間件<code>kafak</code>思想,但已經是青出于藍而勝于藍。
本文不對<code>metaq</code>做全面的介紹,隻選擇高性能這點來分析。
以上測試圖檔,來自消息測中間件試團隊 @以夕 妹子的性能測試結果
<code>metaq</code>作為一款消息中間件,消息中間件該有的功能,<code>metaq</code>也有。本文并不全面介紹<code>metaq</code>方面方面,隻是選取性能這一角度,來剖析其高性能的原因。
<code>metaq server</code>
最為核心的元件,它主要可以接收應用程式發送過來的消息并存儲,然後再投遞。
<code>metaq master</code>
是<code>metaq server</code>邏輯上的角色,和<code>mysql master</code>概念類型,對外提供發送消息、訂閱消息以及維護着管理資訊。
<code>metaq slave</code>
是<code>metaq server</code>邏輯上的角色,和<code>mysql slave</code>概念類型,對外提供訂閱消息功能。
<code>metaq client</code>
主要是應用程式使用,使用<code>metaq client</code>來發送消息、訂閱消息、其它控制資訊。
其它無資料管理及控制資訊元件
提供訂閱關系管理功能,<code>metaq server</code>服務發現功能。
<code>metaq client</code> 發送消息,<code>metaq server</code>收到消息,并存儲到檔案系統。也就是說<code>metaq</code>會有大量<code>write</code>系統調用。
<code>metaq client</code> 訂閱消息,因其是<code>pull</code>的模型。<code>metaq server</code>收到<code>pull</code>消息的請求,會從磁盤上讀取出消息,然後傳回給<code>metaq client</code>。這一步有大量的<code>read</code>系統調用。
從上面的功能上看,<code>metaq server</code>要支援大量的磁盤<code>io</code>操作,因為其是建構檔案系統之上的消息中間件。既然使用了檔案系統來存儲資料,但磁盤<code>qps</code>每秒也就是幾百。<code>metaq server</code>又必須高性能(如<code>metaq server</code>性能是10w級别的qps),才能在可接收的成本範圍内,滿足業務需求(不丢消息)。如何在<code>qps</code>隻有幾百的磁盤上,建構出一個高性能的<code>metaq</code>消息間件正是本文的中心。
前面介紹了<code>metaq</code>高性能的難點,那麼我們如何解決這些難點。要解決這些難點,就必須找出這些難點。那麼要寫一個高性能的消息中間件,會有哪些會部分會對影響性能。
序列化與反序列化
從<code>metaq cleint</code>要發送消息,必須要先序列化,然後才能通過網絡發送出去。 <code>metaq server</code>收到消息後,要進行反序列化,才能解析出消息内容,最後序列化存儲到檔案系統。
<code>metaq client</code>收到消息,首頁<code>metaq server</code>必須從檔案中讀取消息,然後通過網絡發送給<code>metaq client</code>,收到消息,進行反序列化,應用才能識别消息内容。
<code>metaq</code>核心功能,都要通過序列化與反序列化,是以其性能,對<code>metaq</code>性能有關鍵性的影響,其實不是對<code>metaq</code>,隻要使用了序列化與反序列化,其對性能影響都很大。
<code>write</code>性能
因為<code>metaq server</code>會有大量的<code>write</code>系統調用 ,是以其性能對<code>metaq</code>性能有着重要的影響。
<code>read</code>性能
因為<code>metaq server</code>會有大量的<code>read</code>系統調用 ,是以其性能對<code>metaq</code>性能有着重要的影響。
網絡架構
因為發送消息,訂閱消息都必須經過網絡,如果網絡元件性能不好,對<code>metaq</code>性能有着關鍵的影響。
要解決序列化與反序列化性能問題,我們就必須尋種各種序列化與反序列化技術性能對比,進而選出一個高性能的序列化與反序列化技術來作為<code>metaq</code>。
我們來看下<code>java</code>世界可以選擇的序列化與反序列化技術
從圖中性能資料,可以看出,個人認為<code>google</code>出品的<code>protocol buffers</code>應該是最佳選擇,不管軟體的品質、社群活躍、軟體的後續發展上來說,都是不錯的選擇。
但<code>metaq</code>并沒有選擇<code>protocol buffers</code>作為其序列化與反序列化的技術,一個原因是<code>protocol buffers</code>居然在小版之間本都不相容,<code>2.3</code>和<code>2.5</code>的版本都不相容。這會帶來一個嚴重的問題,如果<code>metaq</code>選擇<code>2.3</code>的版本,應用程式選擇了<code>2.5</code>,都會導緻沖突,反之亦然。
<code>metaq</code>消息中繼資料是通過<code>json</code>來序列化與反序列化,消息<code>body</code>是交給應用自己序列化與反序列化。
雖然使用<code>protocol buffers</code>性能會更好,但帶給使用者帶來麻煩。是以<code>metaq</code>選擇使用<code>json</code>。
<code>io</code>優化
前面也已經介紹了,<code>metaq server</code> 存大大量的<code>io</code>,那麼怎麼優化呢?
<code>read</code>優化主要是使用了<code>mmap</code>檔案映射技術。這樣可以減少系統上下文切換和複制資料的開銷。。
同時檔案系統提供了檔案預讀的功能,也使的讀取檔案開銷,特别是順序讀時,開銷比較低。
前面也介紹了,<code>write</code>可能存在并發問題,那麼<code>metaq</code>是如何解決的?
<code>metaq</code>消息隻保留在一個實體檔案上,所有的消息都會寫一個實體檔案,每個實體檔案都是固定大小,超過設定的閥值後,自動建立新的一個檔案。當磁盤快滿時,會自動删除老的檔案。
<code>group commit</code>也就是組送出,組送出是指可以多次分寫請求隻要通過一次重新整理資料,就可以實作這些請求的資料都已重新整理到磁盤上。
<code>mysql</code>資料庫能保證<code>acid</code>,事務送出也使用了<code>group commit</code>來提高性能(為了保證<code>d</code>,資料需要持久化到檔案系統)。
詳細見下圖
當<code>寫請求1</code>到<code>metaq server</code>時,把線程寫入核心後,觸發<code>flush</code>線程重新整理資料到磁盤,以保證資料的可靠性。
然後再向<code>metaq client</code> 響應發送消息成功。這個時間,隻要檔案系統和磁盤不損壞,資料是不會丢失的。
正在<code>flush線程</code>要準備重新整理資料時,<code>寫請求2</code>,<code>寫請求3</code>,<code>寫請求4</code>也到<code>metaq server</code>且寫入資料,這樣因<code>寫請求1</code>寫資料,觸發的<code>flush</code>順便也把<code>寫請求2</code>,<code>寫請求3</code>,<code>寫請求4</code>的資料也重新整理到磁盤。這樣減少了重新整理磁盤的次數,性能自然就高了,同時也保證的資料的可靠性。
如何實作<code>group commit</code>,請看源碼
<code>write</code>如何保證并發安全,在寫資料前,需要搶占一個鎖,因為這隻是把資料寫到檔案系統緩存中,是以持有鎖的時間非常短,對性能友好。請看代碼
<code>metaq</code>的網絡架構,選擇了<code>netty4</code>。<code>netty4</code>因出色的性能和易用性,成為高性能場景的不二選擇。
<code>metaq</code>高性能的秘密,我們從其功能結構,從功能的作用,一個個解釋了可能影響性能的點,及怎麼解決這些問題,提高性能。
雖然一個個點看起來簡單,但要實作一個穩定、高性能的消息系統,還是不容易的。