RetryMiddleware未生效主因是未在processes.php中为ConsumerProcess显式注册中间件,且任务内异常被try-catch吞掉导致无法冒泡;必须配置middleware数组、加#[Retry]注解,并确保Redis连接池充足。

RetryMiddleware 为什么没生效
Hyperf 的 RetryMiddleware 默认不启用,且只对「抛出异常」的任务生效——如果任务里用了 try-catch 吞掉异常、或直接 return false、或调用 exit(),中间件完全感知不到失败,自然不会重试。
常见错误现象:
- 日志里只有「任务开始」没「任务结束」,也无任何错误输出
- 队列里消息被消费一次就消失,但业务逻辑实际没跑通
- 手动在任务里
throw new \Exception('test')能触发重试,但真实异常却不行
根本原因不是中间件写错了,而是异常没冒泡到它能拦截的位置。Hyperf 异步队列的执行链是:Consumer → Job::handle() → 中间件栈(含 RetryMiddleware)。只要 handle() 方法内把异常 catch 住了,中间件就收不到信号。
RetryMiddleware 配置必须显式注册到 consumer 进程
RetryMiddleware 不是全局中间件,它只对异步队列的 consumer 进程有效,必须在 config/autoload/processes.php 中为 ConsumerProcess 显式配置中间件数组,否则压根不加载。
正确写法示例:
return [
Hyperf\AsyncQueue\Process\ConsumerProcess::class => [
'handler' => \Hyperf\AsyncQueue\Handler\DriverHandler::class,
'middleware' => [
\Hyperf\AsyncQueue\Middleware\RetryMiddleware::class,
],
],
];
注意点:
- 键名必须是
Hyperf\AsyncQueue\Process\ConsumerProcess::class,不能写成字符串'ConsumerProcess' -
middleware是子键,不是顶层配置;放在processes.php根数组里会无效 - 如果用了自定义 Consumer 类(如继承
ConsumerProcess),要确保该类的$handler和$middleware属性被正确继承或覆盖
重试次数和间隔必须通过注解或配置双控制
RetryMiddleware 本身不决定重试多少次、隔多久重试——它只提供重试能力,具体策略由任务类上的 #[Retry] 注解或 config/autoload/async_queue.php 中的全局 retry 配置驱动。
容易踩的坑:
- 只配了中间件,但任务类没加
#[Retry(max: 3, delay: 1000)],结果永远只执行 1 次 - 注解写了
delay: 1000,但单位是毫秒,误以为是秒,导致重试间隔过短,打爆下游服务 - 全局配置
'retry' => ['max_attempts' => 2]和注解同时存在时,以注解为准;但若注解没写max,才 fallback 到全局值 -
delay支持数组形式(如[1000, 2000, 5000])实现退避策略,但必须确保数组长度 ≥max,否则越界报错
失败后进死信队列前,得先确认 Redis 连接池没耗尽
任务执行失败、重试完毕仍不成功时,Hyperf 会尝试将任务推入死信队列(queue_name:failed)。但这一步本身依赖 Redis 客户端——如果连接池已满、Redis 服务不可达、或 wait_timeout 设置过短,连死信都写不进去,任务就彻底丢失,日志里只剩一句 Failed to push failed job to dead queue。
检查要点:
- 确认
config/autoload/async_queue.php中的driver配置是否指向正确的 Redis pool 名(如'default'),且该 pool 在redis.php中已正确定义 - 查看
redis.php里对应 pool 的max_connections是否足够:默认20往往不够,高并发下建议 ≥50 - 设置
wait_timeout≥3.0(秒),避免因获取连接超时导致死信写入失败 - 用
redis-cli LLEN queue_name:failed手动查死信队列长度,验证是否真有堆积
最常被忽略的一点:重试不是万能兜底。当任务失败源于数据状态不一致(比如订单已取消,积分任务还去查原订单)、或下游接口永久性变更(如短信网关废弃了旧 API),重试只会放大问题。这类场景必须靠任务内部的状态校验 + 明确的失败分支处理,而不是依赖 RetryMiddleware。


















