聊聊 Kafka: Kafka 為啥這么快?

點(diǎn)擊上方老周聊架構(gòu)關(guān)注我
一、前言
我們都知道 Kafka 是基于磁盤(pán)進(jìn)行存儲(chǔ)的,但 Kafka 官方又稱(chēng)其具有高性能、高吞吐、低延時(shí)的特點(diǎn),其吞吐量動(dòng)輒幾十上百萬(wàn)。小伙伴們是不是有點(diǎn)困惑了,一般認(rèn)為在磁盤(pán)上讀寫(xiě)數(shù)據(jù)是會(huì)降低性能的,因?yàn)閷ぶ窌?huì)比較消耗時(shí)間。那 Kafka 又是怎么做到其吞吐量動(dòng)輒幾十上百萬(wàn)的呢?
Kafka 高性能,是多方面協(xié)同的結(jié)果,包括宏觀架構(gòu)、分布式 partition 存儲(chǔ)、ISR 數(shù)據(jù)同步、以及“無(wú)所不用其極”的高效利用磁盤(pán)、操作系統(tǒng)特性。
別急,下面老周從數(shù)據(jù)的寫(xiě)入與讀取兩個(gè)維度來(lái)帶大家一探究竟。
二、順序?qū)懭?/span>
磁盤(pán)讀寫(xiě)有兩種方式:順序讀寫(xiě)或者隨機(jī)讀寫(xiě)。在順序讀寫(xiě)的情況下,磁盤(pán)的順序讀寫(xiě)速度和內(nèi)存持平。
因?yàn)榇疟P(pán)是機(jī)械結(jié)構(gòu),每次讀寫(xiě)都會(huì)尋址->寫(xiě)入,其中尋址是一個(gè)“機(jī)械動(dòng)作”。為了提高讀寫(xiě)磁盤(pán)的速度,Kafka 就是使用順序 I/O。

Kafka 利用了一種分段式的、只追加 (Append-Only) 的日志,基本上把自身的讀寫(xiě)操作限制為順序 I/O,也就使得它在各種存儲(chǔ)介質(zhì)上能有很快的速度。一直以來(lái),有一種廣泛的誤解認(rèn)為磁盤(pán)很慢。實(shí)際上,存儲(chǔ)介質(zhì) (特別是旋轉(zhuǎn)式的機(jī)械硬盤(pán)) 的性能很大程度依賴(lài)于訪(fǎng)問(wèn)模式。在一個(gè) 7200 轉(zhuǎn)/分鐘的 SATA 機(jī)械硬盤(pán)上,隨機(jī) I/O 的性能比順序 I/O 低了大概 3 到 4 個(gè)數(shù)量級(jí)。此外,一般來(lái)說(shuō)現(xiàn)代的操作系統(tǒng)都會(huì)提供預(yù)讀和延遲寫(xiě)技術(shù):以大數(shù)據(jù)塊的倍數(shù)預(yù)先載入數(shù)據(jù),以及合并多個(gè)小的邏輯寫(xiě)操作成一個(gè)大的物理寫(xiě)操作。正因?yàn)槿绱耍樞?I/O 和隨機(jī) I/O 之間的性能差距在 flash 和其他固態(tài)非易失性存儲(chǔ)介質(zhì)中仍然很明顯,盡管它遠(yuǎn)沒(méi)有旋轉(zhuǎn)式的存儲(chǔ)介質(zhì)那么明顯。
這里給出著名學(xué)術(shù)期刊 ACM Queue 上的性能對(duì)比圖:https://queue.acm.org/detail.cf

下圖就展示了 Kafka 是如何寫(xiě)入數(shù)據(jù)的, 每一個(gè) Partition 其實(shí)都是一個(gè)文件 ,收到消息后 Kafka 會(huì)把數(shù)據(jù)插入到文件末尾(虛框部分):

這種方法采用了只讀設(shè)計(jì) ,所以 Kafka 是不會(huì)修改、刪除數(shù)據(jù)的,它會(huì)把所有的數(shù)據(jù)都保留下來(lái),每個(gè)消費(fèi)者(Consumer)對(duì)每個(gè) Topic 都有一個(gè) offset 用來(lái)表示讀取到了第幾條數(shù)據(jù) 。

磁盤(pán)的順序讀寫(xiě)是磁盤(pán)使用模式中最有規(guī)律的,并且操作系統(tǒng)也對(duì)這種模式做了大量?jī)?yōu)化,Kafka 就是使用了磁盤(pán)順序讀寫(xiě)來(lái)提升的性能。Kafka 的 message 是不斷追加到本地磁盤(pán)文件末尾的,而不是隨機(jī)的寫(xiě)入,這使得 Kafka 寫(xiě)入吞吐量得到了顯著提升。
三、頁(yè)緩存
即便是順序?qū)懭胗脖P(pán),硬盤(pán)的訪(fǎng)問(wèn)速度還是不可能追上內(nèi)存。所以 Kafka 的數(shù)據(jù)并不是實(shí)時(shí)的寫(xiě)入硬盤(pán) ,它充分利用了現(xiàn)代操作系統(tǒng)分頁(yè)存儲(chǔ)來(lái)利用內(nèi)存提高 I/O 效率。具體來(lái)說(shuō),就是把磁盤(pán)中的數(shù)據(jù)緩存到內(nèi)存中,把對(duì)磁盤(pán)的訪(fǎng)問(wèn)變?yōu)閷?duì)內(nèi)存的訪(fǎng)問(wèn)。
Kafka 接收來(lái)自 socket buffer 的網(wǎng)絡(luò)數(shù)據(jù),應(yīng)用進(jìn)程不需要中間處理、直接進(jìn)行持久化時(shí)。可以使用 mmap 內(nèi)存文件映射。
3.1 Memory Mapped Files
簡(jiǎn)稱(chēng) mmap,簡(jiǎn)單描述其作用就是:將磁盤(pán)文件映射到內(nèi)存,用戶(hù)通過(guò)修改內(nèi)存就能修改磁盤(pán)文件。
它的工作原理是直接利用操作系統(tǒng)的 Page 來(lái)實(shí)現(xiàn)磁盤(pán)文件到物理內(nèi)存的直接映射。完成映射之后你對(duì)物理內(nèi)存的操作會(huì)被同步到硬盤(pán)上(操作系統(tǒng)在適當(dāng)?shù)臅r(shí)候)。

通過(guò) mmap,進(jìn)程像讀寫(xiě)硬盤(pán)一樣讀寫(xiě)內(nèi)存(當(dāng)然是虛擬機(jī)內(nèi)存)。使用這種方式可以獲取很大的 I/O 提升,省去了用戶(hù)空間到內(nèi)核空間復(fù)制的開(kāi)銷(xiāo)。
mmap 也有一個(gè)很明顯的缺陷:不可靠,寫(xiě)到 mmap 中的數(shù)據(jù)并沒(méi)有被真正的寫(xiě)到硬盤(pán),操作系統(tǒng)會(huì)在程序主動(dòng)調(diào)用 flush 的時(shí)候才把數(shù)據(jù)真正的寫(xiě)到硬盤(pán)。
Kafka 提供了一個(gè)參數(shù) producer.type 來(lái)控制是不是主動(dòng) flush:
如果 Kafka 寫(xiě)入到 mmap 之后就立即 flush,然后再返回 Producer 叫同步(sync);
寫(xiě)入 mmap 之后立即返回 Producer 不調(diào)用 flush 叫異步(async)。
3.2 Java NIO 對(duì)文件映射的支持
Java NIO,提供了一個(gè) MappedByteBuffer 類(lèi)可以用來(lái)實(shí)現(xiàn)內(nèi)存映射。
MappedByteBuffer 只能通過(guò)調(diào)用 FileChannel 的 map() 取得,再?zèng)]有其他方式。
FileChannel.map() 是抽象方法,具體實(shí)現(xiàn)是在 FileChannelImpl.map() 可自行查看 JDK 源碼,其 map0() 方法就是調(diào)用了 Linux 內(nèi)核的 mmap 的 API。



3.3 使用 MappedByteBuffer 類(lèi)注意事項(xiàng)
mmap 的文件映射,在 full gc 時(shí)才會(huì)進(jìn)行釋放。當(dāng) close 時(shí),需要手動(dòng)清除內(nèi)存映射文件,可以反射調(diào)用 sun.misc.Cleaner 方法。
當(dāng)一個(gè)進(jìn)程準(zhǔn)備讀取磁盤(pán)上的文件內(nèi)容時(shí):
操作系統(tǒng)會(huì)先查看待讀取的數(shù)據(jù)所在的頁(yè)(page)是否在頁(yè)緩存(pagecache)中,如果存在(命中) 則直接返回?cái)?shù)據(jù),從而避免了對(duì)物理磁盤(pán)的 I/O 操作;
如果沒(méi)有命中,則操作系統(tǒng)會(huì)向磁盤(pán)發(fā)起讀取請(qǐng)求并將讀取的數(shù)據(jù)頁(yè)存入頁(yè)緩存,之后再將數(shù)據(jù)返回給進(jìn)程。
如果一個(gè)進(jìn)程需要將數(shù)據(jù)寫(xiě)入磁盤(pán):
操作系統(tǒng)也會(huì)檢測(cè)數(shù)據(jù)對(duì)應(yīng)的頁(yè)是否在頁(yè)緩存中,如果不存在,則會(huì)先在頁(yè)緩存中添加相應(yīng)的頁(yè),最后將數(shù)據(jù)寫(xiě)入對(duì)應(yīng)的頁(yè)。
被修改過(guò)后的頁(yè)也就變成了臟頁(yè),操作系統(tǒng)會(huì)在合適的時(shí)間把臟頁(yè)中的數(shù)據(jù)寫(xiě)入磁盤(pán),以保持?jǐn)?shù)據(jù)的一致性。
對(duì)一個(gè)進(jìn)程而言,它會(huì)在進(jìn)程內(nèi)部緩存處理所需的數(shù)據(jù),然而這些數(shù)據(jù)有可能還緩存在操作系統(tǒng)的頁(yè)緩存中,因此同一份數(shù)據(jù)有可能被緩存了兩次。并且,除非使用 Direct I/O 的方式, 否則頁(yè)緩存很難被禁止。
當(dāng)使用頁(yè)緩存的時(shí)候,即使 Kafka 服務(wù)重啟, 頁(yè)緩存還是會(huì)保持有效,然而進(jìn)程內(nèi)的緩存卻需要重建。這樣也極大地簡(jiǎn)化了代碼邏輯,因?yàn)榫S護(hù)頁(yè)緩存和文件之間的一致性交由操作系統(tǒng)來(lái)負(fù)責(zé),這樣會(huì)比進(jìn)程內(nèi)維護(hù)更加安全有效。
Kafka 中大量使用了頁(yè)緩存,這是 Kafka 實(shí)現(xiàn)高吞吐的重要因素之一。
消息先被寫(xiě)入頁(yè)緩存,由操作系統(tǒng)負(fù)責(zé)刷盤(pán)任務(wù)。
四、零拷貝
零拷貝并不是不需要拷貝,而是減少不必要的拷貝次數(shù)。通常是說(shuō)在IO讀寫(xiě)過(guò)程中。
nginx 的高性能也有零拷貝的身影。
4.1 傳統(tǒng) IO
比如:讀取文件,socket 發(fā)送。
傳統(tǒng)方式實(shí)現(xiàn):先讀取、再發(fā)送,實(shí)際經(jīng)過(guò) 1~4 四次 copy。
buffer=File.read
Socket.send(buffer)第一次:將磁盤(pán)文件讀取到操作系統(tǒng)內(nèi)核緩沖區(qū);
第二次:將內(nèi)核緩沖區(qū)的數(shù)據(jù),copy 到 application 應(yīng)用程序的 buffer;
第三步:將 application 應(yīng)用程序 buffer 中的數(shù)據(jù),copy 到 socket 網(wǎng)絡(luò)發(fā)送緩沖區(qū)(屬于操作系統(tǒng)內(nèi)核的緩沖區(qū));
第四次:將 socket buffer 的數(shù)據(jù),copy 到網(wǎng)絡(luò)協(xié)議棧,由網(wǎng)卡進(jìn)行網(wǎng)絡(luò)傳輸。

實(shí)際 IO 讀寫(xiě),需要進(jìn)行 IO 中斷,需要 CPU 響應(yīng)中斷(內(nèi)核態(tài)到用戶(hù)態(tài)轉(zhuǎn)換),盡管引入DMA(Direct Memory Access,直接存儲(chǔ)器訪(fǎng)問(wèn))來(lái)接管 CPU 的中斷請(qǐng)求,但四次 copy 是存在“不必要的拷貝”的。
實(shí)際上并不需要第二個(gè)和第三個(gè)數(shù)據(jù)副本。數(shù)據(jù)可以直接從讀緩沖區(qū)傳輸?shù)教捉幼志彌_區(qū)。
4.2 kafka 的兩個(gè)過(guò)程
網(wǎng)絡(luò)數(shù)據(jù)持久化到磁盤(pán) (Producer 到 Broker)
磁盤(pán)文件通過(guò)網(wǎng)絡(luò)發(fā)送 (Broker 到 Consumer)
數(shù)據(jù)落盤(pán)通常都是非實(shí)時(shí)的,Kafka 的數(shù)據(jù)并不是實(shí)時(shí)的寫(xiě)入硬盤(pán),它充分利用了現(xiàn)代操作系統(tǒng)分頁(yè)存儲(chǔ)來(lái)利用內(nèi)存提高 I/O 效率。
磁盤(pán)文件通過(guò)網(wǎng)絡(luò)發(fā)送 (Broker 到 Consumer)
磁盤(pán)數(shù)據(jù)通過(guò)DMA(Direct Memory Access,直接存儲(chǔ)器訪(fǎng)問(wèn))拷貝到內(nèi)核態(tài) Buffer。
直接通過(guò) DMA 拷貝到 NIC Buffer(socket buffer),無(wú)需 CPU 拷貝。

除了減少數(shù)據(jù)拷貝外,整個(gè)讀文件 ==> 網(wǎng)絡(luò)發(fā)送由一個(gè) sendfile 調(diào)用完成,整個(gè)過(guò)程只有兩次上下文切換,因此大大提高了性能。
Java NIO對(duì)sendfile的支持就是FileChannel.transferTo()/transferFrom()。
fileChannel.transferTo(position, count, socketChannel);
把磁盤(pán)文件讀取OS內(nèi)核緩沖區(qū)后的fileChannel,直接轉(zhuǎn)給socketChannel發(fā)送;底層就是 sendfile。消費(fèi)者從broker讀取數(shù)據(jù),就是由此實(shí)現(xiàn)。
具體來(lái)看,Kafka 的數(shù)據(jù)傳輸通過(guò) TransportLayer 來(lái)完成,其子類(lèi) PlaintextTransportLayer 通過(guò) Java NIO 的 FileChannel 的 transferTo 和 transferFrom 方法實(shí)現(xiàn)零拷貝。

注: transferTo 和 transferFrom 并不保證一定能使用零拷貝,需要操作系統(tǒng)支持。
Linux 2.4+ 內(nèi)核通過(guò) sendfile 系統(tǒng)調(diào)用,提供了零拷貝。
有些小伙伴們可能對(duì)零拷貝不太熟悉?沒(méi)關(guān)系,這里老周準(zhǔn)備了一篇之前專(zhuān)門(mén)寫(xiě)零拷貝的文章,這篇被很多像 CSDN、51CTO 等大平臺(tái)轉(zhuǎn)發(fā)過(guò)!請(qǐng)戳:零拷貝技術(shù)在Java中為何這么牛?
五、Broker 性能
5.1 日志記錄批處理
順序 I/O 在大多數(shù)的存儲(chǔ)介質(zhì)上都非常快,幾乎可以和網(wǎng)絡(luò) I/O 的峰值性能相媲美。在實(shí)踐中,這意味著一個(gè)設(shè)計(jì)良好的日志結(jié)構(gòu)的持久層將可以緊隨網(wǎng)絡(luò)流量的速度。事實(shí)上,Kafka 的瓶頸通常是網(wǎng)絡(luò)而非磁盤(pán)。因此,除了由操作系統(tǒng)提供的底層批處理能力之外,Kafka 的 Clients 和 Brokers 會(huì)把多條讀寫(xiě)的日志記錄合并成一個(gè)批次,然后才通過(guò)網(wǎng)絡(luò)發(fā)送出去。日志記錄的批處理通過(guò)使用更大的包以及提高帶寬效率來(lái)攤薄網(wǎng)絡(luò)往返的開(kāi)銷(xiāo)。
5.2 批量壓縮
當(dāng)啟用壓縮功能時(shí),批處理的影響尤為明顯,因?yàn)閴嚎s效率通常會(huì)隨著數(shù)據(jù)量大小的增加而變得更高。特別是當(dāng)使用 JSON 等基于文本的數(shù)據(jù)格式時(shí),壓縮效果會(huì)非常顯著,壓縮比通常能達(dá)到 5 到 7 倍。此外,日志記錄批處理在很大程度上是作為 Client 側(cè)的操作完成的,此舉把負(fù)載轉(zhuǎn)移到 Client 上,不僅對(duì)網(wǎng)絡(luò)帶寬效率、而且對(duì) Brokers 的磁盤(pán) I/O 利用率也有很大的提升。
5.3 非強(qiáng)制刷新緩沖寫(xiě)操作
另一個(gè)助力 Kafka 高性能、同時(shí)也是一個(gè)值得更進(jìn)一步去探究的底層原因:Kafka 在確認(rèn)寫(xiě)成功 ACK 之前的磁盤(pán)寫(xiě)操作不會(huì)真正調(diào)用 fsync 命令;通常只需要確保日志記錄被寫(xiě)入到 I/O Buffer 里就可以給 Client 回復(fù) ACK 信號(hào)。這是一個(gè)鮮為人知卻至關(guān)重要的事實(shí):事實(shí)上,這正是讓 Kafka 能表現(xiàn)得如同一個(gè)內(nèi)存型消息隊(duì)列的原因 —— 因?yàn)?Kafka 是一個(gè)基于磁盤(pán)的內(nèi)存型消息隊(duì)列 (受緩沖區(qū)/頁(yè)面緩存大小的限制)。
另一方面,這種形式的寫(xiě)入是不安全的,因?yàn)楦北镜膶?xiě)失敗可能會(huì)導(dǎo)致數(shù)據(jù)丟失,即使日志記錄似乎已經(jīng)被確認(rèn)成功。換句話(huà)說(shuō),與關(guān)系型數(shù)據(jù)庫(kù)不同,確認(rèn)一個(gè)寫(xiě)操作成功并不等同于持久化成功。真正使得 Kafka 具備持久化能力的是運(yùn)行多個(gè)同步的副本的設(shè)計(jì);即便有一個(gè)副本寫(xiě)失敗了,其他的副本(假設(shè)有多個(gè))仍然可以保持可用狀態(tài),前提是寫(xiě)失敗是不相關(guān)的(例如,多個(gè)副本由于一個(gè)共同的上游故障而同時(shí)寫(xiě)失敗)。因此,不使用 fsync 的 I/O 非阻塞方法和冗余同步副本的結(jié)合,使得 Kafka 同時(shí)具備了高吞吐量、持久性和可用性。
六、流數(shù)據(jù)并行
日志結(jié)構(gòu) I/O 的效率是影響性能的一個(gè)關(guān)鍵因素,主要影響寫(xiě)操作;Kafka 在對(duì) Topic 結(jié)構(gòu)和 Consumer 群組的并行處理是其讀性能的基礎(chǔ)。這種組合產(chǎn)生了非常高的端到端消息傳遞總體吞吐量。并發(fā)性根深蒂固地存在于 Kafka 的分區(qū)方案和 Consumer Groups 的操作中,這是 Kafka 中一種有效的負(fù)載均衡機(jī)制 —— 把數(shù)據(jù)分區(qū) (Partition) 近似均勻地分配給組內(nèi)的各個(gè) Consumer 實(shí)例。將此與更傳統(tǒng)的 MQ 進(jìn)行比較:在 RabbitMQ 的等效設(shè)置中,多個(gè)并發(fā)的 Consumers 可能以輪詢(xún)的方式從隊(duì)列讀取數(shù)據(jù),然而這樣做,就會(huì)失去消息消費(fèi)的順序性。
分區(qū)機(jī)制也使得 Kafka Brokers 可以水平擴(kuò)展。每個(gè)分區(qū)都有一個(gè)專(zhuān)門(mén)的 Leader;因此,任何重要的主題 Topic (具有多個(gè)分區(qū)) 都可以利用整個(gè) Broker 集群進(jìn)行寫(xiě)操作,這是 Kafka 和消息隊(duì)列之間的另一個(gè)區(qū)別;后者利用集群來(lái)獲得可用性,而 Kafka 將真正地在 Brokers 之間負(fù)載均衡,以獲得可用性、持久性和吞吐量。
生產(chǎn)者在發(fā)布日志記錄之時(shí)指定分區(qū),假設(shè)你正在發(fā)布消息到一個(gè)有多個(gè)分區(qū)的 Topic 上。(也可能有單一分區(qū)的 Topic, 這種情況下將不成問(wèn)題。) 這可以通過(guò)直接指定分區(qū)索引來(lái)完成,或者間接通過(guò)日志記錄的鍵值來(lái)完成,該鍵值能被確定性地哈希到一個(gè)一致的 (即每次都相同) 分區(qū)索引。擁有相同哈希值的日志記錄將會(huì)被存儲(chǔ)到同一個(gè)分區(qū)中。假設(shè)一個(gè) Topic 有多個(gè)分區(qū),那些不同哈希值的日志記錄將很可能最后被存儲(chǔ)到不同的分區(qū)里。但是,由于哈希碰撞的緣故,不同哈希值的日志記錄也可能最后被存儲(chǔ)到相同的分區(qū)里。這是哈希的本質(zhì),如果你理解哈希表的原理,那應(yīng)該是顯而易見(jiàn)的。
日志記錄的實(shí)際處理是由一個(gè)在 (可選的) Consumer Group 中的 Consumer 操作完成。Kafka 確保一個(gè)分區(qū)最多只能分配給它的 Consumer Group 中的一個(gè) Consumer 。(我們說(shuō) "最多" 是因?yàn)榭紤]到一種全部 Consumer 都離線(xiàn)的情況。) 當(dāng)?shù)谝粋€(gè) Consumer Group 里的 Consumer 訂閱了 Topic,它將消費(fèi)這個(gè) Topic 下的所有分區(qū)的數(shù)據(jù)。當(dāng)?shù)诙€(gè) Consumer 緊隨其后加入訂閱時(shí),它將大致獲得這個(gè) Topic 的一半分區(qū),減輕第一個(gè) Consumer 先前負(fù)荷的一半。這使得你能夠并行處理事件流,并根據(jù)需要增加 Consumer (理想情況下,使用自動(dòng)伸縮機(jī)制),前提是你已經(jīng)對(duì)事件流進(jìn)行了合理的分區(qū)。
日志記錄吞吐量的控制一般通過(guò)以下兩種方式來(lái)達(dá)成:
Topic 的分區(qū)方案。應(yīng)該對(duì) Topics 進(jìn)行分區(qū),以最大限度地增加獨(dú)立子事件流的數(shù)量。換句話(huà)說(shuō),日志記錄的順序應(yīng)該只保留在絕對(duì)必要的地方。如果任意兩個(gè)日志記錄在某種意義上沒(méi)有合理的關(guān)聯(lián),那它們就不應(yīng)該被綁定到同一個(gè)分區(qū)。這暗示你要使用不同的鍵值,因?yàn)?Kafka 將使用日志記錄的鍵值作為一個(gè)散列源來(lái)派生其一致的分區(qū)映射。
一個(gè)組里的 Consumers 數(shù)量。你可以增加 Consumer Group 里的 Consumer 數(shù)量來(lái)均衡入站的日志記錄的負(fù)載,這個(gè)數(shù)量的上限是 Topic 的分區(qū)數(shù)量。(如果你愿意的話(huà),你當(dāng)然可以增加更多的 Consumers ,不過(guò)分區(qū)計(jì)數(shù)將會(huì)設(shè)置一個(gè)上限來(lái)確保每一個(gè)活躍的 Consumer 至少被指派到一個(gè)分區(qū),多出來(lái)的 Consumers 將會(huì)一直保持在一個(gè)空閑的狀態(tài)。) 請(qǐng)注意, Consumer 可以是進(jìn)程或線(xiàn)程。依據(jù) Consumer 執(zhí)行的工作負(fù)載類(lèi)型,你可以在線(xiàn)程池中使用多個(gè)獨(dú)立的 Consumer 線(xiàn)程或進(jìn)程記錄。
如果你之前一直想知道 Kafka 是否很快、它是如何擁有其現(xiàn)如今公認(rèn)的高性能標(biāo)簽,或者它是否可以滿(mǎn)足你的使用場(chǎng)景,那么相信你現(xiàn)在應(yīng)該有了所需的答案。
為了讓事情足夠清楚,必須說(shuō)明 Kafka 并不是最快的 (也就是說(shuō),具有最大吞吐量能力的) 消息傳遞中間件,還有其他具有更大吞吐量的平臺(tái) —— 有些是基于軟件的 —— 有些是在硬件中實(shí)現(xiàn)的。Apache Pulsar 是一項(xiàng)極具前景的技術(shù),它具備可擴(kuò)展性,在提供相同的消息順序性和持久性保證的同時(shí),還能實(shí)現(xiàn)更好的吞吐量-延遲效果。使用 Kafka 的根本原因是,它作為一個(gè)完整的生態(tài)系統(tǒng)仍然是無(wú)與倫比的。它展示了卓越的性能,同時(shí)提供了一個(gè)豐富和成熟而且還在不斷進(jìn)化的環(huán)境,盡管 Kafka 的規(guī)模已經(jīng)相當(dāng)龐大了,但仍以一種令人羨慕的速度在成長(zhǎng)。
Kafka 的設(shè)計(jì)者和維護(hù)者們?cè)趧?chuàng)造一個(gè)以性能導(dǎo)向?yàn)楹诵牡慕鉀Q方案這方面做得非常出色。它的大多數(shù)設(shè)計(jì)/理念元素都是早期就構(gòu)思完成、幾乎沒(méi)有什么是事后才想到的,也沒(méi)有什么是附加的。從把工作負(fù)載分?jǐn)偟?Client 到 Broker 上的日志結(jié)構(gòu)持久性,批處理、壓縮、零拷貝 I/O 和流數(shù)據(jù)級(jí)并行 —— Kafka 向幾乎所有其他面向消息的中間件 (商業(yè)的或開(kāi)源的) 發(fā)起了挑戰(zhàn)。而且最令人嘆為觀止的是,它做到這些事情的同時(shí)竟然沒(méi)有犧牲掉持久性、日志記錄順序性和至少交付一次的語(yǔ)義等特性。
七、總結(jié)
7.1 mmap 和 sendfile
Linux 內(nèi)核提供、實(shí)現(xiàn)零拷貝的 API。
mmap 將磁盤(pán)文件映射到內(nèi)存,支持讀和寫(xiě),對(duì)內(nèi)存的操作會(huì)反映在磁盤(pán)文件上。
sendfile 是將讀到內(nèi)核空間的數(shù)據(jù),轉(zhuǎn)到 socket buffer,進(jìn)行網(wǎng)絡(luò)發(fā)送。
RocketMQ 在消費(fèi)消息時(shí),使用了 mmap;Kafka 使用了 sendfile。
7.2 Kafka 為啥這么快?
Partition 順序讀寫(xiě),充分利用磁盤(pán)特性,這是基礎(chǔ)。
Producer 生產(chǎn)的數(shù)據(jù)持久化到 Broker,采用 mmap 文件映射,實(shí)現(xiàn)順序的快速寫(xiě)入。
Customer 從 Broker 讀取數(shù)據(jù),采用 sendfile,將磁盤(pán)文件讀到 OS 內(nèi)核緩沖區(qū)后,直接轉(zhuǎn)到 socket buffer 進(jìn)行網(wǎng)絡(luò)發(fā)送。
Broker 性能優(yōu)化:日志記錄批處理、批量壓縮、非強(qiáng)制刷新緩沖寫(xiě)操作等。
流數(shù)據(jù)并行
歡迎大家關(guān)注我的公眾號(hào)【老周聊架構(gòu)】,Java后端主流技術(shù)棧的原理、源碼分析、架構(gòu)以及各種互聯(lián)網(wǎng)高并發(fā)、高性能、高可用的解決方案。
喜歡的話(huà),點(diǎn)贊、再看、分享三連。

點(diǎn)個(gè)在看你最好看
