目錄
一 体谒、常用消息中間件支持模型
二、消費端Push模型優(yōu)缺點
三臼婆、消費端Pull模型優(yōu)缺點
四抒痒、兩種模型在實際場景中的優(yōu)缺點分析
五、常見消息中間Push和Pull實現(xiàn)實戰(zhàn)和客戶端源碼分析
這篇文章前颁褂,先拋出個問題
- 1 所謂的推拉模型僅僅是指消費端模型嗎评汰?是否需要broker支持這種模型呢?
當然不是痢虹,如果沒有服務端Broker支持被去,它如何知道消費端當前選取的消息消費模型,準確講推拉模型是指消息的消費模型奖唯,需要服務端Broker支持惨缆,也需要消費端Client支持。(通常服務端Broker和消費端Client都是互相配合工作的丰捷,一起開發(fā)坯墨,一起作用的)。
?
?
一病往、常用消息中間件支持模型
中間件 | push模型 | pull模型 |
---|---|---|
RabbitMQ | 支持 | 支持 |
Kafka | --- | 支持(只有) |
RocketMQ | 支持 | 支持 |
二、兩種模型優(yōu)缺點對比
Push 和 Pull 的區(qū)別
- 所謂 Push 模型停巷,即當 Producer 發(fā)出的消息到達后耍攘,服務端馬上將這條消息投遞給 Consumer;
- 而 Pull 則是服務端收到這條消息后什么也不做畔勤,只是等著 Consumer 主動到自己這里來讀蕾各,即 Consumer 這里有一個“拉取”的動作。
舉個例子:
消息=食物庆揪;服務端Broker=老爸式曲;消費端 = 兒子
如果采用Push模型:
優(yōu)點:老爸一拿到食物就給兒子吃【食物送達及時】
缺點:兒子已經(jīng)吃跑了,老爸還強塞兒子吃缸榛,可能導致兒子被撐死【兒子不堪重負吝羞,撐死了】如果采用Pull模型
優(yōu)點:兒子餓了,主動找老爸要食物内颗,不餓的時候不要钧排。【兒子根據(jù)饑餓程度獲取食物】
缺點:兒子餓了起暮,發(fā)消息給老爸給我點食物卖氨,等老爸收到消息已經(jīng)過了十分鐘了,兒子等不及了餓死了负懦⊥厕啵【食物沒有及時送給兒子】
Push模型優(yōu)缺點
Push模型優(yōu)點
實時(因為服務端Broker一旦收到消息系吭,就會發(fā)送給消費者,不管消費這準備好沒有颗品,消費者是死是活肯尺,緩存到消費端的BlockingQueue中)Push缺點
[1]、消息保存在服務端broker躯枢,容易造成消息堆積则吟。(如何理解呢?為什么會存在服務端呢锄蹂? 因為服務端Broker在和消費端第一次建立通信時就明確了該消費者的消費喜好氓仲,他選擇的就是Push模型,那就不管三七二十一都發(fā)給你的緩存隊列中去)得糜。
[2]敬扛、服務端broker需要維護每次傳輸狀態(tài),遇到問題需要重試朝抖。(如何理解啥箭,為什么push模型broker要維護傳輸狀態(tài)?Pull模型不需要嗎治宣?)
[3]急侥、服務端broker需要依據(jù)訂閱者消費能力做流控(流轉(zhuǎn)機制)。(這個好理解侮邀,消費能力差時就不能瘋狂塞消息給消費端缆巧,流轉(zhuǎn)機制怎么做呢? RabbitMQ的做法是可以在消費者新建時,設置Qos豌拙,對服務端Borker提前表明消費端的消費能力陕悬,這樣服務端最多推送指定數(shù)量的消息給消費者。)
Pull模型優(yōu)缺點
Pull模型優(yōu)點
[1] 保存在消費端按傅,獲取消息方便捉超。(什么保存在消費端)
[2] 傳輸失敗,不需要重試唯绍。(如何理解拼岳?)
[3] 消費端可以根據(jù)自身消費能力決定是否pull(流轉(zhuǎn)機制) (這個好理解)Pull缺點
默認的短輪詢方式的實時性依賴于pull間隔時間,間隔越大况芒,實時性越低惜纸,長輪詢方式和push一致。(默認的端輪訓指的是什么? 指的當長時間沒有消息時耐版,消費端實現(xiàn)的間隔時間去服務端輪訓消息的過程)
三祠够、兩種模型在實際場景中的優(yōu)缺點分析
選擇 Push 還是 Pull
簡要分析下 Push 和 Pull模型,在不同場景下各自存在的利弊粪牲。
場景 1:Producer 的速率大于 Consumer 的速率
對于 Producer 速率大于 Consumer 速率的情況古瓤,有兩種可能性需要討論:
- 第一種是Producer 本身的效率就要比 Consumer 高(比如說,Consumer 端處理消息的業(yè)務邏輯可能很復雜腺阳,或者涉及到磁盤落君、網(wǎng)絡等 I/O操作);
- 另一種則是 Consumer 出現(xiàn)故障亭引,導致短時間內(nèi)無法消費或消費不暢绎速。
Push方式由于無法得知當前 Consumer 的狀態(tài)()焙蚓,所以只要有數(shù)據(jù)產(chǎn)生朝氓,便會不斷地進行推送,在以上兩種情況下時主届,可能會導致 Consumer 的負載進一步加重赵哲,甚至是崩潰(比如生產(chǎn)者是 flume 瘋狂抓日志,消費者是 HDFS+hadoop君丁,處理效率跟不上)枫夺。除非Consumer 有合適的反饋機制能夠讓服務端知道自己的狀況。(也可以 通過消費端限流方案绘闷,比如RabbitMQ消費者設置Qos橡庞,服務端Borker就會限制對消費端的發(fā)送流程,但是這個流量設置就需要衡量印蔗,不能太大也不能太邪亲睢)
而采取 Pull 的方式問題就簡單了許多,由于 Consumer 是主動到服務端拉取數(shù)據(jù)华嘹,此時只需要降低自己訪問頻率就好了吧趣。舉例:如前端是 flume 等日志收集業(yè)務,不斷往 CMQ 生產(chǎn)消息耙厚,CMQ 往后端投遞强挫,后端業(yè)務如數(shù)據(jù)分析等業(yè)務,效率可能低于生產(chǎn)者薛躬。
場景 2:強調(diào)消息的實時性
- 采用 Push 的方式時俯渤,一旦消息到達,服務端即可馬上將其推送給服務端型宝,這種方式的實時性顯然是非常好的八匠;
- 而采用 Pull 方式時絮爷,為了不給服務端造成壓力(尤其是當數(shù)據(jù)量不足時,不停的輪詢顯得毫無意義)梨树,需要控制好自己輪詢的間隔時間坑夯,但這必然會給實時性帶來一定的影響。(Pull不會頻繁拉取劝萤,設置一定間隔)渊涝。
場景 3:Pull 的長輪詢
Pull 模式有什么問題呢慎璧?由于主動權(quán)在消費方床嫌,消費方無法準確地決定何時去拉取最新的消息。如果一次 Pull 取到消息了還可以繼續(xù)去 Pull胸私,如果沒有 Pull 取到消息則需要等待一段時間再重新 Pull厌处。
但等待時間就很難判定了。你可能會說岁疼,我可以有xx 動態(tài)拉取時間調(diào)整算法阔涉,但問題的本質(zhì)在于慷暂,有沒有消息到來這件事情決定權(quán)不在消費方疼进。也許 1 分鐘內(nèi)連續(xù)來了 1000 條消息铅鲤,然后半個小時沒有新消息產(chǎn)生凤优,可能你的算法算出下次最有可能到來的時間點是31分鐘之后讹蘑,或者 60 分鐘之后遵岩,結(jié)果下條消息 10 分鐘后到了支竹,是不是很讓人沮喪牧挣?
當然也不是說延遲就沒有解決方案了字逗,業(yè)界較成熟的做法是從短時間開始(不會對 CMQ broker 有太大負擔)京郑,然后指數(shù)級增長等待。比如開始等 5ms葫掉,然后 10ms些举,然后 20ms,然后 40ms……直到有消息到來俭厚,然后再回到 5ms户魏。即使這樣,依然存在延遲問題:假設 40ms 到 80ms 之間的 50ms 消息到來挪挤,消息就延遲了 30ms绪抛,而且對于半個小時來一次的消息,這些開銷就是白白浪費的电禀。
總之就是消費端長時間沒有消息消費的話幢码,消費端輪訓時間間隔如果太長,可能在輪訓間隔中讓部分消息延時消費尖飞,如果輪訓時間太短症副,則頻繁的請求在消耗服務端Broker店雅,broker要應答消費端的請求(線程開銷等)而造成服務端Broker的負擔。
在騰訊云的 CMQ 里贞铣,有一種優(yōu)化的做法-長輪詢闹啦,來平衡 Pull/Push 模型各自的缺點。
基本方式是:消費者如果嘗試拉取失敗辕坝,不是直接 return窍奋,而是把連接掛在那里 wait,服務端如果有新的消息到來酱畅,把連接拉起琳袄,返回最新消息。
場景 4:部分或全部 Consumer 不在線
在消息系統(tǒng)中纺酸,Producer 和 Consumer 是完全解耦的窖逗,Producer 發(fā)送消息時,并不要求Consumer 一定要在線餐蔬,對于 Consumer 也是同樣的道理碎紊,這也是消息通信區(qū)別于 RPC 通信的主要特點;但是對于 Consumer不在線的情況樊诺,卻有很多值得討論的場景仗考。
首先,在 Consumer 偶然宕機或下線的情況下词爬,Producer 的生產(chǎn)是可以不受影響的秃嗜,Consumer 上線后,可以繼續(xù)之前的消費缸夹,此時消息數(shù)據(jù)不會丟失痪寻;但是如果 Consumer 長期宕機或是由于機器故障無法再次啟動,就會出現(xiàn)問題虽惭,即服務端需不需要為 Consumer 保留數(shù)據(jù)橡类,以及保留多久的數(shù)據(jù)等等。(消費端宕機芽唇,服務端Broker數(shù)據(jù)堆積)
采用 Push 方式時顾画,因為無法預知 Consumer 的宕機或下線是短暫的還是持久的,如果一直為該 Consumer 保留自宕機開始的所有歷史消息匆笤,那么即便其他所有的 Consumer 都已經(jīng)消費完成研侣,數(shù)據(jù)也無法清理掉,隨著時間的積累炮捧,隊列的長度會越來越大庶诡,此時無論消息是暫存于內(nèi)存還是持久化到磁盤上(采用 Push 模型的系統(tǒng),一般都是將消息隊列維護于內(nèi)存中咆课,以保證推送的性能和實時性末誓,這一點會在后邊詳細討論)扯俱,都將對 MQ 服務端造成巨大壓力,甚至可能影響到其他 Consumer 的正常消費喇澡,尤其當消息的生產(chǎn)速率非逞刚ぃ快時更是如此;但是如果不保留數(shù)據(jù)晴玖,那么等該 Consumer 再次起來時读存,則要面對丟失數(shù)據(jù)的問題。
折中的方案是:MQ 給數(shù)據(jù)設定一個超時時間呕屎,當 Consumer 宕機時間超過這個閾值時让簿,則清理數(shù)據(jù);但這個時間閾值也并太容易確定榨惰。
- 在采用 Pull 模型時拜英,情況會有所改善静汤;服務端不再關(guān)心 Consumer 的狀態(tài)琅催,而是采取“你來了我才服務”的方式,Consumer 是否能夠及時消費數(shù)據(jù)虫给,服務端不會做任何保證(也有超時清理時間)藤抡。
參考文章:
1 、選擇 Push 還是 Pull(騰訊消息中間件CMQ技術(shù)文檔)
2抹估、 RabbitMQ之Consumer消費模式(Push & Pull)(廝大文章缠黍,抓包說明Push模型不會等待消費者是否消費完成前一個消息,就會發(fā)送第二個消息)药蜻。