深入理解 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 非要走分布式?

传统单机向量方案的瓶颈,说穿了就三条:

  1. FAISS / HNSWlib 只是索引库:没有持久化、没有分布式、没有 API,一切得自己造轮子;
  2. 单机内存撑不住:一台机器内存上限 1~2TB,百亿级向量(还是高维的)根本放不下;
  3. 企业级要求满足不了:高可用、容灾、弹性扩缩容,单机架构天生做不到。

所以 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 (最密集):    ●──●──●──●──●──●──●──●──●──●          ← 精确搜索层,包含所有节点

关键规则(面试必背):

  1. 第 0 层包含所有节点,连接局部最近的邻居;
  2. 上层是下层的随机子集,节点数以指数递减(默认 M=16 时,每层大约只剩 6.25%);
  3. 上层节点连接更远的邻居,形成"高速公路",让搜索能"大跨步";
  4. 同一节点在不同层之间垂直连接,方便"坐电梯下楼"。

搜索过程:从顶层随机入口开始 → 贪心移动到最近的邻居(横向跳)→ 本地找不到更近的就下钻一层 → 直到第 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 功能(需要自行集成嵌入模型)——选它之前,先确认你的量级和运维预算配不配得上它。


九、金句总结(看完只需要记住这几句)

  1. Milvus = 四层存算分离(Proxy/Coordinator/Worker/Storage)+ 15+ 种索引(Knowhere 统一管理)+ 流批分离的写入查询流水线,专为百亿级生产场景而生。
  2. "存算分离"是灵魂:计算节点无状态、随便扩,数据统一躺对象存储,元数据归 etcd,谁干活谁取数。
  3. Segment 三段式人生:Growing(内存草稿箱,写入即查)→ Sealed(对象存储定稿)→ Indexed(异步建索引上架)——写入即查询靠 Growing 兜底,快查询靠异步索引。
  4. IVF = 先分桶再找nlist 定书架数量,nprobe 定翻几个书架,复杂度 O(nprobe × N/nlist)。
  5. HNSW = 跳表的高维版:上层"高速公路"大跨步,第 0 层精确找,复杂度 O(log N);M 管邻居密度,efConstruction 管盖楼质量,ef 管找房耐心。
  6. SQ 是"逐个数值压缩",PQ 是"分段后用编号压缩"——结构管往哪找,量化管省着存。
  7. 写入先记账(WAL)再干活:确认持久化才返回成功,崩溃也不丢数据——兵马未动,粮草先行。
  8. 查询是"海选 → 复赛 → 决赛":Query Node 段级 topK → Streaming Node 合并 → Proxy 多级归约,MPP 打法的精髓。

课后思考题(检验你是否真懂了):

  • 为什么 Milvus 要把索引构建做成异步的?如果改成同步构建,写入链路会发生什么?
  • IVF 的 nprobe 和 HNSW 的 ef 在思路上是不是"同一个道理"?它们的共同 trade-off 是什么?
  • "写入即查询"的新数据其实是用暴力搜索兜底的——那为什么大规模场景下依然能接受?
  • 为什么说 Milvus 的"存算分离"让它比单机方案更适合弹性伸缩?数据放对象存储不是多了一次 IO 吗?

参考资料

官方文档

学术论文


如果这篇文章对你有帮助,欢迎点赞、收藏、关注。下一篇可以聊聊:Milvus 与 Qdrant / Weaviate 在生产环境的实战对比,咱们下期见。

更多推荐