laravel12下基于redis的消息队列原理回顾
1. 命令参数含义
queue:work👉 启动一个 队列 Worker 进程redis👉 不是 queue 名称,而是 队列连接(connection)名称
Laravel 的队列配置在 config/queue.php,里面有 connections:
'connections' => [
'sync' => [...],
'database' => [...],
'redis' => [ // ← 这里就是 connection 名字
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'), // 默认队列名叫 "default"
'retry_after' => 90,
'block_for' => null,
],
],
所以你运行:
php artisan queue:work redis
表示:用 redis 这个 connection 去取任务。
如果你要指定队列名字(queue name),要额外传:
php artisan queue:work redis --queue=emails
这样就会消费 emails 队列,而不是默认的 default。
2. Worker 的运行原理
queue:work 会启动一个常驻 PHP 进程,内部大致流程是:
- 进入循环 → 不断向 Redis 里
BLPOP(阻塞式 pop) 队列- 默认队列 key 格式:
queues:default - 如果指定
--queue=emails→ key 就是queues:emails
- 默认队列 key 格式:
- 拿到队列消息(就是被
dispatch()序列化过的 Job 数据) - 反序列化 Job → 调用 Job 的
handle()方法 - 如果失败 → 重新入队(延迟),或者进入
failed_jobs表 本质上就是一个常驻消费者进程,用 Redis 列表实现消息队列。
3. Redis 中的存储形式
当你派发 Job:
SendEmailJob::dispatch('test@example.com');
Redis 里会多一条数据:
LPUSH queues:default "序列化后的 Job"
Worker 消费时就是:
BRPOP queues:default
然后执行 Job。
✅ 总结:
php artisan queue:work redis里的 redis = connection 名,不是 queue 名。- 真正的 队列名(queue name) 默认是
default,可以用--queue=参数指定。如: php artisan queue:work redis --queue=emails - Worker 内部就是不断 BRPOP Redis 列表,取出 Job,执行
handle()。
config/queue.php下关于 after_commit配置的使用,这个特性很有必要,貌似从larael9.x 就有了,实际上有时候确实需要数据库操作完成,才回去触发异步任务,这样的需求很多
在 Laravel 12 的 queue.php 配置中,after_commit 是一个比较新的选项,用来控制 Job 是否在数据库事务提交后再入队。
1. 默认配置示例
'connections' => [
'database' => [
'driver' => 'database',
'table' => 'jobs',
'queue' => 'default',
'retry_after' => 90,
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
'after_commit' => false,
],
],
2. after_commit 的作用
after_commit = false(默认)- Job 会立即入队,即使当前数据库事务尚未提交
- 如果事务回滚,Job 依然会被消费 → 可能导致业务逻辑异常
after_commit = true- Job 会 等到数据库事务提交成功后 再入队
- 保证 Job 消费时,相关数据库操作已经生效
- 常用于依赖数据库状态的异步任务,例如发送邮件、延迟计算统计等
3. 使用示例
use App\Jobs\MyJob;
use Illuminate\Support\Facades\DB;
DB::transaction(function () use ($user) {
$user->update(['status' => 'active']);
// Job 只有在事务提交成功后才会入队
MyJob::dispatch($user)->afterCommit();
});
afterCommit()方法可以在 dispatch 时单独控制- 配置文件
after_commit选项是全局默认值
4. 总结
| 配置 / 方法 | 行为 |
|---|---|
after_commit = false | Job 立即入队,不依赖事务提交 |
after_commit = true | Job 只有在事务提交成功后入队 |
->afterCommit() 方法 | 单次 dispatch 时覆盖全局配置 |
✅ 小结:
- 如果 Job 依赖数据库状态,推荐开启
after_commit或使用->afterCommit() - 如果 Job 与事务无关,可以保持默认
false
使用延迟队列
在 Laravel 12 下,如果你想让某个 Job 延迟执行,可以用 delay() 方法 或 onQueue + delay 的组合。这里详细讲一下:
1. 在 Dispatch 时指定延迟
use App\Jobs\MyJob;
use Illuminate\Support\Facades\Bus;
// 延迟 10 秒执行
MyJob::dispatch($param1, $param2)->delay(now()->addSeconds(10));
now()→ 当前时间addSeconds(10)→ 延迟 10 秒- 也可以用
addMinutes(),addHours()等
例子:
MyJob::dispatch($user)->delay(now()->addMinutes(5));
- Job 会被推到队列,但 不会立即被 worker 执行
- Worker 取到任务时会检查
available_at时间,未到时会跳过
2. 在 Job 类里使用 ShouldQueue
确保你的 Job 类实现了 ShouldQueue:
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class MyJob implements ShouldQueue
{
use Dispatchable, Queueable;
public function handle()
{
// 任务逻辑
}
}
Queueabletrait 提供了delay()方法的支持ShouldQueue告诉 Laravel 这是一个异步任务
3. 结合队列名
如果你有多个队列,也可以指定队列:
MyJob::dispatch($user)
->onQueue('emails')
->delay(now()->addMinutes(5));
- Job 会被推到
emails队列 - Worker 可以专门监听
emails队列
4. 延迟实现原理(Redis 队列)
- Laravel 会把 Job 推到 Redis 的
delayed队列 - Worker 会周期性检查任务的
available_at时间 - 只有到达时间的 Job 才会从
delayed队列移动到正常队列,等待消费
所以延迟执行是通过 Redis 队列 + 时间戳 控制的,不依赖 PHP sleep 或阻塞
5.关于 延迟原理的详细分析
在 Laravel 队列 + Redis 的场景下,Worker 确实同时扮演了 两个角色:
- 消费者(Consumer)
- 从主队列(ready queue)取任务执行
- 核心行为:
- 监听队列(BLPOP 或轮询)
- 拿到任务 → 调用 Job 的
handle()执行
- 类似“真正的工人”,负责处理业务逻辑
- 延迟任务搬运者(Delayed Job Scheduler)
- 周期性检查 Redis
delayed队列 - 核心行为:
- 取出到达
available_at时间的 Job - 把它移动到主队列(ready queue)
- 取出到达
- 类似“调度员”,保证延迟任务在到期时进入可执行队列
- 图示理解(逻辑流程)
Delayed Queue (Redis zset)
|
| Worker 检查 available_at
v
Ready Queue (Redis list)
|
| Worker 消费任务
v
Job handle() 执行
表格解析:
| 概念 | 说明 |
|---|---|
| Worker | 运行 php artisan queue:work 的 CLI 进程 |
| Delayed 队列 | Redis 的 zset,score = available_at |
| 周期检查 | Worker 循环中调用 migrateExpiredJobs() 检查到期任务 |
| Ready 队列 | Job 到时间后被移入这个队列,等待执行 |
- Worker 是 单个常驻进程,同时做 调度 和 消费
- 所以即使延迟任务很多,也不需要额外的 Cron,Worker 自己就能处理
✅ 总结
- Worker = 消费者 + 延迟任务搬运者
- 延迟任务逻辑是 Worker 内部循环的一部分
- 你可以通过启动多个 Worker 或指定队列,灵活管理消费策略
✅ 总结:
- Job 类要
implements ShouldQueue - Dispatch 时调用
->delay(now()->addMinutes(...)) - 可选
onQueue()指定队列 - Worker 会自动处理延迟逻辑
参考文档: https://laravel.com/docs/12.x/queues#delayed-dispatching
关于 多个不同的queue name 的使用以及消费
1. 多队列共享一个 Redis 连接
connection决定的是 Redis 实例/数据库queue name决定的是 逻辑队列分组所以你可以在同一个 Redis 里,有多个逻辑队列:
defaultemailsnotifications
2. Worker 消费方式
(a) 消费所有队列
php artisan queue:work redis
- 不指定
--queue→ 默认会消费default队列 - 如果想消费多个队列,可以:
php artisan queue:work redis --queue=default,emails,notifications
- Worker 会按顺序轮询这些队列的任务
(b) 为特定队列指定 Supervisor
- 假设你只想消费
emails队列:
php artisan queue:work redis --queue=emails
- 然后配置 Supervisor 管理这个 worker:
[program:laravel-emails]
command=php /var/www/html/artisan queue:work redis --queue=emails --sleep=3 --tries=3
process_name=%(program_name)s_%(process_num)02d
numprocs=1
autostart=true
autorestart=true
- 这样就专门为
emails队列单独管理 worker,避免其他队列任务影响它的消费速度
3. 总结
| 概念 | 作用 |
|---|---|
| connection | Redis 实例/队列驱动,底层存储 |
| queue name | 逻辑队列分组,用于区分不同任务类型 |
| Worker 不指定队列 | 默认消费默认队列,也可通过 --queue=a,b,c 轮询多个队列 |
| Worker 指定队列 | 可以专门消费某个队列,用 Supervisor 单独管理性能/资源 |
简单理解:
- connection = Redis 后台仓库
- queue name = Redis 仓库里的不同抽屉
- Worker = 打开抽屉取任务的人
这样你就可以灵活地按业务拆分队列、单独管理资源。
关于redis的BLPOP
BLPOP 的名字就是 Blocking Left POP:
- L = Left 👉 从列表(list)的左边(头部)取数据
- POP 👉 取出并删除该元素
举例:
LPUSH myqueue "task1"
LPUSH myqueue "task2"
现在队列内容是(左边是头部):
["task2", "task1"]
执行:
BLPOP myqueue 0
返回:
1) "myqueue"
2) "task2"
队列变为:
["task1"]
再执行一次:
1) "myqueue"
2) "task1"
队列变空。
对比命令:
BLPOP→ 从左边取BRPOP→ 从右边取
queue:work 会启动一个常驻 PHP 进程 这个和php-fpm有关吗
1. php artisan queue:work
- 启动的是一个 常驻的 PHP CLI 进程。
- 它直接跑在命令行(
php二进制)里,不依赖 Nginx/Apache。 - 原理是一个死循环:不断用
BLPOP从 Redis 拉任务,然后执行 Job 的handle()。 - 你可以用
ps -ef | grep queue:work看到它就是一个普通的php进程。
2. php-fpm
php-fpm(FastCGI Process Manager)是 Web 请求的 PHP 进程池,通常由 Nginx/Apache 调用。- 用来处理 HTTP 请求(比如你访问一个 Laravel 页面,Nginx 转发给 php-fpm)。
php-fpm的进程是短生命周期的:一个请求 → 执行 → 返回 → 等待下一个请求。
3. 两者的区别
| 特性 | queue:work | php-fpm |
|---|---|---|
| 启动方式 | 命令行 php artisan queue:work | 系统服务(php-fpm) |
| 生命周期 | 常驻进程(死循环) | 短请求生命周期 |
| 触发来源 | Redis 队列任务 | Nginx/Apache 发来的 HTTP 请求 |
| 场景 | 异步任务消费(发邮件、导出、通知等) | Web 页面请求处理 |
4. 有没有关系?
- 运行环境:它们都用同一个 PHP 解释器(
php二进制 + PHP 扩展)。 - 进程独立:
queue:work跟php-fpm没有直接关系,它们互不干扰。 - 间接联系:Web 请求(php-fpm 执行)里
dispatch()了一个 Job → Job 被推到 Redis →queue:work消费并执行。
所以可以理解为:
- php-fpm 负责请求入口
- queue:work 负责后台异步任务 它们是配套而不是相互依赖的。
✅ 总结: queue:work 启动的常驻 PHP 进程 和 php-fpm 没关系,它就是一个独立的 PHP CLI worker。 唯一的联系是:Web 请求(php-fpm)可能会产生任务,交给 queue:work 去处理。
php cli 和 php fpm 在当前php docker环境下的对比
本质上这两个都是 php 的 SAPI(Server API)
$ php -v / -i
PHP 8.3.0alpha1 (cli) (built: Jun 13 2023 11:11:54) (NTS) Copyright (c) The PHP Group Zend Engine v4.3.0-dev, Copyright (c) Zend Technologies
完美 ✅
这个输出说明你的 PHP CLI 已经可用:
(cli)→ 告诉你这是 命令行接口(CLI)版本(NTS)→ 表示 非线程安全(Non Thread Safe)- 版本信息和 Zend Engine 都正常显示
结合你之前的 php-fpm -v 输出:
| 组件 | 状态 | 作用 |
|---|---|---|
| php-cli | 已安装 | 命令行运行 Artisan 命令、队列 Worker、Cron 等 |
| php-fpm | 已安装 | Web 请求处理(通过 Nginx/Apache) |
✅ 总结:
你的系统环境 完全可以:
- 运行
php artisan queue:work(依赖 CLI) - 处理 HTTP 请求(依赖 php-fpm)
- 运行
两者是独立的,互不影响,但可以配合使用:Web 请求产生任务 → CLI Worker 异步执行
$ php-fpm -v / -i
PHP 8.3.0alpha1 (fpm-fcgi) (built: Jun 13 2023 11:11:57) Copyright (c) The PHP Group Zend Engine v4.3.0-dev, Copyright (c) Zend Technologies
完美 ✅
你的输出说明了几件事:
PHP-FPM 已安装
(fpm-fcgi)明确标识这是 FastCGI Process Manager 版本- 可以被 Nginx/Apache 用来处理 Web 请求
PHP 版本
- 你安装的是 PHP 8.3.0 alpha1,属于开发版/测试版
- 注意生产环境最好用稳定版本
Zend Engine 版本
- 用于解释执行 PHP 代码的核心引擎信息
所以结论:
- php-fpm 可用 → 可以处理 Web 请求
- php-cli 依然独立,你的
php -v输出 CLI 信息的话,也能跑命令行脚本和queue:work
简单理解:
- php-fpm → Web 请求入口
- php-cli → 命令行脚本、队列 Worker、Cron
查看不同Server API 加载的模块
$ php -m
$ php-fpm -m
这两个命令输出的内容是一样的,为什么呢,按道理讲这是两个不同的SAPI环境,为什么会一样呢?
1. Docker 镜像里的 PHP 结构
- 大多数官方 PHP 镜像(如
php:8.3-fpm或php:8.3-cli)本身包含 相同的核心和扩展目录 - Docker 安装扩展(
docker-php-ext-install或pecl install)会把扩展统一安装到 PHP 的 extension_dir 下 - 无论你是 CLI 还是 FPM,都共享同一套扩展路径
2. SAPI 独立,但扩展共享
- SAPI:CLI 和 FPM 是不同的接口,行为不同
- 扩展:安装在同一目录 → 都能被加载
- 所以即便 FPM 和 CLI 各自有自己的
php.ini,只要都启用相同扩展,就会看到几乎一样的php -m/php-fpm -m
3. 实际效果
- CLI → 可以跑
artisan queue:work、Cron、命令行脚本 - FPM → 处理 Web 请求
- 都能用 Redis、PDO、curl 等扩展
- 开发时不必担心扩展缺失
✅ 总结:
- Docker 安装的 PHP 扩展默认对 CLI 和 FPM 都可用
- CLI / FPM 的行为和配置仍然独立,只是扩展共享
- 所以看到
php -m和php-fpm -m输出一致是正常现象
