本文目录导读:

针对 PHP 项目的队列监控,采集生产消费指标数据通常涉及以下几个核心层面,以下是具体的实现方案和工具推荐:
核心指标定义
首先明确需要采集的关键指标:
| 指标类型 | 指标名称 | 说明 |
|---|---|---|
| 生产端 | 生产速率 | 每秒/分钟入队数量 |
| 生产端 | 生产延迟 | 消息从创建到入队的时间 |
| 队列 | 队列深度 | 当前待处理消息数 |
| 队列 | 延迟队列大小 | 延迟消息数量 |
| 消费端 | 消费速率 | 每秒/分钟处理数量 |
| 消费端 | 消费延迟 | 消息入队到被消费的时间 |
| 消费端 | 失败率 | 处理失败占比 |
| 消费端 | 处理耗时 | 平均/最大处理时间 |
| 系统 | Worker数量 | 当前活跃消费者数 |
数据采集实现方案
使用 Redis + Laravel Horizon(推荐)
如果你的项目基于 Laravel,Horizon 提供了完善的监控能力:
// config/horizon.php
'metrics' => [
'snapshots' => [
// 采集频率配置
'rate' => 5, // 每5秒采集一次
],
],
// 自定义采集逻辑
class QueueMetricsCollector
{
public function collect(): array
{
return [
'queue_size' => $this->getQueueSize(),
'processing' => $this->getProcessingCount(),
'failed' => $this->getFailedCount(),
'throughput' => $this->getThroughput(),
];
}
private function getThroughput(): array
{
$redis = Redis::connection('horizon');
$recent = $redis->zcount('horizon:jobs:recent', '-inf', '+inf');
$timeWindow = 60; // 60秒窗口
return [
'rate' => $recent / $timeWindow,
'total' => $recent,
];
}
}
通用 PHP 实现(基于 Redis)
class QueueMonitor
{
private Redis $redis;
private string $prefix = 'queue:metrics:';
public function recordProduction(string $queue, array $data): void
{
$now = microtime(true);
$minute = floor($now / 60) * 60;
// 速率统计(每分钟)
$this->redis->hIncrBy(
"{$this->prefix}prod_rate:{$queue}:{$minute}",
'count',
1
);
// 记录生产延迟
$this->redis->lPush(
"{$this->prefix}prod_latency:{$queue}",
$now - $data['created_at'] // 消息创建到入队的时间
);
// 设置过期时间(保留24小时)
$this->redis->expire("{$this->prefix}prod_rate:{$queue}:{$minute}", 86400);
$this->redis->lTrim("{$this->prefix}prod_latency:{$queue}", 0, 999);
$this->redis->expire("{$this->prefix}prod_latency:{$queue}", 3600);
}
public function recordConsumption(string $queue, array $data): void
{
$now = microtime(true);
$minute = floor($now / 60) * 60;
$cost = ($now - $data['pushed_at']) * 1000; // 处理耗时(ms)
// 消费速率
$this->redis->hIncrBy(
"{$this->prefix}consume_rate:{$queue}:{$minute}",
'count',
1
);
// 处理耗时统计(P50, P95, P99)
$this->redis->lPush(
"{$this->prefix}cost:{$queue}",
json_encode([
'cost' => $cost,
'time' => $now
])
);
// 消费延迟
$this->redis->lPush(
"{$this->prefix}consume_latency:{$queue}",
$now - $data['pushed_at']
);
// 成功/失败统计
$status = $data['success'] ? 'success' : 'failed';
$this->redis->hIncrBy(
"{$this->prefix}status:{$queue}:{$minute}",
$status,
1
);
}
public function getMetrics(string $queue): array
{
$now = time();
$metrics = [];
// 当前队列深度
$metrics['queue_depth'] = $this->redis->lLen("queue:{$queue}");
// 最近5分钟生产速率
for ($i = 0; $i < 5; $i++) {
$minute = floor(($now - $i * 60) / 60) * 60;
$count = $this->redis->hGet(
"{$this->prefix}prod_rate:{$queue}:{$minute}",
'count'
) ?: 0;
$metrics['prod_rate'][] = [
'time' => $minute,
'count' => (int)$count
];
}
// 平均处理耗时
$costs = $this->redis->lRange("{$this->prefix}cost:{$queue}", 0, -1);
$costValues = [];
foreach ($costs as $cost) {
$costValues[] = json_decode($cost, true)['cost'];
}
sort($costValues);
$metrics['avg_cost'] = !empty($costValues) ?
array_sum($costValues) / count($costValues) : 0;
$metrics['p95_cost'] = !empty($costValues) ?
$costValues[(int)(count($costValues) * 0.95)] : 0;
// 失败率
$failed = $this->redis->hGet(
"{$this->prefix}status:{$queue}:" . floor($now / 60) * 60,
'failed'
) ?: 0;
$total = $this->redis->hGet(
"{$this->prefix}status:{$queue}:" . floor($now / 60) * 60,
'success'
) ?: 0;
$metrics['failure_rate'] = ($total + $failed) > 0 ?
$failed / ($total + $failed) * 100 : 0;
return $metrics;
}
}
// 使用示例
class JobProcessor
{
private QueueMonitor $monitor;
public function handle(Job $job): void
{
$startTime = microtime(true);
try {
// 处理业务逻辑
$job->process();
$this->monitor->recordConsumption('default', [
'pushed_at' => $job->pushedAt,
'success' => true
]);
} catch (\Exception $e) {
$this->monitor->recordConsumption('default', [
'pushed_at' => $job->pushedAt,
'success' => false
]);
throw $e;
}
}
}
使用 Prometheus + Grafana(推荐生产环境)
// 使用 promphp/prometheus_client_php
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Redis;
class PrometheusMetrics
{
private CollectorRegistry $registry;
public function __construct()
{
$adapter = new Redis(['host' => '127.0.0.1']);
$this->registry = new CollectorRegistry($adapter);
}
public function initMetrics(): void
{
// 队列深度 Gauge
$gauge = $this->registry->registerGauge(
'queue',
'queue_depth',
'Current queue depth',
['queue', 'host']
);
// 生产速率 Counter
$counter = $this->registry->registerCounter(
'queue',
'messages_produced_total',
'Total number of produced messages',
['queue']
);
// 处理耗时 Histogram
$histogram = $this->registry->registerHistogram(
'queue',
'job_processing_seconds',
'Job processing duration in seconds',
['queue', 'job_type'],
[0.01, 0.05, 0.1, 0.5, 1, 2, 5]
);
// 失败率 Counter
$failedCounter = $this->registry->registerCounter(
'queue',
'failed_jobs_total',
'Total number of failed jobs',
['queue', 'error_type']
);
}
public function recordMetrics(string $queue, array $stats): void
{
$gauge = $this->registry->getGauge('queue', 'queue_depth');
$gauge->set($stats['depth'], [$queue, gethostname()]);
$counter = $this->registry->getCounter('queue', 'messages_produced_total');
$counter->incBy($stats['produced_count'], [$queue]);
if (isset($stats['processing_time'])) {
$histogram = $this->registry->getHistogram('queue', 'job_processing_seconds');
$histogram->observe($stats['processing_time'], [$queue, $stats['job_type']]);
}
if ($stats['failed']) {
$failedCounter = $this->registry->getCounter('queue', 'failed_jobs_total');
$failedCounter->inc([$queue, $stats['error_type'] ?? 'unknown']);
}
}
}
实时监控实现
使用 WebSocket 推送实时数据
// 基于 Swoole + WebSocket 的实时监控
class QueueRealTimeMonitor
{
public function __construct(
private Redis $redis,
private \Swoole\WebSocket\Server $ws
) {}
public function broadcastMetrics(): void
{
// 每5秒广播一次
swoole_timer_tick(5000, function () {
$metrics = $this->collectAllMetrics();
foreach ($this->ws->connections as $fd) {
if ($this->ws->isEstablished($fd)) {
$this->ws->push($fd, json_encode([
'type' => 'metrics',
'data' => $metrics
]));
}
}
});
}
private function collectAllMetrics(): array
{
return [
'timestamp' => microtime(true),
'queues' => [
'default' => $this->getQueueMetrics('default'),
'high' => $this->getQueueMetrics('high'),
'email' => $this->getQueueMetrics('email'),
],
'system' => [
'workers' => $this->getActiveWorkers(),
'memory_usage' => memory_get_usage(true),
'cpu_load' => sys_getloadavg()[0],
]
];
}
}
日志采集方案
使用结构化日志 + ELK 栈:
class QueueLogger
{
private Logger $logger;
public function logMetrics(string $queue, array $context): void
{
$this->logger->info('queue.metrics', [
'queue' => $queue,
'timestamp' => time(),
'depth' => $context['depth'],
'throughput' => $context['throughput'],
'latency_ms' => $context['latency'] ?? null,
'worker_id' => getmypid(),
'host' => gethostname(),
]);
}
}
可视化展示
Grafana Dashboard 配置示例
{: "PHP Queue Monitor",
"panels": [
{
"title": "Queue Depth",
"type": "graph",
"targets": [
{
"expr": "queue_queue_depth{queue=~\"$queue\"}",
"legendFormat": "{{queue}} - {{host}}"
}
]
},
{
"title": "Processing Rate",
"type": "graph",
"targets": [
{
"expr": "rate(queue_messages_produced_total[1m])",
"legendFormat": "{{queue}}"
}
]
},
{
"title": "P95 Processing Time",
"type": "heatmap",
"targets": [
{
"expr": "histogram_quantile(0.95, sum(rate(queue_job_processing_seconds_bucket[5m])) by (le, queue))",
"legendFormat": "{{queue}}"
}
]
}
]
}
最佳实践建议
-
选择合适存储:
- 实时指标:Redis(Pipeline 批量写入)
- 历史趋势:InfluxDB/TimescaleDB
- 聚合分析:ClickHouse
-
控制采样频率:
- 高吞吐场景:每5-10秒采样一次
- 低吞吐场景:每60秒采样一次
-
避免副作用:
// 异步指标上报,不阻塞主流程 $promise = \Amp\call(function () use ($metrics) { yield $this->metricsClient->pushAsync($metrics); }); -
告警规则:
# prometheus-alert.yml groups: - name: queue_alerts rules: - alert: QueueTooDeep expr: queue_queue_depth > 10000 for: 5m labels: severity: warning annotations: summary: "Queue {{ $labels.queue }} is too deep" - alert: HighFailureRate expr: rate(queue_failed_jobs_total[5m]) / rate(queue_messages_produced_total[5m]) > 0.05 for: 3m labels: severity: critical
实际部署架构
[PHP Workers] → [Redis/Beanstalkd] → [Metrics Collector]
↓
[Prometheus Pushgateway] ← [Prometheus Client]
↓
[Prometheus Server]
↓
[Alertmanager] → [钉钉/邮件/企微]
↓
[Grafana Dashboard]
这种架构可以同时保证:
- 低延迟的指标采集(微秒级)
- 高可用性(无单点故障)
- 可扩展性(水平扩展 Worker 自动增加指标采集能力)
- 丰富的可视化(Grafana 社区有成熟的 Queue Dashboard 模板)
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播