數據可靠性
Kafka 作為一個商業級消息中間件,消息可靠性的重要性可想而知。本文從 Producter 往 Broker 發送消息、Topic 分區副本以及 Leader 選舉幾個角度介紹數據的可靠性。
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 在發生崩潰時仍能保證消息的持久性。
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 在收到消息并把它寫入到分區數據文件(不一定同步到磁盤上)時會返回確認或錯誤響應。在這個模式下,如果發生正常的 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。
Leader 選舉
在介紹 Leader 選舉之前,讓我們先來了解一下 ISR(in-sync replicas)列表。每個分區的 leader 會維護一個 ISR 列表,ISR 列表里面就是 follower 副本的 Borker 編號,只有跟得上 Leader 的 follower 副本才能加入到 ISR 里面,這個是通過 replica.lag.time.max.ms
參數配置的。只有 ISR 里的成員才有被選為 leader 的可能。
所以當 Leader 掛掉了,而且 unclean.leader.election.enable=false
的情況下,Kafka 會從 ISR 列表中選擇第一個 follower 作為新的 Leader,因為這個分區擁有最新的已經 committed 的消息。通過這個可以保證已經 committed 的消息的數據可靠性。
綜上所述,為了保證數據的可靠性,我們最少需要配置一下幾個參數:
- producer 級別:acks=all(或者 request.required.acks=-1),同時發生模式為同步 producer.type=sync
- topic 級別:設置 replication.factor>=3,并且 min.insync.replicas>=2;
- broker 級別:關閉不完全的 Leader 選舉,即 unclean.leader.election.enable=false;
數據一致性
這里介紹的數據一致性主要是說不論是老的 Leader 還是新選舉的 Leader,Consumer 都能讀到一樣的數據。那么 Kafka 是如何實現的呢?
如果想及時了解Spark、Hadoop或者HBase相關的文章,歡迎關注微信公眾號:iteblog_hadoop
假設分區的副本為3,其中副本0是 Leader,副本1和副本2是 follower,并且在 ISR 列表里面。雖然副本0已經寫入了 Message4,但是 Consumer 只能讀取到 Message2。因為所有的 ISR 都同步了 Message2,只有 High Water Mark 以上的消息才支持 Consumer 讀取,而 High Water Mark 取決于 ISR 列表里面偏移量最小的分區,對應于上圖的副本2,這個很類似于木桶原理。
這樣做的原因是還沒有被足夠多副本復制的消息被認為是“不安全”的,如果 Leader 發生崩潰,另一個副本成為新 Leader,那么這些消息很可能丟失了。如果我們允許消費者讀取這些消息,可能就會破壞一致性。試想,一個消費者從當前 Leader(副本0) 讀取并處理了 Message4,這個時候 Leader 掛掉了,選舉了副本1為新的 Leader,這時候另一個消費者再去從新的 Leader 讀取消息,發現這個消息其實并不存在,這就導致了數據不一致性問題。
當然,引入了 High Water Mark 機制,會導致 Broker 間的消息復制因為某些原因變慢,那么消息到達消費者的時間也會隨之變長(因為我們會先等待消息復制完畢)。延遲時間可以通過參數 replica.lag.time.max.ms
參數配置,它指定了副本在復制消息時可被允許的最大延遲時間。