需求背景

项目中常把持续使用的数据放到缓存中,以便更快响应、减少数据库操作。排行榜就是一个典型场景——如果每次用户查询都去查数据库,性能会很差。

以基于用户好友的排行榜为例,直接查库需要:获取好友 ID → 判断授权 → 获取数据 → 整合,中间还有各种异常处理,请求量上来后体验堪忧。

核心思路

Redis 的 ZSet(有序集合)是实现排行榜的核心数据结构:

  • 有序性:每个元素有一个分数(score),Redis 根据分数排序
  • 唯一性:每个元素唯一,不允许重复
  • 高效操作:支持 O(log N) 的插入、删除、查找,支持按分数范围分页查询

代码实现

添加数据到排行榜

1
2
3
4
5
6
7
@Autowired
private RedisTemplate redisTemplate;

public R add(Double grades, String userId) {
redisTemplate.opsForZSet().add("leaderboard", userId, grades);
return R.ok();
}

获取指定用户排名

1
2
3
4
5
6
7
public R getRanking(String userId) {
RankDTO rankDTO = new RankDTO();
rankDTO.setUserId(userId);
// reverseRank 从高到低排名,结果从 0 开始,+1 才是真实排名
rankDTO.setGrades(redisTemplate.opsForZSet().reverseRank("leaderboard", userId) + 1);
return R.ok().data("rank", rankDTO);
}

获取整体排行榜

1
2
3
4
5
public R getLeaderboard() {
Set<ZSetOperations.TypedTuple<String>> leaderboardSet =
redisTemplate.opsForZSet().reverseRangeWithScores("leaderboard", 0, -1);
return R.ok().data("leaderboard", leaderboardSet);
}

踩坑与思考

1. ZSet 的实现原理?

ZSet 底层基于跳表(SkipList)和哈希表实现,兼顾有序性和高效查找。

2. 用户独立排行榜如何更新?

如果每个用户有自己的好友排行榜,好友数据变化后不方便逐个更新。建议对好友列表做缓存(增删好友时更新),再配合 ZSet 或数据库查询获取实时数据。ZSet 更适合平台维度的整体排行,如积分榜、月度爱心榜等。

3. 排行榜过期时间怎么处理?

ZSet 只能对整个集合设置过期时间,无法针对单条数据。可以用定时任务(如每天 0 点或每月 1 号 0 点)清空旧排行榜,统计当天或当月的有效记录。历史数据可单独落表用于归档。

小结

  • Redis ZSet 是实现排行榜的首选方案,简单高效
  • 注意排名从 0 开始,取排名时 reverseRank + 1
  • 排行榜过期结合定时任务即可实现按天/月轮换

参考:CSDN - Redis的使用–排行榜实现