十二張圖,踹開訊息佇列的大門

三分惡發表於2021-07-08

訊息佇列つ登場

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

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

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

訂單支付-1

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

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

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

訂單支付-2

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

又過兩天,她說要新增營銷系統,行吧,幹!

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

又過兩天……

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

訂單支付-3

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

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

週一,營銷系統;

週二,庫存系統;

……

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

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

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

“你個Loser,竟不知道用訊息佇列,怪不得天天被人欺負,哼!”

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

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

訊息佇列重構

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

……

引入訊息佇列之後,又是一個閒適的下午。

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

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

我在群裡嘲諷一個老哥,技術真菜,連訊息佇列都不會!

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

我瞬間san值狂掉!

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

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

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

訊息佇列つ用途

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

  • 解耦

通過訊息佇列,降低系統間的耦合,避免過多的呼叫。

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

  • 非同步

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

訊息佇列非同步

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

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

  • 削峰

訊息佇列同樣可以用來削峰。

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

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

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

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

削峰

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

訊息佇列つ本質

過氣老北鼻馬老師有三招——

訊息佇列核心有三板斧:消費

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

訊息佇列核心

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

  1. 訊息:就是要傳輸的資料,可以是最簡單的文字字串,也可以是自定義的複雜格式。

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

所以訊息佇列的本質就是把要傳輸的資料放在佇列中。

圍繞著這個本質:

  • 把資料放到訊息佇列的角色就是生產者
  • 把資料從佇列中取出的就是消費者

訊息佇列つ模型

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

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

  • 佇列模型

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

佇列模型

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

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

  • 釋出 - 訂閱模型

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

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

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

釋出-訂閱模型

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

仔細對比下它和 “佇列模式” 的異同:生產者就是釋出者,佇列就是主題,消費者就是訂閱者,無本質區別。唯一的不同點在於:一份訊息資料是否可以被多次消費。

訊息佇列つ代價

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

  • 高可用

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

訊息佇列故障

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

  • 訊息丟失問題

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

訊息丟失

那可怎麼辦呢?

訊息佇列中的資料得想辦法存在別的地方,這樣才儘可能減少資料的丟失。

問題又來了,存在哪呢?

  • 磁碟?
  • 資料庫?
  • Redis?
  • 分散式檔案系統?

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

  • 重複消費問題

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

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

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

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

訊息佇列つ選擇

目前在市面上比較主流的主要有四大訊息中介軟體:Kafka、ActiveMQ、RabbitMQ、RocketMQ

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

四大訊息佇列對比

訊息佇列つ終焉

好了,到這我們就瞭解了訊息佇列的一些基礎的知識。

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


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

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

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


參考:

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

[2]. 《進大廠系列》系列-訊息佇列基礎

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

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

[5]. 什麼是訊息佇列?

相關文章