《面試八股文》之 Kafka 21卷

大家好,我是 杰哥,作為在消息中間件中擁有神一樣地位的 kafka,你真的了解它嗎?
其他《面試八股文》系列文章請(qǐng)關(guān)注公號(hào)moon聊技術(shù)獲取~
1.什么是消息中間件?
2.kafka 是什么?有什么作用?
3.kafka 的架構(gòu)是怎么樣的?
4.Kafka Replicas是怎么管理的?
5.如何確定當(dāng)前能讀到哪一條消息?
6.生產(chǎn)者發(fā)送消息有哪些模式?
7.發(fā)送消息的分區(qū)策略有哪些?
8.Kafka 支持讀寫分離嗎?為什么?
9.那 Kafka 是怎么去實(shí)現(xiàn)負(fù)載均衡的?
10.Kafka 的負(fù)責(zé)均衡會(huì)有什么問題呢?
11.Kafka 的可靠性是怎么保證的?
12.Kafka 的消息消費(fèi)方式有哪些?
13.分區(qū)再分配是做什么的?解決了什么問題?
14.副本 leader 是怎么選舉的?
15.分區(qū)數(shù)越多越好嗎?吞吐量就會(huì)越高嗎?
16.如何增強(qiáng)消費(fèi)者的消費(fèi)能力?
17.消費(fèi)者與 topic 的分區(qū)分配策略有哪些?
18.kafka 控制器是什么?有什么作用
19.kafka 控制器是怎么進(jìn)行選舉的?
20.kafka 為什么這么快?
21.什么情況下 kafka 會(huì)丟失消息?
1.什么是消息中間件?
消息中間件是基于隊(duì)列與消息傳遞技術(shù),在網(wǎng)絡(luò)環(huán)境中為應(yīng)用系統(tǒng)提供同步或異步、可靠的消息傳輸?shù)闹涡攒浖到y(tǒng)。
消息中間件利用高效可靠的消息傳遞機(jī)制進(jìn)行平臺(tái)無關(guān)的數(shù)據(jù)交流,并基于數(shù)據(jù)通信來進(jìn)行分布式系統(tǒng)的集成。通過提供消息傳遞和消息排隊(duì)模型,它可以在分布式環(huán)境下擴(kuò)展進(jìn)程間的通信。
2.kafka 是什么?有什么作用?
Kafka 是一個(gè)分布式的流式處理平臺(tái),它以高吞吐、可持久化、可水平擴(kuò)展、支持流數(shù)據(jù)處理等多種特性而被廣泛使用

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

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

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

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

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

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

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

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

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

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

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

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

問題2:當(dāng)集群新增 broker 時(shí),只有新的主題分區(qū)會(huì)分配在該 broker 上,而老的主題分區(qū)不會(huì)分配在該 broker 上,就造成了老節(jié)點(diǎn)和新節(jié)點(diǎn)之間的負(fù)載不均衡。
為了解決該問題就出現(xiàn)了分區(qū)再分配,它可以在集群擴(kuò)容,broker 失效的場(chǎng)景下進(jìn)行分區(qū)遷移。
分區(qū)再分配的原理就是通化控制器給分區(qū)新增新的副本,然后通過網(wǎng)絡(luò)把舊的副本數(shù)據(jù)復(fù)制到新的副本上,在復(fù)制完成后,將舊副本清除。 當(dāng)然,為了不影響集群正常的性能,在此復(fù)制期間還會(huì)有一些列保證性能的操作,比如復(fù)制限流。
14.副本 leader 是怎么選舉的?
當(dāng)分區(qū) leader 節(jié)點(diǎn)崩潰時(shí),其中一個(gè) follower 節(jié)點(diǎn)會(huì)成為新的 leader 節(jié)點(diǎn),這樣會(huì)導(dǎo)致集群的負(fù)載不均衡,從而影響服務(wù)的健壯性和穩(wěn)定性。
如下:
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
我們可以看到
0 分區(qū) 1 是 leader 1 分區(qū) 2 是 leader 2 分區(qū) 0 是 leader
如果此時(shí)中間的節(jié)點(diǎn)重啟
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
我們又可以看到:
0 分區(qū) 1 是 leader 1 分區(qū) 0 是 leader 2 分區(qū) 0 是 leader
我們會(huì)發(fā)現(xiàn),原本 1 分區(qū)有兩個(gè) ledaer,經(jīng)過重啟后 leader 都消失了,如此就負(fù)載不均衡了。
為了解決這種問題,就引入了優(yōu)先副本的概念
優(yōu)先副本就是說在 AR 集合中的第一個(gè)副本。比如分區(qū) 2 的 AR 為 0,1,2,那么分區(qū) 2 的優(yōu)先副本就為0。理想情況下優(yōu)先副本就是 leader 副本。優(yōu)先副本選舉就是促使優(yōu)先副本成為 leader 副本,從而維護(hù)集群的負(fù)載均衡。
15.分區(qū)數(shù)越多越好嗎?吞吐量就會(huì)越高嗎?
一般類似于這種問題的答案,都是持否定態(tài)度的。
但是可以說,在一定條件下,分區(qū)數(shù)的數(shù)量是和吞吐量成正比的,分區(qū)數(shù)和性能也是成正比的。
那么為什么說超過了一定限度,就會(huì)對(duì)性能造成影響呢?原因如下:

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

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

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

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

