原文地址:https://kafka.apache.org/0101/documentation.html#maximizingefficiency
我們?cè)谛噬贤度肓吮姸嗟呐︱咧保粋€(gè)我們的主要用例是具有大吞吐量的 web 活動(dòng)日志,每頁面的每次訪問都會(huì)產(chǎn)生好幾十次的寫,進(jìn)一步壹士,我們假定每次消息發(fā)布尘吗,至少會(huì)被一個(gè)消費(fèi)者讀取(經(jīng)常情況下是多個(gè)消費(fèi)者)钾埂,因此球涛,我們努力使消費(fèi)消息的代價(jià)盡可能小砾医。
從構(gòu)建一些相似的系統(tǒng)的經(jīng)驗(yàn)中泼掠,我們也發(fā)現(xiàn)怔软,有效的多租戶操作是提升性能的關(guān)鍵。下游的基礎(chǔ)服務(wù)很容易由于程序的很小的使用錯(cuò)誤成為瓶頸择镇,例如挡逼,一些小的變化很常導(dǎo)致一些新的問題,我們可以非衬逋悖快速在程序發(fā)布到基礎(chǔ)平臺(tái)前家坎,進(jìn)行迭代測(cè)試,這對(duì)需要在集中式的集群里跑幾十個(gè)吝梅,幾千個(gè)應(yīng)用時(shí)虱疏,程序每天都在變動(dòng)時(shí)非常有用。
前面一個(gè)章節(jié)我們討論了磁盤的性能憔涉,低效率的磁盤訪問模式就忽略不說了订框,這里在系統(tǒng)上還有兩個(gè)可能會(huì)導(dǎo)致效率低下的地方:很多小的 I/O 操作和過多的字節(jié)拷貝。
小 I/O 問題在客戶端和服務(wù)器端都會(huì)發(fā)生兜叨,在服務(wù)器端有它自己的存儲(chǔ)操作穿扳。
為了避免這個(gè)問題,我們的通訊協(xié)議正是基于消息集合這個(gè)概念構(gòu)建的国旷,很容易把多個(gè)消息組合起來矛物。這樣允許網(wǎng)絡(luò)組合消息后進(jìn)行發(fā)送,而不是每次發(fā)送一條信息跪但,減少網(wǎng)絡(luò)的來回開銷履羞。服務(wù)器也是每次寫入一堆數(shù)據(jù)到日志中,消費(fèi)者也是每次線性讀取一堆數(shù)據(jù)。
這種簡(jiǎn)單的優(yōu)化可以提升大量的性能忆首,批量處理導(dǎo)致大的網(wǎng)絡(luò)數(shù)據(jù)包爱榔,大的磁盤順序讀寫,連續(xù)的內(nèi)存塊等等糙及。所有的這些能把 kafka 的間接性的隨機(jī)消息寫改成線性寫入后详幽,發(fā)送給消費(fèi)者。
另外一個(gè)低效率的地方是字節(jié)拷貝浸锨。在消息吞吐量不多的時(shí)候這不是一個(gè)問題唇聘,但在高負(fù)載下的影響是非常顯著。為了避免這種情況柱搜,我們?cè)谏a(chǎn)者迟郎、服務(wù)器和消費(fèi)者間使用一個(gè)標(biāo)準(zhǔn)化的二進(jìn)制消息格式(這樣數(shù)據(jù)塊可以在它們之間直接進(jìn)行傳輸而不需要再做修改)。
服務(wù)器端使用文件的形式維護(hù)消息日志聪蘸,所有的消息都按生產(chǎn)者和消費(fèi)者使用的格式順序?qū)懭氲酱疟P中宪肖,維護(hù)這樣的格式需要優(yōu)化最常用的一些操作:對(duì)持久日志塊的網(wǎng)絡(luò)傳輸。現(xiàn)在的unix操作系列通常都有提供高效的優(yōu)化代碼直接把數(shù)據(jù)從緩存頁發(fā)送到socket宇姚,在linux下使用 sendfile 的系統(tǒng)調(diào)用匈庭。
如果要理解一下sendfile調(diào)用的功效,需要了解下正常情況下數(shù)據(jù)從文件發(fā)送到socket的過程:
- 1.操作系統(tǒng)從磁盤讀取數(shù)據(jù)到系統(tǒng)內(nèi)核空間的緩存頁中
- 2.應(yīng)用從內(nèi)核空間讀取數(shù)據(jù)到用戶空間緩沖區(qū)中
- 3.應(yīng)用把數(shù)據(jù)寫回到內(nèi)核空間的socket緩沖區(qū)中
- 4.系統(tǒng)拷貝 socket 緩沖區(qū)的數(shù)據(jù)到網(wǎng)卡緩沖區(qū)浑劳,然后由網(wǎng)卡發(fā)送數(shù)據(jù)到網(wǎng)絡(luò)中
這很明顯效率很低阱持,有4次拷貝還有2次系統(tǒng)調(diào)用,如果使用sendfile命令魔熏,重新拷貝運(yùn)行系統(tǒng)直接把數(shù)據(jù)從緩存頁拷貝到網(wǎng)絡(luò)衷咽,優(yōu)化后,最終只需要一次從緩存頁到網(wǎng)卡緩沖區(qū)拷貝蒜绽。
我們預(yù)期一個(gè)常見消費(fèi)方式是使用多個(gè)消費(fèi)者同時(shí)消費(fèi)一個(gè)主題镶骗,使用上面提到的 zero-copy 的優(yōu)化方式,數(shù)據(jù)只被拷貝到頁緩存一次躲雅,并被多次消費(fèi)鼎姊,而不是緩存到(用戶空間的)內(nèi)存中,然后在每次消費(fèi)時(shí)拷貝到系統(tǒng)內(nèi)核空間中相赁。這可以使消費(fèi)者消費(fèi)消息的速度達(dá)到網(wǎng)絡(luò)連接的速度相寇。
組合頁緩存和sendfile機(jī)制后,kafka集群在跟上消費(fèi)者消費(fèi)的同時(shí)钮科,讓你覺得好像沒有多少的磁盤讀活動(dòng)唤衫,因?yàn)榇蟛糠值臄?shù)據(jù)響應(yīng)需求都是從緩存獲取的。
如果想要知道更多關(guān)于java對(duì)sendfile和zero-copy的支持, 可以閱讀這篇文章 article.
端到端的數(shù)據(jù)壓縮
在大部分情況下绵脯,瓶頸不會(huì)是cpu或磁盤佳励,而是網(wǎng)絡(luò)帶寬休里。這個(gè)在數(shù)據(jù)中心之間建立需要跨越廣域網(wǎng)發(fā)送消息的數(shù)據(jù)管道時(shí)更為明顯,當(dāng)然用戶可以獨(dú)立于 kafka 自己做消息壓縮赃承,但是這有可能由于消息類型冗余妙黍,導(dǎo)致壓縮比例很低(例如json的字段名或web中的用戶代理日志或常用的字符串值),有效的壓縮方式應(yīng)該是允許壓縮重復(fù)的消息瞧剖,而不是分別壓縮單個(gè)消息废境。
kafka 通過遞歸的消息集合支持這樣的操作。一批的消息可以被收集在一起后壓縮筒繁,并發(fā)送到服務(wù)器端。這樣被壓縮的一批數(shù)據(jù)巴元,日志也是使用壓縮的格式毡咏,只有在消費(fèi)者消費(fèi)的時(shí)候才會(huì)被解壓。
kafka 支持 GZIP逮刨,Snappy and LZ4 壓縮協(xié)議呕缭,更多關(guān)于壓縮的細(xì)節(jié)可以查看這里 here。