🎯 什麼情境該想到我
當你要在同一個時間點對大量使用者做同一件事(每晚的推播、月初的帳單、整點的提醒),而單一台排程機「掃出來再一筆筆發」怎麼算都發不完的時候。
⚙️ 怎麼用(步驟 / 公式)
1. 先算帳:速率 = 總量 ÷ 時間窗
原文的例子(數字都是假設值):1 億人、希望 1 分鐘內發完:
1e8 ÷ 60 秒 ≈ 1.67e6 條/秒
如果全部定在 22:00:00 觸發,就是 1 億條落在同一秒。再看單機:每秒 5000 條已經不錯,
1e8 ÷ 5e3 = 2e4 秒 ≈ 5.5 小時
晚上 10 點開始,發完已經凌晨三點半,而且這台機器中途不能掛。先把這兩個數字算出來,就知道「一張任務表+每分鐘掃一次」的做法在哪個規模會破。 量級估算的方法見 工具-容量估算。
2. 把「什麼時候發」和「怎麼發」拆開
- 調度器到點只做一件事:把這一批 uid 寫進訊息佇列。它不呼叫下游,也不等發送結果。
- 消費者負責發送,按自己的能力從佇列拉。
- 這裡佇列的作用不是解耦上下游,是緩衝:上游一次寫進 1 億條,下游慢慢消化,佇列長度會先漲後落。這就是削峰填谷(見 工具-網站高可用與伸縮性設計)。
- 限流放在消費端:推播通道、簡訊閘道都有速率上限,超過會被批量拒絕;被拒的再重試,又會把積壓放大一輪。所以消費者要按下游的上限發,詳見 工具-限流器設計。
3. 分片:讓每台調度器只管一部分
uid % N 把任務分給 N 個調度實例,每個實例只掃自己那一片,單機壓力降到 1/N。
4. 錯峰:在可接受的時間窗裡把發送時刻攤開
發送秒數 = hash(uid) % 60 # 這個人在 22:00 後的第幾秒發
- 每秒約 167 萬條,hash 讓分布大致均勻,不會全部擠在第 0 秒。
- 能這樣做,是因為使用者感知不到秒級差異:大家只知道「十點過一會兒收到通知」。產品允許的時間窗,本身就是可以自己安排的資源。
5. 兩種做法對照
| 單機定時任務 | 分片+佇列 | |
|---|---|---|
| 任務存在哪 | 資料庫裡一行一筆 | 佇列訊息,可以重放 |
| 怎麼擴容 | 換更好的機器 | 加消費者 |
| 失敗怎麼辦 | 查 log、人工補發 | 重試、死信佇列 |
| 峰值 | 全壓在整點那一秒 | 按秒攤開 |
🧪 我實際套用的紀錄
- 2026-10-31:(待填)
⚠️ 注意 / 什麼時候不適用
- 量小時別上這套。原文自己說:1 萬個使用者時,一個 crontab 加一次記憶體排序就夠了,多一層佇列反而是負擔。佇列要運維、要監控積壓。
- 佇列是至少一次投遞,同一則推播可能被發兩次。消費端要用「uid+日期」這類業務鍵去重,見 工具-MQ消費端防禦三原則(經驗推論,原文沒提)。
- 重試要有上限和退避,否則下游一拒絕,重試就會變成第二波尖峰。
- 這張卡處理的是「很多人在同一時刻要做同一件事」。如果是「每筆各自在不同時間到期」(訂單 30 分鐘沒付款就取消),要看 工具-延遲任務方案選型。
- 錯峰只能在產品允許的時間窗內做。如果需求是「整點準時」(例如開賣),就不能攤開,要改從容量與限流下手。
🔗 相關工具
- 工具-每人一份的結果要預算還是現算 —— 同一篇的另一半:推播背後的排行榜要不要先算好
- 工具-網站高可用與伸縮性設計 —— 削峰填谷:佇列當緩衝的通則
- 工具-限流器設計 —— 消費端按下游上限發送
- 工具-MQ消費端防禦三原則 —— 重複投遞、重試、背壓
- 工具-延遲任務方案選型 —— 對照:個別到期的延遲任務
- 工具-容量估算 —— 先算帳再選架構