上一篇比較了 ECS、EKS 與 Fargate,決定容器由誰管理、使用哪些運算資源,接下來帶大家看的主題是:當一個請求需要多個服務合作,哪些工作必須當下完成?
例如使用者下單後,系統需要建立訂單、保留庫存、寄送確認信與更新報表,如果全部同步處理,寄信服務變慢也會拖長下單時間;某個下游服務故障,還可能讓整個請求失敗。
今天會比較 Amazon SQS、Amazon SNS 與 Amazon EventBridge,看看佇列(Queue)、發布/訂閱(Pub/Sub)與事件路由(Event Routing)分別適合什麼情境。
以下用一個假設的購物網站來看,在本文的業務規則下:
因此,系統必須先確認庫存保留與訂單建立成功,才能回覆使用者「下單成功」,確認信與報表則可以交給背景程式處理,使用者不必留在頁面上等待。
選擇同步或非同步時,可以先問:這項工作的結果,是否必須在這一次請求內確認,才能決定要回覆使用者什麼?
這個案例中需要立即知道訂單是否成立,但不需要等確認信寄出,才能完成下單流程。
服務之間傳送的訊息,可以先分成兩種意思:
| 訊息 | 表達的意思 |
|---|---|
GenerateOrderPDF |
請背景程式產生訂單 PDF |
OrderCreated |
訂單已經成立,讓需要知道的服務自行處理 |
前者是 Command(工作指令),送出端已經決定要執行什麼工作,後者是 Event(事件),描述已經發生的事情,後續行為由接收端決定。
如果需要把工作排隊交給背景程式,SQS 很適合;如果希望發布事件,讓下游透過訂閱或路由條件取得,再評估 SNS 或 EventBridge。
這個是訊息設計上的差異,而不是服務限制,SQS 同樣可以保存事件,承接 SNS 或 EventBridge 傳來的消息。
假設使用者要求產生一份訂單 PDF,處理需要十秒:
| 處理方式 | API 做什麼? | 使用者取得什麼? |
|---|---|---|
| 同步 | 等待 PDF 產生後才回應 | 完成的檔案 |
| 非同步 | 將工作送入佇列後回應 | 工作編號,稍後查詢結果 |
Amazon SQS(Simple Queue Service) 負責保存待處理訊息,讓背景程式依自己的能力取出工作。
送出訊息的程式稱為 Producer(生產者),取得訊息並執行工作的程式稱為 Consumer(消費者),背景工作程式也常稱為 Worker。
例如 API 將以下工作放進 SQS:
{
"jobId": "JOB-1001",
"action": "GenerateOrderPDF",
"orderId": "ORD-5001"
}
背景程式取出訊息、產生 PDF,再更新工作狀態,API 不需要等待整份檔案完成,背景程式短暫停止時,已送入佇列的工作也能在保留期限內等待後續處理。
假設促銷期間,十秒內每秒進來 2,000 件工作,背景程式每秒只能完成 500 件,這段時間就會累積約 15,000 件工作。
SQS 可以先保存這些工作,讓消費者逐步消化,這種做法稱為 Load Leveling(負載平滑)。
但如果流入速度長期高於處理速度,等待時間仍會持續增加,因此除了監控訊息數量,也要看最舊訊息已經等了多久,以及是否需要增加消費者。
消費者取得訊息後,訊息會進入 Visibility Timeout(可見性逾時),通常會暫時對其他消費者不可見,工作成功後才刪除訊息;若程式中斷、未完成刪除,逾時後訊息就能再次被取出。
本文先以 Standard Queue(標準佇列) 為例:它採至少一次傳遞,不保證嚴格順序,即使在可見性逾時期間,仍可能重複傳遞訊息。
例如同一份 PDF 工作收到兩次,可以透過 jobId 唯一性限制或可靠的狀態更新機制,避免建立重複結果,這種重複執行仍不造成額外業務影響的設計,稱為 Idempotency(冪等性)。
如果訂單成立後,寄信、物流與分析服務都需要收到通知,就需要一對多的分送方式。
讓三個服務共同讀同一個 SQS 佇列,是分攤工作,無法保證每個服務各收到一份。
這時可以使用 Amazon SNS(Simple Notification Service),發布者將訊息送到 Topic(主題),SNS 再傳送給符合訂閱條件的接收方,這就是 Publish/Subscribe(發布/訂閱,Pub/Sub)。
將一份訊息分送多個接收方,也稱為 Fan-out(扇出),如果各服務需要獨立排隊,可以讓每個服務各有一個 SQS 佇列,再訂閱同一個 SNS Topic:
| 同一個訂單主題的訂閱者 | 處理方式 |
|---|---|
| 寄信佇列 | 寄信程式取出訊息,寄送確認信 |
| 物流佇列 | 物流程式取出訊息,建立配送工作 |
| 分析佇列 | 分析程式取出訊息,更新統計 |
每個服務都有自己的工作進度與重試流程,寄信服務暫時停止時,物流服務仍能繼續處理自己的佇列。
在這個組合中,SNS 負責分送,SQS 負責讓各接收方依自己的速度處理,兩者可以搭配使用。
Amazon EventBridge 可以依事件來源、類型與內容,將事件送到對應目標,也能整合 AWS 服務、自訂應用程式與支援的 SaaS 事件。
這裡聚焦在 Event Bus(事件匯流排),例如訂單服務發布一筆事件:
{
"source": "shop.orders",
"detail-type": "OrderCreated",
"detail": {
"orderId": "ORD-5001",
"amount": 12000
}
}
接收方可以依不同條件取得事件:
| 事件條件 | 後續處理 |
|---|---|
| 訂單成立 | 寄送確認信 |
| 訂單成立且金額超過指定門檻 | 交給風險檢查服務 |
| 訂單取消 | 通知庫存服務釋放保留數量 |
| 所有訂單事件 | 送往分析服務 |
這就是 Event Routing(事件路由),即使目前只有訂單服務這一個來源,也可以使用 EventBridge,來源數量不是必要條件。
SNS 的訂閱可以設定 Filter Policy(篩選政策),依訊息屬性或 JSON 訊息內容決定要接收哪些訊息,因此需要篩選不一定就要使用 EventBridge。
我會先比較以下條件:
| 判斷方向 | 優先評估 |
|---|---|
| 圍繞明確主題,將消息分送給訂閱者,且訂閱篩選已滿足需求 | SNS |
| 希望透過事件匯流排,統一管理依事件來源、類型與內容進行的路由 | EventBridge |
EventBridge 也適合整合應用程式、AWS 服務、SaaS 或跨帳號事件,SNS 同樣支援內容篩選與跨帳號存取,因此仍要依實際整合方式選擇,不能只靠單一功能區分。
接收端若需要獨立排隊與控制處理速度,兩者都可以搭配 SQS。
如果同一筆訂單的訊息必須依序處理,可以評估 SQS FIFO,或 SNS FIFO 搭配 SQS FIFO,維持同一訊息群組內的順序。
EventBridge 的新版 Custom Event Bus 也支援 FIFO Subscriber(依序傳遞的訂閱者),可以維持同一事件群組內的發布順序。
這些機制維持的是訊息傳入的順序,不會自動修正來源端已經送錯順序的業務事件。
失敗處理則要分清楚兩個階段:
| 失敗發生在哪裡? | 要處理的問題 |
|---|---|
| 訊息還沒成功送到接收端 | SNS/EventBridge 的傳遞重試與 DLQ 設定 |
| 消費者已取得訊息,但工作失敗 | 程式重試、冪等性,以及 SQS 的 DLQ 設定 |
Dead-letter Queue(DLQ,死信佇列) 用來隔離符合失敗轉送條件的訊息,方便後續分析與重新處理,哪些失敗會送入 DLQ 取決於服務與設定,傳遞端的 DLQ 不一定能捕捉接收程式內部的業務處理失敗。
團隊仍要設定告警、查明原因,再決定如何重新處理,訊息成功送達也不等於寄信、出貨等業務工作已經完成,處理結果仍要由應用程式記錄。
| 需求 | 優先評估 | 需要承擔的管理責任 |
|---|---|---|
| 有明確工作需要排隊交給背景程式 | SQS | 處理速度、逾時、重試與冪等性 |
| 將消息發布到主題,讓訂閱者各自取得 | SNS | 訂閱與篩選設定、傳遞失敗處理 |
| 依事件條件路由,並整合不同事件來源與目標 | EventBridge | 事件格式、分流條件、權限與失敗處理 |
| 接收端需要自己的緩衝與處理速度 | 在 SNS/EventBridge 後搭配 SQS | 各佇列的積壓、消費速度與重試流程 |
回到開頭的購物網站,如果訂單服務已經明確決定「請寄信程式寄送確認信」,可以優先評估 SQS 保存這項背景工作。
在「訂單與庫存必須先確認、確認信可延後一分鐘,而且寄信是明確指定的背景工作」的條件下選擇 SQS,同時需承擔可靠發布、重試、冪等性與等待時間監控的責任。
如果希望訂單服務只發布 OrderCreated,由下游自行決定後續行為,即使目前只有一個接收方,也可以使用 SNS 或 EventBridge,再依主題訂閱、事件路由與整合需求比較。
SQS、SNS 與 EventBridge 其實沒有固定的升級順序 ,評估架構時要先確認訊息要交給誰、如何分送,以及接收端是否需要排隊,再決定服務與組合方式。
當網路、資料庫、容器與訊息服務逐漸增加,每套環境都靠 Console 手動建立,很容易出現遺漏與設定差異。
下一篇會進入 Infrastructure as Code(IaC,基礎設施即程式碼),看看如何共用架構定義、明確管理環境差異,並讓每次變更都能審查與追蹤。