Pop模式消費和消息粒度負載均衡 在RocketMQ 5.0之前,消費有兩種方式可以從Broker獲取消息,分別為Pull模式和Push模式。 Pull模式:消費需要不斷的從阻塞隊列中獲取數據,如果沒有數據就等待,這個阻塞隊列中的數據由消息拉取線程從Broker拉取消息之後加入的,所以Pull模式下 ...
Pop模式消費和消息粒度負載均衡
在RocketMQ 5.0之前,消費有兩種方式可以從Broker獲取消息,分別為Pull模式和Push模式。
- Pull模式:消費需要不斷的從阻塞隊列中獲取數據,如果沒有數據就等待,這個阻塞隊列中的數據由消息拉取線程從Broker拉取消息之後加入的,所以Pull模式下消費需要不斷主動從Broker拉取消息。
- Push模式:需要註冊消息監聽器,當有消息到達時會通過回調函數進行消息消費,從錶面上看就像是Broker主動推送給消費者一樣,所以叫做推模式,底層依舊是消費者從Broker拉取數據然後觸發回調函數進行消息消費,只不過不需要像Pull模式一樣不斷判斷是否有消息到來。
註:圖片來自RocketMQ官方文檔
不過不管是Pull模式還是Push模式,在集群模式下,一個消息隊列只能分配給同一個消費組內的某一個消費者進行消費,所以需要進行Rebalance負載均衡為每個消費者分配消息隊列之後才可以進行消息消費。
Rebalance的工作是在每個消費者端進行的,消費端負責的工作太多,除了負載均衡還有消費位點管理等功能,如果新增一種語言的支持,就需要重新實現一遍對應的業務邏輯代碼。
除此以外,在RocketMQ 5.0以前負載均衡是以消息隊列為維度為每個消費者分配的,一個消息隊列只能分給組內一個消費者消費,所以會存在以下問題:
(1)隊列只能分給組內一個消費者消費,也就無法通過擴展消費者的數量來提升消費能力;
(2)消息隊列數量與消費者數量比例不均衡時,可能會導致某些消費者沒有消息隊列可以分配或者某些消費者承擔過多的消息隊列,分配不均勻;
(3)如果某個消費者hang主,會導致分配到該消費者的消息隊列中的消息無法消費,導致消息積壓;
在RocketMQ 5.0增加了Pop模式消費,將負載均衡、消費位點管理等功能放到了Broker端,減少客戶端的負擔,使其變得輕量級,並且5.0之後支持消息粒度的負載均衡。
消息粒度負載均衡
對於PushConsumer和SimpleConsumer類型的消費者,預設且僅使用消息粒度負載均衡策略。
註:圖片來自RocketMQ官方文檔
消息粒度負載均衡策略中,同一消費組內的多個消費者將按照消息粒度平均分攤主題中的所有消息,即同一個隊列中的消息,可被平均分配給組內多個消費者共同消費。
消息粒度負載均衡策略保證同一個隊列的消息可以被組內多個消費者共同處理,但是該策略使用的消息分配演算法結果是隨機的,不能指定消息被哪一個特定的消費者處理。當消費者獲取到某條消息後,服務端會對該消息加鎖,保證該消息對其他消費者不可見,直到消息消費成功或者超時,所以多個消費者同時消費同一個消息隊列中的消息,服務端也可以保證消息不會被多個消費者重覆消費。
消息粒度負載均衡策略適用於絕大多數線上處理的業務場景。
Pop消息消費
首先客戶端(消費者)向服務端(Broker)發送Pop請求,Broker端收到請求後以Pop模式獲取消息,之後返回給客戶端,客戶端消費消息成功之後,向Broker發送ACK請求確認消息消費成功。
當POP出一條消息之後,這條消息就會在一段時間內不可見,在這個時間段內,這條消息不會再被POP出來,如果在這個期間未能收到該消息的ACK請求,過了這個不可見的時間之後,消息就會恢復可見狀態,重新被消費。
POP的消費位點由Broker保存和控制,並且POP模式可以使多個消費者端消費同一個消息隊列中的消息,消費者端不再需要在本地做負載均衡分配消息隊列,只需要調用服務端提供的POP介面獲取消息進行消費即可,即便某個消費者hang住,其他消費者依舊可以繼續消費隊列中的數據,不會造成消息堆積。
POP消息在Broker端的實現
-
Broker端在處理POP請求時,先在隊列維度加鎖,保證同一時間只有一個消費者可以從該隊列中獲取消息;
-
Broker端會從隊列中獲取一批消息,並構建這批消息對應的CheckPoint信息保存在Broker中,之後會與ACK的消息進行匹配;
CheckPoint主要包括消息的 Topic,ConsumerGroup,QueueId,offset,POPTime,msgCout,reviveQueueId等信息。 -
CheckPoint會優先保存在記憶體中,如果在一段時間內收到了客戶端的ACK消息,就會將對應的CheckPoint清除,並更新消費進度;
-
對於一段時間內為收到ACK消息的CheckPoint,會將其從記憶體中刪除,然後發送到延時主題
SCHEDULE_TOPIC_XXXX
中,到達延時時間之後,消息會再被轉發到REVIVE_TOPIC(會使用REVIVE_LOG_ + 集群名稱
作為主題)中,有一個線程去處理REVIVE_TOPIC中的數據,將裡面的消息拉取放入到一個
MAP中,如果後續收到對應的ACK消息,則會更新REVIVE_TOPIC主題中的消費位點標識消息消費完成,如果過了一定時間依舊未收到對應的ACK消息,會查找這個CheckPoint對應的真實消息,將其放入到重試隊列中,等待客戶端消費,所以消費者消費的時候有一定概率可以消費到重試隊列中的消息。
由於一個消息隊列中的消息可以被多個消費者消費,如果某個消費者在消費某條消息之後一直未發生ACK消息,那麼Broker是如何管理消費進度的,比如隊列1中有1、2、3、4、5條消息,此時有三個消費者1、2、3,分別分配到了隊列中的1、2、3條消息,此時消費者1已經對消息1ACK完畢,消費者3也對消息3ACK完畢,消費者2一直未ACK消息2,那麼Broker如何設置消費進度?
個人認為,在一段時間內消息2對應的CheckPoint未匹配到對應的ACK消息,為了保證消費可以繼續向後消費消息,應該會推進消費進度跳過這個消息,對於消息2,會按照超時處理邏輯,將其對應的CheckPoint先放入延時隊列,再放入REVIVE_TOPIC中,之後等待ACK,如果之後一直還未收到ACK再將其放入重試隊列,等待重新消費。
參考
RocketMQ官方文檔