一句话总结
实时排行榜用 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。