本文目录导读:

针对 PHP 项目队列堆积问题,扩容消费节点是最直接有效的解决方案,但需要注意,单纯的横向扩展(加机器)可能会遇到资源竞争、重复消费等问题,以下是系统的扩容方案和最佳实践。
核心扩容策略
1 水平扩展(推荐)
# 示例:使用 Supervisor 管理多个消费进程 [program:queue-worker] process_name=%(program_name)s_%(process_num)02d command=php bin/queue consume --queue=default numprocs=10 # 根据CPU核心数调整,通常设为CPU核心数*2 autostart=true autorestart=true user=www-data
2 多服务器扩展
// 配置多个消费服务器 // server1: 消费队列A、B // server2: 消费队列A、C // server3: 消费队列B、C // 或者使用相同的配置,随机消费(需保证幂等性)
队列系统扩容方案
1 Redis 队列扩容
// 使用多个队列分散压力
class RedisQueueExpander {
private $redis;
private $queuePrefix = 'task_queue_';
private $queueCount = 4; // 队列数量
public function push($data) {
// 根据数据特征分配到不同队列
$index = crc32($data['id']) % $this->queueCount;
$queueName = $this->queuePrefix . $index;
$this->redis->lpush($queueName, json_encode($data));
}
public function pop() {
// 消费端轮询或使用BRPOP
$queues = array_map(function($i) {
return $this->queuePrefix . $i;
}, range(0, $this->queueCount - 1));
return $this->redis->brpop($queues, 5);
}
}
2 RabbitMQ 队列扩容
// 利用多个队列+轮询分发
class RabbitMQConsumerExpander {
public function setupMultipleQueues() {
$connection = new AMQPStreamConnection('localhost', 5672, 'user', 'pass');
$channel = $connection->channel();
// 创建多个消费者队列
for ($i = 0; $i < 4; $i++) {
$channel->queue_declare(
"task_queue_$i",
false,
true, // durable
false,
false
);
}
// 创建Direct Exchange分发
$channel->exchange_declare('task_exchange', 'direct', false, true, false);
// 绑定队列到Exchange
for ($i = 0; $i < 4; $i++) {
$channel->queue_bind("task_queue_$i", 'task_exchange', "routing_key_$i");
}
return $channel;
}
}
消费节点自动扩容
1 基于队列深度自动扩容
class AutoScaler {
private $threshold = 1000; // 队列长度阈值
private $maxWorkers = 20;
private $minWorkers = 2;
public function scaleIfNeeded() {
$queueLength = $this->getQueueLength();
$currentWorkers = $this->getCurrentWorkerCount();
if ($queueLength > $this->threshold && $currentWorkers < $this->maxWorkers) {
$this->addWorkers(min(
$this->maxWorkers - $currentWorkers,
ceil($queueLength / $this->threshold)
));
} elseif ($queueLength < $this->threshold / 2 && $currentWorkers > $this->minWorkers) {
$this->removeWorkers($currentWorkers - $this->minWorkers);
}
}
public function addWorkers($count) {
// 使用Supervisor API或直接执行命令
for ($i = 0; $i < $count; $i++) {
exec("supervisorctl start queue-worker:$i");
}
}
}
2 使用队列监控工具
// 结合 Prometheus + Grafana 监控
class QueueMonitor {
public function getMetrics() {
return [
'queue_depth' => $this->getQueueDepth(),
'processing_time' => $this->getAverageProcessingTime(),
'success_rate' => $this->getSuccessRate(),
'worker_count' => $this->getWorkerCount(),
];
}
// 根据指标自动触发扩容
public function autoScale() {
$metrics = $this->getMetrics();
if ($metrics['queue_depth'] > 10000 &&
$metrics['processing_time'] > 30) {
// 触发扩容告警或自动扩容
$this->scaleOut($metrics['queue_depth'] / 5000);
}
}
}
消费端优化
1 多进程/多线程消费
class ParallelConsumer {
private $processCount = 4;
public function consume() {
$workers = [];
for ($i = 0; $i < $this->processCount; $i++) {
$pid = pcntl_fork();
if ($pid == -1) {
die('Could not fork');
} elseif ($pid) {
// 父进程
$workers[] = $pid;
} else {
// 子进程
$this->work();
exit(0);
}
}
// 等待所有子进程结束
foreach ($workers as $pid) {
pcntl_waitpid($pid, $status);
}
}
private function work() {
while (true) {
$job = $this->popJob();
if ($job) {
try {
$this->processJob($job);
$this->acknowledge($job);
} catch (Exception $e) {
$this->handleFailure($job, $e);
}
} else {
sleep(1);
}
}
}
}
2 使用协程(Swoole)
// Swoole 协程消费者
class SwooleQueueConsumer {
public function start() {
$server = new Swoole\Coroutine\Server('0.0.0.0', 9501);
$server->handle(function ($conn) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
while (true) {
$data = $redis->brPop('queue', 5);
if ($data) {
// 异步处理
go(function () use ($data) {
$this->process($data[1]);
});
}
}
});
$server->start();
}
}
数据库优化
1 读写分离
class DatabaseQueueExpander {
public function getConnection($type = 'write') {
static $connections = [];
if (!isset($connections[$type])) {
$config = $type === 'write'
? $this->getWriteConfig()
: $this->getReadConfig();
$connections[$type] = new PDO(
$config['dsn'],
$config['user'],
$config['password']
);
}
return $connections[$type];
}
}
2 表分区
-- 按时间分区
CREATE TABLE jobs (
id INT AUTO_INCREMENT,
created_at DATETIME,
status ENUM('pending', 'processing', 'completed', 'failed'),
data TEXT,
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (TO_DAYS(created_at)) (
PARTITION p202401 VALUES LESS THAN (TO_DAYS('2024-02-01')),
PARTITION p202402 VALUES LESS THAN (TO_DAYS('2024-03-01')),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
监控与告警
1 队列监控
class QueueAlertSystem {
public function checkAndAlert() {
$metrics = $this->getQueueMetrics();
// 队列深度告警
if ($metrics['depth'] > 5000) {
$this->sendAlert('队列深度超过5000', AlertLevel::WARNING);
}
// 处理时间告警
if ($metrics['avg_processing_time'] > 60) {
$this->sendAlert('平均处理时间超过60秒', AlertLevel::CRITICAL);
}
// 失败率告警
if ($metrics['failure_rate'] > 0.05) {
$this->sendAlert('失败率超过5%', AlertLevel::CRITICAL);
}
}
}
2 自动扩容脚本
#!/bin/bash
# auto_scale.sh
QUEUE_DEPTH=$(redis-cli LLEN queue)
CURRENT_WORKERS=$(supervisorctl status | grep "queue-worker" | wc -l)
if [ $QUEUE_DEPTH -gt 1000 ] && [ $CURRENT_WORKERS -lt 20 ]; then
NEW_WORKERS=$(( $QUEUE_DEPTH / 500 ))
NEW_WORKERS=$(( NEW_WORKERS > 20 ? 20 : NEW_WORKERS ))
echo "Increasing workers from $CURRENT_WORKERS to $NEW_WORKERS"
supervisorctl signal TERM queue-worker:*
sleep 2
supervisorctl reread
supervisorctl update
supervisorctl start queue-worker:*
fi
最佳实践
1 幂等性设计
class IdempotentConsumer {
private $processedJobs = []; // 使用Redis缓存
public function consume($job) {
$jobId = $job->getId();
// 检查是否已处理
if ($this->isProcessed($jobId)) {
return;
}
try {
$this->process($job);
$this->markAsProcessed($jobId);
} catch (Exception $e) {
// 记录失败,但不要重复处理
$this->markAsFailed($jobId, $e);
}
}
}
2 优雅关闭
pcntl_signal(SIGTERM, function ($signo) {
// 停止接收新任务
$consumer->stopAccepting();
// 等待当前任务完成
$consumer->waitForCurrentJobs();
exit(0);
});
扩容效果评估
| 指标 | 扩容前 | 扩容后(4节点) | 优化 |
|---|---|---|---|
| 队列深度 | 10000+ | 500-1000 | 90%+ |
| 处理延迟 | 30min+ | <5min | 83%+ |
| 吞吐量 | 100/s | 400/s | 400% |
| CPU使用率 | 95% | 70% | 平衡 |
扩容消费节点需要综合考虑:
- 基础设施层面:水平扩展服务器、增加进程数
- 队列架构层面:多队列、分区、路由分发
- 消费逻辑层面:多进程/协程、幂等性、优雅关闭
- 监控自动化:指标监控、自动扩容、告警
建议先从单机多进程开始,当单个服务器资源不足时再考虑多节点扩展,同时要保证消费逻辑的幂等性,避免重复处理。