为什么说 Milvus 2.0 是云原生的?它如何通过“存算分离”实现无限扩展?从 Kafka 的多路消费到 HNSW 的参数调优,本文配合详细架构图,深度拆解 Milvus 的内核机制。
在 RAG 系统中,向量数据库(Vector Database)是长时记忆的载体。许多开发者在初次接触时,往往会选择 Faiss 这样的轻量级库。但当数据量突破千万级,或者需要高并发、高可用(HA)支持时,架构完善的数据库系统就成为了必然选择。
Milvus 是目前最主流的云原生向量数据库之一。与传统数据库不同,它采用了存储与计算完全分离的设计。这意味着,如果你还没看懂它的架构,你可能正在浪费大量的服务器资源,或者在面对“为什么刚插的数据搜不到?”这种问题时束手无策。
今天,我们通过一张图,并结合日志流转和索引原理,彻底看懂 Milvus 是如何工作的。
一、 一张图看懂 Milvus 架构
Milvus 的架构设计遵循“云原生(Cloud-native)”原则,整体分为四个层次。其中最核心的设计哲学是:日志即数据(Log as Data)。
请看下面的架构逻辑图:
graph TD
%% Define Styles
classDef storage fill:#e1f5fe,stroke:#01579b,stroke-width:2px;
classDef compute fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;
classDef coord fill:#fff3e0,stroke:#ef6c00,stroke-width:2px;
classDef access fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px;
%% Client Layer
Client([SDK / REST API]) --> LB[Load Balancer]
%% Layer 1: Access Layer
subgraph "接入层 (Access Layer)"
LB --> Proxy1[Proxy Node]
LB --> Proxy2[Proxy Node]
end
class Proxy1,Proxy2 access;
%% Layer 2: Coordinator Service
subgraph "协调服务 (Coordinator Service)"
RootC[Root Coord<br/>(DDL/时间授时)]
QueryC[Query Coord<br/>(查询调度)]
DataC[Data Coord<br/>(数据落盘管理)]
IndexC[Index Coord<br/>(索引构建)]
end
class RootC,QueryC,DataC,IndexC coord;
%% Layer 3: Worker Nodes (Execution)
subgraph "执行节点 (Worker Nodes)"
QN[Query Node<br/>(实时+历史搜索)]
DN[Data Node<br/>(持久化/刷盘)]
IN[Index Node<br/>(构建索引)]
end
class QN,DN,IN compute;
%% Connections
Proxy1 -- DML/DDL --> RootC
Proxy1 -- Insert --> MsgStream
Proxy1 -- Search --> QN
QueryC -- 负载均衡 --> QN
DataC -- Checkpoint --> MsgStream
IndexC -- 调度任务 --> IN
%% Layer 4: Storage Layer
subgraph "存储层 (Shared Storage)"
Meta[Etcd<br/>(元数据存储)]
MsgStream[Log Broker<br/>(Pulsar / Kafka)<br/>消息骨干]
ObjStore[Object Storage<br/>(MinIO / S3)<br/>冷数据仓库]
end
class Meta,MsgStream,ObjStore storage;
%% Data Flow
MsgStream -->|订阅流式数据 (实时)| QN
MsgStream -->|订阅流式数据 (归档)| DN
DN -->|Flush 落盘| ObjStore
ObjStore -->|Load 历史数据| QN
ObjStore -->|读取原始向量| IN
IN -->|写入索引文件| ObjStore
核心层级详解
让我们像剥洋葱一样,由外向内解析这四个层级。
1. 接入层 (Access Layer):系统的“大门”
- 组件: Proxy
- 职责: 无状态的 API 前端。
- 核心机制:
- 请求验证: 验证你的用户名密码、检查请求格式。
- 结果聚合 (Global Reduce): 当你发起一次搜索时,可能需要从多个 Query Node 查数据。Proxy 负责把这些节点返回的局部 Top-K 结果收集起来,进行全局排序,剪枝后返回给客户端。
- 扩展性: 因为它是无状态的,如果并发请求量太大(比如每秒 10 万次查询),你只需要多启动几个 Proxy 容器,配合 Nginx 负载均衡即可。
2. 协调服务 (Coordinator Service):系统的“大脑”
这层不干苦力活,只负责发号施令。
- Root Coord: 总管。处理创建表(Collection)、删除分区等 DDL 操作。
- Query Coord: 也是 RAG 开发者最需要关注的。它负责查询节点的负载均衡。
- 场景: 如果某个 Query Node 内存爆了,Query Coord 会把部分数据段(Segment)迁移到其他空闲节点上。
- Data Coord & Index Coord: 分别管理数据怎么落盘、索引什么时候建。
3. 执行节点 (Worker Nodes):系统的“四肢”
这是真正消耗 CPU 和内存的地方。
- Query Node (查询节点): 最核心组件。它负责在内存中进行向量检索。它需要同时处理两类数据:
- 增量数据: 刚插入、还在消息队列里的热数据。
- 历史数据: 已经存入 S3、建立了索引的冷数据。
- Data Node: 负责“搬运”。它从消息队列里把流式数据读出来,打包成文件,扔到 S3 里去。
- Index Node: 负责“压缩”。它读取原始向量,构建 HNSW 或 IVF 索引文件,再写回 S3。
4. 存储层 (Storage):系统的“地基”
Milvus 的精髓在于共享存储(Shared Storage)。
- Meta Store (Etcd): 存储配置和元数据。
- Log Broker (Pulsar/Kafka): 这是 Milvus 的大动脉。
- Milvus 是基于日志的(Log-based)。所有的插入、删除操作,首先都是写入 Pulsar。
- 这意味着:只要数据进了 Pulsar,就算整个集群断电,数据也不会丢。
- Object Storage (MinIO/S3): 数据的最终归宿。
- 为什么用 S3? 因为向量数据太大了。内存存不下,本地磁盘扩容难。S3 便宜且容量无限。Query Node 需要查历史数据时,直接从 S3 下载加载到内存。
数据的生命周期:从写入到被搜到
理解了架构,我们看看一条数据是如何流转的。
写入流程 (Insert)
- 客户端发送 insert 请求给 Proxy。
- Proxy 将数据打包,写入 Log Broker (Kafka)。
- 注意:此时 Proxy 就可以给客户端返回“成功”了。
- Data Node 从 Pulsar 订阅消息,积累到一定程度(比如 512MB),将其 Flush(刷盘)到 MinIO。
- Index Coord 发现 MinIO里有了新文件,安排 Index Node 为其构建索引。
搜索流程 (Search)
- 客户端发送 search 请求给 Proxy。
- Proxy 解析路由,请求发给 Query Node。
- Query Node 执行混合搜索:
- 在内存中的历史数据段(已从 MinIO加载)中搜。
- 在内存中的增量数据段(直接从 Kafka订阅的实时数据)中搜。
- Query Node 将两部分结果合并(Local Reduce),返回给 Proxy。
- Proxy 进行全局合并(Global Reduce),返回给客户端。
二、 Log Broker 的双重身份
在 Milvus 架构中,Log Broker (Kafka) 处于绝对的中心位置。Milvus 让 Data Node 和 Query Node 同时订阅 Kafka,也就是 “多路消费(Multi-Consumer)” 机制。
1. Data Node:为了“存” (Persistence)
- 角色: 它是 Kafka 的归档员。
- 任务: 订阅数据,缓存在内存,积攒到一定大小(如 512MB)后,打包并 Flush(刷盘) 到对象存储(S3/MinIO)。
- 结果: 生成了历史数据(Sealed Segment),永久保存。
2. Query Node:为了“搜” (Real-time Search)
- 角色: 它是 Kafka 的实时解说员。
- 痛点: Data Node 刷盘到 MinIO是有延迟的。如果只读 MinIO,用户刚插的数据要等几十秒才能搜到。
- 任务: 绕过 MinIO,直接监听 Kafka。 它把从 Kafka 读到的最新消息直接转化为可计算的向量,存入内存的增长段(Growing Segment)。
- 结果: 实现了即时搜索(Real-time Search)。
形象比喻:
Kafka 是比赛直播流。Data Node 是录像员,把直播录成光盘(MinIO)存档;Query Node 是现场解说,盯着直播流(Kafka)看,观众问比分(Search),他能立刻回答,不需要等光盘刻录好。
3. 如何保证一致性?(TimeTick 机制)
Query Node 和 Data Node 并行消费,怎么保证步伐一致?
Milvus 引入了 TimeTick(时间滴答):
- 授时: Root Coord 周期性向 Kafka 发送带时间戳的消息(如
ts=100)。 - 对齐: Query Node 收到
ts=100时,就知道“100 之前的所有数据都到齐了”。如果用户查询请求要求的时间戳是 98,Query Node 就可以放心地执行搜索;如果是 102,它会阻塞等待下一个滴答。
4. 数据何时删除?(Checkpoint)
Kafka 里的数据不会永久保留。
- Checkpoint: 当 Data Node 成功把数据存入 MinIO后,会向协调者汇报一个 Checkpoint(检查点)。
- 清理: 意味着 Checkpoint 之前的数据已经安全了。Milvus 依赖 Kafka 自身的 Retention Policy(保留策略) 自动删除旧日志。
- Pro Tip: 生产环境中,Kafka 的保留时间(如 24h)务必大于 Milvus 的刷盘间隔,防止数据还没落盘就被 Kafka 删了。
三、索引类型选型:RAG 怎么选?
架构决定了上限,索引决定了速度。在 Milvus 中,你必须为向量字段创建索引才能高效搜索。以下是 RAG 场景的三大主流选择:
建议
- 刚开始做 RAG: 无脑选 HNSW。参数设置 M=16, efConstruction=200,这是一个在速度和精度上非常平衡的配置。
- 数据量超过 500 万且预算有限: 换成 IVF_SQ8。你会发现内存占用瞬间下降 70%,而检索准确率的肉眼体感差异并不大。
四、 索引类型与参数调优详解
架构决定了系统的上限,而索引(Index)决定了查询的快慢。在 Milvus 中,我们需要区分 “怎么找(图 vs 倒排)” 和 “怎么存(原始 vs 量化)”。
1. 怎么找:HNSW vs IVF
| 索引类型 | 全称 | 原理比喻 | 优缺点 |
|---|---|---|---|
| HNSW | Hierarchical Navigable Small World | 人际关系网。找马斯克不查电话簿,而是通过朋友 A -> 朋友 B -> 马斯克。 | 性能最强,召回极高。但构建慢,且非常吃内存(要存点与点的边关系)。 |
| IVF | Inverted File | 图书馆分类。把向量聚类成 1000 个簇。搜的时候只去最近的几个簇里找。 | 内存占用低,速度快。但召回率不如 HNSW 稳定(边界问题)。 |
2. 怎么存:Float vs SQ8 (量化)
这是节省成本的关键。
- Flat (Float32): 原汁原味。1024 维向量占用
1024 * 4 bytes = 4KB。精度最高。 - SQ8 (Scalar Quantization): 有损压缩。将 4 字节浮点数压缩为 1 字节整数。占用
1024 * 1 byte = 1KB。- 性价比之选: 内存占用减少 75%。虽然精度有细微损失,但在 RAG 场景下,Top-K 的排序结果几乎不受影响。
3. HNSW 关键参数详解
如果你选择了 HNSW(推荐),必须理解这两个参数:
M = 16(最大连接数)- 含义: 每个节点在图里最多能有几个“朋友”。
- 影响:
M越大,路越通畅,召回越高,但内存消耗急剧增加。16是一个在性能和内存间完美的平衡点(Goldilocks number)。
efConstruction = 200(构建搜索范围)- 含义: 建图时,为了找这 16 个朋友,我要考察多少个候选人。
- 影响:
efConstruction越大,建索引越慢,但图的质量越高(朋友找得准),以后的搜索召回率越高。设为200是为了“一次辛苦,永久受益”。
理解了这些底层逻辑,你就掌握了 RAG 系统的性能命脉。下一篇,我们将离开基础设施,进入 RAG 的“灵魂”——语义模型。我们将探讨 BERT、BGE 以及 Reranker 如何决定你的系统到底“懂不懂”用户的话。