Redis Zset 使用教程 – wiki基地


Redis 有序集合 (Zset) 使用教程详解

Redis 不仅支持简单的数据结构如字符串、列表、哈希和集合,还提供了一种非常强大的复合数据结构:有序集合(Sorted Set),在 Redis 中通常称为 Zset。Zset 结合了集合(Set)的唯一性特点和哈希(Hash)的关联性特点,同时为每个成员(Member)关联了一个分数(Score),并根据这个分数对成员进行排序。这使得 Zset 在实现各种排行榜、带有优先级的任务队列、需要按权重或时间排序的数据存储等场景中表现出色。

本文将详细介绍 Redis Zset 的核心概念、常用命令以及典型应用场景,帮助你深入理解和掌握 Zset 的使用。

1. Zset 的核心概念

一个 Zset 数据结构包含以下几个关键组成部分:

  1. 键 (Key):一个 Zset 在 Redis 中由一个唯一的键名标识,就像其他所有 Redis 数据结构一样。
  2. 成员 (Member):有序集合中的每个元素被称为成员。成员是唯一的,就像 Redis 的 Set 一样,不允许有重复的成员。成员通常是字符串类型。
  3. 分数 (Score):与每个成员关联的一个浮点数值。Zset 的核心特性就在于根据这个分数对成员进行排序。分数可以是整数或双精度浮点数。

Zset 的特点总结:

  • 唯一性:同一个 Zset 中的成员是唯一的。
  • 有序性:Zset 中的成员根据其关联的分数进行排序,分数低的成员排在前面,分数高的成员排在后面。如果两个成员的分数相同,则按成员的字典序( lexicographical order)进行排序。
  • 可查找性:可以通过成员快速查找其对应的分数,也可以通过分数或排名范围查找成员。

2. Zset 的常用命令详解

掌握 Zset 的使用主要依赖于理解和运用其提供的命令。下面我们将介绍一些最常用和关键的 Zset 命令。

2.1 添加/更新成员:ZADD

ZADD 命令用于向有序集合中添加一个或多个成员及其分数。如果成员已经存在,则会更新其分数并重新排序。

redis
ZADD key score1 member1 [score2 member2 ...] [NX | XX] [CH] [INCR]

  • key: 有序集合的键名。
  • score: 成员的分数,浮点数。
  • member: 成员的值,字符串。
  • NX: 只在成员不存在时添加。
  • XX: 只在成员存在时更新。
  • CH: 返回值从新添加成员的数量变为被改变(新添加或分数被更新)成员的总数。
  • INCR: 将成员的分数加上指定的分数(分数可以是负数,实现减法)。这个选项只能用于一次添加一个成员。

示例:

“`redis

添加多个成员

ZADD myzset 10 “memberA” 20 “memberB” 5 “memberC”

输出:3 (表示成功添加了3个成员)

更新成员A的分数

ZADD myzset 15 “memberA”

输出:0 (因为是更新,不是新增,如果使用了CH选项,会返回1)

尝试添加已存在的成员,但不更新分数 (使用 NX)

ZADD myzset 25 “memberA” NX

输出:0 (因为memberA已存在)

尝试更新不存在的成员 (使用 XX)

ZADD myzset 30 “memberD” XX

输出:0 (因为memberD不存在)

使用 INCR 增加成员B的分数

ZADD myzset INCR 5 “memberB”

输出:25.0 (memberB的新分数为20+5=25)

“`

2.2 获取成员排名(按分数从小到大):ZRANK

ZRANK 命令用于获取指定成员在有序集合中的排名(从0开始)。排名按分数从小到大计算。

redis
ZRANK key member

  • key: 有序集合的键名。
  • member: 要查询排名的成员。

示例:

承接上例,myzset 中成员按分数排序(从小到大)为:memberC(5) -> memberA(15) -> memberB(25)

“`redis
ZRANK myzset “memberC”

输出:0 (memberC是排名第一,索引为0)

ZRANK myzset “memberA”

输出:1 (memberA是排名第二,索引为1)

ZRANK myzset “memberB”

输出:2 (memberB是排名第三,索引为2)

ZRANK myzset “memberX”

输出:(nil) (memberX不存在)

“`

2.3 获取成员排名(按分数从大到小):ZREVRANK

ZREVRANK 命令与 ZRANK 类似,但排名按分数从大到小计算(最高分排名为0)。

redis
ZREVRANK key member

  • key: 有序集合的键名。
  • member: 要查询排名的成员。

示例:

承接上例,myzset 中成员按分数排序(从大到小)为:memberB(25) -> memberA(15) -> memberC(5)

“`redis
ZREVRANK myzset “memberB”

输出:0 (memberB是最高分,排名为0)

ZREVRANK myzset “memberA”

输出:1

ZREVRANK myzset “memberC”

输出:2

“`

2.4 按排名范围获取成员:ZRANGE

ZRANGE 命令用于按排名(索引)范围获取有序集合中的成员。排名按分数从小到大计算。

redis
ZRANGE key start stop [WITHSCORES]

  • key: 有序集合的键名。
  • start: 开始索引(0为第一个成员)。
  • stop: 结束索引。
  • startstop 都可以使用负数表示从末尾开始计数,-1 表示最后一个成员,-2 表示倒数第二个,以此类推。范围是闭区间,包含 startstop 对应的成员。
  • WITHSCORES: 可选参数,如果指定,返回结果会包含成员及其对应的分数。

示例:

承接上例,myzset 按分数从小到大:memberC(5) -> memberA(15) -> memberB(25)

“`redis
ZRANGE myzset 0 2

输出:1) “memberC” 2) “memberA” 3) “memberB” (获取所有成员)

ZRANGE myzset 0 1 WITHSCORES

输出:1) “memberC” 2) “5” 3) “memberA” 4) “15” (获取排名0到1的成员及其分数)

ZRANGE myzset -2 -1

输出:1) “memberA” 2) “memberB” (获取倒数第2到倒数第1的成员)

“`

2.5 按排名范围获取成员(从大到小):ZREVRANGE

ZREVRANGE 命令与 ZRANGE 类似,但按分数从大到小获取成员范围。

redis
ZREVRANGE key start stop [WITHSCORES]

参数含义与 ZRANGE 相同,但索引是针对从大到小排序的列表。

示例:

承接上例,myzset 按分数从大到小:memberB(25) -> memberA(15) -> memberC(5)

“`redis
ZREVRANGE myzset 0 2

输出:1) “memberB” 2) “memberA” 3) “memberC” (获取所有成员,从最高分到最低分)

ZREVRANGE myzset 0 1 WITHSCORES

输出:1) “memberB” 2) “25” 3) “memberA” 4) “15” (获取排名0到1的成员及其分数)

“`

2.6 按分数范围获取成员:ZRANGEBYSCORE

ZRANGEBYSCORE 命令是 Zset 中非常强大且常用的命令,它允许你根据成员的分数范围来获取成员。

redis
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

  • key: 有序集合的键名。
  • min: 最小分数。
  • max: 最大分数。
  • WITHSCORES: 可选参数,返回结果包含分数。
  • LIMIT offset count: 可选参数,用于分页。从匹配结果的 offset 位置开始,获取 count 个成员。

minmax 可以是具体的分数,也可以是 -inf (负无穷) 或 +inf (正无穷)。默认情况下,范围是闭区间(包含等于 minmax 的成员)。可以使用前缀 ( 表示开区间(不包含等于该值的成员)。

示例:

承接上例,myzset 按分数从小到大:memberC(5) -> memberA(15) -> memberB(25)

“`redis

获取分数在10到20之间的成员 (包含10和20)

ZRANGEBYSCORE myzset 10 20

输出:1) “memberA”

获取分数大于5小于25的成员 (开区间)

ZRANGEBYSCORE myzset (5 (25

输出:1) “memberA”

获取分数小于等于15的所有成员 (包含负无穷和15)

ZRANGEBYSCORE myzset -inf 15 WITHSCORES

输出:1) “memberC” 2) “5” 3) “memberA” 4) “15”

获取分数大于等于15的所有成员 (包含15和正无穷)

ZRANGEBYSCORE myzset 15 +inf

输出:1) “memberA” 2) “memberB”

获取分数大于5的所有成员,只取前1个 (分页)

ZRANGEBYSCORE myzset (5 +inf LIMIT 0 1

输出:1) “memberA”

“`

2.7 按分数范围获取成员(从大到小):ZREVRANGEBYSCORE

ZREVRANGEBYSCORE 命令与 ZRANGEBYSCORE 类似,但按分数从大到小获取成员范围。注意 max 参数在前,min 参数在后。

redis
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]

示例:

承接上例,myzset 按分数从大到小:memberB(25) -> memberA(15) -> memberC(5)

“`redis

获取分数在20到10之间的成员 (包含20和10),注意这里是 max min 的顺序

ZREVRANGEBYSCORE myzset 20 10

输出:1) “memberA”

获取分数小于25大于5的成员 (开区间)

ZREVRANGEBYSCORE myzset (25 (5

输出:1) “memberA”

“`

2.8 获取成员分数:ZSCORE

ZSCORE 命令用于获取指定成员的分数。

redis
ZSCORE key member

  • key: 有序集合的键名。
  • member: 要查询分数的成员。

示例:

“`redis
ZSCORE myzset “memberA”

输出:”15″ (返回分数的字符串表示)

ZSCORE myzset “nonexistent”

输出:(nil) (成员不存在)

“`

2.9 移除成员:ZREM

ZREM 命令用于从有序集合中移除一个或多个成员。

redis
ZREM key member [member ...]

  • key: 有序集合的键名。
  • member: 要移除的成员。

示例:

“`redis
ZREM myzset “memberC”

输出:1 (表示成功移除了1个成员)

ZREM myzset “memberA” “memberX”

输出:1 (memberA被移除,memberX不存在,所以总共移除1个)

“`

2.10 获取成员数量:ZCARD

ZCARD 命令用于获取有序集合中的成员数量。

redis
ZCARD key

  • key: 有序集合的键名。

示例:

“`redis
ZCARD myzset

输出:1 (在移除了 memberC 和 memberA 后,myzset 只剩下 memberB)

“`

2.11 统计分数范围内的成员数量:ZCOUNT

ZCOUNT 命令用于统计分数在指定范围内的成员数量。

redis
ZCOUNT key min max

参数含义与 ZRANGEBYSCORE 中的 minmax 相同,支持开闭区间和无穷。

示例:

重新添加一些成员:ZADD myzset 10 "m1" 20 "m2" 30 "m3" 40 "m4" 50 "m5"

“`redis
ZCOUNT myzset 20 40

输出:3 (成员 m2, m3, m4 的分数在 [20, 40] 范围内)

ZCOUNT myzset (20 40

输出:2 (成员 m3, m4 的分数在 (20, 40] 范围内)

“`

2.12 按字典序范围获取成员:ZRANGEBYLEX / ZREVRANGEBYLEX

当多个成员拥有相同的分数时,它们会按成员的字典序进行排序。ZRANGEBYLEXZREVRANGEBYLEX 命令允许你按成员的字典序范围来获取成员。这些命令要求指定的成员范围必须 全部 具有相同的分数,或者在指定分数范围(通常是 -inf+inf)内,所有成员都必须具有相同的分数才能正确使用。它们通常用于对具有相同分数的成员进行进一步的范围查找,或者在所有成员分数都相同时,完全按字典序进行操作。

redis
ZRANGEBYLEX key min max [LIMIT offset count]
ZREVRANGEBYLEX key max min [LIMIT offset count]

  • min, max: 指定成员的字典序范围。可以使用 [ 表示闭区间,( 表示开区间。使用 + 表示无穷大字符串,- 表示无穷小字符串。

示例:

假设我们有一个 Zset,所有成员的分数都是0:ZADD myzset 0 "apple" 0 "banana" 0 "cherry" 0 "date"

“`redis
ZRANGEBYLEX myzset [apple [cherry

输出:1) “apple” 2) “banana” 3) “cherry” (获取字典序在 [apple, cherry] 范围内的成员)

ZRANGEBYLEX myzset (banana +

输出:1) “cherry” 2) “date” (获取字典序大于 banana 的所有成员)

“`

2.13 获取并移除分数最低的成员:ZPOPMIN

ZPOPMIN 命令从有序集合中移除并返回分数最低的成员及其分数。可以指定数量,移除并返回多个分数最低的成员。

redis
ZPOPMIN key [count]

  • key: 有序集合的键名。
  • count: 要移除并返回的成员数量。

示例:

“`redis
ZADD myzset 10 “m1” 20 “m2” 5 “m0” 30 “m3”
ZPOPMIN myzset

输出:1) “m0” 2) “5” (移除并返回 m0 及其分数 5)

ZPOPMIN myzset 2

输出:1) “m1” 2) “10” 3) “m2” 4) “20” (移除并返回分数最低的两个成员及其分数)

“`

2.14 获取并移除分数最高的成员:ZPOPMAX

ZPOPMAX 命令与 ZPOPMIN 类似,但移除并返回分数最高的成员及其分数。

redis
ZPOPMAX key [count]

示例:

“`redis
ZADD myzset 10 “m1” 20 “m2” 5 “m0” 30 “m3”
ZPOPMAX myzset

输出:1) “m3” 2) “30” (移除并返回 m3 及其分数 30)

“`

2.15 阻塞式获取并移除分数最低的成员:BZPOPMIN

BZPOPMINZPOPMIN 的阻塞版本。当指定的有序集合为空时,连接将被阻塞,直到有成员被添加到其中一个有序集合,或者达到指定的超时时间。

redis
BZPOPMIN key [key ...] timeout

  • key [key ...]: 一个或多个有序集合的键名。
  • timeout: 阻塞的秒数。0 表示永远阻塞。

返回值是包含三部分的数组:被弹出的成员来源的键名、被弹出的成员以及其分数。

2.16 阻塞式获取并移除分数最高的成员:BZPOPMAX

BZPOPMAXZPOPMAX 的阻塞版本。

redis
BZPOPMAX key [key ...] timeout

参数和返回值类似 BZPOPMIN

2.17 计算有序集合的并集和交集:ZUNIONSTORE / ZINTERSTORE

这些命令用于计算多个有序集合的并集或交集,并将结果存储到一个新的有序集合中。

redis
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]

  • destination: 存储结果的有序集合的键名。
  • numkeys: 输入的有序集合数量。
  • key [key ...]: 输入的有序集合的键名。
  • WEIGHTS: 可选参数,为每个输入集合指定一个乘法因子,成员的分数在聚合前会先乘以这个因子。
  • AGGREGATE: 可选参数,指定当一个成员出现在多个输入集合中时,如何计算其最终分数。SUM(默认)将所有分数相加,MIN 取最小分数,MAX 取最大分数。

示例:

“`redis
ZADD zset1 10 “a” 20 “b”
ZADD zset2 15 “b” 25 “c” 30 “d”

计算并集,分数相加 (默认)

ZUNIONSTORE zset_union 2 zset1 zset2

结果 zset_union: “a”:10, “b”:35, “c”:25, “d”:30

计算交集,取最小分数

ZINTERSTORE zset_inter 2 zset1 zset2 AGGREGATE MIN

结果 zset_inter: “b”:15 (只有”b”同时存在,取 min(20, 15)=15)

计算并集,zset2 的分数乘2,分数相加

ZUNIONSTORE zset_union_weighted 2 zset1 zset2 WEIGHTS 1 2 AGGREGATE SUM

结果 zset_union_weighted: “a”:10, “b”:(20 + 152)=50, “c”:252=50, “d”:30*2=60

“`

3. Zset 的应用场景

Zset 的有序性和分数特性使其非常适合以下场景:

  • 排行榜 (Rankings):这是 Zset 最典型的应用。例如,游戏玩家得分排行榜、网站文章点击量排行榜、商品销量排行榜等。分数就是得分、点击量或销量,成员是玩家ID、文章ID或商品ID。使用 ZADD 添加/更新得分,ZREVRANK 获取用户排名,ZREVRANGE 获取排行榜前N名。
  • 优先级队列 (Priority Queues):将任务作为成员,优先级作为分数。使用 ZADD 添加任务,ZPOPMIN 获取并移除最高优先级(分数最低)的任务。BZPOPMIN 可以实现阻塞等待,适合作为生产者-消费者模型中的任务队列。
  • 时间序列数据 (Time Series Data):将事件作为成员,时间戳作为分数。可以方便地按时间范围 (ZRANGEBYSCORE) 查询事件。
  • 延迟队列 (Delayed Queue):将任务作为成员,执行时间戳作为分数。使用 ZRANGEBYSCORE 查询到达执行时间的任务 (-inf 到当前时间戳),然后使用 ZREM 移除或 ZPOPMIN 弹出执行。
  • 带权重的标签/分类 (Weighted Tags/Categories):给内容打标签,标签本身是成员,权重是分数,表示标签与内容的关联度或重要性。
  • 限流/滑动窗口 (Rate Limiting/Sliding Window):可以使用 Zset 存储某个用户在特定时间窗口内的操作记录(时间戳作为分数),然后使用 ZCOUNT 统计窗口内的操作次数进行限流判断。

4. Zset 的内部实现与性能

Redis Zset 的内部实现通常结合使用了跳跃表 (Skip List) 和哈希表 (Hash Table)。

  • 哈希表 用于存储成员到分数的映射关系,这样可以通过成员 O(1) 的时间复杂度快速查找其分数。
  • 跳跃表 用于存储成员到分数的映射关系,并按分数有序排列,这样可以在 O(log N) 的时间复杂度内完成范围查询、排名查询等操作。

当 Zset 中的元素数量较少或者成员字符串较短时,Redis 会采用一种更节省内存的紧凑结构 (ziplist 或 listpack)。当元素数量或成员大小超过一定阈值时,才会转换为跳跃表和哈希表的实现。

大多数 Zset 操作,如 ZADD, ZRANK, ZSCORE, ZREM, ZRANGE, ZRANGEBYSCORE 的时间复杂度都是 O(log N) 或 O(log N + M)(M为返回的元素数量),这使得 Zset 在处理大量数据时依然能保持较高的性能。ZCARD 的时间复杂度是 O(1)。

5. 总结

Redis 的有序集合 (Zset) 是一个功能强大且灵活的数据结构,它通过为每个成员关联一个分数来实现有序存储,并提供了丰富的命令用于成员的添加、移除、查询(按排名或分数范围)、统计等操作。凭借其独特的有序性和高效的底层实现,Zset 在各种需要处理排序数据的应用场景中发挥着核心作用,尤其是在构建排行榜、优先级队列和时间序列相关功能时。理解 Zset 的核心概念和常用命令,将极大地扩展你在使用 Redis 进行应用开发时的能力。

开始实践吧!尝试使用 Redis CLI 或你喜欢的客户端库连接到 Redis,按照上面的命令示例操作 Zset,加深理解。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部