建議先自行學(xué)習(xí) Kafka 的基礎(chǔ)知識(shí)內(nèi)容引镊。
通常我們講 Kafka 是一個(gè)高可靠猴蹂、高吞吐的分布式數(shù)據(jù)流系統(tǒng)退子。本文只討論其高吞吐的特性。思考幾個(gè)問(wèn)題:
- Kafka 的哪些設(shè)計(jì)促成了其高吞吐的特性
- 對(duì)比一般的消息隊(duì)列(RabbitMQ等)宫仗,Kafka 怎么做到高吞吐的情況下還能保存大量消息
Partion 模型帶來(lái)的高吞吐
Topic 下的 Partion 概念,可以橫向擴(kuò)展旁仿,部署到多臺(tái)服務(wù)器上藕夫。故此不論網(wǎng)絡(luò) I/O 還是服務(wù)器的本地 I/O 都能擴(kuò)展,特別是針對(duì)消費(fèi)端需要高 CPU 計(jì)算的場(chǎng)景枯冈,通過(guò)增加 Partion 數(shù)量和對(duì)應(yīng) Consumer Group 中 Consumer 的數(shù)量毅贮,來(lái)提升系統(tǒng)的吞吐量。
生產(chǎn)環(huán)節(jié):
消費(fèi)環(huán)節(jié):
配合下面的機(jī)制尘奏,Partion 可以說(shuō)是 Kafka 并行起來(lái)的基礎(chǔ)滩褥。Partion 內(nèi)部是由 Segment 文件組成的,這點(diǎn)和 Elasticsearch 相似炫加。
Broker 層面
磁盤順序讀寫(xiě)
一般印象中瑰煎,內(nèi)存讀寫(xiě)肯定比磁盤讀寫(xiě)快铺然。事實(shí)上磁盤可能比人們預(yù)想的更慢、或者更快酒甸,取決于怎么使用它魄健。一個(gè)良好設(shè)計(jì)的磁盤結(jié)構(gòu)通常和我們的網(wǎng)絡(luò) I/O 一樣快。
磁盤的模型圖:
說(shuō)起磁盤慢的主要原因在于其尋道操作插勤,在于磁盤的磁頭在盤面上的物理位移沽瘦。我們來(lái)看磁盤調(diào)度算法里面經(jīng)典的 SCAN 算法(又稱電梯算法)如下圖所示:
例如,磁盤請(qǐng)求隊(duì)列中的請(qǐng)求順序分別為 55饮六、58其垄、39、18卤橄、90绿满、160、150窟扑、38喇颁、184,磁頭初始位置是 100 磁道嚎货。釆用 SCAN 算法時(shí)橘霎,不但要知道磁頭的當(dāng)前位置,還要知道磁頭的移動(dòng)方向殖属,假設(shè)磁頭沿磁道號(hào)增大的順序移動(dòng)姐叁,則磁頭的運(yùn)動(dòng)過(guò)程如上圖所示。磁頭共移動(dòng)了( 50+10+24+94+32+3+16+1+20 ) = 250 個(gè)磁道洗显,平均尋找長(zhǎng)度 = 250/9 = 27.8外潜。那么對(duì)于我們最先想要訪問(wèn)的 55 號(hào)位置,要等到磁頭第六次真實(shí)位移后才能訪問(wèn)到挠唆。
如此可見(jiàn)順序讀寫(xiě)對(duì)磁盤性能的重要性处窥。如果我們只進(jìn)行順序讀寫(xiě),則能極大提高讀寫(xiě)效率玄组,甚至能高于內(nèi)存的隨機(jī)訪問(wèn)滔驾。更多的速度對(duì)比在文章《ACM Queue article》中,結(jié)論如圖:
故此俄讹,Producer 生產(chǎn)消息是不斷追加到磁盤文件的哆致,Consumer 消費(fèi)消息也是從磁盤順序讀取的,都充分利用到了磁盤的順序讀寫(xiě)性能患膛。
利用Page Cache
Kafka 利用到了現(xiàn)代操作系統(tǒng)的特性沽瞭,Page Cache。Page Cache 是通過(guò)將磁盤中的數(shù)據(jù)緩存到內(nèi)存中,從而減少磁盤 I/O 操作驹溃,從而提高性能城丧。此外,還要確保在 Page Cache 中的數(shù)據(jù)更改時(shí)能夠被同步到磁盤上豌鹤,后者被稱為 page 回寫(xiě)(page writeback)亡哄。
當(dāng)上層有寫(xiě)操作時(shí),操作系統(tǒng)只是將數(shù)據(jù)寫(xiě)入 Page Cache 布疙,同時(shí)標(biāo)記 Page 屬性為 Dirty蚊惯。當(dāng)讀操作發(fā)生時(shí),先從 Page Cache 中查找灵临,如果發(fā)生缺頁(yè)才進(jìn)行磁盤調(diào)度截型,最終返回需要的數(shù)據(jù)。實(shí)際上 Page Cache 是把盡可能多的空閑內(nèi)存都當(dāng)做了磁盤緩存來(lái)使用儒溉。同時(shí)如果有其他進(jìn)程申請(qǐng)內(nèi)存宦焦,回收 Page Cache 的代價(jià)又很小,所以現(xiàn)代的 OS 都支持 Page Cache顿涣。
相比于應(yīng)用程序自己(Kafka 是 Java 程序)做 cache波闹, 使用 Page Cache 有如下優(yōu)勢(shì):
- 操作系統(tǒng)會(huì)自行將連續(xù)的小量寫(xiě)操作批量處理為物理寫(xiě)操作,從而提高 IO 吞吐涛碑。
- 操作系統(tǒng)會(huì)盡量將寫(xiě)操作重新排序以減小寫(xiě)磁盤時(shí)的磁頭偏移量精堕,從而提高 IO 吞吐。
- 所有空閑內(nèi)存都自動(dòng)構(gòu)成 Page Cache蒲障。
- 如果在應(yīng)用程序 Heap 內(nèi)管理緩存歹篓,JVM 的 GC 線程會(huì)頻繁掃描 Heap 空間,帶來(lái)不必要的開(kāi)銷揉阎。如果 Heap 過(guò)大庄撮,執(zhí)行一次 Full GC 對(duì)系統(tǒng)的可用性來(lái)說(shuō)將是極大的挑戰(zhàn)。
- 所有在 JVM 內(nèi)的對(duì)象都不免帶有一個(gè) Object Overhead(千萬(wàn)不可小視)余黎,內(nèi)存的有效空間利用率會(huì)因此降低重窟。
- 所有的 In-Process Cache 在OS中都有一份同樣的 PageCache载萌。所以通過(guò)只在PageCache 中做緩存至少可以提高一倍的緩存空間惧财。
- 如果 Kafka 重啟,所有的 In-Process Cache都會(huì)失效扭仁,而 OS 管理的 PageCache 依然可以繼續(xù)使用垮衷。
底層 zero-copy 技術(shù)帶來(lái)的高吞吐
首先來(lái)看一下傳統(tǒng)的通過(guò)網(wǎng)絡(luò)讀取文件涉及到的傳輸過(guò)程:
涉及到四次內(nèi)存拷貝過(guò)程:
DMA data from disk to read buffer
Copy data from read buffer to application buffer
Copy data from application buffer to socket buffer
DMA buffer from socket buffer to network
注:read buffer 為 page cache,socket buffer 為內(nèi)核 socket buffer乖坠。
并涉及到四次上下文切換:
看上去現(xiàn)代操作系統(tǒng)怎么這么蠢搀突?實(shí)際上當(dāng)初設(shè)計(jì)這一套機(jī)制是為了提高性能,利用操作系統(tǒng)內(nèi)部 kernel buffer:
- read 操作可以利用
readahead cache
機(jī)制熊泵,提前為應(yīng)用程序準(zhǔn)備好數(shù)據(jù)仰迁,當(dāng)應(yīng)用程序所需的數(shù)據(jù)量小于 kernel buffer 時(shí)可以顯著地提升性能 - write 操作可以異步執(zhí)行
但實(shí)際上 Kafka 的場(chǎng)景是要實(shí)現(xiàn)高吞吐的文件數(shù)據(jù)傳輸甸昏,這種機(jī)制就成為了系統(tǒng)瓶頸。
利用 zero-copy 技術(shù)
上訴內(nèi)存拷貝的步驟 2 和 3 看起來(lái)都是浪費(fèi)徐许,因?yàn)閼?yīng)用程序并沒(méi)有對(duì)數(shù)據(jù)進(jìn)行過(guò)加工施蜜。實(shí)際上,數(shù)據(jù)可以直接從 read buffer 傳輸?shù)?socket buffer雌隅。在 Linux 系統(tǒng)里面的系統(tǒng)調(diào)用 sendfile() 對(duì)此進(jìn)行了支持翻默。
涉及到三次內(nèi)存拷貝過(guò)程:
DMA data from disk to read buffer
Copy data from read buffer to socket buffer
DMA buffer from socket buffer to network
并且使得上線文切換減少到兩次:
但這還不是 zero-copy,CPU 依然參與了一次內(nèi)存拷貝恰起。在網(wǎng)卡支持gather operations
特性并且 Linux 2.4 之后修械,可以進(jìn)一步優(yōu)化為:
涉及到兩次內(nèi)存拷貝過(guò)程:
DMA data from disk to read buffer
No data copied to socket buffer, only the descriptors with information about the location and length
DMA buffer from read buffer to network
優(yōu)勢(shì)和劣勢(shì)
為了使用 zero-copy,很明顯的一點(diǎn)劣勢(shì)就是應(yīng)用程序即 Kafka 的 Broker 不能對(duì)數(shù)據(jù)進(jìn)行二次加工检盼,數(shù)據(jù)進(jìn)來(lái)是什么樣子出去就是什么樣子肯污。與此同時(shí)的優(yōu)勢(shì)就是,Producer 到 Consumer 可以做端到端的壓縮梯皿,反正中間的 Broker 不能修改數(shù)據(jù)本身仇箱。實(shí)際上這種端到端的壓縮也是構(gòu)成高吞吐的原因之一。
綜合一下 PageCahce 和 zero-copy
熱數(shù)據(jù)就直接讀 PageCahce东羹,冷數(shù)據(jù)就走 zero-copy剂桥,性能都很好,完美属提!
Producer 層面
批量化處理
為了避免「small I/O」帶來(lái)的性能損失权逗,Kafka 提出了message set
的概念來(lái)批量化處理消息。這個(gè)簡(jiǎn)單的優(yōu)化帶來(lái)了巨大的性能提升冤议,因?yàn)榕炕幚韼?lái)了更大的網(wǎng)絡(luò)報(bào)文斟薇、更大的順序磁盤操作、更大的連續(xù)內(nèi)存空間恕酸,由此 Kafka 將「突發(fā)式的隨機(jī)消息」轉(zhuǎn)變?yōu)轫樞虻南⒘魈峁┙o下游的 Consumer堪滨。
批量化處理使得 Producer 的發(fā)送變成了異步,那么為了保證消息的可靠性蕊温,Kafka 提供了 ack 的機(jī)制:
- 0:這意味著 Producer 無(wú)需等待來(lái)自 Broker 的確認(rèn)而繼續(xù)發(fā)送下一批消息袱箱。這種情況下數(shù)據(jù)傳輸效率最高,但是數(shù)據(jù)可靠性確是最低的义矛。
- 1(默認(rèn)):這意味著 Producer 在 ISR 中的 leader 已成功收到的數(shù)據(jù)并得到確認(rèn)后發(fā)送下一條消息发笔。如果 leader宕機(jī)了 ,則會(huì)丟失數(shù)據(jù)凉翻。
- -1(或者是all): Producer 需要等待 ISR 中的所有 follower 都確認(rèn)接收到數(shù)據(jù)后才算一次發(fā)送完成了讨,可靠性最高。
數(shù)據(jù)壓縮
Kafka 支持?jǐn)?shù)據(jù)端到端的壓縮,Producer 可以通過(guò) GZIP 或 Snappy 格式對(duì)消息集合進(jìn)行壓縮前计,以減輕網(wǎng)絡(luò)傳輸量和磁盤數(shù)據(jù)量胞谭。Producer 壓縮之后,在 Consumer 需進(jìn)行解壓男杈,雖然增加了 CPU 的工作韭赘,但在對(duì)大數(shù)據(jù)處理上,瓶頸在網(wǎng)絡(luò)上而不是 CPU 势就,所以這個(gè)成本很值得泉瞻。
Consumer 層面
pull 模型
到底是用 push 模型還是 pull 模型,不同的系統(tǒng)有不同的選擇苞冯。Kafka 使用的 pull 模型袖牙,這樣有助于 Consumer 自行控制消費(fèi)速度,不會(huì)產(chǎn)生消息積壓到 Consumer 的情形舅锄。
另一個(gè)好處是鞭达,Consumer 可以根據(jù)自身的情況,選擇是否批量去 Broker 拉取消息皇忿,以增加整體的吞吐畴蹭。這在 push 模型里就很難辦,因?yàn)?Broker 很難知道 Consumer 的負(fù)載情況從而不知道是否應(yīng)該批量推送鳍烁。
針對(duì)傳統(tǒng) pull 模型的一個(gè)劣勢(shì)叨襟,即如果 Broker 那里沒(méi)有消息 Consumer 會(huì)一直不斷嘗試獲取,Kafka 這里使用了「long polling」長(zhǎng)輪詢機(jī)制幔荒。Consumer 在發(fā)起一次請(qǐng)求后立即掛起糊闽,一直到 Broker 有更新的時(shí)候,Broker 才會(huì)主動(dòng)推送信息到 Consumer爹梁。 在 Broker 有更新并推送信息過(guò)來(lái)之前這個(gè)周期內(nèi)右犹,Consumer 不會(huì)有新的多余的請(qǐng)求發(fā)生,Broker 對(duì)此 Consumer 也啥都不用干姚垃,只保留最基本的連接信息念链,一旦 Broker 有更新將推送給 Consumer,Consumer 將相應(yīng)的做出處理积糯,處理完后再重新發(fā)起下一輪請(qǐng)求掂墓。
消息確認(rèn)機(jī)制
以往的消息隊(duì)列為了記錄一條消息是否被消費(fèi)掉,在其 broker 層做了很多工作:
- 記錄每條消息的狀態(tài)絮宁,是否發(fā)送梆暮、是否確認(rèn)等
- 和 consumer 確認(rèn)之間的 ACK 機(jī)制
- 異常邏輯的處理服协,比如消息發(fā)送了之后一直沒(méi)有 ACK 確認(rèn)要怎么處理
相比之下 Kafka 使用了簡(jiǎn)單的 offset 機(jī)制绍昂,摒棄了對(duì)消息的狀態(tài)轉(zhuǎn)換。因?yàn)槊總€(gè) Partition 都只有一個(gè) Consumer 在消費(fèi),故每個(gè) Consumer 都只需要記錄其本身的消費(fèi)偏移量 offset窘游,簡(jiǎn)單有效唠椭。老版本的 offset 保存在 zookeeper 中,后面改為存放在一個(gè)特殊的 Topic 中忍饰。
使用 offset 的另一個(gè)好處是 Consumer 可以重復(fù)去消費(fèi)贪嫂,以適配某些場(chǎng)景。
參考
- 官方文檔
- 磁盤(操作系統(tǒng))
- 我也聊聊數(shù)據(jù)與磁盤IO
- zero-copy
- kafka 高吞吐量性能揭秘
- Kafka是如何實(shí)現(xiàn)高吞吐率的
- 為什么Kafka那么快