首页
/ Coolify 队列运维深度指南:Laravel Horizon 的 Supervisor 配置、面板授权与指标排障实战

Coolify 队列运维深度指南:Laravel Horizon 的 Supervisor 配置、面板授权与指标排障实战

2026-09-03 17:25:25作者:秋泉律Samson

本篇围绕 Coolify 仓库中的 Horizon 配置技能文档(.agents/skills/configuring-horizon/SKILL.md)及其配套参考资料展开,系统讲解 Laravel Horizon 的完整生命周期:从安装(horizon:install)、config/horizon.php 的 supervisor 与 environments 合并规则、负载均衡策略,到仪表盘授权(viewHorizon Gate)、指标快照调度(horizon:snapshot)、LongWaitDetected 告警阈值(waits)、任务标签与静默机制,并结合 Coolify 仓库中真实的 config/horizon.phpHorizonServiceProvider 源码印证每一步的实际落地方式。读完后,你应当能够在任何使用 Redis 队列的 Laravel 项目中正确配置 Horizon 自动扩缩容、打通指标面板,并快速定位生产环境的常见故障。

需要强调的前提:Horizon 仅支持 Redis 队列驱动databaseSQS 等其他驱动均不受支持;同时 Horizon 不支持 Redis Cluster,它要求一个独立的 Redis 连接。这两条是全部配置讨论的边界条件。

安装与入口

安装只需一条命令:

php artisan horizon:install

该命令会生成 config/horizon.php 配置文件。运行 php artisan horizon 启动 Horizon 主进程,仪表盘则部署在应用的 horizon 路径下(路径可通过配置中的 path 项修改)。Coolify 的 config/horizon.php 中即有如下定义:

'domain' => env('HORIZON_DOMAIN'),
'path' => env('HORIZON_PATH', 'horizon'),

其中 domain 允许把 Horizon 挂到独立子域名;若为 null,则与应用共用同一域名。

启动后请完成三个基本验证:

  1. 运行 php artisan horizon 并访问 /horizon
  2. 确认仪表盘访问权限按预期受限(未授权用户被拒绝);
  3. 确认调度了 horizon:snapshot 之后指标能够填充——仅运行 php artisan horizon 本身不会生成任何指标数据

Supervisor 配置:defaults 与 environments 的合并规则

Supervisor 是 Horizon 的工作进程管理单元,定义在 config/horizon.phpdefaults 数组中。技能文档给出的典型示例如下:

'defaults' => [
    'supervisor-1' => [
        'connection' => 'redis',
        'queue' => ['default'],
        'balance' => 'auto',
        'minProcesses' => 1,
        'maxProcesses' => 10,
        'tries' => 3,
    ],
],

'environments' => [
    'production' => [
        'supervisor-1' => ['maxProcesses' => 20, 'balanceCooldown' => 3],
    ],
    'local' => [
        'supervisor-1' => ['maxProcesses' => 2],
    ],
],

这里最关键、也最容易被误解的一条规则是:environments 数组是按 key 合并(merge)进 defaults 的,而不是整体替换 supervisor 块。也就是说,环境块中只需写要覆盖的 key,其余配置自动继承 defaults。这是 supervisors 参考文档中明确指出的重点,常见写法是:在 defaults 中定义 connectionqueuebalanceautoScalingStrategytriestimeout 等完整基线,然后在 production 中仅覆盖 maxProcessesbalanceMaxShiftbalanceCooldown

Coolify 的真实配置:一个生产级 supervisor 样本

Coolify 的 config/horizon.php 提供了一个信息量很大的生产级配置,值得逐项解读:

'defaults' => [
    's6' => [
        'connection' => 'redis',
        'balance' => env('HORIZON_BALANCE', 'false'),
        'queue' => env('HORIZON_QUEUES', 'high,default'),
        'maxTime' => env('HORIZON_MAX_TIME', 0),
        'maxJobs' => 400,
        'memory' => 128,
        'tries' => 1,
        'nice' => 0,
        'sleep' => 3,
        'timeout' => min(
            max((int) env('HORIZON_TIMEOUT', 39600), ScheduledVolumeBackup::DEFAULT_TIMEOUT + 600),
            85800,
        ),
    ],
],

'environments' => [
    'production' => [
        's6' => [
            'autoScalingStrategy' => 'size',
            'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
            'maxProcesses' => env('HORIZON_MAX_PROCESSES', 4),
            'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
            'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
        ],
    ],
    // local 与 production 的 supervisor 覆盖项相同
],

几个值得注意的实现细节:

  • timeout 的下限保护:Coolify 用 max() 保证 worker 超时不低于 ScheduledVolumeBackup::DEFAULT_TIMEOUT + 600,因为该项目的卷备份任务是最长耗时的队列作业,worker 超时若小于作业自身超时,会导致作业被 worker 提前杀掉。再用 min(..., 85800) 封顶,避免环境变量误配置出极端值。这是"timeout 链条"(后文详述)在真实项目中的防御式写法。
  • 扩缩容参数全部走环境变量HORIZON_MIN_PROCESSES / HORIZON_MAX_PROCESSES 默认 1~4,balanceMaxShift 限制每个周期最多增减 1 个进程,balanceCooldown 设为 1 秒,配合 autoScalingStrategy => 'size'(按队列中等待作业数量决定进程数)实现温和的自动扩缩容。
  • balance 默认为 false:Coolify 默认关闭负载均衡,由 HORIZON_BALANCE 环境变量显式开启。

队列优先级:单个 supervisor + balance: auto 不保证顺序

Supervisors 参考文档指出两个实操要点:

  1. queue 数组的顺序对负载均衡是无效的。当你给单个 supervisor 配置 balance: auto 并传入多个队列时,Horizon 不会按数组顺序处理队列。若业务上要求 notifications 先于 default 被消费,正确做法是拆成两个命名 supervisor:高优先级队列的 supervisor 设置更高的 maxProcesses,低优先级队列设置更低的上限。
  2. 需要恒定 N 个 worker 的队列请用 balance: false + maxProcesses: N。例如一个视频转码队列限制为 2 个并发,自动均衡会在流量突发时把进程数往上抬,这恰恰是这类资源受限队列不想要的。若使用 balance: auto,建议同时设置 balanceCooldown(参考文档建议 3~5 秒)来平滑突发负载下的频繁扩缩。

仪表盘授权:viewHorizon Gate

Horizon 仪表盘默认受 viewHorizon Gate 保护,自定义授权逻辑写在 App\Providers\HorizonServiceProvidergate() 方法中。技能文档给出的通用范式:

protected function gate(): void
{
    Gate::define('viewHorizon', function (User $user) {
        return $user->is_admin;
    });
}

Coolify 的实际实现(见 HorizonServiceProvider)则展示了另一种按白名单放行的模式,并与 config/horizon.php 中的 allowed_emails 配置联动:

protected function gate(): void
{
    Gate::define('viewHorizon', function (User $user) {
        if ($user->id === 0) {
            return true;
        }

        return str(config()->string('horizon.allowed_emails'))
            ->lower()
            ->explode(',')
            ->map(fn (string $email) => trim($email))
            ->contains($user->email);
    });
}

allowed_emailsconfig/horizon.php 中定义为逗号分隔的字符串:

'allowed_emails' => env('HORIZON_ALLOWED_EMAILS', ''),

即:id 为 0 的 root 用户始终可访问,其余用户必须命中 HORIZON_ALLOWED_EMAILS 环境变量中的邮箱白名单(比较时统一小写并 trim)。修改该环境变量即可调整可见仪表盘的账号集合。

此外,Coolify 在同一 Provider 中做了两件与 Horizon 数据流相关的定制,体现了技能文档之外源码级的真实用法:

  • 通过 Event::listen(JobReserved) 监听作业被领取事件:当被领取的是 App\Jobs\ApplicationDeploymentJob 时,从 payload 的 tags 中解析出 App\Models\ApplicationDeploymentQueue:{id} 形式的标签,把 horizon_job_id 回写到部署队列表。这使得部署记录与 Horizon 中的具体作业一一对应,运维脚本(如 bootstrap/helpers/shared.php 中按 horizon_job_id 查询作业状态的逻辑)可以据此向 Horizon 实时拉取每个部署的进度。
  • register() 中将 JobRepository 单例绑定为自定义的 CustomJobRepository(继承自 RedisJobRepository 并实现 CustomJobRepositoryInterface),替换 Horizon 默认的 Redis 作业仓库实现。

指标面板与快照调度:空白指标的第一嫌疑

指标仪表盘空白是 Horizon 最常见的"伪故障"。参考文档 metrics.md 给出了明确结论:

  • 运行 horizon 命令本身不会填充指标。指标图完全由快照构建,必须在 Laravel 调度器中注册 horizon:snapshot 每 5 分钟执行一次。
  • 手动跑一次 snapshot 只能临时点亮面板,面板不会保持更新;必须注册到调度器(注意 Laravel 10 与 11+ 的注册语法有差异,以所用版本的队列文档为准)。
  • metrics.trim_snapshots 的取值是"快照个数",不是时间。Coolify 的 config/horizon.php 中:
'metrics' => [
    'trim_snapshots' => [
        'job' => 24,
        'queue' => 24,
    ],
],

按每 5 分钟一次快照计算,保留 24 个即保留 2 小时的历史指标。调大该值可以换取更长的历史窗口,代价是 Redis 内存占用上升。

超时链条:timeout、supervisor timeout 与 retry_after 的排序

技能文档"Common Pitfalls"中一条极易踩中的规则是超时链条必须严格有序

作业 timeout < supervisor timeout < retry_after

顺序颠倒的后果是:作业还没被 Horizon 判定超时,retry_after 就先到期把作业视为"失联"重新投递,造成同一作业被并发重复执行。Coolify 的 supervisor timeout 配置(上文 max(..., 85800) 封顶)正是在保证链条中 supervisor 层足够宽裕,让 ScheduledVolumeBackup 这类长作业有机会在作业自身超时之前完成。修改任何一层超时时,都应把整条链条放在一起核对。

告警:waits 阈值与通知路由

config/horizon.php 中的 waits 数组控制 LongWaitDetected 事件的触发阈值——即作业在队列中等待多少秒后告警:

'waits' => [
    'redis:default' => 60,
],

notifications 参考文档 的说法:

  • 每个"连接/队列"组合都可以有独立阈值(键形如 redis:default),Coolify 的默认值为 60 秒;
  • 告警触发太频繁或太迟时,应调整 waits,而不是去改通知路由配置
  • 通知路由应使用 Horizon 内置 API,在 App\Providers\HorizonServiceProvider::boot() 中调用 Horizon::routeMailNotificationsTo()Horizon::routeSlackNotificationsTo()Horizon::routeSmsNotificationsTo()。Horizon 已把 LongWaitDetected 事件接到其通知发送器,无需手动注册监听器;
  • 失败作业(failed job)的告警不属于 Horizon 文档化的通知路由范畴,应视为自定义的队列事件处理,参考队列文档而非 Horizon 的 routing API。

标签、静默与数据保留

自动标签

若作业的构造函数接收 Eloquent 模型实例,Horizon 会自动为作业打上 ModelClass:id 形式的标签(如 App\Models\User:42),无需在作业类中写任何代码,且这些标签在仪表盘里可直接用于筛选。只有需要超出自动标签的自定义标签时,才实现 tags() 方法。Coolify 前文提到的 ApplicationDeploymentQueue:{id} 标签即利用了标签机制做部署追溯。

静默(silenced)

silenced 参考文档 澄清了两个概念的边界:

  • 把作业类加入 config/horizon.phpsilenced 数组,只是把它从仪表盘"已完成作业"视图中隐藏,作业本身照常运行。它是降噪工具,不是禁用手段:
'silenced' => [
    // App\Jobs\ExampleJob::class,
],
  • silenced_tags 则按标签隐藏:携带匹配标签字符串的作业全部不出现在已完成列表中,适合静默一整类作业(如所有打了 notifications 标签的作业),而不是逐个屏蔽类。

作业记录裁剪(trim)

trim 配置控制各类作业记录在 Redis 中的保留时长(单位:分钟)。Coolify 的配置:

'trim' => [
    'recent' => 60,
    'pending' => 60,
    'completed' => 60,
    'recent_failed' => 10080,
    'failed' => 10080,
    'monitored' => 10080,
],

即近期/待处理/已完成作业保留 1 小时,失败与受监控作业保留 10080 分钟(7 天)。调整时应注意:失败作业保留越久,事故回溯窗口越大,但 Redis 占用也越高。

生产运维细节:Redis 连接、前缀与快速终止

config/horizon.php 还有几项与部署运维直接相关的配置,Coolify 中均保留默认值:

'use' => 'default',          // Horizon 元数据使用的 Redis 连接
'prefix' => env('HORIZON_PREFIX',
    Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:'),
'fast_termination' => false,
'memory_limit' => 64,        // master supervisor 的内存上限(MB)
  • useprefix:Horizon 把 supervisor 列表、失败作业、指标等元数据都存在这个 Redis 连接下,并以 prefix 为键前缀。同一 Redis 上跑多套 Horizon 实例时,务必用 HORIZON_PREFIX 区分前缀,避免元数据互相污染。
  • fast_termination:开启后 horizon:terminate 命令不会等待所有 worker 退出,允许新实例在旧实例收尾期间启动,从而缩短部署停机时间;不想要这种"交叠"语义时保持 false 即可。
  • memory_limit:限制 Horizon master supervisor 本身可占用的内存(worker 的内存限制由 supervisor 的 memory 项控制,Coolify 设为 128 MB,超过即重启 worker)。

验证清单与常见陷阱汇总

完成配置后,按技能文档的验证清单逐项确认:

  1. php artisan horizon 能启动,/horizon 可访问;
  2. 未授权账号访问仪表盘被拒(viewHorizon Gate 生效);
  3. 调度 horizon:snapshot 后指标正常填充。

最后汇总技能文档的常见陷阱清单,作为交付前的自检项:

  • Horizon 只支持 Redis 队列驱动databaseSQS 等驱动不受支持;
  • 不支持 Redis Cluster,需要独立 Redis 连接;
  • 改动前先读一遍 config/horizon.php,弄清当前 supervisor 与环境配置再下手;
  • environments 只做按键合并覆盖,不会整体替换 defaults
  • 超时链条排序:job timeout < supervisor timeout < retry_after,顺序错误会导致作业在 Horizon 判定超时前就被重试;
  • 指标空白直到 horizon:snapshot 被调度——单独运行 php artisan horizon 不会生成指标。

延伸阅读:技能包内的参考资料

本仓库在 .agents/skills/configuring-horizon/ 下还维护了四个专题参考文档,可作为 Horizon 相关排查时的分主题入口:

  • supervisors.md:supervisor 块、balance 的 auto/simple/false 模式、多队列与自动扩缩容;
  • notifications.mdLongWaitDetected 告警、通知路由与 waits 配置;
  • tags.md:作业标签、仪表盘筛选与噪音作业静默;
  • metrics.md:空白指标面板、快照调度与保留期配置。

这些文档与 config/horizon.phpapp/Providers/HorizonServiceProvider.php 中的真实实现相互印证,是理解 Coolify 队列体系(尤其是部署作业经 Horizon 领取、跟踪与失败清理的完整链路)最直接的一手材料。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384