前言:为什么RAG离不开向量数据库?

在学习RAG(检索增强生成)的过程中,你一定听过这样的流程:文档 → 切分 → 向量化 → 存入向量库 → 检索相似片段 → 送入大模型。

整个流程的核心枢纽,就是向量数据库。它负责存储海量向量,并在毫秒级返回最相似的Top-K结果。没有高效的向量检索,RAG就无从谈起。

而Milvus,是目前开源界最主流、生态最完善、性能最强的向量数据库之一,也是RAG项目的首选方案。

本文将带你系统掌握Milvus,读完你将:

  • 彻底理解向量数据库的核心概念
  • 搞懂Milvus的内部架构与工作流程
  • 吃透常见向量索引的原理与选型
  • 能够独立完成Milvus的部署与CRUD
  • 清楚它在RAG架构中扮演的角色

一、基础概念扫盲

1.1 什么是向量(Embedding)

向量,也叫嵌入向量(Embedding),是将文本、图片、音频等非结构化数据转化为的一组浮点数数组。

举个例子:

"苹果" → [0.12, -0.34, 0.56, ..., 0.78] (1024维)

语义相近的内容,在向量空间中距离也更近。向量数据库就是利用这个特性,实现"语义搜索"。

1.2 什么是向量数据库

传统数据库存的是结构化数据(字符串、数字),用精确匹配或模糊查询;
向量数据库存的是高维向量,用相似度计算来检索。

它解决的核心问题:

给定一个查询向量,在百万/亿级向量中,快速找到最相似的N个向量

如果不用向量数据库,你只能逐条计算距离(暴力搜索),数据量大了之后慢到无法接受。

1.3 Milvus是什么

Milvus 是一款开源的云原生向量数据库,2019年由Zilliz公司开源,目前是LF AI & Data基金会的毕业项目。

核心特点:

  • 性能强悍:亿级向量毫秒级检索
  • 云原生:存算分离架构,支持弹性扩缩容
  • 功能丰富:支持标量过滤、多向量、分区、TTL等
  • 生态完善:支持Python/Java/Go等多语言SDK,与LangChain、LlamaIndex深度集成

二、Milvus核心概念详解

这部分是重中之重,建议反复理解。

2.1 Collection(集合)

Collection 是Milvus中最顶层的数据组织单元,相当于关系型数据库中的"表"。

每个Collection有自己的Schema(结构定义),包含字段定义、主键、向量字段等。

2.2 Partition(分区)

Partition 是Collection内部的逻辑分片,相当于"分表"。

  • 一个Collection可以包含多个Partition
  • 插入数据时可以指定存入哪个Partition
  • 检索时可以指定只搜某些Partition,大幅提升性能

典型使用场景:按时间分区(按月/按天),检索时只查最近3个月的数据。

2.3 Field(字段)

Field就是Collection中的"列",主要分两类:

字段类型说明示例
标量字段传统数据类型,用于过滤INT64、VARCHAR、BOOL、FLOAT、JSON
向量字段存储向量,用于相似度检索FLOAT_VECTOR、BINARY_VECTOR

注意:一个Collection可以有多个向量字段,但至少要有一个向量字段才能建索引。

2.4 Entity(实体)

Entity 就是一行数据,包含主键、各个标量字段和向量字段。相当于关系型数据库的"行记录"。

2.5 Index(索引)

索引是向量数据库的灵魂。它是基于向量字段构建的数据结构,目的是把暴力搜索变成近似搜索,以牺牲极小精度换取百倍千倍的速度提升。

没有索引的向量字段,只能走暴力搜索(FLAT),数据量大了会非常慢。

2.6 Segment(段)

Segment 是Milvus物理存储的最小单元,是内部概念,用户感知不强但很重要。

  • 数据写入时先进入内存,达到一定大小后落盘成一个Segment
  • 一个Partition由多个Segment组成
  • 索引是按Segment构建的

理解了这些,你就掌握了Milvus的"数据组织世界观":

Collection(集合)
  └── Partition(分区)
       └── Segment(段)
            └── Entity(实体)
                 ├── 主键字段
                 ├── 标量字段(可多个)
                 └── 向量字段(可多个)

三、Milvus系统架构

Milvus采用经典的存算分离 + 微服务架构,整体分为四层。

3.1 四层架构详解

第一层:接入层

  • 对外提供统一的gRPC/HTTP接口
  • 负责连接管理、鉴权、限流
  • 对应组件:Proxy(无状态,可水平扩展)

第二层:协调服务层
大脑,负责元数据管理和任务调度,包含四个Coordinator:

  • Root Coord:集群元数据管理、DDL操作(建删Collection)
  • Data Coord:数据节点调度、Segment管理
  • Query Coord:查询节点调度、负载均衡
  • Index Coord:索引任务调度、索引节点管理

第三层:工作节点层
干活的执行节点,三类节点各司其职:

  • Data Node:处理数据写入,构建Segment并落盘
  • Query Node:加载Segment到内存,执行向量检索
  • Index Node:异步构建向量索引

第四层:存储层
持久化存储,分为三类存储:

  • 元数据存储:etcd,存Schema、节点状态等
  • 消息存储:Pulsar/Kafka,数据写入的消息队列
  • 对象存储:S3/MinIO,存向量数据、索引文件

3.2 架构总览

存储层

工作节点层

协调服务层

客户端 SDK

Proxy 接入层

Root Coord

Data Coord

Query Coord

Index Coord

Data Node

Query Node

Index Node

etcd 元数据

Pulsar 消息队列

S3/MinIO 对象存储

这种架构的好处是各组件独立扩展——查询压力大就加Query Node,写入压力大就加Data Node,非常灵活。


四、核心工作流程(流程图详解)

这是理解Milvus运行原理的关键。

4.1 数据写入流程

Milvus的写入是异步流式的,不是直接写磁盘。

对象存储 Pulsar Data Node Data Coord Proxy 客户端 对象存储 Pulsar Data Node Data Coord Proxy 客户端 后台异步消费 insert(collection, data) 校验Collection信息 返回通道信息 写入消息队列 写入成功 返回成功(此时数据还未落盘) 消费数据 在内存中构建Segment Segment落盘 上报Segment信息

关键点

  1. 写入是异步的,SDK返回成功不代表数据立即可检索
  2. 数据先写消息队列,再由Data Node消费落盘
  3. 新数据从写入到可查通常有秒级延迟(近实时)

4.2 向量检索流程

Query Node Query Coord Proxy 客户端 Query Node Query Coord Proxy 客户端 search(向量, topk, 过滤条件) 获取目标Segment分布 返回Segment所在节点 分发检索请求 在内存中执行向量检索 标量过滤 + 重排序 返回局部结果 合并多节点结果,全局排序 返回最终Top-K结果

关键点

  1. 检索是分布式的,每个Query Node只搜自己加载的Segment
  2. Proxy负责全局合并与排序,保证结果准确性
  3. 支持标量过滤(先过滤再检索,或先检索再过滤)

五、向量索引原理详解

索引是向量数据库最核心的技术,也是面试高频考点。Milvus支持多种索引类型,各有优劣。

5.1 FLAT - 暴力搜索

原理:不构建索引,查询时与所有向量逐一计算距离。

  • 优点:精度100%,内存占用小
  • 缺点:速度极慢,只适合万级以下小数据量
  • 适用场景:测试、小数据集、追求百分百召回率

5.2 IVF_FLAT - 倒排文件索引

原理:基于聚类的倒排索引。

  1. 先对所有向量做K-Means聚类,得到nlist个聚类中心
  2. 查询时,先找到距离最近的nprobe个聚类
  3. 只在这nprobe个聚类内做暴力搜索

查询向量

计算与各聚类中心距离

选出nprobe个最近的聚类

在聚类内暴力计算所有向量距离

返回Top-K

  • 优点:速度快,参数可调,平衡精度与性能
  • 缺点:聚类需要训练时间,高维下性能下降
  • 关键参数:nlist(聚类中心数)、nprobe(查询时探查的聚类数)

经验值:nlist一般设为 sqrt(数据量),nprobe越大精度越高、速度越慢。

5.3 IVF_SQ8 - 标量量化

原理:在IVF基础上,将每个向量维度从float32压缩为uint8(标量量化),体积压缩约4倍。

  • 优点:内存占用极低,速度比IVF_FLAT更快
  • 缺点:有精度损失
  • 适用场景:海量数据、内存有限、对精度要求不极致

5.4 HNSW - 层次化导航小世界

目前最主流、综合性能最好的索引类型。

原理:构建多层有向图,上层稀疏、下层稠密。查询时从上层入口出发,逐层向下导航,最终在底层找到最近邻。

渲染错误: Mermaid 渲染失败: Lexical error on line 2. Unrecognized text. ...subgraph Layer 2 最上层(稀疏) A <--> -----------------------^
  • 优点:查询速度极快,召回率高,不需要训练
  • 缺点:构建索引慢,内存占用大
  • 关键参数:M(每层每个节点的连接数,默认16)、efConstruction(构建时搜索范围,默认200)、ef(查询时搜索范围)

经验:M一般取8~64,维度越高M可以适当调大;ef越大精度越高速度越慢。

5.5 索引选型指南

索引类型召回率速度内存占用构建速度推荐场景
FLAT100%最慢-万级以下数据
IVF_FLAT中等中等中等百万级,平衡型
IVF_SQ8很低中等千万级,内存紧张
HNSW很高最快千万级,追求极致查询性能

RAG项目首选:HNSW,因为RAG对查询延迟要求高,且数据量通常在千万级以内。


六、Milvus快速上手实战

6.1 Docker 单机部署

最简单的入门方式,使用Docker Compose一键启动:

# 下载docker-compose文件
wget https://github.com/milvus-io/milvus/releases/download/v2.4.0/milvus-standalone-docker-compose.yml -O docker-compose.yml

# 启动
docker-compose up -d

启动后,Milvus会监听 19530 端口(gRPC)。

6.2 Python SDK 基本操作

安装SDK:

pip install pymilvus

步骤1:连接Milvus

from pymilvus import connections, utility

connections.connect(
    alias="default",
    host="localhost",
    port="19530"
)

步骤2:创建Collection

from pymilvus import CollectionSchema, FieldSchema, DataType, Collection

# 定义字段
book_id = FieldSchema(name="book_id", dtype=DataType.INT64, is_primary=True, auto_id=True)
book_name = FieldSchema(name="book_name", dtype=DataType.VARCHAR, max_length=200)
word_count = FieldSchema(name="word_count", dtype=DataType.INT64)
embedding = FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=768)

# 定义Schema
schema = CollectionSchema(
    fields=[book_id, book_name, word_count, embedding],
    description="图书知识库",
    enable_dynamic_field=True  # 开启动态字段,灵活扩展
)

# 创建Collection
collection = Collection(name="book_kb", schema=schema)

步骤3:插入数据

import numpy as np

# 模拟数据
data = [
    ["深度学习入门", "自然语言处理实战", "向量数据库指南"],  # book_name
    [50000, 80000, 30000],  # word_count
    np.random.random([3, 768]).tolist()  # 向量,实际项目中用Embedding模型生成
]

# 插入
mr = collection.insert(data)
print(f"插入了 {mr.insert_count} 条数据")

步骤4:创建HNSW索引

index_params = {
    "metric_type": "COSINE",  # 距离度量:L2 / IP / COSINE
    "index_type": "HNSW",
    "params": {
        "M": 16,
        "efConstruction": 200
    }
}

collection.create_index(
    field_name="embedding",
    index_params=index_params
)

距离度量选型:文本相似度推荐 COSINEIP(内积),向量归一化后两者等价。

步骤5:加载并检索

# 将Collection加载到内存(必须加载才能检索)
collection.load()

# 查询向量
search_vector = np.random.random([1, 768]).tolist()

search_params = {
    "metric_type": "COSINE",
    "params": {"ef": 64}
}

# 执行检索
results = collection.search(
    data=search_vector,
    anns_field="embedding",
    param=search_params,
    limit=3,  # Top-K
    expr="word_count > 40000",  # 标量过滤条件
    output_fields=["book_name", "word_count"]  # 返回的字段
)

# 打印结果
for hits in results:
    for hit in hits:
        print(f"ID: {hit.id}, 书名: {hit.entity.get('book_name')}, 相似度: {hit.score}")

七、Milvus在RAG中的完整应用

7.1 RAG全流程图

在线检索阶段

用户问题

Embedding模型

问题向量

Milvus检索

返回Top-K相似片段

拼接Prompt

大模型生成答案

离线构建阶段

原始文档

文档切分

Embedding模型

生成向量

Milvus存储

7.2 Milvus在RAG中的核心价值

  1. 语义检索:基于向量相似度找到语义相关的文档片段
  2. 混合检索:支持标量字段过滤(如按文档来源、时间过滤)
  3. 高性能:知识库即使扩充到百万级,检索依然毫秒级
  4. 增量更新:支持随时新增、删除、修改知识库内容

7.3 一个最小RAG检索函数

def rag_retrieve(question: str, top_k: int = 3):
    """
    RAG检索阶段:将问题向量化,从Milvus中检索最相关的文档片段
    """
    # 1. 将问题转为向量(实际用OpenAI/BGE等Embedding模型)
    question_vector = embedding_model.encode(question).tolist()
    
    # 2. Milvus检索
    results = collection.search(
        data=[question_vector],
        anns_field="embedding",
        param={"metric_type": "COSINE", "params": {"ef": 64}},
        limit=top_k,
        output_fields=["content", "source"]
    )
    
    # 3. 提取检索到的文本片段
    contexts = []
    for hit in results[0]:
        contexts.append({
            "content": hit.entity.get("content"),
            "source": hit.entity.get("source"),
            "score": hit.score
        })
    
    return contexts

拿到contexts后,拼接进Prompt送入大模型,就完成了完整的RAG流程。


八、最佳实践与常见坑

8.1 Collection设计建议

  • 主键尽量用 INT64,性能比 VARCHAR
  • 维度不要超过4096,越高维检索越慢、索引效果越差
  • 善用Partition做时间分片,避免全量检索
  • 开启动态字段(enable_dynamic_field=True),方便后续加字段

8.2 性能优化建议

  • 数据量小于100万,HNSW + COSINE 是黄金组合
  • 查询时合理设置 ef,兼顾精度与速度
  • 检索时尽量加过滤条件,减少扫描范围
  • 定期做 compact,清理已删除数据,优化Segment

8.3 常见问题

Q:插入数据后搜不到?
A:Milvus写入是近实时的,默认1秒刷新一次;可调用 collection.flush() 强制落盘。

Q:检索结果不准怎么办?
A:调大 nprobe(IVF系列)或 ef(HNSW),牺牲速度换精度;检查Embedding模型质量。

Q:内存不够用怎么办?
A:换IVF_SQ8索引,或开启磁盘索引(DiskANN),用磁盘换内存。


结语

Milvus是RAG技术栈中不可或缺的一环。理解了它的核心概念、架构原理和索引机制,你才能在实际项目中做出合理的技术选型和性能调优。

本文覆盖了入门到实战的完整路径,但Milvus的深度远不止于此——还有标量索引、事务、多租户、冷热分层等进阶特性等待你探索。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐