跳转到内容

Redis 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 个元素
Terminal window
# 写入
ZADD rank 9850 "Alice" # 添加元素
ZADD rank 8720 "Bob" 7600 "Carol" # 批量添加
ZADD rank NX 6300 "Dave" # 不存在才添加
ZADD rank XX 9000 "Alice" # 存在才更新 score
ZADD rank GT 9900 "Alice" # 仅当新 score 更大时更新(Redis 6.2+)
# 更新 score
ZINCRBY rank 100 "Bob" # Bob 的 score +100(原子操作)
# 删除
ZREM rank "Dave" # 删除元素
ZREMRANGEBYRANK rank 0 1 # 删除排名 0~1 的元素(最低分两个)
ZREMRANGEBYSCORE rank 0 5000 # 删除 score 在 0~5000 之间的元素
Terminal window
# 按排名查询(升序,0 = 最低分)
ZRANGE rank 0 -1 # 所有元素(score 从低到高)
ZRANGE rank 0 -1 WITHSCORES # 同时返回 score
ZRANGE 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)
# 查询 score
ZSCORE rank "Alice" # → 9850.0
# 统计
ZCARD rank # 元素总数
ZCOUNT rank 7000 9000 # score 在范围内的元素数量
命令说明复杂度
ZADD key score member添加/更新元素O(log N)
ZINCRBY key n memberscore 自增O(log N)
ZREM key member删除元素O(log N)
ZSCORE key member查询 scoreO(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 maxscore 范围内计数O(log N)
ZSet 底层编码
├── listpack(紧凑列表)
│ → 元素数 ≤ 128 且每个 member 长度 ≤ 64 字节
│ → 内存紧凑,连续存储 (member, score) 对
└── skiplist(跳表 + 哈希表)
→ 超过阈值后自动升级
→ 跳表:支持 O(log N) 的有序范围查询
→ 哈希表:支持 O(1) 的 member → score 查询
→ 两种结构共同维护,互为补充

跳表结构示意:

level 3: [head] ─────────────────────────────▶ [Alice:9850] ──▶ null
level 2: [head] ────────────▶ [Bob:8720] ────▶ [Alice:9850] ──▶ null
level 1: [head] ─▶ [Dave:6300] ─▶ [Carol:7600] ─▶ [Bob:8720] ─▶ [Alice:9850] ──▶ null
查找 Bob:从 level 3 开始,跳过不必要节点,O(log N) 定位
@Autowired
private 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
// 查询 score
Double 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);
@Service
@RequiredArgsConstructor
public 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;
}
}
// 利用 score 存储执行时间戳,实现延迟任务
@Service
@RequiredArgsConstructor
public 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);
}
}
场景实现方式关键命令
积分排行榜score 存积分,ZREVRANGE 取前 NZINCRBY / ZREVRANGE
热搜榜score 存热度值,定时衰减ZINCRBY / ZREVRANGEBYSCORE
延迟队列score 存执行时间戳,定时轮询ZRANGEBYSCORE / ZREM
时间线排序score 存时间戳,范围查询ZRANGEBYSCORE
搜索自动补全利用字典序范围查询ZRANGEBYLEX