在 Redis 的五大基础数据类型中,ZSet(Sorted Set,有序集合)是一种非常强大而灵活的数据结构,广泛应用于排行榜、延时队列、权重排名等场景。
如果说 String 是 Redis 的“最小原子”,那么 ZSet 就是 Redis 的“重量级选手”——不仅能存数据,还能排序查询,这正是它的魅力所在!
ZSet = Set + Score + 排序!
特性 | Set | ZSet |
---|---|---|
是否唯一 | 是 | 是(按成员) |
是否可排序 | 否 | 是(按 score) |
是否可以按分值查找 | 否 | 是 |
是否支持范围查找 | 否 | 是 |
ZSet 的定义:每个元素(成员)都关联一个称为 score 的 double 类型的分数,Redis 会按 score 从小到大自动排序。
ZADD ranking 100 Alice
ZADD ranking 200 Bob
ZADD ranking 150 Tom
最终集合顺序为:Alice(100) -> Tom(150) -> Bob(200)
ZSet 在 Redis 内部是由两种数据结构组合实现的:
跳表是一种多层链表结构,通过层级索引提升搜索效率。
Redis 中跳表最多支持 32 层。每个节点包含:
❗ Redis 7.0 重要变更
在 Redis 7.0 中,取消了 ziplist 编码,统一采用 skiplist + dict 作为底层结构,性能更稳定。
旧版本(Redis < 7.0)编码规则
1.ziplist(压缩列表)
2.skiplist + dict(标准结构)
查看编码命令:
OBJECT ENCODING myZset
命令 | 说明 |
---|---|
ZADD key score member | 添加元素 |
ZREM key member | 删除元素 |
ZSCORE key member | 获取分数 |
ZRANK key member | 获取排名 |
ZREVRANK key member | 获取倒序排名 |
ZRANGE key start stop [WITHSCORES] | 获取排名区间元素 |
ZREVRANGE key start stop [WITHSCORES] | 获取倒序区间元素 |
ZRANGEBYSCORE key min max | 获取分数区间元素 |
ZREMRANGEBYRANK key start stop | 删除排名区间元素 |
ZREMRANGEBYSCORE key min max | 删除分数区间元素 |
ZINCRBY key increment member | 分数自增 |
Java 操作 ZSet 示例(Jedis 客户端)
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
import java.util.Set;
public class RedisZSetDemo {
public static void main(String[] args) {
// 连接 Redis
Jedis jedis = new Jedis("localhost", 6379);
// 1. 添加元素
jedis.zadd("ranking", 100, "Alice");
jedis.zadd("ranking", 200, "Bob");
jedis.zadd("ranking", 150, "Tom");
// 2. 获取正序排名区间元素(带分数)
Set<Tuple> rangeResult = jedis.zrangeWithScores("ranking", 0, -1);
System.out.println("正序排名结果:");
for (Tuple tuple : rangeResult) {
System.out.println(tuple.getElement() + " -> " + tuple.getScore());
}
// 3. 分数自增
jedis.zincrby("ranking", 50, "Tom");
double newScore = jedis.zscore("ranking", "Tom");
System.out.println("Tom 新分数:" + newScore);
// 4. 获取倒序排名前 2 名
Set<Tuple> revRange = jedis.zrevrangeWithScores("ranking", 0, 1);
System.out.println("倒序前 2 名:");
for (Tuple tuple : revRange) {
System.out.println(tuple.getElement() + " -> " + tuple.getScore());
}
jedis.close();
}
}
# 命令行示例
ZADD leaderboard 3000 Jack
ZADD leaderboard 2800 Rose
ZADD leaderboard 3200 Lucy
ZREVRANGE leaderboard 0 2 WITHSCORES # 获取前三名
Java 实现排行榜查询
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
import java.util.Set;
public class LeaderboardSystem {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
// 初始化排行榜
jedis.zadd("game:leaderboard", 3000, "Jack");
jedis.zadd("game:leaderboard", 2800, "Rose");
jedis.zadd("game:leaderboard", 3200, "Lucy");
// 查询前三名(倒序)
Set<Tuple> top3 = jedis.zrevrangeWithScores("game:leaderboard", 0, 2);
System.out.println("游戏排行榜 TOP3:");
int rank = 1;
for (Tuple tuple : top3) {
System.out.println(rank + ". " + tuple.getElement() + " - 积分:" + tuple.getScore());
rank++;
}
jedis.close();
}
}
# 命令行示例(时间戳为任务执行时间)
ZADD delay_queue 1710000000 "task_1"
ZADD delay_queue 1710000500 "task_2"
ZRANGEBYSCORE delay_queue -inf 1710000500 # 获取可执行任务
Java 实现延迟任务处理
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
import java.util.Set;
public class DelayTaskQueue {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
// 添加延迟任务(时间戳为秒级)
long now = System.currentTimeMillis() / 1000;
jedis.zadd("delay:queue", now + 10, "sendEmail"); // 10秒后执行
jedis.zadd("delay:queue", now + 30, "cleanCache"); // 30秒后执行
// 获取当前可执行的任务
Set<Tuple> executableTasks = jedis.zrangeByScoreWithScores("delay:queue", 0, now);
System.out.println("当前可执行任务:");
for (Tuple task : executableTasks) {
System.out.println("任务:" + task.getElement() + ",计划执行时间:" + task.getScore());
// 处理任务后删除
jedis.zrem("delay:queue", task.getElement());
}
jedis.close();
}
}
将点赞/浏览等计算后作为 score,实现实时推荐排序。
用户活跃行为计分后,存入 ZSet,取前 N 名。
typedef struct zskiplistNode {
sds ele; // 成员字符串
double score; // 分数
struct zskiplistNode *backward; // 反向指针
struct zskiplistLevel {
struct zskiplistNode *forward; // 正向指针
unsigned int span; // 跨距(用于计算排名)
} level[]; // 层级数组(柔性数组,长度由插入时随机决定)
} zskiplistNode;
操作 | 平均复杂度 | 说明 |
---|---|---|
ZADD | O(logN) | 插入并维护跳表索引 |
ZREM | O(logN) | 删除节点并更新索引 |
ZRANK | O(logN) | 通过跳表层级快速定位排名 |
ZRANGE | O(logN + M) | 定位起始点后遍历M个元素 |
ZSCORE | O(1) | 直接查询哈希表 |
ZINCRBY | O(logN) | 分数更新后调整跳表节点位置 |
将 ziplist/skiplist 数据结构保存为二进制快照。
记录操作命令,如:
ZADD leaderboard 3000 Jack
MEMORY USAGE leaderboard
ZREMRANGEBYRANK leaderboard 100 -1
建议使用整数 score *1000 来控制小数误差。
确认 score 无重复,或成员字典序排序无误。
定期清理低排名元素 + 设置最大长度限制。
功能需求 | 推荐结构 |
---|---|
唯一值集合,无排序 | Set |
唯一值集合,有排序 | ZSet |
需要权重排行榜 | ZSet |
映射字段 | Hash |
栈/队列 | List |
ZSet 相关源码:t_zset.c
,跳表实现:zskiplist.c
阅读建议:
ZSet 是 Redis 的黄金数据结构之一。它集合了哈希表的高效查询与跳表的有序特性,是实现高性能排行榜、调度系统、热度算法的首选结构。掌握 ZSet,不仅能让你 Redis 玩得更转,还能提升你的架构设计能力。
一文深入 Redis ZSet 的底层结构、命令原理与实战技巧。适合初学者入门进阶、也适合架构师优化系统。收藏 + 关注不迷路!