十二張圖,踹開消息隊列的大門

消息隊列つ登場

大家好,我是老三,是一個電商公司的程式設計師,負責訂單系統的開發。

掉了不少頭髮之後,我完成了用戶訂單支付的開發。

訂單支付的業務是這樣的。用戶支付完成之後,我需要更新訂單狀態,這一部分是在本系統完成的。接下來,我要調用庫存系統,減庫存,好了,剩下的就是庫存系統的事情了。

订单支付-1

開發、聯調、測試、上線,我的小日子變得清閑起來,每天就是在群里吹牛打屁。

可是沒過兩天,產品妹子,找過來了,她說,她想加個功能,用戶完成訂單支付以後,要增加用戶的積分。

沒問題,so easy,噼里啪啦,我兩天就做完了,無非是調用一下會員系統。

订单支付-2

這天,正和沙雕群友斗圖的時候,產品妹子過來,他說要接入消息系統,好,搞!

又過兩天,她說要添加營銷系統,行吧,干!

又過兩天,她說要搞推薦系統,嗯……,來吧!

又過兩天……

於是系統就變成了這個樣子:

订单支付-3

就這樣,我過上了暗無天日的日子,我要維護和若干個系統的對接,每次他們發布新版本,我都要跟著值班。

我要迭代,也要改和幾個系統的對接程式碼。

周一,營銷系統;

周二,庫存系統;

……

這天,眼圈發黑的我正在和下游服務撕巴的時候,突然忍不住兩腿戰戰,她來了,產品妹(女)子(王)來了——她是我不能拒絕的女人。

脆弱的眼淚流了滿面,我的猿生一片灰暗……

沒想到,代救星出現了,我的好朋友傲天過來了,拿鼻孔看著我。

「你個Loser,竟不知道用消息隊列,怪不得天天被人欺負,哼!」

一語驚醒夢中人,為什麼不用消息隊列啊?

於是我引入消息隊列,對系統進行了重構。

消息队列重构

這下好了,我只管更新訂單狀態,剩下的丟給消息隊列,你們這些下游自己去消息隊列消費消息就好了,別來纏著我了。

……

引入消息隊列之後,又是一個閑適的下午。

我沒有在群聊里扯扯,因為我退群了。

前幾天,我受到了前所未有的傷害——

我在群里嘲諷一個老哥,技術真菜,連消息隊列都不會!

老哥反手就發出他和女朋友的合照,「單身狗,技術好又怎麼樣,連個女朋友都沒有!」

我瞬間san值狂掉!

「程式設計師單身,不算單身……new個對象的事,能算單身么?」接連著便是什麼難懂的話,什麼「沒有妹子」,什麼「哲學」之類,引得眾人都鬨笑起來,群里充滿了快活的空氣。

於是,這個下午我盯著空空如也的需求單發獃,公司真的沒有妹子么?……

好了,冗長的前奏結束了,接下來該進入正文了😂。

消息隊列つ用途

在上面的前言中,我們已經了解了消息隊列最重要的一個用途:

  • 解耦

通過消息隊列,降低系統間的耦合,避免過多的調用。

就好像公司的行政小姐姐要通知一件事情,她通常會是在群里發一個公告,然後我們扣1就行了。要是一個個通知,她要通知幾十上百次。

  • 非同步

還是上面我們提到的訂單支付,支付之後,我們要扣減庫存、增加積分、發送消息等等,這樣一來這個鏈路就長了,鏈路一長,響應時間就變長了。引入消息隊列,除了更新訂單狀態,其它的都可以非同步去做,這樣一來就來,就能更快地響應我們的上游。

消息队列异步

為什麼不用多執行緒之類的方式做非同步呢?——

嗨,只用多執行緒做非同步,不是還得寫程式碼去調那一堆下游嗎,所以這又回到了解耦這個問題上。

  • 削峰

消息隊列同樣可以用來削峰。

用一個比喻,一條河流,假如它的下游能容納的水量是有限的,為了防止洪水衝垮堤壩,我們應該怎麼辦呢?

我們可以在上游修建一個水庫,洪峰來的時候,我們先把水給蓄起來,閘口裡只放出下游能承受地住的水量。

放在我們的系統,也是一個道理。比如秒殺系統,平時流量很低,但是要做秒殺活動,秒殺的時候流量瘋狂懟進來,你的伺服器,Redis,MySQL各自的承受能力都不一樣,直接全部流量照單全收肯定有問題啊,嚴重點可能直接打掛了。

所以一樣,我們可以把請求放到隊列裡面,只放出我們下游服務能處理的流量,這樣就能抗住短時間的大流量了。

削峰

除了這三大用途之外,還可以利用隊列本身的順序性,來滿足消息必須按順序投遞的場景;利用隊列 + 定時任務來實現消息的延時消費 ……

消息隊列つ本質

過氣老北鼻馬老師有三招——接、化、發。

消息隊列核心有三板斧:發、存、消費。

生產者先將消息投遞一個叫做「隊列」的容器中,然後再從這個容器中取出消息,最後再轉發給消費者[1]。

消息队列核心

上面這個圖便是消息隊列最原始的模型,它包含了消息隊列中的一兩個關鍵詞消息和隊列隊列:

  1. 消息:就是要傳輸的數據,可以是最簡單的文本字元串,也可以是自定義的複雜格式。

  2. 隊列:大家應該再熟悉不過了,是一種先進先出數據結構。它是存放消息的容器,消息從隊尾入隊,從隊頭出隊,入隊即發消息的過程,出隊即收消息的過程。

所以消息隊列的本質就是把要傳輸的數據放在隊列中。

圍繞著這個本質:

  • 把數據放到消息隊列的角色就是生產者
  • 把數據從隊列中取出的就是消費者

消息隊列つ模型

[1]我們上面已經了解了消息隊列模型的本質,隨著應用場景的變化,消息隊列的模型逐漸發生了一些演進。

就好像我們的文字通訊,最開始單對單地發消息,後來可以群發,再後來,可以拉一個群聊。

  • 隊列模型

最初的消息隊列就是上一節講的原始模型,它是一個嚴格意義上的隊列(Queue)。消息按照什麼順序寫進去,就按照什麼順序讀出來。不過,隊列沒有 「讀」 這個操作,讀就是出隊,從隊頭中 「刪除」 這個消息。

队列模型

如果有多個生產者往同一個隊列裡面發送消息,這個隊列中可以消費到的消息,就是這些生產者生產的所有消息的合集。消息的順序就是這些生產者發送消息的自然順序。

如果有多個消費者接收同一個隊列的消息,這些消費者之間實際上是競爭的關係,每個消費者只能收到隊列中的一部分消息,也就是說任何一條消息只能被其中的一個消費者收到。

  • 發布 - 訂閱模型

如果需要將一份消息數據分發給多個消費者,並且每個消費者都要求收到全量的消息。很顯然,隊列模型無法滿足這個需求。

一個可行的方案是:為每個消費者創建一個單獨的隊列,讓生產者發送多份。這種做法比較笨,而且同一份數據會被複制多份,也很浪費空間。

為了解決這個問題,就演化出了另外一種消息模型:發布-訂閱模型。

发布-订阅模型

在發布 - 訂閱模型中,消息的發送方稱為發布者(Publisher),消息的接收方稱為訂閱者(Subscriber),服務端存放消息的容器稱為主題(Topic)。發布者將消息發送到主題中,訂閱者在接收消息之前需要先「訂閱主題」。「訂閱」在這裡既是一個動作,同時還可以認為是主題在消費時的一個邏輯副本,每份訂閱中,訂閱者都可以接收到主題的所有消息。

仔細對比下它和 「隊列模式」 的異同:生產者就是發布者,隊列就是主題,消費者就是訂閱者,無本質區別。唯一的不同點在於:一份消息數據是否可以被多次消費。

消息隊列つ代價

天下沒有白吃的午餐——這句話放在系統設計中同樣適用。引入了消息隊列,解決了一些問題,但同時也增加了系統的複雜性和維護成本。[5]

  • 高可用

前面說,我們引入了消息隊列,降低了系統間的耦合,但是,我們對消息隊列的依賴也變重了,想想,要是消息隊列掛了,那我們下游就全涼了。

消息队列故障

所以,我們消息隊列的部署必須保證高可用,至少是主/從,一般都得集群/分散式。

  • 消息丟失問題

我們將數據寫到消息隊列上,下游服務沒來得及取消息隊列的數據,突然掛了。如果沒有做任何的措施,我們的數據就丟了。

消息丢失

那可怎麼辦呢?

消息隊列中的數據得想辦法存在別的地方,這樣才儘可能減少數據的丟失。

問題又來了,存在哪呢?

  • 磁碟?
  • 資料庫?
  • Redis?
  • 分散式文件系統?

同步存儲還是非同步存儲?

  • 重複消費問題

要是我們網路延遲或者什麼原因,導致下游重複消費怎麼辦?

我們這個問題是在消息隊列解決?還是在下游服務解決?

還有其它的順序消息等等問題。

這些問題都是選型時候需要充分考慮的。

消息隊列つ選擇

目前在市面上比較主流的主要有四大消息中間件:Kafka、ActiveMQ、RabbitMQ、RocketMQ 。

它們的對比我整理了一張圖:

四大消息队列对比

消息隊列つ終焉

好了,到這我們就了解了消息隊列的一些基礎的知識。

門外漢,這個門,你入了嗎?我反正是在裡面打滾了。


"簡單的事情重複做,重複的事情認真做,認真的事情有創造性地做!"——

我是三分惡,一個能文能武的全棧開發。

點贊、關注不迷路,咱們下期見!


參考:

[1]. 《吃透MQ系列》核心基礎全在這裡了

[2]. 《進大廠系列》系列-消息隊列基礎

[3]. 知乎問答:消息隊列怎麼能通俗點解釋?

[4].系統學習消息隊列分享(四) 消息模型:主題和隊列有什麼區別?

[5]. 什麼是消息隊列?