原文:https://blog.csdn.net/zwgdft/article/details/54633105
在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的分配,但是不容易控制,應盡量避免。
