Webman配合Prometheus采集业务指标,关键在于CollectorRegistry单例复用、Nginx显式路由/metrics、响应轻量无IO;需在Bootstrap.php中初始化Redis存储的单例,禁用try_files,metrics端点仅渲染指标,所有埋点前置到中间件或回调中。

Webman 配合 Prometheus 做业务指标采集,关键不在“能不能”,而在于「指标是否跨进程持久、路由是否被正确转发、/metrics 是否轻量可抓取」——三者任一出错,Prometheus 就会拉到空数据、跳变值或 500 错误。
CollectorRegistry 必须单例复用,不能每次请求 new
Webman 是常驻内存框架,但 PHP-FPM 或多 worker 场景下,每个请求仍可能由不同进程执行。若在 MetricsController 或 metrics.php 中每次请求都 new CollectorRegistry(),会导致:
- Counter 从 0 开始计数,Prometheus 图表出现断崖式归零
- Gauge 值被覆盖,无法反映真实状态(如当前连接数)
- 直方图 buckets 数据丢失,
tp_api_response_time_seconds_bucket无数据
正确做法是:在 app/Bootstrap.php 中初始化并绑定为全局单例:
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Redis;
$storage = new Redis(['host' => '127.0.0.1', 'port' => 6379]);
$registry = new CollectorRegistry($storage);
container()->bind('prometheus.registry', function () use ($registry) {
return $registry;
});
后续所有中间件或控制器通过 container()->get('prometheus.registry') 获取同一实例。
立即学习“PHP免费学习笔记(深入)”;
Nginx 必须显式路由 /metrics 到 PHP 脚本,不能依赖 try_files
phpEnv 或标准 Nginx 配置中,try_files $uri $uri/ /index.php?$query_string 会把 /metrics 当作路径匹配,最终转发给 index.php,而非你的 metrics.php —— 导致返回 HTML 或 404。
必须在 server 块中单独声明 location:
location /metrics {
alias /var/www/my-webman/app/controller/MetricsController.php;
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $request_filename;
}
注意点:
-
alias后必须是完整物理路径,结尾不加/ - 不能用
root + index,否则 Nginx 会尝试加载/metrics/index.php - 确保该 PHP 文件有读权限,且开头已
require __DIR__ . '/vendor/autoload.php';
/metrics 响应必须轻量,禁止 IO 操作和长耗时逻辑
Prometheus 默认抓取超时为 10 秒。若你在 MetricsController::index() 中执行 DB 查询、Redis 扫描或调用 file_get_contents,极易触发 timeout,导致指标中断。
这个端点只做一件事:序列化已采集的指标。所有埋点必须前置到业务逻辑中:
- HTTP 耗时:在全局中间件
RequestLog的after()中调用$histogram->observe($duration) - 订单积压数:在队列消费前,用
$gauge->set($count)更新 - WebSocket 在线人数:在
onConnect/onClose回调中增减$gauge->inc()或$gauge->dec()
同时加防护:
set_time_limit(5);
header('Content-Type: text/plain; version=0.0.4');
echo $registry->render();
直方图 observe() 的数值和 label 必须合规,否则整条指标静默丢弃
常见现象:tp_api_response_time_seconds_bucket 在 Prometheus 中完全查不到。不是没注册,而是 observe() 被跳过了。
两个硬性条件必须同时满足:
-
$duration必须是秒级浮点数:ThinkPHP 的毫秒值要除以 1000,$histogram->observe($time_ms / 1000) -
label值不能含非法字符:/、?、=、空格等 ——Request::url()返回/user/123会直接失效,应改用Request::rule()归一为/user/{id}
另外,buckets 必须显式传入数组,不要依赖默认值;API 场景推荐:[0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0]。
最易被忽略的一点:Redis 存储后端的连接稳定性。一旦 Redis 暂时不可用,CollectorRegistry 会静默降级到内存存储,指标又开始跨进程丢失 —— 所以得在初始化时加健康检查或 fallback 日志,不能只靠“它应该连得上”。



















