


专栏:Redis 修行录
个人主页:手握风云
目录
[一、Zset 类型](#一、Zset 类型)
[1.1. 概述与特点](#1.1. 概述与特点)
[1.2. 添加与更新命令](#1.2. 添加与更新命令)
[1. ZADD](#1. ZADD)
[2. ZINCRBY](#2. ZINCRBY)
[1.3. 查询与获取命令](#1.3. 查询与获取命令)
[1. ZCARD](#1. ZCARD)
[2. ZCOUNT](#2. ZCOUNT)
[3. ZRANGE](#3. ZRANGE)
[4. ZRANK](#4. ZRANK)
[5. ZREVRANK](#5. ZREVRANK)
[6. ZSCORE](#6. ZSCORE)
[1.4. 删除与弹出命令](#1.4. 删除与弹出命令)
[1. ZPOPMAX 和 ZPOPMIN](#1. ZPOPMAX 和 ZPOPMIN)
[2. BZPOPMAX 和 BZPOPMIN](#2. BZPOPMAX 和 BZPOPMIN)
[3. ZREM](#3. ZREM)
[4. ZREMRANGEBYRANK](#4. ZREMRANGEBYRANK)
[5. ZREMRANGEBYSCORE](#5. ZREMRANGEBYSCORE)
[1.5. 集合间操作命令](#1.5. 集合间操作命令)
[1.6. 内部编码方式](#1.6. 内部编码方式)
[1.7. 典型使用场景](#1.7. 典型使用场景)
一、Zset 类型
1.1. 概述与特点
Redis 中的 Zset(有序集合)是一种重要的数据结构,它保留了集合(Set)不能包含重复成员的特点。与普通集合不同的是,Zset 中的每个元素都会关联一个唯一的浮点类型的分数(score)。Zset 正是通过这个分数来维护元素的有序性,而不是使用索引下标作为排序依据。在 Zset 中,元素是唯一的,但是分数允许重复(类似于考试成绩,每个人分数唯一,但允许多人同分)。
与列表(List)和集合(Set)的对比:列表(List)允许重复元素,有序(基于索引下标),常用于时间轴、消息队列等场景。集合(Set),不允许重复元素,无序,常用于标签、社交等场景。有序集合(Zset):不允许重复元素,有序(基于分数),常用于排行榜系统、社交等场景。
1.2. 添加与更新命令
1. ZADD
bash
ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member...]
ZADD 命令用于向有序集合(sorted set)中添加一个或多个指定的元素及其关联的分数,如果该键不存在,则会创建一个新的有序集合。该命令操作的分数需要符合双精度浮点数(double)的类型,同时也支持使用 +inf 和 -inf 作为正负极限的有效值。在执行效率上,ZADD 每操作一个项目的时间复杂度为 O(log(N)),其中 N 为当前有序集合中元素的数量。通过搭配不同的参数选项,ZADD 可以实现更灵活的操作控制:例如,使用 XX 参数可以限制命令仅更新已存在的元素而不添加新元素,而 NX 参数则指定仅添加新元素而不更新已有的元素。此外,LT 和 GT 参数分别规定只有当新提供分数小于或大于当前分数时,才会对已有元素进行更新。在返回值方面,默认情况下 ZADD 返回的是本次成功添加的新元素个数,但如果指定了 CH 参数,返回值将变为所有发生更改(包括新增的元素和分数被更新的已有元素)的元素总数。最后,如果使用了 INCR 参数,ZADD 的行为将类似于 ZINCRBY 命令,作用是将指定元素的分数加上给定的数值,但需要注意的是,在此模式下每次只能指定一个元素和分数对。
bash
# 向键名为 rank 的有序集合中添加一个元素 "Alice",分数为 100
ZADD rank 100 Alice
# 一次性向 rank 中添加多个元素(Bob 90分,Cindy 85分)
ZADD rank 90 Bob 85 Cindy
# 尝试添加 Alice(分数 110),但如果 Alice 已经存在,则操作无效,分数不会更新
ZADD rank NX 110 Alice
# 尝试添加 Dave(分数 70),如果 Dave 不存在,则添加成功
ZADD rank NX 70 Dave
# 更新已存在的 Bob 的分数为 95(如果 Bob 不存在,则操作无效)
ZADD rank XX 95 Bob
# 尝试更新不存在的 Eve 的分数,操作会被忽略
ZADD rank XX 80 Eve
ZADD rank CH 105 Alice
ZADD rank CH 70 Dave

2. ZINCRBY
bash
ZINCRBY key increment member
ZINCRBY 命令主要用于为有序集合(sorted set)中指定元素的关联分数加上给定的增量值。如果指定的元素在集合中不存在,该命令会将其添加到集合中,并把给定的增量值作为其初始分数(即假定其原始分数为 0.0)。如果指定的键完全不存在,则会创建一个新的有序集合,并将该元素作为唯一成员加入。需要注意的是,当操作的键存在但并非有序集合类型时,该命令会返回错误。在分数值类型方面,增量值应当是数字的字符串表示形式,并且支持双精度浮点数,同时用户也可以通过传入负数值来实现对分数的递减操作。在执行效率与版本支持上,ZINCRBY 的时间复杂度为 O(log(N)),其中 N 代表当前有序集合中的元素总数,该命令自 Redis 1.2.0 版本起便可使用。最后,该命令执行成功后,会返回对应元素更新后的新分数。
bash
# 给 Alice 的分数增加 20
ZINCRBY rank 20 Alice
# 集合中原本没有 Frank,执行后 Frank 被加进集合中并初始化为 50
ZINCRBY rank 50 Frank
# 给 Bob 扣除 15 分
ZINCRBY rank -15 Bob
# 给 Cindy 增加 2.5 分
ZINCRBY rank 2.5 Cindy

1.3. 查询与获取命令
1. ZCARD
bash
ZCARD key
ZCARD 命令主要用于获取有序集合(sorted set)中的元素个数(即集合的基数)。当用户指定一个键进行查询时,该命令会返回存储在该键中的有序集合的元素总数。如果指定的键在数据库中不存在,该命令则会默认返回 0。在执行效率和版本支持方面,由于它属于一个只读的快捷操作命令(fast command),ZCARD 的时间复杂度极低,仅为 O(1),并且该命令自 Redis 1.2.0 版本起便可供使用。
bash
ZCARD rank

2. ZCOUNT
bash
ZCOUNT key min max
ZCOUNT 命令主要用于返回有序集合(sorted set)中,关联分数(score)位于指定的最小值(min)和最大值(max)范围内的元素个数。默认情况下,该命令的查询范围是闭区间(即结果包含 min 和 max 本身),但用户也可以通过在数值前添加 ( 符号来设定开区间以排除端点值,此外它也支持使用 +inf 和 -inf 作为正负无穷大的极限范围。在执行效率方面,由于 ZCOUNT 底层利用元素的排名机制(类似于 ZRANK)来快速计算范围大小,避免了与区间长度成正比的遍历开销,因此它的时间复杂度仅为 O(log(N)),其中 N 代表当前有序集合中的元素总数。最后,该命令自 Redis 2.0.0 版本起便可供使用,执行后会以整数的形式返回符合分数范围条件的成员总数。
bash
# 查询成绩在 80 分到 100 分之间(包含 80 和 100)的学生人数
ZCOUNT rank 80 100
# 查询成绩大于 80 分,且小于等于 100 分的学生人数(不包含 80 分)
ZCOUNT rank (80 100
# 查询成绩大于 80 分,且小于 100 分的学生人数(两端都不包含)
ZCOUNT rank (80 (100
# 查询及格的学生人数
ZCOUNT scores 60 +inf
# 查询不及格的学生人数
ZCOUNT scores -inf (60

3. ZRANGE
bash
ZRANGE key start stop [BYSCORE | BYLEX] [REV] [LIMIT offset count]
[WITHSCORES]
ZRANGE 命令主要用于返回存储在指定键中的有序集合(sorted set)内特定范围的元素列表。默认情况下,该命令执行基于索引(排名)的查询,按照分数值从低到高(升序)的顺序排列输出,若遇到分数值相同的元素,则按字典序进行排列。用户指定的 start 和 stop 索引构成了一个闭区间(即包含首尾元素),并且该命令支持使用负数索引来表示从集合末尾开始的偏移量,例如 -1 代表倒数第一个元素,-2 代表倒数第二个元素。自 Redis 6.2.0 版本起,ZRANGE 的功能得到了极大的扩展,它可以通过搭配不同的参数选项来执行更多样化的查询:引入 BYSCORE 选项可以实现按分数范围(支持正负无穷大 +inf 和 -inf)查询,引入 BYLEX 选项可以实现按字典序范围查询,而使用 REV 参数则能将整体排序反转为降序输出,这些特性的加入使得 ZRANGE 可以完全替代 ZREVRANGE、ZRANGEBYSCORE 以及 ZRANGEBYLEX 等多个传统的范围查询命令。此外,如果用户在命令中附加 WITHSCORES 参数,即可在返回元素成员的同时一并返回它们对应的分数。在执行效率与版本支持上,该命令的时间复杂度为 O(log(N)+M),其中 N 代表有序集合中的元素总数,M 代表最终返回的元素个数,该命令自 Redis 1.2.0 版本开始便可供使用。
bash
# 获取分数最低的前 3 名成员
ZRANGE rank 0 2
# 获取集合中的所有成员
ZRANGE rank 0 -1
# 获取所有成员及其对应的分数
ZRANGE rank 0 -1 WITHSCORES
# 获取分数最高的前 3 名成员,并同时展示他们的分数
ZRANGE rank 0 2 REV WITHSCORES
# 查询分数在 80 分到 100 分之间的成员(默认升序排列)
ZRANGE rank 80 100 BYSCORE
# 查询分数在 80 分到 100 分之间的成员,按分数降序排列
ZRANGE rank 100 80 BYSCORE REV

4. ZRANK
bash
ZRANK key member [WITHSCORE]
ZRANK 命令主要用于获取有序集合(sorted set)中指定元素的排名,其排名规则是按照元素的关联分数值从低到高(升序)进行排列。该命令返回的排名(或索引)是从 0 开始计算的,这意味着分数值最低的元素其排名为 0。在返回值方面,如果指定的元素存在于集合中,默认情况下该命令会返回该元素的整数排名;但如果指定的键不存在或者该元素不包含在集合中,则会返回空回复(Nil/Null reply)。自 Redis 7.2.0 版本起,ZRANK 得到了扩展,引入了可选的 WITHSCORE 参数,当附加此参数进行查询时,命令将以数组的形式同时返回目标元素的排名及其对应的分数。在执行效率与版本支持上,该命令的时间复杂度为 O(log(N)),其中 N 代表当前有序集合中的元素总数,并且 ZRANK 自 Redis 2.0.0 版本起便可供使用。最后,如果用户需要获取按分数从高到低(降序)排列的元素排名,则可以搭配使用相对应的 ZREVRANK 命令。
bash
ZRANK rank Bob
ZRANK rank Alice
ZRANK rank Hush

5. ZREVRANK
bash
ZREVRANK key member [WITHSCORE]
ZREVRANK 命令主要用于获取有序集合(sorted set)中指定元素的排名,其排名规则与 ZRANK 相反,是按照元素的关联分数值从高到低(降序)进行排列的。该命令返回的排名(或索引)是从 0 开始计算的,这意味着分数值最高的元素其排名为 0。在返回值方面,如果指定的键不存在或者该元素不包含在集合中,该命令会返回空回复(Nil/Null reply);如果指定的元素存在,默认情况下会返回该元素的整数排名。自 Redis 7.2.0 版本起,该命令得到了扩展,引入了可选的 WITHSCORE 参数,当附加此参数进行查询时,命令将以数组的形式同时返回目标元素的排名及其对应的分数。在执行效率与版本支持上,ZREVRANK 的时间复杂度为 O(log(N)),其中 N 代表当前有序集合中的元素总数,并且该命令自 Redis 2.0.0 版本起便可供使用。
bash
ZREVRANK rank Bob
ZREVRANK rank Alice

6. ZSCORE
bash
ZSCORE key member
ZSCORE 命令主要用于获取有序集合(sorted set)中指定成员所关联的分数值。当用户传入键名和成员名执行查询时,如果指定的成员存在于集合中,该命令会返回该成员对应的分数(底层为双精度浮点数类型,通常以字符串的形式返回)。如果指定的成员在有序集合中不存在,或者用户查询的键完全不存在,该命令则会返回空回复(Nil/Null reply)。在执行效率与版本支持方面,作为一个只读的快捷操作,ZSCORE 的时间复杂度极低,仅为 O(1),这意味着它可以非常高效地直接定位到元素的分数,并且该命令自 Redis 1.2.0 版本起便可供使用。
bash
# 获取 Alice 的分数
ZSCORE rank Alice
# 获取 Bob 的分数
ZSCORE rank Bob
# 查询集合中不存在的 Mike 的分数
ZSCORE rank Mike
# 查询不存在的集合中 Alice 的分数
ZSCORE score Alice

1.4. 删除与弹出命令
1. ZPOPMAX 和 ZPOPMIN
bash
ZPOPMAX key [count]
ZPOPMIN key [count]
ZPOPMAX 命令主要用于从有序集合(sorted set)中删除并返回分数最高的一个或多个成员。用户可以通过 ZPOPMAX key [count] 的语法来指定想要弹出的元素数量 count,如果未指定,默认弹出分数最高的一个元素;当有序集合中的最后一个元素被弹出后,该键将被自动删除。在执行效率与版本支持方面,ZPOPMAX 的时间复杂度为 O(log(N)*M),其中 N 代表当前有序集合中的元素总数,M 代表本次操作实际弹出的元素个数,该命令自 Redis 5.0.0 版本起便可供使用。命令执行成功后,会以数组(列表)的形式返回被弹出元素及其对应的分数值。此外,如果需要在集合为空时进行阻塞等待,可以使用其对应的阻塞版本 BZPOPMAX 命令。
ZPOPMIN 命令的作用与前者刚好相反,它主要用于从有序集合中删除并返回分数最低的一个或多个成员。其语法为 ZPOPMIN key [count],允许用户指定要弹出的低分元素个数,同样在集合内的最后一个元素被弹出后会自动删除该有序集合。在执行效率和引入版本上,ZPOPMIN 的时间复杂度也为 O(log(N)*M)(N 为集合元素总数,M 为被弹出的元素个数),并且也是在 Redis 5.0.0 版本中被正式引入的。该命令的返回值格式与 ZPOPMAX 一致,为包含被弹出元素及其分数的列表。同时,该命令也提供了一个用于阻塞操作的对应版本 BZPOPMIN。
bash
# 弹出当前分数最高的 1 名学生
ZPOPMAX rank
# 弹出分数最高的 2 名学生
ZPOPMAX scores 2
# 弹出当前分数最低 1 名学生
ZPOPMIN rank
# 弹出分数最低的 2 名学生
ZPOPMIN rank 2

2. BZPOPMAX 和 BZPOPMIN
bash
BZPOPMAX key [key ...] timeout
BZPOPMIN key [key ...] timeout
BZPOPMAX 和 BZPOPMIN 分别是 ZPOPMAX 和 ZPOPMIN 命令的阻塞版本,主要用于从一个或多个指定的有序集合(sorted set)中弹出关联分数最高(对于 BZPOPMAX)或最低(对于 BZPOPMIN)的成员。当操作的所有有序集合均为空时,这两个命令会阻塞当前连接,直到集合中有可弹出的成员或达到指定的超时时间(timeout)为止。命令支持同时传入多个键,执行时会按照键名传入的先后顺序依次进行检查,并从第一个非空的有序集合中弹出符合条件的元素;如果集合中的最后一个元素被弹出,该有序集合的键将被自动删除。在超时控制方面,timeout 参数代表允许最大阻塞的秒数(自 Redis 6.0.0 版本起,该参数被解析为双精度浮点数而非整数),若将其设置为 0 则表示无限期阻塞等待。在返回值方面,命令执行成功后会以数组的形式依次返回被弹出元素所属的键名、成员名及其对应的分数值;若超过了设定的超时时间仍未获取到元素,则返回空回复(Nil/Null reply)。在执行效率与版本支持上,这两个命令的时间复杂度均为 O(log(N))(其中 N 为对应有序集合中的元素总数),并且它们都是自 Redis 5.0.0 版本起便可供使用的。
3. ZREM
bash
ZREM key member [member ...]
ZREM 命令主要用于从有序集合(sorted set)中删除一个或多个指定的元素。在执行操作时,如果用户指定的成员在集合中不存在,这些成员会被直接忽略;如果指定的键存在但其存储的并非有序集合类型,该命令则会返回错误;另外,当有序集合中的所有成员都被成功移除后,该键会被自动删除。在执行效率与版本支持方面,ZREM 的时间复杂度为 O(M*log(N)),其中 N 代表当前有序集合中的元素总数,M 代表本次操作需要删除的元素个数,并且该命令自 Redis 1.2.0 版本起便可供使用(自 Redis 2.4.0 版本开始支持同时传入多个元素进行批量删除)。最后,在返回值方面,该命令执行结束后会以整数的形式返回本次实际成功删除的元素个数,不包括那些原本就不存在的元素。
bash
ZADD game_scores 100 Alice 200 Bob 300 Charlie 400 David 500 Eve
ZREM game_scores Bob Eve

4. ZREMRANGEBYRANK
bash
ZREMRANGEBYRANK key start stop
ZREMRANGEBYRANK 命令主要用于移除有序集合(sorted set)中指定排名(索引)范围内的所有元素。该命令底层的排名规则是按照元素关联的分数从低到高(升序)进行排列,用户传入的 start 和 stop 均为从 0 开始计算的索引,这意味着分数值最低的元素其索引为 0。该命令查询的范围是左闭右闭区间(即结果包含 start 和 stop 对应排名的元素本身),并且支持使用负数索引来表示从集合得分最高端(末尾)开始的偏移量,例如 -1 代表分数最高的元素,-2 代表分数第二高的元素。在执行时,如果有序集合中的所有成员都被移除,那么存储该集合的键将会被自动删除。在执行效率与版本支持方面,ZREMRANGEBYRANK 的时间复杂度为 O(log(N)+M),其中 N 代表当前有序集合中的元素总数,M 代表本次操作实际移除的元素个数,并且该命令自 Redis 2.0.0 版本起便可供使用。最后,命令执行结束后,会以整数的形式返回本次实际成功删除的成员数量。
bash
ZREMRANGEBYRANK game_scores 0 1
# 删除集合中所有元素
ZREMRANGEBYRANK game_scores 0 -1
5. ZREMRANGEBYSCORE
bash
ZREMRANGEBYSCORE key min max
ZREMRANGEBYSCORE 命令主要用于移除有序集合(sorted set)中,关联分数(score)位于指定范围(即 min 和 max 之间)内的所有元素。默认情况下,该命令查询和移除的范围是左闭右闭区间(即包含 min 和 max 对应分数的元素本身),同时它也支持使用 +inf 和 -inf 来表示正负无穷大,或者通过在具体数值前附加 ( 符号来设定开区间以排除端点值。在执行移除操作时,如果该有序集合中的所有成员都被清空,那么存储该集合的键将会被自动删除。在执行效率与版本支持方面,ZREMRANGEBYSCORE 的时间复杂度为 O(log(N)+M),其中 N 代表当前有序集合中的元素总数,M 代表本次操作实际移除的元素个数,并且该命令自 Redis 1.2.0 版本起便可供使用。最后,该命令执行结束后,会以整数的形式返回本次实际成功删除的成员数量。
bash
# 删除分数在 200 到 400 之间
ZREMRANGEBYSCORE game_scores 200 400
1.5. 集合间操作命令
bash
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight
[weight ...]] [AGGREGATE <SUM | MIN | MAX>]
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight
[weight ...]] [AGGREGATE <SUM | MIN | MAX>]
ZINTERSTORE 命令主要用于计算多个给定有序集合(sorted set)中元素的交集,并将计算结果保存到指定的目录有序集合(destination)中。在合并过程中,该命令会以元素为单位进行处理,并且对应元素的新分数可以根据用户指定的权重(WEIGHTS)以及聚合方式(AGGREGATE,支持 SUM、MIN 或 MAX)计算得出。如果指定的目标键已经存在,它将会被最新的结果覆盖。在执行效率方面,ZINTERSTORE 的时间复杂度为 O(NK)+O(Mlog(M)),其中 N 代表输入集合中最小集合的元素个数,K 代表参与计算的输入集合数量,而 M 代表最终目标集合中的元素个数。该命令自 Redis 2.0.0 版本起便可供使用,执行成功后会以整数形式返回保存在目标集合中的元素总数。
bash
# 赛季1得分
ZADD season1 80 Alice 70 Bob 90 Carol
# 赛季2得分
ZADD season2 85 Bob 95 Carol 75 Dave
# 求交集
ZINTERSTORE total-inter 2 season1 season2
ZRANGE total-inter 0 -1
# 找出两个赛季都参与了的玩家,并记录他们在所有赛季中的最高分
ZINTERSTORE max-inter 2 season1 season2 AGGREGATE MAX
ZRANGE max-inter 0 -1
ZSCORE max-inter Bob
ZSCORE max-inter Carol
# 第二赛季的积分权重是第一赛季的 2 倍
ZINTERSTORE weight-inter 2 season1 season2 WEIGHTS 1 2
ZSCORE weight-inter Bob
ZSCORE weight-inter Carol

ZUNIONSTORE 命令则主要用于计算多个给定有序集合中元素的并集,同样会将结果保存到目标有序集合中。与交集操作类似,在合并并集时默认新元素的分数是其在各个参与集合中分数的总和,但用户也可以通过附加权重和指定聚合方式来进行自定义的分数计算。需要特别注意的是,在调用该命令时,必须在传入具体的输入键名之前,优先明确提供输入键的数量(numkeys)。如果指定的目录键已经存在,该命令同样会将其直接覆盖。在执行效率与版本支持上,ZUNIONSTORE 的时间复杂度为 O(N)+O(M*log(M)),其中 N 是所有输入集合元素数量的总和,M 是最终结果集合中的元素个数,并且它也是在 Redis 2.0.0 版本中被正式引入的。命令执行结束后,会返回结果有序集合内的最终元素个数。
bash
# 求并集
ZUNIONSTORE total-union 2 season1 season2
ZRANGE total-union 0 -1
ZSCORE total-union Alice
ZSCORE total-union Bob
# 合并时取两个赛季折算后的最高分
ZUNIONSTORE custom 2 season1 season2 WEIGHTS 0.5 1 AGGREGATE MAX
ZSCORE custom Alice
ZSCORE custom Alice

1.6. 内部编码方式
Zset 的底层内部编码有两种实现方式:
- ziplist(压缩列表):当有序集合的元素个数小于 zset-max-ziplist-entries 配置(默认 128 个),且每个元素的值都小于 zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会使用 ziplist 作为内部实现。ziplist 可以有效减少内存的使用。
- skiplist(跳表):当不满足 ziplist 的条件时(例如元素个数超过 128 个,或存在大于 64 字节的元素),有序集合会使用 skiplist 作为内部编码实现。因为在数据量较大时,ziplist 的操作效率会下降,而 skiplist 的插入、查询和删除时间复杂度均为 O(logN)。
1.7. 典型使用场景
有序集合最典型的使用场景是排行榜系统。例如维护每天的热榜,可以按点赞数维度进行排名:
- 添加/更新赞数:用户发布文章或初步获赞时,使用 ZADD 添加数据;后续继续获赞时,使用 ZINCRBY 增加分数。
- 取消赞数/删除用户:当由于用户注销或作弊等原因需要从榜单删除用户时,使用 ZREM 命令清除。
- 展示前 N 名用户:使用 ZREVRANGEBYRANK 获取赞数最多的前几个用户列表。
- 展示单用户信息及排名:可结合哈希类型保存用户详细属性,同时通过有序集合的 ZSCORE 获取赞数,通过 ZRANK 获取排名。