基于OpenTelemetry与ClickHouse构建AI应用性能与成本监控系统
2026/8/25 18:37:37 网站建设 项目流程

你的AI应用响应慢、费用高,问题可能出在你看不见的地方。

最近在跟几个做AI应用的朋友聊天,发现一个普遍现象:项目上线初期一切顺利,但随着用户量增长,两个问题开始浮出水面。一是用户抱怨“AI反应变慢了”,二是月底一看账单,API调用费用远超预期。大家的第一反应往往是“模型不行了”或者“该升级服务器了”,但一通操作下来,问题依旧。

这背后,很可能是因为你缺少一套“显微镜”——一套能看清AI应用内部每一次请求的延迟和Token消耗的监控系统。没有数据,优化就无从谈起。今天,我们就来彻底解密如何搭建这样一套系统。它不只是一个技术选型,更是一种工程思维的转变:从“感觉慢了”到“知道哪里慢了,为什么慢,花了多少钱”。

本文将带你从零构建一个基于 OpenTelemetry 和 ClickHouse 的AI应用监控方案。读完本文,你将能:

  1. 清晰理解AI应用延迟与Token消耗的监控核心指标。
  2. 掌握使用OpenTelemetry进行应用埋点与数据收集的完整流程。
  3. 学会部署和配置ClickHouse,将其作为高性能的监控数据存储与分析引擎。
  4. 获得一套可立即复用的代码示例与配置,快速搭建自己的监控看板。

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应用监控,它的价值在于:

  1. 标准化:提供统一的API和SDK(支持Python, Java, Go, JS等),埋点代码一次编写,后端可随意更换。
  2. 上下文传播:能自动将一次请求的链路信息(Trace)在各个服务间传递,轻松实现跨服务、跨AI API调用的全链路追踪。
  3. 生态丰富:拥有成熟的收集器(Collector),可以接收、处理、导出数据到各种后端(如ClickHouse, Prometheus, Jaeger)。

ClickHouse是一个开源的列式OLAP数据库,以其惊人的查询速度著称。在监控场景下,它的优势无可替代:

  1. 吞吐量极高:轻松应对每秒数十万甚至百万级别的监控数据写入。
  2. 查询极快:对于按时间范围、标签聚合查询(这正是监控看板的需求)效率极高。
  3. 成本较低:相比传统的时序数据库(如InfluxDB)或云服务,在自托管场景下硬件成本更具优势,且压缩比高。
  4. 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-world

3.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-otlp

4.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 关键代码解析

  1. SDK初始化:我们同时初始化了Trace和Metrics的Provider,并配置它们通过OTLP HTTP协议将数据发送到本地的Collector(localhost:4318)。
  2. 自动仪表化FastAPIInstrumentorOpenAIInstrumentor会自动为框架和客户端库的特定操作创建Span,极大减少了手动埋点工作量。例如,它会自动追踪HTTP请求和OpenAI API调用。
  3. 手动埋点增强:在/chat端点中,我们手动创建了一个chat_completionSpan,并记录了业务相关的属性(如模型名、消息长度)。这让我们能在链路中清晰看到这个业务操作的耗时。
  4. 自定义指标:我们创建了四个指标:
    • ai.request.latency(Histogram): 记录每次请求的延迟分布,便于计算P50, P90, P99等分位数。
    • ai.tokens.prompt/completion/total(Counter): 记录各类Token的消耗总量,这些计数器只会增加,适合统计总用量和计算速率。
  5. 属性(Attributes):在记录指标时,我们添加了ai.modelstatus等属性。这是监控的“黄金标签”,后续我们可以按模型、按成功/失败状态来聚合和分析数据。

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启动。

打开另一个终端,使用curlhttpie发送测试请求:

# 使用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数据源

  1. 在Grafana左侧菜单,进入Configuration > Data Sources
  2. 点击Add data source,选择ClickHouse
  3. 配置如下:
    • Name:ClickHouse-OTel
    • HTTP
      • URL:http://clickhouse:8123(注意:在Docker网络内使用服务名clickhouse)
    • Auth:取消选中Basic auth(因为我们未设置密码)。
    • ClickHouse Details
      • Database:otel
      • Username:default
      • Password:(留空)
  4. 点击Save & test,应显示“Data source is working”。

6.3 创建监控仪表盘

现在,我们可以创建几个核心监控面板。

面板1:AI请求延迟趋势(分模型)

  1. 点击Create > Dashboard > Add new panel
  2. 在查询编辑器中选择数据源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
  1. 设置:
    • Table:otel_metrics_histogram
    • Time field:timestamp
    • Metric column:value
    • Group by:metric
  2. 在右侧Panel options设置TitleAI请求平均延迟(按模型)Visualization选择Time series

面板2:Token消耗速率(堆叠图)

  1. 添加新面板。
  2. 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
  1. 设置同上,Title设为Token消耗速率Visualization选择Time series,并在Legend设置中勾选As tableTo the right

面板3:请求状态分布(饼图)

  1. 添加新面板。
  2. 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']
  1. Title设为最近1小时请求状态分布Visualization选择Pie chart

面板4:慢请求追踪列表

  1. 添加新面板,Visualization选择Table
  2. 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-collector
2. 检查应用日志是否有OTel错误
3. 在Collector配置中启用debugexporter查看
1. 确认Collector的endpoint指向正确的ClickHouse地址和端口(tcp://clickhouse:9000
2. 确保应用、Collector、ClickHouse在同一个Docker网络
Grafana无法连接ClickHouse1. 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中PeriodicExportingMetricReaderexport_interval_millis
2. 检查Collector配置中batch处理器的timeout
1. 调小导出间隔(如改为1000ms),但会增加网络开销
2. 观察ClickHouse的system.metrics
Token计数不准或为01. 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_idtenant_id,可以分析不同用户群体的使用模式和成本,为精细化运营和计费提供依据。
  • 与业务指标关联:将AI调用监控与业务指标(如订单转化率、用户满意度评分)关联分析,评估AI能力对业务的实际影响。

构建AI应用的监控体系,是一个从“不可知”到“可知”,从“被动救火”到“主动优化”的过程。本文提供的OpenTelemetry + ClickHouse方案,为你提供了一个强大、灵活且面向未来的起点。它不仅能帮你立刻看清延迟与Token消耗,其标准化和可扩展的设计,更能伴随你的业务一起成长,应对未来更复杂的监控需求。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询