把耗時工作交給背景程序,會縮短呼叫者等待受理的時間,也會增加一個必須追蹤的交接過程。 要理解訊息佇列,先把「接受意圖」「交付工作」與「完成結果」分開。
本文從文章匯出服務出發,只要求理解一次請求回應與基本資料庫交易。對外操作資源與輪詢方式見長時間執行操作與非同步 API 契約,這裡研究 API 後面的工作如何接續。
查證日期:2026-11-04。依 RFC 9110 與當日 Microsoft 官方文件整理;所有工作狀態、識別與歷程均為自創概念例子,未部署 broker 或執行故障實驗。
先從不使用 broker 的版本開始
假設使用者要求匯出一組文章。同步版本由 API 驗證輸入、產生檔案,最後回傳下載入口。若等待期間連線中斷,使用者可能只看到逾時,卻不知道檔案是否已產生。
背景版本可以先保存工作 job-42,回傳工作識別,讓 worker 稍後讀取待處理的資料列。這已經把受理與執行分開,並不必然需要獨立的訊息服務。是否增加 broker,應從工作分派、負載緩衝與不同程序協作的需求決定。
HTTP 的 202 Accepted 只表示已接受處理,不能推論處理一定完成,甚至不能單憑狀態碼保證未來一定執行。更強的受理承諾要由服務契約補上。參考 RFC 9110 §15.3.3。
本例選擇較強的應用契約:回傳受理成功前,必須持久保存可查詢的工作;工作在依賴恢復且資源足夠時,應走向成功或明確失敗。這是案例需求,並非 202 自動提供的能力。
引入佇列,是增加一次責任交接
生產者(producer)建立並送出訊息;訊息代理(broker)依其設定保存、路由或提供訊息;消費者(consumer)讀取訊息並處理。worker 是本例執行匯出的程序,也是工作訊息的 consumer。
佇列可以把到達速度與處理速度暫時分開。Microsoft 的 Queue-Based Load Leveling 說明以佇列緩衝流量,讓處理端按可承受速率工作。這種緩衝仍受保存期限、容量及處理能力限制,不能把有限資源變成無限容量。參考 Azure:Queue-Based Load Leveling。
以下歷程中的「發布已確認」暫指 producer 取得 broker 按設定回覆的成功證據;不同產品確認到哪裡,第四篇再拆解。
| 步驟 | 發生的事 | 目前能確認的進度 |
|---|---|---|
| 1 | 呼叫端以意圖識別 export-intent-7 建立匯出 | 發出了建立請求,受理結果仍未知 |
| 2 | API 保存 job-42 為 pending | 工作存在,尚不能證明訊息已交給 broker |
| 3 | producer 發布 cmd-42,指向 job-42,取得發布確認 | 依 broker 契約完成交接,尚未證明 worker 開始 |
| 4 | worker 取得訊息,將工作推進為 running | 有一次處理嘗試開始,檔案仍可能失敗 |
| 5 | worker 保存檔案與結果,工作進入 succeeded | 達到本例定義的匯出完成條件 |
| 6 | worker 告知 broker 本次投遞可結束 | broker 的消費進度才接上業務完成進度 |
表格是概念歷程,沒有宣稱上述六步共用一個交易。實際上,步驟 2 與 3 的資料庫/broker 雙寫,以及步驟 5 與 6 的效果/確認,正是新增的故障窗口。
狀態必須對應具體證據
本例用 pending 表示已受理但尚未確認開始,running 表示有處理嘗試,succeeded 表示檔案已按契約保存且結果可查,failed 表示業務已決定終止。各狀態的寫入者與轉移條件也要明訂;收到第二份訊息不能無條件把已成功工作改回執行中。
程序停止時,資料庫可能長時間留下 running。因此這個字不能獨自證明 worker 現在仍活著;恢復程序必須查明處理嘗試與結果,依契約重試、核對或終止。單次讀取失敗也不應立即被寫成永久的業務失敗。
三個停止點,檢查非同步增加的責任
| 停止點 | 可能留下的狀態 | 設計必須回答的問題 |
|---|---|---|
| 保存工作後、發布前 | 有 pending 工作,沒有可消費訊息 | 誰能找出並補交?只等待 worker 不會自行修復 |
| broker 接受後、producer 收到確認前 | 訊息可能存在,producer 結果未知 | 重送如何辨認同一命令,避免另建工作? |
| 效果保存後、消費確認前 | 工作已成功,訊息仍可能再次出現 | 下一個 worker 如何辨認已完成效果? |
第一個缺口不能靠調整 consumer ack 解決;第三個也不能靠「producer 發布成功」解決。先保存工作再發訊息與先發訊息再保存工作,只是改變故障窗口,沒有消除跨系統原子性問題。後續的資料庫與訊息原子缺口篇會展開 Outbox;本篇只確認這個問題仍存在。
重送的基本條件可回查從重送請求到冪等副作用:識別、去重紀錄與業務變更必須在適用範圍內一致處理。佇列本身不替應用保存這些業務規則。
緩衝之後,壓力仍然需要出口
假設每秒進來 12 個工作,worker 合計每秒只能完成 10 個,且持續一分鐘都沒有失敗與重試,則積壓增加 2 × 60 = 120 個。這是明列假設的計算,並非產品效能數據。
若流入長期大於流出,新增佇列只會延後暴露問題。服務仍需控制受理量、並行度、等待期限與過期工作,並告訴使用者何時無法履行受理承諾。相關原理見過載、時間預算與背壓。
讀完本篇,應能指出:受理成功、訊息已交接、工作已完成是三個不同觀察點。下一篇會替傳遞的內容命名,區分命令、工作與事件。
相關文件
- 訊息佇列與事件驅動架構知識索引:查看完整閱讀主線與待寫方向。
- 分散式系統、部分失敗與故障模型:為何未知結果不能直接視為失敗。
- 發布確認與消費確認:確認訊號各自涵蓋的邊界。