PHP项目指标收集与Prometheus

wen PHP项目 1

本文目录导读:

PHP项目指标收集与Prometheus

  1. 基础架构
  2. 安装与配置
  3. 中间件实现
  4. Metrics端点
  5. Prometheus配置
  6. 自定义业务指标
  7. Redis性能指标监控
  8. Grafana仪表板配置
  9. 性能优化建议
  10. Alerts规则

我来帮你设计一个PHP项目指标收集并与Prometheus集成的方案。

基础架构

graph LR
    A[PHP应用] --> B[Prometheus客户端]
    B --> C[/metrics端点]
    C --> D[Prometheus Server]
    D --> E[Grafana]

安装与配置

安装Prometheus PHP客户端

composer require promphp/prometheus_client_php

基础配置示例

<?php
// src/Metrics/PrometheusMetrics.php
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Redis;
use Prometheus\Storage\APC;
use Prometheus\RenderTextFormat;
class PrometheusMetrics
{
    private CollectorRegistry $registry;
    private static ?self $instance = null;
    public function __construct()
    {
        // 选择存储后端
        $adapter = new Redis(['host' => '127.0.0.1', 'port' => 6379]);
        // 或使用APC(单机适用)
        // $adapter = new APC();
        $this->registry = new CollectorRegistry($adapter);
    }
    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    // HTTP请求指标
    public function trackRequest(string $method, string $endpoint, int $statusCode, float $duration): void
    {
        $counter = $this->registry->getOrRegisterCounter(
            'app',
            'http_requests_total',
            'Total HTTP requests',
            ['method', 'endpoint', 'status_code']
        );
        $counter->incBy(1, [$method, $endpoint, (string)$statusCode]);
        $histogram = $this->registry->getOrRegisterHistogram(
            'app',
            'http_request_duration_seconds',
            'HTTP request duration',
            ['method', 'endpoint'],
            [0.1, 0.5, 1, 2, 5, 10]
        );
        $histogram->observe($duration, [$method, $endpoint]);
    }
    // 数据库查询指标
    public function trackQuery(string $queryType, string $table, float $duration): void
    {
        $histogram = $this->registry->getOrRegisterHistogram(
            'app',
            'db_query_duration_seconds',
            'Database query duration',
            ['query_type', 'table'],
            [0.01, 0.05, 0.1, 0.5, 1, 2]
        );
        $histogram->observe($duration, [$queryType, $table]);
    }
    // 业务指标
    public function incBusinessMetric(string $metricName, array $labels = []): void
    {
        $counter = $this->registry->getOrRegisterCounter(
            'app_business',
            $metricName,
            'Business metric: ' . $metricName,
            array_keys($labels)
        );
        $counter->incBy(1, array_values($labels));
    }
    // 渲染metrics
    public function render(): string
    {
        $renderer = new RenderTextFormat();
        return $renderer->render($this->registry->getMetricFamilySamples());
    }
}

中间件实现

Laravel中间件

<?php
// app/Http/Middleware/PrometheusMiddleware.php
namespace App\Http\Middleware;
use Closure;
use App\Metrics\PrometheusMetrics;
class PrometheusMiddleware
{
    public function handle($request, Closure $next)
    {
        $startTime = microtime(true);
        $response = $next($request);
        $duration = microtime(true) - $startTime;
        $metrics = PrometheusMetrics::getInstance();
        $metrics->trackRequest(
            $request->method(),
            $request->path(),
            $response->status(),
            $duration
        );
        return $response;
    }
}

Symfony中间件

<?php
// src/Middleware/PrometheusMiddleware.php
namespace App\Middleware;
use PrometheusMetrics;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class PrometheusMiddleware implements HttpKernelInterface
{
    private HttpKernelInterface $app;
    public function handle(Request $request, int $type = self::MAIN_REQUEST, bool $catch = true)
    {
        $start = microtime(true);
        $response = $this->app->handle($request, $type, $catch);
        $duration = microtime(true) - $start;
        $metrics = PrometheusMetrics::getInstance();
        $metrics->trackRequest($request->getMethod(), $request->getPathInfo(), $response->getStatusCode(), $duration);
        return $response;
    }
}

Metrics端点

<?php
// routes/api.php (Laravel)
// 或直接在入口文件
Route::get('/metrics', function () {
    $metrics = PrometheusMetrics::getInstance();
    // 添加系统指标
    $metrics->registry->getOrRegisterGauge(
        'system',
        'memory_usage_bytes',
        'Current memory usage'
    )->set(memory_get_usage(true));
    $metrics->registry->getOrRegisterGauge(
        'system',
        'memory_peak_usage_bytes',
        'Peak memory usage'
    )->set(memory_get_peak_usage(true));
    return response($metrics->render(), 200, [
        'Content-Type' => 'text/plain; charset=utf-8'
    ]);
});

Prometheus配置

# prometheus.yml
scrape_configs:
  - job_name: 'php_app'
    scrape_interval: 15s
    static_configs:
      - targets: ['localhost:8080']
    metrics_path: '/metrics'
  - job_name: 'php_app_advanced'
    scrape_interval: 10s
    metrics_path: '/metrics'
    static_configs:
      - targets: 
        - 'app1.example.com:8080'
        - 'app2.example.com:8080'
    relabel_configs:
      - source_labels: [__address__]
        regex: '(.*):.*'
        target_label: instance
        replacement: '${1}'

自定义业务指标

<?php
// Services/BusinessMetrics.php
class BusinessMetrics
{
    private PrometheusMetrics $metrics;
    public function __construct()
    {
        $this->metrics = PrometheusMetrics::getInstance();
    }
    // 用户注册指标
    public function trackUserRegistration(string $source, string $country): void
    {
        $this->metrics->registry->getOrRegisterCounter(
            'business',
            'user_registrations_total',
            'Total user registrations',
            ['source', 'country']
        )->incBy(1, [$source, $country]);
    }
    // 订单指标
    public function trackOrder(string $status, string $paymentMethod, float $amount): void
    {
        $this->metrics->registry->getOrRegisterCounter(
            'business',
            'orders_total',
            'Total orders',
            ['status', 'payment_method']
        )->incBy(1, [$status, $paymentMethod]);
        $this->metrics->registry->getOrRegisterCounter(
            'business',
            'order_amount_total',
            'Total order amount',
            ['status']
        )->incBy($amount, [$status]);
    }
    // 并发用户数
    public function trackConcurrentUsers(int $count): void
    {
        $this->metrics->registry->getOrRegisterGauge(
            'business',
            'concurrent_users',
            'Number of concurrent users'
        )->set($count);
    }
    // 队列指标
    public function trackQueue(string $queueName, int $size, int $processed): void
    {
        $this->metrics->registry->getOrRegisterGauge(
            'queue',
            'queue_size',
            'Queue size',
            ['queue_name']
        )->set($size, [$queueName]);
        $this->metrics->registry->getOrRegisterCounter(
            'queue',
            'messages_processed_total',
            'Total messages processed',
            ['queue_name']
        )->incBy($processed, [$queueName]);
    }
}

Redis性能指标监控

<?php
// Monitoring/RedisMonitor.php
class RedisMonitor
{
    private PrometheusMetrics $metrics;
    private \Redis $redis;
    public function __construct(\Redis $redis)
    {
        $this->redis = $redis;
        $this->metrics = PrometheusMetrics::getInstance();
    }
    public function collectRedisMetrics(): void
    {
        $info = $this->redis->info();
        // Redis关键指标
        $gauges = [
            'redis_connected_clients' => $info['connected_clients'],
            'redis_used_memory_bytes' => $info['used_memory'],
            'redis_used_memory_rss_bytes' => $info['used_memory_rss'],
            'redis_hit_rate' => $this->calculateHitRate($info),
            'redis_instantaneous_ops_per_sec' => $info['instantaneous_ops_per_sec'],
            'redis_keyspace_hits' => $info['keyspace_hits'],
            'redis_keyspace_misses' => $info['keyspace_misses']
        ];
        foreach ($gauges as $name => $value) {
            $this->metrics->registry->getOrRegisterGauge(
                'redis',
                $name,
                'Redis metric: ' . $name
            )->set($value);
        }
    }
    private function calculateHitRate(array $info): float
    {
        $hits = $info['keyspace_hits'];
        $misses = $info['keyspace_misses'];
        $total = $hits + $misses;
        return $total > 0 ? ($hits / $total) * 100 : 0;
    }
}

Grafana仪表板配置

{
  "dashboard": {: "PHP Application Metrics",
    "panels": [
      {
        "title": "HTTP Requests Rate",
        "type": "graph",
        "targets": [
          {
            "expr": "rate(app_http_requests_total[5m])",
            "legendFormat": "{{method}} {{endpoint}}"
          }
        ]
      },
      {
        "title": "Request Duration (95th Percentile)",
        "type": "heatmap",
        "targets": [
          {
            "expr": "histogram_quantile(0.95, sum(rate(app_http_request_duration_seconds_bucket[5m])) by (le))"
          }
        ]
      },
      {
        "title": "Database Query Duration",
        "type": "graph",
        "targets": [
          {
            "expr": "rate(app_db_query_duration_seconds_sum[5m]) / rate(app_db_query_duration_seconds_count[5m])",
            "legendFormat": "{{query_type}} - {{table}}"
          }
        ]
      },
      {
        "title": "System Memory",
        "type": "gauge",
        "targets": [
          {
            "expr": "system_memory_usage_bytes"
          }
        ]
      }
    ]
  }
}

性能优化建议

批量采集

<?php
// 使用APCu本地缓存,减少Redis写入
class BatchMetricsCollector
{
    private array $buffer = [];
    private int $bufferSize = 100;
    public function record(string $metric, array $labels, float $value): void
    {
        $this->buffer[] = [
            'metric' => $metric,
            'labels' => $labels,
            'value' => $value,
            'timestamp' => microtime(true)
        ];
        if (count($this->buffer) >= $this->bufferSize) {
            $this->flush();
        }
    }
    public function flush(): void
    {
        // 批量写入Redis或数据库
        // ...
        $this->buffer = [];
    }
}

异步发送

<?php
// 使用消息队列异步处理
class AsyncMetricsSender
{
    private Queue $queue;
    public function send(array $metrics): void
    {
        // 将metrics推送到消息队列
        $this->queue->push(json_encode($metrics));
    }
}

Alerts规则

# alerts.yml
groups:
  - name: php_app_alerts
    rules:
      - alert: HighErrorRate
        expr: rate(app_http_requests_total{status_code=~"5.."}[5m]) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          description: "Error rate is {{ $value }} for the last 5 minutes"
      - alert: SlowRequests
        expr: histogram_quantile(0.95, rate(app_http_request_duration_seconds_bucket[5m])) > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          description: "95th percentile request duration is {{ $value }}s"
      - alert: HighMemoryUsage
        expr: system_memory_usage_bytes > 500000000
        for: 10m
        labels:
          severity: warning
        annotations:
          description: "Memory usage is {{ $value }} bytes"

这套方案提供了完整的PHP与Prometheus集成方案,覆盖了基础监控、业务指标、性能优化和告警配置,你可以根据实际需求调整存储后端和具体指标。

抱歉,评论功能暂时关闭!