RocketMQ 為什么性能不如 Kafka?
在上篇文章《rocketmq 是什么》中,我們了解到 RocketMQ 的架構(gòu)其實參考了 kafka 的設計思想,同時又在 kafka 的基礎上做了一些調(diào)整??雌饋?,RocketMQ 好像各方面都比 kafka 更能打。
圖片
但 kafka 卻一直沒被淘汰,說明 RocketMQ 必然是有著不如 kafka 的地方。是啥呢? 性能,嚴格來說是吞吐量。阿里中間件團隊對它們做過壓測,同樣條件下,kafka 比 RocketMQ 快 50%左右。但即使這樣,RocketMQ 依然能每秒處理 10w 量級的數(shù)據(jù),依舊非常能打。你不能說 RocketMQ 弱,只能說 Kafka 性能太強了。
不過這就很奇怪了,為什么 RocketMQ 參考了 kafka 的架構(gòu),卻不能跟 kafka 保持一樣的性能呢?在回答這個問題之前,我們來聊下什么是零拷貝。
零拷貝是什么
我們知道,消息隊列的消息為了防止進程崩潰后丟失,一般不會放內(nèi)存里,而是放磁盤上。那么問題就來了,消息從消息隊列的磁盤,發(fā)送到消費者,過程是怎么樣的呢?
消息的發(fā)送過程
操作系統(tǒng)分為用戶空間和內(nèi)核空間。程序處于用戶空間,而磁盤屬于硬件,操作系統(tǒng)本質(zhì)上是程序和硬件設備的一個中間層。程序需要通過操作系統(tǒng)去調(diào)用硬件能力。
圖片
如果用戶想要將數(shù)據(jù)從磁盤發(fā)送到網(wǎng)絡。那么就會發(fā)生下面這幾件事:程序會發(fā)起系統(tǒng)調(diào)用read(),嘗試讀取磁盤數(shù)據(jù),
? 磁盤數(shù)據(jù)從設備拷貝到內(nèi)核空間的緩沖區(qū)。
? 再從內(nèi)核空間的緩沖區(qū)拷貝到用戶空間。
程序再發(fā)起系統(tǒng)調(diào)用write(),將讀到的數(shù)據(jù)發(fā)到網(wǎng)絡:
? 數(shù)據(jù)從用戶空間拷貝到 socket 發(fā)送緩沖區(qū)
? 再從 socket 發(fā)送緩沖區(qū)拷貝到網(wǎng)卡。
最終數(shù)據(jù)就會經(jīng)過網(wǎng)絡到達消費者。
圖片
整個過程,本機內(nèi)發(fā)生了 2 次系統(tǒng)調(diào)用,對應 4 次用戶空間和內(nèi)核空間的切換,以及 4 次數(shù)據(jù)拷貝。
圖片
一頓操作猛如虎,結(jié)果就是同樣一份數(shù)據(jù)來回拷貝。有沒有辦法優(yōu)化呢?有,它就是零拷貝技術(shù),常見的方案有兩種,分別是 mmap 和 sendfile。我們來看下它們是什么。
mmap 是什么
mmap 是操作系統(tǒng)內(nèi)核提供的一個方法,可以將內(nèi)核空間的緩沖區(qū)映射到用戶空間。
圖片
用了它,整個發(fā)送流程就有了一些變化。程序發(fā)起系統(tǒng)調(diào)用mmap(),嘗試讀取磁盤數(shù)據(jù),具體情況如下:
? 磁盤數(shù)據(jù)從設備拷貝到內(nèi)核空間的緩沖區(qū)。
? 內(nèi)核空間的緩沖區(qū)映射到用戶空間,這里不需要拷貝。
程序再發(fā)起系統(tǒng)調(diào)用write(),將讀到的數(shù)據(jù)發(fā)到網(wǎng)絡:
? 數(shù)據(jù)從內(nèi)核空間緩沖區(qū)拷貝到 socket 發(fā)送緩沖區(qū)。
? 再從 socket 發(fā)送緩沖區(qū)拷貝到網(wǎng)卡。
圖片
整個過程,發(fā)生了 2 次系統(tǒng)調(diào)用,對應 4 次用戶空間和內(nèi)核空間的切換,以及 3 次數(shù)據(jù)拷貝,對比之前,省下一次內(nèi)核空間到用戶空間的拷貝。
圖片
看到這里大家估計也蒙了,不是說零拷貝嗎?怎么還有 3 次拷貝。mmap 作為一種零拷貝技術(shù),指的是用戶空間到內(nèi)核空間這個過程不需要拷貝,而不是指數(shù)據(jù)從磁盤到發(fā)送到網(wǎng)卡這個過程零拷貝。
圖片
確實省了一點,但不多。有沒有更徹底的零拷貝?有,用 sendfile.
sendfile 是什么
sendfile,也是內(nèi)核提供的一個方法,從名字可以看出,就是用來發(fā)送文件數(shù)據(jù)的。程序發(fā)起系統(tǒng)調(diào)用sendfile(),內(nèi)核會嘗試讀取磁盤數(shù)據(jù)然后發(fā)送,具體情況如下:
? 磁盤數(shù)據(jù)從設備拷貝到內(nèi)核空間的緩沖區(qū)。
? 內(nèi)核空間緩沖區(qū)里的數(shù)據(jù)可以直接拷貝到網(wǎng)卡。
圖片
整個過程,發(fā)生了 1 次系統(tǒng)調(diào)用,對應 2 次用戶空間和內(nèi)核空間的切換,以及 2 次數(shù)據(jù)拷貝。這時候問題很多的小明就有意見了,說好的零拷貝怎么還有 2 次拷貝?
圖片
其實,這里的零拷貝指的是零 CPU拷貝。也就是說 sendfile 場景下,需要的兩次拷貝,都不是 CPU 直接參與的拷貝,而是其他硬件設備技術(shù)做的拷貝,不耽誤我們 CPU 跑程序。
kafka 為什么性能比 RocketMQ 好
聊完兩種零拷貝技術(shù),我們回過頭來看下 kafka 為什么性能比 RocketMQ 好。這是因為 RocketMQ 使用的是 mmap 零拷貝技術(shù),而 kafka 使用的是 sendfile。kafka 以更少的拷貝次數(shù)以及系統(tǒng)內(nèi)核切換次數(shù),獲得了更高的性能。但問題又來了,為什么 RocketMQ 不使用 sendfile?參考 kafka 抄個作業(yè)也不難?。课覀儊砜聪?nbsp;sendfile 函數(shù)長啥樣。
ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);
// num = sendfile(xxx);
再來看下 mmap 函數(shù)長啥樣。
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
// buf = mmap(xxx)
注釋里寫的是兩個函數(shù)的用法,mmap 返回的是數(shù)據(jù)的具體內(nèi)容,應用層能獲取到消息內(nèi)容并進行一些邏輯處理。
圖片
而 sendfile 返回的則是發(fā)送成功了幾個字節(jié)數(shù),具體發(fā)了什么內(nèi)容,應用層根本不知道。
圖片
而 RocketMQ 的一些功能,卻需要了解具體這個消息內(nèi)容,方便二次投遞等,比如將消費失敗的消息重新投遞到死信隊列中,如果 RocketMQ 使用 sendfile,那根本沒機會獲取到消息內(nèi)容長什么樣子,也就沒辦法實現(xiàn)一些好用的功能了。
圖片
而 kafka 卻沒有這些功能特性,追求極致性能,正好可以使用 sendfile。
除了零拷貝以外,kafka 高性能的原因還有很多,比如什么批處理,數(shù)據(jù)壓縮啥的,但那些優(yōu)化手段 rocketMQ 也都能借鑒一波,唯獨這個零拷貝,那是毫無辦法。
圖片
所以還是那句話,沒有一種架構(gòu)是完美的,一種架構(gòu)往往用于適配某些場景,你很難做到既要又要還要。當場景不同,我們就需要做一些定制化改造,通過犧牲一部分能力去換取另一部分能力。做架構(gòu),做到最后都是在做折中。是不是感覺升華了。
kafka 和 RocketMQ 怎么選?
這時候大家估計還是想知道 kafka 和 RocketMQ 到底該怎么選,用哪個。官方點的回答是"這個要看場景的"。說了等于沒說。這不是我的風格。我的標準只有一個,如果是大數(shù)據(jù)場景,比如你能頻繁聽到 spark,flink 這些關(guān)鍵詞的時候,那就用 kafka。除此之外,如果公司組件支持,盡量用 RocketMQ。
現(xiàn)在大家通了嗎?