系統設計高頻率元件之訊息佇列 (Message Queue)

2024年6月21日

💎 加入 E+ 成長計畫 與超過 400+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源

先前在《系統設計高頻率元件之 Redis》一文我們談了 Redis,今天要進一步來談另一個系統設計中,高頻率會用到的元件 — 訊息佇列 (Message Queue)。

佇列與訊息佇列

相信多數人對於佇列 (Queue) 這個資料結構都不陌生,佇列是一個先進先出 (First-In, First-Out,FIFO) 原則的資料結構。就像在排隊買東西一樣,最先進入隊伍的那個人會最先被服務。事實上,在英文當中 queue 這個詞就是平常在排隊時會用的詞。

假如你對佇列還不熟,非常推薦《An interactive study of queueing strategies》這個網路教材,可以讓你快速掌握佇列這個資料結構,推薦讀完後再往下看這期的內容。

《An interactive study of queueing strategies》
圖片來源:《An interactive study of queueing strategies》

如果你讀完上面那篇《An interactive study of queueing strategies》,讓我們回到訊息佇列這個主題上。如其名所述,訊息佇列是專門用來傳遞訊息的佇列。一般來說,訊息佇列會用來在系統中不同元件之間傳遞訊息,例如在不同的應用或服務之間傳遞訊息。如同上面提到的佇列,訊息佇列的運作方式,也是先進的訊息先處理。

具體來說,會是像下面這樣

1. 生產者發送訊息到佇列:
+------------+             +--------------+
|   生產者    |--Message 1->|    訊息佇列   |
|            |--Message 2->|  [Message 1] |
|            |--Message 3->|  [Message 2] |
+------------+             |  [Message 3] |
                           +--------------+

2. 消費者從佇列取出最先放入的訊息 1 進行處理:
+--------------+             +--------------+
|    訊息佇列   |--Message 1->|   消費者      |
|  [Message 1] |             |              |
|  [Message 2] |             +--------------+
|  [Message 3] |
+--------------+

3. 消費者繼續處理訊息 2
+--------------+             +--------------+
|    訊息佇列   |--Message 2->|   消費者      |
|  [Message 2] |             |              |
|  [Message 3] |             +--------------+
+--------------+

4. 最後處理訊息 3
+--------------+             +--------------+
|    訊息佇列   |--Message 3->|   消費者      |
|  [Message 3] |             |              |
+--------------+             +--------------+

上面這張圖可以看到,訊息佇列作為傳遞訊息的中介角色,會把從生產者 (producer) 拿到的訊息,依照先進先出的順序,傳給消費者 (consumer)。

為什麼要用訊息佇列?

上面這樣講完,你大概能懂訊息佇列在做什麼。只是為什麼要在系統中用這個元件呢? 畢竟系統中每多一個元件,複雜性就會提升,沒有特別好處的話,沒道理要用。 對此,讓我們透過實際的例子來了解,訊息佇列的不同用途。

協助解耦

在系統中最常用到訊息佇列的用途之一,是當有多個消費者,需要收到同樣的訊息。舉例來說,今天在一個電商平台底下有訂單系統,在一筆新的訂單下來後,需要同步讓不同的微服務獲得該筆訂單的資訊。

這時訂單系統把訂單資訊發給訊息佇列,然後其他系統,可以從該訊息佇列獲得該筆訂單資訊。在這個設計下,訂單系統跟其他系統彼此是解耦合的,因為訂單系統只需用確保把訊息發到訊息佇列,後面有哪些系統要接收訊息,訂單系統不用擔心。而後面要接的其他系統,想要接入或移除,都不用動到訂單系統的邏輯,這讓系統的靈活性與可擴展性都有所提升。

+-----------+         +-------------+
|  訂單      |         |   訊息      |
|  系統      |---->    |   佇列      |
+-----------+         +-------------+
                 /       /      |       \\
                /       /       |        \\
         +--------+  +-------+ +-------+ +-------+
         |  倉儲   |  |  物流  | | 客服  | | 分析   |
         |  系統   |  |  系統  | | 系統  | | 系統   |
         +--------+  +-------+ +-------+ +-------+

做為緩衝提高故障容忍度

除了解耦外,另一個訊息佇列帶來的優點是能夠做為緩衝。以目前常見的電子信系統來說,一個發信人發出電子信後,會有數百到數萬的收件人要收。那麼假如其中有收件人的服務出問題,假如是直接傳的方式,就會讓訊息丟失。

因此,當發信人發出新一期的電子信後,不會直接傳給收件人,而是會先進到訊息佇列。訊息佇列在這邊可以做為一個緩衝區,如果收件人的電子信箱服務出問題,信件還停留在佇列中,直到問題被解決後再發送,這樣做將能提高系統的故障容忍度。

+------------+          +---------------+          +------------+
|  發信人     |  ---->   |   訊息佇列     |  ---->   |  收件人     |
+------------+          +---------------+          +------------+

作為緩衝區,有時也能夠解決運算過度繁重的問題。舉例來說,現在的生成式 AI 應用,背後都需要有繁重的運算任務,例如影音生成、文字轉語音等。如果直接把這些任務放在伺服器處理,伺服器可能應接不暇。

這時有訊息佇列作為緩衝區,可以先把任務丟到佇列中,然後有專門處理的 Worker 來處理 (備註:Worker 是指專門處理特定任務的伺服器)。如果目前有的 Worker 都在處理其他任務,也不擔心。可以等有 Worker 有閒置產能時,在去佇列中拿任務來處理。而待處理的任務,會一直在佇列中,不擔心會丟失。

1. 使用者需要某個運算繁重的任務 (例如要文字轉語音)
+---------+            +------------+
|  使用者  |   ---->    |    伺服器   |
+---------+            +------------+

2. Web 伺服器將請求放入訊息佇列:
+------------+        +--------------+
|   伺服器    | ---->  |    訊息佇列    |
+------------+        +--------------+

3. 丟給對應的 Worker 處理 (例如文字轉語音的 Worker)
+--------------+        +---------+
|    訊息佇列    | ----> |  Worker  |
+--------------+        +---------+

4. 處理完成後,通知用戶或更新狀態:
+---------+            +---------+
|  Worker |   ---->    |  使用者  |
+---------+            +---------+

更多深入內容

以上我們介紹了訊息佇列的基本概念,以及為什麼要用訊息佇列。在實際做系統設計時,還有一些深入概念推薦大家要知道,而我們在 E+ 的深度版本,會進一步談到:

  • Pub/Sub 設計模式
  • 如何解決使用訊息佇列的不一致問題
  • 哪些場景不適合使用訊息佇列
  • 不同的訊息佇列工具,該選擇哪一個

有興趣的人,歡迎加入 E+ 成長計畫,詳細介紹可以 參考此頁面

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們