📌 30 秒摘要
這篇用「微信運動每晚 10 點推步數與好友排行」當題目,推演 1 億人規模下的三個難點。
- 怎麼在 1 分鐘內發完:調度器只把任務送進佇列、發送交給消費者,再用分片與
hash(uid) % 60把發送攤到每一秒。- 每個人的好友榜都不同,該先算好還是查詢時再算:要看好友數 k 的分布與存取頻率 f,不能看平均值。
- 這 1 億份排名真的要先算嗎:推播只帶自己的步數,排名等使用者點開才算,計算量少一個數量級。
作者標明所有規模都是假設,不猜微信實際的做法。
🔍 查證與評比
可信度 22/30|查證 2026-10-31|✅ 4 ⚠️ 3 ❌ 0|判定:完整收(門檻邊緣,經使用者確認)
來源是 UGC 平台(掘金)。主軸是推演,斷言多為算術,以下逐條核對。
| 原文斷言 | 查證 | 依據 |
|---|---|---|
| 1 億 ÷ 60 秒 ≈ 每秒 167 萬條 | ✅ | 算術 |
| 單機每秒 5000 條要 2×10⁴ 秒 ≈ 5.5 小時 | ✅ | 20000 ÷ 3600 ≈ 5.56 |
ZINCRBY 是 O(log N) | ✅ | Redis 官方指令文件 |
ZREVRANGE 是 O(log N + M) | ⚠️ | 複雜度正確,但這個指令自 Redis 6.2 起已標為 deprecated,現在寫 ZRANGE … REV |
| 1 億成員的 ZSet 約十 GB | ⚠️ | 數量級合理,未能實測 |
| 2×10¹⁰ 筆達 TB 級;200×log₂200 ≈ 1500 次比較 | ✅ | 算術 |
| 微信好友上限約 1 萬 | ⚠️ | 未能查證,不影響主軸 |
評分:A 4|B 4|C 4|D 4|E 3(推演是作者自己的,但沒有實測數字)|F 3(庫裡已有扇出、削峰、ZSet 排行榜;新增量是「整點批量發送」與「預算/現算的判準」)。
🗺 心智圖(Canvas)
微信運動排行榜推送的系統設計推演
Link to original
🎯 為什麼存這篇 / 未來想拿它做什麼
- 下次要做「每晚/每月對所有人發一次」的批量任務,先照它算速率、再決定要不要上佇列。
- 遇到「每個使用者看到的結果都不一樣」的功能,拿「k 的分布 × 存取頻率」判斷要預算還是現算,不要反射性地全部先算好。
- 它的結尾是一個好提醒:能用產品流程消掉的負載,就別用架構去硬扛。
🧰 這篇給我的工具
- 工具-整點批量任務的調度與錯峰 — 當我要在同一時刻對大量使用者做同一件事、單機發不完的時候
- 工具-每人一份的結果要預算還是現算 — 當每個人看到的結果都不同,在猶豫要先算好還是現場算的時候
✨ 關鍵重點
- 先算帳:1 億人、1 分鐘內發完,就是每秒 167 萬條;單機每秒 5000 條要跑 5.5 小時。一張任務表加每分鐘掃一次,在這個規模一定破。
- 拆開「什麼時候發」與「怎麼發」:調度器到點只把 uid 寫進佇列,消費者按能力拉。佇列在這裡是緩衝,不是解耦;限流要放在消費端,否則下游批量拒絕、重試又放大積壓。
- 分片+錯峰:
uid % N分給 N 個調度實例;hash(uid) % 60決定在第幾秒發。使用者感知不到秒級差異,這 1 分鐘怎麼排是自己可以決定的。 - 全域榜和好友榜不是同一件事:全域榜一個 ZSet 就好;好友榜每人一份,預算要 2×10¹⁰ 筆、TB 級記憶體,而且一次步數更新要改 200 個榜。
- k 是長尾:照平均 200 設計,遇到 8000 好友的帳號讀取成本翻 40 倍。依 k 與存取頻率分場景:現算、快取短 TTL、大帳號單獨處理。
- 按需計算:推播只帶自己的步數,點開才排名。假設 10% 點開,計算量少一個數量級;代價是讀取洪峰、快取冷啟動、依賴放大,要準備降級(先回自己的步數、排名稍後載入)。
- 作者自己標的邊界:1 萬使用者時,一個 crontab 加一次記憶體排序就夠,多一層佇列反而是負擔。
💬 原文摘錄
- 「最有效的优化,往往不是让计算更快,而是让不需要的计算根本不发生。」
- 「排行榜怎么算,取决于 k 的分布和访问频率 f,不取决于『排行榜』这个功能名。」
- 「能用业务流程消掉的负载,就别用架构去硬扛。」
🔗 相關
- 工具-可靠可擴展可維護 —— Twitter 時間線的扇出問題,和好友榜是同一個取捨
- 訂單超時自動取消的三種延遲任務方案 —— 對照:個別到期的延遲任務,而不是同一時刻的批量任務