生产级RAG系统架构实战:从POC到10万QPS的演进之路

3 阅读7分钟

生产级RAG系统架构实战:从POC到10万QPS的演进之路

本文基于笔者在构建企业级RAG系统过程中的实战经验,涵盖架构设计、性能优化、召回策略等核心技术点。

一、为什么你的RAG系统跑不通生产环境

去年团队接了一个知识库问答项目,POC阶段用LangChain+ChromaDB两周搞定,demo效果惊艳。但上线第一天就崩了:

  • 延迟爆炸:平均响应8秒,P99超过30秒
  • 召回率差:用户问法稍微变化就找不到答案
  • 并发雪崩:50个并发就把GPU打满

问题出在哪?POC代码和生产级系统之间,隔着一个架构设计的鸿沟。

本文分享我们从零构建10万QPS RAG系统的完整技术方案。


二、RAG系统核心架构

2.1 系统架构图

┌─────────────────────────────────────────────────────────────┐
│                         接入层                               │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │   API Gateway │  │  限流/熔断    │  │   负载均衡(LVS)      │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                        编排层 (Orchestration)               │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                  Agent Framework                      │   │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────────────┐  │   │
│  │  │ Query理解 │→│ 路由决策  │→│ 多轮对话管理      │  │   │
│  │  │(意图分类) │  │(选择工具) │  │(上下文压缩)      │  │   │
│  │  └──────────┘  └──────────┘  └──────────────────┘  │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                      检索层 (Retrieval)                     │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │  向量数据库   │  │  倒排索引    │  │   知识图谱          │  │
│  │(Milvus/Qdrant)│ │(Elasticsearch)│  │   (Neo4j)          │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │  Embedding  │  │  重排序(Rerank)│  │   缓存层(Redis)     │  │
│  │  服务集群    │  │  模型(Cross-Encoder)            │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                     生成层 (Generation)                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              LLM推理服务 (vLLM/TGI)                   │   │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────────────┐  │   │
│  │  │ Prompt工程│  │ 动态上下文 │  │ 流式响应         │  │   │
│  │  │ 模板管理  │  │ 长度控制   │  │ SSE推送          │  │   │
│  │  └──────────┘  └──────────┘  └──────────────────┘  │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

2.2 关键设计决策

决策1:多路召回策略

单一向量检索在生产环境不够稳,我们采用三路召回:

召回方式技术实现适用场景召回率
向量召回Dense Embedding语义相似75%
关键词召回BM25 + 倒排索引专有名词、ID60%
图谱召回实体关系检索关联推理45%

融合策略:RRF (Reciprocal Rank Fusion)

def reciprocal_rank_fusion(results_lists: List[List[Dict]], k: int = 60) -> List[Dict]:
    """RRF多路召回融合"""
    scores = defaultdict(float)
    doc_map = {}

    for source_idx, results in enumerate(results_lists):
        for rank, doc in enumerate(results):
            doc_id = doc['id']
            doc_map[doc_id] = doc
            # RRF公式: score = Σ(1 / (k + rank))
            scores[doc_id] += 1.0 / (k + rank + 1)

    # 排序返回
    sorted_docs = sorted(scores.items(), key=lambda x: x[1], reverse=True)
    return [doc_map[doc_id] for doc_id, _ in sorted_docs[:10]]

决策2:检索-重排序分离架构

初筛用轻量模型,精排用Cross-Encoder,平衡效率和精度:

用户Query → Embedding模型(Top 100) → Cross-Encoder(Top 10) → LLM生成
              (延迟10ms)                (延迟100ms)

三、核心模块实现

3.1 高性能Embedding服务

生产环境不能用OpenAI API(延迟高、成本高),我们自建Embedding服务:

# embedding_service.py
from sentence_transformers import SentenceTransformer
import torch
from typing import List
import numpy as np

class EmbeddingService:
    def __init__(self, model_path: str, device: str = "cuda"):
        self.model = SentenceTransformer(model_path, device=device)
        self.model.eval()

        # 动态批处理配置
        self.max_batch_size = 64
        self.max_seq_length = 512

    @torch.inference_mode()
    def encode(self, texts: List[str]) -> np.ndarray:
        """批量编码,自动处理长文本"""
        # 文本截断和清理
        processed_texts = [
            t[:self.max_seq_length * 3]  # 中文字符约3字节
            for t in texts
        ]

        # 动态批处理
        embeddings = []
        for i in range(0, len(processed_texts), self.max_batch_size):
            batch = processed_texts[i:i + self.max_batch_size]
            batch_emb = self.model.encode(
                batch,
                convert_to_numpy=True,
                normalize_embeddings=True  # L2归一化,方便余弦相似度计算
            )
            embeddings.append(batch_emb)

        return np.vstack(embeddings)

# FastAPI部署
from fastapi import FastAPI
app = FastAPI()

service = EmbeddingService("BAAI/bge-large-zh-v1.5")

@app.post("/embed")
async def embed(texts: List[str]):
    embeddings = service.encode(texts)
    return {"embeddings": embeddings.tolist()}

性能数据

  • 单机QPS:800 (A100 GPU)
  • 平均延迟:15ms (batch=32)
  • 相比OpenAI API:成本降低90%,延迟降低80%

3.2 向量数据库选型与优化

我们对比了主流向量数据库:

特性MilvusQdrantPineconeWeaviate
开源
十亿级规模
混合检索
国产化支持
云原生

最终选择 Milvus,核心配置:

# milvus_cluster.yaml
cluster:
  enabled: true

components:
  proxy:
    replicas: 3
    resources:
      cpu: 4
      memory: 8Gi

  queryNode:
    replicas: 6  # 查询节点,可水平扩展
    resources:
      cpu: 8
      memory: 32Gi
      gpu: 1  # GPU加速索引构建

  indexNode:
    replicas: 2
    resources:
      cpu: 16
      memory: 64Gi
      gpu: 2

dataCoord:
  segment:
    maxSize: 1024  # MB,控制segment大小影响查询性能
    sealProportion: 0.25

性能优化技巧

from pymilvus import Collection, FieldSchema, CollectionSchema, DataType

# 1. 选择合适的索引类型
def create_optimized_collection():
    fields = [
        FieldSchema(name="id", dtype=DataType.VARCHAR, max_length=64, is_primary=True),
        FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1024),
        FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=65535),
        FieldSchema(name="metadata", dtype=DataType.JSON),
    ]

    schema = CollectionSchema(fields, "RAG知识库")
    collection = Collection("knowledge_base", schema)

    # GPU加速的IVF-FLAT索引,适合亿级数据
    index_params = {
        "metric_type": "COSINE",
        "index_type": "GPU_IVF_FLAT",  # GPU版本比CPU快10倍
        "params": {"nlist": 4096}  # 聚类中心数,数据量越大越大
    }
    collection.create_index("embedding", index_params)
    return collection

# 2. 查询参数调优
def optimized_search(collection, query_vector, top_k=10):
    search_params = {
        "metric_type": "COSINE",
        "params": {
            "nprobe": 256,  # 查询时搜索的聚类数,精度vs性能平衡
            "limit": top_k
        }
    }

    results = collection.search(
        data=[query_vector],
        anns_field="embedding",
        param=search_params,
        limit=top_k,
        output_fields=["text", "metadata"]
    )
    return results

3.3 LLM推理服务优化

从Transformers切换到vLLM,吞吐量提升20倍:

# vllm_deployment.py
from vllm import LLM, SamplingParams
from vllm.engine.arg_utils import AsyncEngineArgs
from vllm.engine.async_llm_engine import AsyncLLMEngine

class LLMService:
    def __init__(self):
        # 关键优化参数
        engine_args = AsyncEngineArgs(
            model="Qwen/Qwen2-72B-Instruct",
            tensor_parallel_size=4,  # 4卡并行
            gpu_memory_utilization=0.9,
            max_num_seqs=256,  # 最大并发序列数
            max_num_batched_tokens=4096,
            # 连续批处理,关键性能优化
            enable_chunked_prefill=True,
            # Prefix Caching,重复prompt只计算一次
            enable_prefix_caching=True,
        )
        self.engine = AsyncLLMEngine.from_engine_args(engine_args)

    async def generate(self, prompt: str, **kwargs):
        sampling_params = SamplingParams(
            temperature=kwargs.get('temperature', 0.7),
            max_tokens=kwargs.get('max_tokens', 1024),
            # 流式生成
            stream=True,
        )

        # 异步流式返回
        async for output in self.engine.generate(
            prompt, sampling_params, request_id=uuid.uuid4()
        ):
            yield output.outputs[0].text

部署配置

FROM vllm/vllm-openai:latest

# 72B模型需要4张A100
ENV CUDA_VISIBLE_DEVICES=0,1,2,3
ENV PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:512

ENTRYPOINT python -m vllm.entrypoints.openai.api_server \
    --model Qwen/Qwen2-72B-Instruct \
    --tensor-parallel-size 4 \
    --max-num-seqs 256 \
    --gpu-memory-utilization 0.9

四、关键性能优化

4.1 延迟优化:从8秒到800ms

原始链路延迟分析:

Query理解    Embedding    向量检索    重排序    LLM生成    总延迟
  50ms   +   200ms   +   150ms  +  300ms  +  7500ms  =  8200ms

优化策略

  1. Embedding缓存:高频Query直接走Redis
import hashlib
import redis

cache = redis.Redis(host='localhost', port=6379, db=0)

def cached_embed(query: str):
    # Query标准化后取hash
    normalized = query.strip().lower()
    key = f"emb:{hashlib.md5(normalized.encode()).hexdigest()}"

    # 检查缓存
    cached = cache.get(key)
    if cached:
        return json.loads(cached)

    # 计算并缓存
    embedding = model.encode([query])[0]
    cache.setex(key, 3600, json.dumps(embedding.tolist()))
    return embedding
  1. 检索结果缓存:相同Query直接返回历史结果
  2. LLM预生成:热点问题预生成答案,命中直接返回

优化后延迟:

缓存命中: 50ms (Query理解) + 10ms (Redis) = 60ms
缓存未命中: 50ms + 200ms + 150ms + 300ms + 500ms = 1200ms
平均延迟: ~800ms (缓存命中率70%)

4.2 吞吐量优化:从50 QPS到10万QPS

水平扩展架构

                    ┌─────────────┐
                    │  API Gateway │
                    │   (Kong/AWS)  │
                    └──────┬──────┘
                           │
           ┌───────────────┼───────────────┐
           ↓               ↓               ↓
    ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
    │  RAG Pod 1  │ │  RAG Pod 2  │ │  RAG Pod N  │
    │  (HPA自动扩缩) │ │             │ │             │
    └─────────────┘ └─────────────┘ └─────────────┘
           ↓               ↓               ↓
    ┌─────────────────────────────────────────────┐
    │           共享存储层                          │
    │  Redis Cluster + Milvus Cluster + S3         │
    └─────────────────────────────────────────────┘

K8s HPA配置

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: rag-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: rag-service
  minReplicas: 10
  maxReplicas: 1000
  metrics:
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: "100"  # 每Pod 100 QPS
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

五、生产环境 checklist

5.1 可观测性

# OpenTelemetry埋点
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

tracer = trace.get_tracer(__name__)

@tracer.start_as_current_span("rag_pipeline")
def rag_pipeline(query: str):
    with tracer.start_as_current_span("retrieve") as span:
        span.set_attribute("query_length", len(query))
        docs = retrieve_docs(query)
        span.set_attribute("retrieved_count", len(docs))

    with tracer.start_as_current_span("generate") as span:
        answer = llm_generate(docs, query)
        span.set_attribute("answer_length", len(answer))

    return answer

5.2 降级策略

class RAGService:
    def __init__(self):
        self.llm_available = True
        self.retriever_available = True

    async def query(self, query: str) -> str:
        try:
            # 正常RAG流程
            if self.retriever_available and self.llm_available:
                return await self._full_rag(query)
        except RetrievalException:
            self.retriever_available = False

        try:
            # 降级:只用LLM,不走检索
            if self.llm_available:
                return await self._llm_only(query)
        except LLMException:
            self.llm_available = False

        # 最终降级:返回模板回复
        return "系统繁忙,请稍后重试"

六、总结

构建生产级RAG系统的关键经验:

  1. 架构先行:POC验证后,立即设计可扩展架构,不要直接拿demo代码上线
  2. 多路召回:单一检索策略不够稳,向量+关键词+图谱组合才是生产方案
  3. 性能分层:Embedding缓存、检索缓存、LLM预生成,层层削峰填谷
  4. 可观测性:每个环节都要有metrics,否则出问题只能抓瞎

有RAG部署问题欢迎评论区交流。