指标与用量事件
AISIX 网关会公开聚合指标和可导出的用量事件。这些信号共同反映服务健康状况、流量趋势,以及每次请求尝试对应的模型、路由和策略结果。
选择遥测来源
请先选择与运维问题匹配的来源;需要深入调查问题时,再按请求、模型或服务提供方关联各类信号。
| 运维问题 | 首选来源 | 提供的信息 |
|---|---|---|
| 各网关实例的流量是否健康? | Prometheus 指标 | 请求速率、延迟分布、Token 计数、预算 Gauge、策略结果、路由健康、缓存行为和导出器投递健康。 |
| 单个请求发生了什么? | 访问日志 | 结构化请求字段,包括状态、延迟、模型、模型服务提供方、请求 ID,以及可用时的路由结果。 |
| 调用应用可以观察到什么? | 响应头 | 受支持路由上的请求关联、缓存结果、重试时机和选中目标提示。 |
| 请求记录可以存储到哪里、如何分析或用于核算? | 用量事件 | 通过可观测性导出器投递的逐次尝试结果和消耗记录。 |
抓取 Prometheus 指标
AISIX 默认通过专用指标监听器在 /metrics 提供 Prometheus 指标。你可以通过启动期可观测性设置修改路径或禁用该端点。
该端点设计上不做认证。请保持专用指标监听器私有。
在启动配置中配置 Prometheus 暴露:
observability:
metrics:
prometheus:
enabled: true
path: "/metrics"
专用监听器默认绑定到 0.0.0.0:9090。如果 Prometheus 应从其他接口或端口抓取指标,请设置不同的监听地址。
抓取默认指标端点:
curl -sS "http://127.0.0.1:9090/metrics"
AISIX 会在每次抓取时发布配置状态。其他指标族会在首次观测时注册,因此流量指标可能不会在启动后立即出现。请先发送一次模型请求,再检查 aisix_requests_total 和 aisix_tokens_consumed_total 等序列。
AISIX 使用 aisix_ 前缀输出原生指标名称。使用直方图序列计算跨网关实例的延迟百分位数,并使用请求计数器分析成功率和路由情况。精确指标名称、标签范围和 PromQL 示例请参见指标参考。
通过 observability.metrics.labels 可以选择各指标族输出的标签。配置示例、默认值和完整变量列表参见指标标签与变量。
区分请求计数与尝试计数
请求是一次调用方交互。重试或故障转移可能让一个请求产生多次用量事件尝试。部分尝试会在目标级限流或请求组装阶段停止,尚未调用模型服务提供方,因此 AISIX 在不同信号中对尝试的计量方式也不同:
| 单位 | 统计位置 | 一个样本或一行代表什么 |
|---|---|---|
| 请求 | aisix_proxy_requests_total、aisix_llm_requests_total | 一次客户端请求,状态是请求最终结束时的状态。对流式响应而言,这是流结束时的状态,可能与流开始时调用方收到的 200 不同。 |
| 尝试 | aisix_deployment_requests_total 及部署指标族 | 对某一个目标模型的一次上游调用。 |
| 发出尝试 | aisix_usage_events_emitted_total | 一次用量事件的发出尝试,在投递队列接受或拒绝它之前计数。 |
| 尝试 | 用量事件记录,以及基于其构建的用量日志 | 一次已记录的处理尝试,包括在上游调用前停止的尝试。同一请求的各次尝试共享 request_id,按 attempt_index 排序,并用 is_final_attempt: true 标出该请求的终态行。 |
如果某个目标返回 502,而后备目标成功,请求计数器只记录最终返回给调用方的 200。部署计数器和用量事件则会保留失败的尝试。因此,用量日志中的 5xx 尝试数可能远高于请求指标;两者计量的单位不同。
请按各查询真正回答的问题选用:
# 最终以服务端错误结束的请求——调用方实际经历的情况。
sum(increase(aisix_proxy_requests_total{status=~"5.."}[24h]))
# 发生过故障转移、但最终仍以服务端错误结束的请求。
sum(increase(aisix_proxy_requests_total{status=~"5..", is_fallback="true"}[24h]))
# 失败的上游尝试,按目标聚合——包含后续被故障转移救回的那些。
# 这是 5xx 用量日志行数在尝试层面的对应视图,范围限于调用已配置模型的路由。
sum(increase(aisix_deployment_failure_responses_total[24h])) by (model)
# 成功救回请求的故障转移,按模型组和实际到达的目标聚合。
sum(increase(aisix_routing_successful_fallbacks_total[24h])) by (model, fallback_model)
# 用量事件的发出尝试,以及被交接队列拒绝的事件。
sum(increase(aisix_usage_events_emitted_total{status_code="5xx"}[24h]))
sum(increase(aisix_usage_event_drops_total[24h]))
# 某个成员被限流拒绝的次数。`status` 携带原始 HTTP 状态码,
# 因此无需扫描整个状态族即可定位单一故障模式。
sum(increase(aisix_usage_events_emitted_total{user_id="<member-id>", status="429"}[24h]))
# 哪些队列交接被拒绝了。队列侧的 reason 与发出计数器携带相同的模型与
# Provider Key 标签,因此该差值在按模型的粒度上同样成立,而不只是总量。
sum(increase(aisix_usage_event_drops_total{reason=~"sink_.*"}[24h])) by (model, provider_key_name)
# 网关已记录但未能投递到控制面的用量。这些样本只携带 成员标签,
# 因此按成员分组,而不是按模型分组。
sum(increase(aisix_usage_event_drops_total{reason=~"send_failed|retry_budget_exhausted"}[24h])) by (reason, user_name)
比较相同的数据范围
在将两个总数视为不一致之前,请先检查它们的覆盖范围:
| 差异 | 原因 | 检查方法 |
|---|---|---|
| 抓取覆盖范围 | 请求计数器不带环境标签。只有 Prometheus 抓取了服务该环境的每个网关,单环境用量总数才可与之比较。 | 按 job 和 instance 对原始计数器分组,再与正在运行的网关实例核对。 |
| 时间覆盖范围 | increase(...[24h]) 只使用该区间内存在的样本。重启或更短的 Prometheus 保留期可能造成用量日志查询中没有的缺口。 | 在相同时间范围内绘制原始计数器。 |
| 投递覆盖范围 | aisix_usage_event_drops_total 既统计因接收端被禁用、队列已满或关闭而无法进入投递队列的事件(sink_disabled、sink_full、sink_closed),也统计工作线程已接受、但未能投递到控制面的事件(send_failed、retry_budget_exhausted)。导出器侧的丢失不计入该指标。 | 队列侧丢弃按 model 分组;投递侧丢弃按 user_name 分组——它们不带模型和 Provider Key 归因。监控控制面投递的 telemetry batch failed (events dropped) 和导出器投递的 sink delivery dropped。控制面在已接受的批次中拒绝的事件则由 aisix_usage_events_rejected_total 统计。 |
| 没有上游调用 | 目标级限流和请求组 装失败仍会产生用量事件,但没有部署计数器样本。组装失败包括凭证不可用、缺少 model_name,以及 api_base 缺失或格式错误。 | 配置错误的目标会产生失败请求,但没有部署失败,因为模型服务提供方从未被调用。 |
部署指标族覆盖对已配置模型的上游调用,而用量事件还覆盖 MCP 工具调用、A2A 调用和透传路由。请逐项隔离差异,不要将全部差值都解释为网关侧拒绝。
导出用量事件
用量事件是受支持代理路径输出的逐次尝试记录。发生重试或故障转移的请求会生成多条具有相同 request_id 的事件,并按 attempt_index 排序。因此统计这些记录得到的是尝试数而非请求数——在拿记录条数与请求指标作比较之前,请先阅读区分请求计数与尝试计数。
每个请求恰好有一条 is_final_attempt: true 的事件。按该字段筛选可以按最终结果统计请求数,它能认出一个状态和尝试形态都与之前失败相同的失败的最后一次尝试。每个请求也恰好有一条 attempt_index 为 0 的事件,即它第一个尝试的事件,或者没有上游尝试时唯一的事件。控制面用这条首事件做聚合请求计数,但它的状态可能描述的是之后被重试或回退恢复的失败。1.8.0 及更早版本的网关不输出 is_final_attempt。
最后一行是普通请求、缓存命中、拒绝、MCP、A2A 或 Realtime 调用的唯一一行。其他情况下是以下之一:
- 给出答复的回退,它之前失败的尝试为
false。 - 所有尝试都失败时的最后一个失败。
- 某次尝试计费后被安全护栏拒绝时,紧随其后的拒绝行。
- 流结束、中途失败或被放弃时写入的那一行。
- 合议的评审模型行或失败行,其合议成员行为
false。
批量归因的每一行都有自己的 request_id,取值为 true。如果网关丢弃了某个请求的最后一条事件(例如投递队列已满),该请求就没有取值为 true 的行。
该字段写在每一行上,包括 false,metadata_only 和 full 下都一样:
| 目的地 | 字段 | 取值 |
|---|---|---|
| 控制面、对象存储、Datadog | is_final_attempt | JSON 布尔值 |
| 阿里云 SLS | is_final_attempt | 字符串 "true" 或 "false" |
| OTLP | aisix.is_final_attempt | 布尔属性 |
控制面用它实现每个请求只看最后一条。
/v1/messages/count_tokens 以及支持的端点中列出的单次调用端点遵循同样的规则:每次导致重试或故障转移的失败尝试都会产生一条自己的零 Token 事件。
每条事件都携带 occurred_at,即网关记录该事件的时间,格式为带恰好三位小数的 RFC 3339 UTC 时间戳,例如 2026-09-24T12:42:25.123Z。对象存储、阿里云 SLS 和 Datadog 按原样接收该字符串,OTLP 导出器则在其纳秒时间戳中携带同一时间。因此,同一请求的各次尝试,以及在同一秒内完成的事件,都可以精确到毫秒排序。RFC 3339 解析器都接受带小数的形式;只接受整秒(…:SSZ)的解析器需要更新。
用量事件不能从本地端点读取。请在可观测性导出器中配置导出器,将其投递到 OTLP/HTTP、对象存储、阿里云 SLS 或 Datadog。
每条事件都会包含请求结果、消耗详情、调用方请求的模型别名,以及网关能够观测到时处理该次尝试的解析模型。在 /v1/chat/completions 上,每条事件还会携带 cache_status(disabled、miss、hit 或 bypass);由响应缓存返回的响应会把命中层记录在 cache_hit_layer(exact 或 semantic),语义命中还会把匹配相似度记录在 cache_similarity。流式响应从不被缓存,因此即使环境中启用了缓存策略,流式请求的事件也会上报 cache_status="disabled"。
Responses 结束原因
Responses API 的结果不声明结束原因,因此 AISIX 在 /v1/responses 上根据调用方收到的结果推导事件的 finish_reason,无论原生处理还是协议转换、流式还是非流式,取值沿用聊天补全的词汇:
结果 status | 记录的 finish_reason |
|---|---|
completed | 输出中含有 function_call 条目、custom_tool_call 条目或客户端执行的 tool_search_call 条目时为 tool_calls |