微信公眾號:moon聊技術
關注選擇“ 星標 ”, 重磅干貨,第一 時間送達!
[如果你覺得文章對你有幫助,歡迎關注,在看,點贊,轉發(fā)]

大家好,我是moon,最新一篇面試八股文系列 kafka 篇也出爐了,大家還不卷起來嗎?
- 1.什么是消息中間件?
- 2.kafka 是什么?有什么作用?
- 3.kafka 的架構是怎么樣的?
- 4.Kafka Replicas是怎么管理的?
- 5.如何確定當前能讀到哪一條消息?
- 6.生產者發(fā)送消息有哪些模式?
- 7.發(fā)送消息的分區(qū)策略有哪些?
- 8.Kafka 支持讀寫分離嗎?為什么?
- 9.那 Kafka 是怎么去實現負載均衡的?
- 10.Kafka 的負責均衡會有什么問題呢?
- 11.Kafka 的可靠性是怎么保證的?
- 12.Kafka 的消息消費方式有哪些?
- 13.分區(qū)再分配是做什么的?解決了什么問題?
- 14.副本 leader 是怎么選舉的?
- 15.分區(qū)數越多越好嗎?吞吐量就會越高嗎?
- 16.如何增強消費者的消費能力?
- 17.消費者與 topic 的分區(qū)分配策略有哪些?
- 18.kafka 控制器是什么?有什么作用
- 19.kafka 控制器是怎么進行選舉的?
- 20.kafka 為什么這么快?
- 21.什么情況下 kafka 會丟失消息?
1.什么是消息中間件?
消息中間件是基于隊列與消息傳遞技術,在網絡環(huán)境中為應用系統(tǒng)提供同步或異步、可靠的消息傳輸的支撐性軟件系統(tǒng)。
消息中間件利用高效可靠的消息傳遞機制進行平臺無關的數據交流,并基于數據通信來進行分布式系統(tǒng)的集成。通過提供消息傳遞和消息排隊模型,它可以在分布式環(huán)境下擴展進程間的通信。
2.kafka 是什么?有什么作用?
Kafka 是一個分布式的流式處理平臺,它以高吞吐、可持久化、可水平擴展、支持流數據處理等多種特性而被廣泛使用

主要功能體現于三點:
- 消息系統(tǒng):kafka與傳統(tǒng)的消息中間件都具備系統(tǒng)解耦、冗余存儲、流量削峰、緩沖、異步通信、擴展性、可恢復性等功能。與此同時,kafka還提供了大多數消息系統(tǒng)難以實現的消息順序性保障及回溯性消費的功能。
- 存儲系統(tǒng):kafka把消息持久化到磁盤,相比于其他基于內存存儲的系統(tǒng)而言,有效的降低了消息丟失的風險。這得益于其消息持久化和多副本機制。也可以將kafka作為長期的存儲系統(tǒng)來使用,只需要把對應的數據保留策略設置為“永久”或啟用主題日志壓縮功能。
- 流式處理平臺:kafka為流行的流式處理框架提供了可靠的數據來源,還提供了一個完整的流式處理框架,比如窗口、連接、變換和聚合等各類操作。
3.kafka 的架構是怎么樣的?

一個典型的 kafka 體系架構包括若干 Producer、若干 Consumer、以及一個 Zookeeper 集群(在2.8.0版本中移,除了 Zookeeper,通過 KRaft 進行自己的集群管理)
Producer 將消息發(fā)送到 Broker,Broker 負責將受到的消息存儲到磁盤中,而 Consumer 負責從 Broker 訂閱并消費消息。
Kafka 基本概念:
- Producer :生產者,負責將消息發(fā)送到 Broker
- Consumer :消費者,從 Broker 接收消息
- Consumer Group :消費者組,由多個 Consumer 組成。消費者組內每個消費者負責消費不同分區(qū)的數據,一個分區(qū)只能由一個組內消費者消費;消費者組之間互不影響。所有的消費者都屬于某個消費者組,即消費者組是邏輯上的一個訂閱者。
- Broker :可以看做一個獨立的 Kafka 服務節(jié)點或 Kafka 服務實例。如果一臺服務器上只部署了一個 Kafka 實例,那么我們也可以將 Broker 看做一臺 Kafka 服務器。
- Topic :一個邏輯上的概念,包含很多 Partition,同一個 Topic 下的 Partiton 的消息內容是不相同的。
- Partition :為了實現擴展性,一個非常大的 topic 可以分布到多個 broker 上,一個 topic 可以分為多個 partition,每個 partition 是一個有序的隊列。
- Replica :副本,同一分區(qū)的不同副本保存的是相同的消息,為保證集群中的某個節(jié)點發(fā)生故障時,該節(jié)點上的 partition 數據不丟失,且 kafka 仍然能夠繼續(xù)工作,kafka 提供了副本機制,一個 topic 的每個分區(qū)都有若干個副本,一個 leader 和若干個 follower。
- Leader :每個分區(qū)的多個副本中的"主副本",生產者以及消費者只與 Leader 交互。
- Follower :每個分區(qū)的多個副本中的"從副本",負責實時從 Leader 中同步數據,保持和 Leader 數據的同步。Leader 發(fā)生故障時,從 Follower 副本中重新選舉新的 Leader 副本對外提供服務。
4.Kafka Replicas是怎么管理的?

- AR:分區(qū)中的所有 Replica 統(tǒng)稱為 AR
- ISR:所有與 Leader 副本保持一定程度同步的Replica(包括 Leader 副本在內)組成 ISR
- OSR:與 Leader 副本同步滯后過多的 Replica 組成了 OSR
Leader 負責維護和跟蹤 ISR 集合中所有 Follower 副本的滯后狀態(tài),當 Follower 副本落后過多時,就會將其放入 OSR 集合,當 Follower 副本追上了 Leader 的進度時,就會將其放入 ISR 集合。
默認情況下,只有 ISR 中的副本才有資格晉升為 Leader。
5.如何確定當前能讀到哪一條消息?
分區(qū)相當于一個日志文件,我們先簡單介紹幾個概念

如上圖是一個分區(qū)日志文件
- 標識共有7條消息,offset (消息偏移量)分別是0~6
- 0 代表這個日志文件的開始
- HW(High Watermark) 為4,0~3 代表這個日志文件可以消費的區(qū)間,消費者只能消費到這四條消息
- LEO 代表即將要寫入消息的偏移量 offset
分區(qū) ISR 集合中的每個副本都會維護自己的 LEO,而 ISR 集合中最小的LEO 即為分區(qū)的 HW

如上圖: 三個分區(qū)副本都是 ISR集合當中的,最小的 LEO 為 3,就代表分區(qū)的 HW 為3,所以當前分區(qū)只能消費到 0~2 之間的三條數據,如下圖

6.生產者發(fā)送消息有哪些模式?
總共有三種模式
-
1.發(fā)后即忘(fire-and-forget)
- 它只管往 Kafka 里面發(fā)送消息,但是不關心消息是否正確到達,這種方式的效率最高,但是可靠性也最差,比如當發(fā)生某些不可充實異常的時候會造成消息的丟失
-
2.同步(sync)
- producer.send()返回一個Future對象,調用get()方法變回進行同步等待,就知道消息是否發(fā)送成功,發(fā)送一條消息需要等上個消息發(fā)送成功后才可以繼續(xù)發(fā)送
-
3.異步(async)
- Kafka支持 producer.send() 傳入一個回調函數,消息不管成功或者失敗都會調用這個回調函數,這樣就算是異步發(fā)送,我們也知道消息的發(fā)送情況,然后再回調函數中選擇記錄日志還是重試都取決于調用方
7.發(fā)送消息的分區(qū)策略有哪些?

- 1.輪詢:依次將消息發(fā)送該topic下的所有分區(qū),如果在創(chuàng)建消息的時候 key 為 null,Kafka 默認采用這種策略。
- 2.key 指定分區(qū):在創(chuàng)建消息是 key 不為空,并且使用默認分區(qū)器,Kafka 會將 key 進行 hash,然后根據hash值映射到指定的分區(qū)上。這樣的好處是 key 相同的消息會在一個分區(qū)下,Kafka 并不能保證全局有序,但是在每個分區(qū)下的消息是有序的,按照順序存儲,按照順序消費。在保證同一個 key 的消息是有序的,這樣基本能滿足消息的順序性的需求。但是如果 partation 數量發(fā)生變化,那就很難保證 key 與分區(qū)之間的映射關系了。
- 3.自定義策略:實現 Partitioner 接口就能自定義分區(qū)策略。
- 4.指定 Partiton 發(fā)送
8.Kafka 支持讀寫分離嗎?為什么?
Kafka 是不支持讀寫分離的,那么讀寫分離的好處是什么?主要就是讓一個節(jié)點去承擔另一個節(jié)點的負載壓力,也就是能做到一定程度的負載均衡,而且 Kafka 不通過讀寫分離也可以一定程度上去實現負載均衡。
但是對于 Kafka 的架構來說,讀寫分離有兩個很大的缺點

- 1.數據不一致的問題:讀寫分離必然涉及到數據的同步,只要是不同節(jié)點之間的數據同步,必然會有數據不一致的問題存在。
- 2.延時問題:由于 Kafka 獨特的數據處理方式,導致如果將數據從一個節(jié)點同步到另一個節(jié)點必然會經過主節(jié)點磁盤和從節(jié)點磁盤,對一些延時性要求較高的應用來說,并不太適用
9.那 Kafka 是怎么去實現負載均衡的?
Kafka 的負責均衡主要是通過分區(qū)來實現的,我們知道 Kafka 是主寫主讀的架構,如下圖:

共三個 broker ,里面各有三個副本,總共有三個 partation, 深色的是 leader,淺色的是 follower,上下灰色分別代表生產者和消費者,虛線代表 follower 從 leader 拉取消息。
我們從這張圖就可以很明顯的看出來,每個 broker 都有消費者拉取消息,每個 broker 也都有生產者發(fā)送消息,每個 broker 上的讀寫負載都是一樣的,這也說明了 kafka 獨特的架構方式可以通過主寫主讀來實現負載均衡。
10.Kafka 的負責均衡會有什么問題呢?
kafka的負載均衡在絕對理想的狀況下可以實現,但是會有某些情況出現一定程度上的負載不均衡
- 1.broker 端分配不均:當創(chuàng)建 topic 的時候可能會出現某些 broker 分配到的分區(qū)數多,而有些 broker 分配的分區(qū)少,這就導致了 leader 多副本不均。
- 2.生產者寫入消息不均:生產者可能只對某些 broker 中的 leader 副本進行大量的寫入操作,而對其他的 leader 副本不聞不問。
- 3.消費者消費不均:消費者可能只對某些 broker 中的 leader 副本進行大量的拉取操作,而對其他的 leader 副本不聞不問。
- 4.leader 副本切換不均:當主從副本切換或者分區(qū)副本進行了重分配后,可能會導致各個 broker 中的 leader 副本分配不均勻。
11.Kafka 的可靠性是怎么保證的?

1.acks
這個參數用來指定分區(qū)中有多少個副本收到這條消息,生產者才認為這條消息是寫入成功的,這個參數有三個值:
- 1.acks = 1,默認為1。生產者發(fā)送消息,只要 leader 副本成功寫入消息,就代表成功。這種方案的問題在于,當返回成功后,如果 leader 副本和 follower 副本還沒有來得及同步,leader 就崩潰了,那么在選舉后新的 leader 就沒有這條消息,也就丟失了。
- 2.acks = 0。生產者發(fā)送消息后直接算寫入成功,不需要等待響應。這個方案的問題很明顯,只要服務端寫消息時出現任何問題,都會導致消息丟失。
- 3.acks = -1 或 acks = all。生產者發(fā)送消息后,需要等待 ISR 中的所有副本都成功寫入消息后才能收到服務端的響應。毫無疑問這種方案的可靠性是最高的,但是如果 ISR 中只有l(wèi)eader 副本,那么就和 acks = 1 毫無差別了。
2.消息發(fā)送的方式
第6問中我們提到了生產者發(fā)送消息有三種方式,發(fā)完即忘,同步和異步。我們可以通過同步或者異步獲取響應結果,失敗做重試來保證消息的可靠性。
3.手動提交位移
默認情況下,當消費者消費到消息后,就會自動提交位移。但是如果消費者消費出錯,沒有進入真正的業(yè)務處理,那么就可能會導致這條消息消費失敗,從而丟失。我們可以開啟手動提交位移,等待業(yè)務正常處理完成后,再提交offset。
4.通過副本 LEO 來確定分區(qū) HW
可參考第五問
12.Kafka 的消息消費方式有哪些?
一般消息消費有兩種模式,推和拉。Kafka的消費是屬于拉模式的,而此模式的消息消費方式有兩種,點對點和發(fā)布訂閱。

- 1.點對點:如果所有消費者屬于同一個消費組,那么所有的消息都會被均勻的投遞給每一個消費者,每條消息只會被其中一個消費者消費。

- 2.發(fā)布訂閱:如果所有消費者屬于不同的消費組,那么所有的消息都會被投遞給每一個消費者,每個消費者都會收到該消息。
13.分區(qū)再分配是做什么的?解決了什么問題?
分區(qū)再分配主要是用來維護 kafka 集群的負載均衡
既然是分區(qū)再分配,那么 kafka 分區(qū)有什么問題呢?

-
問題1:當集群中的一個節(jié)點下線了
- 如果該節(jié)點的分區(qū)是單副本的,那么分區(qū)將會變得不可用
- 如果是多副本的,就會進行 leader 選舉,在其他機器上選舉出新的 leader
kafka 并不會將這些失效的分區(qū)遷移到其他可用的 broker 上,這樣就會影響集群的負載均衡,甚至也會影響服務的可靠性和可用性

- 問題2:當集群新增 broker 時,只有新的主題分區(qū)會分配在該 broker 上,而老的主題分區(qū)不會分配在該 broker 上,就造成了老節(jié)點和新節(jié)點之間的負載不均衡。
為了解決該問題就出現了分區(qū)再分配,它可以在集群擴容,broker 失效的場景下進行分區(qū)遷移。
分區(qū)再分配的原理就是通化控制器給分區(qū)新增新的副本,然后通過網絡把舊的副本數據復制到新的副本上,在復制完成后,將舊副本清除。 當然,為了不影響集群正常的性能,在此復制期間還會有一些列保證性能的操作,比如復制限流。
14.副本 leader 是怎么選舉的?
當分區(qū) leader 節(jié)點崩潰時,其中一個 follower 節(jié)點會成為新的 leader 節(jié)點,這樣會導致集群的負載不均衡,從而影響服務的健壯性和穩(wěn)定性。
如下:
<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">Topic: test Partation:0 Leader:1 Replicas:1,2,0 Isr:1,2,0 Topic: test Partation:1 Leader:2 Replicas:2,0,1 Isr:2,0,1 Topic: test Partation:2 Leader:0 Replicas:0,1,2 Isr:0,1,2 </pre>
我們可以看到
- 0 分區(qū)有 1 個 leader
- 1 分區(qū)有 2 個 leader
- 2 分區(qū)有 0 個 leader
如果此時中間的節(jié)點重啟
<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">Topic: test Partation:0 Leader:1 Replicas:1,2,0 Isr:1,0,2 Topic: test Partation:1 Leader:0 Replicas:2,0,1 Isr:0,1,2 Topic: test Partation:2 Leader:0 Replicas:0,1,2 Isr:0,1,2 </pre>
我們又可以看到:
- 0 分區(qū)有 1 個 leader
- 1 分區(qū)有 0 個 leader
- 2 分區(qū)有 0 個 leader
我們會發(fā)現,原本 1 分區(qū)有兩個 ledaer,經過重啟后 leader 都消失了,如此就負載不均衡了。
為了解決這種問題,就引入了優(yōu)先副本的概念
優(yōu)先副本就是說在 AR 集合中的第一個副本。比如分區(qū) 2 的 AR 為 0,1,2,那么分區(qū) 2 的優(yōu)先副本就為0。理想情況下優(yōu)先副本就是 leader 副本。優(yōu)先副本選舉就是促使優(yōu)先副本成為 leader 副本,從而維護集群的負載均衡。
15.分區(qū)數越多越好嗎?吞吐量就會越高嗎?
一般類似于這種問題的答案,都是持否定態(tài)度的。
但是可以說,在一定條件下,分區(qū)數的數量是和吞吐量成正比的,分區(qū)數和性能也是成正比的。
那么為什么說超過了一定限度,就會對性能造成影響呢?原因如下:

1.客戶端/服務器端需要使用的內存就越多
- 服務端在很多組件中都維護了分區(qū)級別的緩存,分區(qū)數越大,緩存成本也就越大。
- 消費端的消費線程數是和分區(qū)數掛鉤的,分區(qū)數越大消費線程數也就越多,線程的開銷成本也就越大
- 生產者發(fā)送消息有緩存的概念,會為每個分區(qū)緩存消息,當積累到一定程度或者時間時會將消息發(fā)送到分區(qū),分區(qū)越多,這部分的緩存也就越大
2.文件句柄的開銷
每個 partition 都會對應磁盤文件系統(tǒng)的一個目錄。在 Kafka 的數據日志文件目錄中,每個日志數據段都會分配兩個文件,一個索引文件和一個數據文件。每個 broker 會為每個日志段文件打開一個 index 文件句柄和一個數據文件句柄。因此,隨著 partition 的增多,所需要保持打開狀態(tài)的文件句柄數也就越多,最終可能超過底層操作系統(tǒng)配置的文件句柄數量限制。
3.越多的分區(qū)可能增加端對端的延遲
Kafka 會將分區(qū) HW 之前的消息暴露給消費者。分區(qū)越多則副本之間的同步數量就越多,在默認情況下,每個 broker 從其他 broker 節(jié)點進行數據副本復制時,該 broker 節(jié)點只會為此工作分配一個線程,該線程需要完成該 broker 所有 partition 數據的復制。
4.降低高可用性
在第 13 問我們提到了分區(qū)再分配,會將數據復制到另一份副本當中,分區(qū)數量越多,那么恢復時間也就越長,而如果發(fā)生宕機的 broker 恰好是 controller 節(jié)點時:在這種情況下,新 leader 節(jié)點的選舉過程在 controller 節(jié)點恢復到新的 broker 之前不會啟動。controller 節(jié)點的錯誤恢復將會自動地進行,但是新的 controller 節(jié)點需要從 zookeeper 中讀取每一個 partition 的元數據信息用于初始化數據。例如,假設一個Kafka 集群存在 10000個partition,從 zookeeper 中恢復元數據時每個 partition 大約花費 2 ms,則 controller 的恢復將會增加約 20 秒的不可用時間窗口。
16.如何增強消費者的消費能力?
- 1.可以考慮增加 topic 的分區(qū)數,并且同時提升消費組的消費者數量,消費者數=分區(qū)數。
- 2.如果是消費者消費不及時,可以采用多線程的方式進行消費,并且優(yōu)化業(yè)務方法流程,同樣的分區(qū)數,為什么人家并發(fā)那么高,你的就不行??
17.消費者與 topic 的分區(qū)分配策略有哪些?

1.RangeAssignor 分配策略
該分配策略是按照消費者總數和分區(qū)總數進行整除運算來獲得一個跨度,然后分區(qū)按照跨度來進行平均分配,盡可能保證分區(qū)均勻的分配給所有的消費者。
對于每個 topic,該策略會講消費者組內所有訂閱這個主題的消費者按照名稱的字典順序排序,然后為每個消費者劃分固定過的區(qū)域,如果不夠平均分配,那么字典排序考前的就會多分配一個分區(qū)。
比如 2 個消費者屬于一個消費者組,有 2 個 topic t1,t2,每個 topic 都有 3 個分區(qū),p1,p2,p3,那么分配的情況如下:
<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">消費者A:t0-p0,t0-p1,t1-p0,t1-p1, 消費者B:t0-p2,t1-p2 </pre>
這樣就會出現非配不均勻的情況
2.RoundRobinAssignor 分配策略
該分配策略是按將消費者組內所有消費者及消費者訂閱的所有主題的分區(qū)按照字典排序,然后通過輪詢的方式分配給每個消費者。
比如有 3 個消費者 A,B,C,訂閱了 3 個 topic ,t0,t1,t2,每個 topic 各有 3 個分區(qū) p0,p1,p2。 如果 A 訂閱了 t0,B 訂閱了 t0 和 t1,C 訂閱了 t0,t1,t2,那么分配的情況如下:
<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">消費者A:t0-p0 消費者B:t1-p0 消費者C:t1-p1,t2-p0,t2-p1,t2-p2 </pre>
這樣也會出現分配不均勻的情況,按照訂閱情況來講完全可以吧 t1p1 分配給消費者B
3.StickyAssignor分配策略
這種分配策略有兩個目的
- 1.分區(qū)的分配要盡可能的均勻
- 2.分區(qū)的分配盡可能的與上次分配的保持相同。
當兩者發(fā)生沖突時,第一個目標優(yōu)先于第二個目標。
假設消費組內有3個消費者:C0、C1、C2
它們都訂閱了4個主題:t0、t1、t2、t3
并且每個主題有2個分區(qū),也就是說整個消費組訂閱了,t0p0、t0p1、t1p0、t1p1、t2p0、t2p1、t3p0、t3p1 這8個分區(qū)
最終的分配結果如下:
<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`消費者C0:t0p0、t1p1、t3p0
消費者C1:t0p1、t2p0、t3p1
消費者C2:t1p0、t2p1` </pre>
這樣初看上去似乎與采用RoundRobinAssignor策略所分配的結果相同
此時假設消費者C1脫離了消費組,那么消費組就會執(zhí)行再平衡操作,進而消費分區(qū)會重新分配。如果采用RoundRobinAssignor策略,那么此時的分配結果如下:
<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`消費者C0:t0p0、t1p0、t2p0、t3p0
消費者C2:t0p1、t1p1、t2p1、t3p1` </pre>
如分配結果所示,RoundRobinAssignor策略會按照消費者C0和C2進行重新輪詢分配。而如果此時使用的是StickyAssignor策略,那么分配結果為:
<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`消費者C0:t0p0、t1p1、t3p0、t2p0
消費者C2:t1p0、t2p1、t0p1、t3p1` </pre>
可以看到分配結果中保留了上一次分配中對于消費者C0和C2的所有分配結果,并將原來消費者C1的“負擔”分配給了剩余的兩個消費者C0和C2,最終C0和C2的分配還保持了均衡。
如果發(fā)生分區(qū)重分配,那么對于同一個分區(qū)而言有可能之前的消費者和新指派的消費者不是同一個,對于之前消費者進行到一半的處理還要在新指派的消費者中再次復現一遍,這顯然很浪費系統(tǒng)資源。StickyAssignor策略如同其名稱中的“sticky”一樣,讓分配策略具備一定的“粘性”,盡可能地讓前后兩次分配相同,進而減少系統(tǒng)資源的損耗以及其它異常情況的發(fā)生。
到目前為止所分析的都是消費者的訂閱信息都是相同的情況,我們來看一下訂閱信息不同的情況下的處理。
舉例: 同樣消費組內有3個消費者:C0、C1、C2
集群中有3個主題 t0、t1、t2
這3個主題分別有 1、2、3個分區(qū)
也就是說集群中有 t0p0、t1p0、t1p1、t2p0、t2p1、t2p2 這6個分區(qū)
消費者C0訂閱了主題t0,消費者C1訂閱了主題t0和t1,消費者C2訂閱了主題t0、t1和t2
如果此時采用RoundRobinAssignor策略:
<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`消費者C0:t0p0
消費者C1:t1p0
消費者C2:t1p1、t2p0、t2p1、t2p2` </pre>
如果此時采用的是StickyAssignor策略:
<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`消費者C0:t0p0
消費者C1:t1p0、t1p1
消費者C2:t2p0、t2p1、t2p2` </pre>
此時消費者C0脫離了消費組,那么RoundRobinAssignor策略的分配結果為:
<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`消費者C1:t0p0、t1p1
消費者C2:t1p0、t2p0、t2p1、t2p2` </pre>
StickyAssignor策略,那么分配結果為:
<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`消費者C1:t1p0、t1p1、t0p0
消費者C2:t2p0、t2p1、t2p2` </pre>
可以看到StickyAssignor策略保留了消費者C1和C2中原有的5個分區(qū)的分配:
<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">t1p0、t1p1、t2p0、t2p1、t2p2。 </pre>
從結果上看StickyAssignor策略比另外兩者分配策略而言顯得更加的優(yōu)異,這個策略的代碼實現也是異常復雜。
4.自定義分區(qū)分配策略
可以通過實現 org.apache.kafka.clients.consumer.internals.PartitionAssignor 接口來實現
18.kafka 控制器是什么?有什么作用
在 Kafka 集群中會有一個或多個 broker,其中有一個 broker 會被選舉為控制器,它負責管理整個集群中所有分區(qū)和副本的狀態(tài),kafka 集群中只能有一個控制器。
- 當某個分區(qū)的 leader 副本出現故障時,由控制器負責為該分區(qū)選舉新的 leader 副本。
- 當檢測到某個分區(qū)的ISR集合發(fā)生變化時,由控制器負責通知所有 broker 更新其元數據信息。
- 當為某個 topic 增加分區(qū)數量時,由控制器負責分區(qū)的重新分配。
19.kafka 控制器是怎么進行選舉的?
kafka 中的控制器選舉工作依賴于 Zookeeper,成功競選成為控制器的 broker 會在Zookeeper中創(chuàng)建/controller臨時節(jié)點。
每個 broker 啟動的時候會去嘗試讀取/controller 節(jié)點的 brokerid的值
- 如果讀取到的 brokerid 的值不為-1,表示已經有其他broker 節(jié)點成功競選為控制器,所以當前 broker 就會放棄競選;
如果Zookeeper中不存在/controller 節(jié)點,或者這個節(jié)點的數據異常,那么就會嘗試去創(chuàng)建/controller 節(jié)點,創(chuàng)建成功的那個 broker 就會成為控制器。
每個 broker 都會在內存中保存當前控制器的 brokerid 值,這個值可以標識為 activeControllerId。
Zookeeper 中還有一個與控制器有關的/controller_epoch 節(jié)點,這個節(jié)點是持久節(jié)點,節(jié)點中存放的是一個整型的 controller_epoch 值。controller_epoch 值用于記錄控制器發(fā)生變更的次數。
controller_epoch 的初始值為1,即集群中的第一個控制器的紀元為1,當控制器發(fā)生變更時,每選出一個新的控制器就將該字段值加1。
每個和控制器交互的請求都會攜帶 controller_epoch 這個字段,
- 如果請求的 controller_epoch 值小于內存中的 controller_epoch值,則認為這個請求是向已經過期的控制器發(fā)送的請求,那么這個請求會被認定為無效的請求。
- 如果請求的 controller_epoch 值大于內存中的 controller_epoch值,那么說明已經有新的控制器當選了
20.kafka 為什么這么快?

-
1.順序讀寫
磁盤分為順序讀寫與隨機讀寫,基于磁盤的隨機讀寫確實很慢,但磁盤的順序讀寫性能卻很高,kafka 這里采用的就是順序讀寫。
-
2.Page Cache
為了優(yōu)化讀寫性能,Kafka 利用了操作系統(tǒng)本身的 Page Cache,就是利用操作系統(tǒng)自身的內存而不是JVM空間內存。
-
3.零拷貝
Kafka使用了零拷貝技術,也就是直接將數據從內核空間的讀緩沖區(qū)直接拷貝到內核空間的 socket 緩沖區(qū),然后再寫入到 NIC 緩沖區(qū),避免了在內核空間和用戶空間之間穿梭。
-
4.分區(qū)分段+索引
Kafka 的 message 是按 topic分 類存儲的,topic 中的數據又是按照一個一個的 partition 即分區(qū)存儲到不同 broker 節(jié)點。每個 partition 對應了操作系統(tǒng)上的一個文件夾,partition 實際上又是按照segment分段存儲的。
通過這種分區(qū)分段的設計,Kafka 的 message 消息實際上是分布式存儲在一個一個小的 segment 中的,每次文件操作也是直接操作的 segment。為了進一步的查詢優(yōu)化,Kafka 又默認為分段后的數據文件建立了索引文件,就是文件系統(tǒng)上的.index文件。這種分區(qū)分段+索引的設計,不僅提升了數據讀取的效率,同時也提高了數據操作的并行度。
-
5.批量讀寫
Kafka 數據讀寫也是批量的而不是單條的,這樣可以避免在網絡上頻繁傳輸單個消息帶來的延遲和帶寬開銷。假設網絡帶寬為10MB/S,一次性傳輸10MB的消息比傳輸1KB的消息10000萬次顯然要快得多。
-
6.批量壓縮
Kafka 把所有的消息都變成一個批量的文件,并且進行合理的批量壓縮,減少網絡 IO 損耗,通過 mmap 提高 I/O 速度,寫入數據的時候由于單個Partion是末尾添加所以速度最優(yōu);讀取數據的時候配合 sendfile 進行直接讀取。
21.什么情況下 kafka 會丟失消息?
Kafka 有三次消息傳遞的過程: 生產者發(fā)消息給 Broker,Broker 同步消息和持久化消息,Broker 將消息傳遞給消費者。

這其中每一步都有可能丟失消息.
-
1.生產者發(fā)送數據: 在第 11 問中的 acks中有說到
- 當 acks 為 0,只要服務端寫消息時出現任何問題,都會導致消息丟失。
- 當 acks 配置為 1 時,生產者發(fā)送消息,只要 leader 副本成功寫入消息,就代表成功。這種方案的問題在于,當返回成功后,如果 leader 副本和 follower 副本還沒有來得及同步,leader 就崩潰了,那么在選舉后新的 leader 就沒有這條消息,也就丟失了。
-
2.Broker 存儲數據:kafka 通過 Page Cache 將數據寫入磁盤。
- Page Cache 就是當往磁盤文件寫入的時候,系統(tǒng)會先將數據流寫入緩存中,但是什么時候將緩存的數據寫入文件中是由操作系統(tǒng)自行決定。所以如果此時機器突然掛了,也是會丟失消息的。
3.消費者消費數據:在開啟自動提交 offset 時,只要消費者消費到消息,那么就會自動提交偏移量,如果業(yè)務還沒有來得及處理,那么消息就會丟失。