圖文詳解:如何設計一個億級用戶排行榜?
面試時被問到"如何設計排行榜系統",可別以為是考簡單的排序算法。
一旦場景升級到用戶量破億、每秒要處理 10 萬次排名查詢,同時數據還得保持分鐘級更新時,你會發現這道題藏著分布式系統設計的幾乎所有核心挑戰。
今天牛哥就從需求拆解開始,一步步推導出能支撐億級用戶的排行榜架構。
明確需求
設計前不明確核心需求,就像航海沒有指南針。億級用戶場景下,這四個指標直接決定系統成敗,每個都要給出量化標準。
圖片
1. 實時性是關鍵
實時性怎么定義才合理?實測數據告訴我們:
- 核心榜單(游戲戰力榜)更新延遲超過5秒,用戶投訴率會上升15%;
- 非核心榜單(周銷量榜)可放寬到分鐘級,但必須在前端明確提示"5分鐘更新一次"。
這里的關鍵是用戶感知:哪怕數據是異步更新的,也要通過前端動效讓用戶覺得實時生效。
2. 準確性是底線
榜單的核心價值在于傳遞可信信息,要做到「最終一致 + 不丟數據」,需要滿足以下要求:
- 用戶行為必須 100% 接入計算,確保數據無遺漏;
- 分數更新需采用原子化處理,避免并發場景下的計數錯誤;
- 系統發生故障時,數據恢復后仍能準確追溯完整記錄;
- 億級數據規模下,針對分片間數據同步可能產生的短暫不一致,需設計數據對賬機制,進行跨分片的分數總和校驗。
圖片
3. 抗壓力決定系統能否"活下來"
雙11零點的銷量榜、游戲版本更新后的戰力榜,峰值QPS可能從日常的 1 萬飆升到100萬。這時候不僅要扛住,還要保證P99 延遲 < 200ms,一旦超過這個閾值,用戶會明顯感覺"卡頓"。
4. 靈活性關系到業務迭代
業務方經常提需求 "今天要日榜,明天加個周榜,后天還得支持按「銷量+好評率」混合排序" 。如果每次調整都要改代碼、重啟服務,技術團隊會被業務拖著走,所以設計時要預留「排序規則動態配置」能力。比如用JSON配置權重因子,無需發版就能生效。
{"like":2,"comment":3,"share":5}技術選型:"扛億級"的工具組合
需求明確后,下一步是選對工具。很多同學一上來就說"用Redis ZSet",但真實場景的選型要復雜得多。這三個核心工具的組合,直接決定系統的性能天花板。
Redis ZSet:為實時榜單而生
Redis的 ZSet 為什么是實時排行榜的首選?看三個核心特性:
- O(logN)的分數更新:ZINCRBY 命令能原子化更新分數,億級數據量下依然高效;
- 內置排序能力:自動按score排序,無需額外計算;
- 豐富的范圍查詢:ZREVRANGE(查TOP N)、ZCOUNT(查分數區間人數)等命令完美匹配排行榜需求;
這里我們想知道,為什么不用數據庫或搜索引擎呢?
看實際表現就很清楚:MySQL的ORDER BY在百萬級數據時就會卡頓;Elasticsearch雖然支持排序,但寫入延遲和資源消耗遠高于 Redis。
圖片
對于90%的實時榜單場景,ZSet的性價比無人能敵。
但ZSet有個致命缺點:單Key存儲上限。
Redis 單 Key 的存儲大小建議控制在 100MB 以內。但在實際場景中,如果每個用戶的榜單數據約為 16字節,1 億用戶的 ZSet 總大小就約為 1.6GB,遠超最佳閾值 ,會導致持久化慢、主從同步延遲等性能問題。
所以億級場景必須配合 Redis Cluster 分片,將大Key拆分成多個小 Key, 存儲在不同節點,每個分片只存100萬用戶,大小約16MB,符合Redis最佳實踐。
圖片
定時任務+分布式計算:非實時榜單的最優解
不是所有榜單都需要實時更新。日銷量榜、周熱門榜這類"周期結算型"榜單,用定時任務預計算比實時計算節省 90% 資源。但億級場景下,普通定時任務框架不夠用,需要分布式計算引擎配合。
XXL-Job + Spark/Flink 怎么選?看數據量和計算復雜度:
- XXL-Job + 分片執行:適合數據量中等(千萬級)、計算邏輯簡單的場景。比如全平臺日銷量榜,數據量不算很大。這種方案支持控制臺暫停/恢復任務,失敗重試策略也很完善。
- Spark/Flink 批處理:適合億級數據規模、計算邏輯復雜的場景。以內容熱度周榜為例,計算過程中需要關聯用戶畫像和時間衰減因子, Spark 的 DataFrame API 能夠高效處理這類多表關聯計算。

實際項目中建議分層計算:核心日榜采用 XXL-Job,因其對實時性要求較高;歷史月榜選用 Spark 批處理,依托凌晨時段進行計算,能有效降低資源成本。
多存儲體系:冷熱數據的分層存儲
Redis適合存熱數據,比如實時榜、近7天榜單,但歷史數據如用戶歷史排名記錄,需要長期存儲,這時候要構建多級存儲體系,降低成本。
數據類型 | 存儲介質 | 存儲周期 | 訪問延遲 | 月成本(1億條數據) |
熱數據 | Redis Cluster | 7天 | <1ms | 約5萬元(100GB內存) |
溫數據 | MySQL分表 | 3個月 | <100ms | 約5000元(100GB SSD) |
冷數據 | ClickHouse/Hive | 永久 | <1s | 約500元(1TB HDD) |
架構拆解:四層核心邏輯
工具選好了,接下來是搭架構,一個能抗住億級用戶的系統,一定是職責分明的。從用戶行為產生到最終展示,排行榜系統可拆成 "數據接入層 - 計算排序層 - 存儲層 - 展示層" 四層架構:

每層專注解決一類問題,這樣即使流量翻10倍,也能通過分層擴容扛住。
數據接入層:消息隊列 + 多活部署
用戶的每次點擊、購買、點贊,都是榜單數據的源頭。億級場景下,數據接入層的目標是「不丟數據、低延遲、高可用」,單Kafka集群不夠用,需要多活部署 + 異地容災。
比如電商平臺的「全國商品銷量榜」,用戶廣泛分布于華北、華東、華南三大核心區域,需在這三個地域分別部署 Kafka 集群,各區域用戶行為數據直接寫入本地 Kafka 集群;然后通過 Kafka MirrorMaker 工具,將各區域的商品銷量數據跨地域同步。

數據接入層消費到 Kafka 中的行為消息后,按照特定規則計算出商品銷量的實時變動。最后調用榜單系統的分數更新接口,實現榜單動態刷新。
為什么必須多活?設想一下:若僅依賴華北地區的單 Kafka 集群,一旦集群故障,華北用戶的下單、加購數據將無法接入,直接導致「全國商品銷量榜」 缺失華北區域數據。
圖片
而多活部署能確保任一地域集群出現問題時,其他地域集群仍能正常工作,RTO(恢復時間目標)可控制在 5 分鐘內。
此外,電商數據接入層還需做好兩大關鍵保障:
- 流量控制:借助 Kafka 的配額機制(Quota)限制單個生產者或消費者的流量,例如設定單商家每秒最多發送 100 條商品點擊數據,防范惡意刷流量的攻擊;
- 死信隊列(DLQ):專門存儲處理失敗的消息,像訂單 ID 不存在、用戶賬號已注銷卻產生下單數據等異常消息,都會被導入死信隊列,后續可定期安排人工排查處理,避免數據丟失。
計算排序層:按場景選擇架構
計算層是排行榜的"大腦",負責把原始分數轉化為有序排名。億級場景下,沒有萬能方案,只有按場景選擇的混合架構。
實時計算:基于 Lua 腳本的即時計算
適合游戲戰力榜、直播人氣榜這類更新頻率在秒級、數據量中等(千萬級)的場景。當用戶產生行為后,系統執行的命令示例如下:
1. 當用戶產生行為后,通過 Lua 腳本直接操作 ZSet 完成原子化更新:
-- 原子化更新分數并返回最新排名(降序排名,戰力高排第1)
local newScore = redis.call('ZINCRBY', 'game_power_ranking:server1', 50, 'user:10086')
local rank = redis.call('ZREVRANK', 'game_power_ranking:server1', 'user:10086')
return {newScore, rank}2. 同步更新本地緩存 Caffeine(Java 示例):
// 使用Caffeine更新本地緩存(用戶排名與分數)
caffeineCache.put("user:10086:rank", rank);
caffeineCache.put("user:10086:score", newScore);通過這種方式,既保證了 Redis 集群中分數更新的原子性,又通過 Caffeine 本地緩存提升了后續查詢效率,讓排名變化能實時反饋給用戶。
批量處理:Spark + ClickHouse
適合內容熱度周榜、電商月銷量榜這類場景。更新頻率不高,通常是小時級或天級,數據量卻能達到億級規模。
每天凌晨3點,Spark 從 Kafka 消費全量行為數據,結合用戶畫像、時間衰減因子等維度算出最終分數,之后批量寫入 ClickHouse,再同步到 Redis ZSet 中供查詢。
圖片
混合計算:Flink 實時處理 + 批處理
適合「實時+歷史」雙維度的榜單,比如綜合熱度榜,既關注當下的實時熱度,也參考 7 天內的累計表現,其運作流程為:
用戶剛產生的點贊、評論等即時行為,會由 Flink 實時捕捉并計算出對應的實時分;到了每天凌晨,Spark 會啟動批處理任務專門核算歷史分,例如 7 天前的互動數據影響力會減弱,權重調整為 0.5;
圖片
之后按照 “實時分占 70%、歷史分占 30%” 的公式算出綜合分,比如某內容實時分 80 分、歷史分 60 分,綜合分就是:
80(實時分) × 0.7 + 60(歷史分) × 0.3 = 74 分;
而 0.7 和 0.3 這樣的權重比例會預先存在 Redis 的 Hash 結構中,方便靈活調整,最終的綜合分會寫入 Redis ZSet,由它完成排序,既保證實時性又兼顧歷史數據的影響。
存儲層:Redis Cluster + 多級緩存 + 冷熱分離
存儲層是排行榜的"數據底座",在億級用戶的榜單場景下,存儲層需要同時滿足查詢快和成本省的需求,核心靠 Redis Cluster 分片、多級緩存、冷熱分離 這三大策略搭配實現。
Redis Cluster分片:分而治之
面對億級用戶的榜單數據,我們按用戶 ID 做哈希分片:
- 把數據均勻分成 100 個分片,用「用戶 ID 除以 100 取余數」的方式,確定每條數據該存入哪個分片;每個分片大約存 100 萬用戶的榜單數據,約 16 MB;
圖片
- 再把這些分片分散到 10 個 Redis 節點上,每個節點負責 10 個分片;
圖片
后續如果數據量增長,只需直接增加 Redis 節點,系統會自動遷移分片到新節點,輕松實現 “橫向擴容”。
多級緩存:讓查詢層層加速
大多數用戶查榜單,只看熱門內容或自己的排名,沒必要每次都查全量數據。所以我們用 「三級緩存」分層承載查詢需求:
- 本地緩存(Caffeine):直接存在應用服務器的內存里,專門緩存 TOP20 的熱門榜單,1 分鐘刷新一次。承載 90% 的首頁榜單查詢,延遲不到 1 ms,快得像讀本地文件;
- Redis Cluster:存儲 TOP 1000 榜單數據 + 用戶個人分數,5分鐘刷新一次,承載 10% 的非首頁查詢;
- ClickHouse/MySQL:存儲歷史榜單 + 完整排名數據,按需查詢,如用戶主動查看"我的歷史排名"數據。
圖片
冷熱分離:給 Redis 減負
Redis 用內存存儲,速度快但成本高,而超過 7 天的榜單數據,用戶查詢頻率會大幅下降。所以我們每周日凌晨做一次 “冷熱數據搬家”。
圖片
將超過7天的實時榜數據從Redis Cluster 同步到 ClickHouse,同步完成后刪除 Redis 中的歷史數據,只留下索引方便后續快速定位。
遷移過程用「雙寫一致性」保證:先寫ClickHouse,成功后再刪Redis,避免數據丟失。
展示層:CDN + API 網關 + 應用集群
展示層直接面對用戶請求,核心目標是做到「毫秒級響應、全球低延遲、扛住高并發」。億級場景下,單應用集群不夠用,需要CDN加速、API網關限流、多地域應用集群三者配合。
CDN 加速靜態榜單
針對首頁 TOP20 這類訪問頻率極高的榜單,我們會先把數據生成靜態 JSON 文件,再通過 CDN 分發到全球各地的節點。這樣一來,不管用戶在哪個地區,都能從離自己最近的 CDN 節點獲取榜單數據,延遲控制在 50 毫秒以內,打開頁面幾乎秒加載。
圖片
為了保證數據不過時,我們給 CDN 緩存設置了 1 分鐘的有效期,同時借助 API 網關的主動刷新(PURGE)機制,只要榜單數據更新,就能立刻觸發 CDN 節點緩存同步,既兼顧了速度,又能讓用戶看到最新排名。
API 網關動態路由
全球用戶的查榜請求,會先匯總到 API 網關。它主要做兩件事:
- 一是動態路由,根據用戶所在地區,自動把請求轉發到最近的應用集群,比如北美用戶的請求直接分配到美東集群,進一步縮短跨地域訪問的延遲;
- 二是限流保護,給單個用戶、單個 IP 設定訪問上限,比如限制每個用戶每秒最多查 5 次榜單,避免惡意刷量沖垮后端服務。
圖片
應用集群彈性擴容
應用集群基于 K8s 部署,搭配 HPA 機制,也就是水平 Pod 自動擴縮容,根據實際流量自動調整資源。
比如把 CPU 利用率 70% 設為閾值:當超過 70% 時,比如晚間 8-10 點用戶查榜的高峰時段,集群會自動增加 Pod 數量,最多能擴展到 100 個;而當流量回落,CPU 利用率降低時,又會自動縮減 Pod,最低保留 8 個。
圖片
這樣既保證了高峰期能扛住壓力,又避免了低峰期的資源浪費。
關鍵實現:億級場景的避坑指南
基礎架構搭好后,系統可能能用,但未必扛得住億級流量。這些關鍵實現細節,決定了系統從及格到優秀的差距。
跨分片查詢:從慢合并到預計算加速
查詢全服 TOP100 榜單時,需要從100個分片中各查 TOP100,得到100×100=10000 個候選結果后,再合并排序取 TOP100。
億級場景下,這個過程異常耗時,顯然無法滿足用戶對"秒開"的需求,需要優化。
1. 預計算候選集
每個分片每 5 分鐘預計算 TOP1000 的榜單數據,把這些數據緩存到本地,合并時從每個分片取 TOP1000,得到100×1000=100000 個候選結果。
圖片
雖然數據量增加10倍,但能避免"分片內 TOP100 之外的用戶,實際可能是全局 TOP100" 的情況。
2. 分布式合并計算
在應用層,我們用 Java 的 PriorityQueue 也就是小頂堆來合并候選結果,把堆的大小固定為 100。遍歷所有候選數據時,只要當前用戶的分數高于堆頂分數,就替換堆頂元素,最終堆里剩下的就是全局 TOP100。
不過,100000 條數據的合并耗時約 30ms,再加上 100 次 Redis 查詢(每次約 5ms),總耗時會達到 530ms,不滿足 P99 延遲 ≤200ms 的主流標準,依然需要優化。
3. 終極優化 — 分層合并
終極解決方案是「分層合并」:先把 100 個分片按機架或可用區,分成 10 個組,每組包含 10 個分片。
圖片
第一步先在組內合并,每個組算出自己的 TOP1000;第二步再合并 10 個組的 TOP1000,得到最終的全局 TOP100。
圖片
組內合并可以直接在 Redis Proxy 層完成,這樣應用層只需發起 10 次組查詢,再做一次全局合并即可。優化后總耗時降到 80ms(10 次查詢 ×5ms + 合并 30ms),完全滿足億級場景的響應要求。
數據一致性:從最終一致到可追溯
億級場景下,絕對一致性無法實現,因為跨分片實時同步成本太高,但要保證「最終一致+可追溯」。具體通過三層策略實現:
實時數據一致性
主要通過保證單分片原子性,跨分片定期修復的策略實現:
- 單分片內:用 Lua 腳本保證「查詢狀態 + 修改分數」的原子性,確保高并發下不會出現 “同一用戶重復通關,導致通關積分重復增加” 的臟數據。
- 跨分片間:允許短暫的不一致,但每天凌晨會啟動定期對賬,比對各分片的總分,發現偏差后自動修復。
圖片
歷史數據一致性
主要通過「批處理對賬 + 告警排查」實現。每天用 Spark 批處理計算用戶的每日總分,再和 Redis Cluster 中存儲的分數總和對比,允許誤差控制在 0.1% 以內;如果超過這個閾值,就會觸發告警,提醒工程師排查原因。
用戶行為可追溯
每一次分數更新操作,都會記錄詳細日志,包括用戶 ID、行為類型、分數增減量、操作時間戳、請求 ID 等。日志通過ELK存儲,支持按用戶ID/時間范圍查詢,當用戶投訴分數異常時,工程師能通過日志快速定位問題根源。
圖片
監控告警體系:億級下的可觀測性
億級系統"黑盒運行"等于裸奔,必須構建完善的監控告警體系,覆蓋分片健康度、數據一致性、性能指標。
1. 分片健康度監控
監控每個 Redis 分片的QPS、內存使用率、響應時間,尤其是響應時間重點關注 P99、P999 分位值。一旦任一指標觸及設定的閾值,比如 QPS 超過 1 萬、內存使用率超過 80%、P99 響應時間超過 100ms,就會立刻發送告警;

同時監控分片遷移狀態,要是遷移速度低于 10MB/s,同樣會觸發告警,以此避免遷移超時影響服務的可用性 。
2. 數據一致性監控
- 實時監控:每分鐘計算 Redis Cluster 的總分波動,若當前總分與 5 分鐘前的差值超過 10 萬,立即告警。
- 離線對賬:每天凌晨比對 Redis 和 ClickHouse 中的歷史分數,誤差超過 0.1% 就觸發告警,確保歷史數據不丟不錯。
3. 用戶體驗監控
通過前端埋點收集榜單加載時間,P95>500ms 時觸發告警,及時發現CDN緩存失效、應用集群過載等問題。
總結:億級排行榜的設計心法
設計億級用戶排行榜,本質是對"實時性 - 準確性 - 成本 - 可用性"的四重權衡。記住這6個核心原則,無論面試還是實戰都能游刃有余:
1. 大 Key 必須分片,小 Key 優化存儲:單ZSet存不下億級用戶,用Redis Cluster按哈希分片;非熱門數據啟用ziplist編碼
2. 實時用 Redis+Lua,批處理用 Spark/Flink
3. 跨分片查詢分層合并:先組內合并再全局合并
4. 多級存儲控成本:熱數據、溫數據、冷數據分層存儲
5. 數據一致性可追溯:單分片原子操作+定期對賬+行為日志,保證最終一致且問題可追溯
6. 監控容災不可少:分片健康度、數據一致性、用戶體驗全鏈路監控,確保系統活下來
最后想說,面試時被問到這類問題,別再直接說"用Redis ZSet"了,先問清楚業務場景,再給出分層方案,這才是面試官想看到的系統設計能力。































