[架構選型 】 全面瞭解Kafka和RabbitMQ選型

在這一部分中,我們將探討RabbitMQ和Apache Kafka以及它們的消息傳遞方法。每種技術在設計的每個方面都做出了截然不同的決定,每種方面都有優點和缺點。我們不會在這一部分得出任何有力的結論,而是將其視為技術的入門,以便我們可以深入探討該系列的後續部分。

RabbitMQ

RabbitMQ是一個分佈式消息隊列系統。分佈式,因為它通常作為節點集群運行,其中隊列分佈在節點上,並可選擇複製以實現容錯和高可用性。它原生地實現了AMQP 0.9.1,並通過插件提供其他協議,如STOMP,MQTT和HTTP。

RabbitMQ同時採用經典和新穎方式。從某種意義上來說,它是面向消息隊列的經典,並且具有高度靈活的路由功能。正是這種路由功能才是其殺手級功能。構建快速,可擴展,可靠的分佈式消息傳遞系統本身就是一項成就,但消息路由功能使其在眾多消息傳遞技術中脫穎而出。

交換機(exchanges)和隊列

超簡化概述:

  • 發佈者向交換機(exchanges)發送消息
  • 將消息路由到隊列和其他交換機(exchanges)
  • RabbitMQ在收到消息時向發佈者發送確認
  • 消費者與RabbitMQ保持持久的TCP連接,並聲明他們使用哪個隊列
  • RabbitMQ將消息推送給消費者
  • 消費者發送成功/失敗的確認
  • 成功使用後,消息將從隊列中刪除

隱藏在該列表中的是開發人員和管理員應該採取的大量決策,以獲得他們想要的交付保證,性能特徵等,我們將在本系列的後續部分中介紹所有這些決策。

我們來看看單個發佈者,交換機(exchanges),隊列和消費者:

[架構選型 】 全面瞭解Kafka和RabbitMQ選型

Fig 1 - Single publisher and single consumer

如果您有多個同一消息的發佈者怎麼辦? 如果我們有多個消費者每個人都希望消費每條消息呢?

[架構選型 】 全面瞭解Kafka和RabbitMQ選型

Fig 2 - Multiple publishers, multiple independent

如您所見,發佈者將其消息發送到同一個交換機(exchanges),該交換機(exchanges)將每條消息路由到三個隊列,每個隊列都有一個消費者。 使用RabbitMQ,隊列使不同的消費者能夠使用每條消息。 與下圖對比:

[架構選型 】 全面瞭解Kafka和RabbitMQ選型

Fig 3 - Multiple publishers, one queue with multip

在圖3中,我們有三個消費者都在單個隊列中消費。 這些是競爭的消費者,即他們競爭消費單個隊列的消息。 人們可以預期,平均而言,每個消費者將消耗該隊列消息的三分之一。 我們使用競爭消費者來擴展我們的消息處理,使用RabbitMQ它非常簡單,只需按需添加或刪除消費者。 無論您擁有多少競爭消費者,RabbitMQ都將確保消息僅傳遞給單個消費者。

我們可以將圖2和圖3組合在一起,使多組競爭消費者,每組消費每條消息。

[架構選型 】 全面瞭解Kafka和RabbitMQ選型

Fig 4 - Multiple publishers, multiple queues with

交換和隊列之間的箭頭稱為綁定,我們將仔細研究本系列第2部分中的箭頭。

擔保

RabbitMQ提供“最多一次交付”和“至少一次交付”但不提供“完全一次交付”保證。我們將在本系列的第4部分中深入研究消息傳遞保證。

消息按照到達隊列的順序傳遞(畢竟是隊列的定義)。當您擁有競爭消費者時,這並不能保證完成與完全相同順序的消息處理匹配。這不是RabbitMQ的錯,而是並行處理有序消息集的基本現實。通過使用Consistent Hashing Exchange可以解決此問題,您將在下一部分中看到模式和拓撲。

推和消費者預選

RabbitMQ將消息推送到流中的消費者。有一個Pull API,但它的性能很糟糕,因為每條消息需要一個請求/響應往返(注意,由於Shiva Kumar的評論,我更新了這一段)。

如果消息到達隊列的速度快於消費者可以處理的速度,那麼基於推送的系統可能會使消費者感到壓力。因此,為了避免這種情況,每個消費者都可以配置預取限制(也稱為QoS限制)。這基本上是消費者在任何時候都可以擁有的未確認消息的數量。當消費者開始落後時,這可以作為安全切斷開關。

為什麼推而不拉?首先,它對於低延遲非常有用。其次,理想情況下,當我們擁有單個隊列的競爭消費者時,我們希望在它們之間均勻分配負載。如果每個消費者都會收到消息,那麼根據他們拉動工作分佈的數量,可能會變得非常不平衡。消息分佈越不均勻,延遲越多,處理時消息順序的丟失越多。因此,RabbitMQ的Pull API只允許一次提取一條消息,但這會嚴重影響性能。這些因素使RabbitMQ傾向於推動機制。這是RabbitMQ的縮放限制之一。通過將確認組合在一起可以改善它。

路由

交換基本上是到隊列和/或其他交換的消息的路由器。為了使消息從交換機傳送到隊列或其他交換機,需要綁定。不同的交換需要不同的綁定。有四種類型的交換和相關綁定:

  • 扇出(Fanout)。路由到具有綁定到交換的所有隊列和交換。標準的pub子模型。
  • 直接。根據發佈者設置的消息隨附的路由密鑰路由消息。路由鍵是一個短字符串。直接交換將消息路由到具有與路由密鑰完全匹配的綁定密鑰的隊列/交換機。
  • 話題。根據路由密鑰路由消息,但允許通配符匹配。
  • 頭。 RabbitMQ允許將自定義標頭添加到消息中。標頭根據這些標頭值交換路由消息。每個綁定包括完全匹配標頭值。可以將多個值添加到具有匹配所需的ANY或ALL值的綁定。
  • 一致的哈希。這是一個哈希路由密鑰或郵件頭並僅路由到一個隊列的交換。當您需要使用擴展的消費者處理訂單保證時,這非常有用。
[架構選型 】 全面瞭解Kafka和RabbitMQ選型

Fig 5. Topic exchange example

我們將在第2部分中更仔細地研究路由,但上面是主題交換的示例。發佈者使用路由密鑰格式LEVEL.AppName發佈錯誤日誌。

  • 隊列1將使用多字#通配符接收所有消息。
  • 隊列2將接收ECommerce.WebUI應用程序的任何日誌級別。它使用覆蓋日誌級別的單字*通配符。
  • 隊列3將查看來自任何應用程序的所有ERROR級別消息。它使用多字#通配符來覆蓋所有應用程序。

通過四種路由消息的方式,以及允許交換路由到其他交換,RabbitMQ提供了一組功能強大且靈活的消息傳遞模式。接下來我們將討論死信交換,短暫交換和隊列,您將開始看到RabbitMQ的強大功能。

死信交換機(Dead Letter Exchanges)

我們可以配置隊列在以下條件下向交換機發送消息:

  • 隊列超過配置的消息數。
  • 隊列超出配置的字節數。
  • 消息生存時間(TTL)已過期。發佈者可以設置消息的生命週期,隊列也可以有消息TTL。哪個更短適用。

我們創建一個綁定到死信交換的隊列,這些消息將存儲在那裡直到採取行動。在另一篇文章中,我描述了我已經實現的拓撲,其中所有死信的消息都發送到中央清算所,支持團隊可以在此決定採取何種措施。

與許多RabbitMQ功能一樣,死信交換提供了最初未考慮的額外模式。我們可以使用消息TTL和死信交換來實現延遲隊列和重試隊列,包括指數退避。請參閱我之前的帖子。

短暫的交流和隊列(Ephemeral Exchanges and Queues)

可以動態創建交換和隊列,並賦予自動刪除特徵。經過一段時間後,他們可以自我毀滅。這允許諸如用於基於消息的RPC的ephermal回覆隊列之類的模式。

插件

您要安裝的第一個插件是Management Plug-In,它提供HTTP服務器,Web UI和REST API。它非常易於安裝,併為您提供易於使用的UI,以幫助您啟動和運行。通過REST API進行腳本部署也非常簡單。

其他一些插件包括:

  • 一致的哈希交換,Sharding Exchange等
  • 像STOMP和MQTT這樣的協議
  • 網絡鉤子
  • 額外的交換類型
  • SMTP集成

RabbitMQ還有很多東西,但這是一本很好的入門書,讓您瞭解RabbitMQ可以做些什麼。現在我們來看看Kafka,它採用了完全不同的消息傳遞方法,並且具有驚人的功能。

Apache Kafka

Kafka是一個分佈式複製的提交日誌。 Kafka沒有隊列的概念,因為它主要用作消息系統,所以最初可能看起來很奇怪。長期以來,隊列一直是消息傳遞系統的代名詞。讓我們分解一下“分佈式,複製的提交日誌”:

  • 分佈式,因為Kafka被部署為節點集群,用於容錯和擴展
  • 複製,因為消息通常跨多個節點(服務器)複製。
  • 提交日誌因為消息存儲在分區中,所以只追加稱為主題的日誌。這種日誌概念是Kafka的主要殺手特徵。

瞭解日誌(主題)及其分區是理解Kafka的關鍵。那麼分區日誌與一組隊列有什麼不同呢?讓我們想象一下吧。

[架構選型 】 全面瞭解Kafka和RabbitMQ選型

Fig 6 One producer, one partition, one consumer

Kafka不是將消息放入FIFO隊列並跟蹤像RabbitMQ那樣在隊列中跟蹤該消息的狀態,而是將其附加到日誌中,就是這樣。無論消耗一次還是一千次,該消息都會保留。它根據數據保留策略(通常是窗口時間段)刪除。那麼主題如何被消費?每個消費者跟蹤它在日誌中的位置,它有一個指向消耗的最後消息的指針,該指針稱為偏移量。消費者通過客戶端庫維護此偏移量,並且根據Kafka的版本,偏移量存儲在ZooKeeper或Kafka本身中。 ZooKeeper是一種分佈式共識技術,被許多分佈式系統用於領導者選舉等領域。 Kafka依靠ZooKeeper來管理集群的狀態。

這個日誌模型的驚人之處在於它立即消除了消息傳遞狀態的大量複雜性,更重要的是消費者,它允許它們倒回並返回並消耗先前偏移量的消息。例如,假設您部署了一個計算發票的服務,該發票消耗了客戶預訂。該服務有一個錯誤,並在24小時內錯誤地計算所有發票。最好使用RabbitMQ,您需要以某種方式重新發布這些預訂,並僅發送給發票服務。但是對於Kafka,您只需將該消費者的偏移量移回24小時。

因此,讓我們看一下具有單個分區和兩個消費者的主題的情況,每個消費者都需要消費每條消息。從現在開始,我已經開始為消費者貼上標籤,因為它不是那麼清晰(如RabbitMQ圖),它們是獨立的,也是競爭對手的消費者。

[架構選型 】 全面瞭解Kafka和RabbitMQ選型

Fig 7 One producer, one partition, two independent

從圖中可以看出,兩個獨立的消費者都使用相同的分區,但他們正在從不同的偏移中讀取。 也許發票服務處理消息所需的時間比推送通知服務要長,或者發票服務可能會停機一段時間並且趕上,或者可能存在錯誤並且其偏移量必須移回幾個小時。

現在讓我們說發票服務需要擴展到三個實例,因為它無法跟上消息速度。 使用RabbitMQ,我們只需部署兩個發票服務應用程序,這些應用程序將使用預訂發票服務隊列。 但是Kafka不支持單個分區上的競爭消費者,Kafka的並行單元就是分區本身。 因此,如果我們需要三個發票消費者,我們至少需要三個分區。 所以現在我們有:

[架構選型 】 全面瞭解Kafka和RabbitMQ選型

Fig 8 Three partitions and two sets of three consu

因此,這意味著您至少需要與最大規模的消費者一樣多的分區。我們來談談分區。

分區和消費者組

每個分區都是一個單獨的數據文件,可保證消息排序。這一點很重要:消息排序只能保證在一個分區內。這可能會在消息排序需求和性能需求之間引入一些緊張,因為並行單元也是分區。一個分區不能支持競爭消費者,因此我們的發票應用程序只能有一個實例消耗每個分區。

消息可以循環方式或通過散列函數路由到分區:散列(消息密鑰)%分區數。使用散列函數有一些好處,因為我們可以設計消息密鑰,使得同一實體的消息(例如預訂)始終轉到同一分區。這可以實現許多模式和消息排序保證。

消費者群體就像RabbitMQ的競爭消費者。組中的每個使用者都是同一應用程序的實例,並將處理主題中所有消息的子集。儘管RabbitMQ的競爭消費者都使用相同的隊列,但消費者群體中的每個消費者都使用同一主題的不同分區。因此,在上面的示例中,發票服務的三個實例都屬於同一個使用者組。

在這一點上,RabbitMQ看起來更加靈活,它保證了隊列中的消息順序,以及它應對不斷變化的競爭消費者數量的無縫能力。使用Kafka,如何對日誌進行分區非常重要。

Kafka從一開始就有一個微妙而重要的優勢,即RabbitMQ後來添加的關於消息順序和並行性的優點。 RabbitMQ維護整個隊列的全局順序,但在並行處理該隊列期間無法維護該順序。 Kafka無法提供該主題的全局排序,但它確實提供了分區級別的排序。因此,如果您只需要訂購相關消息,那麼Kafka提供有序消息傳遞和有序消息處理。想象一下,您有消息顯示客戶預訂的最新狀態,因此您希望始終按順序(按時間順序)處理該預訂的消息。如果您按預訂ID進行分區,那麼給定預訂的所有消息都將到達單個分區,我們會在其中進行消息排序。因此,您可以創建大量分區,使您的處理高度並行化,並獲得消息排序所需的保證。

RabbitMQ中也存在此功能,它通過Consistent Hashing交換機以相同的方式在隊列上分發消息。雖然Kafka強制執行此有序處理,因為每個使用者組只有一個使用者可以使用單個分區,並且當協調器節點為您完成所有工作以確保遵守此規則時,可以輕鬆實現。而在RabbitMQ中,您仍然可以讓競爭消費者從一個“分區”隊列中消費,並且您必須完成工作以確保不會發生這種情況。

這裡還有一個問題,當你改變分區數量時,訂單Id 1000的那些消息現在轉到另一個分區,因此訂單Id 1000的消息存在於兩個分區中。根據您處理郵件的方式,這會引起頭疼。現在存在消息不按順序處理的情況。

我們將在本系列的第4部分“消息傳遞語義和保證”部分中更詳細地介紹此主題。

PUSH VS PULL

RabbitMQ使用推送模型,並通過消費者配置的預取限制來防止壓倒性的消費者。這對於低延遲消息傳遞非常有用,並且適用於RabbitMQ基於隊列的架構。另一方面,Kafka使用拉模型,消費者從給定的偏移量請求批量消息。當沒有超出當前偏移量的消息時,為了避免緊密循環,Kafka允許進行長輪詢。

由於其分區,拉模型對Kafka有意義。由於Kafka在沒有競爭消費者的分區中保證消息順序,我們可以利用消息批處理來實現更高效的消息傳遞,從而為我們提供更高的吞吐量。這對RabbitMQ沒有多大意義,因為理想情況下我們希望儘可能快地分配一個消息,以確保工作均勻並行處理,並且消息處理接近它們到達隊列的順序。但是對於Kafka來說,分區是並行和消息排序的單位,所以這兩個因素都不是我們關注的問題。

發佈訂閱

Kafka支持基本的pub sub,其中包含一些與日誌相關的額外模式,它是一個日誌並具有分區。生成器將消息附加到日誌分區的末尾,並且消費者可以在分區中的任何位置放置它們的偏移量。

[架構選型 】 全面瞭解Kafka和RabbitMQ選型

Fig 9. Consumers with different offsets

當存在多個分區和使用者組時,這種風格的圖表不容易快速解釋,因此對於Kafka的其餘圖表,我將使用以下樣式:

[架構選型 】 全面瞭解Kafka和RabbitMQ選型

Fig 10. One producer, three partitions and one con

我們的消費者群體中沒有與分區相同數量的消費者:

[架構選型 】 全面瞭解Kafka和RabbitMQ選型

Fig 11. Sone consumers read from more than one par

一個消費者組中的消費者將協調分區的消耗,確保一個分區不被同一個消費者組的多個消費者使用。

同樣,如果我們擁有的消費者多於分區,那麼額外的消費者將保持閒置狀態。

[架構選型 】 全面瞭解Kafka和RabbitMQ選型

Fig 12. One idle consumer

添加和刪除消費者後,消費者群體可能會變得不平衡。 重新平衡會在分區中儘可能均勻地重新分配使用者。

[架構選型 】 全面瞭解Kafka和RabbitMQ選型

Fig 13. Addition of new consumers requires rebalan

在以下情況之後自動觸發重新平衡:

  • 消費者加入消費者群體
  • 消費者離開消費者群體(它關閉或被視為死亡)
  • 添加了新分區

重新平衡將導致短時間的額外延遲,同時消費者停止閱讀批量消息並分配到不同的分區。消費者維護的任何內存狀態現在都可能無效。 Kafka的消費模式之一是能夠將給定實體的所有消息(如給定的預訂)指向同一個分區,從而導致同一個消費者。這稱為數據局部性。在重新平衡任何內存中有關該數據的數據將是無用的,除非將消費者分配回同一分區。因此,維持國家的消費者需要在外部堅持下去。

日誌壓縮

標準數據保留策略是基於時間和空間的策略。存儲到最後一週的消息或最多50GB,例如。但是存在另一種類型的數據保留策略 - 日誌壓縮。壓縮日誌時,結果是僅保留每個消息密鑰的最新消息,其餘消息將被刪除。

讓我們假設我們收到一條消息,其中包含用戶預訂的當前狀態。每次更改預訂時,都會根據預訂的當前狀態生成新事件。該主題可能包含一些預訂的消息,這些消息表示自創建以來預訂的狀態。在主題被壓縮之後,將僅保留與該預訂相關的最新消息。

根據預訂量和每次預訂的大小,理論上可以將所有預訂永久存儲在主題中。通過定期壓縮主題,我們確保每個預訂只存儲一條消息。

日誌壓縮可以實現一些不同的模式,我們將在第3部分中探討。

有關消息排序的更多信息

我們已經討論過,RabbitMQ和Kafka都可以擴展和維護消息排序,但是Kafka使它變得容易多了。使用RabbitMQ,我們必須使用Consistent Hashing Exchange並使用像ZooKeeper或Consul這樣的分佈式共識服務自己手動實現使用者組邏輯。

但RabbitMQ有一個有趣的功能,卡夫卡沒有。 RabbitMQ本身並不特別,但任何基於發佈 - 訂閱隊列的消息傳遞系統。能力是這樣的:基於隊列的消息系統允許訂戶訂購任意事件組。

讓我們再深入瞭解一下。不同的應用程序無法共享隊列,因為它們會競爭使用消息。他們需要自己的隊列。這使應用程序可以自由地配置他們認為合適的隊列。他們可以將多個主題中的多個事件類型路由到其隊列中。這允許應用程序維護相關事件的順序。它想要組合的事件可以針對每個應用程序進行不同的配置。

使用像Kafka這樣的基於日誌的消息傳遞系統是不可能的,因為日誌是共享資源。多個應用程序從同一日誌中讀取。因此,將相關事件分組到單個主題中是在更廣泛的系統架構級別做出的決策。

所以這裡沒有勝利者。 RabbitMQ允許您維護任意事件集的相對排序,Kafka提供了一種維持大規模排序的簡單方法。

更新:我已經構建了一個名為Rebalanser的庫,它為RabbitMQ for .NET應用程序提供了使用者組邏輯。查看它上面的帖子和GitHub repo。如果人們表現出任何興趣,那麼我就會用其他語言製作版本。讓我知道。

結論

RabbitMQ由於其提供的各種功能,提供了瑞士軍刀的消息模式。憑藉其強大的路由功能,它可以消除消費者在只需要一個子集時檢索,反序列化和檢查每條消息的需要。它易於使用,通過簡單地添加和刪除消費者來完成擴展和縮小。它的插件架構允許它支持其他協議並添加新功能,例如Consistent散列交換,這是一個重要的補充。

卡夫卡的分佈式日誌與消費者抵消使得時間旅行成為可能。它能夠將相同密鑰的消息按順序路由到同一個消費者,從而實現高度並行化的有序處理。 Kafka的日誌壓縮和數據保留允許RabbitMQ無法提供的新模式。最後是的,Kafka可以比RabbitMQ進一步擴展,但是我們大多數人都處理一個可以輕鬆處理的消息量。

在下一部分中,我們將使用RabbitMQ仔細研究消息傳遞模式和拓撲。


分享到:


相關文章: