Redis 有序集合(ZSET)指南:从入门到精通
Redis 有序集合(Sorted Set,简称 ZSET)是 Redis 中一种独特且功能强大的数据结构,它结合了哈希表和跳跃表的特点,提供了集合的唯一性以及按照分数(score)进行排序的能力。本文将深入探讨 Redis ZSET 的基本概念、常用命令、应用场景以及一些高级用法,帮助您从入门到精通。
1. 什么是 Redis 有序集合 (ZSET)?
有序集合是字符串的集合,其中每个成员(member)都关联一个分数(score),分数是一个浮点数。ZSET 的主要特点如下:
- 成员唯一性:集合中的每个成员都是唯一的,不能重复。
- 有序性:集合中的成员是按照其关联的分数从小到大排序的。如果分数相同,则按照成员的字典顺序(ASCII 码)排序。
- 高效查找:Redis ZSET 在内部使用跳跃表(skiplist)和哈希表(hash table)实现,因此可以高效地执行范围查询、成员查找、分数更新等操作。
- 支持范围查询:可以根据分数范围或成员的排名范围来获取成员列表。
2. ZSET 的内部实现
Redis ZSET 的底层实现是结合了两种数据结构:
- 哈希表 (Hash Table):用于存储成员(member)到分数(score)的映射。这使得我们可以快速通过成员查找其分数,时间复杂度为 O(1)。
- 跳跃表 (Skip List):用于存储所有成员及其分数,并按照分数进行排序。跳跃表是一种概率性数据结构,它允许在 O(log N) 的平均时间复杂度内进行搜索、插入和删除操作。
这种组合使得 ZSET 既能快速查找成员,又能高效地进行范围查询和排序操作。
3. ZSET 常用命令
以下是 Redis ZSET 常用的一些命令及其用法:
3.1 添加成员:ZADD
ZADD key score member [score member ...]
将一个或多个成员及其分数添加到有序集合中。如果成员已经存在,则更新其分数。
示例:
redis
ZADD myzset 1 "member1"
ZADD myzset 2 "member2" 3 "member3"
ZADD myzset 2.5 "member4"
3.2 获取有序集合的成员数量:ZCARD
ZCARD key
返回有序集合中成员的数量。
示例:
“`redis
ZCARD myzset
结果: 4
“`
3.3 获取成员的分数:ZSCORE
ZSCORE key member
返回有序集合中指定成员的分数。
示例:
“`redis
ZSCORE myzset “member2”
结果: “2”
“`
3.4 增加成员的分数:ZINCRBY
ZINCRBY key increment member
给有序集合中指定成员的分数加上增量 increment。如果成员不存在,则添加该成员并设置其分数为 increment。
示例:
“`redis
ZINCRBY myzset 10 “member1”
结果: “11” (原分数 1 + 增量 10)
“`
3.5 移除成员:ZREM
ZREM key member [member ...]
从有序集合中移除一个或多个成员。
示例:
“`redis
ZREM myzset “member3”
结果: 1 (表示移除了一个成员)
ZCARD myzset
结果: 3
“`
3.6 范围查询:ZRANGE 和 ZREVRANGE
这两个命令用于根据排名(rank)范围获取成员。
ZRANGE key start stop [WITHSCORES]
返回有序集合中排名在 start 和 stop 之间(包含)的成员。排名从 0 开始。start 和 stop 也可以是负数,表示从末尾开始计数 (-1 表示最后一个成员)。
ZREVRANGE key start stop [WITHSCORES]
与 ZRANGE 类似,但返回的成员是按照分数从大到小排序的。
示例:
“`redis
假设 myzset 成员及分数如下:
(“member1”, 11) (“member2”, 2) (“member4”, 2.5)
ZRANGE 获取排名 0 到 1 的成员 (从小到大排序)
ZRANGE myzset 0 1
结果:
1) “member2”
2) “member4”
ZRANGE 获取排名 0 到 1 的成员及分数
ZRANGE myzset 0 1 WITHSCORES
结果:
1) “member2”
2) “2”
3) “member4”
4) “2.5”
ZREVRANGE 获取排名 0 到 1 的成员 (从大到小排序)
ZREVRANGE myzset 0 1
结果:
1) “member1”
2) “member4”
“`
3.7 分数范围查询:ZRANGEBYSCORE 和 ZREVRANGEBYSCORE
这两个命令用于根据分数范围获取成员。
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
返回有序集合中分数在 min 和 max 之间(包含)的成员。min 和 max 可以是 -inf (负无穷大) 或 +inf (正无穷大)。使用 ( 表示开区间,例如 (10 表示大于 10。
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
与 ZRANGEBYSCORE 类似,但按照分数从大到小排序。注意 max 和 min 的顺序。
示例:
“`redis
获取分数在 2 到 3 之间的成员
ZRANGEBYSCORE myzset 2 3 WITHSCORES
结果:
1) “member2”
2) “2”
3) “member4”
4) “2.5”
获取分数大于 2.5 的成员
ZRANGEBYSCORE myzset (2.5 +inf
结果:
1) “member1”
“`
3.8 获取成员排名:ZRANK 和 ZREVRANK
ZRANK key member
返回有序集合中指定成员的排名(从 0 开始,分数从小到大排序)。如果成员不存在,返回 nil。
ZREVRANK key member
返回有序集合中指定成员的逆序排名(从 0 开始,分数从大到小排序)。
示例:
“`redis
ZRANK myzset “member1”
结果: 2 (因为 member1 分数是 11,是最大的)
ZREVRANK myzset “member1”
结果: 0
“`
3.9 移除指定排名范围的成员:ZREMRANGEBYRANK
ZREMRANGEBYRANK key start stop
移除有序集合中排名在 start 和 stop 之间(包含)的所有成员。
3.10 移除指定分数范围的成员:ZREMRANGEBYSCORE
ZREMRANGEBYSCORE key min max
移除有序集合中分数在 min 和 max 之间(包含)的所有成员。
3.11 交集和并集:ZINTERSTORE 和 ZUNIONSTORE
这两个命令用于计算多个有序集合的交集或并集,并将结果存储到一个新的有序集合中。
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
计算给定 key 的交集,并将结果存储到 destination。WEIGHTS 参数可以为每个输入集合指定权重,成员的分数会乘以权重。AGGREGATE 参数指定如何处理共同成员的分数(求和、取最小值、取最大值)。
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
计算给定 key 的并集,并将结果存储到 destination。
示例:
“`redis
ZADD set1 10 “a” 20 “b”
ZADD set2 15 “b” 25 “c”
计算并集,默认 AGGREGATE SUM
ZUNIONSTORE union_set 2 set1 set2
union_set: (“a”, 10) (“b”, 35) (“c”, 25)
计算交集,默认 AGGREGATE SUM
ZINTERSTORE inter_set 2 set1 set2
inter_set: (“b”, 35)
“`
4. ZSET 的应用场景
ZSET 因其独特的排序和范围查询能力,在许多场景下都非常有用:
-
排行榜:这是 ZSET 最经典的用途。例如,游戏中的积分排行榜、网站文章点击量排行榜、用户活跃度排行榜等。成员是用户ID或文章ID,分数是积分或点击量。
ZADD更新用户积分/点击量。ZREVRANGE获取前 N 名用户。ZRANK获取特定用户的排名。ZRANGEBYSCORE获取分数在某个范围内的用户。
-
最新文章/热门商品列表:根据发布时间(时间戳作为分数)或销售量(销售量作为分数)来展示最新的或最热门的商品/文章。
ZADD添加商品/文章及分数。ZREVRANGE获取最新或最热门的 N 个商品/文章。
-
优先级队列:将任务ID作为成员,任务的优先级作为分数。高分数的任务优先执行。
ZADD添加任务及其优先级。ZRANGE获取优先级最高的任务(分数最小的)。
-
社交应用中的“附近的人”:将用户的地理位置信息(例如经纬度组合成的哈希值)作为分数,用户ID作为成员。通过分数范围查询可以找出附近的用户。
-
延迟任务队列:将任务的执行时间戳作为分数,任务ID作为成员。定时从 ZSET 中取出分数小于当前时间戳的任务进行处理。
ZADD添加任务及其执行时间戳。ZRANGEBYSCORE key -inf current_timestamp获取需要执行的任务。
5. 高级用法和注意事项
- 分数精度:ZSET 的分数是双精度浮点数,可以支持非常大的范围和精度。
- 成员字符串长度:成员可以是任意长度的字符串,但过长的字符串会增加内存消耗。
- 内存使用:ZSET 会消耗比 List 或 Hash 更多的内存,因为它需要同时维护哈希表和跳跃表。当 ZSET 较小(例如,成员数量小于 128 且所有成员的字符串长度小于 64 字节)时,Redis 会采用优化后的
ziplist编码来节省内存。 - 原子性:所有 Redis 命令都是原子性的,这意味着 ZSET 的操作是线程安全的。
- 范围查询优化:
LIMIT offset count参数在进行范围查询时非常有用,可以用于分页。
6. 总结
Redis 有序集合(ZSET)是一个多功能且高效的数据结构,它通过结合哈希表和跳跃表,在提供成员唯一性的同时,实现了基于分数的快速排序和范围查询。无论是构建排行榜、实现优先级队列还是处理时间序列数据,ZSET 都能提供强大的支持。掌握 ZSET 的使用,将使您能够更灵活、高效地设计和实现各种 Redis 应用。