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 数据,不产生额外内存冗余。
2.2 score 与 member
ZSET 的每个元素由 member 和 score 两部分组成:
- member:唯一标识,不可重复。在延迟队列中通常存储任务 ID 或消息体摘要。
- score:双精度浮点数,用于排序。在延迟队列中存储任务到期时间戳(Unix 时间戳,单位秒或毫秒)。
延迟队列映射规则:
member = 任务唯一标识(如 order:10086)
score = 任务到期时间戳(如 1700000000)
消费者通过 ZRANGEBYSCORE 获取 score ≤ 当前时间的所有 member,即“已到期任务”。
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 延迟队列操作流程如下:
- 生产者入队:
ZADD delay_queue 1700000000 task:001 - 消费者轮询:
ZRANGEBYSCORE delay_queue -inf 1700000100 WITHSCORES - 消费任务: 处理 member 对应的业务逻辑
- 删除任务:
ZREM delay_queue task:001
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 以内的整数,毫秒时间戳完全在范围内)。