本文目录导读:

我来为您设计一个完整的PHP业务监控指标采集方案,包括核心代码实现和最佳实践。
基础监控类
1 指标采集器基类
<?php
namespace App\Monitor;
/**
* 监控指标采集器基类
*/
abstract class BaseCollector
{
protected array $metrics = [];
protected float $startTime;
protected float $endTime;
public function start(): void
{
$this->startTime = microtime(true);
}
public function end(): void
{
$this->endTime = microtime(true);
}
abstract public function collect(): array;
protected function duration(): float
{
return round(($this->endTime - $this->startTime) * 1000, 2);
}
}
2 请求监控采集器
<?php
namespace App\Monitor\Collectors;
use App\Monitor\BaseCollector;
/**
* HTTP请求监控
*/
class RequestCollector extends BaseCollector
{
private array $requestData;
public function __construct(array $requestData)
{
$this->requestData = $requestData;
$this->start();
}
public function collect(): array
{
$this->end();
return [
'type' => 'request',
'timestamp' => time(),
'duration_ms' => $this->duration(),
'method' => $this->requestData['method'] ?? 'GET',
'path' => $this->requestData['path'] ?? '/',
'status_code' => $this->requestData['status_code'] ?? 200,
'ip' => $this->requestData['ip'] ?? '',
'user_agent' => substr($this->requestData['user_agent'] ?? '', 0, 200),
'memory_usage' => memory_get_usage(true),
'memory_peak' => memory_get_peak_usage(true),
];
}
}
3 数据库查询监控
<?php
namespace App\Monitor\Collectors;
use App\Monitor\BaseCollector;
/**
* 数据库查询监控
*/
class DatabaseCollector extends BaseCollector
{
private array $queryLog = [];
private int $queryCount = 0;
private float $totalTime = 0;
public function addQuery(string $sql, array $bindings, float $time): void
{
$this->queryCount++;
$this->totalTime += $time;
$this->queryLog[] = [
'sql' => $sql,
'bindings' => $bindings,
'time_ms' => $time
];
}
public function collect(): array
{
return [
'type' => 'database',
'timestamp' => time(),
'query_count' => $this->queryCount,
'total_time_ms' => round($this->totalTime, 2),
'avg_time_ms' => $this->queryCount > 0 ? round($this->totalTime / $this->queryCount, 2) : 0,
'slow_queries' => $this->getSlowQueries(),
'queries' => $this->queryLog
];
}
private function getSlowQueries(int $threshold = 100): array
{
return array_filter($this->queryLog, fn($q) => $q['time_ms'] > $threshold);
}
}
4 缓存监控
<?php
namespace App\Monitor\Collectors;
use App\Monitor\BaseCollector;
/**
* 缓存监控
*/
class CacheCollector extends BaseCollector
{
private array $cacheStats = [
'hits' => 0,
'misses' => 0,
'writes' => 0,
'deletes' => 0,
'hit_keys' => [],
'miss_keys' => []
];
public function addHit(string $key): void
{
$this->cacheStats['hits']++;
$this->cacheStats['hit_keys'][] = $key;
}
public function addMiss(string $key): void
{
$this->cacheStats['misses']++;
$this->cacheStats['miss_keys'][] = $key;
}
public function addWrite(string $key): void
{
$this->cacheStats['writes']++;
}
public function addDelete(string $key): void
{
$this->cacheStats['deletes']++;
}
public function collect(): array
{
$total = $this->cacheStats['hits'] + $this->cacheStats['misses'];
return [
'type' => 'cache',
'timestamp' => time(),
'hits' => $this->cacheStats['hits'],
'misses' => $this->cacheStats['misses'],
'hit_rate' => $total > 0 ? round($this->cacheStats['hits'] / $total * 100, 2) : 0,
'writes' => $this->cacheStats['writes'],
'deletes' => $this->cacheStats['deletes'],
'recent_hit_keys' => array_slice($this->cacheStats['hit_keys'], -10),
'recent_miss_keys' => array_slice($this->cacheStats['miss_keys'], -10)
];
}
}
业务指标监控
1 业务指标管理器
<?php
namespace App\Monitor;
/**
* 业务监控中心
*/
class BusinessMonitor
{
private static ?BusinessMonitor $instance = null;
private array $counters = [];
private array $gauges = [];
private array $timers = [];
private array $histograms = [];
private array $events = [];
private function __construct() {}
private function __clone() {}
public static function getInstance(): BusinessMonitor
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* 计数器增加
*/
public function increment(string $name, int $value = 1, array $labels = []): void
{
$key = $this->buildKey($name, $labels);
if (!isset($this->counters[$key])) {
$this->counters[$key] = [
'name' => $name,
'labels' => $labels,
'value' => 0
];
}
$this->counters[$key]['value'] += $value;
}
/**
* 设置瞬时值
*/
public function gauge(string $name, $value, array $labels = []): void
{
$key = $this->buildKey($name, $labels);
$this->gauges[$key] = [
'name' => $name,
'labels' => $labels,
'value' => $value,
'updated_at' => time()
];
}
/**
* 计时器记录
*/
public function timing(string $name, float $duration, array $labels = []): void
{
$key = $this->buildKey($name, $labels);
if (!isset($this->timers[$key])) {
$this->timers[$key] = [
'name' => $name,
'labels' => $labels,
'values' => []
];
}
$this->timers[$key]['values'][] = $duration;
// 限制保留最近1000个值
if (count($this->timers[$key]['values']) > 1000) {
array_shift($this->timers[$key]['values']);
}
}
/**
* 直方图记录
*/
public function histogram(string $name, $value, array $labels = []): void
{
$key = $this->buildKey($name, $labels);
if (!isset($this->histograms[$key])) {
$this->histograms[$key] = [
'name' => $name,
'labels' => $labels,
'values' => []
];
}
$this->histograms[$key]['values'][] = $value;
}
/**
* 记录业务事件
*/
public function event(string $name, array $data = []): void
{
$this->events[] = [
'name' => $name,
'data' => $data,
'timestamp' => time()
];
// 保留最近100个事件
if (count($this->events) > 100) {
array_shift($this->events);
}
}
/**
* 获取所有指标
*/
public function getMetrics(): array
{
return [
'counters' => array_values($this->counters),
'gauges' => array_values($this->gauges),
'timers' => array_map([$this, 'processTimers'], $this->timers),
'histograms' => array_map([$this, 'processHistograms'], $this->histograms),
'events' => $this->events
];
}
/**
* 重置所有指标
*/
public function reset(): void
{
$this->counters = [];
$this->gauges = [];
$this->timers = [];
$this->histograms = [];
$this->events = [];
}
private function buildKey(string $name, array $labels): string
{
return $name . ':' . json_encode($labels);
}
private function processTimers(array $timer): array
{
$values = $timer['values'];
$count = count($values);
return [
'name' => $timer['name'],
'labels' => $timer['labels'],
'count' => $count,
'sum' => array_sum($values),
'avg' => $count > 0 ? array_sum($values) / $count : 0,
'min' => $count > 0 ? min($values) : 0,
'max' => $count > 0 ? max($values) : 0,
'p50' => $this->percentile($values, 50),
'p95' => $this->percentile($values, 95),
'p99' => $this->percentile($values, 99)
];
}
private function processHistograms(array $histogram): array
{
$values = $histogram['values'];
$count = count($values);
return [
'name' => $histogram['name'],
'labels' => $histogram['labels'],
'count' => $count,
'sum' => array_sum($values),
'avg' => $count > 0 ? array_sum($values) / $count : 0,
'min' => $count > 0 ? min($values) : 0,
'max' => $count > 0 ? max($values) : 0,
'buckets' => $this->createBuckets($values)
];
}
private function percentile(array $values, int $percentile): float
{
if (empty($values)) return 0;
sort($values);
$index = ceil($percentile / 100 * count($values)) - 1;
return $values[$index];
}
private function createBuckets(array $values): array
{
$buckets = [];
$limits = [
10, 25, 50, 100, 250, 500, 1000, 2500, 5000
];
foreach ($limits as $limit) {
$buckets[$limit] = count(array_filter($values, fn($v) => $v <= $limit));
}
// 超过最大限制的值
$buckets['+Inf'] = count($values);
return $buckets;
}
}
监控数据导出
1 Prometheus格式导出
<?php
namespace App\Monitor\Exporters;
use App\Monitor\BusinessMonitor;
/**
* Prometheus格式导出器
*/
class PrometheusExporter
{
private BusinessMonitor $monitor;
public function __construct(BusinessMonitor $monitor)
{
$this->monitor = $monitor;
}
public function export(): string
{
$metrics = $this->monitor->getMetrics();
$output = '';
// 导出计数器
foreach ($metrics['counters'] as $counter) {
$output .= $this->formatCounter($counter);
}
// 导出Gauges
foreach ($metrics['gauges'] as $gauge) {
$output .= $this->formatGauge($gauge);
}
// 导出Timers
foreach ($metrics['timers'] as $timer) {
$output .= $this->formatTimer($timer);
}
// 导出Histograms
foreach ($metrics['histograms'] as $histogram) {
$output .= $this->formatHistogram($histogram);
}
return $output;
}
private function formatLabels(array $labels): string
{
if (empty($labels)) return '';
$pairs = [];
foreach ($labels as $key => $value) {
$pairs[] = sprintf('%s="%s"', $key, $value);
}
return '{' . implode(',', $pairs) . '}';
}
private function formatCounter(array $counter): string
{
$line = sprintf(
"# TYPE %s counter\n%s%s %d\n",
$counter['name'],
$counter['name'],
$this->formatLabels($counter['labels']),
$counter['value']
);
return $line;
}
private function formatGauge(array $gauge): string
{
$line = sprintf(
"# TYPE %s gauge\n%s%s %s\n",
$gauge['name'],
$gauge['name'],
$this->formatLabels($gauge['labels']),
$gauge['value']
);
return $line;
}
private function formatTimer(array $timer): string
{
$output = '';
// 主指标
$output .= sprintf(
"# TYPE %s_seconds summary\n",
$timer['name']
);
$output .= sprintf(
"%s_sum%s %f\n",
$timer['name'],
$this->formatLabels($timer['labels']),
$timer['sum']
);
$output .= sprintf(
"%s_count%s %d\n",
$timer['name'],
$this->formatLabels($timer['labels']),
$timer['count']
);
// 百分位数
foreach (['p50' => 0.5, 'p95' => 0.95, 'p99' => 0.99] as $percent => $quantile) {
$output .= sprintf(
"%s%s{%s,quantile=\"%s\"} %f\n",
$timer['name'],
'_seconds',
$this->formatLabels($timer['labels']),
$quantile,
$timer[$percent]
);
}
return $output;
}
private function formatHistogram(array $histogram): string
{
$output = '';
$labels = $this->formatLabels($histogram['labels']);
$output .= sprintf(
"# TYPE %s histogram\n",
$histogram['name']
);
// 桶数据
foreach ($histogram['buckets'] as $le => $count) {
$output .= sprintf(
"%s_bucket{%s,le=\"%s\"} %d\n",
$histogram['name'],
$labels,
$le,
$count
);
}
// 计数和总和
$output .= sprintf(
"%s_bucket{%s,le=\"+Inf\"} %d\n",
$histogram['name'],
$labels,
$histogram['count']
);
$output .= sprintf(
"%s_sum%s %f\n",
$histogram['name'],
$labels,
$histogram['sum']
);
$output .= sprintf(
"%s_count%s %d\n",
$histogram['name'],
$labels,
$histogram['count']
);
return $output;
}
}
2 JSON导出器
<?php
namespace App\Monitor\Exporters;
use App\Monitor\BusinessMonitor;
/**
* JSON格式导出器
*/
class JsonExporter
{
private BusinessMonitor $monitor;
public function __construct(BusinessMonitor $monitor)
{
$this->monitor = $monitor;
}
public function export(bool $pretty = false): string
{
$metrics = $this->monitor->getMetrics();
// 添加系统资源信息
$metrics['system'] = [
'memory' => [
'usage' => memory_get_usage(true),
'peak' => memory_get_peak_usage(true),
'limit' => ini_get('memory_limit')
],
'cpu' => [
'usage' => sys_getloadavg()
],
'server_time' => date('c'),
'php_version' => PHP_VERSION
];
return json_encode(
$metrics,
$pretty ? JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE : 0
);
}
}
集成示例
1 中间件集成
<?php
namespace App\Http\Middleware;
use Closure;
use App\Monitor\BusinessMonitor;
use App\Monitor\Collectors\RequestCollector;
use App\Monitor\Collectors\DatabaseCollector;
use Illuminate\Support\Facades\DB;
/**
* 监控中间件
*/
class MonitorMiddleware
{
private BusinessMonitor $monitor;
private RequestCollector $requestCollector;
private DatabaseCollector $dbCollector;
public function __construct(BusinessMonitor $monitor)
{
$this->monitor = $monitor;
$this->requestCollector = new RequestCollector(request()->all());
$this->dbCollector = new DatabaseCollector();
// 启用数据库查询日志
DB::enableQueryLog();
}
public function handle($request, Closure $next)
{
$startTime = microtime(true);
// 记录请求开始
$this->monitor->increment('http_requests_total');
// 处理业务
$response = $next($request);
// 收集数据库查询
$this->collectDatabaseMetrics();
// 收集请求指标
$this->collectRequestMetrics($response);
// 将监控数据附加到响应
$this->attachMetricsToResponse($response);
return $response;
}
private function collectDatabaseMetrics(): void
{
$queries = DB::getQueryLog();
foreach ($queries as $query) {
$this->dbCollector->addQuery(
$query['query'],
$query['bindings'],
$query['time']
);
}
$metrics = $this->dbCollector->collect();
// 记录到全局监控
$this->monitor->gauge('db_query_count', $metrics['query_count']);
$this->monitor->gauge('db_total_time', $metrics['total_time_ms']);
$this->monitor->gauge('db_avg_time', $metrics['avg_time_ms']);
if (!empty($metrics['slow_queries'])) {
$this->monitor->event('slow_queries', [
'count' => count($metrics['slow_queries']),
'queries' => $metrics['slow_queries']
]);
}
}
private function collectRequestMetrics($response): void
{
$duration = microtime(true) - $this->requestCollector->startTime;
// 按状态码分类
$statusCode = $response->getStatusCode();
$this->monitor->increment(
'http_requests_total',
1,
['status_code' => $statusCode]
);
// 记录请求耗时
$this->monitor->timing(
'http_request_duration_seconds',
$duration,
['method' => request()->method(), 'status_code' => $statusCode]
);
// 记录内存使用
$this->monitor->gauge('memory_usage', memory_get_usage(true));
$this->monitor->gauge('memory_peak', memory_get_peak_usage(true));
}
private function attachMetricsToResponse($response): void
{
if (method_exists($response, 'headers')) {
$metrics = $this->monitor->getMetrics();
$response->headers->set('X-Monitor-Timestamp', time());
$response->headers->set('X-Monitor-Duration',
round((microtime(true) - $this->requestCollector->startTime) * 1000, 2)
);
}
}
}
2 业务逻辑监控示例
<?php
namespace App\Services;
use App\Monitor\BusinessMonitor;
/**
* 订单服务 - 业务监控示例
*/
class OrderService
{
private BusinessMonitor $monitor;
public function __construct()
{
$this->monitor = BusinessMonitor::getInstance();
}
public function createOrder(array $orderData)
{
$startTime = microtime(true);
try {
// 业务处理
$order = $this->processOrder($orderData);
// 监控成功
$this->monitor->increment('orders_created_total');
$this->monitor->increment('orders_total', 1, [
'type' => $order['type'],
'channel' => $order['channel']
]);
// 记录订单金额
$this->monitor->histogram(
'order_amount',
$order['amount'],
['currency' => $order['currency']]
);
// 记录处理时间
$duration = microtime(true) - $startTime;
$this->monitor->timing('order_create_duration', $duration);
// 监控订单状态
$this->monitor->gauge('active_orders', $this->getActiveOrders());
return $order;
} catch (\Exception $e) {
// 监控失败
$this->monitor->increment('orders_failed_total', 1, [
'error' => get_class($e),
'stage' => 'create'
]);
// 记录异常事件
$this->monitor->event('order_error', [
'order_id' => $orderData['id'] ?? null,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
}
public function cancelOrder(int $orderId)
{
// 监控取消操作
$this->monitor->increment('orders_cancelled_total');
$reason = request()->input('reason', 'unknown');
$this->monitor->increment('orders_cancelled_total', 1, [
'reason' => $reason
]);
// 其他业务逻辑...
}
private function processOrder(array $orderData)
{
// 模拟订单处理
return array_merge($orderData, [
'id' => rand(1000, 9999),
'status' => 'created',
'created_at' => time()
]);
}
private function getActiveOrders()
{
// 模拟获取活跃订单数
return rand(10, 100);
}
}
3 监控端点路由
<?php
// routes/web.php 或 routes/api.php
use App\Monitor\BusinessMonitor;
use App\Monitor\Exporters\PrometheusExporter;
use App\Monitor\Exporters\JsonExporter;
// 监控指标端点(需要认证)
Route::get('/metrics', function () {
// 认证检查
if (!auth()->user()->isAdmin()) {
abort(403);
}
$monitor = BusinessMonitor::getInstance();
$exporter = new PrometheusExporter($monitor);
return response($exporter->export(), 200, [
'Content-Type' => 'text/plain; version=0.0.4'
]);
})->middleware('auth');
// JSON格式监控端点
Route::get('/metrics/json', function () {
if (!auth()->user()->isAdmin()) {
abort(403);
}
$monitor = BusinessMonitor::getInstance();
$exporter = new JsonExporter($monitor);
return response($exporter->export(true), 200, [
'Content-Type' => 'application/json'
]);
})->middleware('auth');
// 监控端点(用于Prometheus)
Route::get('/health', function () {
return response()->json([
'status' => 'ok',
'timestamp' => time(),
'memory' => memory_get_usage(true),
'load' => sys_getloadavg()
]);
});
自动化部署和集成
1 Docker部署监控
version: '3.8'
services:
app:
image: php:8.2-fpm
volumes:
- ./:/var/www/html
environment:
- APP_MONITOR_ENABLED=true
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
ports:
- "9090:9090"
grafana:
image: grafana/grafana:latest
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
node-exporter:
image: prom/node-exporter:latest
ports:
- "9100:9100"
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
ports:
- "8080:8080"
volumes:
prometheus_data:
grafana_data:
2 Prometheus配置
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets: []
rule_files:
- "alerts.yml"
scrape_configs:
- job_name: 'php-app'
static_configs:
- targets: ['app:8080']
metrics_path: /metrics
scrape_interval: 10s
- job_name: 'node'
static_configs:
- targets: ['node-exporter:9100']
- job_name: 'docker'
static_configs:
- targets: ['cadvisor:8080']
- job_name: 'mysql'
static_configs:
- targets: ['mysql-exporter:9104']
3 监控告警规则
# alerts.yml
groups:
- name: php_alerts
rules:
- alert: HighErrorRate
expr: sum(rate(http_requests_total{status_code=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "High HTTP error rate"
description: "Error rate is {{ $value }}%"
- alert: SlowResponse
expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "Slow HTTP responses"
description: "P95 response time is over 2 seconds"
- alert: HighMemoryUsage
expr: memory_usage / memory_peak > 0.8
for: 10m
labels:
severity: warning
annotations:
summary: "High memory usage"
description: "Memory usage is {{ $value }}%"
配置和最佳实践
1 配置文件
<?php
// config/monitor.php
return [
'enabled' => env('APP_MONITOR_ENABLED', true),
'export' => [
'format' => env('MONITOR_EXPORT_FORMAT', 'prometheus'),
'endpoint' => '/metrics',
'auth' => [
'enabled' => true,
'token' => env('MONITOR_AUTH_TOKEN')
]
],
'collectors' => [
'request' => true,
'database' => true,
'cache' => true,
'business' => true
],
'thresholds' => [
'slow_query' => 100, // ms
'slow_request' => 500, // ms
'error_rate' => 0.05 // 5%
],
'storage' => [
'driver' => 'redis', // redis, file, array
'ttl' => 86400, // 24 hours
'path' => storage_path('monitor') // for file driver
]
];
2 最佳实践清单
<?php
/**
* 监控最佳实践
*/
class MonitorBestPractices
{
/**
* 1. 命名规范
*/
public function namingConventions(): void
{
// 计数器:_total 后缀
$this->monitor->increment('http_requests_total');
// 时间:_duration_seconds 后缀
$this->monitor->timing('order_create_duration_seconds', $duration);
// 数值:_count 后缀
$this->monitor->gauge('active_orders_count', $count);
// 非官方但常用:_total, _sum, _bucket
// 使用 namespace: 应用名_模块名_指标名
$this->monitor->increment('app_checkout_orders_total');
}
/**
* 2. 标签使用
*/
public function labelUsage(): void
{
// 少用标签,但要有足够维度
$this->monitor->increment('orders_total', 1, [
'channel' => 'mobile', // 渠道
'status' => 'paid' // 状态
]);
// 避免高基数标签
// 不要使用:order_id, uuid, timestamp
// 预定义标签
$this->monitor->increment('requests_total', 1, [
'method' => 'GET',
'path' => '/api/orders',
'status' => '200'
]);
}
/**
* 3. 采样策略
*/
public function samplingStrategy(): void
{
// 1% 采样
if (rand(1, 100) === 1) {
$this->monitor->timing('payment_duration', $duration);
}
// 错误全量记录
if ($error) {
$this->monitor->increment('errors_total');
$this->monitor->event('error_details', $errorData);
}
}
/**
* 4. 性能考虑
*/
public function performanceConsiderations(): void
{
// 使用内存存储,异步导出
$monitor = BusinessMonitor::getInstance();
// 限制数据结构大小
// - 计数器:无限制
// - Gauges:数量控制
// - Timers:保留最近1000个
// - Events:保留最近100个
// 避免在循环中调用监控
foreach ($largeArray as $item) {
// 批量处理,而不是逐条记录
}
// 批量导出
if ($batchSize >= 100) {
$this->flushToStorage();
}
}
/**
* 5. 错误处理
*/
public function errorHandling(): void
{
try {
// 业务代码
} catch (\Exception $e) {
// 记录错误但不影响业务
try {
$this->monitor->increment('errors_total', 1, [
'type' => get_class($e)
]);
} catch (\Throwable $monitorError) {
// 监控失败不应影响主流程
error_log('Monitor error: ' . $monitorError->getMessage());
}
throw $e;
}
}
/**
* 6. 数据保护
*/
public function dataProtection(): void
{
// 不记录敏感数据
$this->monitor->event('user_login', [
// 'password' => $password, // NEVER!
// 'ssn' => $ssn, // NEVER!
'user_id' => $userId,
'ip' => $ip
]);
// 对用户数据进行脱敏
$safeEmail = substr($email, 0, 3) . '***@' . substr(strrchr($email, '@'), 1);
}
}
这个完整的PHP业务监控方案包括:
- 基础监控采集器 - 请求、数据库、缓存监控
- 业务指标管理器 - 计数器、计时器、直方图等
- 多种导出格式 - Prometheus、JSON
- 集成示例 - 中间件、业务服务
- 自动化和部署 - Docker、Prometheus、Grafana
- 最佳实践 - 命名、标签、采样、安全性
您可以根据实际需求选择使用的组件,并进行适当调整。