RocketMQ 如何取代kafka,成為滴滴出行的千億級消息引擎新選擇?

圖片發自簡書App

摘要:本文整理自滴滴的江海挺在2018年9月1日Apache RocketMQ開發者沙龍北京站的分享。江海挺是Apache RocketMQ Contributor,北大信科畢業后,一直在做消息隊列相關的服務,在消息隊列方面積累了豐富的經驗。

本文的主要內容包括以下幾個方面:

  1. 滴滴的消息技術選型
  2. 為什么選擇RocketMQ
  3. 如何構建自己的消息隊列服務
  4. RocketMQ擴展改造
  5. RocketMQ使用經驗

1. 滴滴的消息技術選型

1.1 消息歷史

image-20181014102848377.png

如圖,初期公司內部沒有專門的團隊維護消息隊列服務,所以消息隊列使用方式較多,主要以kafka為主,有業務直連的,也有通過獨立的服務轉發消息的。另外有一些團隊也會用 RocketMQ、Redis的list,甚至會用比較非主流的beanstalkkd。導致的結果就是,比較混亂,無法維護,資源使用也很浪費。

1.2 棄用kafka

一個核心業務在使用kafka的時候,出現了集群數據寫入抖動非常嚴重的情況,經常會有數據寫失敗。

主要有兩點原因:

  1. 隨著業務增長,topic的數據增多,集群負載增大,性能下降。
  2. 我們用的是kafka 0.8.2那個版本,有個bug,會導致副本重新復制,復制的時候有大量的讀,我們存儲盤用的又是機械盤,導致磁盤IO過大,影響寫入。

所以我們決定做自己的消息隊列服務。

![image-20181014113200764.png](https://upload-images.jianshu.io/upload_images/10425061-8d679ffbcd6af652.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

首先需要解決上面的解決業務方消息生產失敗的問題。因為這個kafka用的是發布/訂閱模式,一個topic的訂閱方會有很多,涉及到的下游業務也就非常多,沒辦法一口氣直接替換kafka,遷移到新的一個消息隊列服務上。
所以我們當時的方案是加了一層代理,然后利用codis作為緩存,解決了kafka不定期寫入失敗的問題,如上圖。
就是當后面的kafka出現不可寫入的時候,我們就會先把數據寫入到codis中,然后延時進行重試,直到寫成功為止。

1.3 選擇RocketMQ

經過一系列的調研和測試之后,我們決定采用RocketMQ。具體原因在后面會介紹。

為了支持多語言環境、解決一些遷移和某些業務的特殊需求,我們又在消費側加上了一個代理服務。

然后形成了這么一個核心框架。業務端只跟代理層交互。中間的消息引擎,負責消息的核心存儲。

在之前的基本框架之后,我們后面就主要圍繞三個方向做。

一個是遷移。把之前提到的所有五花八門的隊列環境,全部遷移到我們上面。這里面的遷移方案后面會跟大家介紹一下。

第二個就是功能迭代和成本性能上的優化。

最后一個重點就是服務化,業務直接通過平臺界面來申請資源,申請到之后直接使用。

1.4 演進中的架構

image-20181014113633110.png

這張圖是我們消息隊列服務的一個比較新的現狀。

先縱向看,上面是生產的客戶端,包括了7種語言。然后是我們的生產代理服務。

在中間的是我們的消息存儲層。目前主要的消息存儲引擎是RocketMQ。然后還有一些在遷移過程中的Kafka。還有一個chronos,它是我們延遲消息的一個存儲引擎。

再下面就是消費代理。

消費代理同樣提供了多種語言的客戶端。然后還支持多種協議的消息主動推送功能。包括HTTP 協議 RESTful方式。結合我們的groovy腳本功能,還能實現將消息直接轉存到redis、hbase和hdfs上。更多的下游存儲,我們都在陸續接入。

除了存儲系統之外,我們還對接了實時計算平臺,像Flink,Spark,Storm這些平臺,我們也都提供了支持。

左邊是我們的用戶控制臺和運維控制臺。這個是我們服務化的重點。

用戶在需要使用隊列的時候,就通過界面申請topic,填寫各種信息,包括身份信息,消息的峰值流量,消息大小,消息格式等等。

然后消費方,通過我們的界面,就可以申請消費。

運維控制臺,主要負責我們集群的管理,自動化部署,流量調度,狀態顯示之類的功能。

最后所有運維和用戶操作會影響線上的配置,都會通過zookeeper進行同步。

2. 為什么選擇RocketMQ

因為從實際測試結果來看,RocketMQ的效果更好。

主要圍繞兩個測試進行。

2.1 測試-topic數量的支持

如下圖所示,測試環境:

Kafka 0.8.2

RocketMQ 3.4.6

1.0 Gbps Network

16 threads

image-20181014114750847.png

這張圖是Kafka和RocketMQ在不同topic數量下的吞吐測試。橫坐標是每秒消息數,縱坐標是測試case。同時覆蓋了有無消費,和不同消息體的場景。一共8組測試數據,每組數據分別在topic個數為16、32、64、128、256時獲得的,每個topic包括8個partition。下面四組數據是發送消息大小為128字節的情況,上面四種是發送2k消息大小的情況。on 表示消息發送的時候,同時進行消息消費,off表示僅進行消息發送。

先看最上面一組數據,用的是kafka,開啟消費,每條消息大小為2048字節??梢钥吹?,隨著topic數量增加,到256 topic之后,吞吐極具下。
可以先看最上面的一組結果,用的是Kafka,開啟消費,每條消息是2kb(2048)??梢钥吹?,隨著topic數量增加,到256個topic之后,吞吐急劇下降。

第二組是是RocketMQ。可以看到,topic增大之后,影響非常小。

第三組和第四組,是上面兩組關閉了消費的情況。結論基本類似,整體吞吐量會高那么一點點。

下面的四組跟上面的區別是使用了128字節的小消息體??梢钥吹?,kafka吞吐受topic數量的影響特別明顯。對比來看,雖然topic比較小的時候,RocketMQ吞吐較小,但是基本非常穩定,對于我們這種共享集群來說比較友好。

2.2 測試-延遲

  • Kafka

測試環境:

Kafka 0.8.2.2

topic=1/8/32

Ack=1/all,replica=3

測試結果:如下圖

image-20181014153801681.png

(橫坐標對應吞吐,縱坐標對應延遲時間)

上面的一組的3條線對應ack=3,需要3個備份都確認后才完成數據的寫入。

下面的一組的3條線對應ack=1,有1個備份收到數據后就可以完成寫入。

可以看到下面一組只需要主備份確認的寫入,延遲明顯較低。

每組的三條線之間主要是topic數量的區別,topic數量增加,延遲也增大了。

  • RocketMQ

測試環境:

RocketMQ 3.4.6

brokerRole=ASYNC/SYNC_MASTER, 2 Slave

flushDiskType=SYNC_FLUSH/ASYNC_FLUSH

測試結果:如下圖

image-20181014153954985.png

上面兩條是同步刷盤的情況,延遲相對比較高。下面的是異步刷盤。

橙色的線是同步主從,藍色的線是異步主從。

然后可以看到在副本同步復制的情況下,即橙色的線,4w的tps之內都不超過1ms。用這條橙色的線和上面Kafka的圖中的上面三條線橫向比較來看,kafka超過1w tps 就超過1ms了。kafka的延遲明顯更高。

3. 如何構建自己的消息隊列服務

3.1 問題與挑戰

challenge.png

面臨的挑戰(順時針看):

  • 客戶端語言。需要支持PHP、GO、Java、C++。
  • 只有3個開發人員。
  • 決定用RocketMQ,但是沒看過源碼。
  • 上線時間緊,線上的kafka還有問題。
  • 可用性要求高。

使用RocketMQ時的兩個問題:

  • 客戶端語言支持不全,它主要支持Java,而我們還需要支持PHP、Go、C++,RocketMQ目前提供的Go的sdk我們測的有一些問題。
  • 功能特別多,如tag、property、消費過濾、RETRY topic、死信隊列、延遲消費之類的功能,非常豐富。但是這個對我們穩定性維護來說,挑戰非常大。

解決辦法,如下圖所示:

  • 使用Thrift RPC框架來解決跨語言的問題。

  • 簡化調用接口??梢哉J為只有兩個接口,send用來生產,pull用來消費。

主要策略就是堅持KISS原則(Keep it simple, stupid),保持簡單,先解決最主要的問題,讓消息能夠流轉起來。

然后我們把其他主要邏輯都放在了proxy這一層來做,比如限流、權限認證、消息過濾、格式轉化之類的。這樣,我們就能盡可能地簡化客戶端的實現邏輯,不需要把很多功能用各種語言都寫一遍。

image-20181014164732619.png

3.2 遷移方案

架構確定后,接下來是我們的一個遷移過程。


image-20181014183257694.png

遷移這個事情,在pub-sub的消息模型下,會比較復雜。因為下游的數據消費方可能很多,上游的數據沒法做到一刀切流量,這就會導致整個遷移的周期特別長。然后我們為了盡可能地減少業務遷移的負擔,加快遷移的效率,我們在proxy層提供了雙寫和雙讀的功能。

  • 雙寫:Procucer Proxy同時寫RocketMQ和kafka。
  • 雙讀:Consumer Proxy同時從RocketMQ和kafka消費數據。

有了這兩個功能之后,我們就能提供以下兩種遷移方案了。

3.2.1 雙寫

生產端雙寫,同時往kafka和rocketmq寫同樣的數據,保證兩邊在整個遷移過程中都有同樣的全量數據。kafka和RocketMQ有相同的數據,這樣下游的業務也就可以開始遷移。

如果消費端不關心丟數據,那么可以直接切換,切完直接更新消費進度。

如果需要保證消費必達,可以先在Consumer Proxy設置消費進度,消費客戶端保證沒有數據堆積后再去遷移,這樣會有一些重復消息,一般客戶端會保證消費處理的冪等。

生產端的雙寫其實也有兩種方案:

  1. 客戶端雙寫,如下圖:
image-20181014190943810.png

業務那邊不停原來的kafka 客戶端。只是加上我們的客戶端,往RocketMQ里追加寫。

這種方案在整個遷移完成之后,業務還需要把老的寫入停掉。相當于兩次上線。

  1. Producer Proxy雙寫,如下圖:
image-20181014191156474.png

業務方直接切換生產的客戶端,只往我們的proxy上寫數據。然后我們的proxy負責把數據復制,同時寫到兩個存儲引擎中。

這樣在遷移完成之后,我們只需要在proxy上關掉雙寫功能就可以了。對生產的業務方來說是無感知的,生產方全程只需要改造一次,上一下線就可以了。

所以表面看起來,應該還是第二種方案更加簡單。但是,從整體可靠性的角度來看,一般還是認為第一種相對高一點。因為客戶端到kafka這一條鏈路,業務之前都已經跑穩定了。一般不會出問題。但是寫我們proxy就不一定了,在接入過程中,是有可能出現一些使用上的問題,導致數據寫入失敗,這就對業務方測試質量的要求會高一點。

然后消費的遷移過程,其實風險是相對比較低的。出問題的時候,可以立即回滾。因為它在老的kafka上消費進度,是一直保留的,而且在遷移過程中,可以認為是全量雙消費。

以上就是數據雙寫的遷移方案,這種方案的特點就是兩個存儲引擎都有相同的全量數據。

3.2.2 雙讀

特點:保證不會重復消費。對于p2p 或者消費下游不太多,或者對重復消費數據比較敏感的場景比較適用。

image-20181014192956694.png

這個方案的過程是這樣的,消費先切換。全部遷移到到我們的proxy上消費,proxy從kafka上獲取。這個時候rocketmq上沒有流量。但是我們的消費proxy保證了雙消費,一旦rocketmq有流量了,客戶端同樣也能收到。

然后生產方改造客戶端,直接切流到rocketmq中,這樣就完成了整個流量遷移過程。

運行一段時間,比如kafka里的數據都過期之后,就可以把消費proxy上的雙消費關了,下掉kafka集群。

整個過程中,生產直接切流,所以數據不會重復存儲。然后在消費遷移的過程中,我們消費proxy上的group和業務原有的group可以用一個名字,這樣就能實現遷移過程中自動rebalance,這樣就能實現沒有大量重復數據的效果。

所以這個方案對重復消費比較敏感的業務會比較適合的。

這個方案的整個過程中,消費方和生產方都只需要改造一遍客戶端,上一次線就可以完成。

4. RocketMQ擴展改造

說完遷移方案,這里再簡單介紹一下,我們在我們的rocketmq分支上做的一些比較重要的事情。

首先一個非常重要的一點是主從的自動切換。熟悉RocketMQ的同學應該知道,目前開源版本的RocketMQ broker 是沒有主從自動切換的。如果你的master掛了,那你就寫不進去了。然后slave只能提供只讀的功能。

當然如果你的topic在多個主節點上都創建了,雖然不會完全寫不進去,但是對單分片順序消費的場景,還是會產生影響。

所以呢,我們就自己加了一套主從自動切換的功能。

第二個是批量生產的功能。RocketMQ 4.0之后的版本是支持批量生產功能的。但是限制了,只能是同一個ConsumerQueue的。這個對于我們的proxy服務來說,不太友好,因為我們的proxy是有多個不同的topic的,所以我們就擴展了一下,讓它能夠支持不同topic、不同consume queue。原理上其實差不多,只是在傳輸的時候,把topic和consumerqueue的信息都編碼進去。

第三個,目前RocketMQ 單機能夠支持的topic數量,基本在幾萬這么一個量級,在增加上去之后,元信息的管理就會非常耗時,對整個吞吐的性能影響相對來說就會非常大。 然后我們有個場景又需要支持單機百萬左右的topic數量,所以我們就改造了一下元信息管理部分,讓RocketMQ單機能夠支撐的topic數量達到了百萬。

后面一些就不太重要了,比如集成了我們公司內部的一些監控和部署工具,修了幾個bug,也給提了PR。最新版都已經修掉了。

5. RocketMQ使用經驗

接下來,再簡單介紹一下,我們在 RocketMQ在使用和運維上的一些經驗。主要是涉及在磁盤IO性能不夠的時候,一些參數的調整。

5.1 讀老數據的問題

我們都知道,RocketMQ的數據是要落盤的,一般只有最新寫入的數據才會在PageCache中。比如下游消費數據,因為一些原因停了一天之后,又突然起來消費數據。這個時候就需要讀磁盤上的數據。

然后RocketMQ的消息體是全部存儲在一個append only的 commitlog 中的。如果這個集群中混雜了很多不同topic的數據的話,要讀的兩條消息就很有可能間隔很遠。最壞情況就是一次磁盤IO讀一條消息。這就基本等價于隨機讀取了。如果磁盤的IOPS(Input/Output Operations Per Second)扛不住,還會影響數據的寫入,這個問題就嚴重了。

值得慶幸的是,RocketMQ提供了自動從Slave讀取老數據的功能。這個功能主要由slaveReadEnable這個參數控制。默認是關的(slaveReadEnable = false by default)。推薦把它打開,主從都要開。

這個參數打開之后,在客戶端消費數據時,會判斷,當前讀取消息的物理偏移量跟最新的位置的差值,是不是超過了內存容量的一個百分比(accessMessageInMemoryMaxRatio = 40 by default)。如果超過了,就會告訴客戶端去備機上消費數據。如果采用異步主從,也就是brokerRole 等于ASYNC_AMSTER的時候,你的備機IO打爆,其實影響不太大。但是如果你采用同步主從,那還是有影響。所以這個時候,最好掛兩個備機。因為RocketMQ的主從同步復制,只要一個備機響應了確認寫入就可以了,一臺IO打爆,問題不大。

5.2 過期數據刪除

第二個是刪除過期數據的問題。

RocketMQ默認數據保留72個小時(fileReservedTime=72)。

然后它默認在凌晨4點開始刪過期數據(deleteWhen="04")。你可以設置多個值用分號隔開。

因為數據都是定時刪除的,所以在磁盤充足的情況,數據的最長保留會比你設置的還多一天。

因為默認都是同一時間,刪除一整天的數據,如果用了機械硬盤,一般磁盤容量會比較大,需要刪除的數據會特別多,這個就會導致在刪除數據的時候,磁盤IO被打滿。這個時候又要影響寫入了。

為了解決這個問題,可以嘗試多個方法,一個是設置文件刪除的間隔,有兩個參數可以設置,

  • deleteCommitLogFilesInterval = 100(毫秒)。每刪除10個commitLog文件的時間間隔。
  • deleteConsumeQueueFilesInterval=100(毫秒)。每刪除一個ConsumeQueue文件的時間間隔。

另外一個就是增加刪除頻率,把00-23都寫到deleteWhen,就可以實現每個小時都刪數據。

5.3 索引

最后一個功能是索引的問題。

默認情況下,所有的broker都會建立索引(messageIndexEnable=true)。這個索引功能可以支持按照消息的uniqId,消息的key來查詢消息體。

索引文件實現的時候,本質上也就是基于磁盤的個一個hashmap。

如果broker上消息數量比較多,查詢的頻率比較高,這也會造成一定的IO負載。

所以我們的推薦方案是在master上關掉了index功能,只在slave上打開。然后所有的index查詢全部在slave上進行。

當然這個需要簡單修改一下MQAdminImpl里的實現。因為默認情況下,它會向master發出請求。

2018年度最受歡迎開源軟件評選活動開始啦,喜歡 RocketMQ的,必須投上一票。
https://www.oschina.net/project/top_cn_2018

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,781評論 18 139
  • kafka的定義:是一個分布式消息系統,由LinkedIn使用Scala編寫,用作LinkedIn的活動流(Act...
    時待吾閱讀 5,346評論 1 15
  • 姓名:周小蓬 16019110037 轉載自:http://blog.csdn.net/YChenFeng/art...
    aeytifiw閱讀 34,737評論 13 425
  • 閱讀時間:2016.02.15 閱讀頁碼:第一章 八(4.5頁) 閱讀心得: 賣豆腐的一收了市,一天的事情都完了。...
    迷路的小豬閱讀 245評論 0 0
  • 一 窗外的雨依舊的大,我瞅了眼表,火車已經晚點三個小時了,而且晚點時間未知,原因未知…… 我并沒有表現出該有的焦急...
    十四花生閱讀 236評論 4 2