使用 Redis 实现排行榜的核心是合理利用其内置的 Sorted Set(有序集合) 数据结构,它能以 O(log N) 的时间复杂度完成元素插入、排序和排名查询。以下是具体实现步骤和示例:
1. 核心数据结构:Sorted Set
Sorted Set 的每个元素由 member
(成员,如用户ID)和 score
(分数,如积分)组成,Redis 会根据 score
自动排序,并支持快速排名查询。
2. 实现排行榜的关键命令
a. 添加/更新成员分数
# 添加或更新成员分数(如用户得分变化)
ZADD leaderboard 1000 "user1" 950 "user2" 1200 "user3"
# 原子性增加分数(如用户积分累加)
ZINCRBY leaderboard 50 "user1" # user1 分数变为 1050
b. 获取排行榜 TOP N
# 获取前10名(按score从高到低,WITHSCORES返回分数)
ZREVRANGE leaderboard 0 9 WITHSCORES
# 输出结果示例:
# 1) "user3"
# 2) "1200"
# 3) "user1"
# 4) "1050"
# ...
c. 查询单个用户的排名和分数
# 获取用户排名(从高到低的排名,0表示第一名)
ZREVRANK leaderboard "user1"
# 获取用户分数
ZSCORE leaderboard "user1"
d. 查询区间内的用户
# 获取分数在 [900, 1100] 之间的用户(按score升序)
ZRANGEBYSCORE leaderboard 900 1100 WITHSCORES
# 按score降序获取
ZREVRANGEBYSCORE leaderboard 1100 900 WITHSCORES
3. 高级优化技巧
a. 相同分数的排序问题
默认情况下,当多个成员分数相同时,Redis 会按 member
的字典序排序。若需要自定义规则(如按时间先后),可以将时间戳作为小数拼接到分数中:
# 分数 = 用户积分 + (1 - 时间戳 / 1e14),确保时间戳不影响主要积分
ZADD leaderboard 1000.999999 "user1"
b. 内存优化
- 短成员名:使用
user:123
代替完整字符串。 - 编码优化:Redis 会根据元素数量和大小自动选择编码方式(如 ziplist 或 skiplist),通常无需手动干预。
c. 分页查询
# 获取第二页数据(每页10条)
ZREVRANGE leaderboard 10 19 WITHSCORES
d. 过期时间
# 设置排行榜30天后过期(自动清理旧数据)
EXPIRE leaderboard 2592000
4. 实战代码示例(Node.js + ioredis)
const Redis = require("ioredis");
const redis = new Redis();
// 更新用户分数
async function updateScore(userId, score) {
await redis.zadd("leaderboard", score, userId);
}
// 获取TOP 10
async function getTop10() {
return await redis.zrevrange("leaderboard", 0, 9, "WITHSCORES");
}
// 获取用户排名
async function getUserRank(userId) {
const rank = await redis.zrevrank("leaderboard", userId);
return rank !== null ? rank + 1 : null; // 将排名从0-based转为1-based
}
5. 适用场景
- 实时性要求高:如游戏积分榜、直播送礼榜。
- 大规模数据:Sorted Set 的查询效率与数据量无关。
- 动态更新:支持频繁的分数增减操作。
通过上述方法,Redis 能轻松支撑百万级用户的实时排行榜需求,且性能稳定。实际应用中可根据业务需求组合命令实现复杂逻辑(如赛季排行榜、多维度排序)。