工作进程
工作进程是长期运行的进程,它们会轮询数据库以查找待处理的作业、执行作业并推进工作流。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() 方法接受字符串或数组:
// 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),不会因其他队列而得不到处理:
[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
由单个工作进程按优先级顺序处理,管理起来更简单,适用于低优先级队列能够容忍延迟的情况:
[program:queuety-worker]
command=wp queuety work --queue=critical,default,low
numprocs=2
你也可以结合这两种方式:为 critical 配置专用工作进程以确保吞吐量,并让另一个按优先级顺序运行的工作进程处理其他所有队列:
[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 就会推迟该作业,而不是启动可能无法顺利完成的工作。
你可以在工作定义中选择启用该策略,而不是通过工作进程命令启用:
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
[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
[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 计数。