iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
自我挑戰組

30 天的 SAA 學習筆記系列 第 16 篇

Day 16 - 解耦與無伺服器整合 SQS:佇列解耦的基本應用場景

  • 分享至 

  • xImage
  •  

Day 15 處理的是使用者怎麼連進系統。連進來之後,系統內部的服務也會互相呼叫,這一篇處理的是這一段。

以電視節目投票為例:前端 EC2 每收到一票,就直接寫入資料庫,等資料庫寫完才回應使用者。幾分鐘內幾十萬張票送進來時,資料庫寫不及,前端的請求等到逾時就失敗,這一票沒有被記錄。增加前端 EC2 也沒有用,前端變多,送到資料庫的寫入只會更多;資料庫本身也無法在幾分鐘內擴充,Day 12 的 read replica 只分擔讀取,不分擔寫入。

問題出在前端必須等資料庫寫完,才能回應使用者。所以資料庫慢,使用者就跟著等;資料庫停擺,前端也跟著停擺。訊息佇列要解決的,就是讓前端不必等資料庫。


📨 訊息佇列(message queue)

訊息佇列是放在兩個服務之間的暫存區:

  • 送出工作的一方稱為 Producer,把工作寫成一則訊息放進佇列,放完就繼續做自己的事
  • 處理工作的一方稱為 Consumer,依自己的速度從佇列取出訊息處理
  • 訊息在被處理完之前,一直保存在佇列裡

套回投票:前端 EC2 是 Producer,每一票是一則訊息;另外準備一組 worker EC2 當 Consumer,負責把票寫進資料庫。

https://ithelp.ithome.com.tw/upload/images/20260930/20150978M7BpZEBRym.jpg

訊息佇列的設計目的有三個:

目的 說明 投票的例子
解耦(decoupling) Producer 和 Consumer 不直接連線,一方變慢或停機,另一方照常運作 資料庫停擺時,前端照常收票;訊息留在佇列裡,等 worker 恢復再寫入
緩衝尖峰 進來的速度超過處理速度時,多出來的訊息在佇列裡排隊,不會遺失 幾十萬票在佇列裡排隊,worker 依資料庫承受得了的速度寫入
非同步處理 Producer 不用等工作處理完成就能回應 票放進佇列就回應使用者,不必等資料庫寫完

🔀 Amazon SQS

Amazon SQS(Simple Queue Service)是 AWS 的全受管訊息佇列服務:不需要架設或維護任何伺服器,佇列容量自動擴展,訊息量沒有上限。

SQS 有兩項基本規則:

  • 訊息由 Consumer 主動向 SQS 取出(pull),SQS 不會主動推送
  • 訊息預設保留 4 天,最長可設 14 天;超過保留時間還沒被處理,訊息會被刪除

後面各段依序介紹以下功能:

功能 適用情境 運作方式
⏱️ visibility timeout worker 處理到一半停機,訊息不能遺失,也不能被同時重複處理 訊息被取出後先隱藏,處理完主動刪除;沒刪除就重新出現
📬 Standard 與 FIFO 有些工作要求照順序處理、不能重複 兩種佇列:Standard 吞吐量高但不保證順序;FIFO 保證順序、不重複
🔁 Long polling 佇列常常是空的,worker 一直取到空回應,浪費 API 費用 worker 取訊息時等待最多 20 秒,有訊息才回應
📈 自動增加 worker 尖峰時佇列越排越長,固定數量的 worker 消化不完 依佇列長度讓 Auto Scaling 增減 worker
📣 SNS fan-out 一則訊息要給多個系統各自處理,但 SQS 的訊息被取走就刪除 SNS 把一則訊息複製給多個 SQS 佇列

⏱️ Worker 處理到一半停機:visibility timeout

Worker 從佇列取出一則訊息後,可能在處理途中停機。SQS 必須決定,訊息被取出的當下要怎麼處理它。兩種單純的做法都有問題:

做法 結果
取出就刪除 worker 處理到一半停機,訊息已經不在佇列裡,工作也沒完成,這一票遺失
取出後照樣留在佇列,其他 worker 也看得到 同一則訊息同時被好幾個 worker 取走,一票被算兩次,造成重複處理

SQS 採用兩者之間的做法,稱為 visibility timeout:

  • 訊息被取出後先隱藏一段時間(預設 30 秒),這段期間其他 worker 取不到,避免重複處理
  • worker 處理完,主動呼叫 DeleteMessage,訊息才從佇列刪除
  • worker 沒有刪除(例如停機),時間到訊息重新出現,交給其他 worker 重試,避免遺失

https://ithelp.ithome.com.tw/upload/images/20260930/20150978K7Txpy41ue.jpg

這個設計的取捨是:寧可重複處理,也不要遺失。重新出現的訊息可能已經被處理過一部分,所以 consumer 必須做到同一則訊息處理兩次,結果也不會出錯,稱為 idempotent。例如用每一票的編號當唯一鍵,第二次寫入時發現編號已存在就略過。

另外兩種情況的配套:

情況 做法
處理時間本來就比 visibility timeout 長 把佇列的 visibility timeout 調到比最長處理時間長;處理時間變化大時,worker 在處理中呼叫 ChangeMessageVisibility 延長。上限都是 12 小時。沒有調整的話,處理還沒完成訊息就重新出現,被其他 worker 重複處理
一則訊息一直處理失敗、反覆出現 設定 maxReceiveCount,被取出的次數超過上限就移到 Dead-Letter Queue(DLQ),留給人工檢查原因,避免無限重試

📬 Standard 還是 FIFO

Standard FIFO
順序 盡量照順序,不保證 嚴格照順序(同一個 MessageGroupId 內)
重複 至少一次,可能重複 剛好一次(5 分鐘內同一個 deduplication ID 會被去重)
吞吐量 幾乎無上限 每秒 300 則,batch 可到 3,000
什麼時候用 絕大多數情況 順序不能錯(同一個帳戶的交易)、或不能重複(扣款)

預設選 Standard。只有明講「順序」或「不能重複」才換 FIFO,而且要接受吞吐量的天花板。FIFO 佇列的名字一定以 .fifo 結尾。


🔁 Long polling

預設是 short polling:worker 問一次,有就給、沒有就回空的,隔一下再問。佇列常常是空的時候,這樣一直問一直空,白繳 API 費用。

ReceiveMessage(WaitTimeSeconds = 20)
→ 有訊息:立刻回
→ 沒訊息:在那邊等,最多 20 秒,等到有或等到超時才回

空回應少很多,訊息到了也更快被拿走。實務上幾乎都開;「減少 API 呼叫次數、降低成本」指的就是它。


📈 佇列變長時自動加 worker

Worker 放在 Auto Scaling group 裡,但用 CPU 當指標不對——worker 在等訊息的時候 CPU 是低的,可是佇列可能已經堆了十萬則。正確的指標是佇列長度:

CloudWatch metric:  ApproximateNumberOfMessagesVisible(佇列裡等著被拉的訊息數)
自訂 metric:        每台積壓 = 訊息數 ÷ 目前 worker 台數
Target tracking:    讓「每台積壓」維持在目標值(例如 100)
→ 佇列變長 → 每台積壓上升 → ASG 加 worker;消化完 → 減回去

📣 SNS:一則訊息要給很多人

SQS 是一對一:一則訊息被一個 worker 拉走、刪掉,就沒了。一筆訂單成立後,庫存、出貨、通知三個系統都要各自處理——SQS 做不到,第一個拉走的就把它刪了。

Amazon SNS 是 pub/sub:發到一個 topic,所有訂閱者各收一份。訂閱者可以是 SQS、Lambda、HTTP endpoint、email。SNS 自己不存訊息,推出去就結束。

兩個接起來就是 fan-out:

https://ithelp.ithome.com.tw/upload/images/20260930/20150978AM076Uxw2v.jpg

SNS 負責「一份變多份」,每個 SQS 各自排隊、各自重試、各自有 DLQ。哪個下游掛了,訊息在它自己的佇列裡等,不影響別人。SNS 還能設 filter policy,讓某個佇列只收特定屬性的訊息(例如只收金額大於一萬的訂單)。


🆚 SQS、SNS、Kinesis

誰去拿 一則訊息幾個人收 讀過還在嗎 用在
SQS Consumer 自己拉 一個 刪了就沒了 工作佇列、削峰、解耦
SNS SNS 推出去 全部訂閱者 不保存 通知、fan-out
Kinesis Consumer 自己拉 多個 consumer 各自讀同一份 保留 1–365 天,可以重播 即時串流、分析(Day 20)

「多個系統要各自處理同一個事件」是 SNS + SQS;「即時分析」「重播」「順序且高吞吐」是 Kinesis;其他大多是 SQS。


📌 補充

主題 說明
訊息大小 上限 1 MiB(2025 年 8 月從 256 KB 調高,舊教材和考古題還常寫 256 KB)。更大的(例如圖片、影片)放 S3,訊息裡只帶 S3 的路徑
誰能送、誰能拉 靠 SQS 的 access policy(resource-based)。SNS 要能送進 SQS,佇列的 policy 要允許那個 topic
FIFO 配 FIFO SNS 也有 FIFO topic,fan-out 要保順序時,FIFO topic 接 FIFO queue
Amazon MQ 給已經在用 RabbitMQ、ActiveMQ 的既有系統搬上雲,不想改程式碼時用。新系統直接用 SQS/SNS
Lambda 當 consumer 不用自己開 worker,Lambda 直接接 SQS 當觸發來源,處理完自動刪(Day 18)

✅ 小結

概念 說明
解耦 佇列放中間,producer 和 consumer 各跑各的速度,尖峰堆在佇列裡不遺失
Visibility timeout 訊息被拉走後先隱形,處理完要主動刪;沒刪就重新出現給別人拉
Idempotent 至少一次的代價:同一則處理兩次不能出事
DLQ 反覆失敗的訊息丟進去,不卡住主佇列
Standard vs FIFO 預設 Standard;要順序或去重才 FIFO,吞吐量有天花板
Fan-out SNS 一份變多份,每個下游各自一個 SQS

摘要:SQS 把「來的速度」和「處理的速度」拆開。訊息被拉走不會消失,要主動刪,沒刪就會再出現——所以 consumer 要 idempotent。一則訊息要給多個系統,前面加 SNS 做 fan-out。


🧠 AI 出題

問題 1

某影音平台用 Amazon SQS standard queue 派送轉檔工作,worker 是 Auto Scaling group 裡的 EC2。每個轉檔工作需要 8~12 分鐘,queue 的 visibility timeout 維持預設的 30 秒。最近發現同一支影片常被轉檔 3~4 次,運算費用明顯上升,但沒有任何 worker 當機或回報錯誤。公司希望以維運負擔最低(LEAST operational overhead)的方式解決這個問題。

解決方案架構師應該怎麼做?

  • A. 改用 FIFO queue 並啟用 content-based deduplication,讓每則訊息只會被一個 worker 處理一次
  • B. 把 queue 的 visibility timeout 調高到比最長處理時間多一些,例如 15 分鐘
  • C. 在 worker 啟用 long polling,把 WaitTimeSeconds 設為 20 秒,減少重複收到同一則訊息
  • D. 設定 dead-letter queue 並把 maxReceiveCount 設為 1,讓訊息第二次被收到時直接移走

問題 2

某電商的訂單服務每成立一筆訂單,就要通知庫存、出貨與詐欺偵測三個系統各自處理。任何一個下游系統停機維護數小時的期間,它的訊息都必須保留,恢復後再補處理,而且不能影響其他系統;詐欺偵測系統只需要處理金額超過 NT$30,000 的訂單。公司希望以維運負擔最低(LEAST operational overhead)的方式設計。

哪一個方案最符合需求?

  • A. 建立一個 SQS queue,三個下游系統的 worker 都從這個 queue 拉取訂單訊息,各自完成自己的處理
  • B. 建立 SNS topic,三個系統各自以 HTTPS endpoint 訂閱,由 SNS 直接把訂單事件推送給它們
  • C. 把訂單事件寫入 Kinesis Data Streams,三個系統各自用 consumer 讀取,詐欺系統在程式中自行過濾金額
  • D. 建立 SNS topic 讓三個 SQS queue 訂閱,並在詐欺偵測 queue 的訂閱上設定金額 filter policy

問題 3

某公司的報表服務由 SQS queue 接收產生報表的請求,worker 放在 Auto Scaling group 中,目前以平均 CPU 70% 為目標做 target tracking。請求量難以預測,常在幾分鐘內湧入上萬筆。每台 worker 一次處理一則,每則約 2 秒,大部分時間在等資料庫查詢,所以 CPU 一直很低,queue 積壓了數十分鐘都沒有加機器。公司要求每則請求在 10 分鐘內處理完,同時不讓 worker 在閒時空轉。

哪一個做法最符合成本效益(MOST cost-effective)?

  • A. 發布「可見訊息數 ÷ worker 數」的自訂 metric 做 target tracking,目標設為 300
  • B. 把 target tracking 的 CPU 目標從 70% 調降到 20%,讓 CPU 只要稍微上升就開始加 worker
  • C. 把 Auto Scaling group 的 min 調高到尖峰所需的台數,確保隨時都有足夠的 worker 消化積壓
  • D. 建立排程動作,依過去三個月的紀錄,在最常出現尖峰的時段預先把 worker 的台數調高到尖峰所需的數量

問題 4

某網路銀行把帳戶交易事件送進 SQS,由 worker 寫入帳務系統。同一個帳戶的交易必須嚴格依照發生順序處理,而且不能重複入帳;不同帳戶之間沒有順序要求。系統約有 50 萬個帳戶,尖峰時每秒約 2,000 筆交易,以 batch 方式送出。公司希望在滿足上述要求的前提下,讓多個 worker 盡量平行處理,縮短整體延遲。

解決方案架構師應該怎麼設計?

  • A. 使用 standard queue,在每則訊息中加上序號,由 worker 依序號自行重新排列後再入帳
  • B. 使用 FIFO queue,所有訊息都使用同一個 MessageGroupId,確保全部交易嚴格依序處理
  • C. 使用 FIFO queue 並啟用高吞吐量模式,以帳戶 ID 作為 MessageGroupId、交易 ID 作為去重 ID
  • D. 使用 standard queue 並把 visibility timeout 調長,避免同一筆交易被兩個 worker 同時處理

問題 5

某物流公司的 SQS standard queue 偶爾會收到格式錯誤的訊息。worker 每次處理都失敗、不會刪除它,訊息就在 visibility timeout 到期後反覆出現,連續好幾天佔用 worker 的處理時間。公司要求:這類訊息重試數次後就不要再佔用 worker;失敗的訊息要保留下來供工程師調查;出現這類訊息時要即時通知值班人員。

哪兩項做法的組合最符合需求?(選擇兩項)

  • A. 把 queue 的訊息保留期限從預設的 4 天縮短為 1 小時,讓處理失敗的訊息很快就自動過期
  • B. 設定 redrive policy 到 dead-letter queue,maxReceiveCount 設為 5
  • C. 修改 worker,處理失敗時就直接呼叫 DeleteMessage 刪除訊息,並把錯誤內容與訊息本文寫進應用程式日誌
  • D. 對 dead-letter queue 的可見訊息數建立 CloudWatch alarm,觸發 SNS 通知值班人員
  • E. 把 queue 的 visibility timeout 設為 0,讓處理失敗的訊息立刻重新出現,加快整體的重試節奏

💡 解答

1. B

visibility timeout 只有 30 秒,但轉檔要 8~12 分鐘:worker A 還在處理,30 秒一到訊息就重新出現,被 worker B 拉走,再過 30 秒又被 worker C 拉走……所以同一支影片被轉好幾次,而且沒有任何錯誤。把 visibility timeout 調到比最長處理時間多一些,處理中的訊息就不會重新出現。只要改一個 queue 屬性,維運負擔最低。如果處理時間的變化很大,也可以讓 worker 在處理過程中定期呼叫 ChangeMessageVisibility 延長期限。

A 是最常見的誤解:FIFO 的去重只針對「發送端在 5 分鐘內重複送出同一則訊息」,被接收後逾時重新出現的訊息,一樣會再被處理一次。C 的 long polling 減少的是空回應和 API 呼叫次數,跟訊息逾時後重新出現無關。D 會讓每一則訊息在 30 秒後被當成失敗,直接移到 DLQ,連正常的工作都沒辦法重試。

2. D

這是 SNS 加 SQS 的 fan-out:SNS 把每筆訂單事件複製給三個 SQS queue,每個系統有自己的 queue。某個系統停機時,訊息就在它自己的 queue 裡等(預設保留 4 天),恢復後再補處理,不影響其他系統。詐欺偵測 queue 的訂閱上設定 filter policy,只有金額超過 30,000 的訂單會送進去,詐欺系統不用自己過濾。全部都是受管服務的設定,維運負擔最低。

A 是誤解:一則 SQS 訊息只會被一個 consumer 拉走並刪除,三個系統會互相搶訊息,每筆訂單只有一個系統處理到。B 的問題在停機期間:SNS 自己不保存訊息,推送給 HTTPS endpoint 失敗時只能依 delivery policy 重試有限的次數,重試用完就丟棄(除非另外替訂閱設定 DLQ);系統恢復後也沒辦法依自己的速度慢慢補處理。C 可以做到多個 consumer 各自讀取,但要自己規劃 shard、管理每個 consumer 的讀取進度,詐欺系統還要自己寫過濾邏輯,維運負擔明顯比 D 高。

3. A

這個服務的瓶頸在積壓的訊息,不是 CPU,所以擴展要看「每台 worker 分到多少積壓」。每台一次處理一則、每則 2 秒,一台 10 分鐘能處理 300 則,把目標設為每台 300 則,就能保證積壓的訊息在 10 分鐘內處理完。積壓消化完後這個 metric 會下降,Auto Scaling group 就會縮回去,閒時不會有多餘的 worker 空轉。

B 仍然看 CPU,而這個 worker 大部分時間在等資料庫,積壓再多 CPU 也不會明顯上升,調低目標只會讓擴展時機更不穩定。C 能消化尖峰,但尖峰所需的台數要全天維持,違反「不讓 worker 在閒時空轉」。D 的前提是尖峰有固定時段,但題目說請求量難以預測,排程對不準實際的尖峰。

4. C

FIFO queue 的順序保證是以 MessageGroupId 為單位:同一個 group 的訊息嚴格依序交付,前一批還沒處理完(刪除)之前,這個 group 不會再交出新的訊息;不同 group 則可以同時交給不同的 worker。以帳戶 ID 當 group,就是「同一帳戶依序、不同帳戶平行」,完全對應需求;再啟用高吞吐量模式,以提高整個 queue 的吞吐上限。去重 ID 設為交易 ID,5 分鐘內重送的同一筆交易會被丟掉。不過 5 分鐘以外的重複還是要靠入帳程式本身具備冪等性(idempotent)。

A 等於在應用程式裡自己重做 FIFO:standard queue 可能重複交付,也可能晚到,worker 要等多久才能確定前一筆已經到了,很難做對。B 能保證順序,但所有交易都在同一個 group 裡,同一時間只有一個 worker 能拿到訊息,其他 worker 只能等,違反「盡量平行」。D 是誤解:standard queue 的至少一次交付可能讓同一則訊息被送出兩次,拉長 visibility timeout 只能避免逾時重送,沒辦法消除重複,也完全沒有處理順序。

5. B、D

redrive policy 讓一則訊息被接收超過 maxReceiveCount 次後,自動移到 dead-letter queue,不再佔用 worker;訊息在 DLQ 裡保留著,工程師可以慢慢調查,修好後還能 redrive 回原本的 queue。對 DLQ 的可見訊息數設 CloudWatch alarm,只要大於 0 就透過 SNS 通知值班人員。兩項合起來,三個要求都滿足了。

A 會讓所有來不及處理的正常訊息也一起過期,而且失敗的訊息會直接消失,沒辦法調查。C 雖然不再佔用 worker,日誌裡也留下了訊息內容可以調查,但只要失敗一次就刪除,連暫時性的錯誤都沒有重試的機會;修好之後也不能直接 redrive 重新處理,而且沒有人會被即時通知。E 只會讓失敗的訊息更頻繁地回來,更快佔滿 worker,方向相反。


上一篇
Day 15 - 高可用與流量入口 Route 53:DNS 路由策略與 CloudFront 加速
下一篇
Day 17 - 解耦與無伺服器整合 EventBridge:事件路由服務入門
系列文
30 天的 SAA 學習筆記 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言