Kafka 常見面試問題總結

現如今,Kafka 已不再是一個單純的消息隊列系統。Kafka 是一個分佈式的流處理平臺,被越來越多的公司使用,Kafka 可以被用於高性能的數據管道,流處理分析,數據集成等場景。本文分享總結了幾個 Kafka 常見的面試問題,希望對你有所幫助。主要包括以下內容:

Q1:Kafka 是如何保障數據不丟失的?

該問題已經成爲了 Kafka 面試的慣例,如同 Java 的 HashMap,屬於高頻出現的面試問題。那麼,我們該怎麼理解這個問題呢?問題是 Kafka 如何保障數據不丟失,即 Kafka 的 Broker 提供了什麼機制保證數據不丟失的。

其實對於 Kafka 的 Broker 而言,Kafka 的複製機制分區的多副本架構是 Kafka 可靠性保證的核心。把消息寫入多個副本可以使 Kafka 在發生崩潰時仍能保證消息的持久性。

搞清楚了問題的核心,再來看一下該怎麼回答這個問題:主要包括三個方面

1.Topic 副本因子個數:replication.factor >= 3

  1. 同步副本列表 (ISR):min.insync.replicas = 2

  2. 禁用 unclean 選舉:unclean.leader.election.enable=false

下面將會逐步分析上面的三個配置:

Kafka 的 topic 是可以分區的,並且可以爲分區配置多個副本,該配置可以通過replication.factor參數實現。Kafka 中的分區副本包括兩種類型:領導者副本(Leader Replica)和追隨者副本(Follower Replica),每個分區在創建時都要選舉一個副本作爲領導者副本,其餘的副本自動變爲追隨者副本。在 Kafka 中,追隨者副本是不對外提供服務的,也就是說,任何一個追隨者副本都不能響應消費者和生產者的讀寫請求。所有的請求都必須由領導者副本來處理。換句話說,所有的讀寫請求都必須發往領導者副本所在的 Broker,由該 Broker 負責處理。追隨者副本不處理客戶端請求,它唯一的任務就是從領導者副本異步拉取消息,並寫入到自己的提交日誌中,從而實現與領導者副本的同步。

一般來說,副本設爲 3 可以滿足大部分的使用場景,也有可能是 5 個副本 (比如銀行)。如果副本因子爲 N,那麼在 N-1 個 broker 失效的情況下,仍然能夠從主題讀取數據或向主題寫入數據。所以,更高的副本因子會帶來更高的可用性、可靠性和更少的故障。另一方面,副本因子 N 需要至少 N 個 broker ,而且會有 N 個數據副本,也就是說它們會佔用 N 倍的磁盤空間。實際生產環境中一般會在可用性和存儲硬件之間作出權衡。

除此之外,副本的分佈同樣也會影響可用性。默認情況下,Kafka 會確保分區的每個副本分佈在不同的 Broker 上,但是如果這些 Broker 在同一個機架上,一旦機架的交換機發生故障,分區就會不可用。所以建議把 Broker 分佈在不同的機架上,可以使用 broker.rack 參數配置 Broker 所在機架的名稱。

In-sync replica(ISR) 稱之爲同步副本,ISR 中的副本都是與 Leader 進行同步的副本,所以不在該列表的 follower 會被認爲與 Leader 是不同步的。那麼,ISR 中存在是什麼副本呢?首先可以明確的是:Leader 副本總是存在於 ISR 中。而 follower 副本是否在 ISR 中,取決於該 follower 副本是否與 Leader 副本保持了 “同步”。

Kafka 的 broker 端有一個參數 replica.lag.time.max.ms, 該參數表示 follower 副本滯後與 Leader 副本的最長時間間隔,默認是 10 秒。這就意味着,只要 follower 副本落後於 leader 副本的時間間隔不超過 10 秒,就可以認爲該 follower 副本與 leader 副本是同步的,所以哪怕當前 follower 副本落後於 Leader 副本幾條消息,只要在 10 秒之內趕上 Leader 副本,就不會被踢出出局。

可以看出 ISR 是一個動態的,所以即便是爲分區配置了 3 個副本,還是會出現同步副本列表中只有一個副本的情況 (其他副本由於不能夠與 leader 及時保持同步,被移出 ISR 列表)。如果這個同步副本變爲不可用,我們必須在可用性一致性之間作出選擇 (CAP 理論)。

根據 Kafka 對可靠性保證的定義,消息只有在被寫入到所有同步副本之後才被認爲是已提交的。但如果這裏的 “所有副本” 只包含一個同步副本,那麼在這個副本變爲不可用時,數據就會丟失。如果要確保已提交的數據被寫入不止一個副本,就需要把最小同步副本數量設置爲大一點的值。對於一個包含 3 個副本的主題分區,如果 min.insync.replicas=2 ,那麼至少要存在兩個同步副本才能向分區寫入數據。

如果進行了上面的配置,此時必須要保證 ISR 中至少存在兩個副本,如果 ISR 中的副本個數小於 2,那麼 Broker 就會停止接受生產者的請求。嘗試發送數據的生產者會收到 NotEnoughReplicasException 異常,消費者仍然可以繼續讀取已有的數據。

選擇一個同步副本列表中的分區作爲 leader 分區的過程稱爲 clean leader election。注意,這裏要與在非同步副本中選一個分區作爲 leader 分區的過程區分開,在非同步副本中選一個分區作爲 leader 的過程稱之爲 unclean leader election。由於 ISR 是動態調整的,所以會存在 ISR 列表爲空的情況,通常來說,非同步副本落後 Leader 太多,因此,如果選擇這些副本作爲新 Leader,就可能出現數據的丟失。畢竟,這些副本中保存的消息遠遠落後於老 Leader 中的消息。在 Kafka 中,選舉這種副本的過程可以通過 Broker 端參數 unclean.leader.election.enable 控制是否允許 Unclean 領導者選舉。開啓 Unclean 領導者選舉可能會造成數據丟失,但好處是,它使得分區 Leader 副本一直存在,不至於停止對外提供服務,因此提升了高可用性。反之,禁止 Unclean Leader 選舉的好處在於維護了數據的一致性,避免了消息丟失,但犧牲了高可用性。分佈式系統的 CAP 理論說的就是這種情況。

不幸的是,unclean leader election 的選舉過程仍可能會造成數據的不一致,因爲同步副本並不是完全同步的。由於複製是異步完成的,因此無法保證 follower 可以獲取最新消息。比如 Leader 分區的最後一條消息的 offset 是 100,此時副本的 offset 可能不是 100,這受到兩個參數的影響:

  • replica.lag.time.max.ms:同步副本滯後與 leader 副本的時間

  • zookeeper.session.timeout.ms:與 zookeeper 會話超時時間

簡而言之,如果我們允許不同步的副本成爲 leader,那麼就要承擔丟失數據和出現數據不一致的風險。如果不允許它們成爲 leader,那麼就要接受較低的可用性,因爲我們必須等待原先的首領恢復到可用狀態。

關於 unclean 選舉,不同的場景有不同的配置方式。對數據質量和數據一致性要求較高的系統會禁用這種 unclean 的 leader 選舉 (比如銀行)。如果在可用性要求較高的系統裏,比如實時點擊流分析系統, 一般不會禁用 unclean 的 leader 選舉。

Q2:如何解決 Kafka 數據丟失問題?

你可能會問:這個問題跟 Q1 有什麼區別呢?其實一般在面試問題中可以理解成一個問題。之所以在這裏做出區分,是因爲兩者的解決方式不一樣。Q1 問題是從 Kafka 的 Broker 側來看待數據丟失的問題,而 Q2 是從 Kafka 的生產者與消費者的角度來看待數據丟失的問題

先來看一下如何回答這個問題:主要包括兩個方面:

  • Producer

  • retries=Long.MAX_VALUE

    設置 retries 爲一個較大的值。這裏的 retries 同樣是 Producer 的參數,對應前面提到的 Producer 自動重試。當出現網絡的瞬時抖動時,消息發送可能會失敗,此時配置了 retries > 0 的 Producer 能夠自動重試消息發送,避免消息丟失。

  • acks=all

    設置 acks = all。acks 是 Producer 的一個參數,代表了你對 “已提交” 消息的定義。如果設置成 all,則表明所有副本 Broker 都要接收到消息,該消息纔算是 “已提交”。這是最高等級的“已提交” 定義。

  • max.in.flight.requests.per.connections=1

    該參數指定了生產者在收到服務器晌應之前可以發送多少個消息。它的值越高,就會佔用越多的內存,不過也會提升吞吐量。把它設爲 1 可以保證消息是按照發送的順序寫入服務器的,即使發生了重試。

  • Producer 要使用帶有回調通知的 API,也就是說不要使用 producer.send(msg),而要使用 producer.send(msg, callback)。

  • 其他錯誤處理

    使用生產者內置的重試機制,可以在不造成消息丟失的情況下輕鬆地處理大部分錯誤,不過 仍然需要處理其他類型的錯誤,例如消息大小錯誤、序列化錯誤等等。

  • Consumer

  • 禁用自動提交:enable.auto.commit=false

  • 消費者處理完消息之後再提交 offset

  • 配置 auto.offset.reset

    這個參數指定了在沒有偏移量可提交時 (比如消費者第 l 次啓動時) 或者請求的偏移量在 broker 上不存在時(比如數據被刪了),消費者會做些什麼。

    這個參數有兩種配置。一種是 earliest:消費者會從分區的開始位置讀取數據,不管偏移量是否有效,這樣會導致消費者讀取大量的重複數據,但可以保證最少的數據丟失。一種是 latest(默認),如果選擇了這種配置, 消費者會從分區的末尾開始讀取數據,這樣可以減少重複處理消息,但很有可能會錯過一些消息。

Q3:Kafka 可以保障永久不丟失數據嗎?

上面分析了一些保障數據不丟失的措施,在一定程度上可以避免數據的丟失。但是請注意:Kafka 只對 “已提交” 的消息(committed message)做有限度的持久化保證。所以說,Kafka 不能夠完全保證數據不丟失,需要做出一些權衡。

首先,要理解什麼是已提交的消息,當 Kafka 的若干個 Broker 成功地接收到一條消息並寫入到日誌文件後,它們會告訴生產者程序這條消息已成功提交。此時,這條消息在 Kafka 看來就正式變爲已提交消息了。所以說無論是 ack=all,還是 ack=1, 不論哪種情況,Kafka 只對已提交的消息做持久化保證這件事情是不變的。

其次,要理解有限度的持久化保證,也就是說 Kafka 不可能保證在任何情況下都做到不丟失消息。必須保證 Kafka 的 Broker 是可用的,換句話說,假如消息保存在 N 個 Kafka Broker 上,那麼這個前提條件就是這 N 個 Broker 中至少有 1 個存活。只要這個條件成立,Kafka 就能保證你的這條消息永遠不會丟失。

總結一下,Kafka 是能做到不丟失消息的,只不過這些消息必須是已提交的消息,而且還要滿足一定的條件。

Q4:如何保障 Kafka 中的消息是有序的?

首先需要明確的是:Kafka 的主題是分區有序的,如果一個主題有多個分區,那麼 Kafka 會按照 key 將其發送到對應的分區中,所以,對於給定的 key,與其對應的 record 在分區內是有序的。

Kafka 可以保證同一個分區裏的消息是有序的,即生產者按照一定的順序發送消息,Broker 就會按照這個順序將他們寫入對應的分區中,同理,消費者也會按照這個順序來消費他們。

在一些場景下,消息的順序是非常重要的。比如,先存錢再取錢先取錢再存錢是截然不同的兩種結果。

上面的問題中提到一個參數 max.in.flight.requests.per.connections=1, 該參數的作用是在重試次數大於等於 1 時,保證數據寫入的順序。如果該參數不爲 1,那麼當第一個批次寫入失敗時,第二個批次寫入成功,Broker 會重試寫入第一個批次,如果此時第一個批次重試寫入成功,那麼這兩個批次消息的順序就反過來了。

一般來說,如果對消息的順序有要求,那麼在爲了保障數據不丟失,需要先設置發送重試次數 retries>0, 同時需要把 max.in.flight.requests.per.connections 參數設爲 1,這樣在生產者嘗試發送第一批消息時,就不會有其他的消息發送給 broker,雖然會影響吞吐量,但是可以保證消息的順序。

除此之外,還可以使用單分區的 Topic,但是會嚴重影響吞吐量。

Q5:如何確定合適的 Kafka 主題的分區數量?

選擇合適的分區數量可以達到高度並行讀寫和負載均衡的目的,在分區上達到均衡負載是實現吞吐量的關鍵。需要根據每個分區的生產者和消費者的期望吞吐量進行估計。

舉個栗子:假設期望讀取數據的速率 (吞吐量) 爲 1GB/Sec,而一個消費者的讀取速率爲 50MB/Sec,此時至少需要 20 個分區以及 20 個消費者 (一個消費者組)。同理,如果期望生產數據的速率爲 1GB/Sec,而每個生產者的生產速率爲 100MB/Sec,此時就需要有 10 個分區。在這種情況下,如果設置 20 個分區,既可以保障 1GB/Sec 的生產速率,也可以保障消費者的吞吐量。通常需要將分區的數量調整爲消費者或者生產者的數量,只有這樣纔可以同時實現生產者和消費者的吞吐量。

一個簡單的計算公式爲:分區數 = max(生產者數量,消費者數量)

Q6:如何調整生產環境中 Kafka 主題的分區數量?

需要注意的是:當我們增加主題的分區數量時,會違背同一個 key 進行同一個分區的事實。我們可以創建一個新的主題,使得該主題有更多的分區數,然後暫停生產者,將舊的主題中的數據複製到新的主題中,然後將消費者和生產者切換到新的主題,操作起來會非常棘手。

Q7: 如何重平衡 Kafka 集羣?

在下面情況發生時,需要重平衡集羣:

使用 kafka-reassign-partitions.sh 命令進行重平衡

Q8: 如何查看消費者組是否存在滯後消費?

我們可以使用 kafka-consumer-groups.sh 命令進行查看,比如:

$ bin/kafka-consumer-groups.sh --bootstrap-server cdh02:9092 --describe --group my-group
## 會顯示下面的一些指標信息
TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET   LAG          CONSUMER-ID HOST CLIENT-ID
主題   分區       當前offset      LEO           滯後消息數       消費者id     主機   客戶端id

一般情況下,如果運行良好,CURRENT-OFFSET 的值會與 LOG-END-OFFSET 的值非常接近。通過這個命令可以查看哪個分區的消費出現了滯後。

總結

本文主要分享了 8 個常見的 Kafka 面試題,對於每個題目都給出了相應的答案。對照這些問題,相信會對 Kafka 會有更深刻的認識。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ATCOuthu95lY1CwlaMTnYQ