1 项目背景
业务场景
「云帆科技」的 RAGFlow 平台已服务全公司 800 名员工,日均问答 3000 次,文档总量突破 500 份。系统规模和复杂度的增长带来了新的问题——某天上午 10 点,多名用户同时反馈"聊天机器人没反应了"。运维小李打开监控——只有一个docker ps的输出——她没有任何仪表盘能快速判断是 API Server 挂了、LLM 超时了、还是 Redis 队列堵了。
花了一个小时逐一排查后,发现是 LLM API 的速率限制触发了连续超时,导致 API Server 的线程池耗尽。但这个问题本该在 5 分钟内被定位——如果有日志聚合、关键指标仪表盘和告警规则的话。CTO 拍板:建立 RAGFlow 的完整可观测性体系。
痛点
没有可观测性体系的典型症状:
- 故障定位靠猜:不知道是哪个组件出问题——每个容器单独
docker logs无法看到全貌。 - 性能退化无感知:检索延迟从 500ms 慢慢涨到 3000ms,持续了两周没人发现——因为没有趋势图。
- 告警缺失:队列积压 200 个任务、LLM 错误率超 10%、磁盘使用率 95%——没人知道,直到用户投诉。
- 跨服务追踪困难:一次问答请求横跨 API Server → Redis → Task Executor → Infinity → LLM,没有 TraceID 串联,无法定位慢在哪一步。
从"盲飞"到"可观测"的转变: 盲飞阶段: 用户反馈 → 人工排查 → 花1小时 → 修复 → 没有总结 可观测阶段: 指标趋势图提前发现 → 告警通知 → 5分钟定位根因 → 修复 → 复盘优化2 项目设计
小胖:(指着三块黑屏的监控屏)“大师!老板批了预算买这三块监控大屏,但上面现在啥也没有。RAGFlow 的可观测性到底要监控什么、用什么工具、怎么配告警?”
大师:“可观测性有三根支柱——日志(Logs)、指标(Metrics)、链路追踪(Traces)。三根支柱缺一根,排障就是’猜’。”
可观测性三根支柱: 1. 日志 (Logs):发生了什么 "14:23:05 API Server响应500,错误: LLM timeout" 工具: ELK / Loki / OpenSearch Dashboards 2. 指标 (Metrics):量化趋势 "过去5分钟问答错误率 12%(阈值5%)" 工具: Prometheus + Grafana 3. 链路追踪 (Traces):请求旅程 "请求 req_abc: API(50ms) → Redis(2ms) → LLM(4500ms!!) → Total(4552ms)" 工具: Jaeger / Zipkin / OpenTelemetry技术映射:可观测性 = 医院体检——日志是病历(记录症状),指标是体检报告(血压/血糖数值),链路追踪是 CT/MRI(看清每个器官的运行状态)。
小胖:“那 RAGFlow 具体需要监控哪些指标?”
大师:“从 RED + USE 两个维度分别看:”
RED 指标(面向服务——关注用户体验): Rate: 每秒问答请求数 Errors: 问答失败率(LLM超时、检索异常、解析失败) Duration: 问答 P50/P95/P99 延迟 USE 指标(面向资源——关注系统健康): Utilization: CPU/内存/磁盘使用率 Saturation: Redis队列积压数、MySQL连接池使用率 Errors: OOM次数、磁盘满次数、网络丢包 RAGFlow 专项指标: - 文档解析吞吐(文档/小时) - Embedding 调用耗时与成功率 - 文档引擎查询延迟(P50/P95) - LLM API 调用次数与费用 - 引用准确率趋势(需要评测脚本配合)小白:(列出需要监控的组件清单)“具体到每个组件要监控什么?用什么工具采集?”
大师:
| 组件 | 关键指标 | 采集方式 | 告警阈值 |
|---|---|---|---|
| API Server | HTTP 状态码(2xx/4xx/5xx)、请求延迟、并发连接数 | Prometheus exporter 或日志解析 | 5xx率 > 5%、P95 > 10s |
| Task Executor | 队列长度、解析成功率、Worker 存活数 | Redis + 日志 | 队列 > 50、成功率 < 90% |
| MySQL | 连接数、慢查询数、复制延迟 | mysqld_exporter | 连接数 > 80%、慢查询 > 10/min |
| Redis | 内存使用率、命令延迟、连接数 | redis_exporter | 内存 > 80%、延迟 > 5ms |
| MinIO | 磁盘使用率、上传/下载吞吐 | minio_exporter | 磁盘 > 85% |
| ES/Infinity | 集群状态、查询延迟 P95、JVM堆(ES) | ES exporter / Infinity API | 非 green、P95 > 1s |
| LLM API | 调用次数、成功率、P95延迟、费用 | 应用埋点 | 错误率 > 10%、费用日增 20% |
小胖:“那链路追踪怎么搞?RAGFlow 有自带 TraceID 吗?”
大师:“当前版本 RAGFlow 的 TraceID 支持尚不完善——日志中没有统一的 trace_id 字段。但你可以在网关层注入 TraceID,贯穿整个请求链路。核心思路:在 API 入口生成 TraceID → 写入请求上下文 → 所有下游调用(Redis/ES/LLM)携带 TraceID → 在日志中打印。”
# 日志注入 TraceID(概念示例)importuuidimportloggingfromcontextvarsimportContextVar trace_id_var=ContextVar("trace_id",default=None)classTraceIDFilter(logging.Filter):"""给每条日志自动注入trace_id"""deffilter(self,record):record.trace_id=trace_id_var.get()or"no-trace"returnTrue# API 入口中间件asyncdeftrace_middleware(request,call_next):trace_id=request.headers.get("X-Trace-ID")orstr(uuid.uuid4())[:8]trace_id_var.set(trace_id)response=awaitcall_next(request)response.headers["X-Trace-ID"]=trace_idreturnresponse# 日志输出效果:# [2024-06-15 14:23:05] [trace_id=a3f2b1c4] [INFO] Retrieval took 230ms# [2024-06-15 14:23:05] [trace_id=a3f2b1c4] [INFO] Rerank took 450ms# [2024-06-15 14:23:06] [trace_id=a3f2b1c4] [INFO] LLM generation took 3200ms技术映射:TraceID = 快递单号——从揽收到派送,每个中转站都扫一次单号,全程可追踪。没有单号时,包裹丢了都不知道在哪丢的。
3 项目实战
环境准备
目标:为 RAGFlow 部署 Prometheus + Grafana + Loki + Jaeger 最小可观测套件。
前提:Docker Compose 环境,准备docker-compose-observability.yml。
分步实现
步骤1:部署 Prometheus + Grafana
目标:5 分钟拉起指标采集和可视化。
# docker-compose-observability.ymlservices:prometheus:image:prom/prometheus:v3.0.0container_name:prometheusvolumes:-./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml-prometheus_data:/prometheusports:-"9090:9090"restart:unless-stoppedgrafana:image:grafana/grafana:11.0.0container_name:grafanaenvironment:-GF_SECURITY_ADMIN_PASSWORD=adminports:-"3000:3000"volumes:-grafana_data:/var/lib/grafana-./grafana/dashboards:/etc/grafana/provisioning/dashboardsrestart:unless-stopped# Node Exporter(宿主机指标)node_exporter:image:prom/node-exporter:v1.8.0container_name:node_exportervolumes:-/proc:/host/proc:ro-/sys:/host/sys:ro-/:/rootfs:rocommand:--path.procfs=/host/proc--path.sysfs=/host/sysports:-"9100:9100"restart:unless-stopped# Redis Exporterredis_exporter:image:oliver006/redis_exporter:v1.62.0container_name:redis_exporterenvironment:-REDIS_ADDR=redis://redis:6379-REDIS_PASSWORD=${REDIS_PASSWORD}ports:-"9121:9121"restart:unless-stoppedvolumes:prometheus_data:grafana_data:# prometheus/prometheus.ymlglobal:scrape_interval:15sscrape_configs:-job_name:'node'static_configs:-targets:['node_exporter:9100']-job_name:'redis'static_configs:-targets:['redis_exporter:9121']-job_name:'ragflow'metrics_path:'/metrics'static_configs:-targets:['ragflow-api:9380']# 注:需要 RAGFlow 暴露 /metrics 端点(需二次开发或通过 exporter)步骤2:搭建 Grafana 监控大屏
目标:导入预置 Dashboard 或自建 RAGFlow 专属仪表盘。
# RAGFlow Dashboard 核心面板配置(GrafanaJSON概念){"dashboard":{"title":"RAGFlow 生产监控","panels":[{"title":"API QPS","targets":[{"expr":"rate(http_requests_total[1m])"}],"type":"graph"},{"title":"问答延迟 P50/P95/P99","targets":[{"expr":"histogram_quantile(0.50, chat_latency_seconds)"},{"expr":"histogram_quantile(0.95, chat_latency_seconds)"},{"expr":"histogram_quantile(0.99, chat_latency_seconds)"}],"type":"graph"},{"title":"LLM 错误率","targets":[{"expr":"rate(llm_errors_total[5m]) / rate(llm_calls_total[5m])"}],"type":"stat","thresholds":[{"value":0.05,"color":"red"}]},{"title":"Redis 队列积压","targets":[{"expr":"redis_stream_length"}],"type":"gauge","thresholds":[{"value":50,"color":"orange"},{"value":100,"color":"red"}]},{"title":"各组件健康状态","targets":[{"expr":"up{job='ragflow'}"},{"expr":"up{job='redis'}"},{"expr":"up{job='node'}"}],"type":"stat"}]}}步骤3:配置关键告警规则
目标:在 Prometheus AlertManager 中配置 5 条核心告警。
# prometheus/alert_rules.ymlgroups:-name:ragflow_criticalrules:-alert:LLMErrorRateHighexpr:rate(llm_errors_total[5m]) / rate(llm_calls_total[5m])>0.1for:5mlabels:severity:criticalannotations:summary:"LLM 调用错误率超过 10%"description:"过去5分钟 LLM 错误率为 {{ $value | humanizePercentage }}"-alert:QueueBacklogHighexpr:redis_stream_length>100for:10mlabels:severity:warningannotations:summary:"Redis 任务队列积压超过 100"description:"当前积压 {{ $value }} 个任务,可能需要增加 Worker"-alert:ApiErrorRateHighexpr:rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m])>0.05for:5mlabels:severity:criticalannotations:summary:"API 5xx 错误率超过 5%"-alert:SlowQueryP95expr:histogram_quantile(0.95,rate(chat_latency_seconds_bucket[5m]))>10for:10mlabels:severity:warningannotations:summary:"问答 P95 延迟超过 10 秒"-alert:DiskSpaceLowexpr:(node_filesystem_avail_bytes / node_filesystem_size_bytes) < 0.15for:5mlabels:severity:warningannotations:summary:"磁盘可用空间低于 15%"# AlertManager 告警通知配置(飞书 Webhook 示例)cat>alertmanager/config.yml<<'EOF' receivers: - name: 'feishu-webhook' webhook_configs: - url: 'https://open.feishu.cn/open-apis/bot/v2/hook/xxx' send_resolved: true route: receiver: 'feishu-webhook' group_by: ['alertname'] group_wait: 10s group_interval: 5m repeat_interval: 4h EOF步骤4:TraceID 注入与链路追踪
目标:用 OpenTelemetry 为 RAGFlow 注入 TraceID,导出到 Jaeger。
# opentelemetry_instrument.py - 应用埋点fromopentelemetryimporttracefromopentelemetry.sdk.traceimportTracerProviderfromopentelemetry.sdk.trace.exportimportBatchSpanProcessorfromopentelemetry.exporter.jaeger.thriftimportJaegerExporter# 初始化 Tracertrace.set_tracer_provider(TracerProvider())jaeger_exporter=JaegerExporter(agent_host_name="jaeger",agent_port=6831,)trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(jaeger_exporter))tracer=trace.get_tracer(__name__)asyncdefchat_with_trace(question,dataset_ids):"""带埋点的问答请求"""withtracer.start_as_current_span("chat_request")asspan:span.set_attribute("question",question[:100])span.set_attribute("dataset_count",len(dataset_ids))# 子 Span: 检索withtracer.start_as_current_span("retrieval")asretrieval_span:chunks=awaitretrieve(question,dataset_ids)retrieval_span.set_attribute("chunks_count",len(chunks))# 子 Span: Rerankwithtracer.start_as_current_span("rerank"):reranked=awaitrerank(chunks)# 子 Span: LLM 生成withtracer.start_as_current_span("llm_generation")asllm_span:answer=awaitllm_generate(reranked,question)llm_span.set_attribute("answer_length",len(answer))span.set_attribute("total_chunks",len(reranked))returnanswer步骤5:日志聚合——Loki + Promtail
目标:将 RAGFlow 多容器日志汇聚到 Grafana Loki,实现统一查询。
# docker-compose-observability.yml(补充 Loki + Promtail)services:loki:image:grafana/loki:3.0.0ports:-"3100:3100"volumes:-./loki/loki-config.yaml:/etc/loki/loki-config.yaml-loki_data:/lokipromtail:image:grafana/promtail:3.0.0volumes:-/var/lib/docker/containers:/var/lib/docker/containers:ro-/var/log:/var/log:ro-./promtail/promtail-config.yaml:/etc/promtail/config.ymlcommand:-config.file=/etc/promtail/config.ymlvolumes:loki_data:# promtail/promtail-config.yamlscrape_configs:-job_name:dockerdocker_sd_configs:-host:unix:///var/run/docker.sockrelabel_configs:-source_labels:[__meta_docker_container_name]target_label:container-source_labels:[__meta_docker_container_label_com_docker_compose_service]target_label:servicepipeline_stages:-static_labels:job:ragflow# Grafana Loki 查询示例 # 最近1小时内 API Server 的所有 ERROR 日志 {service="ragflow-api"} |= "ERROR" | json | line_format "{{.message}}" # 包含特定 trace_id 的跨容器日志 {job="ragflow"} |= "trace_id=a3f2b1c4" # 统计5分钟内各容器的日志行数 rate({job="ragflow"}[5m]) by (container)测试验证
# test_observability.pyimporttimeimportrequestsdeftest_metrics_endpoint():"""验证 Prometheus metrics 端点可访问"""r=requests.get("http://localhost:9090/api/v1/query",params={"query":"up"})assertr.status_code==200deftest_grafana_health():"""验证 Grafana 可访问"""r=requests.get("http://localhost:3000/api/health")assertr.status_code==200deftest_alert_fires_on_high_errors():"""模拟高错误率触发告警"""# 连续发送导致 500 的请求for_inrange(20):try:requests.post("http://localhost/api/v1/chats",json={"bad":"data"},timeout=2)except:passtime.sleep(30)# 等 Prometheus 采集# 检查 AlertManager 是否有 firing alertr=requests.get("http://localhost:9093/api/v2/alerts")alerts=r.json()llm_alerts=[aforainalertsif"LLMErrorRateHigh"ina.get("labels",{}).get("alertname","")]print(f"LLM错误告警状态:{llm_alerts[0]['status']ifllm_alertselse'none'}")deftest_trace_in_jaeger():"""验证 Jaeger 中有 trace 数据"""r=requests.get("http://localhost:16686/api/traces",params={"service":"ragflow-api","limit":1})traces=r.json().get("data",[])assertlen(traces)>0,"Jaeger 中无 trace 数据"完整代码清单
| 路径 | 说明 |
|---|---|
api/ragflow_server.py | API 入口(可添加 TraceID 中间件) |
rag/svr/task_executor.py | Task Executor(可添加埋点) |
docker/docker-compose.yml | 基础部署 |
column/chapter27/ | 本章监控配置文件 |
4 项目总结
优点 & 缺点
| 维度 | Prometheus + Grafana | ELK Stack | Datadog | 自建 |
|---|---|---|---|---|
| 部署复杂度 | ★★★ 容器化快速 | ★★☆ ES 较重 | ★★★ SaaS 免部署 | ★☆☆ 开发量大 |
| 指标采集 | ★★★ 丰富 exporter | ★★☆ 偏日志 | ★★★ 全覆盖 | ★★☆ 需开发 |
| 日志查询 | ★★☆ 需 Loki 配合 | ★★★ 核心能力 | ★★★ 强大 | ★★☆ 需开发 |
| 链路追踪 | ★★☆ 需 Jaeger 配合 | ★★☆ APM | ★★★ 一体化 | ★☆☆ 需开发 |
| 告警能力 | ★★★ AlertManager | ★★★ Watcher | ★★★ 智能告警 | ★★☆ 需开发 |
| 成本 | ★★★ 开源免费 | ★★★ 开源免费 | ★★☆ 按量付费 | ★☆☆ 开发成本 |
适用场景
- 生产环境日常运维:三块大屏——服务健康、业务指标、资源水位。
- 故障 5 分钟定位:TraceID → Jaeger 看耗时分布 → Loki 查关联日志 → 定位慢点。
- 容量规划:6 个月的指标趋势 → 判断何时需要扩容。
- 成本监控:LLM API 调用次数和费用的日/周/月趋势。
- SLA 保障:P95 延迟 < 10 秒、可用率 > 99.5% 的持续监控。
不适用场景:
- 开发环境:全套可观测性套件(Prometheus+Loki+Jaeger+Grafana)吃 3-4GB 内存。
- 单机小规模:< 10 人在用、< 100 份文档——
docker logs够用。
注意事项
- Prometheus 的存储:默认保留 15 天。生产环境建议用 Thanos/Cortex/VictoriaMetrics 做长期存储。
- 高基数标签陷阱:不要用
trace_id或user_id作为 Prometheus 的标签——会导致时间序列爆炸。 - Jaeger 采样率:全量采集(100%)Trace 对性能有 2-5% 的影响。生产建议 10-20% 采样。
- 告警静默期:部署窗口期间手动设置 AlertManager 静默,避免误报告警。
- Grafana Dashboard 版本管理:Dashboard JSON 文件纳入 Git,通过 Provisioning 自动加载。
常见踩坑经验
| 故障现象 | 根因 | 解决方法 |
|---|---|---|
| Prometheus 内存 OOM | 时间序列基数过高(用了 user_id 做标签) | 移除高基数标签,用日志存储这些信息 |
| Grafana 面板显示 “No data” | Datasource 配置的 URL 不对(用了 localhost 而非容器名) | 容器间通信用 service name |
| AlertManager 不发告警 | group_wait设太长(默认 30s),测试时以为无效 | 测试时临时改短,生产改回 |
| Loki 查询很慢 | 未建索引或 label 过滤不够精确 | 查询时尽量加{service="xxx"}过滤 |
| Jaeger 中 trace 断掉 | 跨服务时 trace context 未传播(HTTP header 丢失) | 确保中间件正确提取和注入traceparentheader |
思考题
RAGFlow 的 LLM 调用费用每月波动很大($800-$3000)。请设计一个"LLM 费用异常检测"方案——基于历史费用数据建模,当日费用偏离预测区间超过 30% 时触发告警——防止 Prompt 改坏导致费用暴增而不自知。
某次故障中你发现 Jaeger 中的 trace 记录显示 LLM 调用耗时 4.5 秒,但 Loki 日志中同一 trace_id 的 LLM 调用日志只有一条"start"没有"end"。请设计一个"不完整 Span 检测与告警"——自动发现没有子 Span 闭合的异常 trace,并通知开发排查死锁或超时问题。
(答案提示见第28章末尾或附录 D。)
延伸阅读与资源
10倍开发者的 Dify 魔法书:从零构建全栈 AI 应用
后端工程师转型AI第一课-Ollama 与私有化大模型实战
大型语言模型(LLM) vLLM 高性能推理落地实战
Agent开发之LlamaIndex 实战修炼与源码进阶
大语言模型Transformers 实战修炼与源码剖析