訊息佇列 (Message Queue)

一句話定位

Queue 是一個緩衝層:Producer 把訊息丟進去就不管了,Consumer(worker)依自己的節奏拉出來處理。
它買到三件事 —— 非同步、解耦、削峰填谷;代價是最終一致、亂序風險、重複投遞,而且它不會增加你的系統容量,只是把過載往後推延。把 queue 當「解決過載的方法」是最常見的誤解 —— 真正的解法是 backpressure

三個角色與基本流程

角色 職責
Producer(生產者) 把訊息送進 queue,送完即返回,不等處理結果
Queue(佇列本體) 存放訊息,通常支援持久化(落磁碟防丟)
Consumer / Worker 從 queue 拉訊息處理,依自己的處理能力消化
                  ┌───────── Queue ─────────┐
Producer ──put──► │ m5 m4 m3 m2 m1 │ ──poll──► Consumer (Worker)
(立即返回)      └─────────────────────────┘     (自己的節奏處理)
                       訊息堆積在這裡             可水平擴展成多個 worker

訊息可以是任何形式:簡單字串、JSON、或序列化的複雜物件。


Queue 買到的三件事

1. 非同步(Asynchronous)

Producer 把訊息送進 queue 後不等 Consumer 處理完,立即返回。

使用者上傳圖片 → API 寫一筆訊息進 queue → 立刻回 200 OK("上傳成功")
                                          └─ 背景 worker 稍後做壓縮/縮圖

好處:降低使用者等待時間、前端不被慢操作阻塞。這正是 10-Design-Patterns/03-Long-Running-Tasks 的核心模式。

2. 解耦(Decoupling)

Producer 不需要知道 Consumer 是誰、在哪、是否在線;Consumer 也不關心 Producer 的細節。

訂單系統 ──訂單訊息──► Queue ──► Email Service(寄確認信)
                              └─► Inventory Service(扣庫存)

好處:服務間依賴降到最低 → 各自獨立開發、部署、擴展。queue 後面的服務可以重啟或關閉,幾乎不影響整體系統(訊息會在 queue 等著)。

3. 彈性 / 削峰填谷(Elasticity / Load Leveling)

流量高峰時訊息堆在 queue,Consumer 可動態擴容消化;流量降低時縮減 worker 數量省資源。

請求暴增 1000/s ─┐                 ┌─ Worker ×N(黑五時 scale out)
                 ▼                 │
            [ Queue 接住峰值 ] ───►┤  穩定以 200/s 速率消化
                 ▲                 │
平峰 50/s ───────┘                 └─ Worker ×1(縮回去)
削峰 ≠ 擴容

Queue 能平滑瞬時尖峰(burst),但若平均到達率 > 平均處理率,queue 只會無限增長、永遠處理不完。削峰填谷的前提是「長期吞吐量足夠,只是短期不均」。詳見下方 backpressure。


常見功能清單

功能 說明
持久化 Persistence 訊息落磁碟,broker 掛了也不丟(代價是吞吐量略降)
重試 Retry 失敗訊息自動重投,可設最大次數退避延遲
死信佇列 DLQ 重試耗盡後丟進 DLQ,供除錯/稽核,不阻塞正常流量
延遲 / 排程訊息 控制訊息何時才被消費(如 15 分鐘後提醒)
順序性 Ordering 保證同一 partition 內訊息順序一致(非全域順序)
多消費者模型 多個 consumer 平行處理;或 fan-out 給多個訂閱者

投遞語義(面試必考)

Queue 的可靠性核心是「訊息會被投遞幾次」。這幾乎一定會被深挖。

語義 保證 風險 代價
At-most-once 最多一次(可能丟) 訊息遺失 最快、最簡單
At-least-once 至少一次(可能重複 重複處理 多數 queue 預設(SQS 等)
Exactly-once 恰好一次 幾乎做不到純正 昂貴;多靠去重模擬
業界真相:At-least-once + 冪等

純正的 exactly-once 在分散式環境幾乎不可能(網路會丟 ack,broker 不知道 consumer 到底處理完沒)。主流做法是 at-least-once 投遞 + 讓 consumer 冪等(idempotent):用唯一的 message_id 去重,重複收到同一條就跳過。這個組合等效於業務上的 exactly-once。詳見 12-Ops-and-Reliability/03-Reliable-Delivery01-Networking/07-Fault-Handling

訊息確認流程(為什麼會重複):

Consumer 收訊息 → 處理 → 送 ack 給 broker → broker 刪除訊息
                                  ▲
                  若 consumer 處理完但 ack 還沒送到就掛了
                  → broker 沒收到 ack → 超時後重投 → 同一訊息被處理兩次

順序性與分區擴展

順序保證

用分區擴展(Scaling with Partitions)

                ┌─ Partition 0 (key hash %3 == 0) ─► Worker group A
Producer ─路由─►├─ Partition 1 (key hash %3 == 1) ─► Worker group B
(partition key)└─ Partition 2 (key hash %3 == 2) ─► Worker group C

05-Database-Advanced/02-Sharding 概念一致:選對 partition key,讓相關訊息進同一分區(保順序),又讓整體分散到多台(保吞吐)。這正是 09-Messaging-Coord/03-Kafka 的核心設計。


Backpressure(Queue 最大的陷阱)

Queue 會「掩蓋」容量不足,讓過載更容易發生

若系統每秒只能處理 200 個請求,卻持續收到 300/s,queue 永遠清不完、無限堆積、延遲無限增長。Queue 沒有增加任何處理能力,只是把問題藏起來、往後推。

Backpressure = 在 queue 過載時,反向減緩 producer 的生產速度

Queue 將滿 ──► 向 Producer 回壓:
   ① 拒絕新訊息(回 429 / 503 給上游或使用者)
   ② 降低接受速率(rate limit)
   ③ 讓 producer 阻塞 / 等待(有界 queue 寫滿時 block)

這與 12-Ops-and-Reliability/02-Overload-Protection 的 load shedding 是同一套思路:寧可早一點拒絕一部分請求,也不要讓整個系統雪崩。也呼應 10-Design-Patterns/02-Scaling-Writes 處理寫入洪峰的策略。


同步場景慎用 Queue

強延遲要求下加 queue 幾乎一定破壞 SLA

如果有 < 500ms 的延遲要求,加入 queue 後訊息要排隊、等 worker 拉取、可能還要重試 —— 幾乎可確定會打破這個限制。Queue 天生是非同步工具。
判斷準則:呼叫方需要立即拿到結果(同步 RPC、API 即時回應)→ 不要用 queue;呼叫方只要「之後會做完」→ queue 是好選擇。


常見產品與服務

類別 產品 定位 / 特點
雲端託管 AWS SQS 全託管、at-least-once、標準 / FIFO 兩種
Google Pub/Sub 全託管 pub/sub,自動擴展
Azure Queue Storage 簡單可靠的雲端 queue
開源 broker RabbitMQ AMQP 協定,路由靈活(exchange/binding),低延遲
Kafka 高吞吐 log stream,分區 + 可重播,偏資料管線
ActiveMQ 老牌 JMS/AMQP broker
Redis Stream 輕量、低延遲,附在 Redis 上

適用場景

場景 說明
任務非同步化 雲端照片服務:上傳後把昂貴的影像處理丟 queue,多個 worker 拉取平攤
流量削峰 Uber 叫車尖峰:queue 緩衝暴增請求,系統以可控速率消化,不打掛伺服器
服務解耦 訂單系統與寄信系統透過 queue 連接,互不影響、各自擴展
事件驅動架構 queue / topic 當 event bus,一個事件 fan-out 觸發多個下游服務

面試話術

不要「為了用而用」

最有說服力的回答是先講清楚為什麼需要:「這裡使用者上傳圖片後不需要等壓縮完成,所以我用 queue 讓 API 立即回應,背景 worker 非同步處理 —— 這同時帶來解耦(壓縮服務可獨立擴展)和削峰(黑五尖峰被 queue 接住)。」當你需要非同步 / 解耦 / 削峰時才引入 queue,而不是預設加上。

被追問時主動講出三個深度點,立刻顯示你懂:

  1. 「這條訊息可能被處理兩次」 → 我會用 at-least-once + consumer 冪等(message_id 去重)。
  2. 「Queue 不會增加容量」 → 若平均到達率超過處理率,我需要 backpressure(回壓 / 拒絕)而不是無腦堆 queue。
  3. 「失敗訊息怎麼辦」 → 重試 N 次後進 DLQ,對 DLQ 深度設告警,人工或自動回放。

常見 Deep Dive

Queue 不是銀彈

它解決的是「時間上的不均」與「服務間耦合」,不解決「整體吞吐量不足」。容量不夠就是要加機器或限流,queue 只會把過載藏得更深。


自我測驗重點

問題 重點
Queue 買到哪三件事 非同步、解耦、削峰填谷(彈性)
投遞語義主流是哪個 at-least-once + consumer 冪等 ≈ 業務 exactly-once
為什麼會重複投遞 ack 在處理後丟失 → broker 超時重投
Backpressure 解決什麼 queue 掩蓋容量不足;過載時回壓/拒絕 producer
DLQ 的用途 重試耗盡的訊息隔離,供除錯/稽核/回放,不阻塞主流程
多 consumer 為何亂序 平行處理無法控制完成順序;靠 partition key + 單 consumer
何時該用 queue 同步、強延遲 SLA(< 500ms)的場景
RabbitMQ vs Kafka 靈活路由/任務隊列 vs 高吞吐可重播 log stream