[TOC] #### 1. 前言 --- 用户下单了,30 分钟没付款,自动取消 会议要开始了,提前 10 分钟发个提醒 这种 “到点再执行” 的需求,就是延迟队列的场景,用 ZSet 实现,思路很简单 #### 2. 核心思路 --- ZSet 的 score 存执行时间戳,member 存任务内容 添加一个 30 分钟后执行的任务: ```plaintext zadd delay_queue 1715136000 '{"task":"cancelOrder","orderId":1001}' ``` 取到期的任务(score ≤ 当前时间的就是到期了): ```plaintext zrangebyscore delay_queue 0 当前时间戳 LIMIT 0 10 ``` 取完删除: ```plaintext zrem delay_queue "任务内容" ``` #### 3. 消费逻辑 --- ```python while True: now = int(time.time()) tasks = redis.zrangebyscore("delay_queue", 0, now, start=0, num=10) for task in tasks: if redis.zrem("delay_queue", task): # 先删再执行 execute_task(json.loads(task)) time.sleep(1) ``` 为什么要先删再执行 ? 如果先执行再删,执行到一半挂了,任务还在队列里,会被重复执行 zrem 返回 1 表示删除成功,只有删除成功的那个 worker 才执行任务 多个 worker 同时取同一个任务,只有一个能删除成功,天然防重复 #### 4. 执行失败怎么办 --- 放回队列,加个重试次数: ```python try: do_something(task) except: task["retry"] = task.get("retry", 0) + 1 if task["retry"] < 3: redis.zadd("delay_queue", {json.dumps(task): now+10}) ``` 超过重试次数就记录日志,人工处理 #### 5. 本文小结 --- 小总结: + ZSet score 存时间戳,zrangebyscore 取到期任务 + 先删再执行,防重复消费 + zrem 返回值做竞争,多 worker 安全 + 执行失败可重试,设置最大次数 + 复杂场景用 RabbitMQ / RocketMQ