Milvus底层原理(一):概述与架构设计
前言
随着大语言模型(LLM)的爆发式发展,向量数据库作为 AI 应用基础设施的核心组件,正在经历前所未有的关注。从 RAG(检索增强生成)到推荐系统,从语义搜索到异常检测,向量数据库支撑着越来越多的 AI 应用场景。
Milvus 作为全球最流行的开源向量数据库之一,以其高性能、云原生、可扩展的架构设计,被众多企业用于生产环境。本系列将从底层源码角度深入剖析 Milvus 的实现原理,帮助读者不仅"知其然",更"知其所以然"。本文作为系列开篇,将从宏观视角介绍 Milvus 的整体架构设计。
技术亮点
| 技术点 | 难度 | 面试价值 | 本文覆盖 |
|---|---|---|---|
| 向量相似度搜索原理 | ⭐⭐⭐ | 高频考点 | ✅ |
| 存储计算分离架构 | ⭐⭐⭐⭐ | 架构设计 | ✅ |
| Milvus 分布式组件 | ⭐⭐⭐ | 进阶考点 | ✅ |
| 向量索引分类 | ⭐⭐⭐ | 高频考点 | ✅ |
| Segment 数据模型 | ⭐⭐⭐⭐ | 源码级 | ✅ |
面试考点
- 什么是向量数据库?与传统数据库有什么区别?
- Milvus 的架构是如何实现存储计算分离的?
- Milvus 有哪些核心组件?各自的作用是什么?
- 向量索引有哪些类型?各自的适用场景是什么?
- Milvus 如何实现分布式查询?
一、向量数据库概述
1.1 什么是向量数据库
向量数据库是一种专门用于存储、索引和查询高维向量数据的数据库系统。它能够高效地进行向量相似度搜索,支持大规模向量数据的存储和检索。
┌─────────────────────────────────────────────────────────────┐
│ 向量数据库核心定位 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 语义搜索 │ │ 推荐系统 │ │ RAG 应用 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 图像检索 │ │ 异常检测 │ │ 问答系统 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 向量数据的特点
向量数据是将非结构化数据(文本、图像、音频等)通过 Embedding 模型转换得到的高维数值表示:
# 文本转向量示例
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2')
# 将文本转换为 384 维向量
texts = ["Milvus 是一个向量数据库", "向量搜索在 AI 中很重要"]
vectors = model.encode(texts)
print(vectors.shape) # (2, 384)
print(vectors[0][:5]) # [0.123, -0.456, 0.789, ...]
向量数据的关键特征:
| 特征 | 说明 |
|---|---|
| 高维度 | 通常 128-2048 维,甚至更高 |
| 稠密/稀疏 | 稠密向量(所有维度有值)或稀疏向量(大部分为 0) |
| 语义相似性 | 相似内容的向量在空间中距离较近 |
| 计算密集 | 相似度计算需要大量浮点运算 |
1.3 相似度度量方法
向量数据库的核心能力是相似度搜索,常见的度量方法包括:
┌─────────────────────────────────────────────────────────────┐
│ 相似度度量方法 │
├───────────────┬─────────────────────────────────────────────┤
│ 度量方法 │ 公式与说明 │
├───────────────┼─────────────────────────────────────────────┤
│ L2 距离 │ d = √Σ(xᵢ - yᵢ)² │
│ (欧氏距离) │ 值越小越相似,适用于物理距离相关的场景 │
├───────────────┼─────────────────────────────────────────────┤
│ IP (内积) │ s = Σxᵢyᵢ │
│ (Inner Product)│ 值越大越相似,适用于已归一化的向量 │
├───────────────┼─────────────────────────────────────────────┤
│ COSINE │ s = (x·y) / (‖x‖‖y‖) │
│ (余弦相似度) │ 值越大越相似,适用于文本语义相似度 │
└───────────────┴─────────────────────────────────────────────┘
import numpy as np
def cosine_similarity(a, b):
"""余弦相似度计算"""
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
def l2_distance(a, b):
"""L2 距离计算"""
return np.linalg.norm(a - b)
# 示例
v1 = np.array([1.0, 2.0, 3.0])
v2 = np.array([1.5, 2.5, 3.5])
print(f"余弦相似度: {cosine_similarity(v1, v2):.4f}") # 0.9979
print(f"L2 距离: {l2_distance(v1, v2):.4f}") # 0.8660
1.4 向量数据库 vs 传统数据库
┌─────────────────────────────────────────────────────────────────────┐
│ 向量数据库 vs 传统数据库 │
├─────────────────┬─────────────────────┬─────────────────────────────┤
│ 特性 │ 传统数据库 │ 向量数据库 │
├─────────────────┼─────────────────────┼─────────────────────────────┤
│ 数据类型 │ 结构化数据 │ 向量 + 标量 │
│ 查询方式 │ 精确匹配 │ 近似最近邻(ANN) │
│ 索引结构 │ B+ Tree、Hash │ HNSW、IVF、DiskANN │
│ 查询结果 │ 精确结果 │ Top-K 相似结果 │
│ 典型场景 │ 事务处理、报表 │ 语义搜索、推荐 │
│ 代表产品 │ MySQL、PostgreSQL │ Milvus、Pinecone │
└─────────────────┴─────────────────────┴─────────────────────────────┘
二、Milvus 整体架构
2.1 架构分层
Milvus 采用云原生的存储计算分离架构,从上到下分为四层:
┌─────────────────────────────────────────────────────────────────────┐
│ 接入层 (Access Layer) │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Proxy │ │
│ │ • 请求路由 • 结果归并 • 权限校验 • 限流控制 │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 协调服务层 (Coordinator Layer) │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Root Coord │ │ Query Coord│ │ Data Coord │ │ Index Coord│ │
│ │ 元数据管理 │ │ 查询调度 │ │ 数据管理 │ │ 索引管理 │ │
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 执行层 (Worker Layer) │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Query Node │ │ Data Node │ │ Index Node │ │ 流式节点 │ │
│ │ 向量查询 │ │ 数据写入 │ │ 索引构建 │ │ 流处理 │ │
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 存储层 (Storage Layer) │
│ ┌────────────────────┐ ┌────────────────────┐ ┌──────────────┐ │
│ │ Meta Store │ │ Log Broker │ │ Object Store │ │
│ │ (etcd/MySQL) │ │ (Pulsar/Kafka) │ │ (MinIO/S3) │ │
│ │ 元数据存储 │ │ 日志流 │ │ 向量数据 │ │
│ └────────────────────┘ └────────────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
2.2 存储计算分离的优势
┌─────────────────────────────────────────────────────────────────────┐
│ 存储计算分离的优势 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. 独立扩展 │ │
│ │ • 计算节点可根据查询负载独立扩缩容 │ │
│ │ • 存储节点可根据数据量独立扩容 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 2. 成本优化 │ │
│ │ • 计算节点使用高性能 SSD │ │
│ │ • 存储节点使用廉价对象存储 │ │
│ │ • 无状态计算节点可随时释放 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 3. 高可用 │ │
│ │ • 存储层多副本保证数据安全 │ │
│ │ • 计算节点故障可快速恢复 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.3 核心组件详解
2.3.1 Proxy(代理层)
Proxy 是 Milvus 的接入层,负责处理客户端请求:
// pkg/proxy/proxy.go - Proxy 核心职责
type Proxy struct {
// 请求路由
func (p *Proxy) Search(ctx context.Context, req *SearchRequest) {
// 1. 解析请求,确定目标 Shard
// 2. 将请求分发到多个 QueryNode
// 3. 收集并归并结果
// 4. 返回 Top-K 结果
}
// 结果归并
func (p *Proxy) reduceSearchResults(results []*SearchResult) {
// 对多个 QueryNode 的结果进行归并排序
// 返回全局 Top-K
}
}
Proxy 的主要职责:
| 功能 | 说明 |
|---|---|
| 请求解析 | 解析 gRPC 请求,验证参数合法性 |
| 路由分发 | 根据分区/分片信息将请求路由到正确的节点 |
| 负载均衡 | 在多个副本间进行负载均衡 |
| 结果归并 | 将多个节点的查询结果归并排序 |
| 限流控制 | 请求速率限制,防止系统过载 |
2.3.2 Root Coordinator(根协调器)
Root Coordinator 负责全局元数据管理:
// pkg/rootcoord/root_coord.go
type RootCoordinator struct {
// 元数据操作
func (c *RootCoordinator) CreateCollection(ctx, req) {
// 1. 分配 Collection ID
// 2. 创建 Collection Schema
// 3. 分配 Partition ID
// 4. 通知 DataCoord 创建 Segment
// 5. 持久化元数据到 etcd
}
// 分片管理
func (c *RootCoordinator) AllocID(ctx) (int64, error) {
// 分配全局唯一 ID(雪花算法)
}
}
Root Coordinator 管理的元数据:
- Collection(集合)Schema
- Partition(分区)信息
- Segment(数据段)元数据
- 字段索引信息
- 全局时间戳分配
2.3.3 Query Coordinator(查询协调器)
Query Coordinator 负责查询调度和负载均衡:
// pkg/querycoord/query_coord.go
type QueryCoordinator struct {
// 查询节点管理
func (c *QueryCoordinator) LoadCollection(ctx, req) {
// 1. 获取 Collection 的所有 Segment
// 2. 将 Segment 分配到 QueryNode
// 3. 监控加载进度
// 4. 更新路由表
}
// 负载均衡
func (c *QueryCoordinator) BalanceSegments() {
// 定期检查 QueryNode 负载
// 迁移 Segment 实现均衡
}
}
Query Coordinator 的核心职责:
- QueryNode 注册与心跳管理
- Segment 到 QueryNode 的分配
- 查询负载均衡
- 副本管理
2.3.4 Data Coordinator(数据协调器)
Data Coordinator 负责数据写入和 Segment 管理:
// pkg/datacoord/data_coord.go
type DataCoordinator struct {
// Segment 管理
func (c *DataCoordinator) AllocSegment(ctx, req) {
// 1. 选择合适的 Channel
// 2. 分配新的 Segment ID
// 3. 记录 Segment 元数据
}
// 数据 Flush
func (c *DataCoordinator) Flush(ctx, req) {
// 1. 通知 DataNode 执行 Flush
// 2. 更新 Segment 状态为 Flushed
// 3. 触发索引构建
}
}
2.3.5 Index Coordinator(索引协调器)
Index Coordinator 负责索引构建任务调度:
// pkg/indexcoord/index_coord.go
type IndexCoordinator struct {
// 索引任务调度
func (c *IndexCoordinator) CreateIndex(ctx, req) {
// 1. 创建索引元数据
// 2. 为每个 Segment 创建索引任务
// 3. 分配任务到 IndexNode
// 4. 监控构建进度
}
}
2.4 Worker 节点详解
2.4.1 Query Node(查询节点)
Query Node 是查询的执行单元,负责向量搜索:
// pkg/querynode/query_node.go
type QueryNode struct {
// 向量搜索
func (n *QueryNode) Search(ctx, req) *SearchResult {
// 1. 从 Segment 加载向量数据
// 2. 执行索引搜索或暴力搜索
// 3. 应用标量过滤条件
// 4. 返回 Top-K 结果
}
// 流式数据消费
func (n *QueryNode) ConsumeInsertData() {
// 从 DML Channel 消费增量数据
// 写入 growing segment
}
}
Query Node 的数据管理:
┌─────────────────────────────────────────────────────────────────┐
│ Query Node 数据结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Historical Data │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │Segment 1│ │Segment 2│ │Segment 3│ ... (Sealed) │ │
│ │ │ 索引加载 │ │ 索引加载 │ │ 索引加载 │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Streaming Data │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Growing Segment │ │ │
│ │ │ 实时接收新数据,暴力搜索 │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
2.4.2 Data Node(数据节点)
Data Node 负责数据写入和持久化:
// pkg/datanode/data_node.go
type DataNode struct {
// 数据消费与写入
func (n *DataNode) ConsumeInsertMsg(msg *InsertMsg) {
// 1. 从 DML Channel 消费写入消息
// 2. 写入 Growing Segment
// 3. 当 Segment 满时触发 Flush
}
// 数据 Flush
func (n *DataNode) FlushSegment(segmentID int64) {
// 1. 将内存数据序列化
// 2. 写入对象存储(MinIO/S3)
// 3. 通知 DataCoord 更新元数据
}
}
2.4.3 Index Node(索引节点)
Index Node 负责向量索引的构建:
// pkg/indexnode/index_node.go
type IndexNode struct {
// 索引构建
func (n *IndexNode) CreateIndex(ctx, req) {
// 1. 从对象存储加载原始向量
// 2. 根据索引类型构建索引
// - HNSW: 构建图结构
// - IVF: 聚类 + 倒排列表
// - DiskANN: Vamana 图算法
// 3. 序列化索引文件
// 4. 上传到对象存储
}
}
三、数据模型
3.1 Collection(集合)
Collection 是 Milvus 中的顶层逻辑容器,类似于关系数据库中的表:
┌─────────────────────────────────────────────────────────────────┐
│ Collection 结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Collection: product_embeddings │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Schema: │ │
│ │ ┌──────────────┬──────────────┬──────────────┐ │ │
│ │ │ Field Name │ Field Type │ Description │ │ │
│ │ ├──────────────┼──────────────┼──────────────┤ │ │
│ │ │ id │ Int64 │ Primary Key │ │ │
│ │ │ title │ VarChar │ 商品标题 │ │ │
│ │ │ price │ Float │ 价格 │ │ │
│ │ │ embedding │ FloatVector │ 768 维向量 │ │ │
│ │ └──────────────┴──────────────┴──────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
# 创建 Collection 示例
from pymilvus import Collection, FieldSchema, CollectionSchema, DataType
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="title", dtype=DataType.VARCHAR, max_length=256),
FieldSchema(name="price", dtype=DataType.FLOAT),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=768)
]
schema = CollectionSchema(fields=fields, description="商品向量")
collection = Collection(name="product_embeddings", schema=schema)
3.2 Partition(分区)
Partition 是 Collection 的逻辑分区,用于加速查询:
┌─────────────────────────────────────────────────────────────────┐
│ Partition 结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Collection: product_embeddings │
│ ├── Partition: electronics (电子产品) │
│ │ ├── Segment 1 │
│ │ ├── Segment 2 │
│ │ └── ... │
│ ├── Partition: clothing (服装) │
│ │ ├── Segment 3 │
│ │ └── ... │
│ └── Partition: books (图书) │
│ └── Segment 4 │
│ │
│ 查询时指定 Partition 可跳过无关数据,提升性能 │
└─────────────────────────────────────────────────────────────────┘
3.3 Segment(数据段)
Segment 是 Milvus 数据存储的最小单元,分为两种状态:
┌─────────────────────────────────────────────────────────────────┐
│ Segment 生命周期 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ 达到大小阈值 ┌─────────────┐ │
│ │ Growing │ ──────────────────► │ Sealed │ │
│ │ (增长中) │ │ (已封存) │ │
│ │ │ │ │ │
│ │ • 内存存储 │ │ • 对象存储 │ │
│ │ • 暴力搜索 │ │ • 索引搜索 │ │
│ │ • 实时写入 │ │ • 只读 │ │
│ └─────────────┘ └─────────────┘ │
│ │ │
│ Flush 完成│ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Flushed │ │
│ │ (已持久化) │ │
│ │ │ │
│ │ • 索引构建 │ │
│ │ • 可被加载 │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Segment 的内部结构:
┌─────────────────────────────────────────────────────────────────┐
│ Segment 内部结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Segment = 多个 Column(列式存储) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Column: id (Int64) │ │
│ │ [1, 2, 3, 4, 5, ...] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Column: title (VarChar) │ │
│ │ ["手机", "电脑", "耳机", ...] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Column: embedding (FloatVector) │ │
│ │ [[0.1, 0.2, ...], [0.3, 0.4, ...], ...] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Index File (可选) │ │
│ │ HNSW/IVF/DiskANN 索引文件 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
3.4 列式存储设计
Milvus 采用列式存储,针对向量数据优化:
// pkg/storage/column.go
type Column interface {
Name() string
Type() FieldType
Len() int
Get(i int) interface{}
}
// 向量列
type VectorColumn struct {
name string
dim int
data []float32 // 连续内存存储
}
func (c *VectorColumn) Get(i int) []float32 {
start := i * c.dim
return c.data[start : start+c.dim]
}
列式存储的优势:
| 优势 | 说明 |
|---|---|
| 压缩效率高 | 相同类型数据连续存储,压缩率高 |
| 查询效率高 | 只读取需要的列,减少 IO |
| 向量化计算 | 利用 SIMD 指令加速向量运算 |
四、向量索引概述
4.1 为什么需要索引
向量相似度搜索的核心挑战是高维空间中的"维度诅咒":
┌─────────────────────────────────────────────────────────────────┐
│ 向量搜索的挑战 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 暴力搜索时间复杂度: O(N × D) │
│ • N: 向量数量(可达数十亿) │
│ • D: 向量维度(通常 128-2048) │
│ │
│ 示例: 10 亿向量 × 768 维 = 7.68 亿次浮点运算/查询 │
│ │
│ 解决方案: 近似最近邻搜索 (ANN - Approximate Nearest Neighbor) │
│ • 牺牲少量精度换取数量级的速度提升 │
│ • 索引空间复杂度: O(N) 或 O(N × log N) │
│ • 查询时间复杂度: O(log N) 或 O(√N) │
│ │
└─────────────────────────────────────────────────────────────────┘
4.2 索引类型分类
Milvus 支持多种向量索引类型:
┌─────────────────────────────────────────────────────────────────┐
│ 向量索引分类 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 基于树的索引 │ │
│ │ KD-Tree, Ball Tree, Annoy │ │
│ │ 适用于低维数据,高维效果差 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 基于图的索引 │ │
│ │ HNSW, NSW, DiskANN (Milvus 重点支持) │ │
│ │ 高召回率、低延迟,内存消耗较大 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 基于聚类的索引 │ │
│ │ IVF-Flat, IVF-PQ, IVF-SQ8 (Milvus 重点支持) │ │
│ │ 构建快、内存可控,需调参 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 基于量化的索引 │ │
│ │ PQ, SQ8, OPQ │ │
│ │ 大幅压缩内存,精度有损失 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
4.3 Milvus 支持的索引类型
| 索引类型 | 适用度量 | 特点 | 适用场景 |
|---|---|---|---|
| FLAT | 所有 | 暴力搜索,精度最高 | 小数据量、高精度要求 |
| IVF_FLAT | 所有 | 聚类分桶,平衡精度速度 | 中等数据量 |
| IVF_PQ | 所有 | 聚类 + 量化,内存高效 | 大数据量、内存受限 |
| IVF_SQ8 | 所有 | 聚类 + 标量量化 | 内存优化场景 |
| HNSW | L2/COSINE | 图索引,高性能 | 高性能要求场景 |
| DISKANN | L2/COSINE | 磁盘索引,成本低 | 超大规模数据 |
| GPU_IVF_FLAT | 所有 | GPU 加速 | GPU 环境可用 |
| GPU_IVF_PQ | 所有 | GPU 加速 + 量化 | GPU 环境、大数据量 |
4.4 索引选择指南
# 索引选择决策树
def choose_index(data_size: int, memory_limit: int, latency_requirement: float):
"""
根据数据规模和需求选择合适的索引
"""
if data_size < 100_000:
# 小数据量,暴力搜索即可
return "FLAT"
if latency_requirement < 10: # ms
# 低延迟要求,选择图索引
if memory_limit > data_size * 768 * 4 * 1.5:
return "HNSW" # 内存充足
else:
return "DISKANN" # 使用磁盘索引
if memory_limit < data_size * 768 * 4 * 0.3:
# 内存严重不足,使用量化
return "IVF_PQ"
# 平衡选择
return "IVF_FLAT"
五、查询执行流程
5.1 搜索请求处理流程
一个完整的向量搜索请求在 Milvus 中的处理流程:
┌─────────────────────────────────────────────────────────────────┐
│ 搜索请求处理流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 客户端发送 SearchRequest │
│ │ │
│ ▼ │
│ 2. Proxy 接收请求 │
│ │ • 参数校验 │
│ │ • 获取 Collection 元数据 │
│ │ • 确定目标 Partition 和 Segment │
│ ▼ │
│ 3. Proxy 分发请求到多个 QueryNode │
│ │ • 每个 QueryNode 负责部分 Segment │
│ │ • 并行执行搜索 │
│ ▼ │
│ 4. QueryNode 执行搜索 │
│ │ • 加载 Segment 数据/索引 │
│ │ • 执行向量搜索(索引搜索或暴力搜索) │
│ │ • 应用标量过滤条件 │
│ │ • 返回局部 Top-K 结果 │
│ ▼ │
│ 5. Proxy 归并结果 │
│ │ • 收集所有 QueryNode 结果 │
│ │ • 全局 Top-K 归并排序 │
│ │ • 返回最终结果 │
│ ▼ │
│ 6. 返回结果给客户端 │
│ │
└─────────────────────────────────────────────────────────────────┘
5.2 代码示例:完整搜索流程
from pymilvus import connections, Collection
# 1. 连接 Milvus
connections.connect(host="localhost", port="19530")
# 2. 获取 Collection
collection = Collection("product_embeddings")
# 3. 加载 Collection 到内存(QueryNode)
collection.load()
# 4. 执行向量搜索
search_params = {
"metric_type": "COSINE",
"params": {"nprobe": 10} # IVF 索引参数
}
results = collection.search(
data=[query_vector], # 查询向量
anns_field="embedding", # 向量字段
param=search_params, # 搜索参数
limit=10, # Top-K
expr="price < 1000", # 标量过滤条件
partition_names=["electronics"] # 指定分区
)
# 5. 处理结果
for hits in results:
for hit in hits:
print(f"ID: {hit.id}, Distance: {hit.distance}")
5.3 结果归并算法
Proxy 需要将多个 QueryNode 的结果归并:
// pkg/proxy/impl/reduce.go
// 归并搜索结果(类似归并排序的 K 路归并)
func reduceSearchResults(results []*SearchResult, topK int) *SearchResult {
// 使用最小堆进行 Top-K 归并
heap := NewMinHeap()
for _, result := range results {
for _, item := range result.TopK {
heap.Push(item)
if heap.Len() > topK {
heap.Pop() // 移除最小的
}
}
}
// 提取最终 Top-K
final := make([]*SearchResultItem, topK)
for i := topK - 1; i >= 0; i-- {
final[i] = heap.Pop()
}
return &SearchResult{TopK: final}
}
六、分布式设计
6.1 数据分片策略
Milvus 通过 Channel 实现数据分片:
┌─────────────────────────────────────────────────────────────────┐
│ 数据分片设计 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Collection 分片策略: │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ DML Channels │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │Channel 0 │ │Channel 1 │ │Channel 2 │ ... │ │
│ │ │ (Shard 0)│ │ (Shard 1)│ │ (Shard 2)│ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │DataNode 0│ │DataNode 1│ │DataNode 2│ │ │
│ │ │ 消费写入 │ │ 消费写入 │ │ 消费写入 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 写入时根据主键 Hash 到对应 Channel │
│ │
└─────────────────────────────────────────────────────────────────┘
6.2 副本与高可用
Milvus 支持多副本机制:
┌─────────────────────────────────────────────────────────────────┐
│ 多副本架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ QueryNode 副本组 │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Replica 0 │ │ Replica 1 │ │ Replica 2 │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ QueryNode-A │ │ QueryNode-B │ │ QueryNode-C │ │ │
│ │ │ Segment 1,2 │ │ Segment 1,2 │ │ Segment 1,2 │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ • 相同数据分布在多个副本 │ │
│ │ • 查询负载均衡到不同副本 │ │
│ │ • 单副本故障不影响服务 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
# 创建多副本 Collection
from pymilvus import Collection
collection = Collection("product_embeddings")
collection.load(replica_number=3) # 3 个副本
# 查看副本信息
replicas = collection.get_replicas()
print(f"副本数量: {len(replicas)}")
6.3 故障恢复机制
┌─────────────────────────────────────────────────────────────────┐
│ 故障恢复机制 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ QueryNode 故障恢复: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. QueryCoord 通过心跳检测到节点故障 │ │
│ │ 2. 将故障节点的 Segment 重新分配 │ │
│ │ 3. 新节点从对象存储加载数据/索引 │ │
│ │ 4. 更新路由表,恢复服务 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ DataNode 故障恢复: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. DataCoord 检测到节点故障 │ │
│ │ 2. 重新分配 Channel 订阅 │ │
│ │ 3. 新节点从 Log Broker 恢复未处理的消息 │ │
│ │ 4. 继续处理写入请求 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
七、与其他向量数据库对比
7.1 主流向量数据库对比
┌───────────────────────────────────────────────────────────────────────────┐
│ 主流向量数据库对比 │
├─────────────┬──────────────┬──────────────┬──────────────┬───────────────┤
│ 特性 │ Milvus │ Pinecone │ Weaviate │ Chroma │
├─────────────┼──────────────┼──────────────┼──────────────┼───────────────┤
│ 部署方式 │ 自托管/云 │ 仅云托管 │ 自托管/云 │ 本地/云 │
│ 开源 │ ✅ Apache 2.0│ ❌ 闭源 │ ✅ BSD │ ✅ Apache 2.0 │
│ 分布式 │ ✅ │ ✅ │ ✅ │ ❌ 单机 │
│ 索引类型 │ 丰富 │ 有限 | 中等 │ 有限 │
│ 混合查询 │ ✅ │ ✅ │ ✅ │ 有限 │
│ 存储计算分离│ ✅ │ ✅ │ ❌ │ ❌ │
│ 水平扩展 │ ✅ │ ✅ │ ✅ │ ❌ │
│ 性能 │ ⭐⭐⭐⭐⭐ │ ⭐⭐⭐⭐ │ ⭐⭐⭐⭐ │ ⭐⭐⭐ │
│ 适用场景 │ 企业生产 │ 快速原型 │ 知识图谱 │ 开发测试 │
└─────────────┴──────────────┴──────────────┴──────────────┴───────────────┘
7.2 Milvus 的优势
- 开源生态:完全开源,社区活跃,文档完善
- 高性能:支持多种高性能索引,查询延迟低
- 可扩展:存储计算分离,支持水平扩展
- 功能丰富:支持标量过滤、混合查询、多模态
- 生产就绪:被众多企业验证,稳定性高
八、学习路线图
8.1 本系列文章结构
┌─────────────────────────────────────────────────────────────────┐
│ Milvus 底层原理系列 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 第一章(本文)概述与架构设计 │
│ │ │
│ ▼ │
│ 第二章 向量索引算法基础 │
│ │ │
│ ▼ │
│ 第三章 IVF 索引家族 │
│ │ │
│ ▼ │
│ 第四章 HNSW 图索引 │
│ │ │
│ ▼ │
│ 第五章 DiskANN 磁盘索引 │
│ │ │
│ ▼ │
│ 第六章 GPU 索引加速 │
│ │ │
│ ▼ │
│ 第七章 数据模型与存储 │
│ │ │
│ ▼ │
│ 第八章 数据写入流程 │
│ │ │
│ ▼ │
│ 第九章 数据读取流程 │
│ │ │
│ ▼ │
│ 第十章 分布式架构详解 │
│ │ │
│ ▼ │
│ 第十一章 分片与路由策略 │
│ │ │
│ ▼ │
│ 第十二章 副本与高可用 │
│ │ │
│ ▼ │
│ 第十三章 事务与一致性 │
│ │ │
│ ▼ │
│ 第十四章 内存与缓存管理 │
│ │ │
│ ▼ │
│ 第十五章 生产环境实践 │
│ │
└─────────────────────────────────────────────────────────────────┘
8.2 推荐学习资源
| 类型 | 资源 | 说明 |
|---|---|---|
| 官方 | milvus.io | 官方文档 |
| 源码 | github.com/milvus-io/milvus | 最新源码 |
| 论文 | HNSW, DiskANN | 索引算法论文 |
| 工具 | Attu, Birdwatcher | 管理工具 |
总结
本文从宏观视角介绍了 Milvus 向量数据库的整体架构设计,包括:
- 向量数据库基础:向量数据特点、相似度度量、与传统数据库的区别
- Milvus 架构:四层架构设计、存储计算分离、核心组件职责
- 数据模型:Collection、Partition、Segment 的设计
- 向量索引:索引分类、Milvus 支持的索引类型、选择指南
- 分布式设计:数据分片、多副本、故障恢复
- 对比分析:与其他向量数据库的对比
下一章将深入分析向量索引算法的基础知识,包括暴力搜索、向量量化、以及索引算法的理论基础。
参考资料
相关文章
Milvus底层原理(七):数据模型与存储
深入理解 Milvus 的数据模型设计,掌握 Collection、Partition、Segment 的层次结构,了解列式存储格式和 Schema 设计原则。
Milvus底层原理(八):数据写入流程
深入理解 Milvus 的数据写入流程,掌握从客户端请求到数据持久化的完整链路,了解写入优化策略和数据一致性保证机制。
Milvus底层原理(九):数据读取流程
深入理解 Milvus 的数据读取流程,掌握从查询请求到结果返回的完整链路,了解向量化执行引擎和查询优化策略。