python 操作redis之六(有序集合)

有序集合使用与内部实现原理

有序集合类型 (Sorted Set) 相比于集合类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序结合的元素值,一个是排序值。有序集合的存储元素值也是不能重复的,但分值是可以重复的。

当我们把学生的成绩存储在有序集合中时,它的存储结构如下图所示:

学生存储值.png

下面我们先从有序集合的使用开始说起。

1 代码实战

 

from redis import StrictRedis

redis_cli = StrictRedis(host="xx", port=xx, password="xx", db=xx, decode_responses=True)

# 1)添加一个或多个元素
redis_cli.zadd("zset", {"mem1": 10, "mem2": 6, "mem3": 7})

# 2)查询所有元素列表
print(redis_cli.zrange("zset", 0, -1))  # ['mem2', 'mem3', 'mem1']

# 3)查询有序集合的总个数
num = redis_cli.zcard("zset")
print(num)

# 4)删除一个或多个元素(根据元素值) (删除命令中如果包含了不存在的元素,并不会影响命令的正常执行,不存在的元素将会被忽略。)
redis_cli.zrem("zset", "mem3", "mem1")
print(redis_cli.zrange("zset", 0, -1))

# 5)查询某元素的 score 值
redis_cli.zadd("zset1", {"m3": 5, "m1": 10, "m2": 3, "m4": 7})
score = redis_cli.zscore("zset1", "m1")
print(score)

# 6)查询 score 区间内元素
mems = redis_cli.zrangebyscore("zset1", 2, 5)
print(mems)

# 7)查询 score 区间内的元素个数
zcount1 = redis_cli.zcount("zset1", 2, 7)
print(zcount1)

# 8)给元素的 score 值增加值
m1_score = redis_cli.zscore("zset1", "m1")
redis_cli.zincrby("zset1", 10, "m1")
m1_score_after = redis_cli.zscore("zset1", "m1")
print(m1_score, m1_score_after)

# 9)查询某元素排名
redis_cli.zadd("zset2", {"me1": 3, "me2": 1, "me3": 0, "me4": 2})
rank = redis_cli.zrank("zset2", "me4")
print(rank)  # 可以看出,排名是从 0 开始的,排名可以理解为元素排序后的下标值。

# 10)查询某元素倒序排名
des_rank = redis_cli.zrevrank("zset2", "me4")
print(des_rank)

# 11)根据排名删除元素
print(redis_cli.zrange("zset2", 0, -1))
redis_cli.zremrangebyrank("zset2", 1, 2)
print(redis_cli.zrange("zset2", 0, -1))

# 12)删除 score 区间内的元素
redis_cli.zadd("zset3", {"mm1": 100, "mm2": 80, "mm3": 90, "mm4": 70})
print(redis_cli.zrange("zset3", 0, -1))
redis_cli.zremrangebyscore("zset3", 20, 70)
print(redis_cli.zrange("zset3", 0, -1))

# 13)复制交集元素到新集合(''' 获取两个有序集合的交集并放入dest集合,如果遇到相同值不同分数,则按照aggregate进行操作     aggregate的值为: SUM  MIN  MAX''')
redis_cli.zadd("zset4", {"mm1": 100, "mm2": 80, "mm3": 90, "mm4": 70})
redis_cli.zadd("zset5", {"mm5": 100, "mm7": 80, "mm3": 90, "mm2": 70})

redis_cli.zinterstore("dest", ("zset4", "zset5"), aggregate="MIN")
print(redis_cli.zrange("dest", 0, -1))

# 14)复制并集元素到新集合
redis_cli.zunionstore("uni", ("zset4", "zset5"))
print(redis_cli.zrange("uni", 0, -1))

2 内部实现

有序集合是由 ziplist (压缩列表) 或 skiplist (跳跃表) 组成的。

1)ziplist

当数据比较少时,有序集合使用的是 ziplist 存储的,如下代码所示:

127.0.0.1:6379> zadd myzset 1 db 2 redis 3 mysql
(integer) 3
127.0.0.1:6379> object encoding myzset
"ziplist"

从结果可以看出,有序集合把 myset 键值对存储在 ziplist 结构中了。 有序集合使用 ziplist 格式存储必须满足以下两个条件:

  • 有序集合保存的元素个数要小于 128 个;
  • 有序集合保存的所有元素成员的长度都必须小于 64 字节。

如果不能满足以上两个条件中的任意一个,有序集合将会使用 skiplist 结构进行存储。 接下来我们来测试以下,当有序集合中某个元素长度大于 64 字节时会发生什么情况? 代码如下:

127.0.0.1:6379> zadd zmaxleng 1.0 redis
(integer) 1
127.0.0.1:6379> object encoding zmaxleng
"ziplist"
127.0.0.1:6379> zadd zmaxleng 2.0 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
(integer) 1
127.0.0.1:6379> object encoding zmaxleng
"skiplist"

通过以上代码可以看出,当有序集合保存的所有元素成员的长度大于 64 字节时,有序集合就会从 ziplist 转换成为 skiplist。

小贴士:可以通过配置文件中的 zset-max-ziplist-entries(默认 128)和 zset-max-ziplist-value(默认 64)来设置有序集合使用 ziplist 存储的临界值。

2)skiplist

skiplist 数据编码底层是使用 zset 结构实现的,而 zset 结构中包含了一个字典和一个跳跃表,源码如下:

typedef struct zset {
    dict *dict;
    zskiplist *zsl;
} zset;

更多关于跳跃表的源码实现,会在后面的章节详细介绍。

① 跳跃表实现原理

跳跃表的结构如下图所示:

有序集合-跳跃表.png

根据以上图片展示,当我们在跳跃表中查询值 32 时,执行流程如下:

  • 从最上层开始找,1 比 32 小,在当前层移动到下一个节点进行比较;
  • 7 比 32 小,当前层移动下一个节点比较,由于下一个节点指向 Null,所以以 7 为目标,移动到下一层继续向后比较;
  • 18 小于 32,继续向后移动查找,对比 77 大于 32,以 18 为目标,移动到下一层继续向后比较;
  • 对比 32 等于 32,值被顺利找到。

从上面的流程可以看出,跳跃表会想从最上层开始找起,依次向后查找,如果本层的节点大于要找的值,或者本层的节点为 Null 时,以上一个节点为目标,往下移一层继续向后查找并循环此流程,直到找到该节点并返回,如果对比到最后一个元素仍未找到,则返回 Null。

② 为什么是跳跃表?而非红黑树?

因为跳跃表的性能和红黑树基本相近,但却比红黑树更好实现,所有 Redis 的有序集合会选用跳跃表来实现存储。

3使用场景

有序集合的经典使用场景如下:

  • 学生成绩排名
  • 粉丝列表,根据关注的先后时间排序

4 小结

通过本文的学习我们了解到,有序集合具有唯一性和排序的功能,排序功能是借助分值字段 score 实现的,score 字段不仅可以实现排序功能,还可以实现数据的赛选与过滤的功能。我们还了解到了有序集合是由 压缩列表 (ziplist) 或跳跃列表 (skiplist) 来存储的,当元素个数小于 128 个,并且所有元素的值都小于 64 字节时,有序集合会采取 ziplist 来存储,反之则会用 skiplist 来存储,其中 skiplist 是从上往下、从前往后进行元素查找的,相比于传统的普通列表,可能会快很多,因为普通列表只能从前往后依次查找。

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值