Redis ZSet 有序集合
1. ZSet 类型概述
Section titled “1. ZSet 类型概述”ZSet(Sorted Set,有序集合)是 Redis 中有序、不重复的字符串集合。每个元素都关联一个 score(分值),Redis 根据 score 对元素排序,score 相同时按元素字典序排序。
ZSet 结构示意:
key = "rank:weekly"┌────────────────────────────────────┐│ score │ member ││─────────┼──────────────────────────││ 9850 │ Alice ← 第 1 名 ││ 8720 │ Bob ← 第 2 名 ││ 7600 │ Carol ← 第 3 名 ││ 6300 │ Dave ← 第 4 名 │└────────────────────────────────────┘ score 升序排列,ZREVRANGE 可获取降序结果| 特点 | 说明 |
|---|---|
| 有序 | 按 score 自动排序,支持范围查询 |
| 不重复 | member 唯一,但 score 可重复 |
| 实时排名 | 更新 score 后排名自动调整 |
| 双重索引 | 既能按 score 范围查,也能按 member 查 score |
| 上限 | 单个 ZSet 最多 2^32 - 1 个元素 |
2. 常用命令
Section titled “2. 常用命令”2.1. 基础读写
Section titled “2.1. 基础读写”# 写入ZADD rank 9850 "Alice" # 添加元素ZADD rank 8720 "Bob" 7600 "Carol" # 批量添加ZADD rank NX 6300 "Dave" # 不存在才添加ZADD rank XX 9000 "Alice" # 存在才更新 scoreZADD rank GT 9900 "Alice" # 仅当新 score 更大时更新(Redis 6.2+)
# 更新 scoreZINCRBY rank 100 "Bob" # Bob 的 score +100(原子操作)
# 删除ZREM rank "Dave" # 删除元素ZREMRANGEBYRANK rank 0 1 # 删除排名 0~1 的元素(最低分两个)ZREMRANGEBYSCORE rank 0 5000 # 删除 score 在 0~5000 之间的元素2.2. 查询
Section titled “2.2. 查询”# 按排名查询(升序,0 = 最低分)ZRANGE rank 0 -1 # 所有元素(score 从低到高)ZRANGE rank 0 -1 WITHSCORES # 同时返回 scoreZRANGE rank 0 2 REV # 降序(Redis 6.2+)
# 按排名查询(降序,0 = 最高分)ZREVRANGE rank 0 2 # 前 3 名(score 从高到低)ZREVRANGE rank 0 2 WITHSCORES
# 按 score 范围查询ZRANGEBYSCORE rank 7000 9000 # score 在 7000~9000 之间ZRANGEBYSCORE rank -inf +inf # 全部(-inf/+inf 表示无穷)ZRANGEBYSCORE rank 7000 9000 LIMIT 0 3 # 分页,跳过 0 条取 3 条
# 查询排名(升序排名,0 = 最低分)ZRANK rank "Alice" # → 3(从低到高,最低为 0)ZREVRANK rank "Alice" # → 0(从高到低,最高为 0)
# 查询 scoreZSCORE rank "Alice" # → 9850.0
# 统计ZCARD rank # 元素总数ZCOUNT rank 7000 9000 # score 在范围内的元素数量2.3. 命令速查
Section titled “2.3. 命令速查”| 命令 | 说明 | 复杂度 |
|---|---|---|
ZADD key score member | 添加/更新元素 | O(log N) |
ZINCRBY key n member | score 自增 | O(log N) |
ZREM key member | 删除元素 | O(log N) |
ZSCORE key member | 查询 score | O(1) |
ZRANK key member | 升序排名 | O(log N) |
ZREVRANK key member | 降序排名 | O(log N) |
ZRANGE key s e | 按排名范围查询 | O(log N + M) |
ZRANGEBYSCORE key min max | 按 score 范围查询 | O(log N + M) |
ZCARD key | 元素总数 | O(1) |
ZCOUNT key min max | score 范围内计数 | O(log N) |
3. 底层编码
Section titled “3. 底层编码”ZSet 底层编码├── listpack(紧凑列表)│ → 元素数 ≤ 128 且每个 member 长度 ≤ 64 字节│ → 内存紧凑,连续存储 (member, score) 对│└── skiplist(跳表 + 哈希表) → 超过阈值后自动升级 → 跳表:支持 O(log N) 的有序范围查询 → 哈希表:支持 O(1) 的 member → score 查询 → 两种结构共同维护,互为补充跳表结构示意:
level 3: [head] ─────────────────────────────▶ [Alice:9850] ──▶ nulllevel 2: [head] ────────────▶ [Bob:8720] ────▶ [Alice:9850] ──▶ nulllevel 1: [head] ─▶ [Dave:6300] ─▶ [Carol:7600] ─▶ [Bob:8720] ─▶ [Alice:9850] ──▶ null
查找 Bob:从 level 3 开始,跳过不必要节点,O(log N) 定位4. SpringBoot 实战
Section titled “4. SpringBoot 实战”4.1. 基础操作
Section titled “4.1. 基础操作”@Autowiredprivate StringRedisTemplate redisTemplate;
ZSetOperations<String, String> zsetOps = redisTemplate.opsForZSet();
// 添加/更新zsetOps.add("rank:weekly", "Alice", 9850);zsetOps.add("rank:weekly", "Bob", 8720);
// score 自增(原子操作)zsetOps.incrementScore("rank:weekly", "Bob", 100);
// 查询排名(降序,0 = 第 1 名)Long rank = zsetOps.reverseRank("rank:weekly", "Alice"); // → 0
// 查询 scoreDouble score = zsetOps.score("rank:weekly", "Alice");
// 获取前 N 名(降序)Set<String> top3 = zsetOps.reverseRange("rank:weekly", 0, 2);
// 带 score 的前 N 名Set<ZSetOperations.TypedTuple<String>> top3WithScore = zsetOps.reverseRangeWithScores("rank:weekly", 0, 2);
// 按 score 范围查询Set<String> range = zsetOps.rangeByScore("rank:weekly", 7000, 9000);4.2. 实时排行榜
Section titled “4.2. 实时排行榜”@Service@RequiredArgsConstructorpublic class RankService {
private final StringRedisTemplate redisTemplate; private static final String RANK_KEY = "rank:weekly";
// 更新分数(答题、购买等行为加分) public void addScore(String userId, double score) { redisTemplate.opsForZSet().incrementScore(RANK_KEY, userId, score); }
// 查询用户排名(从 1 开始) public long getRank(String userId) { Long rank = redisTemplate.opsForZSet().reverseRank(RANK_KEY, userId); return rank == null ? -1 : rank + 1; }
// 获取 Top N 榜单(含排名和分数) public List<RankVO> getTopN(int n) { Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate.opsForZSet().reverseRangeWithScores(RANK_KEY, 0, n - 1);
if (tuples == null) return Collections.emptyList();
List<RankVO> result = new ArrayList<>(); int rank = 1; for (ZSetOperations.TypedTuple<String> tuple : tuples) { result.add(new RankVO(rank++, tuple.getValue(), tuple.getScore())); } return result; }
@Data @AllArgsConstructor public static class RankVO { private int rank; private String userId; private Double score; }}4.3. 延迟队列
Section titled “4.3. 延迟队列”// 利用 score 存储执行时间戳,实现延迟任务
@Service@RequiredArgsConstructorpublic class DelayQueue {
private final StringRedisTemplate redisTemplate; private static final String DELAY_KEY = "delay:queue";
// 添加延迟任务(delaySeconds 秒后执行) public void push(String taskId, long delaySeconds) { double executeAt = System.currentTimeMillis() + delaySeconds * 1000; redisTemplate.opsForZSet().add(DELAY_KEY, taskId, executeAt); }
// 定时扫描到期任务(每秒执行一次) @Scheduled(fixedDelay = 1000) public void poll() { long now = System.currentTimeMillis(); // 取出 score <= now 的任务(已到期) Set<String> tasks = redisTemplate.opsForZSet() .rangeByScore(DELAY_KEY, 0, now);
if (tasks == null || tasks.isEmpty()) return;
for (String taskId : tasks) { // 删除成功才处理,防止多实例重复消费 Long removed = redisTemplate.opsForZSet().remove(DELAY_KEY, taskId); if (removed != null && removed > 0) { processTask(taskId); } } }
private void processTask(String taskId) { System.out.println("执行任务:" + taskId); }}5. 典型业务场景
Section titled “5. 典型业务场景”| 场景 | 实现方式 | 关键命令 |
|---|---|---|
| 积分排行榜 | score 存积分,ZREVRANGE 取前 N | ZINCRBY / ZREVRANGE |
| 热搜榜 | score 存热度值,定时衰减 | ZINCRBY / ZREVRANGEBYSCORE |
| 延迟队列 | score 存执行时间戳,定时轮询 | ZRANGEBYSCORE / ZREM |
| 时间线排序 | score 存时间戳,范围查询 | ZRANGEBYSCORE |
| 搜索自动补全 | 利用字典序范围查询 | ZRANGEBYLEX |