Skip to content

RAG 部署与可观测性:日志、追踪、脱敏和回滚 ​

开发完成只是第一步,如何将 RAG 服务稳定地运行在生产环境,并对其进行监控,是工程化的最后“一公里”。

1. Docker 容器化部署 ​

Golang 的最大优势之一是编译产物极小(静态链接二进制)。我们可以使用 多阶段构建 (Multi-stage Build) 来制作超小的 Docker 镜像。

Dockerfile 示例 ​

dockerfile
# 阶段 1: 编译
FROM golang:1.21-alpine AS builder
WORKDIR /app

# 设置代理 (国内环境)
ENV GOPROXY=https://goproxy.cn,direct

COPY go.mod go.sum ./
RUN go mod download

COPY . .
# 编译为名为 server 的二进制文件
RUN CGO_ENABLED=0 GOOS=linux go build -o server ./cmd/api

# 阶段 2: 运行 (使用 distroless 或 alpine)
FROM alpine:latest
WORKDIR /root/

# 安装 ca-certificates (调用 HTTPS API 需要)
RUN apk --no-cache add ca-certificates

COPY --from=builder /app/server .
COPY --from=builder /app/config.yaml .

EXPOSE 8080
CMD ["./server"]

构建并运行:

bash
docker build -t go-rag-service .
docker run -p 8080:8080 -e OPENAI_API_KEY=sk-xxx go-rag-service

这段 Dockerfile 用于说明多阶段构建,不是完整生产模板。实际发布应固定并扫描基础镜像、使用非 root 用户、通过密钥管理系统注入凭据、设置只读文件系统与资源限制,并在 CI 中生成可追溯的软件物料与镜像摘要。不要把真实 API Key 写入命令历史、镜像层、配置文件或仓库。

2. 可观测性 (Observability) ​

RAG 系统链路长(API -> Embedding -> VectorDB -> LLM),一旦变慢,很难排查是哪一步的问题。 我们需要引入 OpenTelemetry 进行链路追踪 (Tracing)。

Golang 接入 OpenTelemetry ​

go
package main

import (
	"context"
	"log"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/exporters/jaeger"
	"go.opentelemetry.io/otel/sdk/resource"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)

// 初始化 Tracer
func initTracer(url string) func(context.Context) error {
	exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
	if err != nil {
		log.Fatal(err)
	}

	tp := sdktrace.NewTracerProvider(
		sdktrace.WithBatcher(exporter),
		sdktrace.WithResource(resource.NewWithAttributes(
			semconv.SchemaURL,
			semconv.ServiceName("go-rag-service"),
		)),
	)
	otel.SetTracerProvider(tp)
	return tp.Shutdown
}

// 在业务代码中打点
func (uc *ChatUseCase) Chat(ctx context.Context, query string) (string, error) {
	tr := otel.Tracer("usecase")
	ctx, span := tr.Start(ctx, "Chat")
	defer span.End()

	// 1. Embedding
	_, embedSpan := tr.Start(ctx, "Embedding")
	// ... 调用 Embedding API ...
	embedSpan.End()

	// 2. Retrieval
	_, searchSpan := tr.Start(ctx, "VectorSearch")
	// ... 调用 Milvus ...
	searchSpan.SetAttributes(attribute.Int("top_k", 5))
	searchSpan.End()

	// 3. LLM Generation
	_, llmSpan := tr.Start(ctx, "LLMGenerate")
	// ... 调用 OpenAI ...
	llmSpan.End()

	return "result", nil
}

3. 监控指标 (Metrics) ​

使用 Prometheus 监控关键指标:

  • QPS: 每秒请求数。
  • Latency: 接口响应时间 (P99, P95)。
  • Token Usage: 消耗的 Token 数量(直接关联成本)。
go
import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
	requestDuration = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name: "http_request_duration_seconds",
			Help: "HTTP请求耗时分布",
		},
		[]string{"path"},
	)
)

func init() {
	prometheus.MustRegister(requestDuration)
}

// 在 Handler 中记录
func Handler(c *gin.Context) {
	timer := prometheus.NewTimer(requestDuration.WithLabelValues(c.FullPath()))
	defer timer.ObserveDuration()

	// ... 处理业务 ...
}

指标标签不要直接使用用户 ID、原始 Query、完整 URL 或 Chunk 文本,以免产生高基数和敏感数据泄露。延迟分位数应由后端根据直方图聚合,告警阈值来自服务目标和实测基线,而不是照抄示例值。

4. RAG 日志与追踪应该记录什么 ​

一次请求需要同时解释“系统为什么慢”和“答案为什么错”。建议让 request ID 贯穿网关、Embedding、检索、重排和生成,并记录:

阶段建议记录默认不记录
请求路由、租户/用户的不可逆标识、超时与结果类别原始身份信息、完整问题
检索索引与模型版本、过滤器摘要、候选 ID/排名、降级原因未授权候选正文
上下文Chunk ID、来源版本、截断与去重统计完整敏感片段
生成Provider/模型/Prompt 版本、Token 用量、结束原因密钥、完整 Prompt
引用引用 ID、定位校验结果、失效原因用户无权访问的来源

是否允许采样正文取决于数据分类、用户授权和组织政策。确需采样时,应采用单独的受控存储、脱敏、短保留期和访问审计,而不是把正文混入普通应用日志。Prompt 注入内容和恶意文档同样可能含敏感信息,不应因为“用于调试”而跳过治理。

Trace 中可以建立 ingest、embed、retrieve、rerank、context_build、generate 等 Span,并记录错误类别与版本。OpenTelemetry 的 SDK、Exporter 和语义约定会演进,示例代码应按项目锁定的依赖版本验证,避免直接复制过时初始化方式。

5. 系统指标与质量指标要分开 ​

系统指标包括请求量、错误、取消、各阶段耗时、队列积压、外部依赖限流、资源饱和和 Token 用量。

质量指标包括零召回、无答案、引用缺失或失效、权限拦截、负反馈、人工改写和固定评估集的版本变化。

HTTP 200 不代表回答正确,因此只监控 QPS、P95/P99 和 5xx 无法发现 RAG 质量退化。反过来,Judge 分数也不能解释连接池耗尽或 Provider 限流。两类信号应通过 request ID、配置版本和索引版本关联。

每条告警都要有阈值依据、负责人和处理动作。本文不提供统一数值:不同文档规模、模型、租户和交互方式的正常区间不同,应从压测、灰度和历史基线建立服务目标。

6. 部署前的兼容性检查 ​

  • 应用版本是否兼容当前 Chunk Schema、索引、缓存和引用格式。
  • Embedding 或切片变化是否写入新索引版本,而不是原地混写不兼容向量。
  • 权限更新是否覆盖向量索引、关键词索引、缓存和已生成的分享链接。
  • Readiness 是否验证必要依赖,Liveness 是否避免因短暂外部故障造成重启风暴。
  • 客户端取消和总超时是否传递到检索、重排和模型调用。
  • 流式响应中断时是否停止上游工作并正确记录用量与结束原因。
  • 数据库、索引和队列迁移是否可以前向兼容,是否有恢复演练。

7. 灰度、降级与回滚 ​

发布单位不只是 Go 二进制,还包括 Prompt、生成模型、Embedding、切片、索引、检索参数、权限策略和引用格式。推荐流程是:

text
固定离线评估
  -> 影子流量或内部验证
  -> 小范围灰度
  -> 同时观察系统与质量信号
  -> 分阶段扩大或停止

每一阶段都应预先写明扩大和停止条件。可用的降级方式取决于业务,包括关闭重排、切换到经过评估的检索路径、只返回搜索结果、禁用生成、进入人工队列或拒绝请求。降级不能绕过权限,也不能把“少了一路结果”静默标记为完整成功。

回滚前需保留事故证据,并确认目标组合与当前数据兼容。若新版本已经写入不同 Schema 或索引,应切换读别名或恢复兼容快照,而不是只回退容器。至少演练 Provider 不可用、索引构建失败、错误权限规则、流式中断和新版本质量退化。

8. 总结 ​

Go 可以把 RAG 查询和索引任务接入容器、指标与追踪体系,但生产可靠性来自明确的超时、错误处理、权限、数据治理、质量评估和回滚设计,而不是语言本身。

  • 构建可追溯:镜像、应用和配置版本能够对应到发布记录。
  • 观测可关联:系统信号、质量信号、索引版本和请求证据能够串联。
  • 数据可治理:日志、Trace、缓存和评估样本都执行权限与脱敏规则。
  • 发布可逆:灰度、停止、降级和完整配置组合回滚经过演练。

回到 RAG 专题查看完整学习路径,先用 RAG 评估方法建立发布基线,再按 RAG 生产化检查清单完成上线评审。项目架构与权限、引用链路见 企业级 RAG 知识库。

AI 应用开发训练营 · 现在开始

别只收藏 AI 文章,今天就做出第一个能上线的项目

文章解决认知,项目才证明能力。把 Agent、RAG、MCP 变成可运行、可展示、可面试表达的成果,现在就从一次岗位准备度自测开始。

立即开始 AI 岗位准备度自测 查看训练营路线
真实学员成果先看成果,再决定是否开始 →
  1. 01选方向
  2. 02做项目
  3. 03出成果
  4. 04拿面试