猿學—如何打造高可靠高性能的消息隊列(ZZMQ)

背景

互聯網公司使用最頻繁的服務調用組件是RPC框架,RPC同步調用有些場景並不是很適用,而這些場景剛好是一個可靠MQ的適用場景。

我們看看RPC調用的場景。服務A調用如圖所示服務。在正常情況下,一般都不會有問題。但是在以下情況,服務A調用會遇到問題。

猿學—如何打造高可靠高性能的消息隊列(ZZMQ)

問題一:如果有流量高峰,服務B響應超時,會發生什麼情況?

整個RPC調用鏈路都會受到影響,甚至發生雪崩。

問題二:服務A邏輯複雜,邏輯耦合嚴重,怎麼做拆分?

把一些調用鏈路中可以異步調用的邏輯調整為消費MQ消息。

問題三:RPC調用,jar依賴問題?服務B升級,調用B的相關服務是否需要升級?

RPC服務需要依賴生成的接口描述jar,服務接口升級一般很難做到向前兼容,所以相關調用方也需要升級。

MQ是以消息為載體的可靠異步調用的框架,能很好的應對上面三個問題。流量削峰,MQ是天然支持的,因為MQ有可靠存儲,可以落地。解耦合,交給MQ也很合適。因為MQ的接入方處理的是消息,做到向前兼容也是比較容易的。

猿學—如何打造高可靠高性能的消息隊列(ZZMQ)

使用MQ之後,服務A,B通過MQ做到松耦合,也能很好的應對流量高峰。

MQ很好很強大,是RPC的有效補充,那問題來了怎麼實現一個可靠MQ?本文結合二手交易平臺的特點,詳細介紹轉轉ZZMQ架構設計實踐。

轉轉消息隊列(ZZMQ)架構設計

設計ZZMQ,我們考慮的重點是:可靠,高性能,運維友好,接入方便,可以支持大量堆積,有效緩衝業務高峰,針對這些需求,我們做了一些設計上的考慮和取捨,最終形成了如下的架構方案。

猿學—如何打造高可靠高性能的消息隊列(ZZMQ)

RegisterCenter 作為註冊中心,負責路由服務,無狀態,每個NameNode都是對等的,NameNode 可以任意水平擴展。NameNode 與broker和Client都建立了長連接。Broker 內是主從兩臺機器,slave從master拉取消息Log和消費Offset,做HA。Broker也可以方便地水平擴展,加入新機器,更新topic的路由信息,client會定時更新路由信息。Consumer 和Producer 都需要到註冊中心註冊,同時拉取Topic的路由信息。Management Protal 是用來管理集群,維護Topic的相關聯繫人信息。

存儲設計

一個高性能ZZMQ的瓶頸和難點就是存儲系統,存儲系統關乎到性能,數據可靠存儲實現是否簡單,數據備份控制,消息狀態的表示。

目前最常見的是以Kafka為代表的Log-append-file,文件順序寫,追求吞吐量,消息消費狀態通過offset來控制,還有以各種存儲引擎實現的,比如leveldb(activemq),rocksdb等。

猿學—如何打造高可靠高性能的消息隊列(ZZMQ)

從這張圖(http://queue.acm.org/detail.cfm?id=1563874),我們能看到磁盤順序讀寫的性能甚至超過了內存隨機訪問的性能,能達到50多M/s。並且,相對來說,Log-append-file簡單一些。最後選擇了類似kafka的log-append-file。

Kafkatopic分成partition,順序讀寫,partition有一個小的問題,單機不能支撐大量的topic,單機能支持幾百分區,隨著分區數量的增加,性能有所下降。而我們的場景的是:業務大部分topic的qps並不像kafka處理的日誌文件那麼高,希望能單機支撐更多的topic數量。

針對這一點,我們做了一些調整,topic消息本身不再分區,統一存儲,使用更加輕量的Consumer Queue實現partition的功能。Consumer Queue存儲的只是位置信息,更加輕量,能做到支持更多的topic。

消息寫入pageCache,再Dispatch到對應的Consumer Queue中去,Consumer Queue只有消息Log的offset信息,以及大小和其他的一些元數據,佔用很小的空間。這樣設計,經過驗證,單機能夠支持幾千隊列。

猿學—如何打造高可靠高性能的消息隊列(ZZMQ)

猿學—如何打造高可靠高性能的消息隊列(ZZMQ)

這樣的設計也帶來了一個副作用,因為消息本身是統一存儲,topic數量多的時候,消息的讀取是隨機讀,性能有些許下降,目前,我們通過優化linuxpageCache和IO調度,開啟系統預讀,性能相對順序讀沒有太大下降。

ZZMQ消息訂閱模型

猿學—如何打造高可靠高性能的消息隊列(ZZMQ)

Consumergroup A 有兩個消費者實例,B也有兩個消費者實例,同時訂閱了Topic-A,他們之間的消費進度是獨立的。Group處理相同topic,消費邏輯一致的一個整體,包含不同的消費實例。

ConsumerGroup基本上是跟kafka的Group一致,由不同的ConsumerGroup來區分不同的消費組,維護Group對應的消費狀態,能很方便的實現。

網絡

基於netty實現了網絡層的協議。Netty對nio,和reactor做了很好的封裝,netty4.x 對direct buffer的利用,以及高效的buffer分配和回收算法,netty也解決了TCP協議的半包問題,使用方不需要自己組TCP包。用netty實現網絡層協議,網絡層的可靠由netty來做,減少了複雜度。

Push 還是Pull

這個設計的考慮主要是兩個方面:慢消費,以及消費延遲。主動Push能做到低的消費延遲,但是對於慢消費,不能很好的應對,主動Push需要感知消費者的速率,不至於push 太快,把消費者壓垮了。Pull模式,由消費者控制拉取的速度,能很好的應對慢消費的問題,但是,Pull模式對消費延遲不敏感,拉取的頻率不好控制,處理不好有可能造成CPU使用率飆升。參照kafka的pull,實現了longpull。Consumer 與Broker建立長連接,Consumer發起拉取請求,如果有消息,Broker 返回消息,如果沒有消息,broker hold住這個請求一段時間,等有消息再notify這個請求,返回給客戶端。基於long pull,消費延遲能降到跟push差不多,同時又能由Consumer 控制拉取速度。

重複消息和順序消息

消息重複在一個複雜網絡條件下很難避免的,如果由MQ本身來做去重,代價太大,所以我們要求接入MQ的邏輯做好冪等(狀態機或者版本號的一些機制)。順序消息,跟kafka一樣,Consumer queue跟partition類似,一個Queue內部,消費是有序的。如果要做到完全有序,只能一個Producer,一個Consumer。

高可用

Broker組由主從兩臺機器構成,可以配置的策略有同步雙寫和異步複製。默認情況,採用的異步複製,由slave去拉取master上面的消息Log和offset。Broker 接入了內部的監控系統,每分鐘上報topic的消費情況和broker狀態,能做到分鐘級別發現異常消費。消息寫入PageCache之後,默認是異步刷盤。也可以配置為同步刷盤,只有刷盤成功才會返回。

猿學—如何打造高可靠高性能的消息隊列(ZZMQ)

Broker的默認配置異步刷盤,Master-Slave異步複製。Client消費消費消息的可靠是通過Consume queue的offset來保證的,Client會定時上報已經消費成功的Offset信息。如果Client被異常kill掉,沒有確認的消息會被Client 重新拉取,消費。

一條消息的生命過程

Proudcer通過與NameNode的路由找到topic的broker地址,producer發消息,netty序列化,通過TCP傳輸到對應的broker,broker寫log的pageCache,返回。Broker Dispatch消息到對應的Consumer queue,broker喚醒刷盤線程,broker喚醒slave同步線程,slave拉取消息。

NotifyConsumer 拉取請求,經過netty序列化,通過tcp到達Consumer,Consumer消費成功,Client上報消費成功offset。

Broker持久化Offset,slave同步offset。三天之後,broker刪除消息。

總結

本文主要介紹了轉轉ZZMQ在架構、存儲系統、保證消息可靠存儲,可靠消費,以及netty的使用等經驗,分享出來,與大家一起探討,歡迎拍磚指正。


分享到:


相關文章: