Kafka下的生產消費者模式與訂閱釋出模式
在RabbitMQ下的生產消費者模式與訂閱釋出模式一文中,筆者以“資料接入”和“事件分發”兩種場景為例,介紹瞭如何使用RabbitMQ來設計、實現生產消費者模式與訂閱釋出模式。生產消費者模式,指的是由生產者將資料來源源不斷推送到訊息中心,由不同的消費者從訊息中心取出資料做自己的處理,在同一類別下,所有消費者拿到的都是同樣的資料;訂閱釋出模式,本質上也是一種生產消費者模式,不同的是,由訂閱者首先向訊息中心指定自己對哪些資料感興趣,釋出者推送的資料經過訊息中心後,每個訂閱者拿到的僅僅是自己感興趣的一組資料。這兩種模式是使用訊息中介軟體時最常用的,用於功能解耦和分散式系統間的訊息通訊。
本文將繼續以“資料接入”和“事件分發”這兩個場景為例,來探討Kafka作為訊息系統的應用方法(High Level)。搞清楚Kafka的基本概念和應用方法是進行系統方案設計的前提,編寫程式碼只是具體落地實施,而解決bug和效能調優是系統跑起來之後的事情了。需要指出的是,本文重點是探討應用方法,具體應用時需要根據自身需求來做調整,沒有任何技術方案是萬能的。
為了方便閱讀,筆者首先重複一下這兩種場景:
資料接入
假設有一個使用者行為採集系統,負責從App端採集使用者點選行為資料。通常會將資料上報和資料處理分離開,即App端通過REST API上報資料,後端拿到資料後放入佇列中就立刻返回,而資料處理則另外使用Worker從佇列中取出資料來做,如下圖所示。
這樣做的好處有:第一,功能分離,上報的API介面不關心資料處理功能,只負責接入資料;第二,資料緩衝,資料上報的速率是不可控的,取決於使用者使用頻率,採用該模式可以一定程度地緩衝資料;第三,易於擴充套件,在資料量大時,通過增加資料處理Worker來擴充套件,提高處理速率。這便是典型的生產消費者模式,資料上報為生產者,資料處理為消費者。事件分發
假設有一個電商系統,那麼,使用者“收藏”、“下單”、“付款”等行為都是非常重要的事件,通常後端服務在完成相應的功能處理外,還需要在這些事件點上做很多其他處理動作,比如傳送簡訊通知、記錄使用者積分等等。我們可以將這些額外的處理動作放到每個模組中,但這並不是優雅的實現,不利於功能解耦和程式碼維護。
我們需要的是一個事件分發系統,在各個功能模組中將對應的事件釋出出來,由對其感興趣的處理者進行處理。這裡涉及兩個角色:A對B感興趣,A是處理者,B是事件,由事件處理器完成二者的繫結,並向訊息中心訂閱事件。服務模組是後端的業務邏輯服務,在不同的事件點發布事件,事件經過訊息中心分發給事件處理器對應的處理者。整個流程如下圖所示。這邊是典型的訂閱釋出模式
Kafka基本概念
Kafka是一個分散式流資料系統,使用Zookeeper進行叢集的管理。與其他訊息系統類似,整個系統由生產者、Broker Server和消費者三部分組成,生產者和消費者由開發人員編寫,通過API連線到Broker Server進行資料操作。我們重點關注三個概念:
- Topic,是Kafka下訊息的類別,類似於RabbitMQ中的Exchange的概念。這是邏輯上的概念,用來區分、隔離不同的訊息資料,遮蔽了底層複雜的儲存方式。對於大多數人來說,在開發的時候只需要關注資料寫入到了哪個topic、從哪個topic取出資料。
- Partition,是Kafka下資料儲存的基本單元,這個是物理上的概念。同一個topic的資料,會被分散的儲存到多個partition中,這些partition可以在同一臺機器上,也可以是在多臺機器上,比如下圖所示的topic就有4個partition,分散在兩臺機器上。這種方式在大多數分散式儲存中都可以見到,比如MongoDB、Elasticsearch的分片技術,其優勢在於:有利於水平擴充套件,避免單臺機器在磁碟空間和效能上的限制,同時可以通過複製來增加資料冗餘性,提高容災能力。為了做到均勻分佈,通常partition的數量通常是Broker Server數量的整數倍。
Consumer Group,同樣是邏輯上的概念,是Kafka實現單播和廣播兩種訊息模型的手段。同一個topic的資料,會廣播給不同的group;同一個group中的worker,只有一個worker能拿到這個資料。換句話說,對於同一個topic,每個group都可以拿到同樣的所有資料,但是資料進入group後只能被其中的一個worker消費。group內的worker可以使用多執行緒或多程序來實現,也可以將程序分散在多臺機器上,worker的數量通常不超過partition的數量,且二者最好保持整數倍關係,因為Kafka在設計時假定了一個partition只能被一個worker消費(同一group內)。
生產消費者模式
搞清楚了Kafka的基本概念後,我們來看如何設計生產消費者模式來實現上述的“資料接入”場景。在下圖中,由Producer負責接收前端上報的資料,投遞到對應的topic中(這裡忽略了Broker Server的細節),在Consumer端,所有對該資料感興趣的業務都可以建立自己的group來消費資料,至於group內部開多少個worke來消費完全取決於資料量和業務的實時性要求了。
訂閱釋出模式
再來看“事件分發”的場景,假如我們有“收藏”、“下單”、“付款”三個事件,業務一對“收藏”和“下單”事件感興趣,而業務二對“下單”和“付款”事件感興趣,那麼我們如何進行事件訂閱?不同於RabbitMQ中有資料路由機制(routing key),可以將感興趣的事件繫結到自己的Queue上,Kafka只提供了單播和廣播的訊息模型,無法直接進行消費物件的繫結,所以理論上Kafka是不適合做此種場景下的訂閱釋出模式的,如果一定要做,有這麼幾個方案:
方案一:繼續使用上述生產消費者的模式,在不同的group中過濾出自己感興趣的事件資料,然後進行處理。這種方式簡單有效,缺點就是每個group都會收到很多自己不感興趣的垃圾資料。
方案二:把每個事件的資料推送到不同的topic中,即以事件名稱來作為topic分類,在Consumer端,建立自己的group來消費自己感興趣的一組topic。這種方式適用於事件個數可以明確評估並且數量較少,如果事件種類很多,會導致topic的數量過多,建立過多的topic和partition則會影響到Kafka的效能,因為Kafka的每個Topic、每個分割槽都會對應一個物理檔案,當Topic數量增加時,訊息分散的落盤策略會導致磁碟IO競爭激烈成為瓶頸。
- 方案三:採用流處理方式對資料進行分類,即增加一箇中間資料流處理,將資料按照訂閱規則進行歸類,然後寫入不同的topic中,在Consumer端,每個group可以拿到僅僅是自己感興趣的資料。這種方式適用於資料量較大、但是Consumer端的消費group有限的情況,否則也會出現上述的topic碎片化的問題。
- 方案四:自己做partition的分配,但是不容易控制,應儘量避免。