通過之前的文章《Kafka分割槽分配策略》和《Kafka高效能揭祕》,我們瞭解到:Kafka高吞吐量的原因之一就是通過partition將topic中的訊息儲存到Kafka叢集中不同的broker中。無論是Kafka的producer,還是consumer都可以併發操作topic中的partition,因此partition是Kafka並行度調優的最小單元。
理論上說,如果一個topic分割槽越多,理論上整個叢集所能達到的吞吐量就越大。
但是,實際生產中Kafka topic的分割槽數真的配置越多越好嗎?很顯然不是!
分割槽數過多會有以下弊端:
一、客戶端/伺服器端需要使用的記憶體就越多
Kafka0.8.2之後,在客戶端producer有個引數batch.size,預設是16KB。它會為每個分割槽快取訊息,在資料積累到一定大小或者足夠的時間時,積累的訊息將會從快取中移除併發往broker節點。這個功能是為了提高效能而設計,但是隨著分割槽數增多,這部分快取所需的記憶體佔用也會更多。
與此同時,consumer端在消費訊息時的記憶體佔用、以及為達到更高的吞吐效能開啟的consumer執行緒數也會隨著分割槽數增加而增加。比如有10000個分割槽,同時consumer執行緒數要匹配分割槽數(大部分情況下是最佳的消費吞吐量配置)的話,那麼在consumer client就要建立10000個執行緒,那麼在consumer client就要建立10000個執行緒,也需要建立大約10000個Socket去獲取分割槽資料。執行緒的開銷成本很顯然是不容小覷的!
此外,伺服器端的開銷也不小,如果閱讀Kafka原始碼的話可以發現,伺服器端的很多元件都在記憶體中維護了分割槽級別的快取,比如controller,FetcherManager等,因此分割槽數越多,這種快取的成本就越大。
二、檔案控制程式碼的開銷
在Kafka的broker中,每個partition都會對應磁碟檔案系統的一個目錄。在Kafka的資料日誌檔案目錄中,每個日誌資料段都會分配兩個檔案,一個索引檔案和一個資料檔案。當前版本的kafka,每個broker會為每個日誌段檔案開啟一個index檔案控制程式碼和一個資料檔案控制程式碼。因此,隨著partition的增多,所需要保持開啟狀態的檔案控制程式碼數也就越多,最終可能超過底層作業系統配置的檔案控制程式碼數量限制。
三、越多的分割槽可能增加端對端的延遲
Kafka端對端延遲定義為producer端釋出訊息到consumer端接收訊息所需要的時間,即consumer接收訊息的時間減去producer釋出訊息的時間。
Kafka只有在訊息提交之後,才會將訊息暴露給消費者。例如,訊息在所有in-sync副本列表同步複製完成之後才暴露。因此,in-sync副本複製所花時間將是kafka端對端延遲的最主要部分。在預設情況下,每個broker從其他broker節點進行資料副本複製時,該broker節點只會為此工作分配一個執行緒,該執行緒需要完成該broker所有partition資料的複製。
注意,上述問題可以通過增大kafka叢集來進行緩解。例如,將1000個分割槽leader放到一個broker節點和放到10個broker節點,他們之間的延遲是存在差異的。在10個broker節點的叢集中,每個broker節點平均需要處理100個分割槽的資料複製。此時,端對端的延遲將會從原來的數十毫秒變為僅僅需要幾毫秒。
根據經驗,如果你十分關心訊息延遲問題,限制每個broker節點的partition數量是一個很好的主意:對於b個broker節點和複製因子為r的kafka叢集,整個kafka叢集的partition數量最好不超過100*b*r個,即單個partition的leader數量不超過100。
四、降低高可用性
Kafka通過多副本複製技術,實現Kafka叢集的高可用和穩定性。每個partition都會有多個資料副本,每個副本分別存在於不同的broker。所有的資料副本中,有一個資料副本為leader,其他的資料副本為follower。
在Kafka叢集內部,所有的資料副本皆採用自動化的方式進行管理,並且確保所有的資料副本的資料皆保持同步狀態。不論是producer端還是consumer端發往partition的請求,都通過leader資料副本所在的broker進行處理。當broker發生故障時,對於leader資料副本在該broker的所有partition將會變得暫時不可用。Kafka將會自動在其它資料副本中選擇出一個leader,用於接收客戶端的請求。這個過程由Kafka controller節點broker自動完成,主要是從Zookeeper讀取和修改受影響partition的一些後設資料資訊。
在通常情況下,當一個broker有計劃地停止服務時,那麼controller會在服務停止之前,將該broker上的所有leader一個個地移走。由於單個leader的移動時間大約只需要花費幾毫秒,因此從客戶層面看,有計劃的服務停機只會導致系統在很小時間視窗中不可用。(注:在有計劃地停機時,系統每一個時間視窗只會轉移一個leader,其他leader皆處於可用狀態。)
然而,當broker非計劃地停止服務時(例如,kill -9方式),系統的不可用時間視窗將會與受影響的partition數量有關。假如,一個2節點的kafka叢集中存在2000個partition,每個partition擁有2個資料副本。當其中一個broker非計劃地當機,所有1000個partition同時變得不可用。假設每一個partition恢復時間是5ms,那麼1000個partition的恢復時間將會花費5秒鐘。因此,在這種情況下,使用者將會觀察到系統存在5秒鐘的不可用時間視窗。
而如果發生當機的broker恰好是controller節點時:在這種情況下,新leader節點的選舉過程在controller節點恢復到新的broker之前不會啟動。controller節點的錯誤恢復將會自動地進行,但是新的controller節點需要從zookeeper中讀取每一個partition的後設資料資訊用於初始化資料。例如,假設一個Kafka叢集存在10000個partition,從zookeeper中恢復後設資料時每個partition大約花費2ms,則controller的恢復將會增加約20秒的不可用時間視窗。
總而言之,通常情況下Kafka叢集中越多的partition會帶來越高的吞吐量。但是,如果Kafka叢集中partition總量過大或者單個broker節點partition過多,都可能會對系統的可用性和訊息延遲帶來潛在的負面影響,需要引起我們的重視。
那麼如何確定合理的分割槽數量呢?
在partition級別上達到均衡負載是實現吞吐量的關鍵,合適的partition數量可以達到高度並行讀寫和負載均衡的目的,需要根據每個分割槽的生產者和消費者的目標吞吐量進行估計。
可以遵循一定的步驟來確定分割槽數:根據某個topic日常"接收"的資料量等經驗確定分割槽的初始值,然後測試這個topic的producer吞吐量和consumer吞吐量。假設它們的值分別是Tp和Tc,單位可以是MB/s。然後假設總的目標吞吐量是Tt,那麼numPartitions = Tt / max(Tp, Tc)
說明:Tp表示producer的吞吐量。測試producer通常是很容易的,因為它的邏輯非常簡單,就是直接傳送訊息到Kafka就好了。Tc表示consumer的吞吐量。測試Tc通常與應用消費訊息後進行什麼處理的關係更大,相對複雜一些。
推薦文章:
Hadoop支援的壓縮格式對比和應用場景以及Hadoop native庫
Spark SQL中Not in Subquery為何低效以及如何規避
關注微信公眾號:大資料學習與分享,獲取更對技術乾貨