你的AI应用响应慢、费用高,问题可能出在你看不见的地方。
最近在跟几个做AI应用的朋友聊天,发现一个普遍现象:项目上线初期一切顺利,但随着用户量增长,两个问题开始浮出水面。一是用户抱怨“AI反应变慢了”,二是月底一看账单,API调用费用远超预期。大家的第一反应往往是“模型不行了”或者“该升级服务器了”,但一通操作下来,问题依旧。
这背后,很可能是因为你缺少一套“显微镜”——一套能看清AI应用内部每一次请求的延迟和Token消耗的监控系统。没有数据,优化就无从谈起。今天,我们就来彻底解密如何搭建这样一套系统。它不只是一个技术选型,更是一种工程思维的转变:从“感觉慢了”到“知道哪里慢了,为什么慢,花了多少钱”。
本文将带你从零构建一个基于 OpenTelemetry 和 ClickHouse 的AI应用监控方案。读完本文,你将能:
- 清晰理解AI应用延迟与Token消耗的监控核心指标。
- 掌握使用OpenTelemetry进行应用埋点与数据收集的完整流程。
- 学会部署和配置ClickHouse,将其作为高性能的监控数据存储与分析引擎。
- 获得一套可立即复用的代码示例与配置,快速搭建自己的监控看板。
1. 为什么你的AI应用需要专属监控?
传统的应用监控(如监控CPU、内存、请求QPS)对AI应用来说,就像用体温计量血压——指标不对,诊断不准。AI应用的性能与成本核心,紧密围绕两个独特维度展开。
第一维度:响应延迟。这不仅仅是“服务器处理时间”。一次AI API调用(例如调用OpenAI的ChatCompletion)的延迟,由网络延迟、模型加载、Token生成速度(Time to First Token, TTFT)、输出流式传输等多个环节构成。用户感知的“卡顿”,可能发生在任何一个环节。没有细分数据,你无法定位是网络问题、模型服务商波动,还是自身代码的序列化/反序列化成了瓶颈。
第二维度:Token消耗。Token是AI世界的“硬通货”,直接关联成本。一次对话消耗了多少Token?提示词(Prompt)和补全(Completion)各占多少?不同用户、不同功能模块的Token消耗模式是怎样的?是否存在提示词设计不当导致的Token浪费?这些问题的答案,是成本控制和优化提示词工程的基础。
没有针对这两个维度的监控,你的优化将是盲目的。你可能会为“感觉慢”而升级更贵的模型,却发现延迟并未改善;你可能会因为账单激增而仓促限流,却误伤了正常用户。构建专属监控,就是将“黑盒”变为“白盒”,让每一次AI交互的成本与性能都变得透明、可分析、可优化。
2. 核心概念与架构选型
在动手之前,我们需要统一几个关键概念,并解释为什么选择OpenTelemetry + ClickHouse这套组合拳。
2.1 核心监控指标详解
- 端到端延迟(End-to-End Latency):从用户发起请求到收到完整响应的总时间。这是用户体验的直接体现。
- 首Token时间(Time to First Token, TTFT):从发送请求到收到第一个输出Token的时间。对于流式响应应用,TTFT至关重要,它决定了用户感知的“响应速度”。
- Token生成速率(Tokens per Second):模型每秒输出Token的数量。这反映了模型本身的推理性能。
- Token计数(Token Usage):
- 提示Token(Prompt Tokens):输入给模型的Token数量,对应你的提示词(Prompt)。
- 补全Token(Completion Tokens):模型输出的Token数量。
- 总Token(Total Tokens):两者之和,是计费的直接依据。
- 每秒请求数(Requests Per Second, RPS):衡量应用负载。
- 错误率(Error Rate):API调用失败(如超时、限流、模型过载)的比例。
2.2 为什么是 OpenTelemetry + ClickHouse?
这是一个兼顾标准化、高性能和灵活性的黄金组合。
OpenTelemetry (OTel)是云原生基金会(CNCF)下的开源项目,旨在提供一套与供应商无关的遥测数据(指标、链路、日志)收集标准。对于AI应用监控,它的价值在于:
- 标准化:提供统一的API和SDK(支持Python, Java, Go, JS等),埋点代码一次编写,后端可随意更换。
- 上下文传播:能自动将一次请求的链路信息(Trace)在各个服务间传递,轻松实现跨服务、跨AI API调用的全链路追踪。
- 生态丰富:拥有成熟的收集器(Collector),可以接收、处理、导出数据到各种后端(如ClickHouse, Prometheus, Jaeger)。
ClickHouse是一个开源的列式OLAP数据库,以其惊人的查询速度著称。在监控场景下,它的优势无可替代:
- 吞吐量极高:轻松应对每秒数十万甚至百万级别的监控数据写入。
- 查询极快:对于按时间范围、标签聚合查询(这正是监控看板的需求)效率极高。
- 成本较低:相比传统的时序数据库(如InfluxDB)或云服务,在自托管场景下硬件成本更具优势,且压缩比高。
- SQL接口:使用熟悉的SQL进行数据分析,学习成本和灵活性都很好。
架构流程图:
[你的AI应用] --(通过OTel SDK埋点)--> [OTel Collector] --(导出数据)--> [ClickHouse] | V [Grafana / 自研看板] --(SQL查询)--> [ClickHouse]你的应用代码通过OTel SDK记录指标和链路,发送给OTel Collector。Collector进行必要的处理(如批处理、过滤)后,将数据写入ClickHouse。最后,通过Grafana或自研看板用SQL从ClickHouse查询数据并可视化。
3. 环境准备与组件安装
我们将在一个Linux环境中(Ubuntu 22.04为例)完成所有组件的部署。请确保你拥有一个至少4核8GB内存的服务器或虚拟机。
3.1 安装 Docker 与 Docker Compose
我们将使用Docker容器化部署,保证环境一致性。
# 更新包索引并安装必要工具 sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg lsb-release # 添加Docker官方GPG密钥 sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg # 设置Docker稳定版仓库 echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 安装Docker引擎 sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin # 验证安装 sudo docker run hello-world3.2 部署 ClickHouse
我们使用官方Docker镜像部署一个单节点ClickHouse,并创建监控所需的数据库和表。
首先,创建一个工作目录并编写docker-compose.yml:
# 文件:~/ai-monitor/docker-compose.yml version: '3.8' services: clickhouse: image: clickhouse/clickhouse-server:latest container_name: clickhouse-server ports: - "8123:8123" # HTTP API端口 - "9000:9000" # 原生TCP客户端端口 volumes: - ./clickhouse_data:/var/lib/clickhouse # 数据持久化 - ./clickhouse_config.xml:/etc/clickhouse-server/config.d/custom.xml:ro # 自定义配置 - ./clickhouse_users.xml:/etc/clickhouse-server/users.d/custom-users.xml:ro # 用户配置 ulimits: nproc: 65535 nofile: soft: 262144 hard: 262144 networks: - monitor-net networks: monitor-net: driver: bridge接着,创建自定义配置文件以优化监控数据写入和查询:
<!-- 文件:~/ai-monitor/clickhouse_config.xml --> <yandex> <logger> <level>information</level> <console>1</console> </logger> <!-- 允许接收来自Collector的HTTP插入 --> <http_server_default_response><![CDATA[<html><body>Ok</body></html>]]></http_server_default_response> </yandex><!-- 文件:~/ai-monitor/clickhouse_users.xml --> <yandex> <users> <default> <password></password> <!-- 生产环境务必设置强密码 --> <networks> <ip>::/0</ip> </networks> <profile>default</profile> <quota>default</quota> </default> </users> </yandex>现在,启动ClickHouse并进入容器创建表结构:
cd ~/ai-monitor sudo docker-compose up -d clickhouse # 等待几秒后,进入ClickHouse客户端 sudo docker exec -it clickhouse-server clickhouse-client在ClickHouse客户端中,执行以下SQL:
-- 创建数据库 CREATE DATABASE IF NOT EXISTS otel; -- 切换数据库 USE otel; -- 创建存储链路(Trace)数据的表 CREATE TABLE IF NOT EXISTS 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)), `traceState` String CODEC(ZSTD(1)), `spanName` LowCardinality(String) CODEC(ZSTD(1)), `spanKind` LowCardinality(String) CODEC(ZSTD(1)), `serviceName` LowCardinality(String) CODEC(ZSTD(1)), `resourceAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)), `spanAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)), `duration` Int64 CODEC(ZSTD(1)), `statusCode` LowCardinality(String) CODEC(ZSTD(1)), `statusMessage` String CODEC(ZSTD(1)), `events` Array(String) CODEC(ZSTD(1)), `links` Array(String) CODEC(ZSTD(1)), INDEX idx_trace_id traceId TYPE bloom_filter GRANULARITY 1, INDEX idx_span_name spanName TYPE bloom_filter GRANULARITY 1, INDEX idx_service_name serviceName TYPE bloom_filter GRANULARITY 1, INDEX idx_resource_attr_key mapKeys(resourceAttributes) TYPE bloom_filter GRANULARITY 1, INDEX idx_resource_attr_value mapValues(resourceAttributes) TYPE bloom_filter GRANULARITY 1, INDEX idx_span_attr_key mapKeys(spanAttributes) TYPE bloom_filter GRANULARITY 1, INDEX idx_span_attr_value mapValues(spanAttributes) TYPE bloom_filter GRANULARITY 1 ) ENGINE = MergeTree PARTITION BY toDate(timestamp) ORDER BY (serviceName, spanName, timestamp) TTL toDateTime(timestamp) + INTERVAL 30 DAY SETTINGS index_granularity = 8192; -- 创建存储指标(Metrics)数据的表(以直方图为例) CREATE TABLE IF NOT EXISTS otel_metrics_histogram ( `timestamp` DateTime64(9) CODEC(Delta(8), ZSTD(1)), `metric_name` LowCardinality(String) CODEC(ZSTD(1)), `attributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)), `sum` Float64 CODEC(ZSTD(1)), `count` UInt64 CODEC(ZSTD(1)), `min` Float64 CODEC(ZSTD(1)), `max` Float64 CODEC(ZSTD(1)), `bucket_counts` Array(UInt64) CODEC(ZSTD(1)), `explicit_bounds` Array(Float64) CODEC(ZSTD(1)), INDEX idx_metric_name metric_name TYPE bloom_filter GRANULARITY 1, INDEX idx_attr_key mapKeys(attributes) TYPE bloom_filter GRANULARITY 1, INDEX idx_attr_value mapValues(attributes) TYPE bloom_filter GRANULARITY 1 ) ENGINE = MergeTree PARTITION BY toDate(timestamp) ORDER BY (metric_name, timestamp) TTL toDateTime(timestamp) + INTERVAL 30 DAY SETTINGS index_granularity = 8192;输入exit退出客户端。表结构已就绪,ClickHouse正在监听8123端口等待数据。
3.3 部署 OpenTelemetry Collector
Collector负责接收、处理并转发遥测数据。我们使用OTel官方提供的Contrib发行版,它包含了众多有用的处理器和导出器。
创建Collector的配置文件otel-collector-config.yaml:
# 文件:~/ai-monitor/otel-collector-config.yaml receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 processors: batch: timeout: 5s send_batch_size: 10000 # 添加一个属性处理器,例如为所有数据添加环境标签 attributes: actions: - key: deployment.environment value: production action: upsert exporters: debug: verbosity: detailed clickhouse: endpoint: tcp://clickhouse:9000?database=otel username: default password: "" ttl_days: 30 timeout: 5s logs_table_name: otel_logs traces_table_name: otel_traces metrics_table_name: otel_metrics_histogram # 生产环境建议配置retry_on_failure和queue_settings service: pipelines: traces: receivers: [otlp] processors: [batch, attributes] exporters: [debug, clickhouse] metrics: receivers: [otlp] processors: [batch, attributes] exporters: [debug, clickhouse] logs: receivers: [otlp] processors: [batch, attributes] exporters: [debug, clickhouse]更新docker-compose.yml,添加Collector服务:
# 在services部分添加 otel-collector: image: otel/opentelemetry-collector-contrib:latest container_name: otel-collector command: ["--config=/etc/otel-collector-config.yaml"] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml ports: - "4317:4317" # OTLP gRPC端口 - "4318:4318" # OTLP HTTP端口 depends_on: - clickhouse networks: - monitor-net启动Collector:
sudo docker-compose up -d otel-collector至此,后端存储(ClickHouse)和数据管道(OTel Collector)都已就绪。接下来,我们将在AI应用中进行埋点。
4. 在Python AI应用中集成OpenTelemetry埋点
我们以一个使用OpenAI API的FastAPI应用为例,演示如何埋点监控延迟和Token消耗。
4.1 创建Python虚拟环境与安装依赖
mkdir ~/ai-app && cd ~/ai-app python3 -m venv venv source venv/bin/activate pip install fastapi uvicorn openai opentelemetry-api opentelemetry-sdk opentelemetry-instrumentation-fastapi opentelemetry-instrumentation-openai opentelemetry-exporter-otlp4.2 编写带OTel埋点的FastAPI应用
创建应用主文件app.py:
# 文件:~/ai-app/app.py import os import time from typing import Optional from fastapi import FastAPI, HTTPException from pydantic import BaseModel import openai from openai import OpenAI # 1. 导入OpenTelemetry核心模块 from opentelemetry import trace, metrics from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor from opentelemetry.instrumentation.openai import OpenAIInstrumentor # 2. 初始化OpenTelemetry SDK # 设置TracerProvider trace.set_tracer_provider(TracerProvider()) tracer_provider = trace.get_tracer_provider() # 创建OTLP导出器,指向Collector的HTTP端点 otlp_trace_exporter = OTLPSpanExporter(endpoint="http://localhost:4318/v1/traces") # 将导出器添加到Span处理器 span_processor = BatchSpanProcessor(otlp_trace_exporter) tracer_provider.add_span_processor(span_processor) # 设置MeterProvider meter_provider = MeterProvider() metrics.set_meter_provider(meter_provider) # 创建OTLP指标导出器 otlp_metric_exporter = OTLPMetricExporter(endpoint="http://localhost:4318/v1/metrics") # 创建指标读取器并关联导出器 metric_reader = PeriodicExportingMetricReader(otlp_metric_exporter, export_interval_millis=5000) meter_provider.add_metric_reader(metric_reader) # 获取本应用的Tracer和Meter tracer = trace.get_tracer(__name__) meter = metrics.get_meter(__name__) # 3. 创建自定义指标 # 创建一个直方图来记录请求延迟 request_latency_histogram = meter.create_histogram( name="ai.request.latency", description="Latency of AI API requests in seconds", unit="s", ) # 创建一个计数器来记录Token消耗 prompt_tokens_counter = meter.create_counter( name="ai.tokens.prompt", description="Total number of prompt tokens consumed", unit="1", ) completion_tokens_counter = meter.create_counter( name="ai.tokens.completion", description="Total number of completion tokens consumed", unit="1", ) total_tokens_counter = meter.create_counter( name="ai.tokens.total", description="Total number of tokens consumed", unit="1", ) # 4. 初始化FastAPI和OpenAI客户端 app = FastAPI(title="AI Chat API") client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) # 5. 自动仪表化FastAPI和OpenAI库 FastAPIInstrumentor.instrument_app(app) OpenAIInstrumentor.instrument() # 6. 定义数据模型 class ChatRequest(BaseModel): message: str model: Optional[str] = "gpt-3.5-turbo" max_tokens: Optional[int] = 500 class ChatResponse(BaseModel): reply: str model: str usage: dict latency: float # 7. 核心API端点,包含手动埋点 @app.post("/chat", response_model=ChatResponse) async def chat_with_ai(request: ChatRequest): start_time = time.time() # 使用Tracer创建一个自定义的Span,更精细地追踪这个业务逻辑 with tracer.start_as_current_span("chat_completion") as span: span.set_attribute("ai.model", request.model) span.set_attribute("user.message.length", len(request.message)) try: # 调用OpenAI API response = client.chat.completions.create( model=request.model, messages=[{"role": "user", "content": request.message}], max_tokens=request.max_tokens, stream=False, # 为简化示例,关闭流式 ) # 计算延迟 end_time = time.time() latency = end_time - start_time # 记录延迟到直方图指标(附带属性标签) request_latency_histogram.record(latency, { "ai.model": request.model, "status": "success" }) # 获取Token用量并记录到计数器指标 usage = response.usage prompt_tokens = usage.prompt_tokens completion_tokens = usage.completion_tokens total_tokens = usage.total_tokens prompt_tokens_counter.add(prompt_tokens, {"ai.model": request.model}) completion_tokens_counter.add(completion_tokens, {"ai.model": request.model}) total_tokens_counter.add(total_tokens, {"ai.model": request.model}) # 也将关键信息记录到Span属性中,便于链路追踪查看 span.set_attribute("ai.request.latency", latency) span.set_attribute("ai.tokens.prompt", prompt_tokens) span.set_attribute("ai.tokens.completion", completion_tokens) span.set_attribute("ai.tokens.total", total_tokens) return ChatResponse( reply=response.choices[0].message.content, model=request.model, usage={ "prompt_tokens": prompt_tokens, "completion_tokens": completion_tokens, "total_tokens": total_tokens, }, latency=latency, ) except openai.APIError as e: end_time = time.time() latency = end_time - start_time # 记录失败的请求延迟 request_latency_histogram.record(latency, { "ai.model": request.model, "status": "error", "error.type": e.__class__.__name__ }) span.record_exception(e) span.set_status(trace.Status(trace.StatusCode.ERROR, str(e))) raise HTTPException(status_code=500, detail=f"AI API error: {e}") if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)4.3 关键代码解析
- SDK初始化:我们同时初始化了Trace和Metrics的Provider,并配置它们通过OTLP HTTP协议将数据发送到本地的Collector(
localhost:4318)。 - 自动仪表化:
FastAPIInstrumentor和OpenAIInstrumentor会自动为框架和客户端库的特定操作创建Span,极大减少了手动埋点工作量。例如,它会自动追踪HTTP请求和OpenAI API调用。 - 手动埋点增强:在
/chat端点中,我们手动创建了一个chat_completionSpan,并记录了业务相关的属性(如模型名、消息长度)。这让我们能在链路中清晰看到这个业务操作的耗时。 - 自定义指标:我们创建了四个指标:
ai.request.latency(Histogram): 记录每次请求的延迟分布,便于计算P50, P90, P99等分位数。ai.tokens.prompt/completion/total(Counter): 记录各类Token的消耗总量,这些计数器只会增加,适合统计总用量和计算速率。
- 属性(Attributes):在记录指标时,我们添加了
ai.model和status等属性。这是监控的“黄金标签”,后续我们可以按模型、按成功/失败状态来聚合和分析数据。
5. 运行与数据验证
5.1 启动应用并发送测试请求
首先,设置你的OpenAI API密钥(请替换为你的真实密钥):
export OPENAI_API_KEY='sk-your-openai-api-key-here'在虚拟环境中启动FastAPI应用:
cd ~/ai-app source venv/bin/activate python app.py应用将在http://localhost:8000启动。
打开另一个终端,使用curl或httpie发送测试请求:
# 使用curl curl -X POST "http://localhost:8000/chat" \ -H "Content-Type: application/json" \ -d '{"message": "请用中文简要介绍Python编程语言", "model": "gpt-3.5-turbo"}' # 或者使用httpie (更简洁) http POST http://localhost:8000/chat message="请用中文简要介绍Python编程语言" model="gpt-3.5-turbo"你应该会收到一个包含AI回复、使用情况和延迟的JSON响应。
5.2 验证数据是否进入ClickHouse
多次发送不同模型或消息的请求以生成一些数据。然后,连接到ClickHouse查询数据。
查询链路(Trace)数据:
sudo docker exec -it clickhouse-server clickhouse-client --database otel-- 查看最近的链路记录 SELECT timestamp, spanName, serviceName, spanAttributes['ai.model'] AS model, spanAttributes['ai.request.latency'] AS latency, spanAttributes['ai.tokens.total'] AS total_tokens, duration/1e9 as duration_seconds -- duration单位是纳秒,转换为秒 FROM otel_traces WHERE spanName = 'chat_completion' ORDER BY timestamp DESC LIMIT 5;这条SQL会显示你刚刚的请求,包含模型、延迟、Token数等信息。
查询指标(Metrics)数据:
-- 查看Token消耗计数器的最新值(按模型聚合) SELECT attributes['ai.model'] AS model, sum(sum) AS total_prompt_tokens FROM otel_metrics_histogram WHERE metric_name = 'ai.tokens.prompt' GROUP BY attributes['ai.model']; -- 查看请求延迟的分布情况(例如,查看gpt-3.5-turbo的延迟) SELECT quantile(0.5)(sum) as p50_latency, quantile(0.9)(sum) as p90_latency, quantile(0.99)(sum) as p99_latency, max(max) as max_latency, min(min) as min_latency FROM otel_metrics_histogram WHERE metric_name = 'ai.request.latency' AND attributes['ai.model'] = 'gpt-3.5-turbo' AND attributes['status'] = 'success';如果查询能返回数据,恭喜你!监控数据管道已经完全打通,从应用埋点、Collector收集到ClickHouse存储,整个流程运行正常。
6. 数据可视化与监控看板
原始数据需要可视化才能发挥价值。这里我们使用Grafana连接ClickHouse来创建监控看板。
6.1 部署Grafana
在docker-compose.yml中添加Grafana服务:
grafana: image: grafana/grafana:latest container_name: grafana ports: - "3000:3000" volumes: - ./grafana_data:/var/lib/grafana environment: - GF_SECURITY_ADMIN_PASSWORD=admin123 # 生产环境请修改! depends_on: - clickhouse networks: - monitor-net启动Grafana:
sudo docker-compose up -d grafana访问http://你的服务器IP:3000,使用admin/admin123登录。
6.2 配置ClickHouse数据源
- 在Grafana左侧菜单,进入Configuration > Data Sources。
- 点击Add data source,选择ClickHouse。
- 配置如下:
- Name:
ClickHouse-OTel - HTTP
- URL:
http://clickhouse:8123(注意:在Docker网络内使用服务名clickhouse)
- URL:
- Auth:取消选中
Basic auth(因为我们未设置密码)。 - ClickHouse Details
- Database:
otel - Username:
default - Password:(留空)
- Database:
- Name:
- 点击Save & test,应显示“Data source is working”。
6.3 创建监控仪表盘
现在,我们可以创建几个核心监控面板。
面板1:AI请求延迟趋势(分模型)
- 点击Create > Dashboard > Add new panel。
- 在查询编辑器中选择数据源
ClickHouse-OTel,输入SQL:
SELECT $timeSeries as t, attributes['ai.model'] as metric, avg(sum) as value FROM $table WHERE $timeFilter AND metric_name = 'ai.request.latency' AND attributes['status'] = 'success' GROUP BY metric, t ORDER BY t, metric- 设置:
- Table:
otel_metrics_histogram - Time field:
timestamp - Metric column:
value - Group by:
metric
- Table:
- 在右侧Panel options设置Title为
AI请求平均延迟(按模型),Visualization选择Time series。
面板2:Token消耗速率(堆叠图)
- 添加新面板。
- SQL查询:
SELECT $timeSeries as t, '提示Token' as metric, sum(sum) as value FROM $table WHERE $timeFilter AND metric_name = 'ai.tokens.prompt' GROUP BY t UNION ALL SELECT $timeSeries as t, '补全Token' as metric, sum(sum) as value FROM $table WHERE $timeFilter AND metric_name = 'ai.tokens.completion' GROUP BY t ORDER BY t, metric- 设置同上,Title设为
Token消耗速率,Visualization选择Time series,并在Legend设置中勾选As table和To the right。
面板3:请求状态分布(饼图)
- 添加新面板。
- SQL查询(使用
$__interval获取最近一段时间):
SELECT attributes['status'] as status, count(*) as count FROM otel_metrics_histogram WHERE timestamp >= NOW() - INTERVAL 1 HOUR AND metric_name = 'ai.request.latency' GROUP BY attributes['status']- Title设为
最近1小时请求状态分布,Visualization选择Pie chart。
面板4:慢请求追踪列表
- 添加新面板,Visualization选择
Table。 - SQL查询:
SELECT traceId, spanAttributes['ai.model'] as model, spanAttributes['ai.request.latency'] as latency, spanAttributes['ai.tokens.total'] as tokens, toDateTime(timestamp) as time FROM otel_traces WHERE spanName = 'chat_completion' AND spanAttributes['ai.request.latency'] > 5.0 -- 查找延迟大于5秒的慢请求 ORDER BY timestamp DESC LIMIT 20将这些面板排列在你的仪表盘上,你就拥有了一个实时监控AI应用性能与成本的核心看板。你可以看到哪个模型响应最快,Token消耗的趋势如何,以及是否有异常慢的请求需要排查。
7. 常见问题与排查思路
在搭建和使用过程中,你可能会遇到以下问题:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 应用启动报错,找不到OpenTelemetry模块 | 依赖未正确安装 | 检查pip list | grep opentelemetry | 在虚拟环境中重新安装opentelemetry-*系列包 |
| 发送请求后,ClickHouse中查不到数据 | 1. Collector配置错误 2. 网络不通 3. 应用未发送数据 | 1. 检查Collector日志docker logs otel-collector2. 检查应用日志是否有OTel错误 3. 在Collector配置中启用 debugexporter查看 | 1. 确认Collector的endpoint指向正确的ClickHouse地址和端口(tcp://clickhouse:9000)2. 确保应用、Collector、ClickHouse在同一个Docker网络 |
| Grafana无法连接ClickHouse | 1. Grafana数据源配置错误 2. ClickHouse未启动或端口不对 | 1. 在Grafana数据源页面点击Save & test看错误信息 2. 运行 docker ps确认ClickHouse容器状态 | 1. 确认URL为http://clickhouse:8123(容器内)或http://localhost:8123(宿主机)2. 确认ClickHouse的 users.xml允许无密码访问(仅测试环境) |
| 监控数据延迟很高(>1分钟) | 1. OTel SDK或Collector批处理间隔太长 2. ClickHouse写入压力大 | 1. 检查SDK中PeriodicExportingMetricReader的export_interval_millis2. 检查Collector配置中 batch处理器的timeout | 1. 调小导出间隔(如改为1000ms),但会增加网络开销 2. 观察ClickHouse的 system.metrics表 |
| Token计数不准或为0 | 1. OpenAI响应中未包含usage字段(某些模型或错误时)2. 埋点代码逻辑错误 | 1. 打印OpenAI API的完整响应,检查usage对象2. 在代码中增加日志,确认计数器是否被调用 | 1. 在代码中添加对response.usage是否为None的判断2. 确保在API调用成功后才记录计数器 |
| 查询性能慢,Grafana图表加载卡顿 | 1. ClickHouse表缺少合适索引 2. 查询时间范围太大 3. 数据量过大 | 1. 使用EXPLAIN分析查询计划2. 检查表的分区(PARTITION BY)和排序键(ORDER BY) | 1. 为高频过滤条件(如metric_name,attributes['ai.model'])添加跳数索引(INDEX)2. 强制在查询中指定较小的时间范围 3. 考虑使用物化视图或聚合表预计算常用指标 |
8. 生产环境最佳实践与进阶建议
将这套监控方案用于生产环境,还需要考虑更多因素。
1. 安全与权限
- ClickHouse:生产环境必须设置强密码,并创建专属的、权限受限的用户用于Grafana和Collector。禁用默认用户的远程访问。
- Grafana:修改默认管理员密码,配置合适的组织、用户和文件夹权限。考虑启用HTTPS。
- OTel Collector:考虑使用TLS/mTLS对接收和导出的数据进行加密。对暴露的端口(4317, 4318)进行网络ACL限制。
2. 可扩展性与高可用
- ClickHouse集群:对于海量数据,部署ClickHouse集群(分片与副本),使用
Distributed表引擎。ZooKeeper或ClickHouse Keeper用于元数据同步。 - OTel Collector负载均衡:在多实例应用前部署负载均衡器,或将Collector本身以多副本方式部署,并通过服务发现(如Consul)进行注册。
- 数据采样:在流量极高的场景下,全量采集所有链路可能开销过大。可以在OTel Collector或SDK中配置基于头部或尾部的采样策略(如每秒采集N条,或只采集慢请求)。
3. 监控与告警
- 监控监控系统本身:为ClickHouse、OTel Collector、Grafana本身也配置基础监控(资源使用率、进程状态)。
- 设置智能告警:在Grafana中针对关键指标设置告警规则。
- 延迟告警:当P99延迟连续5分钟超过阈值(如10秒)时触发。
- 错误率告警:当请求错误率超过5%时触发。
- 成本告警:当每小时Token消耗速率异常激增(超过历史平均值的2倍)时触发,这可能是提示词漏洞或遭受攻击的迹象。
- 告警渠道:将告警通知集成到Teams、Slack、钉钉、邮件或PagerDuty等。
4. 数据治理与成本优化
- 数据生命周期(TTL):在ClickHouse建表时已设置
TTL ... + INTERVAL 30 DAY,根据合规和存储成本需求调整保留期限。 - 降精度存储:对于历史久远的数据(如超过7天),可以将其聚合为更低精度(如每分钟一个点)后转移到另一张表,释放存储空间。
- 列式存储优化:根据查询模式,仔细设计
ORDER BY键和索引,这对ClickHouse性能至关重要。
5. 进阶监控场景
- 流式响应(Streaming)监控:对于流式AI响应,需要监控“首Token时间(TTFT)”和“Token流吞吐量”。这需要在收到第一个Chunk和最后一个Chunk时打点记录时间。
- 多模型/多供应商对比:如果你的应用接入了多个AI供应商(如OpenAI、Anthropic、国内大模型),可以在属性中添加
vendor标签,方便对比各家的性能、成本和质量。 - 用户/租户粒度分析:在Span属性或指标属性中加入
user_id或tenant_id,可以分析不同用户群体的使用模式和成本,为精细化运营和计费提供依据。 - 与业务指标关联:将AI调用监控与业务指标(如订单转化率、用户满意度评分)关联分析,评估AI能力对业务的实际影响。
构建AI应用的监控体系,是一个从“不可知”到“可知”,从“被动救火”到“主动优化”的过程。本文提供的OpenTelemetry + ClickHouse方案,为你提供了一个强大、灵活且面向未来的起点。它不仅能帮你立刻看清延迟与Token消耗,其标准化和可扩展的设计,更能伴随你的业务一起成长,应对未来更复杂的监控需求。