【redis】zset有序集合详解
zset有序集合详解
- 一、zset有序集合的介绍
- 二、zset有序集合的常用指令
- 2.1 ZADD
- 2.2 ZCARD
- 2.3 ZCOUNT
- 2.4 ZRANGE
- 2.5 ZREVRANGE
- 2.6 ZRANGEBYSCORE
- 2.7 ZPOPMAX
- 2.8 BZPOPMAX
- 2.9 ZPOPMIN
- 2.10 BZPOPMIN
- 2.11 ZRANK
- 2.12 ZREVRANK
- 2.13 ZSCORE
- 2.14 ZREM
- 2.15 ZREMRANGEBYRANK
- 2.16 ZREMRANGEBYSCORE
- 2.17 ZINCRBY
- 2.18 ZINTERSTORE
- 2.19 ZUNIONSTORE
- 三、set有序集合命令小结
- 四、内部编码
- 4.1压缩列表
- 4.2跳表
- 五、应用场景
一、zset有序集合的介绍
有序集合相对于字符串、列表、哈希、集合来说会有⼀些陌⽣。它保留了集合不能有重复成员的特点,但与集合不同的是,有序集合中的每个元素都有⼀个唯⼀的浮点类型的分数(score)与之关联,这使得有序集合中的元素是可以维护有序性的,但这个有序不是⽤下标作为排序依据⽽是⽤这个分数。例如:
有序集合提供了获取指定分数和元素范围查找、计算成员排名等功能,合理地利⽤有序集合,可以帮助我们在实际开发中解决很多问题。有序集合中的元素是不能重复的,但分数允许重复。类⽐于⼀次考试之后,每个⼈⼀定有⼀个唯⼀的分数,但分数允许相同
二、zset有序集合的常用指令
2.1 ZADD
添加或者更新指定的元素以及关联的分数到 zset 中,分数应该符合 double 类型,+inf/-inf 作为正负极限也是合法的
语法:
ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member...]
ZADD 的相关选项说明:
- XX:仅仅⽤于更新已经存在的元素,不会添加新元素
- NX:仅⽤于添加新元素,不会更新已经存在的元素
- CH:默认情况下,ZADD 返回的是本次添加的元素个数,但指定这个选项之后,就会包含本次更新的元素的个数
- INCR:此时命令类似 ZINCRBY 的效果,将元素的分数加上指定的分数。此时只能指定⼀个元素和分数
返回值:本次添加成功的元素个数
使用样例:
127.0.0.1:6379> zadd key 1 'one'
(integer) 1
127.0.0.1:6379> zadd key 2 'two'
(integer) 1
127.0.0.1:6379> zrange key 0 -1 withscores
1) "one"
2) "1"
3) "two"
4) "2"
127.0.0.1:6379> zadd key 10 one 20 two 30 three
(integer) 1
127.0.0.1:6379> zrange key 0 -1 withscores
1) "one"
2) "10"
3) "two"
4) "20"
5) "three"
6) "30"
2.2 ZCARD
获取⼀个 zset 的基数(cardinality),即 zset 中的元素个数
语法:
ZCARD key
返回值:zset 内的元素个数
使用样例:
127.0.0.1:6379> zadd key 1 'one' 2 'two'
(integer) 2
127.0.0.1:6379> zcard key
(integer) 2
2.3 ZCOUNT
返回分数在 min 和 max 之间的元素个数,默认情况下,min 和 max 都是包含的,可以通过(
排除
语法:
ZCOUNT key min max
返回值:满⾜条件的元素列表个数
使用样例:
127.0.0.1:6379> zadd key 1 one 2 two 3 three
(integer) 3
127.0.0.1:6379> zcount key 1 3
(integer) 3
127.0.0.1:6379> zcount key (1 (3
(integer) 1
2.4 ZRANGE
返回指定区间⾥的元素,分数按照升序。带上 WITHSCORES 可以把分数也返回
语法:
ZRANGE key start stop [WITHSCORES]
返回值:区间内的元素列表
使用样例:
127.0.0.1:6379> zadd key 1 'one'
(integer) 1
127.0.0.1:6379> zadd key 2 'two'
(integer) 1
127.0.0.1:6379> zrange key 0 -1 withscores
1) "one"
2) "1"
3) "two"
4) "2"
2.5 ZREVRANGE
返回指定区间⾥的元素,分数按照降序。带上 WITHSCORES 可以把分数也返回
语法:
ZREVRANGE key start stop [WITHSCORES]
返回值:区间内的元素列表
使用样例:
127.0.0.1:6379> zadd key 1 one 2 two 3 three
(integer) 3
127.0.0.1:6379> zrevrange key 0 -1 withscores
1) "three"
2) "3"
3) "two"
4) "2"
5) "one"
6) "1"
2.6 ZRANGEBYSCORE
返回分数在 min 和 max 之间的元素,默认情况下,min 和 max 都是包含的,可以通过 ( 排除
语法:
ZRANGEBYSCORE key min max [WITHSCORES]
返回值:区间内的元素列表
使用样例:
127.0.0.1:6379> zadd key 1 one 2 two 3 three
(integer) 3
127.0.0.1:6379> zrangebyscore key 1 3
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> zrangebyscore key (1 (3
1) "two"
2.7 ZPOPMAX
删除并返回分数最⾼的 count 个元素
语法:
ZPOPMAX key [count]
返回值:分数和元素列表
使用样例:
127.0.0.1:6379> zadd key 1 one 2 two 3 three
(integer) 3
127.0.0.1:6379> zpopmax key 3
1) "three"
2) "3"
3) "two"
4) "2"
5) "one"
6) "1"
2.8 BZPOPMAX
ZPOPMAX 的阻塞版本 ,如果没有元素那么就会阻塞timeout秒等待元素到来,如果没有元素到来那么就返回nil,否则返回到来的元素
语法:
BZPOPMAX key [key ...] timeout
返回值:元素列表
使用样例:
127.0.0.1:6379> zadd key 3 three
(integer) 1
127.0.0.1:6379> bzpopmax key 2
1) "key"
2) "three"
3) "3"
127.0.0.1:6379> bzpopmax key 2
(nil)
(2.02s)
2.9 ZPOPMIN
删除并返回分数最低的 count 个元素
语法:
ZPOPMIN key [count]
返回值:分数和元素列表
使用样例:
127.0.0.1:6379> zadd key 1 one 2 two 3 three
(integer) 3
127.0.0.1:6379> zpopmin key
1) "one"
2) "1"
127.0.0.1:6379> zpopmin key
1) "two"
2) "2"
2.10 BZPOPMIN
删除并返回分数最低的 count 个元素
语法:
ZPOPMIN key [count]
返回值:分数和元素列表
使用样例:
127.0.0.1:6379> zadd key 3 three
(integer) 1
127.0.0.1:6379> bzpopmin key 2
1) "key"
2) "three"
3) "3"
127.0.0.1:6379> bzpopmin key 2
(nil)
(2.04s)
2.11 ZRANK
返回指定元素的排名,升序
语法:
ZRANK key member
返回值:升序排名
使用样例:
127.0.0.1:6379> zadd key 1 one 2 two 3 three
(integer) 3
127.0.0.1:6379> zrank key two
(integer) 1
127.0.0.1:6379> zrank key three
(integer) 2
127.0.0.1:6379> zrank key one
(integer) 0
2.12 ZREVRANK
返回指定元素的排名,降序
语法:
ZREVRANK key member
返回值:降序排名
使用样例:
127.0.0.1:6379> zadd key 1 one 2 two 3 three
(integer) 3
127.0.0.1:6379> zrevrank key one
(integer) 2
127.0.0.1:6379> zrevrank key two
(integer) 1
127.0.0.1:6379> zrevrank key three
(integer) 0
2.13 ZSCORE
返回指定元素的分数
语法:
ZSCORE key member
返回值:分数
使用样例:
127.0.0.1:6379> zadd key 1 one
(integer) 1
127.0.0.1:6379> zscore key one
"1"
2.14 ZREM
删除指定的元素
语法:
ZREM key member [member ...]
返回值:本次操作删除的元素个数
使用样例:
127.0.0.1:6379> zadd key 1 one 2 two 3 three
(integer) 3
127.0.0.1:6379> zrem key one two three
(integer) 3
127.0.0.1:6379> zrange key 0 -1
(empty array)
2.15 ZREMRANGEBYRANK
按照排序,升序删除指定范围的元素,左闭右闭
语法:
ZREMRANGEBYRANK key start stop
返回值:本次操作删除的元素个数
使用样例:
127.0.0.1:6379> zadd key 1 one 2 two 3 three
(integer) 3
127.0.0.1:6379> zremrangebyrank key 0 1
(integer) 2
127.0.0.1:6379> zrange key 0 -1
1) "three"
2.16 ZREMRANGEBYSCORE
按照分数删除指定范围的元素,左闭右闭
语法:
ZREMRANGEBYSCORE key min max
返回值:本次操作删除的元素个数
使用样例:
127.0.0.1:6379> zadd key 1 one 2 two 3 three
(integer) 3
127.0.0.1:6379> zremrangebyscore key 2 3
(integer) 2
127.0.0.1:6379> zrange key 0 -1
1) "one"
2.17 ZINCRBY
为指定的元素的关联分数添加指定的分数值
语法:
ZINCRBY key increment member
返回值:增加后元素的分数
使用样例:
127.0.0.1:6379> zadd key 1 one
(integer) 1
127.0.0.1:6379> zincrby key 2 one
"3"
127.0.0.1:6379> zrange key 0 1 withscores
1) "one"
2) "3"
2.18 ZINTERSTORE
求出给定有序集合中元素的交集并保存进⽬标有序集合中,在合并过程中以元素为单位进⾏合并,元素对应的分数按照不同的聚合⽅式和权重得到新的分数
语法:
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight[weight ...]] [AGGREGATE <SUM | MIN | MAX>]
- numkeys:表示比较多少个集合
- weights:表示权重,默认值为1
返回值:⽬标集合中的元素个数
使用样例:
127.0.0.1:6379> zadd key 1 one 2 two
(integer) 2
127.0.0.1:6379> zadd key1 1 one 3 three
(integer) 2
#得到的分数 = key * 2 + key1 * 3
127.0.0.1:6379> zinterstore key2 2 key key1 weights 2 3
(integer) 1
127.0.0.1:6379> zrange key2 0 -1 withscores
1) "one"
2) "5"
2.19 ZUNIONSTORE
求出给定有序集合中元素的并集并保存进⽬标有序集合中,在合并过程中以元素为单位进⾏合并,元素对应的分数按照不同的聚合⽅式和权重得到新的分数
语法:
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight[weight ...]] [AGGREGATE <SUM | MIN | MAX>]
- numkeys:表示比较多少个集合
- weights:表示权重,默认值为1
返回值:⽬标集合中的元素个数
使用样例:
127.0.0.1:6379> zadd key 1 one 2 two
(integer) 2
127.0.0.1:6379> zadd key1 1 one 3 three
(integer) 2
#得到的分数 = key * 2 + key1 * 3
127.0.0.1:6379> zunionstore key2 2 key key1 weights 2 3
(integer) 3
127.0.0.1:6379> zrange key2 0 -1 withscores
1) "two"
2) "4"
3) "one"
4) "5"
5) "three"
6) "9"
三、set有序集合命令小结
命令 | 时间复杂度 |
---|---|
zadd key score member [score member …] | O(k * log(n)),k 是添加成员的个数,n 是当前有序集合的元素个数 |
zcard key | O(1) |
zscore key member | O(1) |
zrank key member zrevrank key member | O(log(n)),n 是当前有序集合的元素个数 |
zrem key member [member …] | O(k * log(n)),k 是删除成员的个数,n 是当前有序集合的元素个数 |
zincrby key increment member | O(log(n)),n 是当前有序集合的元素个数 |
zrange key start end [withscores] zrevrange key start end [withscores] | O(k + log(n)),k 是获取成员的个数,n 是当前有序集合的元素个数 |
zrangebyscore key min max [withscores] zrevrangebyscore key max min [withscores] | O(k + log(n)),k 是获取成员的个数,n 是当前有序集合的元素个数 |
zcount | O(log(n)),n 是当前有序集合的元素个数 |
zremrangebyrank key start end | O(k + log(n)),k 是获取成员的个数,n 是当前有序集合的元素个数 |
zremrangebyscore key min max | O(k + log(n)),k 是获取成员的个数,n 是当前有序集合的元素个数 |
zinterstore destination numkeys key [key …] | O(n * k) + O(m * log(m)),n 是输⼊的集合最⼩的元素个数, k 是集合个数, m 是⽬标集合元素个数 |
zunionstore destination numkeys key [key …] | O(n) + O(m * log(m)),n 是输⼊集合总元素个数,m 是⽬标集合元素个数 |
四、内部编码
有序集合类型的内部编码有两种:
-
ziplist(压缩列表):当有序集合的元素个数⼩于 zset-max-ziplist-entries 配置(默认 128 个),同时每个元素的值都⼩于 zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会⽤ ziplist 来作为有序集合的内部实现,ziplist 可以有效减少内存的使⽤。
-
skiplist(跳表):当 ziplist 条件不满⾜时,有序集合会使⽤ skiplist 作为内部实现,因为此时 ziplist 的操作效率会下降
- 使用ziplist压缩列表的条件:
当 Zset 存储的元素数量小于 zset-max-ziplist-entries
的值,且所有元素的最大长度小于 zset-max-ziplist-value
的值时,Redis 会选择使用压缩列表作为底层实现。压缩列表占用的内存较少,但是在需要修改数据时,可能需要对整个压缩列表进行重写,性能较低
- 使用skiplist跳表的条件:
当 Zset 存储的元素数量超过 zset-max-ziplist-entries
的值,或者任何元素的长度超过 zset-max-ziplist-value
的值时,Redis 会将底层结构从压缩列表转换为跳跃表。跳跃表的查找和修改数据的性能较高,但是占用的内存也较多
4.1压缩列表
压缩列表是一种为节省内存而设计的特殊编码结构,它将所有的元素和分数紧凑地存储在一起。这种方式的优点是占用内存少,但是在需要修改数据时,可能需要对整个压缩列表进行重写,性能较低。当 Zset 存储的元素数量较少,且元素的字符串长度较短时,Redis 会选择使用压缩列表作为底层实现。
一个压缩列表的结构如下:
在 Zset 中,每个元素和它的分数都会作为一个独立的元素存储在压缩列表中,元素和分数会交替存储,即第一个元素是成员,第二个元素是分数,第三个元素是成员,第四个元素是分数,以此类推
4.2跳表
跳跃表是一种可以进行快速查找的有序数据结构,它通过维护多级索引来实现快速查找。这种方式的优点是查找和修改数据的性能较高,但是占用的内存也较多。当 Zset 存储的元素数量较多,或者元素的字符串长度较长时,Redis 会选择使用跳跃表作为底层实现
跳表基本结构:
// 跳表节点结构
typedef struct zskiplistNode {
sds ele; // 元素值
double score; // 分数
struct zskiplistNode *backward; // 后退指针
struct zskiplistLevel {
struct zskiplistNode *forward; // 前进指针
unsigned long span; // 跨度
} level[]; // 层级数组
} zskiplistNode;
// 跳表头结构
typedef struct zskiplist {
struct zskiplistNode *header, *tail; // 头尾节点
unsigned long length; // 长度
int level; // 最大层数
} zskiplist;
跳跃表的查找、插入和删除操作的时间复杂度都是 O(logN),其中 N 是跳跃表中的元素数量。这使得跳跃表在处理大量数据时具有很高的性能。跳表在链表的基础上增加了多级索引,通过多级索引位置的跳跃,实现了快速查找元素
五、应用场景
Redis 的 Zset(有序集合)类型在许多场景中都非常有用,以下是一些常见的应用场景:
-
排行榜:Zset 非常适合用于实现各种排行榜。例如,你可以将用户的 ID 作为元素,用户的分数作为分数,然后使用 Zset 来存储和排序所有用户的分数。你可以很容易地获取到分数最高的用户,或者获取到任何用户的排名。
-
带权重的队列:Zset 可以用于实现带权重的队列。例如,你可以将任务作为元素,任务的优先级作为分数,然后使用 Zset 来存储和排序所有的任务。你可以很容易地获取到优先级最高的任务,或者按优先级顺序执行任务。
-
延时队列:你可以将需要延时处理的任务作为元素,任务的执行时间作为分数,然后使用 Zset 来存储和排序所有的任务。你可以定期扫描 Zset,处理已经到达执行时间的任务
-
用户的点赞数:将用户的id作为元素,用户的点赞数作为分数,这样就可以实现用户点赞数的增长