zset 可模拟轻量级延迟队列,需在写入时注入随机偏移(如±300秒)使任务均匀分布,避免集中触发;score 用秒级时间戳,value 存任务id;消费者用 zrangebyscore + zrem 防重复,并校验 score 窗口;须动态调整偏移范围、记录原始计划时间、在 score 中嵌入过期时间防堆积。

用 Redis 的 ZSET 实现任务时间戳分级调度
Redis 本身不支持原生定时任务,但 ZSET 的有序性 + ZRANGEBYSCORE 可以模拟轻量级延迟队列。关键不是“什么时候触发”,而是“让任务在指定时间窗口内均匀分布”。比如原计划凌晨 2:00 执行 10 万条用户签到补发,直接扫库+批量更新会造成 CPU 和 Redis 内存瞬时飙升;改用 ZSET 后,可把这批任务按秒级偏移写入不同 score(如 1717020000 + rand(0, 300)),再由消费者每秒拉取当前时间前后 1 秒内的任务,自然摊平压力。
常见错误是把所有任务 score 都设成同一时间戳——这等于没打散,ZSET 只保证顺序,不自动拆分执行节奏。必须在写入时就注入随机偏移,且偏移范围要大于单次消费周期(例如消费者每秒拉一次,偏移就得 ≥1s)。
- score 建议用 Unix 时间戳(秒级即可,毫秒反而增加 ZSET 比较开销)
- value 推荐存任务 ID 或轻量 payload(如
"task:sign:uid_12345"),避免在 ZSET 中存大 JSON - 消费者需用
ZPOPMIN或ZRANGEBYSCORE + ZREM组合,防止重复消费;若用ZRANGEBYSCORE,务必加WITHSCORES并校验 score 是否仍在目标窗口内(防时钟漂移)
结合业务低峰期动态调整 ZSET score 偏移区间
硬编码偏移(如固定 ±300 秒)在流量突增或运维变更时容易失效。更稳妥的做法是读取实时指标(如 redis-cli info | grep "used_memory_human"、或业务侧上报的 QPS),动态缩放偏移范围。例如:当 Redis 内存使用率 >85% 时,把原本 5 分钟的打散窗口拉长到 15 分钟;当核心接口 P99 >2s 时,暂停新任务入队。
这个逻辑不能放在 Redis 里做——它没有条件判断和外部指标拉取能力。必须由外部调度器(如 Python 脚本或 Go Worker)控制:task_scheduler.py 定期查监控 API,算出当前允许的最大吞吐,再调用 ZADD 时传入动态计算的 score。
- 不要在 Lua 脚本里做复杂判断:Redis Lua 不支持 HTTP 请求、时间差计算易出错,且阻塞主线程
- 偏移值建议用正态分布随机数(如 Python 的
random.gauss(mu=0, sigma=offset/3)),比均匀随机更能避免边缘堆积 - 记录每个任务的原始计划时间(存入 Hash 或额外字段),便于后续审计“为什么这个任务晚了 8 分钟执行”
避免 ZSET 过期任务堆积导致内存泄漏
ZSET 没有自动过期机制,如果消费者 crash 或网络分区,未处理的任务会永久滞留。单纯依赖 EXPIRE 给整个 key 加 TTL 不行——新任务不断写入,TTL 会被重置,老任务永远活不过期。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
正确做法是在 score 中嵌入“绝对过期时间”,消费时先检查 score 。例如任务最多容忍延迟 10 分钟,则只拉取 <code>score 的任务;同时配一个后台清理 job,每 5 分钟跑一次 <code>ZREMRANGEBYSCORE key -inf (now-600) 清理超时任务。
- 注意
ZREMRANGEBYSCORE的括号语法:(now-600)表示“严格小于”,避免删掉刚好卡点的任务 - 清理 job 的频率要低于业务低峰期最小粒度(如低峰期是凌晨 2–4 点,清理间隔别设成 1 小时,选 15 分钟更安全)
- 别用
KEYS扫描所有 ZSET:线上环境 key 数量大时会阻塞,改用SCAN+ 模式匹配
与 Redis Stream 对比:什么场景该换用 Stream
当任务需要严格保序、多消费者组协同、或携带大量上下文(>1KB),ZSET 就力不从心了。Stream 天然支持 XREADGROUP、消息 pending list、自动 ACK,更适合“下单后 30 分钟未支付取消”的强时序场景。但它的代价是:内存占用更高(每条消息自带 ID 和元数据)、命令更复杂、且无法像 ZSET 那样用 score 做范围索引快速捞出“未来 5 秒内该执行的任务”。
简单判断标准:只要任务执行时间点允许 ±10 秒误差,且单条负载 XADD 时把 ID 设为 timestamp_ms-xxxxxx 再按 ID 范围消费)。
最容易被忽略的是连接模型:ZSET 方案里消费者通常是短连接轮询(sleep 1s 后重试),而 Stream 推荐长连接 + BLOCK,对客户端稳定性要求更高。如果业务容器经常滚动重启,ZSET 的容错性反而更好。










