Hyperf 3.0中实现延迟任务需严格配置Redis ZSet驱动、毫秒级score_precision、原子Lua脚本扫描及独立进程,否则多Worker下必重复消费。

在Hyperf 3.0中实现订单超时自动关闭、短信延后发送等延迟任务,不能只靠配置文件改个参数就完事——ZSet的score必须是整型绝对时间戳,member必须可反序列化且带唯一标识,扫描逻辑必须用Lua脚本原子执行“取+删”,否则多Worker下必然重复消费。
确认异步队列组件已安装并启用
执行composer require hyperf/async-queue安装组件。若项目未生成配置文件,运行php bin/hyperf.php vendor:publish hyperf/async-queue生成config/autoload/async_queue.php。
检查config/autoload/processes.php是否已注册消费者进程:Hyperf\AsyncQueue\Process\ConsumerProcess::class。没加这行,任务永远不消费。
配置延迟队列驱动为Redis ZSet
修改config/autoload/async_queue.php,将driver替换为Hyperf\DelayQueue\Driver\RedisDriver::class。
新增delay队列配置块,注意score_precision必须设为0.001(毫秒级精度),否则$delay参数单位会错乱:【$delay传入秒数时,score_precision=0.001才对应毫秒级写入】
示例配置:
'delay' => [
'driver' => Hyperf\DelayQueue\Driver\RedisDriver::class,
'queue_name' => 'order:delay:close',
'score_precision' => 0.001,
'retry_seconds' => 60,
'max_attempts' => 3,
],
创建延迟任务类并投递
新建app/Job/OrderCloseJob.php,继承Hyperf\DelayQueue\Job而非普通AsyncQueue\Job。
在handle()方法里写实际业务逻辑,比如查订单状态、调关单接口、发通知。
投递时调用$this->container->get(DelayQueue::class)->push(new OrderCloseJob($orderId), $delay),其中$delay单位为秒,但底层会乘以1000转成毫秒写入ZSet——前提是score_precision=0.001已生效。
启动独立扫描进程避免多Worker争抢
第一步:禁用默认ConsumerProcess对delay队列的监听,防止它错误消费ZSet数据。
第二步:新建app/Process/DelayQueueScanProcess.php,使用Swoole定时器每5秒触发一次扫描。
第三步:在scan逻辑中,先用setnx delay_queue:scan_lock 1加分布式锁,设置过期时间10秒;抢到锁后,执行Lua脚本eval($luaScript, 1, $key, $nowMs)完成原子取删;完成后del锁。
第四步:Lua脚本必须严格按规范写——zRangeByScore的min设为0而非-inf,避免Redis解析开销;返回值为空时立即return,不要空跑。


















