Skip to content

有序集合

有序集合相对于哈希、列表、集合来说会有一点点陌生,但既然叫有序集合,那么它和集合必然有着联系,它保留了集合不能有重复成员的特性,但不同的是,有序集合的元素可以排序。
但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据。

有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能,合理的利用有序集合,能帮助我们在实际开发中解决很多问题

开发提示:

有序集合中的元素不能重复,但是 score 可以重复,就和一个班里的同学学号不能重复,但是考试成绩可以相同

命令

集合内

添加成员

zadd key score member [socre member...]

下面操作向有序集合 user:ranking 添加用户 tom 和他的分数 251:

1
2
127.0.0.1:6379> zadd user:ranking 251 tom
(integer) 1

返回结果代表成功添加成员的个数:

1
2
127.0.0.1:6379> zadd user:ranking 1 kris 91 mike 200 frank 220 tim 250 martin
(integer) 5

有关 zadd 命令有两点需要注意:

  • Redis3.2 为 zadd 命令添加了 nx、xx、ch、incr 四个选项
  • nx: member 必须不存在,才可以设置成功,用于添加
  • xx: member 必须存在,才可以设置成功,用于更新
  • ch: 返回此操作后,有序集合元素和分数发生变化的个数
  • incr: 对 score 做增加、相当于后面介绍的 zincrby
  • 有序集合相比集合提供了排序字段,但是也产生了代价,zadd 的时间复杂度为 O(log(n)),sadd 的时间复杂度为 O(1)

计算成员个数

zcard key

例如下面操作返回有序集合 user:ranking 的成员数为 5,和集合类型的 scard 命令一样,zcard 的时间复杂度为 O(1)

1
2
127.0.0.1:6379> zcard user:ranking
(integer) 6

获取某个成员的分数

zscore key member

tom 的分数为 251,如果成员不存在则返回 nil:

1
2
3
4
127.0.0.1:6379> zscore user:ranking tom
"251"
127.0.0.1:6379> zscore user:ranking test
(nil)

计算成员的排名

1
2
zrank key member
zrevrank key member

zrank 是从分数从低到高返回排名,zrevrank 反之。

1
2
3
4
127.0.0.1:6379> zrank user:ranking tom
(integer) 5
127.0.0.1:6379> zrevrank user:ranking tom
(integer) 0

删除成员

zrem key member [member ...]

下面操作将成员 mike 从有序集合 user:ranking 中删除

1
2
127.0.0.1:6379> zrem user:ranking mike
(integer) 1

返回结果为成功删除的个数

增加成员的分数

zincrby key increment member

下面操作给 tom 增加了 9 分,分数变为了 260 分:

1
2
127.0.0.1:6379> zincrby user:ranking 9 tom
"260"

返回指定排名范围的成员

1
2
zrange  key start end [withscores]
zrevrange key start end [withscores]

有序集合是按照分值排名的,zrange 是从低到高返回,zrevrange 反之。
下面代码返回排名最低的是三个成员,如果加上 withscores 选项,同时会返回成员的分数:

1
2
3
4
5
6
7
127.0.0.1:6379> zrange user:ranking 0 2 withscores
1) "kris"
2) "1"
3) "frank"
4) "200"
5) "tim"
6) "220"
1
2
3
4
5
6
7
127.0.0.1:6379> zrevrange user:ranking 0 2 withscores
1) "tom"
2) "260"
3) "martin"
4) "250"
5) "tim"
6) "220"

返回指定分数范围的成员

1
2
zrangebyscore key min max [withscores] [limit offset count]
zrevrangebyscore key max min [withscores] [limit offset count]

其中 zrangebyscore 按照分数从低到高返回,zrevrangebyscore 反之。
例如下面操作从低到高返回 200 到 221 分的成员,withscores 选项会同时返回每个成员的分数。
[limit offset count] 选项可以限制输出的起始位置和个数

1
2
3
4
5
6
7
8
9
127.0.0.1:6379> zrangebyscore user:ranking 200 inf withscores
1) "frank"
2) "200"
3) "tim"
4) "220"
5) "martin"
6) "250"
7) "tom"
8) "251"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
127.0.0.1:6379> zrevrangebyscore user:ranking 221 200 withscores
1) "tim"
2) "220"
3) "frank"
4) "200"
127.0.0.1:6379> zrevrangebyscore user:ranking 220 200 withscores
1) "tim"
2) "220"
3) "frank"
4) "200"

同时 min 和 max 还支持开区间(小括号)和闭区间(中括号),-inf 和 +inf 分别代表无限小和无限大:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
127.0.0.1:6379> zrangebyscore user:ranking (200 inf withscores
1) "tim"
2) "220"
3) "martin"
4) "250"
5) "tom"
6) "251"

127.0.0.1:6379> zrangebyscore user:ranking -inf 200 withscores
1) "kris"
2) "1"
3) "mike"
4) "91"
5) "frank"
6) "200"

返回指定分数范围成员个数

zcount key min max

下面操作返回 200 到 221 分的成员的个数

1
2
127.0.0.1:6379> zcount user:ranking 200 221
(integer) 2

删除指定排名内的升序元素

zremrangebyrank key start end

下面操作删除第 srart 到第 end 名的成员

1
2
127.0.0.1:6379> zremrangebyrank user:ranking 0 2
(integer) 3

删除指定分数范围的成员

zremrangebyscore key min max

下面操作将 250 分以上的成员全部删除,返回结果为成功删除的个数

1
2
127.0.0.1:6379> zremrangebyscore user:ranking (250 +inf
(integer) 1

集合间的操作

1
2
3
4
127.0.0.1:6379> zadd user:ranking:1 1 kris 91 mike 200 frank 220 tim 250 martin 251 tom
(integer) 6
127.0.0.1:6379> zadd user:ranking:2 8 james 77 mike 625 martin 888 tom
(integer) 4

交集

zinterstore destination numkeys key [key...] [weights weight [weight ...]] [aggregate sum | min | max]

这个命令参数较多,下面分别进行说明:

  • destination: 交集计算结果保存到这个键
  • numkeys: 需要做交集计算键的个数
  • key [key...]: 需要做交集计算的键
  • weights weight[weight...]: 每个键的权重,在做交集的计算时,每个键中的每个 member 会将自己分数乘以这个权重,每个键的权重默认是 1
  • aggregate sum | min | max: 计算成员交集后,分值可以按照 sum(和)、min(最小值)、max(最大值)做汇总,默认值是 sum

下面操作对 user:ranking:1user:ranking:2 做交集,weights 和 aggregate 使用了默认配置,可以看到目标键 user:ranking:1_inter_2 对分值做了 sum 操作:

1
2
3
4
5
6
7
8
9
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2
(integer) 3
127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "168"
3) "martin"
4) "875"
5) "tom"
6) "1139"

如果想让 user:ranking:2 的权重变为 0.5,并且聚合效果使用 max,可以执行如下操作:

1
2
3
4
5
6
7
8
9
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2 weights 1 0.5 aggregate max
(integer) 3
127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "91"
3) "martin"
4) "312.5"
5) "tom"
6) "444"

并集

zunionstore destination numkeys key [key...] [weights weight [weight...]] [aggregate sum | min | max]

该命令的所有参数和 zinterstore 是一致的,只不过是做并集计算,例如下面操作是计算 user:ranking:1user:ranking:2 的并集,weights 和 aggregate 使用了默认配置,可以看到目标键 user:ranking:1_union_2 对分值做了 sum 操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
127.0.0.1:6379> zunionstore user:ranking:1_union_2 2 user:ranking:1 user:ranking:2
(integer) 7
127.0.0.1:6379> zrange user:ranking:1_union_2 0 -1 withscores
 1) "kris"
 2) "1"
 3) "james"
 4) "8"
 5) "mike"
 6) "168"
 7) "frank"
 8) "200"
 9) "tim"
10) "220"
11) "martin"
12) "875"
13) "tom"
14) "1139"

内部编码

ziplist(压缩列表)

当有序集合的元素个数小于 zset-max-ziplist-entries 配置(默认 128 个),同时每个元素的值都小于 zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会用 ziplist 来作为有序集合的内部实现,ziplist 可以有效减少内存的使用。

skiplist(跳跃表)

当 ziplist 条件不满足时,有序集合会使用 skiplist 作为内部实现,因为此时 ziplist 的读写效率会下降

使用场景

有序集合比价典型的使用场景就是排行榜系统。
例如视频网站需要对用户上传的视频做排行榜,榜单的维度可能是多个方面的: 按照时间、按照播放量、按照获得的赞数。

下面使用赞数这个维度,记录每天用户上传视频的排行榜。主要需要实现以下 4 个功能

(1) 添加用户赞数

例如用户 mike 上传了一个视频,并获得了 3 个赞,可以使用有序集合的 zadd 和 zincrby 功能:

zadd user:ranking:2016_03_15 mike 3

如果之后再获得一个赞,可以使用 zincrby:

1
zincrby user:ranking:2016_03_15 mike 1

(2) 取消用户赞数

由于各种原因(例如用户注销、用户作弊)需要将用户删除,此时需要将用户从榜单中删除掉,可以使用 zrem。例如删除成员 tom:

1
zrem user:ranking:2016_03_15 mike

(3) 展示获取赞数最多的十个用户

此功能使用 zrevrange 命令实现:

1
zrevrangebyrank user:ranking:2016_03_15 0 9

(4) 展示用户信息以及用户信息

此功能将用户名作为键后缀,将用户信息保存在哈希类型中,至于用户的分数和排名可以使用 zscore 和 zrank 两个功能:

1
2
3
4
hgetall user:info:tom    

zscore user:ranking:2016_03_15 mike
zrank usre:ranking:2016_03_15 mike