深入理解 Milvus 向量数据库:从架构到原理,一篇讲透
深入理解 Milvus 向量数据库:从架构到原理,一篇讲透
读完这篇,你将彻底搞懂:Milvus 是怎么把"百亿级向量检索"这件大事做成分布式系统的,以及它四层架构、IVF/HNSW 索引、写入-查询流水线背后的设计智慧。
一、开场:先讲一个"电梯演讲"
想象一下这样的场景:
你的 RAG 应用在本地跑得风生水起,100 万条向量嗖嗖地查。老板一拍桌子:“上生产!” 这时候你发现——
- 用 Chroma:开发体验确实爽,但单机架构扛不住企业级的高可用、容灾需求;
- 用 Pinecone:全托管省心,但数据全在别人手里,账单也一路走高;
- 用 FAISS:它只给你一份"菜谱"(索引),没有持久化、没有分布式、没有 API——菜谱再好,没有厨房也做不出菜。
直到你遇到了 Milvus:CNCF(云原生计算基金会)毕业项目、Apache 2.0 开源协议、GitHub 30,000+ Stars,Walmart、eBay、NVIDIA、Bosch 都在生产环境里跑着它。
一句话定位:Milvus 是由 Zilliz 开源的云原生分布式向量数据库,专为海量特征向量设计,主打"存算分离、百亿级扩展、多模融合",是开源阵营里最能打的生产级选手。
1.1 版本演进:从单机到云原生
| 版本 | 说明 | 状态 |
|---|---|---|
| Milvus 1.x | 基于 Faiss/Annoy/HNSW 的单机/主从架构 | 已停止维护 |
| Milvus 2.x | 云原生分布式架构,存储计算完全分离 | 当前主流 |
| Zilliz Cloud | Milvus 的托管云服务(类似 Pinecone 的 SaaS 版本) | 商业版 |
1.2 为什么 Milvus 非要走分布式?
传统单机向量方案的瓶颈,说穿了就三条:
- FAISS / HNSWlib 只是索引库:没有持久化、没有分布式、没有 API,一切得自己造轮子;
- 单机内存撑不住:一台机器内存上限 1~2TB,百亿级向量(还是高维的)根本放不下;
- 企业级要求满足不了:高可用、容灾、弹性扩缩容,单机架构天生做不到。
所以 Milvus 从 2.x 开始彻底转向存算分离:计算节点无状态、随时弹性伸缩;数据统一持久化到对象存储(MinIO/S3);元数据交给 etcd;WAL 层用 Woodpecker / Kafka / Pulsar。这相当于把"厨房"和"仓库"彻底分开——厨房不够用了就多租几个,反正食材都放在仓库里。
二、先看懂大框架:四个字"分层"
庖丁解牛的故事大家都听过——庖丁为什么游刃有余?因为他看透了牛的结构。理解 Milvus 也一样,先把这头"牛"拆开。Milvus 2.x 采用四层存算分离的云原生架构:
┌─────────────────────────────────────────────────────────┐
│ Layer 1: 接入层 (Access Layer) │
│ Proxy × N · 无状态 · 负载均衡 · 请求验证 · 结果聚合 │
├─────────────────────────────────────────────────────────┤
│ Layer 2: 协调服务层 (Coordinator Layer) │
│ 集群大脑: DDL/DCL · TSO 时间戳 · 拓扑管理 · 任务调度 │
├─────────────────────────────────────────────────────────┤
│ Layer 3: 工作节点层 (Worker Nodes) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Streaming Node│ │ Query Node │ │ Data Node │ │
│ │ (流式写入/WAL)│ │ (历史数据查询) │ │ (索引/压缩) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Layer 4: 存储层 (Storage) │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ etcd │ │ 对象存储 │ │ WAL(消息存储) │ │
│ │ (元数据) │ │ MinIO/S3 │ │ Woodpecker/Pulsar│ │
│ └──────────┘ └──────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────┘
记住这张图,整篇文章就是给这四层"讲故事"。支撑这四层的是三条架构原则:
| 原则 | 含义 |
|---|---|
| 数据面与控制面分离 | Coordinator 管元数据和调度,Worker 管实际计算,互不越权 |
| 存储与计算分离 | Worker 节点全是无状态的,数据统一躺在对象存储里 |
| 流批分离 | Streaming Node 处理实时写入流,Query/Data Node 处理批量查询与后台任务 |
金句:“数据面和控面分离、存储和计算分离、流和批分离”——Milvus 用三招拆解了分布式系统的全部复杂度。 每一层只管自己那一亩三分地,复杂问题就变成了"分工问题"。
一句话人话:Milvus 的架构哲学就是"各司其职"——专门的人(节点)干专门的事,数据统一存在对象存储里,谁要干活谁去取。
三、核心原理一:数据模型与存储设计——你的向量到底躺在哪
3.1 数据模型:四级树状结构
Milvus 的数据模型是层层包含的树状结构:
Database
└── Collection ← 逻辑组,类同"表"
├── Schema ← 字段定义
├── Partition ← 逻辑分区(可选)
│ └── Segment ← 物理数据片(管理和索引的最小单元)
└── Index ← 索引
| 术语 | 解释 | 类比 |
|---|---|---|
| Collection | 向量和标量数据的逻辑组 | 传统数据库的"表" |
| Partition | Collection 的逻辑分区 | 表的分区 |
| Segment | Collection 的物理数据片段 | 表的物理分片 |
| Growing Segment | 正在接收写入的活跃段(数据在内存,未建索引) | 未提交的事务/草稿箱 |
| Sealed Segment | 已刷写到对象存储的不可变段 | 已提交的不可变数据 |
| Shard | 数据在流式层的水平分片 | Kafka 的分区 |
| vchannel / pchannel | 虚拟通道 / 物理通道 | 消息队列的逻辑/物理分区 |
| WAL | 写前日志,保证持久性和一致性 | 数据库的 redo log |
3.2 三层存储:元数据、对象存储、WAL
| 存储 | 技术选型 | 存什么 |
|---|---|---|
| Meta Storage(元数据) | etcd | Collection Schema、消息消费检查点、服务注册/健康检查 |
| Object Storage(对象存储) | MinIO / AWS S3 / Azure Blob | 日志快照、索引文件、段数据、中间查询结果 |
| WAL Storage(消息存储) | Woodpecker / Kafka / Pulsar | 写前日志,持久性和一致性的基础 |
这里重点说三个细节:
① etcd 管"户口":集合长什么样(Schema)、每个消费者读到了哪(消费位点)、集群里有哪些服务活着(注册与健康检查),全在 etcd 里。etcd 挂了,集群就"失忆"了。
② WAL 是保命账本:所有写入先记 WAL 再干活,这和 Redis 的 AOF 一个套路——“兵马未动,粮草先行”。进程崩溃了?从 WAL 重放,一条不丢。
③ Woodpecker 是自研新秀:它是 Milvus 新一代云原生 WAL 组件,零磁盘设计——直接写入对象存储,免去本地磁盘管理开销,弹性伸缩更顺滑,目标是替代传统 Kafka/Pulsar 方案。而在 Standalone(单机)等轻量部署场景下,Milvus 也支持内嵌的 RocksMQ 等消息存储,省去单独部署 Kafka/Pulsar 的负担;集群模式则使用 Kafka / Pulsar / Woodpecker。
3.3 Segment 生命周期:从"草稿"到"定稿"再到"上架"
[ 插入数据 ]
↓
Growing Segment (内存) ← 草稿箱:数据实时可查,未建索引(暴力搜索),可变
↓ flush
Sealed Segment (对象存储) ← 定稿:不可变,已持久化到 S3/MinIO,等待索引构建
↓ build index
Indexed Segment ← 上架:索引已构建,Query Node 加载后高效查询
| 概念 | 说明 |
|---|---|
| Growing Segment | 活跃的写入段,数据在内存中,支持增删改,写入即可查 |
| Sealed Segment | 已关闭的段,数据已写入对象存储,不可变 |
| Flush | Growing → Sealed 的转换操作 |
一句话人话:数据先进"草稿箱"(内存)让大家立刻查得到,再"定稿"存到对象存储,最后"上架"建好索引加速查询——三段式生命周期,每个阶段职责分明。
四、核心原理二:向量索引——全场 MVP(IVF & HNSW 深潜)
这是整篇文章的重头戏,也是面试官最爱问的。
4.1 为什么不能暴力搜索?
100 万条 384 维向量,暴力搜索一次要算 3.8 亿次浮点乘加,数据量一上去,延迟线性飙升。更致命的是维度灾难:在高维空间里,B-Tree、KD-Tree 这些传统索引几乎全部失效——高维空间的点太"稀疏",按维度切分根本没用。
所以业界转向 ANN(近似最近邻):牺牲一点精度,换十倍的快。Milvus 基于自研的 Knowhere 引擎统一管理 15+ 种索引,从 FLAT 到 GPU_CAGRA,丰俭由人。
4.2 索引的三大组件:数据结构 + 量化 + 精炼器
Milvus 把向量索引抽象成三个可组合的积木:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 数据结构 (DS) │ + │ 量化 (Q) │ + │ 精炼器 (R) │
│ IVF / HNSW │ │ SQ / PQ │ │ 精确距离重算 │
└──────────────┘ └──────────────┘ └──────────────┘
(必选) (可选) (可选)
- 数据结构:底层索引结构(IVF/HNSW/Vamana 图),负责"快速圈定候选";
- 量化:压缩向量表示,降低内存和计算量;
- 精炼器:查询时对候选重算精确距离,提升召回率。
配套的关键概念是膨胀率(expansion_rate):索引先快速检索 topK × expansion_rate 个候选向量,精炼器再对这批候选重算精确距离,最后返回真正的 topK。好处是精炼器只处理少量候选——粗筛交给便宜的近似计算,精排交给精确距离,速度与精度兼得。
4.3 FLAT:老实人兜底
FLAT 就是暴力搜索本身——把查询向量和库里每个向量比一遍,100% 召回,没有花活。适用于小数据集(比如 10K 以下),精度要求顶格、数据量又小的时候,它就是最省心的选择。它是所有花哨索引的"对照组",也是精度基准。
4.4 IVF:先分桶,再进桶里找(重点一)
IVF(Inverted File,倒排文件)的思路,用生活化的话说就是图书馆按类目分架:与其满馆瞎逛,不如先定位到"计算机"区,再在里面慢慢挑。
原理:
1. 构建时: K-Means 聚类 → N 个簇中心(nlist)
2. 每个向量分配到最近的簇,记下"住哪个簇"
3. 查询时: 计算查询向量到所有簇中心的距离
只扫描最近的 nprobe 个簇里的向量(暴力扫桶内)
复杂度: O( nprobe × (N / nlist) )
在 Milvus 里,IVF 通常与量化组合出现:IVF_FLAT(分桶 + 原始向量)、IVF_SQ8(分桶 + 标量量化)、IVF_PQ(分桶 + 乘积量化)。结构负责组织,量化负责压缩。
两个必懂参数(调优的灵魂):
| 参数 | 含义 | 调大 | 调小 |
|---|---|---|---|
nlist |
聚类中心(桶)数量 | 桶更多更细 → 精度↑,但构建更慢、内存更多 | 构建快、省内存,但桶内向量变多 |
nprobe |
查询时扫描的桶数 | 多翻几个桶 → 召回↑,但查询更慢 | 查得快,但可能漏掉真邻居 |
类比:
nlist决定把图书馆分成多少个书架,nprobe决定你查一次要翻几个书架。书架越多越细,但你要是不肯多翻几个书架(nprobe 小),照样找不到书。
一句话人话:IVF 就是"先分桶再找"——构建时把相似向量分到同一个桶,查询时只翻最可能藏东西的那几个桶,用空间换速度。
4.5 HNSW:跳表的高维版(重点二)
HNSW(Hierarchical Navigable Small World,分层可导航小世界)的思路,和跳表(Skip List)如出一辙。先看一维的跳表:
跳表(1D):
Level 2: 1 ───────────────────────────── 9
Level 1: 1 ────── 4 ────── 7 ────── 9
Level 0: 1 ─ 2 ─ 3 ─ 4 ─ 5 ─ 6 ─ 7 ─ 8 ─ 9
找数字 8:从顶层 1 → 9,发现 8 在 1 和 9 之间,降一层 1 → 4 → 7 → 9,发现 8 在 7 和 9 之间,再降一层 7 → 8,找到了。只走了 3 步。
HNSW 把跳表推广到高维空间,变成"多层图":
Layer 2 (最稀疏): ● ─────────────────────────── ● ← "高速公路",大跨步导航
↓
Layer 1: ● ───── ● ───── ● ───── ● ← 中等跨度
↓
Layer 0 (最密集): ●──●──●──●──●──●──●──●──●──● ← 精确搜索层,包含所有节点
关键规则(面试必背):
- 第 0 层包含所有节点,连接局部最近的邻居;
- 上层是下层的随机子集,节点数以指数递减(默认 M=16 时,每层大约只剩 6.25%);
- 上层节点连接更远的邻居,形成"高速公路",让搜索能"大跨步";
- 同一节点在不同层之间垂直连接,方便"坐电梯下楼"。
搜索过程:从顶层随机入口开始 → 贪心移动到最近的邻居(横向跳)→ 本地找不到更近的就下钻一层 → 直到第 0 层精确搜索 → 返回 top-K。复杂度 O(log N),插入 O(log N),删除 O(M × log N)。
类比:就像在北京找一家小店——先坐飞机到城市(顶层,一步千里),再打车到街区(中层),最后走路挨家挨户看门牌(第 0 层,精确到户)。飞机管"方向",步行管"细节"。
三个必懂参数(调优的灵魂):
| 参数 | 含义 | 调大 | 调小 |
|---|---|---|---|
M |
每节点最大连接数 | 图更密 → 召回↑,内存大涨 | 省内存,但图太疏容易迷路 |
efConstruction |
建索引时的探索范围 | 图质量↑,构建更慢 | 建得快,精度降 |
ef |
查询时的探索范围 | 候选越多 → 召回↑,查询变慢 | 查得快,可能漏掉真邻居 |
IVF vs HNSW 调优对比:两者本质是同一个 trade-off 的两个分支——IVF 的 nprobe 和 HNSW 的 ef 都控制"查询时愿意多花多少力气找候选";IVF 的 nlist 和 HNSW 的 M 都控制"索引建得有多细"。
| 维度 | IVF(nlist/nprobe) | HNSW(M/ef) |
|---|---|---|
| 构建速度 | 快(聚类即可) | 慢(要建多层图) |
| 查询速度 | 中(扫描桶内) | 极快(图导航) |
| 内存占用 | 可控(配合量化更省) | 较高(邻接表占内存) |
| 精度 | 高(取决于 nprobe) | 高(取决于 ef) |
| 典型场景 | 大数据量、内存受限 | 低延迟、高召回的生产查询 |
一句话人话:HNSW 就是"高维空间的分层导航图"——上层是高速公路只管大方向,下层是步行街负责精确到户;
M决定图的密度,efConstruction管盖楼质量,ef管找房耐心。
4.6 SQ 与 PQ:量化压缩的两种方式
SQ(Scalar Quantization,标量量化)和 PQ(Product Quantization,乘积量化)都为了同一件事:省内存、省计算、提速——代价是精度损失。如果把原始向量看成一张高清照片:
- SQ 像是把每个像素颜色从 32 位降到 8 位,图片大体还清楚,但细节少一点;
- PQ 像是把图片切成很多小块,每块用一个模板编号表示,压缩更强,但细节损失更多。
| 对比项 | SQ(标量量化) | PQ(乘积量化) |
|---|---|---|
| 压缩对象 | 单个维度的数值(float32 → int8) | 向量子段(切成 m 段,每段存码本编号) |
| 压缩方式 | 每个浮点数单独压缩 | 向量分段后用码本中心点编号表示 |
| 压缩比 | ~4x | 8-32x |
| 精度损失 | 较小 | 较大 |
| 典型索引 | IVF_SQ8、HNSW_SQ | IVF_PQ、HNSW_PQ、GPU_IVF_PQ |
放到索引名里怎么读?IVF_FLAT = IVF 分桶 + 原始向量;IVF_SQ8 = IVF 分桶 + SQ8 量化;IVF_PQ = IVF 分桶 + PQ 量化。结构负责"往哪找",量化负责"省着存"。
一句话人话:SQ 是"逐个数值压缩",PQ 是"把向量分段后用编号压缩";SQ 精度损失小,PQ 压缩率更高,适合更大规模数据。
4.7 索引全家福与选择指南
| 索引类型 | 数据结构 | 量化 | 适用场景 |
|---|---|---|---|
| FLAT | 暴力搜索 | 无 | 小数据集(<10K),100% 召回 |
| IVF_FLAT | IVF | 无 | 中等数据量,高召回 |
| IVF_SQ8 | IVF | SQ | 内存约束,精度不错 |
| IVF_PQ | IVF | PQ | 大容量、内存极度受限 |
| IVF_RaBitQ | IVF | RaBitQ | 高压缩、高性能 |
| HNSW | HNSW 图 | 无 | 低延迟、高召回 |
| HNSW_SQ / HNSW_PQ | HNSW 图 | SQ/PQ | 内存优化版 HNSW |
| DISKANN | Vamana 图 | PQ | 十亿级、成本敏感(图结构存 SSD) |
| SCANN | IVFPQ | 非对称量化 | Google 方案,高维向量 |
| GPU_CAGRA | GPU 图(NVIDIA RAFT) | 无 | GPU 环境,极致速度 |
| GPU_IVF_FLAT / GPU_IVF_PQ | GPU IVF | 无/PQ | GPU 加速的 IVF |
| BIN_FLAT / BIN_IVF_FLAT | 二进制暴力/IVF | 二进制 | 二进制向量(汉明距离) |
| SPARSE_INVERTED_INDEX | 稀疏倒排 | — | 稀疏向量(如 BM25 全文检索) |
索引选择决策树(照着走不会错):
→ 需要 100% 召回? → FLAT(数据集 < 10K)
→ 追求极致低延迟 (<1ms)? → HNSW(内存要充足)
→ 内存受限? → IVF_PQ / IVF_SQ8
→ 十亿级数据? → DISKANN
→ GPU 可用? → GPU_CAGRA
→ 通用生产场景? → IVF_FLAT / HNSW
关键参数速查表(面试+调优都靠它):
| 参数 | 含义 | 调大 | 调小 |
|---|---|---|---|
nlist |
IVF 聚类中心数 | 精度↑,内存↑ | 构建快,省内存 |
nprobe |
查询扫描的聚类数 | 精度↑,查询↓快 | 更快,可能漏真邻居 |
M |
HNSW 每节点最大连接数 | 精度↑,内存大涨 | 省内存,精度降 |
efConstruction |
HNSW 构建探索范围 | 图质量↑,构建慢 | 建得快,精度降 |
ef |
HNSW 查询探索范围 | 精度↑,查询慢 | 更快,可能漏真邻居 |
expansion_rate |
查询候选膨胀率 | 精度↑,精炼开销↑ | 更快,精度降 |
别忘了距离度量——选错了一切白搭:
| 度量 | 适用场景 |
|---|---|
| L2 (Euclidean) | 通用 |
| IP (Inner Product) | 推荐系统 |
| COSINE | 文本嵌入 |
| JACCARD / HAMMING | 二进制向量 |
| SUPERSTRUCTURE / SUBSTRUCTURE | 分子结构搜索 |
一句话人话:选索引就是一场"田忌赛马"——小数据用蛮力(FLAT),要快用图(HNSW),要省内存用桶(IVF 系列),要省钱存磁盘(DISKANN),有 GPU 就上显卡(GPU_CAGRA)。没有最好的索引,只有最合适的索引。
五、核心原理三:数据流与处理——写入与查询的流水线
5.1 写入路径:Shard-Channel 机制
Milvus 的写入走的是**分片通道(Shard-Channel)**机制:
Client SDK
│ insert(collection, vectors)
▼
Proxy
│ 1. 数据验证
│ 2. 根据分片路由规则拆分数据包
│ → 每个 Shard 的数据发送到对应的 vchannel
▼
Streaming Node
│ 3. 接收 pchannel → 分配 TSO(全局时间戳)
│ 4. 一致性检查
│ 5. 写入底层 WAL(保命账本)
│ 6. 异步将 WAL 记录切分为 Growing Segment(内存)
│ → 数据即刻可查询(写入即搜索)
│ 7. Flush: Growing → Sealed Segment(对象存储)
▼
Data Node
│ 8. Compaction: 合并小段为大段
│ 9. 索引构建: 为 Sealed Segment 建索引 → 写回对象存储
▼
Query Node
10. 加载新索引 → 替换 Growing 查询
5.2 vchannel 与 pchannel:逻辑分区与物理分区
Collection (shards=4)
├── Shard 0 → vchannel_0 ─┐
├── Shard 1 → vchannel_1 ├── pchannel_A → Streaming Node A
├── Shard 2 → vchannel_2 ─┤
└── Shard 3 → vchannel_3 ─┘
- vchannel(虚拟通道):每个 Shard 对应一个虚拟通道;
- pchannel(物理通道):绑定到特定的 Streaming Node;
- 多对一映射:多个 vchannel → 一个 pchannel → 一个 Streaming Node。
类比:vchannel 是"门牌号",pchannel 是"街道"。多个门牌可以落在同一条街上,一条街对应一个"居委会"(Streaming Node)负责管理。
5.3 异步索引构建:磨刀不误砍柴工
Milvus 的一个重要设计:索引构建是异步的,而且默认不阻塞查询。
insert → Growing Segment(内存,无索引 → 暴力搜索)
→ flush → Sealed Segment(对象存储)
→ Data Node 异步构建索引 → 索引写回对象存储
→ Query Node 加载新索引 → 替换暴力查询
这意味着:写入完成即可查询(新数据先用暴力搜索兜底),索引在后台悄悄构建,构建完成后自动生效。用户永远"先上车后补票",体验不掉线。
索引构建是计算和内存密集型任务(K-Means 聚类、图遍历的迭代计算),Data Node 还会用 SIMD 指令集(SSE / AVX2 / AVX512) 加速向量运算,构建完序列化写回对象存储,再通知 Query Node 加载。
5.4 Compaction:整理收纳,合并同类项
Compaction 是把多个小 Sealed Segment 合并为一个大段的后台操作:
小段 A ─┐
小段 B ─┼→ Compaction → 大段(合并了 A+B+C)
小段 C ─┘
| 好处 | 说明 |
|---|---|
| 减少段数量 | 查询时扫描更少的段 → 更快搜索 |
| 清理数据 | 物理删除已标记删除的记录 |
| 优化索引 | 在更大数据集上重建索引可提高精度 |
| 存储优化 | 消除碎片和冗余 |
支持大小合并、分层合并、聚类合并等多种策略(聚类合并还能顺带提升索引质量)。
类比:这就跟搬家一样——零碎的小盒子一大堆,搬起来手忙脚乱;先合并成几个大收纳箱,搬得又快又整齐。
5.5 查询路径:多级归约的"海选 + 决赛"
Client → Proxy
│ search(collection, query_vectors, topK, params)
▼
Proxy
│ 1. 路由缓存 → 确定目标 Streaming Node(缓存未命中 → 问 Coordinator)
▼
Streaming Node × N
│ 2. 生成物理查询计划
│ 3. 查询本地 Growing Segment(内存暴力/临时索引)
│ 4. 向 Query Node 分发 Sealed Segment 查询
▼
Query Node × M
│ 5. 在已加载的 Sealed Segment 索引上执行 ANN 搜索
│ 6. 返回段级别 topK 候选
▼
Streaming Node
│ 7. 合并 Growing + Sealed 结果
▼
Proxy
│ 8. 多级 Reduce(多个 Streaming Node 结果聚合)
│ 9. 最终 topK → 返回客户端
注意这里的"多级归约(Reduce)":每个 Query Node 只返回自己段里的 topK,Streaming Node 合一次,Proxy 再合一次——像海选赛制,逐轮淘汰,最后只剩 Top K 站上领奖台。这就是 MPP(大规模并行处理)架构的典型打法。
四种一致性级别(丰俭由人):
| 级别 | 含义 | 适用场景 |
|---|---|---|
| Strong | 最新写入的数据必定可见 | 一致性要求最高 |
| Bounded | 在指定时间窗口内到最新 | 允许轻微延迟 |
| Session | 同一 Session 内写入立即可见 | 交互式应用 |
| Eventually | 最终所有副本一致 | 容忍短时不一致的批量场景 |
5.6 Knowhere:查询到底怎么执行的
Knowhere 是 Milvus 的向量执行引擎,封装了 Faiss / HNSWlib / DiskANN / SCANN / Annoy / NVIDIA RAFT 等底层库,对外提供统一接口。一次查询的执行路径:
查询向量
│
▼
Knowhere(向量执行引擎)
│ 1. 根据索引类型选择查询路径
├─ IVF 系列: 计算到 nprobe 个聚类中心的距离,在最近 nprobe 个簇内暴力搜索
├─ HNSW: 多层图贪心搜索,ef 控制探索范围
├─ DiskANN: 图遍历 + SSD 读取
└─ GPU: CUDA 加速搜索
│ 2. 返回 topK × expansion_rate 个候选
│ 3. 精炼器重算精确距离
│ 4. 返回最终 topK
一句话人话:写入是"先记账(WAL)、再上桌(Growing)、再归档(Sealed)、再建索引(Indexed)"四步走;查询是"海选(各段 ANN)→ 复赛(Streaming 合并)→ 决赛(Proxy 归约)"三场赛。写入即查询靠的是 Growing 段兜底,快查询靠的是异步建好的索引。
六、核心原理四:分布式架构——Proxy / Coordinator / Worker 各司其职
前面四层架构里,各组件到底各自管什么?一张表说清:
| 组件 | 角色 | 核心职责 |
|---|---|---|
| Proxy(接入层) | 门面 | 唯一请求入口、无状态可水平扩展、MPP 结果聚合、负载均衡(Nginx/K8s Ingress/NodePort/LVS) |
| Coordinator(协调层) | 大脑 | DDL/DCL/TSO、WAL 绑定与流式服务管理、Query Node 拓扑与负载均衡、Compaction/索引构建任务分发 |
| Streaming Node | 分片级"迷你大脑" | WAL 写入(分片级一致性+故障恢复)、Growing Segment 实时查询、Growing→Sealed 段转换、物理查询计划生成 |
| Query Node | 历史数据查手 | 从对象存储加载 Sealed Segment 索引、段内向量/标量搜索、可为 Growing 构建临时索引 |
| Data Node | 后台苦力 | Compaction 合并小段、异步索引构建、索引文件持久化到对象存储 |
Coordinator 实际上是四个"各管一摊"的协调器兄弟(任何时刻集群只有一个活跃的 Coordinator 当家):
| 组件 | 职责 |
|---|---|
| RootCoord | DDL/DCL 操作 + TSO 时间戳分配 |
| QueryCoord | Query Node 的拓扑管理和负载均衡 |
| DataCoord | 数据段分配、Compaction、索引构建任务分发 |
| IndexCoord | 索引构建任务的统一管理 |
其中 TSO(Timestamp Oracle) 值得一提:它为所有操作分配全局唯一的单调递增时间戳,保证分布式系统的因果一致性——先发生的事时间戳一定更小,这为强一致性查询提供了基石。
一句话人话:Proxy 是"前台",Coordinator 是"老板",Streaming/Query/Data 三种 Node 是"流水线工人"——前台接单、老板派活、工人干活,数据仓库(对象存储)是大家的公共库房。
七、串起来:一次写入 + 一次查询的完整链路
# 【写入】collection.insert([vectors...])
# 1. Proxy: 数据验证 → 按 Shard 路由拆分
# 2. Streaming Node: 分配 TSO → 一致性检查 → 写 WAL(先记账,保命)
# 3. Growing Segment: 内存中构建,数据即刻可查(暴力搜索兜底)
# 4. Flush: Growing → Sealed Segment(对象存储),不可变
# 5. Data Node: 异步 Compaction 合并小段 + SIMD 加速构建索引
# 6. Query Node: 加载新索引 → 后续查询走高效索引
# 【查询】collection.search(query_vector, topK=10, params={"nprobe": 8})
# 1. Proxy: 路由缓存定位 Streaming Node(未命中问 Coordinator)
# 2. Streaming Node: 生成物理查询计划,查本地 Growing Segment
# 3. Query Node: 在 Sealed Segment 索引上 ANN 搜索 → 段级 topK
# 4. Streaming Node: 合并 Growing + Sealed 结果
# 5. Proxy: 多级 Reduce 聚合 → 精炼器重算精确距离 → 返回最终 topK
发现没有?写入时"实时流(Growing)+ 后台批(Sealed 建索引)"两条腿走路;查询时"新数据暴力搜 + 老数据索引搜"两条路汇合。 整个设计就围绕"流批分离 + 存算分离"这两个原则展开。
八、技术栈与选型建议
8.1 完整技术栈一览
| 层面 | 技术 | 作用 |
|---|---|---|
| API 层 | Python (PyMilvus) / Java / Go / Node.js / C# SDK、RESTful API | 多语言客户端 |
| 接入层 | Proxy(Go) | 无状态代理、认证、结果聚合 |
| 协调层 | Coordinator(Go) | DDL/DCL/TSO/集群拓扑/任务调度 |
| 执行层 | Streaming Node / Query Node / Data Node(Go) | 流式写入、历史查询、索引与压缩 |
| 向量引擎 | Knowhere(C++) | 封装 Faiss/HNSWlib/DiskANN/SCANN,SIMD 加速 |
| 元数据 | etcd | 分布式键值存储 |
| 对象存储 | MinIO / AWS S3 / Azure Blob | 日志、索引、段数据 |
| WAL/消息 | Woodpecker / Kafka / Pulsar(单机可内嵌 RocksMQ) | 流式持久化 |
| GPU | NVIDIA RAFT(CUDA) | GPU 索引与搜索(GPU_CAGRA 等) |
| SIMD | SSE / AVX2 / AVX512 | CPU 向量运算加速 |
8.2 索引对比速查(一张图记住)
| 索引 | 数据结构 | 量化 | 内存 | 速度 | 精度 | 适用 |
|---|---|---|---|---|---|---|
| FLAT | 暴力 | 无 | 高 | 慢 | 100% | 小数据集 |
| IVF_FLAT | IVF | 无 | 中 | 中 | 高 | 通用 |
| IVF_SQ8 | IVF | SQ | 低 | 中 | 较高 | 内存约束 |
| IVF_PQ | IVF | PQ | 极低 | 中 | 中 | 大容量 |
| HNSW | HNSW 图 | 无 | 高 | 极快 | 高 | 低延迟 |
| DISKANN | Vamana 图 | PQ | 极低 | 中 | 高 | 十亿级 |
| SCANN | IVFPQ | 非对称 | 低 | 快 | 较高 | 高维 |
| GPU_CAGRA | GPU 图 | 无 | GPU | 极快 | 高 | GPU 环境 |
8.3 和其他向量数据库怎么选
| 维度 | Milvus | Chroma | Pinecone | Qdrant | Weaviate |
|---|---|---|---|---|---|
| 开源 | ✓ Apache 2.0 | ✓ Apache 2.0 | ✗ SaaS | ✓ | ✓ 开源 |
| 架构 | 四层存算分离微服务 | 嵌入式 / Client-Server | 全托管云服务 | 单机/分布式 | 单机/分布式 |
| 开发门槛 | 中 | 极低 | 低 | 低 | 低 |
| 索引数量 | 15+ 种(含 GPU) | 1 种(HNSW) | 专有 | HNSW 为主 | HNSW 为主 |
| GPU 支持 | ✓ | ✗ | — | ✗ | ✗ |
| 一致性 | Strong/Bounded/Session/Eventually | Strong | Strong | — | — |
| 规模上限 | 百亿级 | 百万~亿级 | 弹性 | 亿级 | 亿级 |
| 运维复杂度 | 高 | 极低 | 零(付费) | 低~中 | 低~中 |
| 适用场景 | 企业级生产、大模型训练检索 | RAG 原型、中小规模 | 免运维生产 | 生产级搜索 | RAG、多模态、知识图谱 |
8.4 一句话选型
- RAG 原型 / 个人项目 → Chroma(零配置,10 行代码开工)
- 中小规模生产(百万级) → Chroma / Qdrant(简单部署,够用)
- 大规模生产(千万~亿级) → Milvus / Qdrant(分布式、高吞吐)
- 超大规模(十亿级+) → Milvus / Pinecone(存算分离、自动扩展)
- GPU 加速需求 → Milvus(GPU_CAGRA 等 GPU 索引)
- 全托管免运维 → Pinecone / Zilliz Cloud(SaaS 服务)
- 企业私有部署 → Milvus / Qdrant(开源、可控)
也要知道 Milvus 的"脾气":部署复杂度高(分布式需要 K8s + etcd + MinIO + Kafka)、资源需求大(至少 8-16GB 内存起步)、学习曲线陡,而且不内置 embedding 功能(需要自行集成嵌入模型)——选它之前,先确认你的量级和运维预算配不配得上它。
九、金句总结(看完只需要记住这几句)
- Milvus = 四层存算分离(Proxy/Coordinator/Worker/Storage)+ 15+ 种索引(Knowhere 统一管理)+ 流批分离的写入查询流水线,专为百亿级生产场景而生。
- "存算分离"是灵魂:计算节点无状态、随便扩,数据统一躺对象存储,元数据归 etcd,谁干活谁取数。
- Segment 三段式人生:Growing(内存草稿箱,写入即查)→ Sealed(对象存储定稿)→ Indexed(异步建索引上架)——写入即查询靠 Growing 兜底,快查询靠异步索引。
- IVF = 先分桶再找:
nlist定书架数量,nprobe定翻几个书架,复杂度 O(nprobe × N/nlist)。 - HNSW = 跳表的高维版:上层"高速公路"大跨步,第 0 层精确找,复杂度 O(log N);
M管邻居密度,efConstruction管盖楼质量,ef管找房耐心。 - SQ 是"逐个数值压缩",PQ 是"分段后用编号压缩"——结构管往哪找,量化管省着存。
- 写入先记账(WAL)再干活:确认持久化才返回成功,崩溃也不丢数据——兵马未动,粮草先行。
- 查询是"海选 → 复赛 → 决赛":Query Node 段级 topK → Streaming Node 合并 → Proxy 多级归约,MPP 打法的精髓。
课后思考题(检验你是否真懂了):
- 为什么 Milvus 要把索引构建做成异步的?如果改成同步构建,写入链路会发生什么?
- IVF 的
nprobe和 HNSW 的ef在思路上是不是"同一个道理"?它们的共同 trade-off 是什么? - "写入即查询"的新数据其实是用暴力搜索兜底的——那为什么大规模场景下依然能接受?
- 为什么说 Milvus 的"存算分离"让它比单机方案更适合弹性伸缩?数据放对象存储不是多了一次 IO 吗?
参考资料
官方文档
- Milvus 官方文档
- Milvus Architecture Overview(架构概览)
- Milvus Main Components(主要组件)
- Milvus Data Processing(数据处理)
- Milvus Index Explained(索引详解)
- Milvus Vector Indexes(向量索引列表)
- Knowhere Vector Engine(Knowhere 引擎)
- Milvus GitHub
学术论文
- HNSW - Malkov & Yashunin (2016/2018)
- FAISS - Johnson et al. (2019)
- DiskANN - Subramanya et al. (NeurIPS 2019)
- CAGRA - Ootomo et al. (2023)
- SCANN - Guo et al. (2020)
如果这篇文章对你有帮助,欢迎点赞、收藏、关注。下一篇可以聊聊:Milvus 与 Qdrant / Weaviate 在生产环境的实战对比,咱们下期见。
更多推荐



所有评论(0)