ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

建立大模型网关的 SLA 指标体系与可用性度量

建立大模型网关的 SLA 指标体系与可用性度量 许多团队将传统微服务网关的运维经验直接套用到大语言模型LLM调用链路上随后很快发现监控面板出现了严重失真。在传统 RPC 或 REST 场景下P99 延迟超过 1 秒通常意味着严重故障错误率也以 HTTP 状态码 5xx 为基准。但在大模型场景下一个包含多轮长上下文的生成式请求持续耗时 15 秒甚至更久是常态即使 HTTP 响应状态码为 200也可能因为上游算力耗尽而在输出几个 Token 后意外中断。要在大模型服务落地过程中建立可靠的服务等级协议SLA与监控体系必须重构度量指标从传统的“请求-响应”维度转向“首字延迟、吞吐速率、流式完整性与有效可用性”的多维评价体系。大模型核心 SLA 指标定义针对大模型生成的特有生命周期我们需要重点关注以下四个黄金指标指标名称简称定义与计算方式业务影响首字时间TTFT (Time to First Token)客户端发出请求到接收到第一个有效 Token 的耗时直接决定用户感知的界面响应速度单字生成耗时TPOT (Time Per Output Token)模型生成后续每个 Token 的平均耗时即(总耗时 - TTFT) / (输出 Token 数 - 1)决定打字机效果的流畅度输出吞吐率TPS (Tokens Per Second)客户端每秒接收到的 Token 数量即输出 Token 数 / 总耗时衡量整体生成效率与通道带宽流式完成率Stream Completion Rate正常接收到结束符[DONE]或finish_reason: stop的请求占比衡量链路稳定性与截断异常传统可用性公式简单以成功请求数 / 总请求数计算而在大模型网关中我们将其升级为有效可用性Effective Availability$$\text{Effective Availability} \frac{\text{总请求数} - (\text{HTTP 异常} \text{TTFT 超时} \text{意外截断} \text{内容安全拦截误杀})}{\text{总请求数}} \times 100%$$在我们的网关落地实践中这套度量逻辑完全贯穿于 SSE 长连接的全生命周期当客户端请求到达网关时网关在向上游模型集群转发请求的瞬间首先打下纳秒级请求时间戳T0上游模型完成上下文 Prefill 阶段并吐出第一个 SSE Chunk 时网关记录首包时间戳T1二者相减即得出决定用户首屏感知的TTFT T1 - T0并立即向客户端转发首包随后在持续的 Decode 解码阶段上游高频泵出数据块网关在转发 Chunk 的同时递增累加 Token 计数动态核算相邻 Token 之间的平摊耗时TPOT与实时速率直到上游最终推送[DONE]或终止信号Tn网关才正式闭合本次会话上下文将总耗时、输出 Token 总量以及流式完整性状态统一投递至 Prometheus 监控流水线。要让这套高精度的时序度量在海量并发长连接下既不阻塞网络 I/O又不引发堆内存膨胀网关层的拦截器必须做到极度轻量网关层流式指标埋点实践在 Spring Cloud Gateway 或基于 Netty 的自定义网关中我们需要在全局过滤器GlobalFilter中拦截 SSE 流记录时间戳并计算 Token 数据。以下为基于 Micrometer 的大模型指标收集过滤器核心实现package com.example.gateway.filter; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.DistributionSummary; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import org.reactivestreams.Publisher; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; Component public class LlmMetricsFilter implements WebFilter { private final Timer ttftTimer; private final DistributionSummary tokenSummary; private final Counter streamErrorCounter; public LlmMetricsFilter(MeterRegistry registry) { this.ttftTimer Timer.builder(llm.gateway.ttft) .description(大模型首字延迟时间) .publishPercentiles(0.5, 0.9, 0.99) .register(registry); this.tokenSummary DistributionSummary.builder(llm.gateway.tokens.output) .description(单次请求输出 Token 数量) .register(registry); this.streamErrorCounter Counter.builder(llm.gateway.stream.error) .description(流式生成异常中断计数) .register(registry); } Override public MonoVoid filter(ServerWebExchange exchange, WebFilterChain chain) { long startTime System.currentTimeMillis(); AtomicLong firstTokenTime new AtomicLong(0); AtomicInteger tokenCount new AtomicInteger(0); AtomicBoolean isCompleted new AtomicBoolean(false); ServerHttpResponseDecorator decoratedResponse new ServerHttpResponseDecorator(exchange.getResponse()) { Override public MonoVoid writeWith(Publisher? extends DataBuffer body) { if (body instanceof Flux) { Flux? extends DataBuffer fluxBody (Flux? extends DataBuffer) body; return super.writeWith(fluxBody.map(dataBuffer - { // 首次获取数据块时记录 TTFT if (firstTokenTime.compareAndSet(0, System.currentTimeMillis())) { long ttftMillis firstTokenTime.get() - startTime; ttftTimer.record(ttftMillis, TimeUnit.MILLISECONDS); } byte[] content new byte[dataBuffer.readableBytes()]; dataBuffer.read(content); dataBuffer.readPosition(0); // 重置读取游标供下游消费 String chunkText new String(content, StandardCharsets.UTF_8); // 粗粒度估算或解析 SSE 内部 token 结构 tokenCount.addAndGet(estimateTokenCount(chunkText)); if (chunkText.contains([DONE])) { isCompleted.set(true); } return dataBuffer; }).doFinally(signalType - { tokenSummary.record(tokenCount.get()); if (!isCompleted.get()) { streamErrorCounter.increment(); } })); } return super.writeWith(body); } }; return chain.filter(exchange.mutate().response(decoratedResponse).build()); } private int estimateTokenCount(String sseChunk) { // 实际场景中可解析 JSON 内的 usage 字段或按字符比例估算 return Math.max(1, sseChunk.length() / 4); } }多模型提供商动态路由与降级治理单一供应商的服务抖动是大模型系统中最常见的 SLA 杀手。在大模型网关中度量指标不仅用于告警展示还应直接参与动态调度。通过动态探测多通道的实时 TTFT 与错误率网关可以在上游服务响应恶化时毫秒级切换至备用模型提供商llm: gateway: routing-rules: - model-group: general-reasoning primary-provider: provider-a backup-provider: provider-b degrade-threshold: max-ttft-ms: 3000 continuous-5xx-count: 5 window-seconds: 10当网关检测到主提供商的 TTFT 连续突破阈值或者由于并发限流返回 HTTP 429 时熔断器自动触发并进行平滑切换避免直接向上游业务层抛出不可恢复的异常。运营成本与配额度量大模型的可用性管理与成本控制紧密耦合。一个请求即使响应成功若消耗了数万个超出预期的 Token也会对集群整体可用性造成资源挤兑。因此网关层的度量报表必须将财务维度与技术维度结合按部门/业务线打标对每个请求注入tenant_id、app_id与model_id标签实现成本精细化核算。动态滑动窗口配额通过 Redis 记录各业务线在过去 1 小时内的 Token 消耗总量超出预算后触发限流降级如切换至轻量化小参数模型。缓存命中度量记录语义缓存Semantic Cache的命中率有效减轻 LLM 计算负载并显著降低整体 TTFT。
返回列表