文章目录
- Redis的set和zset类及主要指令
- 一、Set(集合)
- 1. Set的特点
- 2. 内部实现
- 3. 常用命令
- 3.1 SADD
- 3.2 SREM
- 3.3 SMEMBERS
- 3.4 SISMEMBER
- 3.5 SCARD
- 3.6 SPOP
- 3.7 SRANDMEMBER
- 3.8 SMOVE
- 3.9 SINTER
- 3.10 SUNION
- 3.11 SDIFF
- 3.12 交集/并集/差集的 STORE 版本
- 3.13 SSCAN
- 二、ZSet(有序集合)
- 1. ZSet的特点
- 2. 内部实现
- 3. 常用命令
- 3.1 ZADD
- 3.2 ZREM
- 3.3 ZSCORE
- 3.4 ZRANK / ZREVRANK
- 3.5 ZCARD
- 3.6 ZRANGE
- 3.7 ZREVRANGE(仍可用,建议用 ZRANGE ... REV 代替)
- 3.8 ZRANGEBYSCORE / ZREVRANGEBYSCORE
- 3.9 ZINCRBY
- 3.10 ZCOUNT
- 3.11 ZREMRANGEBYRANK
- 3.12 ZREMRANGEBYSCORE
- 3.13 ZPOPMIN / ZPOPMAX
- 3.14 ZSCAN
- 3.15 ZINTER / ZUNION(Redis 6.2+)
- 三、小结:Set 和 ZSet 的核心差异
Redis的set和zset类及主要指令
一、Set(集合)
1. Set的特点
- 无序:集合里的元素没有先后顺序,不能通过下标访问
- 唯一:集合里的元素不会重复,同一个元素插多次也只保留一份
- 支持集合间的交、并、差运算,适合做共同好友、标签聚合这类场景
2. 内部实现
| 编码 | 说明 |
|---|---|
| intset(整数集合) | 元素全是整数且数量较少时使用,内存连续,查找用二分,O(log n) |
| hashtable(哈希表) | 元素不全是整数或数量超过阈值时使用,也就是只用键、值都设为 NULL 的哈希表 |
什么时候 intset 会转成 hashtable?
- 插入了非整数的元素
- 元素数量超过
set-max-intset-entries(默认 512)这个转换是单向的,就算后来把非整数元素删了,也不会再降回 intset。所以如果确认集合里全是整数且数量可控,intset 能省不少内存。
3. 常用命令
3.1 SADD
往集合里添加元素。已经存在的元素会被忽略,不会报错。
- 语法
SADD key member [member ...]返回值:实际新增的元素个数(已存在的元素不算)
示例
redis> SADD colors "red" (integer) 1 redis> SADD colors "blue" "green" (integer) 2 redis> SADD colors "red" (integer) 0 redis> SMEMBERS colors 1) "blue" 2) "green" 3) "red"注意返回值的含义:第一次 SADD “red” 返回 1,因为确实新增了;第二次再 SADD “red” 返回 0,说明它已经在集合里了,不是报错,只是没干事。
3.2 SREM
从集合里移除元素。元素不存在也不会报错。
- 语法
SREM key member [member ...]返回值:实际移除的元素个数(不存在的元素不算)
示例
redis> SADD colors "red" "blue" "green" (integer) 3 redis> SREM colors "blue" "yellow" (integer) 1 redis> SMEMBERS colors 1) "green" 2) "red"上面 “yellow” 本来就不在集合里,所以 SREM 返回 1(只删了 “blue”),不是 2。
3.3 SMEMBERS
返回集合里所有的元素。注意:集合本身是无序的,所以每次返回的顺序不一定一样。
- 语法
SMEMBERS key返回值:所有元素的列表,key 不存在时返回空列表
示例
redis> SADD colors "red" "blue" "green" (integer) 3 redis> SMEMBERS colors 1) "blue" 2) "green" 3) "red"**⚠️ 注意:**SMEMBERS 跟 hash 里的 HGETALL 一样,属于"重"操作。如果集合里有几十上百万个元素,一次性全拉出来会阻塞 Redis。大集合遍历请用 SSCAN 分批次拿。
3.4 SISMEMBER
判断某个元素是不是在集合里。
- 语法
SISMEMBER key member返回值:1 表示存在,0 表示不存在(包括 key 不存在的情况)
示例
redis> SADD colors "red" "blue" (integer) 2 redis> SISMEMBER colors "red" (integer) 1 redis> SISMEMBER colors "green" (integer) 0 redis> SISMEMBER nonexist "red" (integer) 03.5 SCARD
返回集合里的元素个数。相当于集合版的 LLEN。
- 语法
SCARD key返回值:集合的基数(元素数量),key 不存在时返回 0
示例
redis> SADD colors "red" "blue" "green" (integer) 3 redis> SCARD colors (integer) 3 redis> SCARD nonexist (integer) 03.6 SPOP
随机从集合里弹出一个(或多个)元素,弹出后会从集合中删除。这跟 list 的 LPOP/RPOP 不一样——list 是从固定方向弹,set 是随机弹。
- 语法
SPOP key [count]返回值:弹出的元素(或元素列表),key 不存在 / 空集合返回 nil
示例
redis> SADD colors "red" "blue" "green" "yellow" (integer) 4 redis> SPOP colors "blue" redis> SPOP colors 2 1) "yellow" 2) "red" redis> SMEMBERS colors 1) "green"Redis 3.2 之后支持传 count 参数,一次随机弹出多个。count 不能为负数。
3.7 SRANDMEMBER
随机返回集合里的元素,但不删除。SPOP 是"拿走",SRANDMEMBER 是"看看"。
- 语法
SRANDMEMBER key [count]返回值:不传 count 返回一个元素;传了返回列表
示例
redis> SADD colors "red" "blue" "green" (integer) 3 redis> SRANDMEMBER colors "green" redis> SRANDMEMBER colors 2 1) "blue" 2) "green" redis> SRANDMEMBER colors -3 1) "green" 2) "green" 3) "blue"count 参数跟 HRANDFIELD 一样:
- count 为正:返回的元素不重复,最多返回整个集合
- count 为负:返回的元素可能重复,凑够 |count| 个为止
3.8 SMOVE
把某个元素从源集合移动到目标集合。这是一个原子操作——要么两端都成功,要么都不动。
- 语法
SMOVE source destination member返回值:1 表示移动成功,0 表示源集合里没有这个元素
示例
redis> SADD set1 "a" "b" "c" (integer) 3 redis> SADD set2 "d" "e" (integer) 2 redis> SMOVE set1 set2 "b" (integer) 1 redis> SMEMBERS set1 1) "a" 2) "c" redis> SMEMBERS set2 1) "b" 2) "d" 3) "e" redis> SMOVE set1 set2 "x" (integer) 0如果目标集合已经存在这个元素,SMOVE 也只会把源集合里的删掉,目标集合不会出现重复(集合本来就不允许重复)。
3.9 SINTER
求多个集合的交集,返回同时出现在所有集合里的元素。
- 语法
SINTER key [key ...]返回值:交集的元素列表
示例
redis> SADD set1 "a" "b" "c" (integer) 3 redis> SADD set2 "b" "c" "d" (integer) 3 redis> SADD set3 "b" "c" "e" (integer) 3 redis> SINTER set1 set2 set3 1) "b" 2) "c"3.10 SUNION
求多个集合的并集,把所有集合的元素合在一起,自动去重。
- 语法
SUNION key [key ...]返回值:并集的元素列表
示例
redis> SADD set1 "a" "b" "c" (integer) 3 redis> SADD set2 "d" "e" (integer) 2 redis> SUNION set1 set2 1) "a" 2) "b" 3) "c" 4) "d" 5) "e"3.11 SDIFF
求多个集合的差集,返回在第一个集合里但不在后续集合里的元素。
- 语法
SDIFF key [key ...]返回值:差集的元素列表
示例
redis> SADD set1 "a" "b" "c" "d" (integer) 4 redis> SADD set2 "c" "d" "e" (integer) 3 redis> SDIFF set1 set2 1) "a" 2) "b" redis> SDIFF set2 set1 1) "e"顺序很重要:
SDIFF A B和SDIFF B A结果完全不同,前者是"在 A 但不在 B",后者是"在 B 但不在 A"。
3.12 交集/并集/差集的 STORE 版本
这三个集合运算都有对应的STORE版本,差别是结果不直接返回,而是存到 destination 里。适合中间结果要多次使用的场景,不用每次都重新算。
SINTERSTORE destination key [key ...] SUNIONSTORE destination key [key ...] SDIFFSTORE destination key [key ...]返回值:结果集合的元素个数
示例
redis> SADD set1 "a" "b" "c" (integer) 3 redis> SADD set2 "b" "c" "d" (integer) 3 redis> SINTERSTORE inter set1 set2 (integer) 2 redis> SMEMBERS inter 1) "b" 2) "c"3.13 SSCAN
分批次迭代集合里的元素,是遍历大集合的正确姿势。用法跟 SCAN 一样。
- 语法
SSCAN key cursor [MATCH pattern] [COUNT count]返回值:下一轮的游标和当前批次的元素列表。游标返回 0 时表示遍历结束。
示例
redis> SADD colors "red" "blue" "green" "yellow" "purple" "orange" (integer) 6 redis> SSCAN colors 0 MATCH *e* COUNT 2 1) "4" 2) 1) "purple" 2) "blue" redis> SSCAN colors 4 1) "0" 2) 1) "orange" 2) "green" 3) "yellow" 4) "red"游标不一定连续,不能用 for 循环递增——每次用上一轮返回的游标值就行,0 代表结束。
二、ZSet(有序集合)
1. ZSet的特点
- 跟 set 一样,member 唯一不重复
- 但每个 member 关联一个score(分值),元素按 score 从小到大排列
- score 可以重复——多个 member 可以有相同的分数,这时候按 member 的字典序排
- 适合排行榜、延迟队列、带权重的标签这类既要去重又要排序的场景
2. 内部实现
| 编码 | 说明 |
|---|---|
| ziplist / listpack | 元素少且短时使用,member 和 score 交替紧凑存放,省内存 |
| skiplist(跳表)+ hashtable | 数据量大时使用。跳表负责按 score 排序,hashtable 负责 O(1) 查 member 的 score |
什么时候切换?
- 元素数量超过
zset-max-ziplist-entries(或zset-max-listpack-entries)- 某个 member 的长度超过
zset-max-ziplist-value(或zset-max-listpack-value)这个切换逻辑跟 hash 是一样的:少量数据用紧凑结构省内存,数据多了切跳表保证查询性能。
为什么需要跳表 + 哈希表一起上?
跳表按 score 范围查很高效(ZRANGE、ZRANGEBYSCORE),但按 member 查 score 的时候得遍历,O(log n) 虽然也不差,但配合一个 hashtable 直接 O(1) 更划算。Redis 是拿空间换时间——反正 hashtable 只存 member→score 的指针,实际数据不存两份,开销没那么大。
3. 常用命令
3.1 ZADD
往有序集合里添加 member 和 score。member 已存在时会更新 score。
- 语法
ZADD key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]选项说明:
- NX:只在 member 不存在时才添加
- XX:只在 member 存在时才更新
- GT:只在新 score > 旧 score 时才更新
- LT:只在新 score < 旧 score 时才更新
- CH:返回值改成"被修改的元素数"(新增 + 更新都算),默认只统计新增
- INCR:对 member 的 score 做增量操作,此时只能操作一个 member
返回值:默认返回新增的 member 数;加了 CH 返回变更数;加了 INCR 返回新的 score
示例
redis> ZADD rank 100 "zhangsan" 80 "lisi" 60 "wangwu" (integer) 3 redis> ZADD rank 90 "lisi" (integer) 0 redis> ZADD rank CH 200 "zhangsan" (integer) 1 redis> ZADD rank NX 70 "lisi" (integer) 0第一个 ZADD 返回 3,三个都是新增。第二个 ZADD 更新了 lisi 的 score,但只算新增所以返回 0。第三个加了 CH 参数,“zhangsan” 的 score 被更新了,算一次变更。第四个 NX 了 “lisi”,但它已经存在,不执行,返回 0。
3.2 ZREM
从有序集合里删除 member。
- 语法
ZREM key member [member ...]返回值:实际删除的 member 数
示例
redis> ZADD rank 100 "zhangsan" 80 "lisi" 60 "wangwu" (integer) 3 redis> ZREM rank "lisi" "zhaoliu" (integer) 1 redis> ZRANGE rank 0 -1 1) "wangwu" 2) "zhangsan"3.3 ZSCORE
返回某个 member 的 score。member 不存在返回 nil。
- 语法
ZSCORE key member返回值:score 的值(字符串格式的浮点数),member 不存在返回 nil
示例
redis> ZADD rank 100 "zhangsan" 80 "lisi" (integer) 2 redis> ZSCORE rank "zhangsan" "100" redis> ZSCORE rank "wangwu" (nil)3.4 ZRANK / ZREVRANK
ZRANK 返回 member 在有序集合中的升序排名(从 0 开始,score 最小的排第 0)。ZREVRANK 相反,按 score 降序排名(score 最大的排第 0)。
- 语法
ZRANK key member ZREVRANK key member返回值:排名,member 不存在返回 nil
示例
redis> ZADD rank 100 "zhangsan" 80 "lisi" 60 "wangwu" (integer) 3 redis> ZRANK rank "lisi" (integer) 1 redis> ZREVRANK rank "lisi" (integer) 1“lisi” 的 score=80,升序排第二(0: wangwu/60, 1: lisi/80, 2: zhangsan/100),降序也排第二(0: zhangsan/100, 1: lisi/80, 2: wangwu/60)。
3.5 ZCARD
返回有序集合里的元素个数。
- 语法
ZCARD key返回值:member 数量,key 不存在返回 0
示例
redis> ZADD rank 100 "zhangsan" 80 "lisi" 60 "wangwu" (integer) 3 redis> ZCARD rank (integer) 33.6 ZRANGE
按 score 升序返回指定下标范围的 member。默认只返回 member,加 WITHSCORES 同时返回 score。
- 语法
ZRANGE key start stop [BYSCORE | BYLEX] [REV] [LIMIT offset count] [WITHSCORES]返回值:member 的列表(带 WITHSCORES 时 member、score 交替)
示例
redis> ZADD rank 100 "zhangsan" 80 "lisi" 60 "wangwu" (integer) 3 redis> ZRANGE rank 0 -1 1) "wangwu" 2) "lisi" 3) "zhangsan" redis> ZRANGE rank 0 -1 WITHSCORES 1) "wangwu" 2) "60" 3) "lisi" 4) "80" 5) "zhangsan" 6) "100" redis> ZRANGE rank 0 0 1) "wangwu"Redis 6.2 之后 ZRANGE 功能更丰富了:加了 REV 的效果跟 ZREVRANGE 一样,加了 BYSCORE 就跟 ZRANGEBYSCORE 一样,加了 BYLEX 就跟 ZRANGEBYLEX 一样。不过为了兼容老代码,旧的 ZREVRANGE、ZRANGEBYSCORE 这些命令依然能用。
3.7 ZREVRANGE(仍可用,建议用 ZRANGE … REV 代替)
按 score 降序返回指定下标范围的 member。
- 语法
ZREVRANGE key start stop [WITHSCORES]返回值:member 列表
示例
redis> ZADD rank 100 "zhangsan" 80 "lisi" 60 "wangwu" (integer) 3 redis> ZREVRANGE rank 0 -1 WITHSCORES 1) "zhangsan" 2) "100" 3) "lisi" 4) "80" 5) "wangwu" 6) "60"3.8 ZRANGEBYSCORE / ZREVRANGEBYSCORE
按 score 范围过滤 member。ZRANGEBYSCORE 是升序,ZREVRANGEBYSCORE 是降序。支持开闭区间。
- 语法
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
(表示开区间:(80表示 score > 80,不包括 80
-inf和+inf表示负无穷和正无穷ZREVRANGEBYSCORE 写的是
max在前min在后,因为它从大到小排
返回值:满足条件的 member 列表
示例
redis> ZADD rank 100 "zhangsan" 80 "lisi" 60 "wangwu" 90 "zhaoliu" (integer) 4 redis> ZRANGEBYSCORE rank 70 100 1) "lisi" 2) "zhaoliu" 3) "zhangsan" redis> ZRANGEBYSCORE rank (80 100 1) "zhaoliu" 2) "zhangsan" redis> ZRANGEBYSCORE rank -inf +inf 1) "wangwu" 2) "lisi" 3) "zhaoliu" 4) "zhangsan" redis> ZRANGEBYSCORE rank 70 100 LIMIT 1 2 1) "zhaoliu" 2) "zhangsan" redis> ZREVRANGEBYSCORE rank 100 70 1) "zhangsan" 2) "zhaoliu" 3) "lisi"3.9 ZINCRBY
把 member 的 score 加上一个增量。member 不存在就当它 score 为 0 再加。
- 语法
ZINCRBY key increment member返回值:加完后的 score
示例
redis> ZADD rank 100 "zhangsan" 80 "lisi" (integer) 2 redis> ZINCRBY rank 10 "lisi" "90" redis> ZINCRBY rank 200 "wangwu" "200" redis> ZINCRBY rank -15 "zhangsan" "85"排行榜场景里 ZINCRBY 很实用——用户得分变化时直接调用就完了,不用先 ZSCORE 拿到旧值再加回去,一步到位。
3.10 ZCOUNT
统计 score 在指定区间里的 member 个数。
- 语法
ZCOUNT key min max返回值:符合条件的 member 数量
示例
redis> ZADD rank 100 "zhangsan" 80 "lisi" 60 "wangwu" 90 "zhaoliu" (integer) 4 redis> ZCOUNT rank 70 100 (integer) 3 redis> ZCOUNT rank (80 100 (integer) 23.11 ZREMRANGEBYRANK
按排名范围删除 member。下标从 0 开始。
- 语法
ZREMRANGEBYRANK key start stop返回值:删除的 member 个数
示例
redis> ZADD rank 100 "zhangsan" 80 "lisi" 60 "wangwu" 90 "zhaoliu" (integer) 4 redis> ZREMRANGEBYRANK rank 0 1 (integer) 2 redis> ZRANGE rank 0 -1 1) "zhaoliu" 2) "zhangsan"3.12 ZREMRANGEBYSCORE
按 score 范围删除 member。
- 语法
ZREMRANGEBYSCORE key min max返回值:删除的 member 个数
示例
redis> ZADD rank 100 "zhangsan" 80 "lisi" 60 "wangwu" 90 "zhaoliu" (integer) 4 redis> ZREMRANGEBYSCORE rank 60 80 (integer) 2 redis> ZRANGE rank 0 -1 1) "zhaoliu" 2) "zhangsan"3.13 ZPOPMIN / ZPOPMAX
弹出 score 最小(或最大)的 member。可以一次弹出多个。
- 语法
ZPOPMIN key [count] ZPOPMAX key [count]返回值:弹出 member 和 score 的交替列表
示例
redis> ZADD rank 100 "zhangsan" 80 "lisi" 60 "wangwu" (integer) 3 redis> ZPOPMIN rank 1) "wangwu" 2) "60" redis> ZPOPMAX rank 2 1) "zhangsan" 2) "100" 3) "lisi" 4) "80"ZPOPMIN/ZPOPMAX 在 Redis 5.0 引入。做简单的延迟队列或者任务调度时很好用——比如用 score 存时间戳,ZPOPMIN 弹出最早该执行的任务。
3.14 ZSCAN
跟 SSCAN 一样,分批次迭代有序集合。返回 member 和 score 交替排列。
- 语法
ZSCAN key cursor [MATCH pattern] [COUNT count]返回值:下一轮游标和当前批次的 member-score 列表
示例
redis> ZADD rank 100 "zhangsan" 80 "lisi" 60 "wangwu" (integer) 3 redis> ZSCAN rank 0 1) "0" 2) 1) "wangwu" 2) "60" 3) "lisi" 4) "80" 5) "zhangsan" 6) "100"3.15 ZINTER / ZUNION(Redis 6.2+)
有序集合的交集和并集。比直接使用 ZINTERSTORE/ZUNIONSTORE 多了"直接返回结果"的选项。
- 语法
ZINTER numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] [WITHSCORES] ZUNION numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] [WITHSCORES]参数说明:
- WEIGHTS:每个集合的权重,做运算前会把 score 先乘上对应权重
- AGGREGATE:多个集合里相同 member 的 score 怎么合并。SUM 求和(默认)、MIN 取最小、MAX 取最大
- 示例
redis> ZADD z1 10 "a" 20 "b" (integer) 2 redis> ZADD z2 5 "a" 15 "c" (integer) 2 redis> ZINTER 2 z1 z2 1) "a" redis> ZINTER 2 z1 z2 WITHSCORES 1) "a" 2) "15" redis> ZINTER 2 z1 z2 WEIGHTS 2 3 WITHSCORES 1) "a" 2) "35" redis> ZUNION 2 z1 z2 AGGREGATE MAX WITHSCORES 1) "a" 2) "10" 3) "b" 4) "20" 5) "c" 6) "15"上面 ZINTER 里 “a” 的 score 是 15,因为默认 AGGREGATE 是 SUM:10 × 1 + 5 × 1 = 15。加了 WEIGHTS 2 3 之后:10 × 2 + 5 × 3 = 35。ZUNION 跟的是 MAX 策略,各 member 取最大的 score。
三、小结:Set 和 ZSet 的核心差异
| 维度 | Set(集合) | ZSet(有序集合) |
|---|---|---|
| 有没有顺序 | 无序,遍历顺序不稳定 | 按 score 升序排列,score 相同按字典序 |
| 元素组成 | 只有 member | member + score,score 是双精度浮点数 |
| 唯一性 | member 唯一 | member 唯一,但 score 可以重复 |
| 内部编码 | intset / hashtable | listpack(ziplist) / skiplist + hashtable |
| 典型场景 | 标签、共同好友、去重、随机抽奖 | 排行榜、延迟队列、按时间排序的 feed |
| 核心操作 | 交/并/差集(SINTER/SUNION/SDIFF) | 按 score 范围查(ZRANGEBYSCORE)、按排名查(ZRANK) |
| 内存占用 | 相对小,纯整数场景下 intset 非常省 | 稍大,跳表指针 + hashtable 有额外开销 |
总结:
如果只需要"有没有"(去重、标签、交集),用 Set;
如果还需要"谁排前面"(排行榜、优先级、时间线),用 ZSet。
两者的选择说到底就是一个问题:要不要排序。不要排序就 Set,要排序就 ZSet。ZSet 多出来的那个 score,本质上就是排序用的"权重",没了它 ZSet 跟 Set 做的事差不多。