無論是作為面試官,還是應聘者,我都接觸過很多Kafka面試題。而在最近面試了很多候選人,發現寫了熟悉Kafka,但是對於Kafka相關的知識卻是只知道大概用處,簡單搭建和使用。我想說,雖然我們是SRE(可靠性工程師),但不論你是業務層的SRE還是基礎設施層的SRE,我們都需要對業務方的使用場景有足夠理解,或者對我們要提供的服務有足夠的了解才行,這樣你才能整體的保證你的業務連續性以及業務可靠性。因此,專門總結了如下經典的kafka面試詳解。
以下面試題,參考胡夕胡大的Kafka核心源碼解讀,對相關的知識進行了補充和思考。
基礎題目
Apache Kafka是什么?
能問這道題,主要是想看候選人對於Kafka的使用場景以及定位認知理解有多深,同時候可以知道候選人對於這項技術的關注度。
我們都知道,在開源軟件中,大部分軟件隨着用戶量的增加,整個軟件的功能和定位也有了新的變化,而Apache Kafka一路發展到現在,已經由最初的分布式提交日志系統逐漸演變成了實時流處理框架。
因此,這道題你最好這么回答:Apach Kafka是一款分布式流處理平台,用於實時構建流處理應用。它有一個核心的功能廣為人知,即作為企業級的消息引擎被廣泛使用(通常也會稱之為消息總線message bus)。
關於分布式流處理平台,其實從它官方的Logo以及Slogan我們就很容易看出來。

什么是消費者組?
消費者組是Kafka獨有的概念,如果面試官問這個,就說明他對此是有一定了解的。
胡大給的標准答案是:官網上的介紹言簡意賅,即消費者組是Kafka提供的可擴展且具有容錯性的消費者機制。
但實際上,消費者組(Consumer Group)其實包含兩個概念,作為隊列,消費者組允許你分割數據處理到一組進程集合上(即一個消費者組中可以包含多個消費者進程,他們共同消費該topic的數據),這有助於你的消費能力的動態調整;作為發布-訂閱模型(publish-subscribe),Kafka允許你將同一份消息廣播到多個消費者組里,以此來豐富多種數據使用場景。
需要注意的是:在消費者組中,多個實例共同訂閱若干個主題,實現共同消費。同一個組下的每個實例都配置有相同的組ID,被分配不同的訂閱分區。當某個實例掛掉的時候,其他實例會自動地承擔起它負責消費的分區。因此,消費者組在一定程度上也保證了消費者程序的高可用性。

注意:消費者組的題目,能夠幫你在某種程度上掌控下面的面試方向。
- 如果你擅長位移值原理(Offset),就不妨再提一下消費者組的位移提交機制;
- 如果你擅長Kafka Broker,可以提一下消費者組與Broker之間的交互;
- 如果你擅長與消費者組完全不相關的Producer,那么就可以這么說:“消費者組要消費的數據完全來自於Producer端生產的消息,我對Producer還是比較熟悉的。”
總之,你總得對consumer group相關的方向有一定理解,然后才能像面試官表名你對某一塊很理解。
在Kafka中,ZooKeeper的作用是什么?
這道題,也是我經常會問候選人的題,因為任何分布式系統中雖然都通過一些列的算法去除了傳統的關系型數據存儲,但是畢竟還是有些數據要存儲的,同時分布式系統的特性往往是需要有一些中間人角色來統籌集群。比如我們在整個微服務框架中的Dubbo,它也是需要依賴一些注冊中心或配置中心類的中間件的,以及雲原生的Kubernetes使用etcd作為整個集群的樞紐。
標准答案:目前,Kafka使用ZooKeeper存放集群元數據、成員管理、Controller選舉,以及其他一些管理類任務。之后,等KIP-500提案完成后,Kafka將完全不再依賴於ZooKeeper。
- “存放元數據”是指主題分區的所有數據都保存在 ZooKeeper 中,且以它保存的數據為權威,其他 “人” 都要與它保持對齊。
- “成員管理” 是指 Broker 節點的注冊、注銷以及屬性變更,等等。
- “Controller 選舉” 是指選舉集群 Controller,而其他管理類任務包括但不限於主題刪除、參數配置等。
KIP-500 思想,是使用社區自研的基於Raft的共識算法,替代ZooKeeper,實現Controller自選舉。
解釋下Kafka中位移(offset)的作用
標准答案:在Kafka中,每個主題分區下的每條消息都被賦予了一個唯一的ID數值,用於標識它在分區中的位置。這個ID數值,就被稱為位移,或者叫偏移量。一旦消息被寫入到分區日志,它的位移值將不能被修改。
答完這些之后,你還可以把整個面試方向轉移到你希望的地方:
- 如果你深諳Broker底層日志寫入的邏輯,可以強調下消息在日志中的存放格式
- 如果你明白位移值一旦被確定不能修改,可以強調下“Log Cleaner組件都不能影響位移值”這件事情
- 如果你對消費者的概念還算熟悉,可以再詳細說說位移值和消費者位移值之間的區別
闡述下 Kafka 中的領導者副本(Leader Replica)和追隨者副本(Follower Replica)的區別
推薦的答案:Kafka副本當前分為領導者副本和追隨者副本。只有Leader副本才能對外提供讀寫服務,響應Clients端的請求。Follower副本只是采用拉(PULL)的方式,被動地同步Leader副本中的數據,並且在Leader副本所在的Broker宕機后,隨時准備應聘Leader副本。
加分點:
- 強調Follower副本也能對外提供讀服務。自Kafka 2.4版本開始,社區通過引入新的Broker端參數,允許Follower副本有限度地提供讀服務。
- 強調Leader和Follower的消息序列在實際場景中不一致。通常情況下,很多因素可能造成Leader和Follower之間的不同步,比如程序問題,網絡問題,broker問題等,短暫的不同步我們可以關注(秒級別),但長時間的不同步可能就需要深入排查了,因為一旦Leader所在節點異常,可能直接影響可用性。
注意:之前確保一致性的主要手段是高水位機制(HW),但高水位值無法保證Leader連續變更場景下的數據一致性,因此,社區引入了Leader Epoch機制,來修復高水位值的弊端。
實操題目
如何設置Kafka能接收的最大消息的大小?
對於SRE來講,該題簡直是送分題啊,但是,最大消息的設置通常情況下有生產者端,消費者端,broker端和topic級別的參數,我們需要正確設置,以保證可以正常的生產和消費。
- Broker端參數:message.max.bytes,max.message.bytes(topic級別),replica.fetch.max.bytes(否則follow會同步失敗)
- Consumer端參數:fetch.message.max.bytes
監控Kafka的框架都有哪些?
對於SRE來講,依然是送分題。但基礎的我們要知道,Kafka本身是提供了JMX(Java Management Extensions)的,我們可以通過它來獲取到Kafka內部的一些基本數據。
- Kafka Manager:更多是Kafka的管理,對於SRE非常友好,也提供了簡單的瞬時指標監控。
- Kafka Monitor:LinkedIn開源的免費框架,支持對集群進行系統測試,並實時監控測試結果。
- CruiseControl:也是LinkedIn公司開源的監控框架,用於實時監測資源使用率,以及提供常用運維操作等。無UI界面,只提供REST API,可以進行多集群管理。
- JMX監控: 由於Kafka提供的監控指標都是基於JMX的,因此,市面上任何能夠集成JMX的框架都可以使用,比如Zabbix和Prometheus。
- 已有大數據平台自己的監控體系:像Cloudera提供的CDH這類大數據平台,天然就提供Kafka監控方案。
- JMXTool: 社區提供的命令行工具,能夠實時監控JMX指標。 可以使用kafka-run-class.sh kafka.tools.JmxTool來查看具體的用法。
Broker的Heap Size如何設置?
其實對於SRE還是送分題,因為目前來講大部分公司的業務系統都是使用Java開發,因此SRE對於基本的JVM相關的參數應該至少都是非常了解的,核心就在於JVM的配置以及GC相關的知識。
標准答案:任何Java進程JVM堆大小的設置都需要仔細地進行考量和測試。一個常見的做法是,以默認的初始JVM堆大小運行程序,當系統達到穩定狀態后,手動觸發一次Full GC,然后通過JVM工具查看GC后的存活對象大小。之后,將堆大小設置成存活對象總大小的1.5~2倍。對於Kafka而言,這個方法也是適用的。不過,業界有個最佳實踐,那就是將Broker的Heap Size固定為6GB。經過很多公司的驗證,這個大小是足夠且良好的。
如何估算Kafka集群的機器數量?
該題也算是SRE的送分題吧,對於SRE來講,任何生產的系統第一步需要做的就是容量預估以及集群的架構規划,實際上也就是機器數量和所用資源之間的關聯關系,資源通常來講就是CPU,內存,磁盤容量,帶寬。但需要注意的是,Kafka因為獨有的設計,對於磁盤的要求並不是特別高,普通機械硬盤足夠,而通常的瓶頸會出現在帶寬上。
在預估磁盤的占用時,你一定不要忘記計算副本同步的開銷。如果一條消息占用1KB的磁盤空間,那么,在有3個副本的主題中,你就需要3KB的總空間來保存這條消息。同時,需要考慮到整個業務Topic數據保存的最大時間,以上幾個因素,基本可以預估出來磁盤的容量需求。
需要注意的是:對於磁盤來講,一定要提前和業務溝通好場景,而不是等待真正有磁盤容量瓶頸了才去擴容磁盤或者找業務方溝通方案。
對於帶寬來說,常見的帶寬有1Gbps和10Gbps,通常我們需要知道,當帶寬占用接近總帶寬的90%時,丟包情形就會發生。
Leader總是-1,怎么破?
對於有經驗的SRE來講,早期的Kafka版本應該多多少少都遇到過該種情況,通常情況下就是Controller不工作了,導致無法分配leader,那既然知道問題后,解決方案也就很簡單了。重啟Controller節點上的Kafka進程,讓其他節點重新注冊Controller角色,但是如上面ZooKeeper的作用,你要知道為什么Controller可以自動注冊。
當然了,當你知道controller的注冊機制后,你也可以說:刪除ZooKeeper節點/controller,觸發Controller重選舉。Controller重選舉能夠為所有主題分區重刷分區狀態,可以有效解決因不一致導致的 Leader 不可用問題。但是,需要注意的是,直接操作ZooKeeper是一件風險很大的操作,就好比在Linux中執行了rm -rf /xxx一樣,如果在/和xxx之間不小心多了幾個空格,那”恭喜你”,今年白干了。
炫技式題目
LEO、LSO、AR、ISR、HW都表示什么含義?
講真,我不認為這是炫技的題目,特別是作為SRE來講,對於一個開源軟件的原理以及概念的理解,是非常重要的。
- LEO(Log End Offset):日志末端位移值或末端偏移量,表示日志下一條待插入消息的位移值。舉個例子,如果日志有10條消息,位移值從0開始,那么,第10條消息的位移值就是9。此時,LEO = 10。
- LSO(Log Stable Offset):這是Kafka事務的概念。如果你沒有使用到事務,那么這個值不存在(其實也不是不存在,只是設置成一個無意義的值)。該值控制了事務型消費者能夠看到的消息范圍。它經常與Log Start Offset,即日志起始位移值相混淆,因為有些人將后者縮寫成LSO,這是不對的。在Kafka中,LSO就是指代Log Stable Offset。
- AR(Assigned Replicas):AR是主題被創建后,分區創建時被分配的副本集合,副本個數由副本因子決定。
- ISR(In-Sync Replicas): Kafka中特別重要的概念,指代的是AR中那些與Leader保持同步的副本集合。 在AR中的副本可能不在ISR中,但Leader副本天然就包含在ISR中。
- HW(High watermark):高水位值,這是控制消費者可讀取消息范圍的重要字段。一個普通消費者只能“看到”Leader副本上介於Log Start Offset和HW(不含)之間的所有消息。水位以上的消息是對消費者不可見的。
需要注意的是,通常在ISR中,可能會有人問到為什么有時候副本不在ISR中,這其實也就是上面說的Leader和Follower不同步的情況,為什么我們前面說,短暫的不同步我們可以關注,但是長時間的不同步,我們需要介入排查了,因為ISR里的副本后面都是通過replica.lag.time.max.ms,即Follower副本的LEO落后Leader LEO的時間是否超過閾值來決定副本是否在ISR內部的。
Kafka能手動刪除消息嗎?
Kafka不需要用戶手動刪除消息。它本身提供了留存策略,能夠自動刪除過期消息。當然,它是支持手動刪除消息的。
- 對於設置了Key且參數cleanup.policy=compact的主題而言,我們可以構造一條 的消息發送給Broker,依靠Log Cleaner組件提供的功能刪除掉該 Key 的消息。
- 對於普通主題而言,我們可以使用kafka-delete-records命令,或編寫程序調用Admin.deleteRecords方法來刪除消息。這兩種方法殊途同歸,底層都是調用Admin的deleteRecords方法,通過將分區Log Start Offset值抬高的方式間接刪除消息。
__consumer_offsets是做什么用的?
這是一個內部主題,主要用於存儲消費者的偏移量,以及消費者的元數據信息(消費者實例,消費者id等等)
需要注意的是:Kafka的GroupCoordinator組件提供對該主題完整的管理功能,包括該主題的創建、寫入、讀取和Leader維護等。
分區Leader選舉策略有幾種?
分區的Leader副本選舉對用戶是完全透明的,它是由Controller獨立完成的。你需要回答的是,在哪些場景下,需要執行分區Leader選舉。每一種場景對應於一種選舉策略。
- OfflinePartition Leader選舉:每當有分區上線時,就需要執行Leader選舉。所謂的分區上線,可能是創建了新分區,也可能是之前的下線分區重新上線。這是最常見的分區Leader選舉場景。
- ReassignPartition Leader選舉:當你手動運行kafka-reassign-partitions命令,或者是調用Admin的alterPartitionReassignments方法執行分區副本重分配時,可能觸發此類選舉。假設原來的AR是[1,2,3],Leader是1,當執行副本重分配后,副本集合AR被設置成[4,5,6],顯然,Leader必須要變更,此時會發生Reassign Partition Leader選舉。
- PreferredReplicaPartition Leader選舉:當你手動運行kafka-preferred-replica-election命令,或自動觸發了Preferred Leader選舉時,該類策略被激活。所謂的Preferred Leader,指的是AR中的第一個副本。比如AR是[3,2,1],那么,Preferred Leader就是3。
- ControlledShutdownPartition Leader選舉:當Broker正常關閉時,該Broker上的所有Leader副本都會下線,因此,需要為受影響的分區執行相應的Leader選舉。
這4類選舉策略的大致思想是類似的,即從AR中挑選首個在ISR中的副本,作為新Leader。
Kafka的哪些場景中使用了零拷貝(Zero Copy)?
其實這道題對於SRE來講,有點超綱了,不過既然Zero Copy是Kafka高性能的保證,我們需要了解它。
Zero Copy是特別容易被問到的高階題目。在Kafka中,體現Zero Copy使用場景的地方有兩處:基於mmap的索引和日志文件讀寫所用的TransportLayer。
先說第一個。索引都是基於MappedByteBuffer的,也就是讓用戶態和內核態共享內核態的數據緩沖區,此時,數據不需要復制到用戶態空間。不過,mmap雖然避免了不必要的拷貝,但不一定就能保證很高的性能。在不同的操作系統下,mmap的創建和銷毀成本可能是不一樣的。很高的創建和銷毀開銷會抵消Zero Copy帶來的性能優勢。由於這種不確定性,在Kafka中,只有索引應用了mmap,最核心的日志並未使用mmap機制。
再說第二個。TransportLayer是Kafka傳輸層的接口。它的某個實現類使用了FileChannel的transferTo方法。該方法底層使用sendfile實現了Zero Copy。對Kafka而言,如果I/O通道使用普通的PLAINTEXT,那么,Kafka就可以利用Zero Copy特性,直接將頁緩存中的數據發送到網卡的Buffer中,避免中間的多次拷貝。相反,如果I/O通道啟用了SSL,那么,Kafka便無法利用Zero Copy特性了。
深度思考題
Kafka為什么不支持讀寫分離?
這其實是分布式場景下的通用問題,因為我們知道CAP理論下,我們只能保證C(可用性)和A(一致性)取其一,如果支持讀寫分離,那其實對於一致性的要求可能就會有一定折扣,因為通常的場景下,副本之間都是通過同步來實現副本數據一致的,那同步過程中肯定會有時間的消耗,如果支持了讀寫分離,就意味着可能的數據不一致,或數據滯后。
Leader/Follower模型並沒有規定Follower副本不可以對外提供讀服務。很多框架都是允許這么做的,只是 Kafka最初為了避免不一致性的問題,而采用了讓Leader統一提供服務的方式。
不過,自Kafka 2.4之后,Kafka提供了有限度的讀寫分離,也就是說,Follower副本能夠對外提供讀服務。
如何調優Kafka?
作為SRE來講,任何生產環境的調優,首先需要識別問題和瓶頸點,而不是隨意的進行臆想調優。隨后,需要確定優化目標,並且定量給出目標。
對於Kafka來講,常見的調優方向基本為:吞吐量、延時、持久性和可用性,每種目標之前都是由沖突點,這也就要求了,我們在對業務接入使用時,要進行業務場景的了解,以對業務進行相對的集群隔離,因為每一個方向的優化思路都是不同的,甚至是相反的。
確定了目標之后,還要明確優化的維度。有些調優屬於通用的優化思路,比如對操作系統、JVM等的優化;有些則是有針對性的,比如要優化Kafka的TPS。我們需要從3個方向去考慮:
- Producer端:增加batch.size和linger.ms,啟用壓縮,關閉重試
- Broker端:增加num.replica.fetchers提升Follower同步TPS,避免Broker Full GC等。
- Consumer:增加fetch.min.bytes
Controller發生網絡分區(Network Partitioning)時,Kafka會怎么樣?
這道題目能夠誘發我們對分布式系統設計、CAP理論、一致性等多方面的思考。
一旦發生Controller網絡分區,那么,第一要務就是查看集群是否出現“腦裂”,即同時出現兩個甚至是多個Controller組件。這可以根據Broker端監控指標ActiveControllerCount來判斷。
不過,通常而言,我們在設計整個部署架構時,為了避免這種網絡分區的發生,一般會將broker節點盡可能的防止在一個機房或者可用區。
由於Controller會給Broker發送3類請求,LeaderAndIsrRequest,StopReplicaRequest,UpdateMetadataRequest,因此,一旦出現網絡分區,這些請求將不能順利到達Broker端。
這將影響主題的創建、修改、刪除操作的信息同步,表現為集群仿佛僵住了一樣,無法感知到后面的所有操作。因此,網絡分區通常都是非常嚴重的問題,要趕快修復。
Java Consumer為什么采用單線程來獲取消息?
在回答之前,如果先把這句話說出來,一定會加分:Java Consumer是雙線程的設計。一個線程是用戶主線程,負責獲取消息;另一個線程是心跳線程,負責向Kafka匯報消費者存活情況。將心跳單獨放入專屬的線程,能夠有效地規避因消息處理速度慢而被視為下線的“假死”情況。
單線程獲取消息的設計能夠避免阻塞式的消息獲取方式。單線程輪詢方式容易實現異步非阻塞式,這樣便於將消費者擴展成支持實時流處理的操作算子。因為很多實時流處理操作算子都不能是阻塞式的。另外一個可能的好處是,可以簡化代碼的開發。多線程交互的代碼是非常容易出錯的。
簡述Follower副本消息同步的完整流程
首先,Follower發送FETCH請求給Leader。
接着,Leader會讀取底層日志文件中的消息數據,再更新它內存中的Follower副本的LEO值,更新為FETCH請求中的fetchOffset值。
最后,嘗試更新分區高水位值。Follower接收到FETCH響應之后,會把消息寫入到底層日志,接着更新LEO和HW值。
Leader和Follower的HW值更新時機是不同的,Follower的HW更新永遠落后於Leader的HW。這種時間上的錯配是造成各種不一致的原因。
因此,對於消費者而言,消費到的消息永遠是所有副本中最小的那個HW。
