指尖上的记忆指尖上的记忆
首页
  • 基础
  • Laravel框架
  • Symfony框架
  • 基础
  • Gin框架
  • 基础
  • Spring框架
  • 命令
  • Nginx
  • Ai
  • Deploy
  • Docker
  • K8s
  • Micro
  • RabbitMQ
  • Mysql
  • PostgreSsql
  • Redis
  • MongoDb
  • Html
  • Js
  • 前端
  • 后端
  • Git
  • 知识扫盲
  • Golang
🌟 gitHub
首页
  • 基础
  • Laravel框架
  • Symfony框架
  • 基础
  • Gin框架
  • 基础
  • Spring框架
  • 命令
  • Nginx
  • Ai
  • Deploy
  • Docker
  • K8s
  • Micro
  • RabbitMQ
  • Mysql
  • PostgreSsql
  • Redis
  • MongoDb
  • Html
  • Js
  • 前端
  • 后端
  • Git
  • 知识扫盲
  • Golang
🌟 gitHub
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 进程,内部大致流程是:

  1. 进入循环 → 不断向 Redis 里 BLPOP(阻塞式 pop) 队列
    • 默认队列 key 格式:queues:default
    • 如果指定 --queue=emails → key 就是 queues:emails
  2. 拿到队列消息(就是被 dispatch() 序列化过的 Job 数据)
  3. 反序列化 Job → 调用 Job 的 handle() 方法
  4. 如果失败 → 重新入队(延迟),或者进入 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 = falseJob 立即入队,不依赖事务提交
after_commit = trueJob 只有在事务提交成功后入队
->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()
    {
        // 任务逻辑
    }
}
  • Queueable trait 提供了 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 确实同时扮演了 两个角色:

  1. 消费者(Consumer)
  • 从主队列(ready queue)取任务执行
  • 核心行为:
    • 监听队列(BLPOP 或轮询)
    • 拿到任务 → 调用 Job 的 handle() 执行
  • 类似“真正的工人”,负责处理业务逻辑
  1. 延迟任务搬运者(Delayed Job Scheduler)
  • 周期性检查 Redis delayed 队列
  • 核心行为:
    • 取出到达 available_at 时间的 Job
    • 把它移动到主队列(ready queue)
  • 类似“调度员”,保证延迟任务在到期时进入可执行队列
  1. 图示理解(逻辑流程)
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 或指定队列,灵活管理消费策略

✅ 总结:

  1. Job 类要 implements ShouldQueue
  2. Dispatch 时调用 ->delay(now()->addMinutes(...))
  3. 可选 onQueue() 指定队列
  4. Worker 会自动处理延迟逻辑

参考文档: https://laravel.com/docs/12.x/queues#delayed-dispatching


关于 多个不同的queue name 的使用以及消费
1. 多队列共享一个 Redis 连接
  • connection 决定的是 Redis 实例/数据库

  • queue name 决定的是 逻辑队列分组

  • 所以你可以在同一个 Redis 里,有多个逻辑队列:

    • default
    • emails
    • notifications
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. 总结
概念作用
connectionRedis 实例/队列驱动,底层存储
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:workphp-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 已经可用:

  1. (cli) → 告诉你这是 命令行接口(CLI)版本
  2. (NTS) → 表示 非线程安全(Non Thread Safe)
  3. 版本信息和 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

完美 ✅

你的输出说明了几件事:

  1. PHP-FPM 已安装

    • (fpm-fcgi) 明确标识这是 FastCGI Process Manager 版本
    • 可以被 Nginx/Apache 用来处理 Web 请求
  2. PHP 版本

    • 你安装的是 PHP 8.3.0 alpha1,属于开发版/测试版
    • 注意生产环境最好用稳定版本
  3. 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 输出一致是正常现象