調(diào)優(yōu)目標
在做調(diào)優(yōu)之前,我們必須明確優(yōu)化 Kafka 的目標是什么橱乱。通常來說辜梳,調(diào)優(yōu)是為了滿足系統(tǒng)常見的非功能性需求。在眾多的非功能性需求中泳叠,性能絕對是我們最關(guān)心的那一個作瞄。不同的系統(tǒng)對性能有不同的訴求,比如對于數(shù)據(jù)庫用戶而言危纫,性能意味著請求的響應(yīng)時間宗挥,用戶總是希望查詢或更新請求能夠被更快地處理完并返回。
對 Kafka 而言种蝶,性能一般是指吞吐量和延時契耿。
吞吐量,也就是 TPS螃征,是指 Broker 端進程或 Client 端應(yīng)用程序每秒能處理的字節(jié)數(shù)或消息數(shù)搪桂,這個值自然是越大越好。
延時和我們剛才說的響應(yīng)時間類似盯滚,它表示從 Producer 端發(fā)送消息到 Broker 端持久化完成之間的時間間隔踢械。這個指標也可以代表端到端的延時(End-to-End,E2E)魄藕,也就是從 Producer 發(fā)送消息到 Consumer 成功消費該消息的總時長内列。和 TPS 相反,我們通常希望延時越短越好泼疑。
總之德绿,高吞吐量荷荤、低延時是我們調(diào)優(yōu) Kafka 集群的主要目標,一會兒我們會詳細討論如何達成這些目標移稳。在此之前蕴纳,我想先談一談優(yōu)化漏斗的問題。
優(yōu)化漏斗
優(yōu)化漏斗是一個調(diào)優(yōu)過程中的分層漏斗个粱,我們可以在每一層上執(zhí)行相應(yīng)的優(yōu)化調(diào)整古毛。總體來說都许,層級越靠上稻薇,其調(diào)優(yōu)的效果越明顯,整體優(yōu)化效果是自上而下衰減的胶征,如下圖所示:
第 1 層:應(yīng)用程序?qū)?/strong>塞椎。它是指優(yōu)化 Kafka 客戶端應(yīng)用程序代碼。比如睛低,使用合理的數(shù)據(jù)結(jié)構(gòu)案狠、緩存計算開銷大的運算結(jié)果,抑或是復(fù)用構(gòu)造成本高的對象實例等钱雷。這一層的優(yōu)化效果最為明顯骂铁,通常也是比較簡單的。
第 2 層:框架層罩抗。它指的是合理設(shè)置 Kafka 集群的各種參數(shù)拉庵。畢竟,直接修改 Kafka 源碼進行調(diào)優(yōu)并不容易套蒂,但根據(jù)實際場景恰當?shù)嘏渲藐P(guān)鍵參數(shù)的值钞支,還是很容易實現(xiàn)的。
第 3 層:JVM 層泣懊。Kafka Broker 進程是普通的 JVM 進程伸辟,各種對 JVM 的優(yōu)化在這里也是適用的。優(yōu)化這一層的效果雖然比不上前兩層馍刮,但有時也能帶來巨大的改善效果。
第 4 層:操作系統(tǒng)層窃蹋。對操作系統(tǒng)層的優(yōu)化很重要卡啰,但效果往往不如想象得那么好。與應(yīng)用程序?qū)拥膬?yōu)化效果相比警没,它是有很大差距的匈辱。
基礎(chǔ)性調(diào)優(yōu)
操作系統(tǒng)調(diào)優(yōu)
我先來說說操作系統(tǒng)層的調(diào)優(yōu)。在操作系統(tǒng)層面杀迹,你最好在掛載(Mount)文件系統(tǒng)時禁掉 atime 更新亡脸。atime 的全稱是 access time,記錄的是文件最后被訪問的時間。記錄 atime 需要操作系統(tǒng)訪問 inode 資源浅碾,而禁掉 atime 可以避免 inode 訪問時間的寫入操作大州,減少文件系統(tǒng)的寫操作數(shù)。你可以執(zhí)行mount -o noatime 命令進行設(shè)置垂谢。
至于文件系統(tǒng)厦画,我建議你至少選擇 ext4 或 XFS。尤其是 XFS 文件系統(tǒng)滥朱,它具有高性能根暑、高伸縮性等特點,特別適用于生產(chǎn)服務(wù)器徙邻。
另外就是 swap 空間的設(shè)置排嫌。我個人建議將 swappiness 設(shè)置成一個很小的值,比如 1~10 之間缰犁,以防止 Linux 的 OOM Killer 開啟隨意殺掉進程躏率。你可以執(zhí)行 sudo sysctl vm.swappiness=N 來臨時設(shè)置該值,如果要永久生效民鼓,可以修改 /etc/sysctl.conf 文件薇芝,增加 vm.swappiness=N,然后重啟機器即可
操作系統(tǒng)層面還有兩個參數(shù)也很重要丰嘉,它們分別是ulimit -n 和 vm.max_map_count夯到。前者如果設(shè)置得太小,你會碰到 Too Many File Open 這類的錯誤饮亏,而后者的值如果太小耍贾,在一個主題數(shù)超多的 Broker 機器上,你會碰到OutOfMemoryError:Map failed的嚴重錯誤路幸,因此荐开,我建議在生產(chǎn)環(huán)境中適當調(diào)大此值,比如將其設(shè)置為 655360简肴。具體設(shè)置方法是修改 /etc/sysctl.conf 文件晃听,增加 vm.max_map_count=655360,保存之后砰识,執(zhí)行 sysctl -p 命令使它生效能扒。
后,不得不提的就是操作系統(tǒng)頁緩存大小了辫狼,這對 Kafka 而言至關(guān)重要初斑。在某種程度上,我們可以這樣說:給 Kafka 預(yù)留的頁緩存越大越好膨处,最小值至少要容納一個日志段的大小见秤,也就是 Broker 端參數(shù) log.segment.bytes 的值砂竖。該參數(shù)的默認值是 1GB。預(yù)留出一個日志段大小鹃答,至少能保證 Kafka 可以將整個日志段全部放入頁緩存乎澄,這樣,消費者程序在消費時能直接命中頁緩存挣跋,從而避免昂貴的物理磁盤 I/O 操作三圆。
JVM 層調(diào)優(yōu)
說完了操作系統(tǒng)層面的調(diào)優(yōu),我們來討論下 JVM 層的調(diào)優(yōu)避咆,其實舟肉,JVM 層的調(diào)優(yōu),我們還是要重點關(guān)注堆設(shè)置以及 GC 方面的性能查库。
- 設(shè)置堆大小路媚。
如何為 Broker 設(shè)置堆大小,這是很多人都感到困惑的問題樊销。我來給出一個樸素的答案:將你的 JVM 堆大小設(shè)置成 6~8GB整慎。
在很多公司的實際環(huán)境中,這個大小已經(jīng)被證明是非常合適的围苫,你可以安心使用裤园。如果你想精確調(diào)整的話,我建議你可以查看 GC log剂府,特別是關(guān)注 Full GC 之后堆上存活對象的總大小拧揽,然后把堆大小設(shè)置為該值的 1.5~2 倍。如果你發(fā)現(xiàn) Full GC 沒有被執(zhí)行過腺占,手動運行 jmap -histo:live < pid > 就能人為觸發(fā) Full GC淤袜。
2.GC 收集器的選擇
我強烈建議你使用 G1 收集器,主要原因是方便省事衰伯,至少比 CMS 收集器的優(yōu)化難度小得多铡羡。另外,你一定要盡力避免 Full GC 的出現(xiàn)意鲸。其實烦周,不論使用哪種收集器,都要竭力避免 Full GC临扮。在 G1 中论矾,F(xiàn)ull GC 是單線程運行的,它真的非常慢杆勇。如果你的 Kafka 環(huán)境中經(jīng)常出現(xiàn) Full GC,你可以配置 JVM 參數(shù) -XX:+PrintAdaptiveSizePolicy饱亿,來探查一下到底是誰導(dǎo)致的 Full GC蚜退。
使用 G1 還很容易碰到的一個問題闰靴,就是大對象(Large Object),反映在 GC 上的錯誤钻注,就是“too many humongous allocations”蚂且。所謂的大對象,一般是指至少占用半個區(qū)域(Region)大小的對象幅恋。舉個例子杏死,如果你的區(qū)域尺寸是 2MB,那么超過 1MB 大小的對象就被視為是大對象捆交。要解決這個問題淑翼,除了增加堆大小之外,你還可以適當?shù)卦黾訁^(qū)域大小品追,設(shè)置方法是增加 JVM 啟動參數(shù) -XX:+G1HeapRegionSize=N玄括。默認情況下,如果一個對象超過了 N/2肉瓦,就會被視為大對象遭京,從而直接被分配在大對象區(qū)。如果你的 Kafka 環(huán)境中的消息體都特別大泞莉,就很容易出現(xiàn)這種大對象分配的問題哪雕。
Broker 端調(diào)優(yōu)
我們繼續(xù)沿著漏斗往上走,來看看 Broker 端的調(diào)優(yōu)鲫趁。
Broker 端調(diào)優(yōu)很重要的一個方面斯嚎,就是合理地設(shè)置 Broker 端參數(shù)值,以匹配你的生產(chǎn)環(huán)境饮寞。不過孝扛,后面我們在討論具體的調(diào)優(yōu)目標時再詳細說這部分內(nèi)容。這里我想先討論另一個優(yōu)化手段幽崩,即盡力保持客戶端版本和 Broker 端版本一致苦始。不要小看版本間的不一致問題,它會令 Kafka 喪失很多性能收益慌申,比如 Zero Copy陌选。下面我用一張圖來說明一下。
圖中藍色的 Producer蹄溉、Consumer 和 Broker 的版本是相同的咨油,它們之間的通信可以享受 Zero Copy 的快速通道;相反柒爵,一個低版本的 Consumer 程序想要與 Producer役电、Broker 交互的話,就只能依靠 JVM 堆中轉(zhuǎn)一下棉胀,丟掉了快捷通道法瑟,就只能走慢速通道了冀膝。因此,在優(yōu)化 Broker 這一層時霎挟,你只要保持服務(wù)器端和客戶端版本的一致窝剖,就能獲得很多性能收益了。
應(yīng)用層調(diào)優(yōu)
現(xiàn)在酥夭,我們終于來到了漏斗的最頂層赐纱。其實,這一層的優(yōu)化方法各異熬北,畢竟每個應(yīng)用程序都是不一樣的疙描。不過,有一些公共的法則依然是值得我們遵守的蒜埋。
- 不要頻繁地創(chuàng)建 Producer 和 Consumer 對象實例淫痰。構(gòu)造這些對象的開銷很大,盡量復(fù)用它們
- 用完及時關(guān)閉整份。這些對象底層會創(chuàng)建很多物理資源待错,如 Socket 連接、ByteBuffer 緩沖區(qū)等烈评。不及時關(guān)閉的話火俄,勢必造成資源泄露
- 合理利用多線程來改善性能。Kafka 的 Java Producer 是線程安全的讲冠,你可以放心地在多個線程中共享同一個實例瓜客;而 Java Consumer 雖不是線程安全的。
性能指標調(diào)優(yōu)
調(diào)優(yōu)吞吐量
首先是調(diào)優(yōu)吞吐量竿开。很多人對吞吐量和延時之間的關(guān)系似乎有些誤解谱仪。比如有這樣一種提法還挺流行的:假設(shè) Kafka 每發(fā)送一條消息需要花費 2ms,那么延時就是 2ms否彩。顯然疯攒,吞吐量就應(yīng)該是 500 條 / 秒,因為 1 秒可以發(fā)送 1 / 0.002 = 500 條消息列荔。因此敬尺,吞吐量和延時的關(guān)系可以用公式來表示:TPS = 1000 / Latency(ms)。但實際上贴浙,吞吐量和延時的關(guān)系遠不是這么簡單砂吞。
我們以 Kafka Producer 為例。假設(shè)它以 2ms 的延時來發(fā)送消息崎溃,如果每次只是發(fā)送一條消息蜻直,那么 TPS 自然就是 500 條 / 秒。但如果 Producer 不是每次發(fā)送一條消息,而是在發(fā)送前等待一段時間袭蝗,然后統(tǒng)一發(fā)送一批消息唤殴,比如 Producer 每次發(fā)送前先等待 8ms般婆,8ms 之后到腥,Producer 共緩存了 1000 條消息,此時總延時就累加到 10ms(即 2ms + 8ms)了蔚袍,而 TPS 等于 1000 / 0.01 = 100,000 條 / 秒乡范。由此可見,雖然延時增加了 4 倍啤咽,但 TPS 卻增加了將近 200 倍晋辆。這其實也是批次化(batching)或微批次化(micro-batching)目前會很流行的原因
在實際環(huán)境中,用戶似乎總是愿意用較小的延時增加的代價宇整,去換取 TPS 的顯著提升瓶佳。畢竟,從 2ms 到 10ms 的延時增加通常是可以忍受的鳞青。事實上霸饲,Kafka Producer 就是采取了這樣的設(shè)計思想。
當然臂拓,你可能會問:發(fā)送一條消息需要 2ms厚脉,那么等待 8ms 就能累積 1000 條消息嗎?答案是可以的胶惰!Producer 累積消息時傻工,一般僅僅是將消息發(fā)送到內(nèi)存中的緩沖區(qū),而發(fā)送消息卻需要涉及網(wǎng)絡(luò) I/O 傳輸孵滞。內(nèi)存操作和 I/O 操作的時間量級是不同的中捆,前者通常是幾百納秒級別,而后者則是從毫秒到秒級別不等坊饶,因此泄伪,Producer 等待 8ms 積攢出的消息數(shù),可能遠遠多于同等時間內(nèi) Producer 能夠發(fā)送的消息數(shù)幼东。
我們該怎么調(diào)優(yōu) TPS 呢臂容?我來跟你分享一個參數(shù)列表。
Broker 端參數(shù) num.replica.fetchers 表示的是 Follower 副本用多少個線程來拉取消息根蟹,默認使用 1 個線程脓杉。如果你的 Broker 端 CPU 資源很充足,不妨適當調(diào)大該參數(shù)值简逮,加快 Follower 副本的同步速度球散。因為在實際生產(chǎn)環(huán)境中,配置了 acks=all 的 Producer 程序吞吐量被拖累的首要因素散庶,就是副本同步性能蕉堰。增加這個值后凌净,你通常可以看到 Producer 端程序的吞吐量增加屋讶。
另外需要注意的冰寻,就是避免經(jīng)常性的 Full GC。目前不論是 CMS 收集器還是 G1 收集器皿渗,其 Full GC 采用的是 Stop The World 的單線程收集策略斩芭,非常慢,因此一定要避免乐疆。
在 Producer 端划乖,如果要改善吞吐量,通常的標配是增加消息批次的大小以及批次緩存時間挤土,即 batch.size 和 linger.ms琴庵。目前它們的默認值都偏小,特別是默認的 16KB 的消息批次大小一般都不適用于生產(chǎn)環(huán)境仰美。假設(shè)你的消息體大小是 1KB迷殿,默認一個消息批次也就大約 16 條消息,顯然太小了筒占。我們還是希望 Producer 能一次性發(fā)送更多的消息贪庙。
調(diào)優(yōu)延時
講完了調(diào)優(yōu)吞吐量,我們來說說如何優(yōu)化延時翰苫,下面是調(diào)優(yōu)延時的參數(shù)列表止邮。
在 Broker 端,我們依然要增加 num.replica.fetchers 值以加快 Follower 副本的拉取速度奏窑,減少整個消息處理的延時导披。
在 Producer 端,我們希望消息盡快地被發(fā)送出去埃唯,因此不要有過多停留撩匕,所以必須設(shè)置 linger.ms=0,同時不要啟用壓縮墨叛。因為壓縮操作本身要消耗 CPU 時間止毕,會增加消息發(fā)送的延時。另外漠趁,最好不要設(shè)置 acks=all扁凛。我們剛剛在前面說過,F(xiàn)ollower 副本同步往往是降低 Producer 端吞吐量和增加延時的首要原因闯传。
在 Consumer 端谨朝,我們保持 fetch.min.bytes=1 即可,也就是說,只要 Broker 端有能返回的數(shù)據(jù)字币,立即令其返回給 Consumer则披,縮短 Consumer 消費延時。