如何设计一个实时排行榜?

2025年 阅读约 15 分钟 面试指南 · 系统设计

深入解析实时排行榜系统设计:Redis ZSet实现(ZADD/ZREVRANGE/ZREVRANK)、分段排行榜(小时/天/周/总榜)、榜单更新策略、TopN查询优化,附面试模拟问答。

一句话总结

实时排行榜用 Redis ZSet(有序集合)实现,底层是跳表(skiplist)+ 字典(dict)。核心命令:ZADD(更新分数)、ZINCRBY(增量更新)、ZREVRANGE(TopN)、ZREVRANK(查排名)。分段排行榜:按时间维度拆分 key(如 rank:202501、rank:20250115),定时任务合并到总榜。亿级用户用分段 + 近似排名

初级理解

Redis ZSet 核心命令

# 更新分数 ZADD rank:total 100 user:1 200 user:2 150 user:3 # 增量更新(推荐,原子操作) ZINCRBY rank:total 10 user:1 # user:1 分数 +10 # 查询 Top 10(分数从高到低) ZREVRANGE rank:total 0 9 WITHSCORES # 查询用户排名(从 0 开始) ZREVRANK rank:total user:1 # 分数从低到高的排名 ZREVRANGE rank:total user:1 # 分数从高到低的排名 # 查询用户分数 ZSCORE rank:total user:1 # 查询分数范围内的用户 ZREVRANGEBYSCORE rank:total 200 100 # 200~100 分

基本架构

# 架构 # 用户行为 → 积分服务 → Redis ZSet(实时) # ↓ # 定时任务 → MySQL(持久化) # ↓ # 排行榜 API → 前端展示 # 为什么用 Redis ZSet? # 1. 跳表实现,O(log n) 插入和查询 # 2. 天然有序,不需要每次排序 # 3. 支持范围查询和排名查询
一句话总结:Redis ZSet 天然适合排行榜,ZINCRBY 增量更新,ZREVRANGE 查 TopN。

中级深入

分段排行榜设计

# 按时间分段 # rank:daily:20250115 → 日榜 # rank:weekly:202503 → 周榜 # rank:monthly:202501 → 月榜 # rank:total → 总榜 # 更新策略 # 1. 实时更新日榜 ZINCRBY rank:daily:20250115 10 user:1 # 2. 定时任务合并到周榜/月榜/总榜 # 每天凌晨执行 ZUNIONSTORE rank:total 3 rank:daily:20250115 rank:daily:20250114 rank:daily:20250113 WEIGHTS 1 1 1 AGGREGATE SUM # 3. 清理过期日榜(保留最近 30 天) EXPIRE rank:daily:20250115 2592000

榜单数据量优化

# 问题:亿级用户,ZSet 内存占用大 # 优化方案: # 1. 只存活跃用户(如近 30 天有行为的) # 定时清理 0 分用户 ZREMRANGEBYSCORE rank:total 0 0 # 2. 分段存储(按用户 ID 哈希分片) # rank:total:0, rank:total:1, ... rank:total:9 # 查询 TopN 时从每个分片取 TopN,再合并排序 # 3. 近似排名(牺牲精度换性能) # 用 HyperLogLog 估算排名区间 # 或按分数段统计(0-100, 100-200, ...)
中级要点:分段排行榜按时间维度拆分;亿级用户用分片+近似排名。

高级拓展

排行榜一致性保证

# 问题:Redis 宕机导致数据丢失 # 方案1:Redis 持久化(RDB + AOF) # 方案2:双写(Redis + MySQL) # 方案3:binlog 异步同步 # 双写方案 # 1. 更新 Redis ZSet # 2. 异步写 MySQL(积分流水表) # 3. 定时对账:MySQL 汇总 vs Redis ZSet # 积分流水表 CREATE TABLE score_log ( id BIGINT PRIMARY KEY, user_id BIGINT, score INT, type VARCHAR(20), # 来源类型 created_at DATETIME );

多维度排行榜

# 需求:按不同维度排名(总积分、周积分、等级) # 方案:多个 ZSet # rank:total:score → 总积分榜 # rank:weekly:score → 周积分榜 # rank:level → 等级榜 # 同时更新多个榜单 MULTI ZINCRBY rank:total:score 10 user:1 ZINCRBY rank:weekly:score 10 user:1 ZADD rank:level 5 user:1 EXEC

实战场景

场景:游戏积分排行榜

@Service public class RankService { @Autowired private RedisTemplate redis; # 更新积分 public void addScore(Long userId, int score) { String dailyKey = "rank:daily:" + LocalDate.now(); String totalKey = "rank:total"; redis.opsForZSet().incrementScore(dailyKey, userId, score); redis.opsForZSet().incrementScore(totalKey, userId, score); } # Top 100 public List<RankVO> getTop100() { Set<ZSetOperations.TypedTuple<>> set = redis.opsForZSet().reverseRangeWithScores("rank:total", 0, 99); return set.stream().map(t -> new RankVO(t.getValue(), t.getScore().longValue()) ).collect(Collectors.toList()); } # 查询用户排名 public Long getUserRank(Long userId) { Long rank = redis.opsForZSet().reverseRank("rank:total", userId); return rank == null ? null : rank + 1; # 从 1 开始 } }

面试模拟

面试官:实时排行榜怎么设计?

你:用 Redis ZSet,底层跳表 O(log n)。ZINCRBY 增量更新分数,ZREVRANGE 查 TopN,ZREVRANK 查排名。分段排行榜按时间维度拆分 key(日榜/周榜/总榜),定时任务合并。亿级用户用分片存储+近似排名优化。

面试官:排行榜数据量很大怎么办?

你:1)只存活跃用户,定时清理 0 分用户;2)按用户 ID 哈希分片,查询时从各分片取 TopN 合并;3)近似排名,用分数段统计代替精确排名;4)冷热分离,热数据在 Redis,冷数据在 MySQL。