redis的有序集合对象是什么
在redis中,有一个有序集合的数据结构,采用key+score,根据score进行排序。具体有两种实现方式,分别是ziplist和skiplist。
什么情况下使用ziplist和skiplist呢
- 有序集合保存的元素数量小于128个
- 有序集合保持的所有元素对象的长度都小于64个字节
当然,上面两个属性都是可配置的,分别是zset-max-ziplist-entries选项 和 zset-max-ziplist-value 选项
使用
zadd
将一个或者多个member元素及其score放到有序集合中
zadd key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]
[NX|XX] :可选参数,NX表示不存在时添加,XX 总是添加,默认NX
[GT|LT] :可选参数,表示元素存在时,GT表示大于原score 进行操作,LT是小于
[CH]:返回集合的大小变化,默认不返回
[INCR] : 表示将score加到现有score,而不是替换
注意:GT, LT,NX不能同时使用
示例:
127.0.0.1:6379> zadd test 1 a 2 b 3 c 4 d
(integer) 0
127.0.0.1:6379> zrange test 0 100
1) "a"
2) "b"
3) "c"
4) "d"
127.0.0.1:6379> zrange test 0 100 withscores
1) "a"
2) "1"
3) "b"
4) "2"
5) "c"
6) "3"
7) "d"
8) "4"
zrange
返回有序集中的元素
zrange key start stop [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]
start stop score 区间
BYSCORE BYLEX 根据分数或者字典表排序
REV 倒序
LIMIT 分页
WITHSCORES 打印分数
示例:
127.0.0.1:6379> zrange test 1000 0 byscore rev limit 1 2 WITHSCORES
1) "c"
2) "3"
3) "b"
4) "2"
zincrby
增加有序集合中某个对象的score
zincrby key increment member
示例:
127.0.0.1:6379> zrange test 0 100
1) "b"
2) "c"
3) "d"
4) "a"
127.0.0.1:6379> zrange test 0 100 withscores
1) "b"
2) "2"
3) "c"
4) "3"
5) "d"
6) "4"
7) "a"
8) "11"
zrem
删除一个或者多个有序集合中的对象
zrem key member [member ...]
示例:
127.0.0.1:6379> zrem test a c
(integer) 2
127.0.0.1:6379> zrange test 0 100 withscores
1) "b"
2) "2"
3) "d"
4) "4"
zcount
统计分数区间的元素个数
zcount key min max
示例:
127.0.0.1:6379> zcount test 0 100
(integer) 2
127.0.0.1:6379>
zrank
返回对象在有序集合中的排名
zrank key member [WITHSCORE]
示例:
127.0.0.1:6379> zrank test b
(integer) 0
127.0.0.1:6379> zrank test a
(nil)
127.0.0.1:6379> zrank test c
(nil)
127.0.0.1:6379> zrank test d
(integer) 1
127.0.0.1:6379>
ziplist 数据结构
ziplist使用紧凑的数据结构来存储对象,例如在联系的数值中,第一个位置存在key1,第二个位置存储socre1,以此类推,最终的数据结构如图所示:
zlbytes:记录整个压缩列表占用的字节数,用于地址重分配和获取尾节点
zltail:记录压缩列表尾结点距离头节点的距离
zllen:压缩列表的长度,当这个值等于UINT16_MAX(65535)时,需要遍历整个列表才能计算得出
zlend:特殊标识压缩列表的结尾
zset 数据结构
zset底层实现上既使用了跳表还使用了字典,代码如下:
typedef struct set
{
zskiplist *zs1;
dict *dict;
} zet;
关于为什么要在结构中同时使用跳表和字典,大概有下面的两个原因:
- 首先字典可以实现查找某个key 时间复杂度O(1),跳表可以实现某些范围操作就会需要消耗至少o(NlongN)的时间和o(n)的空间
- 同时字典和跳表可以通过指针共享地址,不会存在额外的内存浪费
最终的数据结构如图所示:
参考: 《Redis 设计与实现》黄健宏 著