一、前言
在如今的分布式環境時代,任何一款中間件產品,大多都有一套機制去保證高可用的,Kafka 作為一個商業級消息中間件,消息可靠性的重要性可想而知,那 Kafka 如何保證可靠性的呢?本文從 Producer 往 Broker 發送消息、Topic 分區副本以及 Leader 選舉幾個角度介紹 Kafka 是如何保證可靠性的。
二、Producer 往 Broker 發送消息
如果我們要往 Kafka 對應的主題發送消息,我們需要通過 Producer 完成。前面我們講過 Kafka 主題對應了多個分區,每個分區下面又對應了多個副本;為了讓用戶設置數據可靠性, Kafka 在 Producer 里面提供了消息確認機制。把選項提供給用戶自己去選擇,我們可以通過配置來決定消息發送到對應分區的幾個副本才算消息發送成功。可以在定義 Producer 時通過 acks 參數指定(在 0.8.2.X 版本之前是通過 request.required.acks
參數設置的,詳見 KAFKA-3043)。
這個參數支持以下三種值:
-
acks = 0
:意味著如果生產者能夠通過網絡把消息發送出去,那么就認為消息已成功寫入 Kafka 。在這種情況下還是有可能發生錯誤,比如發送的對象無能被序列化或者網卡發生故障,但如果是分區離線或整個集群長時間不可用,那就不會收到任何錯誤。在 acks=0 模式下的運行速度是非常快的(這就是為什么很多基準測試都是基于這個模式),你可以得到驚人的吞吐量和帶寬利用率,不過如果選擇了這種模式, 一定會丟失一些消息。 -
acks = 1
:意味著 Leader 在收到消息并把它寫入到本地磁盤時會返回確認或錯誤響應,不管其它的 Follower 副本有沒有同步過這條消息。在這個模式下,如果發生正常的 Leader 選舉,生產者會在選舉時收到一個 LeaderNotAvailableException 異常,如果生產者能恰當地處理這個錯誤,它會重試發送消息,最終消息會安全到達新的 Leader 那里。不過在這個模式下仍然有可能丟失數據,比如消息已經成功寫入 Leader,但在消息被復制到 Follower 副本之前 Leader發生崩潰。 -
acks = all
(這個和request.required.acks = -1
含義一樣):意味著 Leader 在返回確認或錯誤響應之前,會等待所有同步副本都收到消息。如果和min.insync.replicas
參數結合起來,就可以決定在返回確認前至少有多少個副本能夠收到消息,生產者會一直重試直到消息被成功提交。不過這也是最慢的做法,因為生產者在繼續發送其他消息之前需要等待所有副本都收到當前的消息。
根據實際的應用場景,我們設置不同的 acks,以此保證數據的可靠性。
另外,Producer 發送消息還可以選擇同步(默認,通過 producer.type=sync 配置) 或者異步(producer.type=async)模式。如果設置成異步,雖然會極大的提高消息發送的性能,但是這樣會增加丟失數據的風險。如果需要確保消息的可靠性,必須將 producer.type 設置為 sync。
三、Topic 分區副本
在 Kafka 0.8.0 之前,Kafka 是沒有副本的概念的,那時候人們只會用 Kafka 存儲一些不重要的數據,因為沒有副本,數據很可能會丟失。但是隨著業務的發展,支持副本的功能越來越強烈,所以為了保證數據的可靠性,Kafka 從 0.8.0 版本開始引入了分區副本(詳情請參見 KAFKA-50)。也就是說每個分區可以人為的配置幾個副本(比如創建主題的時候指定 replication-factor,也可以在 Broker 級別進行配置 default.replication.factor),一般會設置為 3。
Kafka 可以保證單個分區里的事件是有序的,分區可以在線(可用),也可以離線(不可用)。在眾多的分區副本里面有一個副本是 Leader,其余的副本是 Follower,所有的讀寫操作都是經過 Leader 進行的,同時 Follower 會定期地去 Leader 上的復制數據。當 Leader 掛了的時候,其中一個 Follower 會重新成為新的 Leader。通過分區副本,引入了數據冗余,同時也提供了 Kafka 的數據可靠性。
Kafka 的分區多副本架構是 Kafka 可靠性保證的核心,把消息寫入多個副本可以使 Kafka 在發生崩潰時仍能保證消息的持久性。
四、Leader 選舉
在介紹 Leader 選舉之前,讓我們先來了解一下 ISR(in-sync replicas)列表。每個分區的 Leader 會維護一個 ISR 列表,ISR 列表里面包括 Leader 副本和 Follower 副本的 Broker 編號,只有跟得上 Leader 的 Follower 副本才能加入到 ISR 里面,這個是通過 replica.lag.time.max.ms
參數配置的,這個參數的含義是 Follower 副本能夠落后 Leader 副本的最長時間間隔,默認值是 10 秒。這就是說,只要一個 Follower 副本落后 Leader 副本的時間不連續超過 10 秒,那么 Kafka 就認為該 Follower 副本與 Leader 是同步的,即使此時 Follower 副本中保存的消息明顯少于 Leader 副本中的消息。
我們在前面說過,Follower 副本唯一的工作就是不斷地從 Leader 副本拉取消息,然后寫入到自己的提交日志中。如果這個同步過程的速度持續慢于 Leader 副本的消息寫入速度,那么在 replica.lag.time.max.ms 時間后,此 Follower 副本就會被認為是與 Leader 副本不同步的,因此不能再放入 ISR 中。此時,Kafka 會自動收縮 ISR 集合,將該副本“踢出”ISR。
值得注意的是,倘若該副本后面慢慢地追上了 Leader 的進度,那么它是能夠重新被加回 ISR 的。這也表明,ISR 是一個動態調整的集合,而非靜態不變的。
所以當 Leader 掛掉了,而且 unclean.leader.election.enable=false 的情況下,Kafka 會從 ISR 列表中選擇第一個 Follower 作為新的 Leader,因為這個分區擁有最新的已經 committed 的消息。通過這個可以保證已經 committed 的消息的數據可靠性。
五、思考
Q1
:對于數據的讀寫操作都在 Leader 副本中,Follower 副本只從 Leader 副本復制數據,不對外提供數據讀寫操作,只做數據冗余保證可靠性。假如 Follower 副本還沒同步完,此時 Leader 副本掛掉了,怎么保證數據的可靠性?
A1
:這就要看上面說的 Producer 通過 acks 參數,根據自己的業務場景,設置不同的參數,以此保證數據的可靠性。
Q2
:acks = 1,意味著 Leader 在收到消息并把它寫入到本地磁盤時會返回確認或錯誤響應,不管其它的 Follower 副本有沒有同步過這條消息。假如 Follower 副本還沒同步完,此時 Leader 副本掛掉了,怎么保證數據的可靠性?
A2
:這就要看上面說的 Producer 通過 acks 參數,根據自己的業務場景,設置不同的參數,以此保證數據的可靠性。
Q3
:acks = all,意味著 Leader 在返回確認或錯誤響應之前,會等待所有同步副本都收到消息。如果 Follower 副本同步完成后,Broker 給 Producer 返回 ack 之前,Leader 副本掛掉了,怎么保證數據的可靠性?
A3
:數據發送到 Leader 副本后,部分 ISR 同步完成,也就是部分在 ISR 列表中的 Follower 副本同步完成后,此時 Leader 副本掛掉了,每個 Follower 副本都有可能成為新的 Leader 副本,Producer 端會返回異常,可以設置讓 Producer 端重新發送數據來保證數據的可靠性,不過可能會造成數據的重復,如果要保證數據的一致性的話,業務下游需要做冪等操作,防止數據重復消費。
Q4
:acks = all,就可以代表數據一定不會丟失嗎?
A4
:
- Partition 只有一個副本,也就是只有一個 Leader 副本,沒有任何一個 Follower 副本的時候,接收完消息后宕機,還是會存在數據丟失。
- acks = all,必須跟 ISR 列表里至少有兩個以上的副本配合使用,min.insync.replicas 這個參數是設置 ISR 中最小副本數是多少,默認值是 1,改為 >= 2,如果 ISR 列表中的副本數小于 min.insync.replicas 配置的數量時,Producer 客戶端會返回異常。
Q5
:acks = all 時,ISR 列表中有三個副本,一個 Leader 副本,兩個 Follower 副本。 min.insync.replicas 配置的是 2,比如 Leader 副本和 Follower 1 副本收到消息,此時 Leader 副本掛掉了,就返回異常給 Producer 了,但 Follower 2 副本選舉成 Leader 副本了,Follower 2 副本落后 Leader 副本和 Follower 1 副本一些消息,雖然都是在同一個 ISR 列表中,但 Follower 2 副本沒有落后超過 replica.lag.time.max.ms 配置的值(默認是 10s)就還算是同步副本,Follower 2 副本當選 Leader 副本,前面的消息豈不是丟失了?
A5
:社區在 0.11 版本正式引入了 Leader Epoch 概念,來規避因高水位更新錯配導致的各種不一致問題。
- Epoch。一個單調增加的版本號。每當副本領導權發生變更時,都會增加該版本號。小版本號的 Leader 被認為是過期 Leader,不能再行使 Leader 權力。
- 起始位移(Start Offset)。Leader 副本在該 Epoch 值上寫入的首條消息的位移。
舉個例子來說明一下 Leader Epoch。假設現在有兩個 Leader Epoch<0, 0> 和 <1, 120>,那么,第一個 Leader Epoch 表示版本號是 0,這個版本的 Leader 從位移 0 開始保存消息,一共保存了 120 條消息。之后,Leader 發生了變更,版本號增加到 1,新版本的起始位移是 120。
這樣就可以保證 Follower 2 副本選舉成 Leader 副本時,沿用之前的舊的 Leader 副本的最后的消息位移。