用 Prometheus 与 Grafana 给 AI 网关做业务监控,路径是:Micrometer 定义 Counter、Gauge、Timer 指标,Actuator 暴露 Prometheus 端点,Prometheus 抓取,Grafana 出图,PromQL 写告警。我们在企业级 AI 网关(Spring Boot + Spring AI + 多模型接入 + SSE 对话网关)上落地了 Token 消耗、模型延迟、SSE 在线连接等业务指标,模型故障从”用户投诉才知道”变成”5 分钟内告警”。
一、为什么默认指标不够用
Actuator 自带的 JVM、HTTP 指标只回答”系统资源是否异常”,回答不了哪个模型在烧钱、哪个响应变慢、SSE 连接为何断,这些只能靠自定义业务指标。
二、指标类型先选对
按数据形态选类型,选错全要返工。
| 指标类型 | 适合场景 | 网关示例 |
|---|---|---|
| Counter | 只增不减的累计次数 | 请求总数、失败次数、重试次数 |
| Gauge | 可升可降的瞬时值 | SSE 在线连接数、队列积压、限流触发次数 |
| Timer | 耗时与次数分布 | 模型调用延迟、TTFT、整请求耗时 |
| DistributionSummary | 非时间维度的数值分布 | 单请求 Token 数、上下文长度 |
三、埋点实现
3.1 依赖与端点配置
引入 actuator 与 micrometer-registry-prometheus 两个依赖后开启端点:
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
3.2 核心业务指标
网关核心是对话转发,指标按模型打标签区分不同上游。请求量用 Counter、模型耗时用 Timer,代码如下:
// 请求量与失败次数
Counter.builder("gateway.request.total")
.tag("model", modelName)
.tag("result", success ? "success" : "fail")
.register(registry)
.increment();
// 模型调用耗时,带 P50/P95/P99 分位
Timer.Sample sample = Timer.start(registry);
try {
String reply = chatClient.call(prompt);
return reply;
} finally {
sample.stop(Timer.builder("gateway.model.duration")
.tag("model", modelName)
.publishPercentiles(0.5, 0.95, 0.99)
.register(registry));
}
3.3 SSE 在线连接数用 Gauge
SSE 连接实时升降,用 AtomicInteger 做 Gauge 数据源。
@Component
public class SseConnectionTracker {
private final AtomicInteger online = new AtomicInteger();
public SseConnectionTracker(MeterRegistry registry) {
Gauge.builder("gateway.sse.connections", online, AtomicInteger::get)
.register(registry);
}
public void connect() { online.incrementAndGet(); }
public void disconnect() { online.decrementAndGet(); }
}
四、Prometheus 采集配置
scrape_configs:
- job_name: "ai-gateway"
metrics_path: "/actuator/prometheus"
scrape_interval: 15s
static_configs:
- targets: ["192.168.1.10:8080", "192.168.1.11:8080"]
五、Grafana 面板与 PromQL
建面板时核心是选对查询函数:
- 每秒请求量:
sum(rate(gateway_request_total[5m])) by (model) - 请求失败率:
sum(rate(gateway_request_total{result="fail"}[5m])) / sum(rate(gateway_request_total[5m])) - 模型调用 P95 延迟:
histogram_quantile(0.95, sum(rate(gateway_model_duration_seconds_bucket[5m])) by (le, model)) - 每小时 Token 消耗:
sum(rate(gateway_token_usage_seconds_count[1h])) by (model)
SSE 面板补”连接数骤降”:正常时平稳波动,瞬间归零多为网关重启或上游故障。首 Token 时间单独计时,它决定用户感知的”打字感”,普通耗时 Timer 覆盖不到;客户端中途断开单独打 Counter,区分主动关闭与超时,避免告警误判。
六、告警规则配置
按 5 分钟窗口算失败率,命中即触发:
groups:
- name: gateway_alerts
rules:
- alert: ModelErrorRateHigh
expr: sum(rate(gateway_request_total{result="fail"}[5m])) by (model) / sum(rate(gateway_request_total[5m])) by (model) > 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "模型 {{ $labels.model }} 失败率超过 5%"
七、落地流程与踩坑
按四步上线:
- 埋点:只先做请求量、Token、模型耗时、SSE 连接四个指标,避免一次铺太开;
- 采集:Prometheus 抓取,验证
/actuator/prometheus返回数据完整; - 出图:Grafana 建三个面板,跑一周建立基线;
- 告警:基线确认后再配阈值,先 warning 后 critical。
三个坑:用户 ID 做标签会撑爆时序库,标签基数要控;Gauge 必须绑定 AtomicInteger 等长期存活对象;Counter 以 _total 结尾,rate() 才能正确计算。
常见问题(FAQ)
Q1:业务指标和默认指标怎么分工?
默认指标管资源与存活,业务指标管模型表现与成本,两边都看才是完整监控。
Q2:SSE 连接数为什么用 Gauge 不用 Counter?
连接数有增有减,Gauge 表达当前值;Counter 只增不减,适合累计次数。
Q3:多实例网关的指标会重复吗?
Prometheus 按实例独立抓取,查询时用 sum 聚合,面板上看到的是全集群总量。