1. 程式人生 > >RabbitMQ VS Apache Kafka (二)

RabbitMQ VS Apache Kafka (二)

Kafka

Kafka is a distributed, replicated commit log. Kafka本身沒有佇列的概念,作為一個訊息中介軟體,乍看起來,這略顯奇怪,這可能與我們長期以來的形成的一個固化思維有關——但凡訊息系統,肯定離不開訊息佇列。讓我們重新回頭來看,如何理解distributed, replicated commit log

  • Distributed(分散式的):Kafka通常作為叢集節點部署以實現分割槽容錯和擴容
  • Replicated(可複製的):Kafka之間訊息通常跨服務節點進行復制
  • Commit Log(提交日誌):Kafka的訊息通常以追加日誌(又稱主題Topic)的方式分割槽儲存。在Kafka中,log(又稱Topic)的概念是其核心特徵。

要想正確理解Kafka,你必須首先要理解【日誌,log】(主題,Topic)及其分割槽(Partition)。為了更好的理解其與佇列的區別,我們看下面的圖: 在這裡插入圖片描述

上圖展示的是一個生產者,一個分割槽,一個消費者。

與RabbitMQ通過將訊息放入到FIFO的訊息佇列,並通過這個佇列來維護訊息狀態的處理模式不同,Kafka只是將訊息追加到日誌Log中,並不做清除操作,這也就是說,無論訊息讀取多少次(一次或者多次),訊息都會存在,至於何時會被清除,則依賴於資料保留策略(通常是一個windows時間週期)。那麼,訊息者該如何去讀取處理主題中的訊息,通常來講,每個消費者都會通過一個指標來記錄指向其最後讀取的訊息位置,這個指標我們稱之為偏移量。消費者依賴於客戶端函式庫來維護這個偏移量,基於不同的Kafka版本,這個變數可能會存於Zookeeper或者Kafka本身中,

Zookeeper是一種分散式的共識技術,常用於像我們常見的Leader選舉。Kafka通過Zookeeper來管理叢集狀態。

這種日誌模型的神奇之處在於它很好的消除了訊息傳遞狀態的複雜性,並且更為重要的是,其允許消費者對訊息進行回溯讀取處理。一個簡單的例子:假如有一個服務,其負責計算客戶預定的發票數量,假設服務本身存在BUG,當天資料計算錯誤,那麼如果你使用RabbitMQ,你需要怎麼處理?重新發起預定服務,然後重新計算髮票數量,而使用Kafka呢?你只需要將消費者的偏移量回溯一天即可。

下圖展示的是兩個完全獨立的消費者場景:

在這裡插入圖片描述

下面我們看下,Kafka下競爭消費的場景,還是上面的圖,假設隨著訊息量的增加,我們需要將發票計算服務擴容到三個例項,對於RabbitMQ來說,我們僅僅需要額外增加兩個發票計算服務即可,但對於Kafka來說,Kafka是不支援在單個分割槽上的競爭消費場景的,Kafka的併發機制是分割槽本身,如果我們需要三個不同的發票服務,那麼我們需要至少三個分割槽。

在這裡插入圖片描述

所以說,如果你需要增加擴容消費者,那麼你也同樣需要至少同樣數量的分割槽才可以滿足場景。下面我們深入討論下分割槽

分割槽與消費者組

每一個分割槽都是一個單獨的資料檔案,用以保證訊息順序,注意:要記住這一點,訊息順序只在單個分割槽中得到保證。實際運用中,你可能需要在訊息順序和效能需求之間做一個平衡,原因正是我們之前所提到的,Kafka的併發單元是分割槽。

至於訊息如何路由到分割槽?輪詢或者雜湊的方式(雜湊值 % 分割槽數)。使用雜湊演算法的優點是,我們可以通過一定的雜湊策略確保同樣實體(比如預定服務)的訊息被路由到同一分割槽。

消費者組就類似於RabbitMQ中的競爭消費者。組中的每一個消費者都是一個消費例項,負責處理同一主題下的訊息子集。RabbitMQ中的競爭消費者是讀取處理同一訊息佇列中的訊息,而消費者組中的消費者則是讀取處理不同分割槽中同一主題中的訊息。因此,上圖中的示例中,發票服務消費者屬於同一消費者組。

在這一點上,RabbitMQ通過佇列的方式保證了訊息的有序性,更顯靈活,並且可輕鬆靈活的調整競爭消費者數量。而對於Kafka,你則需要很好的劃分你的Logs以滿足實際的業務應用。

說到訊息順序和併發,Kafka還具備一個功能優勢(RabbitMQ後續增加了類似功能)。RabbitMQ通過訊息佇列的形式維護了訊息的全域性有序性,卻無法保證併發場景下訊息處理的有序性。Kafka不保證訊息的全域性有序,但卻保證分割槽層級的訊息順序。所以,如果僅僅需要保證特定系列訊息的有序性,Kafka提供了訊息的順序傳遞和順序處理。假設這樣的業務場景,用一系列訊息來表示一個客戶端預定資訊的最新狀態,那麼很自然的你需要順序處理這些訊息。因此,我們可以按預定ID來進行分割槽,對於某個特定的預定記錄,其所有的狀態訊息都會發送到同一個分割槽中,在這個分割槽中,Kafka就可以保證訊息的順序處理。所以,我們可以通過建立多個分割槽來提高併發,同時保證訊息的順序處理(RabbitMQ也通過一致性雜湊交換提供了類似的功能)。注意,這裡也會有一個小問題,假設原有999個分割槽,如果你增加了一個分割槽到1000個,那麼對於原有ID為1000的預定訊息,原來可能是路由到分割槽1,增加分割槽之後,可能會被路由到分割槽1000,這種情況下,就可能導致訊息處理非有序性問題的發生,關於這一點,我們後續章節討論。

訊息推送與被動拉取

RabbitMQ採用的是訊息推送模型,並通過配置消費者預取閾值避免消費者超負荷執行。推送模型可以做到非常低的訊息延遲並且非常適用於佇列架構的訊息系統。而Kafka使用的則是另一種訊息拉取的模式,消費者會從給定的訊息偏移兩種批量拉取訊息,為避免空取操作,Kafka支援長輪詢操作。

對於分割槽架構的Kafka來說,資訊拉取的模式是有意義的,Kafka確保在單個分割槽中無競爭消費的情況下訊息的有序性,因此我們可以通過批處理實現訊息的高效訊息路由與處理。對於RabbitMQ來說,如果想要實現高效的併發訊息處理,只能一次一條儘可能的提高訊息傳送的速度。對於Kafka來說,分割槽是併發和訊息有序性的基本單元,因此,對於Kafka來說,可以很輕鬆的實現在保證訊息有序性的前提下,高效地實現訊息併發處理。

釋出訂閱

Kafka支援基本的釋出訂閱模式,生產者將訊息追加到分割槽末尾,消費者通過在分割槽中的不同偏移量來實現定位。

在這裡插入圖片描述

為了更好的便於使用者理解多分割槽和消費者組的場景,我們採用下面的這種方式來表示。

在這裡插入圖片描述

一個消費者可以從多個分割槽中讀取訊息。 在這裡插入圖片描述

同樣,當消費者多於分割槽數目時,多出來的消費者就會處於閒置狀態。 在這裡插入圖片描述

當我們往消費者組中增加或者刪除消費者時,消費者組可能會變得不平衡,這時,我們就需要重新調整以儘可能的實現消費者之間的平衡,即再平衡操作在這裡插入圖片描述

注意,再平衡操作會被自動觸發,當:

  • 新的消費者加入
  • 已有的消費者離開(服務關閉或者停止)
  • 新增分割槽

再平衡操作會導致一定的訊息延遲,這是因為消費者會暫停訊息批處理,然後重新分配到其他分割槽上。我們之前有談到,Kafka可以實現將同一個實體的所有訊息路由到同一個分割槽上,由同一個消費者進行處理,我們稱之為資料區域性性。當再平衡發生時,任何由消費者維護在記憶體中的狀態都會失效,除非消費者還是被分配到同一分割槽,因此,為了克服再平衡時訊息狀態失效的問題,消費者需要通過外部持久化操作來維護這些訊息狀態。

日誌壓縮

總所周知,標準的資料保留策略是基於時間和空間兩要素。除此之外,我們這裡介紹第三種策略-日誌壓縮,所謂的日誌壓縮就是基於訊息鍵值始終保留最新的訊息,其餘的則被刪除。考慮一個業務場景,假設用一個訊息來表示使用者預定的狀態資訊,當每一次預定動作的改變都會產生一個新的訊息來表示預定的最新狀態,因此一個主題下可能會包含多個訊息列表,當我們執行日誌壓縮動作之後,只會保留最近的狀態訊息,其餘的則被全部刪除。

原文連結