2. Redis ZSET 数据结构详解

ZSET(有序集合)是 Redis 中唯一一种同时支持唯一性与排序性的数据结构。在延迟队列场景中,ZSET 天然适合将任务到期时间戳作为 score,任务 ID 作为 member,实现精准的定时扫描与削峰消费。本节深入剖析 ZSET 的底层实现、核心概念与常用命令。

2.1 底层实现:跳跃表 + 字典

ZSET 内部采用 跳跃表(skiplist) 与 字典(dict) 双编码结构,两者协同工作,兼顾读写性能。

组件 作用 时间复杂度
跳跃表 按 score 排序存储所有 member,支持范围查询(ZRANGE、ZRANK) O(log N) 插入/删除/查找
字典 以 member 为 key,score 为 value,支持 O(1) 的成员判重与分数更新 O(1) 插入/删除/查找
设计意图: 跳跃表提供有序性,字典提供快速访问。两者通过指针共享同一份 member 和 score 数据,不产生额外内存冗余。
ZSET 底层双编码结构:跳跃表 + 字典 ZSET 底层双编码结构 跳跃表(skiplist) header L4: score=10 L4: score=30 L2: score=5 L2: score=10 L2: score=30 L0: score=5 L0: score=10 L0: score=30 字典(dict) member → score task:001 → 1700000000 task:002 → 1700000060 task:003 → 1700000120 ... 共享指针

2.2 score 与 member

ZSET 的每个元素由 member 和 score 两部分组成:

  • member:唯一标识,不可重复。在延迟队列中通常存储任务 ID 或消息体摘要。
  • score:双精度浮点数,用于排序。在延迟队列中存储任务到期时间戳(Unix 时间戳,单位秒或毫秒)。
延迟队列映射规则:
member = 任务唯一标识(如 order:10086)
score = 任务到期时间戳(如 1700000000)
消费者通过 ZRANGEBYSCORE 获取 score ≤ 当前时间的所有 member,即“已到期任务”。

2.3 常用命令

以下命令是 ZSET 延迟队列操作的核心,按功能分类说明:

命令 语法 延迟队列用途 时间复杂度
ZADD ZADD key score member [score member ...] 添加任务到队列,score 为到期时间 O(log N)
ZRANGE ZRANGE key start stop [WITHSCORES] 按索引范围查看任务(调试用) O(log N + M)
ZRANGEBYSCORE ZRANGEBYSCORE key min max [WITHSCORES] 获取 score 在 [min, max] 内的任务,即“到期任务” O(log N + M)
ZREM ZREM key member [member ...] 移除已消费或取消的任务 O(log N)
ZCARD ZCARD key 获取队列中待处理任务总数 O(1)
ZSCORE ZSCORE key member 查询某个任务的到期时间 O(1)
ZREMRANGEBYSCORE ZREMRANGEBYSCORE key min max 批量清理已过期或已处理的任务 O(log N + M)
注意: ZRANGEBYSCORE 的 min 和 max 支持 -inf 和 +inf,但延迟队列中通常使用 ZRANGEBYSCORE queue -inf now 获取所有到期任务。务必使用 WITHSCORES 参数确认 score 值。

2.4 典型操作流程

一个完整的 ZSET 延迟队列操作流程如下:

  1. 生产者入队: ZADD delay_queue 1700000000 task:001
  2. 消费者轮询: ZRANGEBYSCORE delay_queue -inf 1700000100 WITHSCORES
  3. 消费任务: 处理 member 对应的业务逻辑
  4. 删除任务: ZREM delay_queue task:001
ZSET 延迟队列操作流程 ZSET 延迟队列操作流程 生产者 ZADD score=到期时间 ZSET 延迟队列 score 排序存储 member 唯一 消费者 ZRANGEBYSCORE ZREM 删除 消费后移除

2.5 性能与内存考量

  • 插入/删除: O(log N),N 为 ZSET 中元素总数。百万级任务队列单次操作约 20-30 微秒。
  • 范围查询: O(log N + M),M 为返回元素数量。延迟队列每次轮询通常只取少量到期任务,M 很小。
  • 内存占用: 每个元素约 40-60 字节(member + score + 跳跃表指针)。100 万任务约占用 50-60 MB。
最佳实践: 延迟队列的 score 使用秒级时间戳即可满足大部分场景。若需要毫秒级精度,可将毫秒时间戳直接作为 score(Redis 的 double 可精确表示 2^53 以内的整数,毫秒时间戳完全在范围内)。