Redis有序集合ZSET:核心概念、命令详解与应用案例 – wiki基地


Redis 有序集合 ZSET:核心概念、命令详解与应用案例

Redis,作为一个高性能的键值存储系统,提供了多种数据结构来满足不同的应用场景。其中,有序集合(Sorted Set),简称 ZSET,是一个非常强大且独特的数据结构,它结合了哈希表和跳跃链表的特性,使得数据既能快速查找,又能按分数(score)进行排序。

一、ZSET 的核心概念

有序集合 ZSET 是一个非重复字符串的集合,其中每个成员(member)都关联着一个浮点数分数(score)。这个分数是 ZSET 的核心,它决定了集合中成员的排序方式。

关键特性:

  1. 成员唯一性: ZSET 中的每个成员都是唯一的,不能有重复的成员。
  2. 分数排序: 集合中的所有成员都根据其关联的分数进行排序。分数可以重复,但成员不能重复。当分数相同时,Redis 会根据成员的字典序进行二次排序(这是 Redis 3.2 版本之后引入的)。
  3. 快速查找: ZSET 内部通过哈希表存储成员到分数的映射,因此可以 O(1) 时间复杂度快速查找成员对应的分数。
  4. 范围查询: ZSET 内部通过跳跃链表(Skip List)维护了成员的有序性,使得可以高效地进行范围查询,例如查询分数在某个区间内的成员,或者查询排名在某个区间内的成员。

内部实现:

Redis 的 ZSET 底层采用了两种数据结构:

  • 哈希表 (Hash Table): 用于存储成员到分数的映射,提供 O(1) 时间复杂度的成员查找和分数获取。
  • 跳跃链表 (Skip List): 用于存储成员及其分数,并根据分数进行排序。跳跃链表是一种概率型数据结构,它维护了多层链表,通过“跳跃”的方式快速定位元素,从而实现 O(logN) 时间复杂度的插入、删除和查找操作。

当 ZSET 中成员数量较少且成员长度较小时,Redis 会使用 ziplist(压缩列表)来优化存储,减少内存开销。一旦满足不了 ziplist 的条件,就会自动转换为哈希表和跳跃链表的组合。

二、ZSET 命令详解

以下是 ZSET 的一些常用命令及其用法:

  1. 添加成员:ZADD key score member [score member ...]

    • 作用: 将一个或多个成员及其分数添加到有序集合中。
    • 示例: ZADD myrank 100 "Alice" 90 "Bob" 85 "Charlie"
    • 选项:
      • NX:只在成员不存在时添加。
      • XX:只在成员存在时更新分数。
      • CH:返回被修改的成员数量(包括新添加的和分数更新的)。
      • INCR:将成员的分数增加指定值。
  2. 获取成员数量:ZCARD key

    • 作用: 返回有序集合中成员的数量。
    • 示例: ZCARD myrank (返回 3)
  3. 获取成员分数:ZSCORE key member

    • 作用: 返回有序集合中指定成员的分数。
    • 示例: ZSCORE myrank "Alice" (返回 100.0)
  4. 获取成员排名(从小到大):ZRANK key member

    • 作用: 返回有序集合中指定成员的排名(0-based index),分数最低的成员排名为 0。
    • 示例: ZRANK myrank "Charlie" (返回 0)
  5. 获取成员排名(从大到小):ZREVRANK key member

    • 作用: 返回有序集合中指定成员的排名(0-based index),分数最高的成员排名为 0。
    • 示例: ZREVRANK myrank "Alice" (返回 0)
  6. 通过排名范围获取成员:ZRANGE key start stop [WITHSCORES]

    • 作用: 返回有序集合中指定排名范围内的成员,分数从小到大排序。startstop 可以是负数,表示从末尾开始计算。
    • 示例: ZRANGE myrank 0 -1 WITHSCORES (返回所有成员及分数)
  7. 通过排名范围反向获取成员:ZREVRANGE key start stop [WITHSCORES]

    • 作用: 返回有序集合中指定排名范围内的成员,分数从大到小排序。
    • 示例: ZREVRANGE myrank 0 1 WITHSCORES (返回分数最高的 2 个成员及分数)
  8. 通过分数范围获取成员:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

    • 作用: 返回有序集合中分数在 minmax 之间的成员,分数从小到大排序。
    • 示例: ZRANGEBYSCORE myrank 80 95 WITHSCORES (返回分数在 80 到 95 之间的成员)
    • 分数区间:
      • (min:不包含 min
      • [min:包含 min
      • +inf:正无穷。
      • -inf:负无穷。
  9. 通过分数范围反向获取成员:ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]

    • 作用:ZRANGEBYSCORE 类似,但分数从大到小排序,且 maxmin 的顺序是反过来的。
    • 示例: ZREVRANGEBYSCORE myrank 95 80 WITHSCORES
  10. 移除成员:ZREM key member [member ...]

    • 作用: 从有序集合中移除一个或多个成员。
    • 示例: ZREM myrank "Bob"
  11. 通过排名范围移除成员:ZREMRANGEBYRANK key start stop

    • 作用: 移除有序集合中指定排名范围内的成员。
    • 示例: ZREMRANGEBYRANK myrank 0 0 (移除排名第一的成员)
  12. 通过分数范围移除成员:ZREMRANGEBYSCORE key min max

    • 作用: 移除有序集合中分数在指定范围内的成员。
    • 示例: ZREMRANGEBYSCORE myrank 0 60 (移除分数低于 60 的成员)
  13. 计算分数范围内的成员数量:ZCOUNT key min max

    • 作用: 返回有序集合中分数在指定范围内的成员数量。
    • 示例: ZCOUNT myrank 80 100
  14. 对 ZSET 进行交集和并集操作:ZINTERSTORE / ZUNIONSTORE

    • 作用: 这两个命令用于计算多个有序集合的交集或并集,并将结果存储到一个新的有序集合中。它们允许你指定如何聚合分数(SUMMINMAX)以及每个输入集合的权重。
    • 复杂命令,详情请查阅 Redis 官方文档。

三、ZSET 的应用案例

ZSET 凭借其独特的排序和范围查询能力,在许多场景中都扮演着关键角色。

  1. 排行榜功能:

    • 场景: 游戏积分榜、商品销售榜、文章点赞榜等。
    • 实现: 将用户 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
  2. 最新文章/热门商品列表:

    • 场景: 根据发布时间或热度(浏览量、评论数)展示文章或商品。
    • 实现: 将文章 ID 作为成员,发布时间戳或热度值作为分数。
    • 操作:
      • 发布新文章:ZADD latest_articles timestamp article_id
      • 获取最新 20 篇文章:ZREVRANGE latest_articles 0 19
      • 增加文章热度:ZADD hot_articles INCR 1 article_id
  3. 限时优惠/过期数据处理:

    • 场景: 需要在特定时间后自动失效的缓存、优惠券或任务队列。
    • 实现: 将数据 ID 作为成员,过期时间戳作为分数。
    • 操作:
      • 添加限时优惠:ZADD timed_offers expire_timestamp offer_id
      • 定时扫描过期数据:ZRANGEBYSCORE timed_offers -inf current_timestamp
      • 移除已过期数据:ZREMRANGEBYSCORE timed_offers -inf current_timestamp
  4. 社交 feed 流:

    • 场景: 微博、朋友圈等社交应用中用户动态的展示。
    • 实现: 将动态 ID 作为成员,发布时间戳作为分数,每个用户的 feed 维护一个 ZSET。
    • 操作:
      • 用户发布新动态:ZADD user:123:feed timestamp dynamic_id
      • 获取用户最新动态:ZREVRANGE user:123:feed 0 9
  5. 地理位置索引(配合 GeoHash):

    • 场景: 查找附近的人或地点。
    • 实现: Redis 的 Geo 命令底层就是基于 ZSET 实现的,它将地理位置信息(经纬度)通过 GeoHash 算法编码成一个整数,作为 ZSET 的分数,经纬度作为成员。
    • 操作: GEOADDGEORADIUS 等命令。

四、总结

Redis 的有序集合 ZSET 是一个非常灵活和高效的数据结构,它巧妙地结合了哈希表和跳跃链表的优势,实现了成员的快速查找和按分数的高效排序。掌握 ZSET 的核心概念和命令,将有助于你在实际开发中设计出更加健壮、高性能的应用程序,尤其是在需要处理排行榜、时间序列数据和带有优先级的队列等场景。


滚动至顶部