有序集合 Sorted Set
本教程共 40 篇 · 第 10 篇 · 更新于 2026-08-02
10. 有序集合 Sorted Set
本节目标:
- 理解 Sorted Set 是「成员唯一 + 按分数 score 排序」的集合
- 掌握写入与读取:
ZADD/ZRANGE/ZREVRANGE/ZSCORE/ZRANK- 掌握按分数区间查询:
ZRANGEBYSCORE/ZCOUNT/ZRANGEBYLEX- 掌握原子增减:
ZINCRBY,以及交集并集ZINTERSTORE/ZUNIONSTORE- 用排行榜、延迟队列等场景理解 Sorted Set 的用武之地
Sorted Set(有序集合)在 Set「成员唯一」的基础上,给每个成员关联了一个 double 类型的分数(score)。Redis 根据分数从小到大自动为成员排序,因此你可以随时按排名、按分数区间取数据。它同时具备「集合去重」和「列表有序」的优点,是最适合做排行榜的数据类型。
几个要点:
- 成员(member)唯一,但分数(score)可以重复——多个成员可以并列同一分数,此时按 member 的字典序排序。
- 底层编码为
listpack(小数据)或skiplist + hashtable(大数据),查询、插入、删除平均 O(log N)。 - 插入、更新分数都会自动重排,无需应用层维护顺序。
写入与按排名读取
用 ZADD 添加成员和分数:
命令:
127.0.0.1:6379> ZADD leaderboard 100 alice 200 bob 150 carol 200 dave
输出:
(integer) 4
返回值表示新加入的成员数(已存在的成员若分数变化属于「更新」,不计入新增)。注意 bob 和 dave 分数都是 200,并列。
按排名区间取成员用 ZRANGE(从小到大,0 是第一名)。加 WITHSCORES 可同时返回分数:
命令:
127.0.0.1:6379> ZRANGE leaderboard 0 -1 WITHSCORES
输出:
1) "alice"
2) "100"
3) "carol"
4) "150"
5) "bob"
6) "200"
7) "dave"
8) "200"
倒序取(从高到低)用 ZREVRANGE:
命令:
127.0.0.1:6379> ZREVRANGE leaderboard 0 2 WITHSCORES
输出:
1) "bob"
2) "200"
3) "dave"
4) "200"
5) "carol"
6) "150"
取某个成员的分数与排名:ZSCORE / ZRANK(升序排名,从 0 起)/ ZREVRANK(降序排名):
命令:
127.0.0.1:6379> ZSCORE leaderboard bob
127.0.0.1:6379> ZRANK leaderboard carol
输出:
"200"
(integer) 2
按分数区间查询
ZRANGEBYSCORE 按分数范围返回成员,-inf / +inf 表示无穷:
命令:
127.0.0.1:6379> ZRANGEBYSCORE leaderboard 150 200 WITHSCORES
输出:
1) "carol"
2) "150"
3) "bob"
4) "200"
5) "dave"
6) "200"
只统计某个分数段有多少人用 ZCOUNT:
命令:
127.0.0.1:6379> ZCOUNT leaderboard 150 200
输出:
(integer) 3
按「字典区间」查询用 ZRANGEBYLEX(要求所有成员分数相同,否则结果无意义),适合做范围分页:
命令:
127.0.0.1:6379> ZRANGEBYLEX lexset [a [c
输出:
1) "alice"
2) "bob"
3) "carol"
提示:自 Redis 6.2 起,官方推荐用统一的
ZRANGE语法配合BYSCORE/BYLEX选项来替代独立的ZRANGEBYSCORE、ZRANGEBYLEX(旧的命令为了兼容仍可使用,但文档优先推荐新写法)。例如ZRANGE leaderboard 150 200 BYSCORE等价于ZRANGEBYSCORE。Redis 8.x 继续沿用这一推荐。
原子增减与交集/并集
给某个成员的分数加增量用 ZINCRBY——它做原子自增,是排行榜实时加分的核心:
命令:
127.0.0.1:6379> ZINCRBY leaderboard 50 alice
127.0.0.1:6379> ZSCORE leaderboard alice
输出:
"150"
"150"
alice 从 100 加到了 150。
多集合之间也能做加权交集/并集,并写回新 key:ZINTERSTORE / ZUNIONSTORE。例如把「本周榜」和「历史总榜」合并成总积分:
命令:
127.0.0.1:6379> ZUNIONSTORE total 2 weekRank allRank WEIGHTS 1 2
输出:
(integer) 4
WEIGHTS 给每个源集合的分数乘上权重后相加,非常灵活。
实战场景
场景一:游戏 / 活动排行榜。 玩家得分用 ZADD,加分用 ZINCRBY,取 Top N 用 ZREVRANGE 0 9,取某玩家名次用 ZREVRANK,全部 O(log N),轻松支撑实时榜单。
场景二:延迟队列( Delayed Queue)。 把「任务 ID」作为 member、把「应执行的时间戳」作为 score,ZRANGEBYSCORE key -inf <now> LIMIT 0 N 取出到期任务处理。多个 worker 抢任务时配合 ZREM 或 Lua 保证只被一个处理。这是轻量延迟任务的经典做法。
场景三:热度 / 权重排序。 把文章、商品按热度分排序展示;用 ZUNIONSTORE 把「点击」「收藏」「评论」按不同权重合成一个综合热度分。
常见误区
- 误区:score 必须是整数。 score 是 double,
ZINCRBY也支持浮点增量(如ZINCRBY board 0.5 x)。 - 误区:并列同分顺序随机。 同分成员按 member 字典序排列,是确定的,不是随机。
- 误区:ZADD 只能新增。
ZADD对已存在 member 会更新其分数并重排,返回 0(不算新增);若只想在不存在时插入,可用NX/XX选项。 - 误区:ZRANGEBYSCORE 参数顺序无所谓。 必须是「小分数 大分数」,写成
(200表示「小于 200 不含 200」的开区间写法也支持。
ZADD 进阶选项与分数注意点
ZADD 还提供几个实用的修饰选项,能覆盖不少并发场景:NX 仅在 member 不存在时才添加;XX 仅在已存在时才更新;GT 仅当新分数大于现有分数才更新;LT 仅当新分数小于现有分数才更新;CH 让返回值表示「发生变化的成员数(含新增与更新)」而不是只算新增数。利用 GT / LT,可以在多个客户端同时写分时实现「分数只升不降」或「只降不升」,避免一条迟到的旧请求把新分数冲掉。
关于分数本身有两点提醒:第一,score 是双精度浮点数,存在精度上限,极大或极精细的小数可能丢失末尾精度,不要拿它做要求完全精确的小数比较;第二,当多个 member 同分时,Redis 按 member 的字典序稳定排序,这个顺序是确定的、不是随机的,可以通过人为设计 member 命名来影响并列时的先后。把 Sorted Set 当作延迟队列时,建议 score 用「未来执行时间戳」,member 携带任务 ID 保证唯一,避免同分带来的顺序歧义。
另一个常见组合是配合 ZCARD(取成员总数)、ZLEXCOUNT(字典区间计数)做分页与统计。当排行榜需要跨周期(日/周/总榜)时,用不同 key(如 leaderboard:20260802)隔离每日数据,再用 ZUNIONSTORE 带 WEIGHTS 跨天合并,就能灵活支持「日榜、周榜、总榜」多种视图,而无需在应用层重新汇总。
删除也是常见操作:按排名区间删用 ZREMRANGEBYRANK,按分数区间删用 ZREMRANGEBYSCORE,按字典区间删用 ZREMRANGEBYLEX,这些在「清理过期榜单、裁剪历史区间」时很方便。另外,ZRANGE / ZREVRANGE 都支持 LIMIT offset count,可以配合做「翻页」——但要注意基于排名的翻页在频繁写入时可能出现「同一元素翻到两页」的偏移漂移,对强一致翻页需求应改用基于游标或分数游标的方案。
小结
- Sorted Set 是「成员唯一 + 按 score 排序」的集合,适合一切排行榜与带权重排序。
- 写入/读取:
ZADD/ZRANGE/ZREVRANGE/ZSCORE/ZRANK/ZREVRANK。 - 区间:
ZRANGEBYSCORE/ZCOUNT/ZRANGEBYLEX(或统一用ZRANGE ... BYSCORE/BYLEX)。 - 增减/聚合:
ZINCRBY/ZINTERSTORE/ZUNIONSTORE(支持WEIGHTS)。 - 用武之地:排行榜、延迟队列、综合热度排序。
提示:关于版本——本教程正文统一以 Redis 8.x(最新稳定版)为准;官方 redis.io 下载页另标 8.8 为 “Latest stable”,而 GitHub 上 redis/redis 的最新发布 tag 为 8.10.0,二者同属 8.x,命令与类型差异对教材影响极小。