系統設計高頻率元件之訊息佇列 (Message Queue)
2024年6月21日
先前在《系統設計高頻率元件之 Redis》一文我們談了 Redis,今天要進一步來談另一個系統設計中,高頻率會用到的元件 — 訊息佇列 (Message Queue)。
佇列與訊息佇列
相信多數人對於佇列 (Queue) 這個資料結構都不陌生,佇列是一個先進先出 (First-In, First-Out,FIFO) 原則的資料結構。就像在排隊買東西一樣,最先進入隊伍的那個人會最先被服務。事實上,在英文當中 queue 這個詞就是平常在排隊時會用的詞。
假如你對佇列還不熟,非常推薦《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+ 成長計畫,詳細介紹可以 參考此頁面。