yii\queue任务类必须实现JobInterface,不可将逻辑写入控制器;需新建独立类实现execute(),确保可序列化、依赖注入,并配置retryLimit、channel等参数以保障队列稳定运行。

yii\queue 任务类必须实现 JobInterface,但别直接写 execute() 逻辑到控制器里
很多人在第一次用 yii\queue 时,会把生成报表、发邮件的代码直接塞进控制器 action 里,再调用 $queue->push() —— 这看似“推了任务”,实际只是把一个闭包或匿名函数扔进队列,而 Yii 默认不支持序列化闭包,运行时直接报 Serialization of 'Closure' is not allowed。
正确做法是:新建一个独立 PHP 类,实现 yii\queue\JobInterface,所有业务逻辑写在 execute($queue) 方法内。这个类必须能被自动加载(命名空间 + 文件路径匹配),且所有依赖(如 DAO、Service)不能硬编码,要通过构造参数或 setter 注入(或用容器解析)。
-
$queue->push(new ReportGeneratorJob(['reportType' => 'sales']))是安全的; -
$queue->push(function () { /* ... */ })会失败; - 类属性必须可序列化(避免
PDO、Redis实例等资源型对象); - 如果要用日志,别用
Yii::info()直接打,建议加as log => \yii\queue\LogBehavior::class配置,它能自动记录 push/pop/failed 状态。
数据库驱动下,yii\queue\db\Queue 的 retryLimit 和 channel 不是摆设
用数据库做队列存储(yii\queue\db\Queue)很常见,但默认配置容易导致“任务卡死”或“重复执行”。比如你没设 retryLimit,某个任务因临时网络错误失败,它会无限重试,占满队列表;又或者多个模块共用一个 channel,报表任务和通知任务混在一起,消费顺序不可控。
关键配置项含义和推荐值:
-
retryLimit => 3:建议显式设为小整数(0 表示不重试,-1 是无限重试,生产环境慎用); -
channel => 'report':按业务域隔离,比如'report'、'notification',避免互相干扰; -
tableName => '{{%queue_report}}':配合channel,不同通道可用不同表(需手动建表或改迁移); - 记得跑迁移:
php yii migrate --migrationPath=@yii/queue/migrations/db,否则表不存在,push()会静默失败(只抛 PDOException,无队列相关提示)。
并发消费靠 yii queue/listen 多进程,不是靠单个命令加 -v
很多人以为加 php yii queue/listen -v 就能并发处理,其实这只是开启一个常驻 worker 进程,串行消费。真正提升吞吐量,得靠操作系统级多实例管理——比如用 Supervisor 启多个 queue/listen 进程,每个绑定不同 --channel 或共享同一 channel(由 DB 行锁保证不重复取)。
注意点:
- DB 驱动下,并发安全靠
SELECT ... FOR UPDATE+ 事务,但前提是 MySQL 使用 InnoDB,且隔离级别不是READ UNCOMMITTED; - 不要在
execute()里做长耗时同步操作(如大文件 IO、外部 HTTP 请求未设 timeout),会导致 worker 卡住,整个队列积压; - Redis 驱动(
yii\queue\redis\Queue)天然支持更高并发,但需要额外配好yii\redis\Connection组件,且注意redis扩展已启用; - 测试并发效果,别只看
queue/stats,要查queue表的done_at和reserved_at时间差,确认是否真在并行执行。
yii\queue 没有内置“延迟队列”语义,靠 delay() 方法或时间字段模拟
Yii 的 push() 不支持原生延迟(如“5 分钟后执行”),但提供了 delay($seconds) 链式方法。它的实现原理是:写入队列表时,把 delayed_at 设为当前时间 + 秒数,worker 在轮询时跳过未到时间的任务。
使用限制很实在:
-
delay()只对 DB 和 Redis 驱动生效,AMQP(RabbitMQ)驱动不支持; - DB 驱动下,延迟精度取决于
queue/listen的轮询间隔(默认 1 秒),无法做到毫秒级; - 别用
delay(0)当“立即执行”——它会被当成延迟任务,worker 会跳过; - 如果需要精确调度(比如每天凌晨 2 点生成报表),别依赖
delay(),改用系统 cron 调php yii queue/run或单独起定时任务触发push()。
最易被忽略的一点:所有延迟任务在 DB 中都存为普通记录,没有索引优化 delayed_at 字段,大量延迟任务堆积时,SELECT 查询会变慢——上线前记得给 delayed_at 加索引。


















