讓分佈式系統的操做變得簡單,在某種程度上是一種藝術,一般這種實現都是從大量的實踐中總結獲得的。Apache Kafka 的受歡迎程度在很大程度上歸功於其設計和操做簡單性。隨着社區添加更多功能,開發者們會回過頭來從新思考簡化複雜行爲的方法。算法
Apache Kafka 中一個更細微的功能是它的複製協議(replication protocol)。對於單個集羣上不一樣大小的工做負載,調整 Kafka replication 以讓它適用不一樣狀況在今天來看是有點棘手的。使這點特別困難的挑戰之一是如何防止副本從同步副本列表(也稱爲ISR)加入和退出。從用戶的角度來看,這意味着若是生產者(producer )發送一批「足夠大」的消息,那麼這可能會致使 Kafka brokers 發出多個警報。這些警報代表某些主題「未被複制」(under replicated),這意味着數據未被複制到足夠多的 brokers 上,從而增長數據丟失的可能性。所以,Kafka cluster 密切監控「未複製的」分區總數很是重要。在這篇文章中,我將討論致使這種行爲的根本緣由以及咱們如何解決這個問題。服務器
一分鐘瞭解 Kafka 複製機制網絡
Kafka 主題中的每一個分區都有一個預寫日誌(write-ahead log),咱們寫入 Kafka 的消息就存儲在這裏面。這裏面的每條消息都有一個惟一的偏移量,用於標識它在當前分區日誌中的位置。以下圖所示:app
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 寫進度的。fetch
每一個分區的 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 中刪除。blog
如今假設 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 的日誌結束偏移,那麼最新的狀況以下圖:
一個副本與 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。