如何暴露 Prometheus 指标?
解读
Prometheus 是云原生监控的事实标准,面试中问“如何暴露”并不是让你背文档,而是考察三点:
- 是否理解 Prometheus 拉取(pull)模型与 Rust 服务的集成点;
- 能否在 不引入 GC 语言 的前提下,用 零成本抽象 的方式完成指标收集与 HTTP 暴露;
- 是否具备 生产级 思维:标签维度、基数爆炸、热路径性能、异步兼容、安全端口、认证、TLS、容器化等国内落地细节。
回答必须围绕 prometheus-client-rs(官方 Rust SDK)展开,兼顾 actix-web/axum 两种国内最常用异步框架,并给出 可编译、可灰度、可熔断 的代码骨架。
知识点
- 指标类型:Counter(单调增)、Gauge(可增可减)、Histogram(桶分布)、Family(标签维度)。
- Registry:全局或局部注册中心,负责聚合所有指标并编码成 text/plain; version=0.0.4 格式。
- HTTP 路由:注册
/metrics路由,只允许运维网段 访问;国内云厂商(阿里云 ACK、腾讯云 TKE)默认通过 ServiceMonitor 或 PodMonitor 拉取,需保证端口在 SecurityGroup 白名单。 - 异步安全:prometheus-client-rs 内部使用 RwLock<HashMap>,热路径仅做 原子递增,无阻塞;Histogram 的桶索引计算 为 O(1)。
- 基数控制:标签值必须从 有限枚举 中选取,禁止把用户 ID、订单号直接作为标签;国内大促场景下,单服务实例指标数 < 10 k 是红线。
- 进程启动参数:通过
--metrics-port独立端口暴露,与业务端口分离,方便 Sidecar 模式 或 istio 做 mTLS 终止。 - 编译期防护:使用 const labels 宏,在编译期把服务名、版本、集群 ID 注入,避免运行时拼接出错。
- 灰度与回滚:利用 Feature Flag 框架(如 rust-feature-flags)动态关闭 Histogram,防止升级后 P99 延迟飙高 导致误告警。
答案
下面给出 生产可直接落地 的最小闭环示例,基于 axum 0.7 + prometheus-client 0.22,单文件即可 cargo run;同时给出 actix-web 4 的差异化注释,方便面试时根据面试官技术栈切换。
use axum::{
routing::get,
Router,
};
use prometheus_client::{
encoding::text::encode,
metrics::{counter::Counter, family::Family, histogram::Histogram},
registry::Registry,
};
use std::{
net::SocketAddr,
sync::{Arc, OnceLock},
time::Duration,
};
// 1. 全局 Registry,OnceLock 保证仅初始化一次
static REG: OnceLock<Arc<Registry>> = OnceLock::new();
// 2. 业务指标定义
#[derive(Clone, Hash, PartialEq, Eq, Encode)]
struct Labels {
method: &'static str,
status: u16,
}
fn metrics_registry() -> Arc<Registry> {
REG.get_or_init(|| {
let mut registry = Registry::default();
// 2.1 Counter:请求总量
let http_requests = Family::<Labels, Counter>::default();
registry.register(
"http_requests_total",
"Total number of HTTP requests",
http_requests.clone(),
);
// 2.2 Histogram:请求延迟,桶覆盖 0.5ms ~ 5s
let http_duration = Histogram::new(
prometheus_client::metrics::histogram::exponential_buckets(0.0005, 2.0, 15),
);
registry.register(
"http_request_duration_seconds",
"HTTP request latencies in seconds",
http_duration.clone(),
);
Arc::new(registry)
})
.clone()
}
// 3. /metrics 路由处理器
async fn metrics_handler() -> String {
let registry = metrics_registry();
let mut buf = String::new();
// encode 内部仅做 RwLock 读锁,性能开销 < 1µs
encode(&mut buf, ®istry).unwrap();
buf
}
// 4. 埋点辅助函数,供业务层调用
pub fn record_request(method: &'static str, status: u16, duration: Duration) {
let registry = metrics_registry();
let labels = Labels { method, status };
// Counter 原子自增
registry
.get::<Family<Labels, Counter>>("http_requests_total")
.unwrap()
.get_or_create(&labels)
.inc();
// Histogram 记录延迟
registry
.get::<Histogram>("http_request_duration_seconds")
.unwrap()
.observe(duration.as_secs_f64());
}
#[tokio::main]
async fn main() {
// 5. 独立端口暴露,与业务端口分离
let metrics_addr: SocketAddr = "0.0.0.0:9090".parse().unwrap();
let app = Router::new().route("/metrics", get(metrics_handler));
// 6. 国内云原生环境必加:仅允许运维网段访问
println!("Metrics server listening on {}", metrics_addr);
axum::Server::bind(&metrics_addr)
.serve(app.into_make_service())
.await
.unwrap();
}
actix-web 差异点:
- 使用
actix_web_prom::PrometheusMetricsBuilder中间件,自动注入http_requests_total和http_request_duration_seconds,无需手写record_request; - 通过
.wrap()注册中间件后,默认路由即为/metrics,但需手动关闭 default-features 中的process指标,防止容器场景下 CPU Throttle 误报。
拓展思考
- 多实例聚合:国内大促常出现 “单 Pod 指标正常,聚合后 QPS 翻倍” 的幻觉,根本原因是 时间窗口对齐 问题;解决方案:在 Prometheus 配置里增加
metric_relabel_configs: { source_labels: [__name__], regex: '.*', target_label: 'pod', replacement: '${1}' }
并强制 scrape_interval = 30s,与 HPA 的 ** stabilizationWindowSeconds** 对齐。 - 高基数熔断:利用
prometheus_client::metrics::histogram::linear_buckets时,桶数过多会导致 内存占用 > 200 MB;可动态开关:
if cfg!(feature = "finer_histogram") { exponential_buckets } else { vec![0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0] },通过 ConfigMap 热更新。 - 安全加固:
- 在 Ingress-NGINX 层加
nginx.ingress.kubernetes.io/whitelist-source-range仅允许 VPC 内网 访问; - 使用 rustls 提供 TLS,证书由 cert-manager 自动轮转,零业务侵入。
- 在 Ingress-NGINX 层加
- eBPF 补充:当需要 内核级 指标(如 TCP 重传率)时,可用 aya 框架编写 eBPF 程序,把数据通过 perf-event array 送到用户态 Rust 服务,再注入到同一 Registry,实现 单端口统一暴露,避免 sidecar 模式 的多端口冲突。