這篇要回答的問題:我需要做 async 工作,選 cron / queue / event-driven 哪個?worker 要不要跟 web 同 process 跑、要不要獨立 deployment?這層沒有 RFC 標準,但有可以照抄的常見 pattern 與取捨。

3 分鐘結論

  • 6 個常見 scenario,每個有「該選哪條 path + 該怎麼部署」具體答案
  • 重疊區(cron polling vs delayed event 都做得到「30 分鐘催繳」)trade-off 看流量 / 及時性 / debug
  • 部署 4 層拓樸:mono → process 分 → deployment 分 → 物理機分。不要預先升級,痛了再升
  • worker 跟 web 一定要分 process,分 deployment 是 production 標配

這篇假設你知道

  • #15 async 原語(cron / queue / event-driven 三條 path)
  • 大致知道什麼是 K8s deployment / pod(不熟也讀得起來,§9 會帶)
  • 不熟下面這些詞時 → #16 EDA 名詞速查deployment topology / HPA / queue depth / K8s CronJob

Production EDA 系列互補關係而非同系列:那系列焦點是 reliability,這篇焦點是 architecture decision。完整 runnable demo:tools3455147/mq-event-driven-demo


1. 三個原語為什麼會打架

#15 講了三個原語:

原語trigger典型用途
Queue / Job queue事件發生(producer 推)「使用者按按鈕後在背景處理」
Event-driven事件發生(多個 consumer)「某事發生 → 多個下游同時做反應」
Cron / Scheduler時間到(固定週期)「每天/每分鐘/每小時定時做某件事」

問題不在「各自做什麼」— 而在於很多場景同時可以用兩三個解。

例如「下單 30 分鐘後催繳」:

  • Cron 路線:每分鐘掃 orders 表,找 created_at > 30 min ago AND status='pending' 的,發 reminder。
  • Delayed event 路線:下單時 schedule_event(fire_at=NOW()+30min),到時間 trigger handler 發 reminder。

兩個都做得到「在下單 30 分鐘後寄一封信」。但兩個的 trade-off 不同,沒有「標準」答案,要看:

  • 及時性要求:精準 30 分鐘還是「30 分鐘左右就好」?
  • 資料量:每天 100 筆下單還是 100 萬筆?
  • 故障容忍:cron job 漏跑一次能不能補?
  • debug 難度:團隊熟 cron 還是 event-driven?

下面 §3-§8 走 6 個常見場景,每個都過一次 decision tree + 對應的部署拓樸。


2. Newcomer primer:先把幾個概念對齊

Trigger 種類

Trigger觸發條件範例
Request-basedHTTP request 進來controller 路由
Event-based業務事件發生order.created
Time-based時間點到凌晨 3 點對帳
External signal外部 webhook / queue 訊息進來Stripe 付款通知

這 4 種 trigger 是正交的。同一個系統會混用:HTTP 入口(request-based)、訂單 handler(event-based)、每日報表(time-based)、Stripe webhook(external signal)。

Process vs Worker vs Deployment

新人常把這 3 個混在一起。其實是 3 層:

Deployment(部署單位)
  ├─ 一個 deployment 通常跑 N 個 pod / 容器
  ├─ 一個 pod 跑一個 process(嚴格說可多個但通常 1:1)
  └─ 一個 process 跑多個 worker / thread / async loop

範例:
  「Web deployment」    = 5 pods × 1 process × 多 thread 處理 HTTP
  「Worker deployment」 = 3 pods × 1 process × 內含 outbox worker + N 個 handler consumer
  「Cron deployment」   = 1 pod × 1 process × cron scheduler loop(HA 場景跑 2 pod + leader lock)

「worker 跟 web 分離」這句話通常指 deployment 分離(不同 k8s deployment / 不同容器 / 不同物理機)。為什麼分離下面 §3 會講。

Async work 的成本面

「改成 async」不是免費。每換一條 async path 都要付的成本:

  • 複雜度:原本 1 行同步 call,變成 publish + handler + retry + dedup
  • 觀察性:debug 從一條 stack trace 變成跨多個 process / log
  • 部署:多一個 deployment 要 build / monitor / on-call
  • 冪等性:handler 一定要冪等(broker 至少一次交付)
  • 延遲:從即時變成「秒級到分級延遲」

判斷一個場景該不該改 async,是看「我需要這個 benefit、付得起這個成本嗎」,不是「大家都用 async 所以我也要」。詳細的 EDA 取捨見 #18 MVC vs Event-driven


3. Scenario A:下單寄信 — event-driven 經典場景

最常見的 async 場景,也是 EDA 教科書範例。

HTTP POST /orders
    │
    ↓
controller 寫 orders 表
    │
    ↓
publish event(order.created)        ← 立刻 return 201
    │
    └─→ broker
           ├─→ email handler   (寄確認信)
           ├─→ inventory handler (扣庫存)
           └─→ recommender handler (通知推薦系統)

為什麼選 event-driven 而不是 cron 或 queue

  • cron:不適合,因為「下單後寄信」不是時間驅動的,是事件驅動的
  • queue (單 consumer):可以,但有多個下游時演進成 fan-out 比較順
  • event-driven (multi-consumer):✅ 4 個下游各看各的、彼此隔離

部署拓樸:worker 為什麼一定要跟 web 分離 process

新人常想:「我能不能在 controller 內 setImmediate(() => sendEmail(...)) 就好?」

不行,至少不該。原因:

  1. process 跟著 web 重啟就丟工作:web deployment rolling update 時,那台 pod 內 in-memory pending 的工作會直接消失。
  2. OOM 一起死:handler 跑歪吃光 memory,web request 也跟著 503
  3. 回壓打不開:流量瞬間飆 10 倍,handler 撐不住會把 web request thread 全占光。

正確姿勢:

┌──────────────┐    publish     ┌─────────────┐
│ Web deploy   │ ─────────────→ │   Broker    │
│ (5 pods)     │                └─────────────┘
└──────────────┘                       │
       ↑                                │ subscribe
   HTTP traffic                         ↓
                                ┌──────────────┐
                                │ Worker deploy│
                                │ (3 pods)     │
                                │ - email      │
                                │ - inventory  │
                                │ - recommender│
                                └──────────────┘

兩個 deployment 各自 scale、各自 deploy、各自 on-call。web pod 死掉 broker 還在;worker pod 死掉 web 還在;worker OOM 不會卡 HTTP。

💡 小流量場景例外:MVP 或內部 tool 流量極低時,跑「web + worker 同 process」(用 Laravel php artisan queue:work 開背景 daemon 或 Node 的 setInterval 同時跑)是 OK 的,省一個 deployment 的維護成本。等流量起來或 incident 開始撞牆再分

跟本系列 demo 的對應

mq-event-driven-demo/demo/place-order + 4 個 handler 就是這個 scenario 的完整實作。詳細 EDA reliability 補洞 ⸺ producer outbox、consumer dedup、retry/DLQ、audit ⸺ 都在 Production EDA 系列 講。


4. Scenario B:下單 30 分鐘催繳 — delayed event vs cron polling

「使用者下單但沒付款,30 分鐘後寄催繳信」這個需求兩條路線都做得到。

路線 1:Cron polling

每分鐘掃一次 orders 表:

SELECT event_id FROM orders o
LEFT JOIN payment_reminders pr ON pr.order_event_id = o.event_id
WHERE o.status = 'pending'
  AND o.created_at < NOW() - INTERVAL '30 minutes'
  AND pr.order_event_id IS NULL;
-- 對每筆 INSERT payment_reminders (order_event_id, mode='cron-poll')

payment_reminders 表 PK on order_event_id 確保「同 order 最多寄一次」。

路線 2:Delayed event

下單時 schedule 一筆 delayed event:

// 下單成功後
await delayed.schedule(
  randomUUID(),
  'payment_reminder',
  { order_event_id: order.event_id },
  new Date(Date.now() + 30 * 60_000),  // 30 分鐘後 fire
);

背景 worker 每秒掃 delayed_eventsWHERE fire_at <= NOW(),到時間執行對應 handler。

對比

維度Cron pollingDelayed event
及時性0~60 秒延遲(poll 週期)~1 秒延遲(poll 週期 1s)
DB 負擔每分鐘全表掃描 + JOININSERT + 索引查詢
資料量擴大時表越大 cron 越慢INSERT/查詢還是 O(待處理量)
取消邏輯orders.status='paid' 即可,下次 cron 不會挑到要顯式 cancel delayed event 或 handler 內判斷 status
失敗復原下次 cron 自動再來worker retry / DLQ 機制要自己加
理解門檻低(一行 SQL)中(delayed_events 表 + worker)
業務邏輯位置SQL WHERE clause程式碼(schedule + handler)

怎麼選

實務上的 rule of thumb:

  • 流量小 + 沒及時性需求 → cron polling(簡單、debug 容易)
  • 資料量大 + 30 分鐘要準 → delayed event(避免全表掃)
  • 取消頻繁 → cron polling(status flag 改完下次自動跳過比較直覺)
  • 要橫向擴展 → delayed event(FOR UPDATE SKIP LOCKED 多 worker 不撞)

在 demo 重現

# Cron polling 路線
curl -X POST 'http://localhost:8000/demo/payment-reminder?mode=cron-poll&stale=3'
 
# Delayed event 路線
curl -X POST 'http://localhost:8000/demo/payment-reminder?mode=delayed-event&stale=3'

兩條 path 都會建 3 筆「30 分鐘前下單但未付款」的 order,跑對應的 reminder,最後寫到 payment_reminders 表。業務結果一樣:每 order 寄一次。差別在「怎麼寄的」。

部署拓樸

Cron polling 路線:               Delayed event 路線:

┌──────────────┐                  ┌──────────────┐
│ Web          │                  │ Web          │
└──────────────┘                  └──────────────┘
                                          │
                                  schedule │
                                          ↓
┌──────────────┐                  ┌──────────────┐
│ Cron worker  │                  │ Delayed-events│
│  - 每分鐘掃   │                  │  worker      │
│    orders 表  │                  │  - 每秒掃 due │
└──────────────┘                  └──────────────┘

兩個 worker 都是「跟 web 分離的獨立 deployment」。差別在 cron worker 單 instance 通常就夠(除非要 HA leader election),delayed-events worker 可以多 instance 水平擴展FOR UPDATE SKIP LOCKED 防爭搶)。


5. Scenario C:每日對帳 — cron 的甜蜜點

「每天凌晨 3 點產出昨日訂單報表」這類需求:

  • 時間驅動:固定凌晨 3 點,跟事件無關
  • 單次執行:一次跑完就好,不需要 fan-out
  • 可重跑:當天有問題重跑一次,業務上沒副作用

這是 cron 的標準場景。沒道理用 event-driven 或 delayed event 解。

部署拓樸:獨立 cronjob workload

K8s 有 CronJob resource type 專門做這個。每次到時間 K8s spawn 一個 pod 跑、跑完 pod 終止。比常駐 worker 省資源。

# 概念示意(不是直接可跑的 YAML)
apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-report
spec:
  schedule: "0 3 * * *"    # 每天凌晨 3 點
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: report
            image: my-app:latest
            command: ["node", "scripts/daily-report.js"]
          restartPolicy: OnFailure

不用 K8s 的話:

  • Docker 環境:cron daemon container 或 supercronic
  • 傳統 VM:/etc/cron.d/ 寫 crontab
  • Laravel:schedule:run 每分鐘跑一次當 dispatcher

冪等性是 hard requirement

cron job 會因為各種原因重跑:手動重跑、K8s 重新調度、上游補資料、bug 修完要重算昨日報表… 所以 cron job 的 SQL 一定要冪等

-- 不要:INSERT INTO daily_summary (...) VALUES (...);
-- 重跑會撞 PK / 產生重複 row
 
-- 要:用 ON CONFLICT 或 UPSERT
INSERT INTO daily_summary (summary_date, order_count, user_count, generated_at)
SELECT CURRENT_DATE, COUNT(*), COUNT(DISTINCT user_id), NOW()
FROM orders WHERE created_at::date = CURRENT_DATE
ON CONFLICT (summary_date) DO UPDATE SET
  order_count = EXCLUDED.order_count,
  user_count  = EXCLUDED.user_count,
  generated_at = EXCLUDED.generated_at;

詳細的「SQL 寫法跟冪等性」⸺ 看 Production EDA 系列篇 4a

在 demo 重現

# 觸發 daily-report job 立即跑一次(demo 不等真的凌晨 3 點)
curl -X POST 'http://localhost:8000/demo/daily-report?verify_idempotent=1' | jq
 
# 回應會帶 first_run / second_run,兩次跑出來 row_count 仍是 1
# {
#   "first_run": { "status": "ok", "duration_ms": 12.3 },
#   "daily_summary_today_row_count": 1,
#   "note": "daily-report job 寫 daily_summary 用 ON CONFLICT DO UPDATE — 重跑同一天最多一筆 row"
# }

6. Scenario D:外部 webhook 進來 — queue 當 buffer

第三方服務(Stripe / GitHub / Slack)發 webhook 到你的系統時,常見痛點:

  • 對方有 timeout(通常 2-5 秒),你回應慢就被認為失敗、被重送
  • 對方有 retry policy:你回 5xx 對方會重投,重複 webhook 你要 dedup
  • 對方流量是 burst(big sale 期間 Stripe 每秒幾百個 payment_completed),你下游 DB 撐不住

直覺寫法:

app.post('/webhooks/stripe', async (req, reply) => {
  await processPayment(req.body);      // ← 5 秒
  return { status: 'ok' };
});

跑 burst 時:

  1. 5 秒內 Stripe timeout
  2. Stripe 認定失敗,第二次重投
  3. 你 controller 第一次還沒處理完,第二次又進來
  4. DB 連線炸滿
  5. 開始 500,Stripe 認定全部失敗,第三次重投…
  6. 雪崩

解法:queue 當 buffer

Stripe ─→ POST /webhooks/stripe ─→ 立刻 publish to queue ─→ 立刻回 200
                                              │
                                              ↓
                                       Background worker
                                              │
                                              ↓
                                       processPayment(...)

webhook controller 變成「驗 signature → 推 queue → 回 200」,永遠 100ms 內回應。worker 在背景慢慢消化。

效果:

  • Stripe 收到 200 不會重投
  • worker 速度跟不上 → queue 累積,但 webhook 入口不雪崩
  • worker 跑 5 秒或 50 秒都不影響 webhook 入口

部署拓樸

跟 Scenario A 類似:webhook controller 在 web deployment,handler 在 worker deployment。差別是 queue 變成「流量 shock absorber」,不只是「fan-out 機制」。

為什麼這個沒寫 demo

webhook 場景跟「Scenario A 下單寄信」結構上一樣(HTTP → 推 queue → worker 跑)⸺ 只是 trigger 不同。本系列 demo 的 /events/:bus/events-outbox/:bus 就是這個 pattern 的最小骨架。/events-outbox/:busIdempotency-Key header 解 webhook 重送的 dedup ⸺ Production EDA 系列篇 3 Consumer 端冪等(規劃中)會講。

💡 Webhook 入口的 dedup 通常做兩層:HTTP 層用 Idempotency-Key / Stripe-Signature + nonce table 擋重送;handler 內 processed_events 表擋 broker 重投。雙保險。


7. Scenario E:批次 ETL — scheduled batch worker

「把交易表的資料每小時 aggregate 到報表表」這類需求:

  • 時間驅動(每小時)+ 批次處理(一次處理一段資料)
  • 跟 daily-report 像,但資料量大很多:不能在一個 transaction 裡跑完,要分批處理

解法:scheduled batch worker idiom

Cron 觸發 ─→ batch worker 啟動 ─→ 找 checkpoint(上次處理到哪)
                                       │
                                       ↓
                              loop:取下一批 N 筆 → 處理 → 更新 checkpoint
                                       │
                                       ↓ 處理完或時間到
                              退出,下次 cron 再來

關鍵設計:

  1. Checkpoint:記錄「上次處理到 created_at = X」,下次從那繼續
  2. 批次大小:每 batch N 筆(500、1000 等)避免單次 query 爆 memory
  3. 每 batch 一個 commit:失敗只回退本批
  4. 冪等性:用 ON CONFLICT 寫 aggregate 表,重跑某批不出錯
  5. 時間預算:跑超過某時間就退出,不要卡住下一輪 cron

在 demo 重現

# 「處理過去 7 天的 daily summary」每天一個 batch
curl -X POST 'http://localhost:8000/demo/batch-etl?n=7' | jq
 
# 重跑同條件:daily_summary_total_rows 仍 7(沒重複)
curl -X POST 'http://localhost:8000/demo/batch-etl?n=7' | jq

Demo 簡化版每批就是「處理一天」,production 真實 ETL 還會:

  • 分頁 checkpoint:表內按 id 或 timestamp 分頁;handler 跑完一頁更新 checkpoint
  • multi-worker 並行:用 FOR UPDATE SKIP LOCKED 多 worker 各撈不同 batch
  • 失敗 alert:某 batch 連續失敗 N 次就 alert,不要悄悄漏跑

跟 stream processing 的差別

Q:為什麼不直接用 Kafka Streams / Flink 做 stream processing?

A:兩個不同的時序模型:

Scheduled batchStream processing
觸發時間到才跑event 來就跑
latency分鐘到小時級秒以下
複雜度一個 SQL + checkpoint一個 streaming 框架 + state store
適合對帳、報表、不急real-time dashboard、詐欺檢測

對 latency 不敏感、能等下次 cron 跑的場景 ⸺ scheduled batch 簡單很多,不用引入 Flink / Kafka Streams 那套基礎設施。


8. Scenario F:Slack bot / 長時間 LLM task — event + delayed retry

「使用者 mention Slack bot,bot 呼叫 LLM 回答,可能要 30 秒」這類場景:

  • HTTP 同步呼叫不可行(Slack webhook 3 秒 timeout)
  • 純 event-driven 也不夠(如果 LLM 暫時 unavailable 要 retry)
  • 結果要 回到使用者上下文(同 channel、同 thread reply)

解法:event + delayed retry + correlation_id

Slack webhook ─→ controller 回 200 ASAP
                       │
                       └─→ publish event(slack_query, correlation_id=msg_id)
                                  │
                                  ↓
                          handler 跑 LLM
                                  │
                  ┌───────────────┼───────────────┐
                  ↓               ↓               ↓
              成功             rate-limited      永久失敗
              發 reply         schedule          發「sorry,
              到 Slack         delayed_retry     try again」
                              (5s後)            到 Slack

關鍵設計:

  • correlation_id 串整條鏈:原始 Slack message_id 從 webhook 到 LLM handler 到 reply handler 一路傳,方便 audit + debug
  • 暫時性失敗 retry:LLM rate limit、network blip 等用 delayed retry(5 秒後再來),不要立刻 retry 浪費 quota
  • 長時間 task 隔離:LLM call 在 handler 內,慢都慢在 worker,不影響其他 webhook

部署拓樸

Slack webhook ─→ Web deployment(5 pods,每個快進快出)
                       │
                       ↓
                  Broker
                       │
                       ↓
                LLM worker deployment(**少而胖** — 每 pod 4 GB memory,
                                       因為 LLM SDK + context 吃 memory)

LLM worker 不該跟「下單寄信」那種輕 handler 同一個 deployment:資源 profile 完全不同(memory-heavy、CPU-burst、latency-tolerant),分開 scale 才不會互相干擾。

為什麼這個沒寫 demo

要寫進 demo 得 mock LLM call、加 Slack SDK。scope 比這篇主題大太多。Pattern 本身在前面 5 個 scenario 都有零件(event 路徑、correlation_id 串接、delayed retry),組起來就是這個 scenario。


9. 部署拓樸總結

四種常見配置,從簡到繁

拓樸 1:mono process(極小流量 / MVP)

┌─────────────────────────┐
│ 一個 process            │
│  ├─ web HTTP handler    │
│  ├─ worker setInterval  │
│  └─ cron setInterval    │
└─────────────────────────┘
  • 好處:一個 deployment 一個 process,最簡單
  • 痛點:worker OOM 拉爆 web;deploy web 順便重啟 worker
  • 適合:流量極低、prototype、內部 tool。不適合 production(除非真的很閒)

拓樸 2:process 內分 worker(小流量單機)

┌─────────────────────────┐
│ 同 deployment           │
│  ├─ pod A:web only     │
│  ├─ pod B:worker only  │
│  └─ pod C:cron only    │
└─────────────────────────┘
  • 同 image / 同 deployment 不同 args / command
  • 好處:不用多管 deployment;同 codebase 重用
  • 痛點:scale 要一起 scale;deploy 要一起 deploy
  • 適合:小流量但已上線、團隊小

拓樸 3:deployment 分離(標準 production)

┌───────────────┐    ┌───────────────┐    ┌───────────────┐
│ web deploy    │    │ worker deploy │    │ cron deploy   │
│ (5 pods)      │    │ (3 pods)      │    │ (1 pod)       │
└───────────────┘    └───────────────┘    └───────────────┘
       ↑                    ↑                    ↑
       └─ HPA on RPS        └─ HPA on queue      └─ K8s CronJob
                              depth                 spawn-on-schedule
  • 各自 image build args / Dockerfile 不同 entry point
  • 好處:各自 scale、各自 deploy、failure isolation
  • 痛點:3 個 deployment 要監控;CI/CD 多 3 條 pipeline
  • 適合:大多數 production 系統的甜蜜點

拓樸 4:物理機分離 / 跨 cluster(超大流量 / 強隔離需求)

Web cluster                Worker cluster              Cron cluster
(高 QPS、低延遲)         (高 throughput、可慢)     (低頻、可重跑)
                                   │
                                   ↓
                          專用機器跑 ML / LLM heavy
  • 物理 / 雲端 region / availability zone 都分
  • 好處:硬體 profile 各自最佳(web 多 CPU、worker 多 memory、ML 多 GPU)
  • 痛點:跨 cluster 網路成本、broker 要跨 cluster 設計、運維複雜
  • 適合:大規模系統、合規隔離要求(pci-dss 把信用卡資料隔離)

怎麼決定升級

從拓樸 1 升級到 4 的 trigger 通常是「某個 process 開始拖累其他 process」:

  • web latency 變差,發現 worker 在 GC 卡住 → 升級到拓樸 3
  • worker OOM 頻率增加,發現 LLM handler 吃光 memory → LLM handler 拆獨立 deployment(部分拓樸 4)
  • 對帳 cron 跑 1 小時,期間 worker pod 排不到資源 → cron 拉去獨立 cluster

不要預先搞拓樸 4,等痛了再升。每升一層運維複雜度都是 N 倍跳。


10. 給 Laravel 讀者的術語對譯(可選)

📍 沒用過 Laravel 的讀者可整節跳過,不影響後面理解。

Laravel 在這層生態做得不錯。下面是「框架幫你做了什麼、你還要設計什麼」:

Laravel 概念本篇對應Laravel 幫你做了你還要自己設計
ShouldQueue listenerEvent-driven workerbroker 連線 + worker bootbroker selection、deployment 分離
dispatch(Job)Queue / job推到 queue + worker pull失敗 retry policy、DLQ
dispatch(...)->delay(now()->addMinutes(30))Delayed eventRedis sorted set 排程timeout policy、cancel 邏輯
php artisan schedule:runCron schedulerdispatcher 路由所有 schedule部署成獨立 cron deployment
$schedule->job(new MyJob)->dailyAt('03:00')Scheduled jobcron-like 表達語法冪等性、checkpoint、scope
Bus::batch([...])->dispatch()Batch ETL進度追蹤 + 部分失敗checkpoint、time budget
Horizon worker configWorker deploymentUI、process 管理拆 deployment、HPA、container size

重點:Laravel 在「框架語法」這層整理得不錯(->delay() 一行解 delayed event、->dailyAt() 一行解 cron)。但**「該不該分 deployment / 流量 burst 時 broker 變 buffer / cron 要不要 leader election」這層框架不會替你決定**。

換句話說:本篇講的 6 個 scenario,Laravel 幫你把「實作」省 80%,剩下 20% 還是要設計


11. 反思

寫這篇的動機:很多新人會把 cron / queue / event-driven 三條 path 當成「我選一個用就好」的選擇題,但實際系統幾乎一定會同時用到 2-3 個。

更常見的 anti-pattern:

  • 把 cron 當萬能膠。所有問題都 cron 解 → 系統長出幾十個 cron job,幾年後沒人知道某個 cron 為什麼存在
  • 把 event-driven 當萬能。連「凌晨 3 點對帳」都改成 delayed event → 過度工程,event_id 滿地是
  • 不分 deployment 跑 worker。把 worker 跟 web 同 process → 上 production 第一次流量飆就翻車

希望讀完這篇你能:

  • 看到一個 async 需求,先問「trigger 是 time-based 還是 event-based?」⸺ 90% 場景 5 秒內就知道該選哪個
  • 看到「30 分鐘催繳」這種模糊地帶,知道兩條 path 的真實取捨在哪
  • 看到團隊在 debate 「要不要分 worker deployment」,能用 §9 的 4 個拓樸層級具體討論

最後一個問題給看完這篇的你:

你現在 maintain 的系統,用了哪幾條 async path?deployment 分得對嗎?如果今天某個 worker 跑歪吃光 memory,web 入口會跟著掛嗎?


12. 相關文章

前置

互補系列

  • Production EDA 系列篇 1-6 — 一旦決定用 event-driven 後,production reliability 該怎麼補
    • 篇 2 Outbox pattern — producer 端 dual-write 怎麼解
    • 篇 3-6 — consumer 冪等 / handler 內 SQL / retry+DLQ / audit

Runnable demo: tools3455147/mq-event-driven-demo — 本篇的 §4 §5 §7 demo 都在 scheduler-demo.ts route 跟 /demo/payment-reminder /demo/daily-report /demo/batch-etl 三個 endpoint 設計文件: docs/architecture.md