消息隊列つ登場
大家好,我是老三,是一個電商公司的程序員,負責訂單系統的開發。
掉了不少頭發之后,我完成了用戶訂單支付的開發。
訂單支付的業務是這樣的。用戶支付完成之后,我需要更新訂單狀態,這一部分是在本系統完成的。接下來,我要調用庫存系統,減庫存,好了,剩下的就是庫存系統的事情了。
開發、聯調、測試、上線,我的小日子變得清閑起來,每天就是在群里吹牛打屁。
可是沒過兩天,產品妹子,找過來了,她說,她想加個功能,用戶完成訂單支付以后,要增加用戶的積分。
沒問題,so easy,噼里啪啦,我兩天就做完了,無非是調用一下會員系統。
這天,正和沙雕群友斗圖的時候,產品妹子過來,他說要接入消息系統,好,搞!
又過兩天,她說要添加營銷系統,行吧,干!
又過兩天,她說要搞推薦系統,嗯……,來吧!
又過兩天……
於是系統就變成了這個樣子:
就這樣,我過上了暗無天日的日子,我要維護和若干個系統的對接,每次他們發布新版本,我都要跟着值班。
我要迭代,也要改和幾個系統的對接代碼。
周一,營銷系統;
周二,庫存系統;
……
這天,眼圈發黑的我正在和下游服務撕巴的時候,突然忍不住兩腿戰戰,她來了,產品妹(女)子(王)來了——她是我不能拒絕的女人。
脆弱的眼淚流了滿面,我的猿生一片灰暗……
沒想到,代救星出現了,我的好朋友傲天過來了,拿鼻孔看着我。
“你個Loser,竟不知道用消息隊列,怪不得天天被人欺負,哼!”
一語驚醒夢中人,為什么不用消息隊列啊?
於是我引入消息隊列,對系統進行了重構。
這下好了,我只管更新訂單狀態
,剩下的丟給消息隊列,你們這些下游自己去消息隊列消費消息就好了,別來纏着我了。
……
引入消息隊列之后,又是一個閑適的下午。
我沒有在群聊里扯扯,因為我退群了。
前幾天,我受到了前所未有的傷害——
我在群里嘲諷一個老哥,技術真菜,連消息隊列都不會!
老哥反手就發出他和女朋友的合照,“單身狗,技術好又怎么樣,連個女朋友都沒有!”
我瞬間san值狂掉!
“程序員單身,不算單身……new個對象的事,能算單身么?”接連着便是什么難懂的話,什么“沒有妹子”,什么“哲學”之類,引得眾人都哄笑起來,群里充滿了快活的空氣。
於是,這個下午我盯着空空如也的需求單發呆,公司真的沒有妹子么?……
好了,冗長的前奏結束了,接下來該進入正文了😂。
消息隊列つ用途
在上面的前言中,我們已經了解了消息隊列最重要的一個用途:
- 解耦
通過消息隊列,降低系統間的耦合,避免過多的調用。
就好像公司的行政小姐姐要通知一件事情,她通常會是在群里發一個公告,然后我們扣1就行了。要是一個個通知,她要通知幾十上百次。
- 異步
還是上面我們提到的訂單支付,支付之后,我們要扣減庫存、增加積分、發送消息等等,這樣一來這個鏈路就長了,鏈路一長,響應時間就變長了。引入消息隊列,除了更新訂單狀態
,其它的都可以異步去做,這樣一來就來,就能更快地響應我們的上游。
為什么不用多線程之類的方式做異步呢?——
嗨,只用多線程做異步,不是還得寫代碼去調那一堆下游嗎,所以這又回到了解耦這個問題上。
- 削峰
消息隊列同樣可以用來削峰。
用一個比喻,一條河流,假如它的下游能容納的水量是有限的,為了防止洪水沖垮堤壩,我們應該怎么辦呢?
我們可以在上游修建一個水庫,洪峰來的時候,我們先把水給蓄起來,閘口里只放出下游能承受地住的水量。
放在我們的系統,也是一個道理。比如秒殺系統,平時流量很低,但是要做秒殺活動,秒殺的時候流量瘋狂懟進來,你的服務器,Redis,MySQL各自的承受能力都不一樣,直接全部流量照單全收肯定有問題啊,嚴重點可能直接打掛了。
所以一樣,我們可以把請求放到隊列里面,只放出我們下游服務能處理的流量,這樣就能抗住短時間的大流量了。
除了這三大用途之外,還可以利用隊列本身的順序性,來滿足消息必須按順序投遞的場景;利用隊列 + 定時任務來實現消息的延時消費 ……
消息隊列つ本質
過氣老北鼻馬老師有三招——接
、化
、發
。
消息隊列核心有三板斧:發
、存
、消費
。
生產者先將消息投遞一個叫做「隊列」的容器中,然后再從這個容器中取出消息,最后再轉發給消費者[1]。
上面這個圖便是消息隊列最原始的模型,它包含了消息隊列中的一兩個關鍵詞消息
和隊列隊列
:
-
消息:就是要傳輸的數據,可以是最簡單的文本字符串,也可以是自定義的復雜格式。
-
隊列:大家應該再熟悉不過了,是一種先進先出數據結構。它是存放消息的容器,消息從隊尾入隊,從隊頭出隊,入隊即發消息的過程,出隊即收消息的過程。
所以消息隊列的本質就是把要傳輸的數據放在隊列中。
圍繞着這個本質:
- 把數據放到消息隊列的角色就是
生產者
- 把數據從隊列中取出的就是
消費者
消息隊列つ模型
[1]我們上面已經了解了消息隊列模型的本質,隨着應用場景的變化,消息隊列的模型逐漸發生了一些演進。
就好像我們的文字通訊,最開始單對單地發消息,后來可以群發,再后來,可以拉一個群聊。
- 隊列模型
最初的消息隊列就是上一節講的原始模型,它是一個嚴格意義上的隊列(Queue)。消息按照什么順序寫進去,就按照什么順序讀出來。不過,隊列沒有 “讀” 這個操作,讀就是出隊,從隊頭中 “刪除” 這個消息。
如果有多個生產者往同一個隊列里面發送消息,這個隊列中可以消費到的消息,就是這些生產者生產的所有消息的合集。消息的順序就是這些生產者發送消息的自然順序。
如果有多個消費者接收同一個隊列的消息,這些消費者之間實際上是競爭的關系,每個消費者只能收到隊列中的一部分消息,也就是說任何一條消息只能被其中的一個消費者收到。
- 發布 - 訂閱模型
如果需要將一份消息數據分發給多個消費者,並且每個消費者都要求收到全量的消息。很顯然,隊列模型無法滿足這個需求。
一個可行的方案是:為每個消費者創建一個單獨的隊列,讓生產者發送多份。這種做法比較笨,而且同一份數據會被復制多份,也很浪費空間。
為了解決這個問題,就演化出了另外一種消息模型:發布-訂閱模型。
在發布 - 訂閱模型中,消息的發送方稱為發布者(Publisher),消息的接收方稱為訂閱者(Subscriber),服務端存放消息的容器稱為主題(Topic)。發布者將消息發送到主題中,訂閱者在接收消息之前需要先“訂閱主題”。“訂閱”在這里既是一個動作,同時還可以認為是主題在消費時的一個邏輯副本,每份訂閱中,訂閱者都可以接收到主題的所有消息。
仔細對比下它和 “隊列模式” 的異同:生產者就是發布者,隊列就是主題,消費者就是訂閱者,無本質區別。唯一的不同點在於:一份消息數據是否可以被多次消費。
消息隊列つ代價
天下沒有白吃的午餐——這句話放在系統設計中同樣適用。引入了消息隊列,解決了一些問題,但同時也增加了系統的復雜性和維護成本。[5]
- 高可用
前面說,我們引入了消息隊列,降低了系統間的耦合,但是,我們對消息隊列的依賴也變重了,想想,要是消息隊列掛了,那我們下游就全涼了。
所以,我們消息隊列的部署必須保證高可用,至少是主/從
,一般都得集群/分布式
。
- 消息丟失問題
我們將數據寫到消息隊列上,下游服務沒來得及取消息隊列的數據,突然掛了。如果沒有做任何的措施,我們的數據就丟了。
那可怎么辦呢?
消息隊列中的數據得想辦法存在別的地方,這樣才盡可能減少數據的丟失。
問題又來了,存在哪呢?
- 磁盤?
- 數據庫?
- Redis?
- 分布式文件系統?
同步存儲還是異步存儲?
- 重復消費問題
要是我們網絡延遲或者什么原因,導致下游重復消費怎么辦?
我們這個問題是在消息隊列解決?還是在下游服務解決?
還有其它的順序消息等等問題。
這些問題都是選型時候需要充分考慮的。
消息隊列つ選擇
目前在市面上比較主流的主要有四大消息中間件:Kafka、ActiveMQ、RabbitMQ、RocketMQ 。
它們的對比我整理了一張圖:
消息隊列つ終焉
好了,到這我們就了解了消息隊列的一些基礎的知識。
門外漢,這個門,你入了嗎?我反正是在里面打滾了。
"簡單的事情重復做,重復的事情認真做,認真的事情有創造性地做!"——
我是三分惡,一個能文能武的全棧開發。
點贊
、關注
不迷路,咱們下期見!
參考:
[1]. 《吃透MQ系列》核心基礎全在這里了
[2]. 《進大廠系列》系列-消息隊列基礎
[3]. 知乎問答:消息隊列怎么能通俗點解釋?
[4].系統學習消息隊列分享(四) 消息模型:主題和隊列有什么區別?
[5]. 什么是消息隊列?