🎯 什麼情境該想到我
當每個使用者看到的結果都不一樣(好友排行榜、個人化動態、「你的朋友也買了」),你在猶豫「要先幫每個人算好存起來」還是「等他來看時再算」的時候。
⚙️ 怎麼用(步驟 / 公式)
1. 先分清楚:全域結果 vs 每人一份
- 全域榜:所有人看同一份,例如「全站步數前 100」。一個 Redis ZSet 就夠:
ZINCRBY更新分數是 O(log N),取前幾名是 O(log N + M)(原文寫ZREVRANGE,這個指令自 Redis 6.2 起已標為 deprecated,現在寫ZRANGE key 0 99 REV,見 工具-Redis單執行緒下的阻塞風險)。 - 每人一份:「我的好友裡誰走最多」,每個人的成員集合不同、名次也不同,全域 ZSet 回答不了。
2. 兩條路的代價對照(k=一個人的好友數,N=使用者數)
| 方案 | 一次查詢 | 一次更新 | 空間 |
|---|---|---|---|
| 預算:每人一個 ZSet | O(log k),最省 | 要改 k 個榜,O(k log k) | O(N·k) |
| 現算:查詢時才排序 | O(k log k) | O(1) | 不額外佔 |
代入原文的假設值(1 億人、平均 200 個好友):
預算的空間:1e8 × 200 = 2e10 筆,每筆幾十位元組 → TB 級常駐記憶體
預算的寫入:一人步數變一次,要改 200 個榜(寫放大 200 倍),而步數是整天持續上報的
現算的查詢:200 × log2(200) ≈ 200 × 7.6 ≈ 1500 次比較 → 毫秒以內
這組數字下,現算明顯划算。
3. 決策看「k 的分布 × 存取頻率 f」,不看平均值
- k 不是定值,是長尾:好友數從幾十到上萬都有。用平均 200 設計,碰上一個 8000 好友的帳號,讀取成本直接翻 40 倍。
- 真正進榜的人比好友數少:沒開功能、不公開的人不參與。
- 依場景分開處理:
- k 小、不常看 → 查詢時直接算,成本低,也沒有一致性問題。
- k 大,或被反覆查看 → 算一次放快取,設短 TTL。
- k 大又被集中存取的少數帳號 → 單獨處理。
- 判準一句話:這個人的 k 乘上 f,值不值得存一份結果。
4. 再往上問一層:這份結果真的需要嗎?
推播和計算本來是兩件事,產品上可以拆開:
- 推播只寫「你今天走了 8324 步」,不需要任何排名。
- 「你在好友裡排第 7」等使用者點開時才算。假設 10% 會點開,就只要算 1000 萬次,少一個數量級;沒點開的 90% 一次都不用算。
按需計算不是白賺的,壓力從發送端搬到了讀取端:
| 新問題 | 表現 | 常見對策 |
|---|---|---|
| 讀取洪峰 | 推播後幾分鐘內大家一起點開 | 入口限流、排隊、骨架畫面 |
| 快取冷啟動 | 第一批請求全部回源 | 預熱熱點、合併同樣的請求 |
| 依賴放大 | 一次請求要讀好友列表+批量取步數 | 好友列表本地快取、批量介面 |
| 高峰算不出來 | — | 降級:先只回自己的步數,排名稍後載入 |
★ 排序本身不貴,貴的是排序前要拿的兩樣東西(好友列表、每個人的步數)。按需方案要優化的是這兩處讀取,不是排序演算法。
★ 最有效的優化,常常不是讓計算更快,而是讓不需要的計算根本不發生:1 億次提速 10 倍,和只算 1000 萬次,省下的是同一個量級,但後者還省機器。
🧪 我實際套用的紀錄
- 2026-10-31:(待填)
⚠️ 注意 / 什麼時候不適用
- 這和社群時間線的「寫擴散 vs 讀擴散」是同一個問題:《設計資料密集型應用》第 1 章的 Twitter 例子,負載參數同樣是「每個使用者追蹤者數的分布」;Twitter 的做法是混合式:一般使用者的推文預先寫進追蹤者的時間線,名人的推文則排除在外,讀取時才另外抓來合併(見 工具-可靠可擴展可維護)。這裡的「大帳號單獨處理」就是同一個思路(對照是我加的,原文沒提 Twitter)。
- 快取的結果會過時:短 TTL 換來的是使用者可能看到幾秒到幾分鐘前的名次。排行榜可以接受,餘額、庫存這類就不行。
- 1 億成員的全域 ZSet 約十 GB 是原文的估計,我沒實測,實際大小要看成員字串長度與 Redis 的編碼方式(見 工具-Redis資料型別的底層編碼)。
- 原文的數字(1 億日活、平均 200 好友、10% 點開率)都是作者標明的假設,不是微信的實際數據。換成自己的系統時,要代入自己量到的 k 分布與點開率。
🔗 相關工具
- 工具-整點批量任務的調度與錯峰 —— 同一篇的另一半:推播本身怎麼發
- 工具-可靠可擴展可維護 —— Twitter 時間線的扇出問題,同一個取捨
- 工具-跳表 —— Redis ZSet 排行榜底下的結構
- 工具-資料分區 —— 熱點大帳號的另一種處理:打散