laravel的$tries仅控制最大执行次数,不决定等待时间;真正的指数退避必须显式配置$backoff为数组(如[1,2,4,8]),否则固定延迟;hyperf需用coroutine::sleep()配合手动重投实现指数退避。

PHP任务重试不是“多试几次”,而是控制节奏和边界
直接结论:Laravel 的 $tries 只管执行次数上限,不决定等待时间;真正的指数退避必须显式配置 $backoff 为数组,否则全是固定延迟。Hyperf 则依赖协程环境下的 Coroutine::sleep() 配合手动重投逻辑,框架默认策略不自动指数增长。
Laravel 中 $backoff = [1, 2, 4, 8] 才算真正指数退避
常见错误是设了 $tries = 5 却只写 $backoff = 2,结果每次失败都等 2 秒——这不是退避,是卡死节奏。
-
$backoff是整数 → 全部重试统一等待该秒数 -
$backoff是数组(如[1, 2, 4, 8])→ 第 1 次失败后等 1 秒,第 2 次等 2 秒,第 3 次等 4 秒……数组长度必须 ≥$tries - 1,否则后续重试重复用最后一个值 -
retryAfter()返回整数秒,仅影响当前任务实例的最小延迟,优先级低于$backoff数组 -
failed()里写sleep()完全无效:worker 进程已退出,这个 sleep 不会阻塞重试调度
Redis 驱动能精准兑现 $backoff 数组;database 驱动因依赖 available_at 字段写入时间,实际延迟可能偏差 ±1 秒以上。
Hyperf 异步队列要退避,必须用 Coroutine::sleep() + 手动 push
Hyperf 默认不提供声明式指数退避配置,sleep($delay) 在协程里会阻塞整个 worker,必须换成 Coroutine::sleep($delay)。
- 前提:config/autoload/coroutine.php 中
must_use_scheduler必须为true,否则Coroutine::sleep()退化为同步阻塞 - 禁用 config/autoload/async_queue.php 中的
'retry_seconds'(设为 0),关闭框架自动重试 - 在
execute()中捕获异常,仅对ConnectionException、TimeoutException等瞬态错误做条件重投 - 调用
$this->container->get(JobDispatcher::class)->push(new static($payload), $delaySeconds)实现带延迟的重新入队
注意:$delaySeconds 要自己按指数规则计算(比如第 n 次失败就设 pow(2, n-1)),框架不帮你算。
别忽略驱动差异和失败兜底的耦合关系
Redis 和 database 队列对退避的支撑能力差很多,但更常被忽略的是:退避策略必须和失败后的可观测性联动。
- 用 database 驱动时,
failed_jobs表写入有延迟,若又配了长$backoff,可能任务已重试多次才进表,查问题时日志断层 - Redis 驱动虽快,但失败任务不会自动落库,必须在
failed()方法里显式记录日志、触发告警或写入审计表 - 所有重试逻辑都要考虑幂等性:指令下发类任务,重试前得先查设备是否已响应,否则可能重复启停
最易被跳过的点:没验证 must_use_scheduler 是否生效,或没确认 Redis 连接是否真在用 zset 排序——这两处一错,退避就形同虚设。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











