這篇要回答的問題:我需要做 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-based | HTTP 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(...)) 就好?」
不行,至少不該。原因:
- process 跟著 web 重啟就丟工作:web deployment rolling update 時,那台 pod 內 in-memory pending 的工作會直接消失。
- OOM 一起死:handler 跑歪吃光 memory,web request 也跟著 503。
- 回壓打不開:流量瞬間飆 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_events 表 WHERE fire_at <= NOW(),到時間執行對應 handler。
對比
| 維度 | Cron polling | Delayed event |
|---|---|---|
| 及時性 | 0~60 秒延遲(poll 週期) | ~1 秒延遲(poll 週期 1s) |
| DB 負擔 | 每分鐘全表掃描 + JOIN | INSERT + 索引查詢 |
| 資料量擴大時 | 表越大 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 環境:
crondaemon 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 時:
- 5 秒內 Stripe timeout
- Stripe 認定失敗,第二次重投
- 你 controller 第一次還沒處理完,第二次又進來
- DB 連線炸滿
- 開始 500,Stripe 認定全部失敗,第三次重投…
- 雪崩
解法: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/:bus 帶 Idempotency-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 再來
關鍵設計:
- Checkpoint:記錄「上次處理到 created_at = X」,下次從那繼續
- 批次大小:每 batch N 筆(500、1000 等)避免單次 query 爆 memory
- 每 batch 一個 commit:失敗只回退本批
- 冪等性:用 ON CONFLICT 寫 aggregate 表,重跑某批不出錯
- 時間預算:跑超過某時間就退出,不要卡住下一輪 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' | jqDemo 簡化版每批就是「處理一天」,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 batch | Stream 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 listener | Event-driven worker | broker 連線 + worker boot | broker selection、deployment 分離 |
dispatch(Job) | Queue / job | 推到 queue + worker pull | 失敗 retry policy、DLQ |
dispatch(...)->delay(now()->addMinutes(30)) | Delayed event | Redis sorted set 排程 | timeout policy、cancel 邏輯 |
php artisan schedule:run | Cron scheduler | dispatcher 路由所有 schedule | 部署成獨立 cron deployment |
$schedule->job(new MyJob)->dailyAt('03:00') | Scheduled job | cron-like 表達語法 | 冪等性、checkpoint、scope |
Bus::batch([...])->dispatch() | Batch ETL | 進度追蹤 + 部分失敗 | checkpoint、time budget |
| Horizon worker config | Worker deployment | UI、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. 相關文章
前置:
- #15 async 原語的三條路 — queue / event-driven / cron 各自的歷史動機
- #18 MVC vs Event-driven — 要不要用 EDA 這個更底層的決策
互補系列:
- 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