文章目录
- Redis的set和zset类及主要指令
-
- 一、Set(集合)
-
- [1. Set的特点](#1. Set的特点)
- [2. 内部实现](#2. 内部实现)
- [3. 常用命令](#3. 常用命令)
-
- [3.1 SADD](#3.1 SADD)
- [3.2 SREM](#3.2 SREM)
- [3.3 SMEMBERS](#3.3 SMEMBERS)
- [3.4 SISMEMBER](#3.4 SISMEMBER)
- [3.5 SCARD](#3.5 SCARD)
- [3.6 SPOP](#3.6 SPOP)
- [3.7 SRANDMEMBER](#3.7 SRANDMEMBER)
- [3.8 SMOVE](#3.8 SMOVE)
- [3.9 SINTER](#3.9 SINTER)
- [3.10 SUNION](#3.10 SUNION)
- [3.11 SDIFF](#3.11 SDIFF)
- [3.12 交集/并集/差集的 STORE 版本](#3.12 交集/并集/差集的 STORE 版本)
- [3.13 SSCAN](#3.13 SSCAN)
- 二、ZSet(有序集合)
-
- [1. ZSet的特点](#1. ZSet的特点)
- [2. 内部实现](#2. 内部实现)
- [3. 常用命令](#3. 常用命令)
-
- [3.1 ZADD](#3.1 ZADD)
- [3.2 ZREM](#3.2 ZREM)
- [3.3 ZSCORE](#3.3 ZSCORE)
- [3.4 ZRANK / ZREVRANK](#3.4 ZRANK / ZREVRANK)
- [3.5 ZCARD](#3.5 ZCARD)
- [3.6 ZRANGE](#3.6 ZRANGE)
- [3.7 ZREVRANGE(仍可用,建议用 ZRANGE ... REV 代替)](#3.7 ZREVRANGE(仍可用,建议用 ZRANGE ... REV 代替))
- [3.8 ZRANGEBYSCORE / ZREVRANGEBYSCORE](#3.8 ZRANGEBYSCORE / ZREVRANGEBYSCORE)
- [3.9 ZINCRBY](#3.9 ZINCRBY)
- [3.10 ZCOUNT](#3.10 ZCOUNT)
- [3.11 ZREMRANGEBYRANK](#3.11 ZREMRANGEBYRANK)
- [3.12 ZREMRANGEBYSCORE](#3.12 ZREMRANGEBYSCORE)
- [3.13 ZPOPMIN / ZPOPMAX](#3.13 ZPOPMIN / ZPOPMAX)
- [3.14 ZSCAN](#3.14 ZSCAN)
- [3.15 ZINTER / ZUNION(Redis 6.2+)](#3.15 ZINTER / ZUNION(Redis 6.2+))
- [三、小结:Set 和 ZSet 的核心差异](#三、小结: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- "blue"
- "green"
- "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- "green"
- "red"
上面 "yellow" 本来就不在集合里,所以 SREM 返回 1(只删了 "blue"),不是 2。
3.3 SMEMBERS
返回集合里所有的元素。注意:集合本身是无序的,所以每次返回的顺序不一定一样。
-
语法
SMEMBERS key
-
返回值:所有元素的列表,key 不存在时返回空列表
-
示例
redis> SADD colors "red" "blue" "green"
(integer) 3
redis> SMEMBERS colors- "blue"
- "green"
- "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) 0
3.5 SCARD
返回集合里的元素个数。相当于集合版的 LLEN。
-
语法
SCARD key
-
返回值:集合的基数(元素数量),key 不存在时返回 0
-
示例
redis> SADD colors "red" "blue" "green"
(integer) 3
redis> SCARD colors
(integer) 3
redis> SCARD nonexist
(integer) 0
3.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- "yellow"
- "red"
redis> SMEMBERS colors - "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- "blue"
- "green"
redis> SRANDMEMBER colors -3 - "green"
- "green"
- "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- "a"
- "c"
redis> SMEMBERS set2 - "b"
- "d"
- "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- "b"
- "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- "a"
- "b"
- "c"
- "d"
- "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- "a"
- "b"
redis> SDIFF set2 set1 - "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- "b"
- "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- "4"
-
- "purple"
- "blue"
redis> SSCAN colors 4
- "0"
-
- "orange"
- "green"
- "yellow"
- "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- "wangwu"
- "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) 3
3.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- "wangwu"
- "lisi"
- "zhangsan"
redis> ZRANGE rank 0 -1 WITHSCORES - "wangwu"
- "60"
- "lisi"
- "80"
- "zhangsan"
- "100"
redis> ZRANGE rank 0 0 - "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- "zhangsan"
- "100"
- "lisi"
- "80"
- "wangwu"
- "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- "lisi"
- "zhaoliu"
- "zhangsan"
redis> ZRANGEBYSCORE rank (80 100 - "zhaoliu"
- "zhangsan"
redis> ZRANGEBYSCORE rank -inf +inf - "wangwu"
- "lisi"
- "zhaoliu"
- "zhangsan"
redis> ZRANGEBYSCORE rank 70 100 LIMIT 1 2 - "zhaoliu"
- "zhangsan"
redis> ZREVRANGEBYSCORE rank 100 70 - "zhangsan"
- "zhaoliu"
- "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) 2
3.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- "zhaoliu"
- "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- "zhaoliu"
- "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- "wangwu"
- "60"
redis> ZPOPMAX rank 2 - "zhangsan"
- "100"
- "lisi"
- "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- "0"
-
- "wangwu"
- "60"
- "lisi"
- "80"
- "zhangsan"
- "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- "a"
redis> ZINTER 2 z1 z2 WITHSCORES - "a"
- "15"
redis> ZINTER 2 z1 z2 WEIGHTS 2 3 WITHSCORES - "a"
- "35"
redis> ZUNION 2 z1 z2 AGGREGATE MAX WITHSCORES - "a"
- "10"
- "b"
- "20"
- "c"
- "15"
- "a"
上面 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 做的事差不多。