Kafka提升--可靠的數據傳遞

       對於系統來說,可靠的數據傳遞不能成爲馬後炮。與性能一樣,在系統的設計之初就應該 考慮可靠性問題,而不能在事後纔來考慮。而且,可靠性是系統的一個屬性,而不是一 個獨立的組件,所以在討論 Kafka 的可靠性保證時,還是要從系統的整體出發。說到可靠 性,那些與 Kafka 集成的系統與 Kafka 本身一樣重要。正因爲可靠性是系統層面的概念, 所以它不只是某個個體的事情。 Kafka 管理員、 Linux 系統管理員、網絡和存儲管理員以及 應用程序開發者,所有人必須協同作戰,才能構建一個可靠的系統。

       Kafka 在數據傳遞可靠性方面具備很大的靈活性。我們知道, Kafka 可以被用在很多場景 裏, 從跟蹤用戶點擊動作到處理信用卡支付操作。有些場景要求很高的可靠性,而有些則更看重速度和簡便性。 Kafka 被設計成高度可配置的,而且它的客戶端 API 可以滿足不同 程度的可靠性需求。

       不過,靈活性有時候也很容易讓人掉入陷阱。有時候,你的系統看起來是可靠的,但實際上有可能不是。下面先討論各種各樣的可靠性及其在 Kafka 場景中的含義。然後介紹 Kafka 的複製功能,以及它是如何提高系統可靠性的。隨後探討如何配置 Kafka 的 broker 和主題來滿足不同的使用場景需求,也會涉及生產者和消費者以及如何在各種可靠性場景裏使用它們。最後介紹如何驗證系統的可靠性,因爲系統的可靠性涉及方方面面——一些前提條件必須先得到滿足。

1、可靠性保證

       在討論可靠性時,我們一般會使用保證這個詞,它是指確保系統在各種不同的環境下能夠發生一致的行爲。 ACID 大概是大家最熟悉的一個例子,它是關係型數據庫普遍支持的標準可靠性保證。
       ACID 指的是原子性、 一致性、隔離性和持久性。如果一個供應商說他們的數據庫遵循 ACID 規範,其實就是在說他們的數據庫支持與事務相關的行爲。 

       有了這些保證,我們才能相信關係型數據庫的事務特性可以確保應用程序的安全。我們知道系統承諾可以做到些什麼,也知道在不同條件下它們會發生怎樣的行爲。我們瞭解這些保證機制,並基於這些保證機制開發安全的應用程序。
       所以,瞭解系統的保證機制對於構建可靠的應用程序來說至關重要,這也是能夠在不同條件下解釋系統行爲的前提。那麼 Kafka 可以在哪些方面作出保證呢?

•   Kafka 可以保證分區消息的順序。如果使用同一個生產者往同一個分區寫入消息,而且消息 B 在消息 A 之後寫入,那麼 Kafka 可以保證消息 B 的偏移量比消息 A 的偏移量大, 而且消費者會先讀取消息 A 再讀取消息 B。

•   只有當消息被寫入分區的所有同步副本時(但不一定要寫入磁盤),它才被認爲是“已提交”的。生產者可以選擇接收不同類型的確認,比如在消息被完全提交時的確認,或者在消息被寫入首領副本時的確認,或者在消息被髮送到網絡時的確認。

•   只要還有一個副本是活躍的,那麼已經提交的消息就不會丟失。

•   消費者只能讀取已經提交的悄息。 

這些基本的保證機制可以用來構建可靠的系統,但僅僅依賴它們是無法保證系統完全可靠的。構建一個可靠的系統需要作出一些權衡, Kafka 管理員和開發者可以在配置參數上作出權衡,從而得到他們想要達到的可靠性。這種權衡一般是指消息存儲的可靠性和一致性的重要程度與可用性、高吞吐量、低延遲和硬件成本的重要程度之間的權衡。下面將介紹 Kafka 的複製機制,並探討 Kafka 是如何實現可靠性的,最後介紹一些重要的配置參數。

2、複製

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

       Kafka 的複製機制主要內容: Kafka 的主題被分爲多個分區,分區是基本的數據塊。分區存儲在單個磁盤上, Kafka 可以保證分區裏的事件是有序的,分區可以在線(可用),也可以離線(不可用)。每個分區可以有多個副本,其中一個副本是首領。所有的事件都直接發送給首領副本,或者直接從首領副本讀取事件。其他副本只需要與首領保持同步,並及時複製最新的事件。當首領副本不可用時,其中一個同步副本將成爲新首領。 分區首領是同步副本,而對於跟隨者副本來說,它需要滿足以下條件才能被認爲是同步的。

•   與 Zookeeper 之間有一個活躍的會話,也就是說,它在過去的6s(可配置)內向 Zookeeper 發送過心跳。

•   在過去的 10s 內(可配置)從首領那裏獲取過消息。

•   在過去的 10s 內從首領那裏獲取過最新的消息。光從首領那裏獲取消息是不夠的,它還必須是幾乎零延遲的。

如果跟隨者副本不能滿足以上任何一點,比如與 Zookeeper 斷開連接,或者不再獲取新消息,或者獲取消息滯後了10s 以上,那麼它就被認爲是不同步的。 一個不同步的副本通過與 Zookeeper 重新建立連接,並從首領那裏獲取最新消息,可以重新變成同步的。這個過程在網絡出現臨時問題並很快得到修復的情況下會很快完成,但如果 broker 發生崩潰就需要較長的時間。 

非同步副本

       如果一個或多個副本在同步和非同步狀態之間快速切換,說明集羣內部出現了問題,通常是 Java 不恰當的垃圾回收配置導致的。不恰當的垃圾回收配置會造成幾秒鐘的停頓,從而讓 broker 與 Zookeeper 之間斷開連接,最後變成不同步的,進而發生狀態切換。

一個滯後的同步副本會導致生產者和消費者變慢,因爲在消息被認爲已提交之前,客戶端會等待所有同步副本接收消息。而如果一個副本不再同步了,我們就不再關心它是否已經收到消息。雖然非同步副本同樣滯後,但它並不會對性能產生任何影響。但是,更少的同步副本意味着更低的有效複製係數,在發生巖機時丟失數據的風險更大。 

3、broker配置

       broker 有 3 個配置參數會影響 Kafka 消息存儲的可靠性。與其他配置參數一樣,它們可以應用在 broker 級別,用於控制所有主題的行爲,也可以應用在主題級別,用於控制個別主題的行爲。

       在主題級別控制可靠性,意味着 Kafka 集羣可以同時擁有可靠的主題和非可靠的主題。例如,在銀行裏,管理員可能把整個集羣設置爲可靠的,但把其中的一個主題設置爲非可靠的,用於保存來自客戶的投訴,因爲這些消息是允許丟失的。 讓我們來逐個介紹這些配置參數,看看它們如何影響消息存儲的可靠性,以及 Kafka 在哪些方面作出了權衡。

3.1 複製係數

      主題級別的配置參數是 replicatlon.factor而在 broker 級別則可以通過 default.replication.factor 來配置自動創建的主題。我們假設主題的複製係數都是 3,也就是說每個分區總共會被 3 個不同的 broker 複製 3 次。這樣的假設是合理的,因爲 Kafka 的默認複製係數就是 3一一不過用戶可以修改它。即使是在主題創建之後,也可以通過新增或移除副本來改變複製係數。 如果複製係數爲 N,那麼在N-1個 broker 失效的情況下,仍然能夠從主題讀取數據或向主題寫入數據。所以,更高的複製係數會帶來更高的可用性、可靠性和更少的故障。另一方面,複製係數 N需要至少 N個 broker,而且會有 N個數據副本,也就是說它們會佔用 N倍的磁盤空間。我們一般會在可用性和存儲硬件之間作出權衡。 

       那麼該如何確定一個主題需要幾個副本呢?這要看主題的重要程度,以及你願意付出多少成本來換取可用性。有時候這與你的偏執程度也有點關係。 如果因 broker 重啓導致的主題不可用是可接受的(這在集羣裏是很正常的行爲),那麼把複製係數設爲 1就可以了。在作出這個權衡的時候,要確保這樣不會對你的組織和用戶造成影響,因爲你在節省了硬件成本的同時也降低了可用性。複製係數爲 2 意味着可以容忍 1 個 broker 發生失效,看起來已經足夠了。不過要記住,有時候 1 個 broker 發生失效會導致集羣不穩定(通常是舊版的 Kafka),迫使你重啓另一個 broker——集羣控制器。也就是說,如果將複製係數設爲 2,就有可能因爲重啓等問題導致集羣不可用。所以這是一個兩難的選擇。

       基於以上幾點原因,我們建議在要求可用性的場景裏把複製係數設爲 3。在大多數情況下, 這已經足夠安全了一一不過我們也見過有些銀行使用 5 個副本,以防不測。 副本的分佈也很重要。默認情況下, Kafka 會確保分區的每個副本被放在不同的 broker 上。 不過,有時候這樣仍然不夠安全。如果這些 broker 處於同一個機架上, 一旦機架的交換機發生故障,分區就會不可用,這時候把複製係數設爲多少都不管用。 爲了避免機架級別的故障,我們建議把 broker 分佈在多個不同的機架上,並使用 broker. rack 參數來爲每個 broker 配置所在機架的名字。如果配置了機架名字, Kafka 會保證分區的副本被分佈在多個機架上,從而獲得更高的可用性。

3.2 不完全的首領選舉

       unclean. leader .election 只能在 broker 級別(實際上是在集羣範圍內)進行配置, 它的默認值是 true。 我們之前提到過,當分區首領不可用時, 一個同步副本會被選爲新首領。 如果在選舉過程中沒有丟失數據,也就是說提交的數據同時存在於所有的同步副本上,那麼這個選舉就是 “完全”的。

但如果在首領不可用時其他副本都是不同步的,我們該怎麼辦呢?

這種情況會在以下兩種場景裏出現。
•   分區有 3 個副本,其中的兩個跟隨者副本不可用(比如有兩個 broker 發生崩潰)。這個時候,如果生產者繼續往首領寫入數據,所有消息都會得到確認並被提交(因爲此時首領是唯一的同步副本)。現在我們假設首領也不可用了(又一個 broker 發生崩潰),這個時候,如果之前的一個跟隨者重新啓動,它就成爲了分區的唯一不同步副本。

•   分區有 3 個副本,因爲網絡問題導致兩個跟隨者副本複製消息滯後,所以儘管它們還在複製消息,但已經不同步了。首領作爲唯一的同步副本繼續接收消息。這個時候,如果首領變爲不可用,另外兩個副本就再也無法變成同步的了。

對於這兩種場景,我們要作出一個兩難的選擇。

•   如果不同步的副本不能被提升爲新首領,那麼分區在舊首領(最後一個同步副本)恢復之前是不可用的。有時候這種狀態會持續數小時(比如更換內存芯片)。

•   如果不同步的副本可以被提升爲新首領,那麼在這個副本變爲不同步之後寫入舊首領的消息、會全部丟失,導致數據不一致。爲什麼會這樣呢?假設在副本 0 和副本1不可用時, 偏移量 100-200 的消息被寫入副本2 (首領)。現在副本 2 變爲不可用的,而副本 0 變 爲可用的。副本 0 只包含偏移量 0~100 的消息,不包含偏移量 100~200 的消息。如果我們允許副本0成爲新首領,生產者就可以繼續寫入數據,消費者可以繼續讀取數據。於是,新首領就有了偏移量 100~200 的新消息。這樣,部分消費者會讀取到偏移量 100~200 的舊消息,部分消費者會讀取到偏移量 100~200 的新消息,還有部分消費者讀取的是二者的混合。這樣會導致非常不好的結果,比如生成不準確的報表。另外,副本2可能會重新變爲可用,併成爲新首領的跟隨者。這個時候,它會把比當前首領舊的消息全部刪除, 而這些消息對於所有消費者來說都是不可用的。

簡而言之,如果我們允許不同步的副本成爲首領,那麼就要承擔丟失數據和出現數據不一致的風險。 如果不允許它們成爲首領,那麼就要接受較低的可用性,因爲我們必須等待原先的首領恢復到可用狀態。 如果把 unclean. leader .election .enable 設爲 true,就是允許不同步的副本成爲首領(也就是“不完全的選舉“),那麼我們將面臨丟失消息的風險。如果把這個參數設爲 false , 就要等待原先的首領重新上線,從而降低了可用性我們經常看到一些對數據質量和數據一致性要求較高的系統會禁用這種不完全的首領選舉(把這個參數設爲 false)。銀行系統是這方面最好的例子,大部分銀行系統寧願選擇在幾分鐘甚至幾個小時內不處理信用卡支付事務,也不會冒險處理錯誤的消息。不過在對可用性要求較高的系統裏,比如實時點擊流分析系統, 一般會啓用不完全的首領選舉。

3.3 最少同步副本

       在主題級別和 broker 級別上,這個參數都叫 min. insync. replicas

       我們知道,儘管爲一個主題配置了 3 個副本,還是會出現只有一個同步副本的情況。如果這個同步副本變爲不可用,我們必須在可用性和一致性之間作出選擇一一這是一個兩難的選擇。 根據 Kafka 對可靠性保證的定義,消息只有在被寫入到所有同步副本之後才被認爲是已提交的。但如果這裏的“所有副本”只包含一個同步副本,那麼在這個副本變爲不可用時,數據就會丟失。 如果要確保已提交的數據被寫入不止一個副本,就需要把最少同步副本數量設置爲大一點的值。對於一個包含 3 個副本的主題,如果 min. insync. replicas被設爲 2,那麼至少要存在兩個同步副本才能向分區寫入數據。 如果 3 個副本都是同步的,或者其中一個副本變爲不可用,都不會有什麼問題。不過,如果有兩個副本變爲不可用,那麼 broker 就會停止接受生產者的請求。嘗試發送數據的生產者會收到 NotEnoughReplicasException 異常。消費者仍然可以繼續讀取已有的數據。實際上,如果使用這樣的配置,那麼當只剩下一個同步副本時,它就變成只讀了,這是爲了避免在發生不完全選舉時數據的寫入和讀取出現非預期的行爲。爲了從只讀狀態中恢復,必須讓兩個不可用分區中的一個重新變爲可用的(比如重啓 broker),並等待它變爲同步的。

4、在可靠的系統裏使用生產者

      即使我們儘可能把 broker 配置得很可靠,但如果沒有對生產者進行可靠性方面的配置, 整個系統仍然有可能出現突發性的數據丟失。

請看以下兩個例子。

 •   爲broker 配置了 3 個副本,並且禁用了不完全首領選舉,這樣應該可以保證萬無一失。 我們把生產者發送消息的 acks 設爲 1 (只要首領接收到消息就可以認爲消息寫入成功)。生產者發送一個消息給首領,首領成功寫入,但跟隨者副本還沒有接收到這個消息。 首領向生產者發送了一個響應,告訴它“消息寫入成功”,然後它崩潰了,而此時消息還沒有被其他副本複製過去。 另外兩個副本此時仍然被認爲是同步的(畢竟判定一個副本不同步需要一小段時間),而且其中的一個副本成了新的首領。 因爲消息還沒有被寫入這個副本,所以就丟失了,但發送消息的客戶端卻認爲消息已成功寫入。 因爲消費者看不到丟失的消息,所以此時的系統仍然是一致的(因爲副本沒有收到這個消息,所以消息不算已提交),但從生產者角度來看,它丟失了一個消息。

•  爲 broker 配置了 3 個副本,並且禁用了不完全首領選舉。我們接受了之前的教訓, 把生產者的 acks 設爲 all。假設現在往 Kafka 發送消息,分區的首領剛好崩潰,新的首領正在選舉當中, Kafka 會向生產者返回“首領不可用”的響應。 在這個時候,如果生產者沒能正確處理這個錯誤,也沒有重試發送消息直到發送成功,那麼消息也有可能丟失。 這算不上是 broker 的可靠性問題,因爲 broker 並沒有收到這個消息。這也不是一致性問題,因爲消費者並沒有讀到這個消息。問題在於如果生產者沒能正確處理這些錯誤, 弄丟消息的是它們自己。

那麼,我們該如何避免這些悲劇性的後果呢?從上面兩個例子可以看出,每個使用 Kafka 的開發人員都要注意兩件事情

•   根據可靠性需求配置恰當的 acks 值。

•   在參數配置和代碼裏正確處理錯誤。 

下面介紹下生產者的幾種模式

4.1 發送確認

生產者可以選擇以下 3 種不同的確認模式。

•   acks=0 意味着如果生產者能夠通過網絡把消息發送出去,那麼就認爲消息已成功寫入 Kafka。在這種情況下還是有可能發生錯誤,比如發送的對象無能被序列化或者網卡發生故障,但如果是分區離線或整個集羣長時間不可用,那就不會收到任何錯誤。 即使是在發生完全首領選舉的情況下,這種模式仍然會丟失消息,因爲在新首領選舉過程中它並不知道首領已經不可用了。在 acks=0 模式下的運行速度是非常快的(這就是爲什麼很多基準測試都是基於這個模式),你可以得到驚人的吞吐量和帶寬利用率,不過如果選擇了這種模式, 一定會丟失一些消息。

•   acks=1 意味若首領在收到消息並把它寫入到分區數據文件(不一定同步到磁盤上)時會返回確認或錯誤響應。在這個模式下,如果發生正常的首領選舉,生產者會在選舉時收到一個 LeaderNotAvailableException 異常,如果生產者能恰當地處理這個錯誤,它會重試發送消息,最終消息會安全到達新的首領那裏。不過在這個模式下仍然有可能丟失數據,比如消息已經成功寫入首領,但在消息被複制到跟隨者副本之前首領發生崩潰。

•   acks=all 意味着首領在返回確認或錯誤響應之前,會等待所有同步副本都收到消息。如果和min. insync. replicas 參數結合起來,就可以決定在返回確認前至少有多少個副本能夠收到消息。 這是最保險的做法一一生產者會一直重試直到消息被成功提交。不過這也是最慢的做法,生產者在繼續發送其他消息之前需要等待所有副本都收到當前的消息。 可以通過使用異步模式和更大的批次來加快速度,但這樣做通常會降低吞吐量。

4.2 配置生產者的重試參數

       生產者需要處理的錯誤包括兩部分: 一部分是生產者可以自動處理的錯誤,還有一部分是需要開發者手動處理的錯誤。 如果 broker 返回的錯誤可以通過重試來解決,那麼生產者會自動處理這些錯誤。生產者向 broker 發送消息時, broker 可以返回一個成功響應碼或者一個錯誤響應碼。錯民響應碼可以分爲兩種, 一種是在重試之後可以解決的,還有一種是無法通過重試解決的。例如,如果 broker 返回的是 LEADER_NOT_AVAILABLE 錯誤,生產者可以嘗試重新發送消息。 也許在這個時候一個新的首領被選舉出來了,那麼這次發送就會成功。 也就是說, LEADER_NOT_AVAILABLE 是一個可重試錯誤。另一方面,如果 broker 返回的是 INVALID_CONFIG 錯誤,即使通過重試也無法改變配置選項,所以這樣的重試是沒有意義的。這種錯誤是不可重試錯誤。

       一般情況下,如果你的目標是不丟失任何消息,那麼最好讓生產者在遇到可重試錯誤時能夠保持重試。爲什麼要這樣?因爲像首領選舉或網絡連接這類問題都可以在幾秒鐘之內得到解決,如果讓生產者保持重試,你就不需要額外去處理這些問題了。經常會有人問 :“爲生產者配置多少重試次數比較好?”這個要看你在生產者放棄重試並拋出異常之後想做些什麼。 如果你想抓住異常並再多重試幾次,那麼就可以把重試次數設置得多一 點, 讓生產者繼續重試;如果你想直接丟棄消息,多次重試造成的延遲已經失去發送消息的意義;如果你想把消息保存到某個地方然後回過頭來再繼續處理,那就可以停止重試。 Kafka 的跨數據中心複製工具(MirrorMaker)默認會進行無限制的重試(例如 retries=MAX_INT)。作爲一個具有高可靠性的複製工具,它決不會丟失消息。

       要注意,重試發送一個已經失敗的消息會帶來一些風險,如果兩個消息都寫入成功,會導致消息重複。例如,生產者因爲網絡問題沒有收到 broker 的確認,但實際上消息已經寫入成功,生產者會認爲網絡出現了臨時故障,就重試發送該消息(因爲它不知道消息已經寫入成功)。在這種情況下, broker 會收到兩個相同的悄息。重試和恰當的錯誤處理可以保證每個消息“至少被保存一次”,但當前的 Kafka 版本(0.10.0)無法保證每個消息“只被保存一次”。現實中的很多應用程序在消息里加入唯一標識符,用於檢測重複消息,消費者在讀取消息時可以對它們進行清理。還要一些應用程序可以做到消息的“幕等”,也就是說,即使出現了重複消息,也不會對處理結果的正確性造成負面影響。例如,消息 “這個賬號裏有 110 美元”就是幕等的,因爲即使多次發送這樣的消息,產生的結果都是一樣的。不過消息“往這個賬號裏增加 10 美元”就不是冪等的。

4.3 額外的錯誤處理

使用生產者內置的重試機制可以在不造成消息丟失的情況下輕鬆地處理大部分錯誤,不過對於開發人員來說,仍然需要處理其他類型的錯誤,包括:
•   不可重試的 broker 錯誤,例如消息大小錯誤、認證錯誤等;

•   在消息發送之前發生的錯誤,例如序列化錯誤;

•   在生產者達到重試次數上限時或者在消息佔用的內存達到上限時發生的錯誤。

我們可以爲同步發送消息和異步發送消息編寫錯誤處理器。這些錯誤處理器的代碼邏輯與具體的應用程序及其目標有關。丟棄“不合法的消息”?把錯誤記錄下來?把這些消息保存在本地磁盤上?回調另一個應用程序?具體使用哪一種邏輯要根據具體的架構來決定。只要記住,如果錯誤處理只是爲了重試發送消息,那麼最好還是使用生產者內置的重試機制。

5、在可靠的系統裏使用消費者

       我們已經學習瞭如何在保證 Kafka 可靠性的前提下生產數據,現在來看看如何在同樣的前提下讀取數據。 在本文的開始部分可以看到,只有那些被提交到 Kafka 的數據,也就是那些已經被寫入所有同步副本的數據,對消費者是可用的,這意味着消費者得到的消息已經具備了一致性。 消費者唯一要做的是跟蹤哪些消息是已經讀取過的,哪些是還沒有讀取過的。這是在讀取消息時不丟失消息的關鍵。 在從分區讀取數據時,消費者會獲取一批事件,檢查這批事件裏最大的偏移量,然後從這個偏移量開始讀取另外一批事件。這樣可以保證消費者總能以正確的順序獲取新數據, 不會錯過任何事件。 如果一個消費者退出,另一個消費者需要知道從什麼地方開始繼續處理,它需要知道前一個消費者在退出前處理的最後一個偏移量是多少。所謂的“另一個”消費者,也可能就是它自己重啓之後重新回來工作。這也就是爲什麼消費者要“提交”它們的偏移量。它們把當前讀取的偏移量保存起來,在退出之後,同一個羣組裏的其他消費者就可以接手它們的工作。如果消費者提交了偏移量卻未能處理完消息,那麼就有可能造成消息丟失,這也是消費者丟失消息的主要原因。在這種情況下,如果其他消費者接手了工作,那些沒有被處理完的消息就會被忽略,永遠得不到處理。這就是爲什麼我們非常重視偏移量提交的時間點和提交的方式。 

已提交消息與已提交偏移量

要注意,此處的已提交消息與之前討論過的已提交消息是不一樣的,它是指已經被寫入所有同步副本並且對消費者可見的消息,而已提交偏移量是指消費者發送給Kafka的偏移量,用於確認它已經收到並處理好的消息位置。

5.1 消費者的可靠性配置

爲了保證消費者行爲的可靠性,需要注意以下 4 個非常重要的配置參數。

第 1 個是 group.id。如果兩個消費者具有相同的 group. id, 並且訂閱了同一個主題,那麼每個消費者會分到主題分區的一個子集, 也就是說它們只能讀到所有消息的一個子集(不過羣組會讀取主題所有的消息)。如果你希望消費者可以看到主題的所有消息,那麼需要爲它們設置唯一的group.id。

第 2 個是 auto.offset.reset。這個參數指定了在沒有偏移量可提交時(比如消費者第1 次啓動時)或者請求的偏移量在 broker 上不存在時,消費者會做些什麼。這個參數有兩種配置。 一種是 earliest,如果選擇了這種配置,消費者會 從分區的開始位置讀取數據,不管偏移量是否有效,這樣會導致消費者讀取大量的重複數據,但可以保證最少的數據丟失。 一種是 latest,如果選擇了這種配置, 消費者會從分區的末尾開始讀取數據,這樣可以減少重複處理消息,但很有可能會錯過一些消息。

第 3 個是 enable .auto.commit。這是一個非常重要的配置參數,你可以讓消費者基於任務調度自動提交偏移量,也可以在代碼裏手動提交偏移量。自動提交的一個最大好處是,在實現消費者邏輯時可以少考慮一些問題。如果你在消費者輪詢操作裏處理所有的數據,那麼自動提交可以保證只提交已經處理過的偏移量。自動提交的主要缺點是,無法控制重複處理消息(比如消費者在自動提交偏移量之前停止處理消息),而且如果把消息交給另外一個後臺線程去處理,自動提交機制可能會在消息還沒有處理完畢就提交偏移量。
第 4 個配置參數 auto.commit. lnterval.ms 與第 3 個參數有直接的聯繫。如果選擇了自動提交偏移量,可以通過該參數配置提交的頻度, 默認值是每 5 秒鐘提交一次。 一般來說,頻繁提交會增加額外的開銷,但也會降低重複處理消息的概率

5.2 顯示提交偏移量

       如果選擇了自動提交偏移量,就不需要關心顯式提交的問題。不過如果希望能夠更多地控制偏移量提交的時間點,那麼就要仔細想想該如何提交偏移量了一一要麼是爲了減少重複處理消息,要麼是因爲把消息處理邏輯放在了輪詢之外。

我們會着重說明幾個在開發具有可靠性的消費者應用程序時需要注意的事項。我們先從簡單的開始,再逐步深入。

1 . 總是在處理完事件後再提交偏移量

       如果所有的處理都是在輪詢裏完成,並且不需要在輪詢之間維護狀態(比如爲了實現聚合操作), 那麼可以使用自動提交,或者在輪詢結束時進行手動提交。

2. 提交頻度是性能和重複消息數量之間的權衡

       即使是在最簡單的場景裏,比如所有的處理都在輪詢裏完成,並且不需要在輪詢之間維護狀態,你仍然可以在一個循環裏多次提交偏移量(甚至可以在每處理完一個事件之後), 或者多個循環裏只提交一次(與生產者的 acks=all 配置有點類似),這完全取決於你在性能和重複處理消息之間作出的權衡。
3. 確保對提交的偏移量心裏有數

       在輪詢過程中提交偏移量有一個不好的地方,就是提交的偏移量有可能是讀取到的最新偏移量,而不是處理過的最新偏移量。要記住,在處理完消息後再提交偏移量是非常關鍵的否則會導致消費者錯過消息。

4. 再均衡

       在設計應用程序時要注意處理消費者的再均衡問題。 一般要在分區被撤銷之前提交偏移量,並在分配到新分區時清理之前的狀態。

5 .消費者可能需要重試

       有時候,在進行輪詢之後,有些消息不會被完全處理,你想稍後再來處理。例如,假設要把 Kafka 的數據寫到數據庫裏,不過那個時候數據庫不可用,於是你想稍後重試。要注意,你提交的是偏移量,而不是對消息的“確認”,這個與傳統的發佈和訂閱消息系統不太一樣。如果記錄#30 處理失敗,但記錄#31 處理成功,那麼你不應該提交 #31 , 否則會導致#31 以內的偏移量都被提交,包括#30在內,而這可能不是你想看到的結果。不過可以採用以下兩種模式來解決這個問題。

第一種模式,在遇到可重試錯誤時,提交最後一個處理成功的偏移量,然後把還沒有處理好的消息保存到緩衝區裏(這樣下一個輪詢就不會把它們覆蓋掉),調用消費者的 pause() 方法來確保其他的輪詢不會返回數據(不需要擔心在重試時緩衝區隘出),在保持輪詢的同時嘗試重新處理。如果重試成功,或者重試次數達到上限並決定放棄,那麼把錯誤記錄下來並丟棄消息,然後調用 resume()方能讓消費者繼續從輪詢裏獲取新數據。

第二種模式,在遇到可重試錯誤時,把錯誤寫入一個獨立的主題,然後繼續。 一個獨立的消費者羣組負責從該主題上讀取錯誤消息,並進行重試,或者使用其中的一個消費者同時從該主題上讀取錯誤消息並進行重試,不過在重試時需要暫停該主題。這種模式有點像其他消息系統裏的 dead-letter-queue。

6. 消費者可能需要維護狀態

       有時候你希望在多個輪詢之間維護狀態,例如,你想計算消息的移動平均數,希望在首次輪詢之後計算平均數,然後在後續的輪詢中更新這個結果。如果進程重啓,你不僅需要從上一個偏移量開始處理數據,還要恢復移動平均數。有一種辦法是在提交偏移量的同時把最近計算的平均數寫到一個“結果”主題上。消費者線程在重新啓動之後,它就可以拿到最近的平均數並接着計算。不過這並不能完全地解決問題,因爲 Kafka 並沒有提供事務支持。消費者有可能在寫入平均數之後來不及提交偏移量就崩潰了,或者反過來也一樣。這是一個很複雜的問題,你不應該嘗試自己去解決這個問題,建議嘗試一下 KafkaStreams 這 個類庫,它爲聚合、連接、時間窗和其他複雜的分析提供了高級的 DSL API。

7. 長時間處理

       有時候處理數據需要很長時間:你可能會從發生阻塞的外部系統獲取信息,或者把數據寫到外部系統,或者進行一個非常複雜的計算。要記住,暫停輪詢的時間不能超過幾秒鐘。即使不想獲取更多的數據,也要保持輪詢,這樣客戶端才能往 broker 發送心跳。在這種情況下, 一種常見的做法是使用一個線程池來處理數據,因爲使用多個線程可以進行並行處理,從而加快處理速度。在把數據移交給線程池去處理之後,你就可以暫停消費者,然後保持輪詢,但不獲取新數據,直到工作線程處理完成。在工作線程處理完成之後,可以讓消費者繼續獲取新數據。因爲消費者一直保持輪詢,心跳會正常發送,就不會發生再均衡。

8. 僅一次傳遞

       有些應用程序不僅僅需要“至少一次”(at-least-once)語義(意味着沒有數據丟失),還需要“僅一次”(exactly-once)語義。儘管 Kafka 現在還不能完全支持僅一次語義,消費者還是有一些辦法可以保證 Kafka 裏的每個消息只被寫到外部系統一次(但不會處理向 Kafka 寫入數據時可能出現的重複數據)。
       實現僅一次處理最簡單且最常用的辦能是把結果寫到一個支持唯一鍵的系統裏,比如鍵值存儲引擎、關係型數據庫、 ElasticSearch 或其他數據存儲引擎。在這種情況下,要麼消息本身包含一個唯一鍵 (通常都是這樣),要麼使用主題、分區和偏移量的組合來創建唯一 鍵一一它們的組合可以唯一標識一個 Kafka 記錄。如果你把消息和一個唯一鍵寫入系統, 然後碰巧又讀到一個相同的消息,只要把原先的鍵值覆蓋掉即可。數據存儲引擎會覆蓋已經存在的鍵值對,就像沒有出現過重複數據一樣。這個模式被叫作冪等性寫入,它是一種很常見也很有用的模式。
       如果寫入消息的系統支持事務, 那麼就可以使用另一種方法。最簡單的是使用關係型數據庫,不過 HDFS 裏有一些被重新定義過的原子操作也經常用來達到相同的目的。我們把消息和偏移量放在同一個事務裏,這樣它們就能保持同步。在消費者啓動時,它會獲取最近處理過的消息偏移量,然後調用 seek()方也從該偏移量位置繼續讀取數據。

6、驗證系統可靠性

       你經過了所有的流程,從確認可靠性需求,到配置 broker,再到配置客戶端,並小心謹慎地使用 API······現在可以把所有東西都放到生產環境裏去運行,然後高枕無憂,自信不會丟失任何消息了,對嗎?
       你當然可以這麼做,不過建議還是先對系統可靠性做一些驗證。我們建議做 3 個層面的驗證 一一配置驗證應用程序驗證以及生產環境的應用程序監控。讓我們來看看每一步都要做些什麼以及該怎麼做。

6.1 配置驗證

       從應用程序裏可以很容易對 broker 和客戶端配置進行驗證,我們之所以建議這麼做,有以下兩方面的原因。
•   驗證配置是否滿足你的需求。

•   幫助你理解系統的行爲,瞭解系統的真正行爲是什麼,瞭解你對 Kafka 基本準則的理解是否存在偏差,然後加以改進,同時瞭解這些準則是如何被應用到各種場景裏的。
       Kafka 提供了兩個重要的工具用於驗證配置: org.apache.kafka.tools 包裏的 VerifiableProducer和 VerifiableConsumer這兩個類。我們可以從命令行運行這兩個類,或者把它們嵌入到自動化測試框架裏。
       其思想是, VerifiableProducer 生成一系列消息,這些消息包含從 1 到你指定的某個數字。你可以使用與生產者相同的方式來配置 VerifiableProducer ,比如配置相同的 acks、 重試次數和消息生成速度。在運行 VerifiableProducer 時,它會把每個消息是否成功發送到 broker 的結果打印出來。 VerifiableProducer 執行的是另一個檢查一一它讀取事件(由 VerifiableProducer 生成)井按順序打印出這些事件。它也會打印出已提交的偏移量和再均衡的相關信息。
你可以考慮運行以下一些測試。
•   首領選舉:如果我停掉首領會發生什麼事情?生產者和消費者重新恢復正常狀態需要多長時間?

•   控制器選舉: 重啓控制器後系統需要多少時間來恢復狀態?

•   依次重啓:可以依次重啓 broker 而不丟失任何數據嗎?

•   不完全首領選舉測試:如果依次停止所有副本(確保每個副本都變爲不同步的),然後啓動一個不同步的 broker 會發生什麼?要怎樣恢復正常?這樣做是可接受的嗎?

       然後你從中選擇一個場景,啓動VerifiableProducer 和VerifiableConsumer 並開始測試這個場景,例如,停掉正在接收消息的分區首領。如果期望在一個短暫的暫停之後狀態恢復正常並且沒有任何數據丟失,那麼只要確保生產者生成的數據個數與消費者讀取的數據個數是匹配的就可以了。
       Kafka 的代碼庫裏包含了大量測試用例。它們大部分都遵循相同的準則一一使用 VerifiableProducer 和 VerifiableConsumer 來確保迭代的版本能夠正常工作。

6.2 應用程序驗證

       在確定 broker 和客戶端的配置可以滿足你的需求之後,接下來要驗證應用程序是否能夠保證達到你的期望。應用程序的驗證包括檢查自定義的錯誤處理代碼、偏移量提交的方式、 再均衡監聽器以及其他使用了 Kafka 客戶端的地方
因爲應用程序是你自己的,關於如何測試應用程序的邏輯,我們無法提供更多的指導, 但願你的開發流程裏已經包含了集成測試。不管如何驗證你的應用程序,我們都建議基於如下的故障條件做一些測試:

•   客戶端從服務器斷開連接(系統管理員可以幫忙模擬網絡故障);

•   首領選舉;

•   依次重啓 broker;

•   依次重啓消費者;

•   依次重啓生產者。

       你對每一個測試場景都會有期望的行爲 ,也就是在開發應用程序時所期望看到的行爲,然後運行測試看看真實的結果是否符合預期。例如,在測試“依次重啓消費者”這一場景時,你期望看到“在短暫的再均衡之後出現的重複消息個數不超過1000 個”。測試結果會告訴我們應用程序提交偏移量的方式和處理再均衡的方式是否與預期的一樣。

6.3 在生產環境監控可靠性

       測試應用程序是很重要的,不過它無法代替生產環境的持續監控,這些監控是爲了確保數據按照期望的方式流動。我們將會在後面會詳細介紹如何監控 Kafka 集羣,不過除了監控集羣的健康狀況之外,監控客戶端和數據流也是很重要的。

       首先, Kafka 的 Java 客戶端包含了 JMX 度量指標,這些指標可以用於監控客戶端的狀態和事件。 對於生產者來說,最重要的兩個可靠性指標是消息的error-rate 和 retry-rate (聚合過的)。如果這兩個指標上升,說明系統出現了問題。 除此以外,還要監控生產者日誌一一發送消息的錯誤日誌被設爲 WARN 級別,可以在“Got error produce response with correlation id 5689 on topic-partition [topic-1,3], retrying (two attempts left). Error: ... ”中找到它們。 如果你看到消息剩餘的重試次數爲 0, 說明生產者已經沒有多餘的重試機會。就像我們在前面所討論的那樣,你也許可以增加重試次數,或者把造成這個錯誤的問題先解決掉。

        對於消費者來說,最重要的指標是 consumer-lag,該指標表明瞭消費者的處理速度與最近提交到分區裏的偏移量之間還有多少差距。理想情況下,該指標總是爲 0,消費者總能讀到最新的消息。不過在實際當中,因爲 poll() 方法會返回很多消息,消費者在獲取更多數據之前需要花一些時間來處理它們,所以該指標會有些波動。關鍵是要確保消費者最終會趕上去,而不是越落越遠。因爲該指標會正常波動,所以在告警系統裏配置該指標有一定難度。 Burrow 是 Linkedln 公司開發的一個 consumer-lag 檢測工具,它可以讓這件事情變得容易一些。

       監控數據流是爲了確保所有生成的數據會被及時地讀取(你的需求決定了“及時”的具體含義)。 爲了確保數據能夠被及時讀取,你需要知道數據是什麼時候生成的。 0.10.0 版本 的 Kafka 在消息裏增加了時間戳,表明了消息的生成時間。如果你使用的是更早版本的客戶端,我們建議自己在消息里加入時間戳、應用程序的名字和機器名,這樣有助於將來診斷問題。

       爲了確保所有消息能夠在合理的時間內被讀取,應用程序需要記錄生成消息的數量(一般用每秒多少個消息來表示),而消費者需要記錄已讀取消息的數量(也用每秒多少個消息來表示) 以及消息生成時間(生成消息的時間)到當前時間(讀取消息的時間)之間的時間差。然後,你需要使用工具來比較生產者和消費者記錄的消息數量(爲了確保沒有丟失消息),確保這兩者之間的時間差不會超出我們允許的範圍。爲了做到更好的監控, 我們可以增加一個“監控消費者”,這個消費者訂閱一個特別的主題,它只進行消息的計數操作,並把數值與生成的消息、數量進行對比,這樣我們就可以在沒有消費者的情況下仍然能夠準確地監控生產者。這種端到端的監控系統實現起來很耗費時間, 具有一定挑戰性。據我們所知,目前還沒有開源的實現。 Confluent 提供了一個商業的實現版本,它是 Confluent Control Center 的一部分。

7、總結

       正如我們在開頭所說的,可靠性並不只是 Kafka 單方面的事情。我們應該從整個系統層面來考慮可靠性問題,包括應用程序的架構、生產者和消費者 API 的使用方式、生產者和消費者的配置、主題的配置以及 broker 的配置。系統的可靠性需要在許多方面作出權衡,比如複雜性、性能、可用性和磁盤空間的使用。 掌握 Kafka 的各種配置和常用模式, 對使用場景的需求做到心中有數,你就可以在應用程序和 Kafka 的可靠性程度以及各種權衡之間作出更好的選擇。
 

文章出處:《Kafka權威指南》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章