attempts 控制最大重试总次数(含首次执行),ttr 限定单次任务最长执行时间;二者需在队列组件中显式配置,否则使用默认值(attempts=0 不重试,ttr=300秒)。

yii\queue 配置 attempts 和 ttr 是怎么生效的?
Yii2 的队列重试机制不是靠“捕获异常自动重发”,而是由队列驱动(如 Redis、DB)在任务失败后,根据配置决定是否重新入队——前提是任务执行中抛出了未捕获异常,且当前重试次数未超限。
attempts 控制最大重试总次数(含首次执行),ttr(Time To Run)是单次任务最长允许执行时间,超时会被强制标记为失败并触发重试逻辑。这两个参数必须在队列组件配置里显式声明,否则使用默认值(attempts=0 表示不重试,ttr=300 秒)。
-
attempts=3意味着:第 1 次执行失败 → 入队重试;第 2 次失败 → 再入队;第 3 次失败 → 进入 failed jobs 表(如果驱动支持),不再重试 -
ttr=60不代表“延迟 60 秒再重试”,而是“这个任务最多运行 60 秒,超时就 kill 并当失败处理” - Redis 驱动下,
ttr依赖BRPOPLPUSH+ 定时扫描,DB 驱动则靠 worker 启动时的超时检查,行为略有差异
任务类里手动判断 $job->attempt 有必要吗?
没必要,除非你要做差异化逻辑——比如第 1 次失败降级调用短信,第 3 次才写日志告警。因为 Yii2 队列底层已自动在抛出异常前检查了 $queue->attempts,只要没超限,直接 throw 就会触发重试。
但要注意:$job->attempt 返回的是当前这次执行的序号(从 1 开始),不是剩余次数。它只在 job 实例中可用,且仅对当前正在执行的任务有效。
- 正确用法:
if ($job->attempt - 错误用法:
if ($job->attempt === 1) { sleep(1); }—— 这会导致首次执行人为阻塞,拖慢整个队列吞吐 - 别在
execute()里 try/catch 后静默 return,这等于放弃重试机会,队列认为“成功完成”
为什么设置了 attempts=3 却只重试了 1 次?
常见原因是任务没真正“失败”:你 catch 了异常但没 re-throw,或者用了 return 提前退出,又或者数据库事务 rollback 后没抛异常。队列驱动只看 execute() 方法是否以异常结束。
另一个隐蔽原因是 delay() 被误用。比如你在 job 类里写 $queue->delay(60)->push(...),这不是设置重试延迟,而是向另一个队列推新任务——和当前失败重试完全无关。
- 重试延迟要靠驱动自身机制:Redis 队列用
retryDelay配置项(需 yii-queue >= 2.3.0),MySQL 队列则依赖failed_at字段 + 定时脚本扫描 - 检查
failed表(或 Redis 中的queue:failedlist)是否存在记录,有则说明重试已耗尽;没有则大概率是任务被当成“成功”处理了 - 开启
YII_DEBUG并监听yii\queue\JobEvent::EVENT_AFTER_EXEC,可确认每次执行的 exit status
DB 队列 vs Redis 队列,重试行为有什么实际差别?
DB 队列(如 shmilyzxt/yii2-queue)重试依赖轮询:worker 每次 fetch 时查 reserved_at IS NULL AND delayed_at IS NULL AND attempts ,所以重试间隔受 worker 启动频率影响(比如你每 5 秒跑一次 <code>php yii worker/listen,那最快也要等 5 秒才重试)。
Redis 队列(yii\queue\redis\Queue)用 BRPOPLPUSH + ZSET 维护延迟队列,能实现秒级甚至亚秒级重试,但要求 Redis 版本 ≥ 6.2 且启用 notify-keyspace-events 才能及时触发。
- DB 方案适合低频、容忍延迟的任务(如日志归档、报表生成)
- Redis 方案适合时效敏感场景(如通知推送、库存预占),但部署复杂度高,出问题更难排查
- 两者都**不保证 exactly-once**,网络分区或 worker crash 可能导致重复执行,业务代码必须幂等
failed 表里的 error 字段,别光盯着配置数。











