本文目录导读:

在 PHP 中实现自定义指标暴露接口,通常用于监控系统(如 Prometheus、Grafana 等),以下是几种常见的实现方案:
Prometheus 格式(最常用)
使用 promphp/prometheus_client_php
<?php
// metrics.php - Prometheus 指标暴露接口
require_once 'vendor/autoload.php';
use Prometheus\CollectorRegistry;
use Prometheus\Storage\InMemory;
use Prometheus\RenderTextFormat;
// 创建注册表
$registry = new CollectorRegistry(new InMemory());
// 定义自定义指标
$counter = $registry->registerCounter(
'app_requests_total',
'Total number of requests',
['method', 'endpoint']
);
$gauge = $registry->registerGauge(
'app_memory_usage_bytes',
'Current memory usage',
['service']
);
$histogram = $registry->registerHistogram(
'app_request_duration_seconds',
'Request duration in seconds',
['method'],
[0.1, 0.5, 1, 2, 5]
);
// 记录指标数据
$counter->incBy(10, ['GET', '/api/users']);
$counter->incBy(5, ['POST', '/api/users']);
$gauge->set(memory_get_usage(true), ['api-service']);
$gauge->set(memory_get_usage(true), ['web-service']);
// 模拟请求耗时数据
$histogram->observe(0.3, ['GET']);
$histogram->observe(1.2, ['POST']);
// 渲染并输出指标
$renderer = new RenderTextFormat();
$result = $renderer->render($registry->getMetricFamilySamples());
header('Content-Type: ' . RenderTextFormat::MIME_TYPE);
echo $result;
轻量级实现(不依赖第三方库)
<?php
// custom_metrics.php
header('Content-Type: text/plain; version=0.0.4');
// 自定义指标数据存储(实际应用中可能从数据库或缓存读取)
$metrics = [
'app_requests_total' => [
'help' => 'Total number of HTTP requests',
'type' => 'counter',
'values' => [
['labels' => ['method="GET"', 'endpoint="/home"'], 'value' => 12345],
['labels' => ['method="POST"', 'endpoint="/api/data"'], 'value' => 6789],
]
],
'app_http_request_duration_seconds' => [
'help' => 'HTTP request duration in seconds',
'type' => 'histogram',
'values' => [
['labels' => ['le="0.1"'], 'value' => 100],
['labels' => ['le="0.5"'], 'value' => 300],
['labels' => ['le="1"'], 'value' => 400],
['labels' => ['le="2"'], 'value' => 450],
['labels' => ['le="+Inf"'], 'value' => 500],
]
]
];
// 输出 Prometheus 格式
foreach ($metrics as $name => $metric) {
echo "# HELP {$name} {$metric['help']}\n";
echo "# TYPE {$name} {$metric['type']}\n";
foreach ($metric['values'] as $data) {
$labels = implode(',', $data['labels']);
echo "{$name}{{$labels}} {$data['value']}\n";
}
echo "\n";
}
JSON 格式(自定义监控系统)
<?php
// json_metrics.php
header('Content-Type: application/json');
class MetricsCollector {
private $metrics = [];
public function increment($name, $labels = [], $value = 1) {
$key = $this->buildKey($name, $labels);
if (!isset($this->metrics[$key])) {
$this->metrics[$key] = [
'name' => $name,
'labels' => $labels,
'value' => 0,
'timestamp' => time()
];
}
$this->metrics[$key]['value'] += $value;
$this->metrics[$key]['timestamp'] = time();
}
public function gauge($name, $value, $labels = []) {
$key = $this->buildKey($name, $labels);
$this->metrics[$key] = [
'name' => $name,
'labels' => $labels,
'value' => $value,
'timestamp' => time()
];
}
public function histogram($name, $value, $labels = []) {
$key = $this->buildKey($name, $labels);
if (!isset($this->metrics[$key])) {
$this->metrics[$key] = [
'name' => $name,
'labels' => $labels,
'count' => 0,
'sum' => 0,
'buckets' => [0.1, 0.5, 1, 2, 5],
'bucket_counts' => array_fill(0, 6, 0)
];
}
$this->metrics[$key]['count']++;
$this->metrics[$key]['sum'] += $value;
foreach ($this->metrics[$key]['buckets'] as $i => $bucket) {
if ($value <= $bucket) {
$this->metrics[$key]['bucket_counts'][$i]++;
}
}
}
private function buildKey($name, $labels) {
return $name . '|' . json_encode($labels);
}
public function render() {
return json_encode([
'timestamp' => time(),
'service' => gethostname(),
'metrics' => $this->metrics
], JSON_PRETTY_PRINT);
}
}
// 使用示例
$collector = new MetricsCollector();
$collector->increment('http_requests', ['endpoint' => '/home', 'method' => 'GET']);
$collector->increment('http_requests', ['endpoint' => '/api/data', 'method' => 'POST'], 5);
$collector->gauge('memory_usage', memory_get_usage(true), ['service' => 'api']);
$collector->histogram('request_duration', 0.3, ['endpoint' => '/home']);
echo $collector->render();
使用框架集成(以 Laravel 为例)
<?php
// routes/api.php
use App\Services\MetricsService;
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Redis;
use Prometheus\RenderTextFormat;
Route::get('/metrics', function () {
// 使用 Redis 存储(适合多实例)
$registry = new CollectorRegistry(new Redis(['host' => 'redis', 'port' => 6379]));
// 或者使用 APCu 存储
// $registry = new CollectorRegistry(new APCng());
$renderer = new RenderTextFormat();
$result = $renderer->render($registry->getMetricFamilySamples());
return response($result)
->header('Content-Type', RenderTextFormat::MIME_TYPE);
});
// app/Services/MetricsService.php
class MetricsService {
private $registry;
public function __construct() {
$this->registry = new CollectorRegistry(new InMemory());
}
public function incrementCounter($name, $labels = []) {
$counter = $this->registry->getOrRegisterCounter(
'app',
$name,
'Application counter: ' . $name,
array_keys($labels)
);
$counter->incBy(1, array_values($labels));
}
}
完整的企业级实现
<?php
// enterprise_metrics.php
require_once 'vendor/autoload.php';
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Redis;
use Prometheus\RenderTextFormat;
use Predis\Client;
class EnterpriseMetrics {
private $registry;
private $appName;
public function __construct($redisConfig, $appName = 'my_app') {
$this->appName = $appName;
// 配置 Redis 存储
$redis = new Client($redisConfig);
$this->registry = new CollectorRegistry(new Redis($redis));
// 注册基本指标
$this->registerBasicMetrics();
}
private function registerBasicMetrics() {
// 系统指标
$this->registry->registerGauge(
'system_cpu_usage_percent',
'CPU usage percentage',
['host', 'app']
);
$this->registry->registerGauge(
'system_memory_usage_bytes',
'Memory usage in bytes',
['host', 'app']
);
// 业务指标
$this->registry->registerCounter(
'business_requests_total',
'Total business requests',
['app', 'endpoint', 'status']
);
$this->registry->registerHistogram(
'business_request_duration_seconds',
'Business request duration',
['endpoint'],
[0.1, 0.5, 1, 2, 5, 10]
);
}
public function updateSystemMetrics() {
$host = gethostname();
// CPU 使用率
$cpu = sys_getloadavg()[0] / 100;
$this->registry->getGauge('system_cpu_usage_percent')
->set($cpu, [$host, $this->appName]);
// 内存使用
$mem = memory_get_usage(true);
$this->registry->getGauge('system_memory_usage_bytes')
->set($mem, [$host, $this->appName]);
}
public function recordRequest($endpoint, $status, $duration) {
$counter = $this->registry->getCounter('business_requests_total');
$counter->incBy(1, [$this->appName, $endpoint, $status]);
$histogram = $this->registry->getHistogram('business_request_duration_seconds');
$histogram->observe($duration, [$endpoint]);
}
public function render() {
$renderer = new RenderTextFormat();
return $renderer->render($this->registry->getMetricFamilySamples());
}
}
// 在入口脚本中使用
$metrics = new EnterpriseMetrics([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
// 更新系统指标
$metrics->updateSystemMetrics();
// 输出指标
header('Content-Type: ' . RenderTextFormat::MIME_TYPE);
echo $metrics->render();
安全防护
<?php
// secure_metrics.php
session_start();
class MetricsSecurity {
private $allowedIPs = ['127.0.0.1', '192.168.1.0/24'];
private $apiKeys = ['your-secure-api-key'];
public function validateAccess() {
// IP 白名单检查
if (!$this->checkIP()) {
http_response_code(403);
die('Forbidden');
}
// API Key 检查
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
if (!in_array($apiKey, $this->apiKeys)) {
http_response_code(401);
die('Unauthorized');
}
// 速率限制
$this->rateLimit();
return true;
}
private function checkIP() {
$ip = $_SERVER['REMOTE_ADDR'];
foreach ($this->allowedIPs as $allowedIP) {
if (strpos($allowedIP, '/') !== false) {
// CIDR 检查
if ($this->ipInCIDR($ip, $allowedIP)) {
return true;
}
} elseif ($ip === $allowedIP) {
return true;
}
}
return false;
}
private function ipInCIDR($ip, $cidr) {
list($subnet, $bits) = explode('/', $cidr);
$ip_bin = ip2long($ip);
$subnet_bin = ip2long($subnet);
$mask = -1 << (32 - (int)$bits);
return (($ip_bin & $mask) === ($subnet_bin & $mask));
}
private function rateLimit() {
$key = 'metrics_limit:' . $_SERVER['REMOTE_ADDR'];
$current = apcu_fetch($key) ?: 0;
if ($current >= 60) { // 每分钟最多60次
http_response_code(429);
die('Too Many Requests');
}
apcu_store($key, $current + 1, 60);
}
}
// 使用安全验证
$security = new MetricsSecurity();
$security->validateAccess();
// 然后输出指标...
部署建议
# Docker Compose 示例
version: '3'
services:
php-app:
image: php:8.2-apache
volumes:
- ./:/var/www/html
ports:
- "8080:80"
environment:
- METRICS_ENABLED=true
- METRICS_API_KEY=your-secure-key
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
# prometheus.yml
scrape_configs:
- job_name: 'php-app'
scrape_interval: 15s
metrics_path: '/metrics'
basic_auth:
username: 'metrics'
password: 'your-secure-password'
static_configs:
- targets: ['php-app:80']
关键要点
- 性能优化:使用 Redis/APCu 存储指标数据,避免阻塞主业务
- 安全防护:添加 IP 白名单、API Key 验证、速率限制
- 容器化改造:支持 Docker 部署,便于监控
- 多维度指标:包含系统指标(CPU、内存)和业务指标(请求数、延迟)
- 标准化格式:使用 Prometheus 标准格式,便于与主流监控系统集成
- 错误处理:确保指标暴露接口不会影响主业务