基于OpenTelemetry与ClickHouse的AI应用延迟与Token消耗监控实践 1. 先搞清楚 AI 响应延迟和 Token 消耗到底在监控什么如果你正在部署或使用一个大模型应用无论是自建的还是调用的第三方 API最头疼的往往不是功能实现而是上线后的“黑盒”状态。模型推理为什么突然慢了Token 消耗为什么和预估的不一样成本是不是失控了这些问题靠猜和看日志是解决不了的。AI 响应延迟指的是从你的应用发出一个请求给 AI 模型或 API到完整收到模型返回结果所花费的时间。这个时间不仅仅是模型“思考”的时间它还包括了网络传输、队列等待、结果流式返回如果支持等多个环节。延迟高用户体验就差系统吞吐量也会下降。Token 消耗对于按 Token 计费的服务如 OpenAI API、Azure OpenAI 等就是直接的成本。对于自建模型它则反映了计算资源的消耗。监控 Token 消耗核心是确保输入输出符合预期没有因为提示词设计不当、系统消息重复、上下文管理错误等原因导致不必要的“浪费”。所以做这个监控的目标很明确不是为了监控而监控而是要能快速定位性能瓶颈和成本异常把“黑盒”变成“白盒”。适合看这篇文章的人包括正在开发 AI 应用的后端工程师、负责模型服务运维的 SRE、以及需要控制 API 调用成本的技术负责人。2. 监控方案选型为什么是 OpenTelemetry ClickHouse市面上监控工具很多从经典的 Prometheus Grafana到各种商业 APM。但对于 AI 应用这种特定场景我们需要一个能灵活处理高基数维度比如每个用户、每个会话、每个模型版本、能存储详细日志和追踪数据、并且查询性能要够快的方案。OpenTelemetry是目前云原生可观测性的事实标准。它不是一个具体的后端存储或展示工具而是一套采集和发送遥测数据指标、日志、追踪的 API 和 SDK。它的最大价值在于“与厂商无关”。你用它的 SDK 在代码里埋点收集到的数据可以导出到任何支持的后端比如 Prometheus、Jaeger或者我们这里要用的 ClickHouse。这样你就不会被某个特定的监控平台绑死。ClickHouse是一个开源的列式数据库以极快的分析查询速度著称。它特别适合存储和查询时间序列数据、事件日志和追踪数据。AI 请求的每次调用都是一条事件记录包含时间戳、延迟、Token 数、用户 ID、模型名称、请求状态等多个维度。用传统关系型数据库查聚合报表会很慢而 ClickHouse 可以轻松应对。Prometheus更适合监控基础设施和系统指标CPU、内存、网络它强大的聚合能力和告警规则很适合做阈值告警。但对于需要关联分析、下钻查询比如“查看用户A在今天下午所有失败请求的详细输入输出”的场景Prometheus 并不擅长。所以一个典型的架构是数据采集层在你的 AI 应用代码中使用 OpenTelemetry SDK如opentelemetry-api,opentelemetry-sdk手动埋点记录每次调用的开始时间、结束时间、输入 Token 数、输出 Token 数、模型名称、用户标识、状态码等。数据导出层配置 OpenTelemetry 的导出器Exporter将收集到的追踪Trace和指标Metric数据通过 OTLPOpenTelemetry Protocol协议发送到OpenTelemetry Collector。数据处理与转发层OpenTelemetry Collector 是一个独立的组件负责接收、处理如过滤、添加属性、批量和转发数据。我们将配置一个clickhouseexporter将数据写入 ClickHouse。数据存储与分析层ClickHouse 负责存储所有明细数据。数据可视化层使用 Grafana 连接 ClickHouse 数据源制作监控仪表盘。这个组合的优势在于明细数据可查任何一次异常请求的完整上下文Trace ID都能找到便于调试。维度自由下钻可以按模型、按用户、按时间段、按状态任意组合分析。性能足够ClickHouse 应对日均百万甚至千万级别的调用记录毫无压力。生态开放未来如果想换存储或增加分析工具成本很低。3. 环境搭建与核心组件部署理论说完了我们开始动手。假设我们从一个干净的 Linux 服务器Ubuntu 20.04开始。3.1 部署 ClickHouseClickHouse 的安装很简单官方提供了多种方式。这里用最通用的方式# 安装必要的工具 sudo apt-get install -y apt-transport-https ca-certificates dirmngr # 添加官方仓库 sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 8919F6BD2B48D754 echo deb https://packages.clickhouse.com/deb stable main | sudo tee /etc/apt/sources.list.d/clickhouse.list sudo apt-get update # 安装 ClickHouse 服务器和客户端 sudo apt-get install -y clickhouse-server clickhouse-client # 启动服务 sudo systemctl enable clickhouse-server sudo systemctl start clickhouse-server # 使用客户端连接本地默认无密码 clickhouse-client进入 ClickHouse 客户端后我们需要创建数据库和表来存储 OpenTelemetry 的数据。OpenTelemetry Collector 的clickhouseexporter 有推荐的表结构。我们创建一个CREATE DATABASE otel; CREATE TABLE otel.traces ( timestamp DateTime64(9) CODEC(Delta(8), ZSTD(1)), traceId String CODEC(ZSTD(1)), spanId String CODEC(ZSTD(1)), parentSpanId String CODEC(ZSTD(1)), spanName LowCardinality(String) CODEC(ZSTD(1)), spanKind LowCardinality(String) CODEC(ZSTD(1)), serviceName LowCardinality(String) CODEC(ZSTD(1)), duration Int64 CODEC(ZSTD(1)), attributes Map(LowCardinality(String), String) CODEC(ZSTD(1)), statusCode LowCardinality(String) CODEC(ZSTD(1)), statusMessage String CODEC(ZSTD(1)), INDEX idx_traceId traceId TYPE bloom_filter GRANULARITY 1, INDEX idx_service serviceName TYPE bloom_filter GRANULARITY 1, INDEX idx_span_name spanName TYPE bloom_filter GRANULARITY 1, INDEX idx_duration duration TYPE minmax GRANULARITY 1 ) ENGINE MergeTree PARTITION BY toDate(timestamp) ORDER BY (serviceName, spanName, toUnixTimestamp(timestamp)) TTL toDateTime(timestamp) INTERVAL 30 DAY SETTINGS index_granularity 8192, ttl_only_drop_parts 1;这张表的核心字段解释timestamp 请求发生的时间高精度。traceId,spanId 分布式追踪的标识用于串联一次完整请求的所有步骤。spanName 跨度名称对我们来说可以是llm_chat_completion,embedding_call等。serviceName 服务名例如ai-backend-service。duration 请求耗时单位纳秒。这是我们监控延迟的核心字段。attributes 一个 Map 类型的字段用于存放所有自定义属性。这是我们存放input_tokens,output_tokens,model_name,user_id,status_code等关键信息的地方。这种设计非常灵活无需频繁修改表结构。3.2 部署 OpenTelemetry CollectorCollector 作为一个独立的网关/处理器负责接收来自多个应用的数据并统一发送到 ClickHouse。从 GitHub Releases 下载最新版本的二进制包例如otelcol-contrib_0.96.0_linux_amd64.tar.gz。# 解压 tar -xzf otelcol-contrib_0.96.0_linux_amd64.tar.gz sudo mv otelcol-contrib /usr/local/bin/ # 创建配置文件目录 sudo mkdir -p /etc/otelcol-contrib接下来创建 Collector 的配置文件/etc/otelcol-contrib/config.yaml。这是核心receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 # 接收 gRPC 数据 http: endpoint: 0.0.0.0:4318 # 接收 HTTP 数据 processors: batch: # 批量处理提高写入效率 send_batch_size: 10000 timeout: 10s memory_limiter: check_interval: 1s limit_mib: 2000 spike_limit_mib: 500 exporters: debug: verbosity: detailed clickhouse: endpoint: tcp://localhost:9000 database: otel traces_table_name: traces ttl_days: 30 timeout: 5s retry_on_failure: enabled: true initial_interval: 5s max_interval: 30s max_elapsed_time: 300s service: pipelines: traces: receivers: [otlp] processors: [memory_limiter, batch] exporters: [clickhouse]配置说明receivers.otlp 在 4317 (gRPC) 和 4318 (HTTP) 端口上监听接收应用发来的数据。exporters.clickhouse 配置了数据写入我们刚创建的 ClickHouse 表和数据库。service.pipelines 定义了一个名为traces的数据流水线数据从otlp接收经过memory_limiter和batch处理器最后由clickhouse导出器发出。创建一个 systemd 服务文件来管理 Collectorsudo tee /etc/systemd/system/otelcol-contrib.service EOF [Unit] DescriptionOpenTelemetry Collector Contrib Afternetwork.target [Service] Typesimple ExecStart/usr/local/bin/otelcol-contrib --config/etc/otelcol-contrib/config.yaml Restarton-failure Userotelcol Groupotelcol [Install] WantedBymulti-user.target EOF sudo useradd -rs /bin/false otelcol sudo chown -R otelcol:otelcol /etc/otelcol-contrib sudo systemctl daemon-reload sudo systemctl enable otelcol-contrib sudo systemctl start otelcol-contrib检查服务状态和日志确保 Collector 正常运行并且能连接到 ClickHousesudo systemctl status otelcol-contrib sudo journalctl -u otelcol-contrib -f4. 在 AI 应用代码中埋点与数据采集基础设施就绪后最关键的一步是在你的业务代码里埋点。这里以 Python 的 FastAPI 应用调用 OpenAI SDK 为例展示如何采集延迟和 Token 数。首先安装必要的 Python 包pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http opentelemetry-instrumentation-fastapi openai然后在你的应用初始化部分通常是main.py或app.py开头设置 OpenTelemetryfrom opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor import fastapi # 1. 设置 TracerProvider trace.set_tracer_provider(TracerProvider()) # 2. 创建 OTLP 导出器指向我们部署的 Collector otlp_exporter OTLPSpanExporter( endpointhttp://你的collector服务器IP:4318/v1/traces # 注意是4318端口HTTP协议 ) # 3. 创建批处理器并添加到 tracer provider span_processor BatchSpanProcessor(otlp_exporter) trace.get_tracer_provider().add_span_processor(span_processor) # 4. 获取一个 Tracer tracer trace.get_tracer(__name__) app fastapi.FastAPI() # 5. 自动注入 FastAPI 中间件捕获 HTTP 请求的追踪 FastAPIInstrumentor.instrument_app(app)接下来在调用 AI 模型的核心函数处进行手动埋点。我们不仅要记录耗时还要把 Token 数作为属性Attribute记录下来import openai from openai import OpenAI from opentelemetry import trace client OpenAI(api_keyyour-api-key) app.post(/v1/chat/completions) async def chat_completion(request: ChatRequest): # 为这个 LLM 调用创建一个独立的 Span with tracer.start_as_current_span(openai_chat_completion) as span: try: # 记录请求开始时间SDK会自动记录 # 设置一些通用属性 span.set_attribute(model, request.model) span.set_attribute(user, request.user_id) # 发起实际调用 response client.chat.completions.create( modelrequest.model, messagesrequest.messages, max_tokensrequest.max_tokens, temperaturerequest.temperature, ) # 调用成功从响应中提取 Token 数 completion_tokens response.usage.completion_tokens prompt_tokens response.usage.prompt_tokens total_tokens response.usage.total_tokens # 将 Token 数作为属性记录到当前 Span span.set_attribute(llm.usage.completion_tokens, completion_tokens) span.set_attribute(llm.usage.prompt_tokens, prompt_tokens) span.set_attribute(llm.usage.total_tokens, total_tokens) span.set_attribute(llm.response.id, response.id) span.set_status(trace.Status(trace.StatusCode.OK)) return {content: response.choices[0].message.content} except openai.APIError as e: # 调用失败记录错误状态和原因 span.set_status(trace.Status(trace.StatusCode.ERROR, str(e))) span.set_attribute(error.type, api_error) span.set_attribute(error.message, str(e)) raise HTTPException(status_code500, detailstr(e))关键点解析with tracer.start_as_current_span(...) 这创建了一个追踪跨度Span它的生命周期就是这个代码块的执行时间。SDK 会自动记录开始和结束时间并计算出duration。span.set_attribute() 这是埋点的精髓。我们把所有需要分析的维度都作为属性Key-Value附加到 Span 上。这些属性最终会进入 ClickHouse 表的attributesMap 字段。延迟 由 Span 的duration字段自动捕获无需手动计算。Token 消耗 我们从 API 响应中提取并通过set_attribute记录。状态 通过span.set_status()记录成功或失败这对于监控错误率至关重要。当这个接口被调用时一个完整的 Trace包含 HTTP 请求的 Span 和内部openai_chat_completion的 Span就会被生成并通过 OTLP 协议发送到 Collector最终存入 ClickHouse。5. 数据查询、可视化与核心监控指标数据存进去了怎么用我们通过 ClickHouse SQL 和 Grafana 来回答业务问题。5.1 核心监控指标与查询示例首先在 ClickHouse 客户端里我们可以直接写 SQL 分析。1. 查看最近1小时各模型的平均响应延迟和 P99 延迟延迟在表中是duration字段单位是纳秒。我们更关心毫秒。SELECT attributes[model] AS model, count() AS request_count, avg(duration / 1000000) AS avg_latency_ms, quantile(0.99)(duration / 1000000) AS p99_latency_ms FROM otel.traces WHERE spanName openai_chat_completion AND timestamp now() - INTERVAL 1 HOUR AND statusCode STATUS_CODE_OK GROUP BY model ORDER BY request_count DESC;2. 查看最近24小时Token 消耗总量 Top 10 的用户Token 数存在attributes里查询时需要从 Map 中提取并转换类型。SELECT attributes[user] AS user_id, sum(toInt64OrZero(attributes[llm.usage.total_tokens])) AS total_tokens_used FROM otel.traces WHERE spanName openai_chat_completion AND timestamp now() - INTERVAL 24 HOUR AND attributes[llm.usage.total_tokens] ! GROUP BY user_id ORDER BY total_tokens_used DESC LIMIT 10;3. 监控错误率SELECT attributes[model] AS model, count() AS total_requests, countIf(statusCode STATUS_CODE_ERROR) AS error_requests, (error_requests / total_requests) * 100 AS error_rate_percent FROM otel.traces WHERE spanName openai_chat_completion AND timestamp now() - INTERVAL 1 HOUR GROUP BY model HAVING total_requests 0;4. 下钻分析发现某个模型延迟很高想看看具体是哪些请求慢。SELECT traceId, spanId, timestamp, (duration / 1000000) as latency_ms, attributes[user] as user, attributes[llm.usage.prompt_tokens] as prompt_tokens, attributes[llm.usage.completion_tokens] as completion_tokens FROM otel.traces WHERE spanName openai_chat_completion AND attributes[model] gpt-4 AND timestamp now() - INTERVAL 10 MINUTE AND (duration / 1000000) 10000 -- 查找延迟大于10秒的请求 ORDER BY latency_ms DESC;5.2 配置 Grafana 仪表盘在 Grafana 中添加 ClickHouse 数据源后就可以基于上述 SQL 创建丰富的仪表盘。必备的监控面板全局概览Global Overview请求量QPScount() over time平均延迟 P95/P99 延迟avg(duration),quantile(0.95)(duration)总 Token 消耗速率sum(toInt64OrZero(attributes[‘llm.usage.total_tokens’])) over time错误率error_count / total_count模型维度分析Per-Model Breakdown用表格Table展示各模型的请求量、平均延迟、P99延迟、总Token消耗、平均每请求Token数。用时间序列图Time series对比不同模型的延迟趋势。用户维度分析Per-User AnalysisToken 消耗 Top N 用户排行榜。单个用户的请求成功率、平均延迟变化。问题排查面板Troubleshooting最近失败请求列表显示 TraceID、错误信息、用户和模型。高延迟请求列表例如延迟 5s。Grafana 查询示例在面板的 Query 编辑框中-- 用于时间序列图过去30分钟每2分钟聚合一次的平均延迟 SELECT $timeSeries as t, attributes[model] as metric, avg(duration / 1000000) as value FROM $table WHERE $timeFilter AND spanName openai_chat_completion AND statusCode STATUS_CODE_OK GROUP BY t, metric ORDER BY t, metric6. 生产环境注意事项与排查指南这套监控系统上线后要让它真正发挥作用还需要注意以下几点。6.1 性能与稳定性考量采样率Sampling 如果 QPS 极高例如每秒数万请求全量采集所有 Trace 会对 Collector 和 ClickHouse 造成巨大压力。OpenTelemetry SDK 支持头部采样Head-based Sampling。可以在 Collector 配置中添加probabilistic采样处理器只收集一定比例如10%的请求这对于监控宏观指标通常足够了。但对于错误请求建议设置tail采样或始终采样。processors: probabilistic_sampler: sampling_percentage: 10 # 10%的采样率批处理与超时 前面配置中使用了batch处理器它累积一定数量或等待一定时间后再发送能极大提升吞吐。但要合理设置send_batch_size和timeout避免在低流量时数据迟迟不发出。ClickHouse 优化分区与TTL 我们建表时使用了PARTITION BY toDate(timestamp)和TTL ... INTERVAL 30 DAY这能有效管理数据生命周期自动删除旧数据。索引 我们为traceId,serviceName,spanName创建了布隆过滤器索引为duration创建了 minmax 索引能加速常见的等值查询和范围查询。监控 ClickHouse 本身 使用system.metrics,system.query_log等表监控 ClickHouse 的 CPU、内存、磁盘使用率和查询性能。6.2 常见问题排查链路当监控仪表盘出现异常如延迟飙升、错误率增加时按以下顺序排查确认现象与范围是全局所有模型都变慢还是特定模型是特定用户还是所有用户错误是超时Timeout还是 API 返回错误如429限流、5xx服务器错误检查数据源Grafana 查询是否正常检查 Grafana 面板的 Query 语句和$timeFilter宏。ClickHouse 是否压力过大登录 ClickHouse执行SELECT * FROM system.metrics查看当前负载。执行SHOW PROCESSLIST看是否有慢查询阻塞。检查数据链路应用是否正常上报查看应用日志确认 OpenTelemetry SDK 没有报错如连接 Collector 失败。Collector 是否正常sudo journalctl -u otelcol-contrib --since 5 minutes ago查看 Collector 日志看是否有导出失败、队列满等错误。网络是否通畅从应用服务器telnet collector_ip 4318测试到 Collector 端口的连通性。从 Collector 服务器telnet clickhouse_ip 9000测试到 ClickHouse 的连通性。检查业务与依赖AI 服务提供商状态 检查 OpenAI、Azure 等服务商的状态页面。自身业务逻辑 是否发布了新代码导致提示词Prompt变长是否引入了新的上下文管理逻辑导致请求体巨大是否突然有用户上传了极大的文件进行解析资源瓶颈 如果调用的是自建模型检查 GPU 利用率、显存、网络 I/O。下钻到具体请求在 Grafana 的表格中找到高延迟或失败的请求复制其traceId。在类似 Jaeger 的追踪系统如果你也配置了中查看该 Trace 的详细瀑布图看时间具体耗在哪个环节网络、序列化、模型推理、结果流式传输。或者在 ClickHouse 中查询该 Trace 的所有 SpansSELECT * FROM otel.traces WHERE traceId ‘你的traceId’ ORDER BY timestamp;6.3 监控的边界与成本监控本身有开销 埋点代码会增加少量 CPU 和内存开销网络传输和存储数据也会消耗资源。需要评估并做好容量规划。不是所有东西都要监控 初期聚焦核心指标延迟、Token 消耗、错误率、请求量。后续再根据需要增加业务指标如会话长度、功能调用次数等。设置合理的告警 不要只盯着平均值。P95/P99 延迟、错误率突然增长如5分钟内1%是更敏感的告警指标。Token 消耗可以设置每日/每周预算告警。数据驱动决策 监控的最终目的是行动。比如发现某个模型的 P99 延迟持续高于 SLA就应该考虑优化提示词、升级模型版本或调整超时设置。发现某个用户 Token 消耗异常可以触发人工审核流程。这套基于 OpenTelemetry ClickHouse 的方案将 AI 应用的运行状态从“感觉”变成了“数据”。它不仅能帮你快速灭火更能让你深入理解自己的应用负载和用户行为为容量规划、成本优化和产品迭代提供坚实的数据基础。

相关新闻

最新新闻

第14章:FastAPI 接口文档与前后端协作

第14章:FastAPI 接口文档与前后端协作

1. 项目背景 业务场景 "支付平台"的后端接口已经开发了 30 多个。前后端协作中出现了严重的沟通问题: 前端小刘问:“订单列表接口的 total 是 int 还是 string?上周还是 "10",这周变成 10 了——我的 type…

2026/8/25 6:54:14
机载电源模块真空甲酸炉关键技术与应用实践

机载电源模块真空甲酸炉关键技术与应用实践

半导体封装的秘密武器——真空共晶炉的有趣事实 大家好!今天要聊的,是那些在半导体封装领域中被工程师们称为“秘密武器”的设备——真空共晶炉。🛠️ 可能你会问,封装?封装有啥好聊的?🤔 要知道…

2026/8/25 6:54:14
基于MCP协议构建AI Agent工作流,打通M365 Copilot与Power Apps业务数据

基于MCP协议构建AI Agent工作流,打通M365 Copilot与Power Apps业务数据

如果你正在使用 M365 Copilot 处理日常办公任务,却常常遇到一个瓶颈:Copilot 能帮你写邮件、做总结,但当你需要它基于公司内部业务数据(比如销售订单、客户反馈、项目进度)生成报告或分析时,它却“一问三不…

2026/8/25 6:54:14
AI时代的三大新习惯的学习总结

AI时代的三大新习惯的学习总结

最近研读了《人工智能时代的三大新习惯》原文连接,产品设计师 Xinran Ma 在辞去企业工作、独立创业后写下的自我反思。作者从哥伦比亚大学建筑学转行产品设计,因工作签证限制整整等了六年才得以全职创业;拿到绿卡后,她通过出版书籍…

2026/8/25 6:54:14
Python 魔术方法

Python 魔术方法

Python 魔术方法(Magic Method / 双下划线方法 __xxx__)特征:前后双下划线 __名字__,又叫特殊方法。 你不要手动直接调用它,Python解释器会在特定语法、内置操作触发时自动调用。举个直观例子: obj obj2 …

2026/8/25 6:54:14
SpringCloud---Gateway

SpringCloud---Gateway

(一).网关介绍1.前置问题当前,我们的生产是介绍openfeign的环境。上图分别是product-service和order-service中的方法。这就有问题,当前所有微服务的接口都是直接对外暴露的,可以直接通过外部访问。为了保证对外服务的安全性,服务…

2026/8/25 6:49:13