跳转到正文

工作进程

工作进程是长期运行的进程,它们会轮询数据库以查找待处理的作业、执行作业并推进工作流。wp queuety work 命令在 WP-CLI 中运行,而队列本身则通过可用的最佳 Queuety DB 驱动直接从 MySQL 领取。

该插件还会安装一个默认的每分钟运行一次的 WordPress cron 后备方案,供无法访问 shell 的环境使用。本页介绍专用的 WP-CLI 工作进程,这是吞吐量更高且推荐的部署模式。

启动工作进程

命令行
wp queuety work

这会在 default 队列上启动一个工作进程。它会循环运行:领取作业、执行作业,然后重复。当队列为空时,它会休眠 QUEUETY_WORKER_SLEEP 秒(默认值:1),然后再次轮询。

选项

选项 说明
--queue=<queue> 要处理的 Queue(s),用逗号分隔以指定优先顺序(默认值:default)
--once 最多处理一个作业后退出
--workers=<n> 派生 N 个工作进程(需要 pcntl 和 posix 扩展)
--min-workers=<n> 自适应池的最小工作进程数。需要 --max-workers
--max-workers=<n> 自适应池的最大工作进程数
命令行
# Process a specific queue
wp queuety work --queue=emails

# Process at most one job and exit
wp queuety work --once

# Run 4 concurrent workers
wp queuety work --workers=4

# Scale between 2 and 6 workers as backlog changes
wp queuety work --queue=providers --min-workers=2 --max-workers=6

多队列优先级

单个工作进程可以按优先级顺序处理多个队列。向 --queue 传入以逗号分隔的队列名称列表:

命令行
wp queuety work --queue=critical,default,low

工作进程会按列出的顺序尝试从每个队列领取作业。它会先检查 critical。如果 critical 中有作业,就会处理该作业。如果 critical 为空,则会继续检查 default,然后检查 low。这意味着高优先级队列总会先于低优先级队列清空。

优先级顺序的工作方式

每次迭代时,工作进程都会尝试从列表中的第一个队列领取作业。如果该队列为空或已暂停,它就会继续检查下一个队列,并依次检查,直到找到作业或检查完列表。如果所有队列都没有作业,工作进程就会休眠后重试。这是一种严格的优先级模式,因此持续不断的 critical 作业流会让 default 和 low 持续得不到处理。如果低优先级队列需要确保吞吐量,请改用专用工作进程。

在 PHP 中使用多队列

Worker::run() 和 Worker::flush() 方法接受字符串或数组:

PHP
// Comma-separated string
Queuety::worker()->run( 'critical,default,low' );

// Array
Queuety::worker()->run( [ 'critical', 'default', 'low' ] );

// flush() also supports multi-queue
$count = Queuety::worker()->flush( 'critical,default,low' );

部署策略

为每个队列配置专用工作进程,可以让你完全控制并发和隔离。每个队列都有自己的工作进程 process(es),不会因其他队列而得不到处理:

Ini
[program:queuety-critical]
command=wp queuety work --queue=critical
numprocs=4

[program:queuety-default]
command=wp queuety work --queue=default
numprocs=2

[program:queuety-low]
command=wp queuety work --queue=low
numprocs=1

由单个工作进程按优先级顺序处理,管理起来更简单,适用于低优先级队列能够容忍延迟的情况:

Ini
[program:queuety-worker]
command=wp queuety work --queue=critical,default,low
numprocs=2

你也可以结合这两种方式:为 critical 配置专用工作进程以确保吞吐量,并让另一个按优先级顺序运行的工作进程处理其他所有队列:

Ini
[program:queuety-critical]
command=wp queuety work --queue=critical
numprocs=2

[program:queuety-fallback]
command=wp queuety work --queue=default,low
numprocs=2

基于资源的准入控制

工作进程的工作不只是领取下一条待处理记录。在已领取的作业开始之前,Queuety 可以根据作业的并发组和对应处理程序近期的执行情况,执行粗略的准入检查。

关于更广泛的模型,包括它如何与作业元数据和工作流预算协同,请参阅 资源管理。

并发组为多个处理程序或作业类别设定共享上限。如果你为多个高开销提供方指定相同的组和上限,达到组上限后,工作进程就会释放新领取的作业,而不是开始处理。这样即使有多个空闲工作进程,彼此独立的队列也不会同时涌向同一个下游系统。

近期执行日志也会用于生成简单的准入配置文件。启用资源准入控制后,Queuety 会查看即将运行的处理程序近期的平均时长和峰值内存。如果当前工作进程已经接近其配置的内存上限、某个队列或提供方组已达到加权预算、当前容器的剩余内存似乎不足以满足该处理程序的已观察配置文件,或者 --once 式运行即将达到时间限制,Queuety 就会推迟该作业,而不是启动可能无法顺利完成的工作。

你可以在工作定义中选择启用该策略,而不是通过工作进程命令启用:

PHP
class CallProviderStep implements \Queuety\Step {
    public function handle( array $state ): array {
        // ...
    }

    public function config(): array {
        return [
            'concurrency_group' => 'providers',
            'concurrency_limit' => 3,
            'cost_units' => 5,
        ];
    }
}

相同的元数据也可以通过公共属性附加到可分派的作业类,或通过 PendingJob::concurrency_group() 和 PendingJob::cost_units() 附加到单个分派任务。

工作进程侧的准入配置文件由以下常量控制:

常量 说明
QUEUETY_RESOURCE_ADMISSION 启用或禁用基于配置文件的准入检查
QUEUETY_RESOURCE_PROFILE_WINDOW_MINUTES 构建近期处理程序配置文件时回溯的时间范围
QUEUETY_RESOURCE_PROFILE_TTL 近期处理程序配置文件的缓存 TTL
QUEUETY_RESOURCE_MEMORY_HEADROOM_MB 准入另一个作业前预留的内存余量
QUEUETY_RESOURCE_SYSTEM_MEMORY_AWARENESS 启用或禁用尽力而为的容器或主机内存检查
QUEUETY_RESOURCE_SYSTEM_MEMORY_HEADROOM_MB 准入另一个作业前预留的系统内存余量
QUEUETY_RESOURCE_QUEUE_COST_BUDGETS 按队列名称索引的加权成本预算
QUEUETY_RESOURCE_GROUP_COST_BUDGETS 按 concurrency_group 索引的加权成本预算
QUEUETY_RESOURCE_TIME_HEADROOM_MS 单次运行工作进程预留的时间余量

工作进程生命周期

满足以下任一条件时,工作进程会在完成当前作业后优雅停止:

条件 配置
已处理的作业数达到上限 QUEUETY_WORKER_MAX_JOBS(默认值:1000)
超出内存上限 QUEUETY_WORKER_MAX_MEMORY(默认值:128 MB)

停止后,工作进程会正常退出。在生产环境中,进程管理器会自动重启它。

目前尚未对信号进行优雅处理(#4)。单个 wp queuety work 进程不会安装信号处理程序,因此 SIGTERM 或 SIGINT(Ctrl+C)会立即终止进程,即使作业正在执行也不例外。该作业会一直处于 processing 状态,直到过了 QUEUETY_STALE_TIMEOUT 秒后,过期作业恢复机制将其重置,因此请确保处理程序可以安全重试。工作进程池会将 SIGTERM 转发给子进程,但子进程不会处理它,因此池会继续运行,直到它们因上述某个原因停止。需要停止工作进程池时,请让进程管理器在停止超时后发送 SIGKILL。

多工作进程并发

--workers=N 标志会使用 pcntl_fork() 派生 N 个子进程。每个子进程都会创建自己的数据库连接并独立处理作业。父进程会监控子进程,并重启任何崩溃的进程。

命令行
wp queuety work --queue=default --workers=4

要求: 必须安装 pcntl 和 posix PHP 扩展(如果没有 pcntl,命令会失败并显示“pcntl extension is required for --workers=N.”),而且每个子进程都使用自己的数据库连接,因此你的 MySQL max_connections 设置必须能满足整个进程池的需求。自适应池(--min-workers/--max-workers)也有相同的要求。

自适应工作进程池

自适应池会保持一定数量的工作进程处于就绪状态,然后在配置的范围内扩容或缩容:

命令行
wp queuety work --queue=providers --min-workers=2 --max-workers=6

父进程会监控所选队列集合中可领取的积压作业,并根据积压情况调整子进程数量,但不会超过配置的最大值。队列安静下来后,进程池会等待空闲宽限时间再缩容,以免短暂的突发流量导致进程列表立即频繁变动。

扩容仍然采取保守策略。当 Queuety 能读取容器或主机内存遥测数据时,如果剩余内存不足以容纳另一个完整的工作进程资源配置,就不会增加子进程。

父进程侧的扩缩容循环由以下设置控制:

常量 说明
QUEUETY_WORKER_POOL_SCALE_INTERVAL 父进程调整进程池大小的频率
QUEUETY_WORKER_POOL_IDLE_GRACE 积压必须持续安静多久才会缩容

过期作业恢复

如果工作进程在处理作业时崩溃,该作业会留在 processing 状态。长期运行的工作进程大约每分钟检查一次,查找处于 processing 状态超过 QUEUETY_STALE_TIMEOUT 秒的作业。仍有尝试次数的过期作业会在退避延迟后重试;已用尽尝试次数的作业会被搁置,其工作流也会标记为失败。--once 工作进程和 WP-Cron 后备方案不会运行此项检查。

手动恢复过期作业:

命令行
wp queuety recover

清空模式

处理所有待处理作业后退出。适用于测试或一次性批处理:

命令行
wp queuety flush
wp queuety flush --queue=emails

生产环境部署

在生产环境中,使用进程管理器保持工作进程持续运行。以下是两种常见做法。

Supervisord

Ini
[program:queuety-default]
command=wp queuety work --queue=default
directory=/var/www/html
autostart=true
autorestart=true
numprocs=2
process_name=%(program_name)s_%(process_num)02d
stdout_logfile=/var/log/queuety-default.log
stderr_logfile=/var/log/queuety-default-error.log

[program:queuety-emails]
command=wp queuety work --queue=emails
directory=/var/www/html
autostart=true
autorestart=true
numprocs=1
stdout_logfile=/var/log/queuety-emails.log
stderr_logfile=/var/log/queuety-emails-error.log

Systemd

Ini
[Unit]
Description=Queuety Worker (default queue)
After=mysql.service

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/html
ExecStart=/usr/local/bin/wp queuety work --queue=default
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

启用并启动:

命令行
sudo systemctl enable queuety-worker
sudo systemctl start queuety-worker

部署提示

如果需要更强的隔离,请为不同队列运行不同的工作进程。工作负载受 CPU 限制时使用 --workers=N;如果工作负载主要受 I/O 限制,则优先使用单独受监控的工作进程。如果希望定期重启以释放内存,请设置 QUEUETY_WORKER_MAX_JOBS,并通过 网络钩子 或 指标 留意 buried 计数。