Milvus 数据分片、扩展与高并发设计
码海寻道 · 大模型、智能体与 RAG 工程组件系列第 19 篇

当 Milvus 从单机开发环境进入生产环境,问题会从"能不能检索"变成"高峰期能不能稳定检索"。这时需要理解三个概念:数据如何分布、组件如何扩展、查询资源如何隔离。
而在讨论扩展之前,必须先区分三种部署形态:
| 形态 | 适合场景 | 组件特征 | 主要限制 |
|---|---|---|---|
| Milvus Lite | Python 本地实验、单元测试、小型 Demo | 以库或轻量方式运行,依赖少 | 不适合作为多服务生产集群 |
| Standalone | 本地开发、测试环境、中小规模单机服务 | Milvus 主要服务集中在单机,通常配合 etcd 与 MinIO | 计算资源和故障域集中,不能按角色独立扩展 |
| Distributed | 生产集群、较高并发、需要独立扩容 | Proxy、Coordinator、Query/Data/Index Node 等角色可拆分扩展 | 部署、监控、备份和升级复杂度更高 |
Standalone 不是"没有 etcd 和 MinIO"。在常见 Docker Compose 方案中,仍会有 milvus-etcd、milvus-minio 和 milvus-standalone 三类服务;只是 Milvus 的多个计算角色被集中在一个实例中。某些嵌入式安装方式会把 etcd 放进同一容器,不能据此推断生产环境也应该把所有依赖揉在一个容器里。
一、先区分三种"分开"
1. Partition:逻辑分组
Partition 是 Collection 内部的数据组织方式,查询时可以只搜索指定分区。它主要解决逻辑范围和查询裁剪问题。
2. Shard:写入流分片
Shard 用于把写入流分散到不同通道和处理节点,帮助提升数据写入和流式处理能力。它不是"按租户建分区"的同义词。
3. Query Node 扩展:搜索计算扩容
增加 Query Node 是把查询计算能力横向扩展。它解决的是并发和查询资源问题,不会自动替代数据建模、索引和过滤设计。
text
Partition:按什么逻辑范围搜索
Shard:写入流如何分散
Query Node:搜索计算如何扩展
二、Standalone、etcd、MinIO 和 Distributed 如何配合?
1. Standalone:把 Milvus 主要服务集中运行
Standalone 的优点是启动和调试简单。应用通过 19530 连接 Milvus,管理页面通常通过 9091 访问;etcd 和 MinIO 主要供 Milvus 内部使用。它适合让开发者快速验证 Collection、索引、插入和检索代码。
它的边界也很清楚:查询、导入、索引构建和故障域都集中在一台机器上。即使底层 MinIO 使用了持久化卷,Standalone 也不等于查询服务高可用。
2. etcd:小而关键的元数据服务
etcd 保存 Milvus 运行所需的关键元数据,并参与服务注册和健康检查。它保存的是"数据在哪里、集合是什么结构、哪些服务存在"等控制信息,而不是每个 Chunk 的完整文本和向量。
etcd 对磁盘延迟和稳定性比较敏感。生产部署中应使用可靠的 SSD、持久化数据卷和备份方案,不要把 etcd 当作可以随意清空的缓存。etcd 不健康时,Milvus 可能无法正确读取集合、索引或节点状态,即使 MinIO 中的数据文件仍然存在。
3. MinIO / S3:持久化对象存储
Milvus 使用对象存储保存数据文件、索引文件以及部分持久化对象。MinIO 是常见的自建 S3 兼容方案,云上也可以使用 S3、OSS、COS、OBS 等兼容对象存储。
对象存储的优势是容量可扩展、计算与存储解耦;代价是网络访问延迟、权限配置、生命周期管理和备份恢复都需要单独设计。Milvus 的 MinIO Bucket 不建议和业务原始文件共用无隔离的目录,至少要区分 Bucket、账号、前缀和备份策略。
4. WAL 与消息队列:让写入可以恢复和传播
Milvus 的写入通常不会直接等同于"把一行数据写进某个查询节点"。变更需要先进入可靠的日志或消息流,再由数据服务消费、持久化并让查询侧逐步可见。这个角色通常被称为 WAL(Write-Ahead Log)或消息队列,常见实现包括 Woodpecker、Kafka、Pulsar,以及特定版本中的 RocksMQ。
在官方 Standalone 方案中,消息能力可能采用嵌入式实现,不一定出现一个单独的消息队列容器;在 Distributed 部署中,则需要根据版本和吞吐目标评估外部消息系统。它解决的是"写入事件如何可靠传播和恢复",不是向量相似度索引,也不是 RAG 应用自己的 RabbitMQ 任务队列。
text
客户端写入
↓
WAL / 消息流:记录可恢复的变更
↓
Data Node:消费并持久化
↓
对象存储:保存数据文件
↓
Query Node:加载后提供检索
因此,遇到"写入成功但查询暂时不可见"时,除了检查 etcd 和 MinIO,还要检查 WAL/消息链路的积压、消费错误和可见性延迟。
5. Distributed:按角色扩展 Milvus
在 Distributed 部署中,可以根据瓶颈增加 Query Node、Data Node 或 Index Node,并由 Proxy 和 Coordinator 负责请求路由、任务调度及状态协调。外部 etcd、对象存储和消息系统也需要按高可用方式部署。
text
客户端
↓
Proxy
├── Query Coord → Query Nodes → MinIO/S3 中的数据与索引
├── Data Coord → Data Nodes → WAL/对象存储
└── Index Coord → Index Nodes → MinIO/S3 中的索引文件
↑
etcd:元数据、服务注册、健康状态
不同 Milvus 版本的组件名和消息队列实现可能变化,架构图用于理解职责,不应当当作固定的容器清单。
三、Milvus 为什么可以水平扩展?
Milvus 的架构强调计算与存储分离。对象存储保存持久化数据和索引文件,计算节点负责数据处理、索引构建或查询执行。
简化理解如下:
text
客户端
↓
Proxy / Access Layer
↓
Coordinator
├── Query Nodes:搜索与查询
├── Data Nodes:数据写入与持久化流
└── Index Nodes:索引构建
↓
对象存储 + 元数据存储
不同版本和部署模式的组件细节可能不同,但工程原则是一致的:根据实际瓶颈扩展对应角色,而不是盲目增加所有节点。
四、什么时候需要扩展?
可以从以下信号判断:
- P95/P99 查询延迟持续升高;
- Query Node CPU 或内存长期接近上限;
- 查询并发增加后出现排队;
- 索引构建影响在线搜索;
- 数据写入速度跟不上知识库导入速度;
- 服务需要跨节点容错和高可用。
不要只根据向量数量扩容。相同数量的向量,在不同维度、索引、过滤条件和查询并发下,资源需求可能完全不同。
五、读写负载应该分开看
RAG 系统通常有两类负载:
在线检索
用户提问需要低延迟返回,关注 P95、P99、并发和查询节点资源。
离线建库
批量解析、Embedding、插入和索引构建,关注吞吐、队列积压和任务失败率。
如果离线导入和在线检索共用资源,可能出现"批量建库时线上变慢"。应通过任务调度、资源隔离、错峰构建或独立节点降低互相影响。
六、租户隔离如何设计?
多租户知识库有三种常见方式:
方案 A:同一 Collection + tenant_id 过滤
优点是结构简单、租户数量扩展容易;缺点是每次搜索都必须带上安全过滤条件。
方案 B:有限数量的 Partition
适合少量稳定的数据域,例如公开知识、内部知识和归档知识。不要为数万租户创建数万 Partition。
方案 C:不同租户使用不同 Collection
隔离强,但集合管理、索引构建、资源利用和运维成本更高,适合少量重要租户或强隔离场景。
权限设计必须由业务服务、数据库权限和检索过滤共同完成。Milvus 的数据组织方式不能单独承担全部授权责任。
七、高并发检索的关键参数
1. 控制 Top K 和候选范围
Top K 过大、IVF 的 nprobe 过高、HNSW 的 ef 过大,都会增加查询成本。先确定答案所需的最小上下文,再反推召回范围。
2. 避免重复查询
相同问题、相同租户和相同知识库版本可以在 Redis 中做短时缓存。缓存命中时,不必重复执行 Embedding 和 Milvus 搜索。
3. 控制批量导入
把大批量写入拆成可观测、可重试的批次,记录批次 ID、向量模型版本和写入结果,避免单个超大请求阻塞服务。
4. 保护服务边界
在 API 层设置并发限制、超时和取消机制。不要让客户端直接无限制调用 Milvus,也不要把 Milvus 的内部错误原样返回给用户。
八、扩展前必须建立的指标
text
请求层:QPS、错误率、P50/P95/P99
检索层:Top K、过滤命中率、空结果率
资源层:CPU、内存、磁盘、网络
数据层:写入吞吐、任务积压、索引构建时长
业务层:Recall@K、答案引用正确率、用户反馈
如果只有 CPU 和内存,没有 Recall@K 与答案质量,就无法判断扩容是否真的解决了用户问题。
九、一个生产化的流量分层示意
text
用户请求
↓
API Gateway:鉴权、限流、超时
↓
RAG 服务:问题改写、Embedding、过滤条件
↓
Redis:热点查询缓存
↓
Milvus:向量检索
↓
Reranker:精排
↓
大模型:生成答案
文档导入走另一条链路:
text
上传文件 → 对象存储 → 消息队列 → 解析/切分/Embedding → 批量写入 Milvus
在线检索和离线建库分开后,系统更容易分别扩容和排障。
十、不要过早上 Kubernetes
Milvus Distributed 和 Kubernetes 适合对弹性、可用性和规模有明确要求的团队,但它们也带来更高的部署和运维复杂度。
如果项目还在验证阶段,可以先使用 Milvus Lite 或 Standalone,等数据规模、并发和可用性目标明确后再升级部署形态。架构升级应由指标驱动,而不是由"生产环境必须上集群"的想象驱动。
十一、扩容与缩容的注意事项
- 一次只调整一个变量,避免无法判断收益;
- 扩容前记录基线,扩容后比较同一批流量;
- 缩容要观察服务可用性和数据副本状态;
- 索引、对象存储和元数据备份不能省略;
- 高可用不等于没有数据同步和恢复演练。
结语
Milvus 的高并发设计不是简单地"多启动几个节点"。它需要把 Partition、Shard、Query Node、在线检索、离线建库、租户隔离和指标体系放在一起考虑。
下一篇将集中处理实际开发中最常见的问题:检索不到、向量维度错误、刚写入数据不可见,以及数据一致性如何判断。
参考资料
- Milvus 官方文档:Architecture Overview
- Milvus 官方文档:Scale a Milvus Cluster
- Milvus 官方文档:Run Milvus with Docker Compose
- Milvus 官方文档:Milvus Architecture
本文为"码海寻道"原创技术文章。集群组件、扩展方式和部署建议会随 Milvus 版本变化,生产部署请结合目标版本验证。