從消費者角度比較Kafka 與 RabbitMQ - OpenCredo

banq發表於2021-08-18

對於大型分散式系統,Kafka 往往是更好的選擇。它可以更有效地橫向擴充套件,為更大的場景實現更好的吞吐量,包括消費者離線和不可用時。RabbitMQ 非常適合具有較低延遲要求的系統,消費者可以跟上訊息的生產,但可能對並行吞吐量處理要求較低。 
RabbitMQ 和 Kafka 遵循非常不同的訊息傳遞方法。RabbitMQ 是更傳統的訊息代理系統,但它利用了 AMQP 交換提供的額外靈活性,這在某些運維上下文中是有益的。Kafka 是一個完全不同的系統,專門為解決分散式系統大規模處理海量資料(例如吞吐量、排序、高可用性等)的問題而設計。
訊息和事件驅動的系統為各種形式和規模的組織提供了一系列好處。它們的核心是幫助生產者和消費者解耦,這樣每個人都可以按照自己的節奏工作,而不必等待另一個——最好的非同步處理。
事實上,此類系統支援一系列訊息傳遞模式,圍繞客戶端的處理和消費選項提供不同級別的保證。以釋出/訂閱模式為例,它使一條訊息可以被多個消費者廣播和消費;或者競爭消費者模式,它使一條訊息能夠被處理一次,但多個併發消費者爭奪榮譽——本質上提供了一種分配負載的方法。然而,這些模式的實際實現方式在很大程度上取決於所使用的技術,因為每種模式都有自己的方法和獨特的權衡。 
在本文中,我們將探討這一切如何應用於 RabbitMQ 和 Apache Kafka,以及這兩種技術有何不同,特別是從訊息消費者的角度來看。
 

RabbitMQ 概述

RabbitMQ是一種流行的開源訊息代理,它透過將訊息推送給消費者來工作。消費者處理並確認每條訊息,然後從佇列中刪除該訊息。RabbitMQ 支援多種標準協議,包括AMQP(主要協議)、STOMP 和 MQTT。RabbitMQ 伺服器是用Erlang編寫的並依賴於Erlang,但是有多種語言的客戶端庫可以與之互動。
與其他傳統的排隊系統相比,RabbitMQ 有一個(好的)轉折點:交換(AMQP 協議的一部分)。使用 RabbitMQ,釋出者通常會發布到交換器,然後交換器負責將該訊息路由到零個或多個佇列,具體取決於實施的策略,例如直接扇出。不同的交換型別與其他後設資料(如路由鍵和標頭)相結合,為使用者提供了極大的靈活性。
RabbitMQ 中的佇列可能是持久的(即在代理失敗的情況下,尚未處理的訊息將在重新啟動時繼續存在),但訊息本身在消費時會被銷燬。代理負責跟蹤並確保它知道所有合適的客戶端何時處理了訊息,以及何時推送更多訊息。 
這種架構方法和實現對消費者有影響。例如,實現 pub/sub 模式需要明確的決策和預先構建,知道哪個合適的交換(在這種情況下使用扇出)和佇列作為構建塊。然而,實現競爭消費者模式並不一定需要您擔心使用哪種型別的交換,而只需擔心多個消費者可以連線到同一個佇列(在擴充套件和訊息傳遞排序方面有注意事項)。 
 

Kafka概覽

Apache Kafka不僅僅是一個訊息傳遞系統,它還是一個成熟的分散式事件流平臺(也處理訊息)。Kafka 使用自定義協議並部署為基於 JVM 的解決方案,該解決方案也依賴於 Apache ZooKeeper,儘管後者正在構建為依賴項。這已經可用於2.8.0 版的早期訪問預覽。Kafka 同樣擁有以大多數流行程式語言提供的客戶端整合庫。
Kafka 是基於拉取的,因為消費者有責任以他們認為合適的速度和速度拉取和消費訊息。在幕後 Kafka 使用分散式僅追加日誌,它在基本級別上與git相當。生產者將訊息附加到日誌中,就像將提交新增到 git 儲存庫一樣,不同的消費者可以按照自己的節奏處理這些訊息,類似於 git 分支指向不同提交的方式,並且可以在不影響歷史記錄的情況下自由移動。
事實上,在 Kafka 中,訊息是持久的,並且會根據保留設定在系統中保留一段時間。相同的訊息可以被不同的消費者多次消費,或者實際上被同一個消費者多次消費。 
Kafka 使用主題和分割槽的概念,其中分割槽是用於實現其令人印象深刻的可擴充套件性特性的主要機制。在基本層面上,可以將主題想象為一個佇列,其中包含感興趣的消費者訂閱的特定型別的訊息(在釋出/訂閱的意義上)。然而,主題被細化為分割槽(即分片),訊息根據一個鍵路由到不同的分割槽。因此,例如,相同國家/地區程式碼的訊息將始終在同一分割槽中結束。
訊息的這種永續性與 Kafka 基於分割槽的架構相結合,提供了一種自然的方式來實現釋出/訂閱模式,其中可以新增多個新消費者,而不必從消費者的角度預先重新構建任何東西。競爭的消費者模式同樣很容易適應,儘管它確實需要引入一個新的“消費者群體”概念。但除此之外,Kafka 還為消費者提供了重放訊息的能力,這進一步開闢了更多事件/訊息模式的可能性,例如Event Sourcing
 

吞吐量和排序
在 RabbitMQ 中,您可以為同一個佇列擁有多個並行消費者——但無需做任何特別的事情,這是以排序為代價的。這是因為並行消費者程式不能保證能夠從佇列中挑選訊息並按順序處理。雖然單個佇列中的訊息是有序的,但不幸的是,將所有內容限制在單個佇列中確實會削弱可擴充套件性,最終也會削弱整體訊息排序。
為了解決排序問題,RabbitMQ 從 3.8 版本開始引入了Single Active Consumer的概念。這與Kafka消費者群體非常相似.
使用此設定,雖然您可能有多個註冊的佇列消費者,但在任何給定時間只有一個消費者處於活動狀態,並且這是從佇列中消費的消費者。如果活動消費者被取消或死亡,它可以故障轉移到另一個註冊消費者。
因此,雖然您透過這種方式獲得了順序保證,但您仍然存在一次只有一個消費者的吞吐量問題。
要解決此問題,您需要將其與附加的Consistent Hash ExchangeSharded Plugins結合起來. 這些外掛為交換提供了一種將傳入訊息一致地拆分為多個佇列的方法。
這假設您的資料能夠以對有序處理有意義的方式進行分割槽或分片。例如,ID 為 1-100 的所有客戶都可能進入一個佇列,101-200 進入另一個佇列,等等。能夠對每個分割槽或共享佇列使用單個活動消費者,引入了以前不可用的並行級別,從而提高吞吐量,同時保留該特定佇列的有序處理。請注意,無法保證跨佇列的有序處理。    

另一方面,Kafka 透過利用其本機分割槽和消費者組功能來完成上述所有工作。這是從一開始就內建的,透過其內建的分割槽架構,可擴充套件性和吞吐量得到提高,同時仍然保持分割槽內的有序處理。與 RabbitMQ 附加元件一樣,不能保證跨分割槽排序。雖然 Kafka 允許一個分割槽有多個消費者,但這些消費者獨立處理訊息。為了確保消費者之間一致的有序處理,您需要將它們組織成一個邏輯消費者組. 這樣的一個組在第一個例項中提供了一個高度可用的消費者池,但是其架構使得在任何給定時間只有一個從分割槽中消費,儘管如果它死了另一個可以接管。如果您可以將主題拆分為適當的分割槽,消費者就可以同時使用該主題,而不會影響訊息排序。 
 

路由
路由是 RabbitMQ 的秘訣。使用不同型別的交換結合生產者傳送的特定資料(路由金鑰和/或標頭),訊息可以路由到不同的佇列或複製到多個佇列。該系統可以輕鬆配置為確保消費者只收到他們需要的訊息。這是非常有效的,並提供了一種方法來最小化消費者需要處理的時間、空間和資料量。 
另一方面,Kafka 沒有實現開箱即用的路由。這將過濾掉不需要的訊息的負擔交給了消費者自己,或者需要引入中間過程。在後一種情況下,這些中間過程通常需要過濾,然後將訊息子集轉發/複製到新主題,然後成為感興趣的消費者的新訂閱點。雖然在某些情況下,分割槽可用於模擬過濾形式(例如,按國家/地區程式碼對主題進行分割槽,以便每個國家/地區擁有不同的消費者),但分割槽的存在主要是為了實現擴充套件,因此它們不一定總是用於路由和過濾合身自然。如果沒有周密的計劃,這種方法在處理和儲存方面可能是浪費的。
 

消費者的高可用性
使用 RabbitMQ,同一佇列上的多個消費者提供更高的吞吐量和高可用性,但如前所述,競爭的消費者不尊重訊息排序。因此,平衡吞吐量、高可用性和排序需要評估和平衡權衡:

  • 如果排序很重要,您需要 在任何給定時間將自己限制為每個佇列的單個活動消費者。這犧牲了高可用性和吞吐量。
  • 您可以透過為該佇列保留一個消費者池來實現高可用性,同時透過某種形式的分散式鎖控制訪問(單個活動消費者可以做到這一點,包括在發生故障時故障轉移到不同的註冊消費者)。然而,這仍然犧牲了吞吐量。
  • 您可以透過Consistent Hash ExchangeSharded Plugins等附加元件獲得更高的吞吐量(同時保持高可用性和按佇列排序)。  

使用 Kafka,您同樣需要考慮如何使用分割槽架構和消費者組的組合來實現這一點,儘管這更自然地開箱即用。 
  • 如果排序很重要,您需要為單個分割槽(全域性主題級別排序很重要)構建架構,或者確保您擁有可以保留部分排序(在每個分割槽內)的分割槽。這將與消費者組的使用相結合,以將一個分割槽的處理一次限制為一個消費者。  
  • 使用消費者組時高可用性是開箱即用的,但必須注意確保組中的消費者數量與主題中的分割槽數量一致。 
  • 更高的吞吐量來自於並行處理分割槽的能力

 

延遲
RabbitMQ 旨在垂直擴充套件,最繁重的工作發生在記憶體中。您需要小心確保佇列積壓保持相當小(理想情況下為空),以便消費者可以跟上,並且代理不會不堪重負。眾所周知,具有不可預測消費模式的複雜消費者(例如,有些消費者正在等待其他下游流程)如果無法跟上,則在這種情況下會出現問題。
另一方面,Kafka 旨在橫向擴充套件。Kafka 以更低的延遲換取更好的耐用性和可用性;保留和儲存方法(見下一小節)意味著它可以處理更高的吞吐量需求,並且系統的穩定性不會受到臨時消費者中斷的威脅。
因此,對於較小的吞吐量場景,RabbitMQ 通常可以實現更好的端到端延遲,在這種情況下,消費者可以保證能夠跟上訊息的生產,但這是以較低的吞吐量、永續性和可用性組合為代價的。這是這個基準測試中的一個結論(但請注意,這是由 Confluent 自己釋出的)。
 

儲存
在 RabbitMQ 中,佇列是持久的,因為它們可以在代理中斷時倖存下來。另一方面,訊息保留是基於確認的,因為訊息一旦被確認就會從佇列中刪除。如果最佳化延遲,您自然會嘗試保持佇列小且移動,因此您的儲存將被最小化。也就是說,現在儲存非常便宜。  
與此同時,Kafka 使用基於策略的訊息保留。它可以配置為保留數天的訊息歷史記錄,允許在以後處理訊息——也許一些消費者離線,或者需要一些重新處理。但是,在這種情況下觀察消費者組滯後至關重要,因為如果消費者無法跟上超過保留期的積壓,訊息仍然可能丟失。然而,這對於 Kafka 來說往往不是問題,因為保留政策通常非常慷慨。並且如果您有良好的監控和警報(請參閱 James Bowkett 的Kafka、Devops 和 Resilience for all talk ),這應該能夠得到控制。
總體而言,消費者在 RabbitMQ 中可以一次性處理訊息,而 Kafka 消費者具有更大的靈活性,支援訊息重放和事件溯源等模式。

 

相關文章