長時間執行任務 (Manage Long-Running Tasks)
核心問題
抓資料只要 100ms,但產生 PDF 報告 / 影片轉碼 / 批量寄信要 30 秒到數分鐘。同步處理下:
- Web server / LB 多有 30~60 秒 timeout → 請求可能來不及完成
- 用戶盯著載入圖示,不知進度 → 重試 → 製造更多重複工作
Web server 變成輕量請求路由器:驗證 → 推進 queue → 立刻回傳 job ID(毫秒)。獨立的 worker pool 做真正繁重的工作。
解法架構
Client ──► Web Server ──push──► Queue ──pull──► Worker Pool
│ (毫秒回 job ID) │
▼ ▼
DB (job: pending) ◄──update status── (processing → completed/failed)
完整流程:
- Web server 驗證請求,DB 建立
pendingjob 記錄 - 推訊息進 queue(只放 job ID,不放完整資料)
- 立刻回傳 job ID 給 client
- Worker 從 queue 拉訊息 → 從 DB 抓 job 詳情
- 更新狀態為
processing→ 執行工作 → 存結果(檔案 S3、metadata DB) - 更新為
completed/failed
Queue 掛 → web server 照收,job 在 DB 堆積;worker 崩 → job 留 queue 等新 worker;DB 出問題 → worker 持續重試。
取捨
| 得到 | 失去 |
|---|---|
| 快速回應(ms 回 job ID,不 timeout) | 系統複雜度(queue、worker、狀態追蹤) |
| 獨立擴展(web 用便宜機,worker 用 GPU 機) | 最終一致性(回傳時工作還沒完成) |
| 故障隔離(一台 worker 崩不拖垮 API) | Job 狀態追蹤基礎設施(增加 DB 負載) |
| 更好的資源利用 | 監控 overhead(queue 深度、worker 健康、失敗率) |
新故障模式:queue 滿了?poison message 一直讓 worker 崩?何時放棄重試?
兩個核心元件
Message Queue(需持久化 durable)
| 選項 | 特點 | 注意 |
|---|---|---|
| Redis + Bull/BullMQ | startup 首選,設定簡單,自動重試/延遲/優先權 | memory-first,硬崩可能丟 job |
| AWS SQS | 全託管、保證送達 | 1MB 訊息上限(所以只傳 ID);大規模成本高 |
| RabbitMQ | 複雜路由、企業久經考驗 | 自行架設、運維負擔 |
| Kafka | 超大流量、可重播、partition 內保序 | event streaming 技術棧首選 |
Worker Pool
| 運行方式 | 適合 | 限制 |
|---|---|---|
| 一般 server | 長時間 job、完全控制、易除錯 | 閒置也要付費 |
| Serverless(Lambda) | 流量高峰不穩定 | 15~60 分執行上限、cold start、本地儲存小 |
| Container(K8s/ECS) | 中間方案,自動擴展 | 比 serverless 複雜 |
沒特殊理由就選 Kafka(或你熟的 queue),worker 用一般 server process。重點是展示關注點分離,不是辯論 queue 技術優劣。
何時主動提出(識別信號)
- 慢操作關鍵字:影片轉碼、圖片處理、PDF 產生、批量寄信、資料匯出 → 立刻說「這太慢,我回傳 job ID 非同步處理」
- 數字不合理:「每天處理 100 萬張圖、每張 10 秒」→ 大聲算:12 張/秒 × 10 秒 = 至少 120 worker,不能跑在 web server 上
- 不同操作需不同硬體:簡單 API + GPU 工作(ML inference / 影片)混在一起
- 規模與故障:「server 處理到一半崩潰怎麼辦」「如何處理 10× 流量」
你應該是那個指出操作太慢的人,而不是等面試官問。展示你在他們提顧慮前,就在想 timeout、資源利用率、用戶體驗。
常見 Deep Dive(五大故障處理)
另一台 worker 重試。靠心跳機制定期回報 worker 還活著,沒回報就假設掛了並重試。心跳間隔:太長 → 崩潰延遲久;太短 → 訊息多、可能誤判執行中的 job(如 GC pause)。SQS 用 visibility timeout、RabbitMQ 用 heartbeat、Kafka 用 session timeout。大多 10~30 秒起步。
不處理 → 無限重試浪費資源;poison message 可能讓整個 worker 群掛掉。失敗達一定次數(通常 3~5)後移到 DLQ 隔離,健康工作繼續。DLQ 變成需人工調查的 batch。監控 DLQ —— 持續增加通常代表有 bug。
不去重 → 重複工作,甚至發三封信/刷三次卡。要求唯一識別碼(user ID + action + timestamp 取整);開工前先查 key 是否已存在,有就回現有 job ID。工作本身也要冪等(重跑安全)。
Queue 無限成長、記憶體爆炸、新 job 被拒。Backpressure:設 queue 深度上限,太深就拒新 job 回「系統忙碌中」。根據 queue 深度自動擴展 worker(關鍵 metric 是 queue 深度,不是 CPU —— 等 CPU 飆高 queue 早就積了)。
長 job 卡住短 job(head-of-line blocking)。按 job 類型/預期時間分 queue:fast(多 worker、小機)vs slow(少但強的 worker、大機)。無法預測就先進 fast,超時移 slow。
簡單鏈式:每個 worker 完成前把下一個 job 加進 queue,帶完整 context(每步可獨立重試)。複雜分支/平行:用 workflow orchestrator(AWS Step Functions / Temporal / Airflow)—— 只在依賴真的複雜時才用。
常見系統範例
| 系統 | async 任務 |
|---|---|
| YouTube | 轉碼多解析度、產縮圖、提字幕、內容審核 |
| 多尺寸、濾鏡、metadata、圖片識別加標籤、feed fan-out | |
| Robotaxi | 乘車配對(評估可用性、算路線、定價)、位置更新 |
| Stripe | 詐欺偵測、3D Secure、webhook、延遲清算 |
| Dropbox | 病毒掃描、搜尋索引、產生預覽、多裝置同步 |
Related Notes
- 10-Design-Patterns/02-Scaling-Writes — Write Queue / Load Shedding(同樣用 queue 解耦)
- 09-Messaging-Coord/01-Data-Pipeline — Kafka、DLQ、exactly-once vs at-least-once
- 01-Networking/07-Fault-Handling — 重試退避、冪等性、熔斷器
- 10-Design-Patterns/04-Handling-Large-Blobs — 上傳後觸發 async worker 處理
- 10-Design-Patterns/05-Real-time-Updates — job 完成時透過 WebSocket/push 通知
- 10-Design-Patterns/Practice-Design-Patterns