Kafka 的副本復制機制

本文僅為筆者平日學習記錄之用,侵刪
原文:https://mp.weixin.qq.com/s/TUFNictt8XXLmmyWlfnj4g

讓分布式系統的操作變得簡單,在某種程度上是一種藝術,通常這種實現都是從大量的實踐中總結得到的。Apache Kafka 的受歡迎程度在很大程度上歸功于其設計和操作簡單性。隨著社區添加更多功能,開發者們會回過頭來重新思考簡化復雜行為的方法。

Apache Kafka 中一個更細微的功能是它的復制協議(replication protocol)。對于單個集群上不同大小的工作負載,調整 Kafka replication 以讓它適用不同情況在今天來看是有點棘手的。使這點特別困難的挑戰之一是如何防止副本從同步副本列表(也稱為ISR)加入和退出。從用戶的角度來看,這意味著如果生產者(producer )發送一批“足夠大”的消息,那么這可能會導致 Kafka brokers 發出多個警報。這些警報表明某些主題“未被復制”(under replicated),這意味著數據未被復制到足夠多的 brokers 上,從而增加數據丟失的可能性。因此,Kafka cluster 密切監控“未復制的”分區總數非常重要。在這篇文章中,我將討論導致這種行為的根本原因以及我們如何解決這個問題。

一分鐘了解 Kafka 復制機制

Kafka 主題中的每個分區都有一個預寫日志(write-ahead log),我們寫入 Kafka 的消息就存儲在這里面。這里面的每條消息都有一個唯一的偏移量,用于標識它在當前分區日志中的位置。如下圖所示:

Kafka 中的每個主題分區都被復制了 n 次,其中的 n 是主題的復制因子(replication factor)。這允許 Kafka 在集群服務器發生故障時自動切換到這些副本,以便在出現故障時消息仍然可用。Kafka 的復制是以分區為粒度的,分區的預寫日志被復制到 n 個服務器。在 n 個副本中,一個副本作為 leader,其他副本成為 followers。顧名思義,producer 只能往 leader 分區上寫數據(讀也只能從 leader 分區上進行),followers 只按順序從 leader 上復制日志。

日志復制算法(log replication algorithm)必須提供的基本保證是,如果它告訴客戶端消息已被提交,而當前 leader 出現故障,新選出的 leader 也必須具有該消息。在出現故障時,Kafka 會從掛掉 leader 的 ISR 里面選擇一個 follower 作為這個分區新的 leader ;換句話說,是因為這個 follower 是跟上 leader 寫進度的。

每個分區的 leader 會維護一個 in-sync replica(同步副本列表,又稱 ISR)。當 producer 往 broker 發送消息,消息先寫入到對應 leader 分區上,然后復制到這個分區的所有副本中。只有將消息成功復制到所有同步副本(ISR)后,這條消息才算被提交。由于消息復制延遲受到最慢同步副本的限制,因此快速檢測慢副本并將其從 ISR 中刪除非常重要。Kafka 復制協議的細節會有些細微差別,本博客并不打算對該主題進行詳盡的討論。感興趣的同學可以到這里詳細了解 Kafka 復制的工作原理。

副本在什么情況下才算跟上 leader

一個副本如果它沒有跟上 leader 的日志進度,那么它可能會被標記為不同步的副本。我通過一個例子來解釋跟上(caught up)的含義。假設我們有一個名為 foo 的主題,并且只有一個分區,同時復制因子為 3。假設此分區的副本分別在 brokers 1,2和3上,并且我們已經在主題 foo 上提交了3條消息。brokers 1上的副本是 leader,副本2和3是 followers,所有副本都是 ISR 的一部分。假設 replica.lag.max.messages 設置為4,這意味著只要 follower 落后 leader 的消息不超過3條,它就不會從 ISR 中刪除。我們把 replica.lag.time.max.ms 設置為500毫秒,這意味著只要 follower 每隔500毫秒或更早地向 leader 發送一個 fetch 請求,它們就不會被標記為死亡并且不會從 ISR 中刪除。

現在假設 producer 往 leader 上發送下一條消息,與此同時,broker 3 上發生了 GC 停頓,現在每個 broker 上的分區情況如下所示:

由于 broker 3 在 ISR中,因此在將 broker 3從 ISR 中移除或 broker 3 上的分區跟上 leader 的日志結束偏移之前,最新消息都是不認為被提交的。注意,由于 border 3 落后 leader 的消息比 replica.lag.max.messages = 4 要小,因此不符合從 ISR 中刪除的條件。這意味著 broker 3 上的分區需要從 leader 上同步 offset 為 3 的消息,如果它做到了,那么這個副本就是跟上 leader 的。假設 broker 3 在 100ms 內 GC 完成了,并且跟上了 leader 的日志結束偏移,那么最新的情況如下圖:

image

什么情況下會導致一個副本與 leader 失去同步

一個副本與 leader 失去同步的原因有很多,主要包括:

  • 慢副本(Slow replica):follower replica 在一段時間內一直無法趕上 leader 的寫進度。造成這種情況的最常見原因之一是 follower replica 上的 I/O瓶頸,導致它持久化日志的時間比它從 leader 消費消息的時間要長;

  • 卡住副本(Stuck replica):follower replica 在很長一段時間內停止從 leader 獲取消息。這可能是以為 GC 停頓,或者副本出現故障;

  • 剛啟動副本(Bootstrapping replica):當用戶給某個主題增加副本因子時,新的 follower replicas 是不同步的,直到它跟上 leader 的日志。

當副本落后于 leader 分區時,這個副本被認為是不同步或滯后的。在 Kafka 0.8.2 中,副本的滯后于 leader 是根據 replica.lag.max.messages 或 replica.lag.time.max.ms 來衡量的;前者用于檢測慢副本(Slow replica),而后者用于檢測卡住副本(Stuck replica)。

如何確認某個副本處于滯后狀態

通過 replica.lag.time.max.ms 來檢測卡住副本(Stuck replica)在所有情況下都能很好地工作。它跟蹤 follower 副本沒有向 leader 發送獲取請求的時間,通過這個可以推斷 follower 是否正常。另一方面,使用消息數量檢測不同步慢副本(Slow replica)的模型只有在為單個主題或具有同類流量模式的多個主題設置這些參數時才能很好地工作,但我們發現它不能擴展到生產集群中所有主題。在我之前的示例的基礎上,如果主題 foo 以 2 msg/sec 的速率寫入數據,其中 leader 收到的單個批次通常永遠不會超過3條消息,那么我們知道這個主題的 replica.lag.max.messages 參數可以設置為 4。為什么?因為我們以最大速度往 leader 寫數據并且在 follower 副本復制這些消息之前,follower 的日志落后于 leader 不超過3條消息。同時,如果主題 foo 的 follower 副本始終落后于 leader 超過3條消息,則我們希望 leader 刪除慢速 follower 副本以防止消息寫入延遲增加。

這本質上是 replica.lag.max.messages 的目標 - 能夠檢測始終與 leader 不同步的副本。假設現在這個主題的流量由于峰值而增加,生產者最終往 foo 發送了一批包含4條消息,等于 replica.lag.max.messages = 4 的配置值。此時,兩個 follower 副本將被視為與 leader 不同步,并被移除 ISR。

但是,由于兩個 follower 副本都處于活動狀態,因此它們將在下一個 fetch 請求中趕上 leader 的日志結束偏移量并被添加回 ISR。如果生產者繼續向 leader 發送大量的消息,則將重復上述相同的過程。這證明了 follower 副本進出 ISR 時觸發不必要的錯誤警報的情況。

replica.lag.max.messages 參數的核心問題是,用戶必須猜測如何配置這個值,因為我們不知道 Kafka 的傳入流量到底會到多少,特別是在網絡峰值的情況下。

一個參數搞定一切

我們意識到,檢測卡住或慢速副本真正重要的事情,是副本與 leader 不同步的時間。我們刪除了通過猜測來設置的 replica.lag.max.messages 參數。現在,我們只需要在服務器上配置 replica.lag.time.max.ms 參數即可;這個參數的含義為副本與 leader 不同步的時間。

檢測卡住副本(Stuck replica)的方式與以前相同 - 如果副本未能在 replica.lag.time.max.ms 時間內發送 fetch 請求,則會將其視為已死的副本并從 ISR 中刪除;

檢測慢副本的機制已經改變 - 如果副本落后于 leader 的時間超過 replica.lag.time.max.ms,則認為它太慢并且從 ISR 中刪除。

因此,即使在峰值流量下,生產者往 leader 發送大量的消息,除非副本始終和 leader 保持 replica.lag.time.max.ms 時間的落后,否則它不會隨機進出 ISR。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。