Redis 有序集合 ZSET:核心概念、命令详解与应用案例
Redis,作为一个高性能的键值存储系统,提供了多种数据结构来满足不同的应用场景。其中,有序集合(Sorted Set),简称 ZSET,是一个非常强大且独特的数据结构,它结合了哈希表和跳跃链表的特性,使得数据既能快速查找,又能按分数(score)进行排序。
一、ZSET 的核心概念
有序集合 ZSET 是一个非重复字符串的集合,其中每个成员(member)都关联着一个浮点数分数(score)。这个分数是 ZSET 的核心,它决定了集合中成员的排序方式。
关键特性:
- 成员唯一性: ZSET 中的每个成员都是唯一的,不能有重复的成员。
- 分数排序: 集合中的所有成员都根据其关联的分数进行排序。分数可以重复,但成员不能重复。当分数相同时,Redis 会根据成员的字典序进行二次排序(这是 Redis 3.2 版本之后引入的)。
- 快速查找: ZSET 内部通过哈希表存储成员到分数的映射,因此可以 O(1) 时间复杂度快速查找成员对应的分数。
- 范围查询: ZSET 内部通过跳跃链表(Skip List)维护了成员的有序性,使得可以高效地进行范围查询,例如查询分数在某个区间内的成员,或者查询排名在某个区间内的成员。
内部实现:
Redis 的 ZSET 底层采用了两种数据结构:
- 哈希表 (Hash Table): 用于存储成员到分数的映射,提供 O(1) 时间复杂度的成员查找和分数获取。
- 跳跃链表 (Skip List): 用于存储成员及其分数,并根据分数进行排序。跳跃链表是一种概率型数据结构,它维护了多层链表,通过“跳跃”的方式快速定位元素,从而实现 O(logN) 时间复杂度的插入、删除和查找操作。
当 ZSET 中成员数量较少且成员长度较小时,Redis 会使用 ziplist(压缩列表)来优化存储,减少内存开销。一旦满足不了 ziplist 的条件,就会自动转换为哈希表和跳跃链表的组合。
二、ZSET 命令详解
以下是 ZSET 的一些常用命令及其用法:
-
添加成员:
ZADD key score member [score member ...]- 作用: 将一个或多个成员及其分数添加到有序集合中。
- 示例:
ZADD myrank 100 "Alice" 90 "Bob" 85 "Charlie" - 选项:
NX:只在成员不存在时添加。XX:只在成员存在时更新分数。CH:返回被修改的成员数量(包括新添加的和分数更新的)。INCR:将成员的分数增加指定值。
-
获取成员数量:
ZCARD key- 作用: 返回有序集合中成员的数量。
- 示例:
ZCARD myrank(返回 3)
-
获取成员分数:
ZSCORE key member- 作用: 返回有序集合中指定成员的分数。
- 示例:
ZSCORE myrank "Alice"(返回 100.0)
-
获取成员排名(从小到大):
ZRANK key member- 作用: 返回有序集合中指定成员的排名(0-based index),分数最低的成员排名为 0。
- 示例:
ZRANK myrank "Charlie"(返回 0)
-
获取成员排名(从大到小):
ZREVRANK key member- 作用: 返回有序集合中指定成员的排名(0-based index),分数最高的成员排名为 0。
- 示例:
ZREVRANK myrank "Alice"(返回 0)
-
通过排名范围获取成员:
ZRANGE key start stop [WITHSCORES]- 作用: 返回有序集合中指定排名范围内的成员,分数从小到大排序。
start和stop可以是负数,表示从末尾开始计算。 - 示例:
ZRANGE myrank 0 -1 WITHSCORES(返回所有成员及分数)
- 作用: 返回有序集合中指定排名范围内的成员,分数从小到大排序。
-
通过排名范围反向获取成员:
ZREVRANGE key start stop [WITHSCORES]- 作用: 返回有序集合中指定排名范围内的成员,分数从大到小排序。
- 示例:
ZREVRANGE myrank 0 1 WITHSCORES(返回分数最高的 2 个成员及分数)
-
通过分数范围获取成员:
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]- 作用: 返回有序集合中分数在
min和max之间的成员,分数从小到大排序。 - 示例:
ZRANGEBYSCORE myrank 80 95 WITHSCORES(返回分数在 80 到 95 之间的成员) - 分数区间:
(min:不包含min。[min:包含min。+inf:正无穷。-inf:负无穷。
- 作用: 返回有序集合中分数在
-
通过分数范围反向获取成员:
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]- 作用: 与
ZRANGEBYSCORE类似,但分数从大到小排序,且max和min的顺序是反过来的。 - 示例:
ZREVRANGEBYSCORE myrank 95 80 WITHSCORES
- 作用: 与
-
移除成员:
ZREM key member [member ...]- 作用: 从有序集合中移除一个或多个成员。
- 示例:
ZREM myrank "Bob"
-
通过排名范围移除成员:
ZREMRANGEBYRANK key start stop- 作用: 移除有序集合中指定排名范围内的成员。
- 示例:
ZREMRANGEBYRANK myrank 0 0(移除排名第一的成员)
-
通过分数范围移除成员:
ZREMRANGEBYSCORE key min max- 作用: 移除有序集合中分数在指定范围内的成员。
- 示例:
ZREMRANGEBYSCORE myrank 0 60(移除分数低于 60 的成员)
-
计算分数范围内的成员数量:
ZCOUNT key min max- 作用: 返回有序集合中分数在指定范围内的成员数量。
- 示例:
ZCOUNT myrank 80 100
-
对 ZSET 进行交集和并集操作:
ZINTERSTORE/ZUNIONSTORE- 作用: 这两个命令用于计算多个有序集合的交集或并集,并将结果存储到一个新的有序集合中。它们允许你指定如何聚合分数(
SUM、MIN、MAX)以及每个输入集合的权重。 - 复杂命令,详情请查阅 Redis 官方文档。
- 作用: 这两个命令用于计算多个有序集合的交集或并集,并将结果存储到一个新的有序集合中。它们允许你指定如何聚合分数(
三、ZSET 的应用案例
ZSET 凭借其独特的排序和范围查询能力,在许多场景中都扮演着关键角色。
-
排行榜功能:
- 场景: 游戏积分榜、商品销售榜、文章点赞榜等。
- 实现: 将用户 ID(或商品 ID、文章 ID)作为成员,积分(或销量、点赞数)作为分数。
- 操作:
- 用户积分更新:
ZADD game_rank INCR score user_id - 获取前 10 名:
ZREVRANGE game_rank 0 9 WITHSCORES - 获取用户排名:
ZREVRANK game_rank user_id - 获取用户分数:
ZSCORE game_rank user_id
- 用户积分更新:
-
最新文章/热门商品列表:
- 场景: 根据发布时间或热度(浏览量、评论数)展示文章或商品。
- 实现: 将文章 ID 作为成员,发布时间戳或热度值作为分数。
- 操作:
- 发布新文章:
ZADD latest_articles timestamp article_id - 获取最新 20 篇文章:
ZREVRANGE latest_articles 0 19 - 增加文章热度:
ZADD hot_articles INCR 1 article_id
- 发布新文章:
-
限时优惠/过期数据处理:
- 场景: 需要在特定时间后自动失效的缓存、优惠券或任务队列。
- 实现: 将数据 ID 作为成员,过期时间戳作为分数。
- 操作:
- 添加限时优惠:
ZADD timed_offers expire_timestamp offer_id - 定时扫描过期数据:
ZRANGEBYSCORE timed_offers -inf current_timestamp - 移除已过期数据:
ZREMRANGEBYSCORE timed_offers -inf current_timestamp
- 添加限时优惠:
-
社交 feed 流:
- 场景: 微博、朋友圈等社交应用中用户动态的展示。
- 实现: 将动态 ID 作为成员,发布时间戳作为分数,每个用户的 feed 维护一个 ZSET。
- 操作:
- 用户发布新动态:
ZADD user:123:feed timestamp dynamic_id - 获取用户最新动态:
ZREVRANGE user:123:feed 0 9
- 用户发布新动态:
-
地理位置索引(配合 GeoHash):
- 场景: 查找附近的人或地点。
- 实现: Redis 的 Geo 命令底层就是基于 ZSET 实现的,它将地理位置信息(经纬度)通过 GeoHash 算法编码成一个整数,作为 ZSET 的分数,经纬度作为成员。
- 操作:
GEOADD、GEORADIUS等命令。
四、总结
Redis 的有序集合 ZSET 是一个非常灵活和高效的数据结构,它巧妙地结合了哈希表和跳跃链表的优势,实现了成员的快速查找和按分数的高效排序。掌握 ZSET 的核心概念和命令,将有助于你在实际开发中设计出更加健壮、高性能的应用程序,尤其是在需要处理排行榜、时间序列数据和带有优先级的队列等场景。