一、Milvus 核心原理与架构设计
1.1 什么是 Milvus?
Milvus 是由 Zilliz 公司开源的云原生分布式向量数据库 ,专为处理海量特征向量而设计。与 Pinecone 的 SaaS 模式不同,Milvus 采用开源 + 自托管模式,提供对底层基础设施的完全控制权。
核心定位:
- 开源可控:Apache 2.0 协议,代码完全透明,可深度定制
- 分布式架构:支持水平扩展至百亿级向量
- 异构计算:CPU/GPU 混合加速,支持 ARM/x86 架构
- 多模融合:支持稠密向量、稀疏向量、二进制向量、全文检索
版本演进:
- Milvus 1.x:基于 Faiss/Annoy/HNSW 的单机/主从架构(已停止维护)
- Milvus 2.x:云原生分布式架构(当前主流,推荐)
- Zilliz Cloud:Milvus 的托管云服务(类似 Pinecone 的 SaaS 版本)
1.2 Milvus 2.x 架构深度解析
Milvus 2.x 采用存储计算分离的云原生架构,核心组件如下:
┌─────────────────────────────────────────────────────────────┐
│ 接入层 (Access Layer) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Proxy │ │ Proxy │ │ Proxy │ │ Proxy │ ← 负载均衡 │
│ │ (SDK入口)│ │ (SDK入口)│ │ (SDK入口)│ │ (SDK入口)│ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
└───────┼────────────┼────────────┼────────────┼─────────────┘
│ │ │ │
└────────────┴────────────┴────────────┘
│
┌────────────────────────┼─────────────────────────────────────┐
│ 协调服务层 (Coordinator Layer) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Root Coord │ │ Data Coord │ │ Query Coord │ │
│ │ (全局管理) │ │ (数据管理) │ │ (查询调度) │ │
│ │ • 时间戳分配 │ │ • 段分配 │ │ • 负载均衡 │ │
│ │ • DDL 管理 │ │ • 索引构建 │ │ • 故障转移 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Index Coord │ │ Data Node │ ← 实际执行索引构建 │
│ │ (索引管理) │ │ (数据持久化) │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌───────┴─────────────────────────────────────────────────────┐
│ 执行层 (Worker Layer) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Query Node │ │ Data Node │ │ Index Node │ │
│ │ (查询执行) │ │ (数据插入) │ │ (索引构建) │ │
│ │ • 段搜索 │ │ • 日志写入 │ │ • ANN 构建 │ │
│ │ • 结果合并 │ │ • 刷盘管理 │ │ • 量化压缩 │ │
│ │ • 缓存管理 │ │ • 压缩合并 │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌───────┴─────────────────────────────────────────────────────┐
│ 存储层 (Storage Layer) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 对象存储 │ │ 消息队列 │ │ 元数据存储 │ │
│ │ (MinIO/S3) │ │ (Pulsar/ │ │ (etcd) │ │
│ │ • 原始数据 │ │ Kafka/Rock │ │ • schema │ │
│ │ • 索引文件 │ │ etMQ) │ │ • 集群状态 │ │
│ │ • 日志快照 │ │ • 流数据 │ │ • 服务发现 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
关键组件详解:
| 组件 | 职责 | 高可用策略 |
|---|---|---|
| Proxy | SDK 接入点,请求路由、鉴权 | 多实例 + K8s Service 负载均衡 |
| Root Coord | 全局时间戳(TSO)、DDL 协调 | 主备模式,自动故障转移 |
| Query Coord | 查询调度、负载均衡、故障恢复 | 主备模式 |
| Data Coord | 数据段管理、compaction 调度 | 主备模式 |
| Query Node | 执行向量搜索,管理缓存 | 无状态,故障自动迁移 |
| Data Node | 执行数据插入、日志消费 | 无状态,水平扩展 |
| Index Node | 构建向量索引(CPU/GPU) | 无状态,资源隔离 |
1.3 数据模型与存储机制
1.3.1 逻辑数据模型
Database → Collection → Partition → Segment → Field → Entity (Row)
类比关系型数据库:
Database → Database
Collection → Table (核心概念,对应一个向量表)
Partition → Partition (可选,逻辑分区)
Segment → Tablet/Shard (物理分片,自动管理)
Field → Column
Entity → Row
Collection 核心属性:
- Schema:字段定义(向量字段 + 标量字段)
- Shard:数据分片数,决定写入并行度
- Partitions:分区数,用于数据隔离和过滤
- Consistency Level:一致性级别(Strong/Bounded Staleness/Session/Eventual)
1.3.2 物理存储结构
日志结构合并树(LSM-Tree)变种:
写入路径:
SDK → Proxy → Pulsar/Kafka (WAL) → Data Node → 内存 Buffer →
↓ 异步刷盘
对象存储 (MinIO/S3) → 不可变段文件 (Segment)
读取路径:
Query Node ← 加载 Segment 到内存/本地磁盘 ← 对象存储
↓
向量索引 (HNSW/IVF_FLAT/IVF_SQ8/IVF_PQ/DISKANN/GPU 索引)
↓
相似度搜索
Segment 生命周期:
- Growing Segment:正在写入的活跃段,驻留内存
- Sealed Segment:大小达到阈值(默认 512MB)或手动封印,转为只读
- Flushed Segment:持久化到对象存储,Index Node 构建索引
- Indexed Segment:索引构建完成,Query Node 加载提供服务
- Compacted Segment:后台合并小段,优化查询性能
1.4 索引算法与性能优化
Milvus 支持多种 ANN 索引算法,适应不同场景:
| 索引类型 | 算法 | 适用场景 | 构建时间 | 内存占用 | 查询延迟 | 召回率 |
|---|---|---|---|---|---|---|
| FLAT | 暴力搜索 | 小数据集(<1万),100%召回 | 无 | 100% | 高 | 1.0 |
| IVF_FLAT | 倒排文件 + 暴力 | 中等规模,平衡性能召回 | 快 | 100% | 中 | 0.95+ |
| IVF_SQ8 | IVF + 标量量化 | 大规模,内存受限 | 中 | 25% | 中 | 0.9+ |
| IVF_PQ | IVF + 乘积量化 | 超大规模,极致压缩 | 慢 | 10% | 较高 | 0.85+ |
| HNSW | 可导航小世界图 | 高召回,低延迟 | 慢 | 200% | 低 | 0.99+ |
| DISKANN | 磁盘驻留图索引 | 十亿级,内存不足 | 慢 | 10% | 较高 | 0.95+ |
| GPU_IVF_FLAT | GPU 加速 IVF | 高吞吐,GPU 可用 | 快 | 100% | 极低 | 0.95+ |
| GPU_IVF_PQ | GPU 加速 PQ | 超大规模 GPU 场景 | 中 | 10% | 极低 | 0.9+ |
| SPARSE_INVERTED_INDEX | 稀疏向量 | 关键词搜索,混合检索 | 快 | 低 | 低 | - |
| BIN_IVF_FLAT | 二进制向量 | 图像哈希,快速去重 | 快 | 100% | 低 | 0.95+ |
索引参数调优:
python
# HNSW 参数
{
"M": 16, # 图的最大出度,越大图越稠密,搜索越慢但召回越高
"efConstruction": 500 # 构建时的搜索深度,越大构建越慢但图质量越高
}
# IVF 系列参数
{
"nlist": 4096, # 聚类中心数,通常为 4*sqrt(n) 到 16*sqrt(n)
"nprobe": 128 # 查询时搜索的聚类中心数,越大召回越高但越慢
}
# 查询时 HNSW 参数
{
"ef": 128 # 搜索时的动态列表大小,应 >= top_k
}
二、部署实战:从单机到分布式
2.1 单机部署(Docker Compose)
适用场景:开发测试、小规模生产(<1000万向量)
bash
# 1. 下载安装脚本
curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh
# 2. 启动(使用嵌入式 etcd 和 MinIO)
bash standalone_embed.sh start
# 3. 验证
docker ps | grep milvus
# 应看到 milvus-standalone, etcd, minio 三个容器
# 4. 停止
bash standalone_embed.sh stop
# 5. 删除数据(谨慎!)
bash standalone_embed.sh delete
自定义 Docker Compose(推荐用于生产级单机):
yaml
# docker-compose.yml
version: '3.5'
services:
etcd:
container_name: milvus-etcd
image: quay.io/coreos/etcd:v3.5.5
environment:
- ETCD_AUTO_COMPACTION_MODE=revision
- ETCD_AUTO_COMPACTION_RETENTION=1000
- ETCD_QUOTA_BACKEND_BYTES=4294967296
- ETCD_SNAPSHOT_COUNT=50000
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd
command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
healthcheck:
test: ["CMD", "etcdctl", "endpoint", "health"]
interval: 30s
timeout: 20s
retries: 3
minio:
container_name: milvus-minio
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
environment:
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
ports:
- "9001:9001" # Console
- "9000:9000" # API
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data
command: minio server /minio_data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
standalone:
container_name: milvus-standalone
image: milvusdb/milvus:v2.3.3
command: ["milvus", "run", "standalone"]
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
ports:
- "19530:19530" # gRPC
- "9091:9091" # Metrics
depends_on:
- etcd
- minio
networks:
default:
name: milvus
2.2 分布式集群部署(Kubernetes)
适用场景:生产环境、大规模数据(>1亿向量)、高可用要求
2.2.1 前提条件
bash
# 1. 准备 K8s 集群(>=1.20)
kubectl version
# 2. 安装 Helm
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
# 3. 添加 Milvus Helm 仓库
helm repo add milvus https://zilliztech.github.io/milvus-helm/
helm repo update
# 4. 准备存储类(以 AWS EBS 为例)
kubectl get sc
# 确保有默认 StorageClass,或指定具体类型
2.2.2 部署 Milvus 集群
bash
# 创建命名空间
kubectl create namespace milvus
# 安装分布式 Milvus(默认配置)
helm install my-milvus milvus/milvus --namespace milvus
# 或自定义配置(values.yaml)
cat > milvus-values.yaml <<EOF
cluster:
enabled: true
# 组件副本数
rootCoord:
replicaCount: 2 # 主备高可用
queryCoord:
replicaCount: 2
dataCoord:
replicaCount: 2
indexCoord:
replicaCount: 2
proxy:
replicaCount: 3 # 多入口负载均衡
service:
type: LoadBalancer # 云厂商提供公网 IP
queryNode:
replicaCount: 6 # 查询节点,可按需扩展
resources:
limits:
memory: 16Gi
cpu: "8"
dataNode:
replicaCount: 2
indexNode:
replicaCount: 4 # 索引构建节点,可配置 GPU
resources:
limits:
memory: 32Gi
cpu: "16"
# nvidia.com/gpu: 1 # 启用 GPU 索引
# 依赖组件配置
etcd:
replicaCount: 3 # etcd 集群,奇数节点
pulsar:
enabled: true # 生产环境推荐 Pulsar 替代 Kafka
replicaCount: 3
minio:
mode: distributed # MinIO 分布式模式
drivesPerNode: 4
replicas: 4
# 监控配置
metrics:
enabled: true
serviceMonitor:
enabled: true # Prometheus Operator
EOF
helm install my-milvus milvus/milvus -f milvus-values.yaml --namespace milvus
# 查看部署状态
kubectl get pods -n milvus -w
# 等待所有组件 Running(约 5-10 分钟)
2.2.3 外部访问配置
bash
# 方式 1:端口转发(临时测试)
kubectl port-forward svc/my-milvus-proxy 19530:19530 -n milvus
# 方式 2:LoadBalancer(云环境)
kubectl get svc my-milvus-proxy -n milvus
# EXTERNAL-IP 即为访问地址
# 方式 3:Ingress(生产推荐)
cat > milvus-ingress.yaml <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: milvus-grpc
namespace: milvus
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
cert-manager.io/cluster-issuer: "letsencrypt"
spec:
ingressClassName: nginx
tls:
- hosts:
- milvus.example.com
secretName: milvus-tls
rules:
- host: milvus.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-milvus-proxy
port:
number: 19530
EOF
kubectl apply -f milvus-ingress.yaml
2.3 使用 Zilliz Cloud(托管版)
如果不想自建,可直接使用 Milvus 的托管云服务:
python
# 与自建 Milvus 完全兼容的 API
from pymilvus import connections, Collection
# 连接 Zilliz Cloud
connections.connect(
alias="default",
uri="https://in03-xxxxxxxx.api.gcp-us-west1.zillizcloud.com", # 集群端点
token="db_admin:password" # 或使用 API Key
)
# 后续操作与自建完全一致
collection = Collection("example_collection")
三、Milvus SDK 完整操作指南
3.1 Python SDK(PyMilvus)深度使用
3.1.1 环境准备与连接
bash
pip install pymilvus==2.3.5 # 确保与服务器版本匹配
python
from pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection, utility
import numpy as np
# ==================== 连接管理 ====================
# 基础连接
connections.connect(
alias="default",
host="localhost", # 或 K8s 集群地址
port="19530",
# 认证(启用时)
# user="root",
# password="Milvus"
)
# 连接池配置(生产环境)
connections.connect(
alias="prod",
host="milvus.example.com",
port="19530",
pool_size=10, # 连接池大小
wait_timeout=5, # 等待连接超时
max_idle_time=600 # 空闲连接回收时间
)
# 多连接管理
connections.connect("cluster1", host="milvus-1.internal", port="19530")
connections.connect("cluster2", host="milvus-2.internal", port="19530")
# 断开连接
connections.disconnect("default")
# ==================== 健康检查 ====================
from pymilvus import utility
print(f"Milvus 版本: {utility.get_server_version()}")
print(f"是否连接: {connections.has_connection('default')}")
3.1.2 集合(Collection)完整生命周期
python
# ==================== 1. 定义 Schema ====================
# 字段定义
fields = [
# 主键字段(必须)
FieldSchema(
name="id",
dtype=DataType.INT64,
is_primary=True,
auto_id=True # 自动生成递增 ID,也可设为 False 手动指定
),
# 向量字段(至少一个)
FieldSchema(
name="embedding",
dtype=DataType.FLOAT_VECTOR,
dim=1536 # 向量维度,必须与模型输出匹配
),
# 稀疏向量(2.4+ 版本支持,用于关键词检索)
# FieldSchema(
# name="sparse_vector",
# dtype=DataType.SPARSE_FLOAT_VECTOR
# ),
# 标量字段(元数据过滤)
FieldSchema(name="title", dtype=DataType.VARCHAR, max_length=512),
FieldSchema(name="category", dtype=DataType.VARCHAR, max_length=64),
FieldSchema(name="tags", dtype=DataType.ARRAY, element_type=DataType.VARCHAR, max_length=32, max_capacity=10),
FieldSchema(name="rating", dtype=DataType.FLOAT),
FieldSchema(name="publish_date", dtype=DataType.INT64), # Unix 时间戳
FieldSchema(name="is_featured", dtype=DataType.BOOL),
# JSON 字段(灵活结构,2.3+ 支持)
FieldSchema(name="metadata", dtype=DataType.JSON)
]
# 集合 Schema
schema = CollectionSchema(
fields=fields,
description="多媒体内容向量库",
enable_dynamic_field=True # 允许未预定义的动态字段
)
# ==================== 2. 创建集合 ====================
collection_name = "multimedia_content"
# 检查存在性
if utility.has_collection(collection_name):
utility.drop_collection(collection_name)
print(f"已删除旧集合: {collection_name}")
collection = Collection(
name=collection_name,
schema=schema,
using="default", # 使用哪个连接
shards_num=2 # 分片数,影响写入并行度
)
print(f"集合创建成功: {collection_name}")
print(f"字段列表: {collection.schema}")
# ==================== 3. 创建索引(关键!) ====================
# 为向量字段创建索引
index_params = {
"index_type": "HNSW", # 或 "IVF_FLAT", "DISKANN", "GPU_IVF_PQ"
"metric_type": "COSINE", # 或 "L2", "IP" (Inner Product)
"params": {
"M": 16, # HNSW 参数
"efConstruction": 500
}
}
collection.create_index(
field_name="embedding",
index_params=index_params,
index_name="embedding_hnsw_idx" # 自定义索引名
)
# 为标量字段创建索引(加速过滤)
collection.create_index(
field_name="category",
index_name="category_idx"
)
# 等待索引构建完成
print("等待索引构建...")
collection.load() # 加载到内存(必须 load 后才能搜索)
utility.wait_for_index_building_complete(collection_name)
# ==================== 4. 分区管理(可选) ====================
# 按类别分区,物理隔离数据
collection.create_partition("articles")
collection.create_partition("videos")
collection.create_partition("images")
print(f"分区列表: {collection.partitions}")
# ==================== 5. 集合配置 ====================
collection.set_properties({
"collection.ttl.seconds": 86400 # 数据自动过期时间(1天)
})
# ==================== 6. 删除与清理 ====================
# 释放内存(unload)
collection.release()
# 删除索引
collection.drop_index(index_name="embedding_hnsw_idx")
# 删除分区
collection.drop_partition("temp_data")
# 删除集合(不可逆)
# utility.drop_collection(collection_name)
3.1.3 数据写入操作
python
import random
from datetime import datetime, timedelta
# ==================== 单条插入 ====================
def generate_entity():
return {
"id": random.randint(1, 1000000), # 如果 auto_id=True,不需要此字段
"embedding": [random.uniform(-1, 1) for _ in range(1536)],
"title": f"Content {random.randint(1, 1000)}",
"category": random.choice(["tech", "lifestyle", "news", "entertainment"]),
"tags": random.sample(["ai", "cloud", "coding", "travel", "food"], k=3),
"rating": round(random.uniform(1, 5), 1),
"publish_date": int((datetime.now() - timedelta(days=random.randint(0, 365))).timestamp()),
"is_featured": random.choice([True, False]),
"metadata": {
"author": f"Author {random.randint(1, 100)}",
"views": random.randint(100, 100000),
"language": random.choice(["zh", "en", "ja"])
}
}
# 单条插入(不推荐,仅测试用)
entity = generate_entity()
collection.insert([entity]) # 注意:参数是列表的列表
# ==================== 批量插入(生产标准) ====================
def batch_insert(collection, total_count, batch_size=1000):
"""高效批量插入"""
inserted = 0
batch = []
for i in range(total_count):
entity = [
# 顺序必须与 schema 字段顺序一致,或使用 dict
[random.randint(1, 10000000)], # id(如果 auto_id=False)
[[random.uniform(-1, 1) for _ in range(1536)]], # embedding
[f"Article {i}"], # title
[random.choice(["tech", "lifestyle"])], # category
[random.sample(["ai", "cloud"], k=2)], # tags
[round(random.uniform(1, 5), 1)], # rating
[int(datetime.now().timestamp())], # publish_date
[random.choice([True, False])], # is_featured
[{"author": f"Author {i}", "views": i * 100}] # metadata
]
batch.append(entity)
if len(batch) >= batch_size:
collection.insert(batch)
inserted += len(batch)
batch = []
if inserted % 10000 == 0:
print(f"已插入: {inserted}/{total_count}")
if batch:
collection.insert(batch)
inserted += len(batch)
# 刷新确保数据持久化
collection.flush()
print(f"完成,总计插入: {inserted}")
return inserted
# 执行 10 万条数据插入
batch_insert(collection, 100000, batch_size=1000)
# ==================== 分区插入 ====================
# 指定分区插入
collection.insert(
data=[generate_entity()],
partition_name="articles"
)
# ==================== 删除操作 ====================
# 按主键删除
collection.delete("id in [1, 2, 3]")
# 按表达式删除(谨慎!)
collection.delete('category == "temp" && publish_date < 1700000000')
# ==================== 更新操作(先删后插) ====================
# Milvus 不支持原地更新,需要删除后重新插入
def update_entity(collection, entity_id, new_data):
# 1. 删除旧数据
collection.delete(f"id == {entity_id}")
# 2. 插入新数据(保持相同 ID)
new_data["id"] = entity_id
collection.insert([new_data])
collection.flush()
3.1.4 查询与搜索操作
python
# ==================== 1. 向量相似度搜索 ====================
# 准备查询向量(实际来自嵌入模型)
query_vector = [random.uniform(-1, 1) for _ in range(1536)]
# 基础 ANN 搜索
results = collection.search(
data=[query_vector], # 支持批量查询:[[vec1], [vec2], ...]
anns_field="embedding", # 搜索的向量字段
param={
"metric_type": "COSINE",
"params": {"ef": 128} # HNSW 搜索参数,应 >= top_k
},
limit=10, # top_k
output_fields=["title", "category", "rating", "metadata"], # 返回字段
consistency_level="Bounded" # 一致性级别
)
# 解析结果
for hits in results: # 每个查询的结果
for hit in hits: # top_k 个结果
print(f"ID: {hit.id}, 距离: {hit.distance:.4f}")
print(f"标题: {hit.entity.get('title')}")
print(f"分类: {hit.entity.get('category')}")
print("---")
# ==================== 2. 带过滤的混合搜索 ====================
# 标量过滤 + 向量搜索(Pre-filtering)
results = collection.search(
data=[query_vector],
anns_field="embedding",
param={"metric_type": "COSINE", "params": {"ef": 64}},
limit=10,
expr='category == "tech" && rating > 4.0 && is_featured == True', # 过滤条件
output_fields=["title", "rating"]
)
# JSON 字段过滤(2.3+)
results = collection.search(
data=[query_vector],
anns_field="embedding",
param={"metric_type": "COSINE"},
limit=5,
expr='metadata["language"] == "zh" && metadata["views"] > 10000',
output_fields=["title", "metadata"]
)
# ==================== 3. 范围搜索(Range Search)2.4+ ====================
# 返回距离在阈值内的所有结果,而非固定 top_k
results = collection.search(
data=[query_vector],
anns_field="embedding",
param={
"metric_type": "COSINE",
"params": {
"radius": 0.8, # 最大距离(COSINE 距离范围 [0, 2])
"range_filter": 0.2 # 最小距离,过滤过于相似(重复)的结果
}
},
limit=100, # 最大返回数
output_fields=["title"]
)
# ==================== 4. 查询(Query)操作 ====================
# 精确查询(非相似度搜索),类似 SQL SELECT
# 按主键查询
results = collection.query(
expr="id in [100, 200, 300]",
output_fields=["title", "category", "embedding"]
)
# 复杂条件查询
results = collection.query(
expr='category == "tech" && publish_date > 1700000000',
output_fields=["title", "rating"],
limit=100,
offset=0, # 分页
order_by="-publish_date" # 排序(2.4+)
)
# ==================== 5. 混合检索(Hybrid Search)2.4+ ====================
# 多向量字段联合检索(如:图片向量 + 文本向量)
from pymilvus import AnnSearchRequest, RRFRanker, WeightedRanker
# 构建多个向量搜索请求
sparse_search = AnnSearchRequest(
data=[[0, 1, 0, 0, 0.5]], # 稀疏向量(关键词权重)
anns_field="sparse_vector",
param={"metric_type": "IP", "params": {"drop_ratio_search": 0.2}},
limit=100
)
dense_search = AnnSearchRequest(
data=[query_vector],
anns_field="embedding",
param={"metric_type": "COSINE", "params": {"ef": 64}},
limit=100
)
# 使用 RRF (Reciprocal Rank Fusion) 融合结果
results = collection.hybrid_search(
reqs=[sparse_search, dense_search],
ranker=RRFRanker(k=60), # RRF 参数
limit=10
)
# 或使用加权融合
# results = collection.hybrid_search(
# reqs=[sparse_search, dense_search],
# ranker=WeightedRanker(0.7, 0.3), # 权重和为 1
# limit=10
# )
# ==================== 6. 迭代器(大数据量遍历) ====================
# 避免一次性加载大量数据
iterator = collection.query_iterator(
expr="category == 'tech'",
output_fields=["id", "title"],
batch_size=1000,
limit=-1 # 无限制,遍历全部
)
while True:
batch = iterator.next()
if not batch:
break
for entity in batch:
process(entity) # 自定义处理逻辑
3.1.5 高级功能:分组搜索与聚合
python
# ==================== 分组搜索(Group By)2.4+ ====================
# 按类别分组,每类返回最相似的一条(去重)
results = collection.search(
data=[query_vector],
anns_field="embedding",
param={"metric_type": "COSINE"},
limit=100,
group_by_field="category", # 按 category 分组
group_size=3, # 每组返回 3 条
strict_group_size=True,
output_fields=["category", "title"]
)
# ==================== 聚合查询 ====================
# Milvus 2.x 聚合能力有限,建议结合外部计算或使用 Zilliz Cloud 的分析功能
# 基础计数
count = collection.query(expr="category == 'tech'", output_fields=["count(*)"])
# 使用 query 结果在外部聚合
results = collection.query(
expr="publish_date > 1700000000",
output_fields=["category", "rating"]
)
# 外部聚合(Pandas)
import pandas as pd
df = pd.DataFrame(results)
aggregation = df.groupby("category")["rating"].mean()
3.2 Node.js SDK 实战
bash
npm install @zilliz/milvus2-sdk-node
javascript
const { MilvusClient, DataType } = require('@zilliz/milvus2-sdk-node');
class MilvusService {
constructor() {
this.client = new MilvusClient({
address: 'localhost:19530',
// token: 'root:Milvus', // 认证
// ssl: true, // TLS
});
}
// ==================== 集合管理 ====================
async createCollection() {
const collectionName = 'nodejs_demo';
// 检查存在性
const exists = await this.client.hasCollection({ collection_name: collectionName });
if (exists.value) {
await this.client.dropCollection({ collection_name: collectionName });
}
// 创建 Schema
const res = await this.client.createCollection({
collection_name: collectionName,
fields: [
{
name: 'id',
data_type: DataType.Int64,
is_primary_key: true,
autoID: true
},
{
name: 'vector',
data_type: DataType.FloatVector,
dim: 1536
},
{
name: 'content',
data_type: DataType.VarChar,
max_length: 65535
},
{
name: 'metadata',
data_type: DataType.JSON
}
],
enable_dynamic_field: true
});
console.log('集合创建成功:', res);
// 创建索引
await this.client.createIndex({
collection_name: collectionName,
field_name: 'vector',
index_type: 'HNSW',
metric_type: 'COSINE',
params: { M: 16, efConstruction: 500 }
});
// 加载
await this.client.loadCollection({ collection_name: collectionName });
return collectionName;
}
// ==================== 数据插入 ====================
async insertData(collectionName, data) {
const entities = data.map(item => ({
vector: item.embedding,
content: item.text,
metadata: item.meta
}));
const res = await this.client.insert({
collection_name: collectionName,
fields_data: entities
});
await this.client.flush({ collection_name: collectionName });
return res;
}
// ==================== 向量搜索 ====================
async search(collectionName, queryVector, filters = {}) {
const expr = filters.category ? `metadata["category"] == "${filters.category}"` : '';
const res = await this.client.search({
collection_name: collectionName,
vector: queryVector,
filter: expr,
output_fields: ['content', 'metadata'],
limit: 10,
params: { ef: 64 }
});
return res.results.map(hit => ({
id: hit.id,
score: hit.score,
content: hit.content,
metadata: hit.metadata
}));
}
// ==================== 混合检索 ====================
async hybridSearch(collectionName, denseVector, sparseVector) {
const res = await this.client.hybridSearch({
collection_name: collectionName,
reqs: [
{
data: [denseVector],
anns_field: 'vector',
limit: 100,
expr: ''
},
{
data: [sparseVector],
anns_field: 'sparse_vector', // 需预先定义
limit: 100,
expr: ''
}
],
rerank: 'RRF', // 或 'weighted'
limit: 10,
output_fields: ['content']
});
return res.results;
}
}
// 使用示例
async function main() {
const service = new MilvusService();
// 创建集合
const coll = await service.createCollection();
// 插入数据
await service.insertData(coll, [
{
embedding: Array(1536).fill(0).map(() => Math.random()),
text: 'Milvus 是开源向量数据库',
meta: { category: 'tech', author: 'admin' }
}
]);
// 搜索
const results = await service.search(
coll,
Array(1536).fill(0).map(() => Math.random()),
{ category: 'tech' }
);
console.log(results);
}
main().catch(console.error);
四、可视化工具:Attu 与生态
4.1 Attu 可视化工具部署
Attu 是 Milvus 的官方 GUI 管理工具,提供类似 pgAdmin 的功能。
bash
# Docker 部署 Attu
docker run -p 8000:3000 \
-e MILVUS_URL=localhost:19530 \
zilliz/attu:latest
# 访问 http://localhost:8000
Attu 核心功能:
- 数据浏览器:可视化查看向量分布、元数据
- 向量搜索测试:图形化界面测试查询参数
- Schema 设计器:拖拽式创建集合
- 性能监控:QPS、延迟、存储使用率图表
- 索引管理:查看索引构建进度、重建索引
4.2 监控与可观测性
4.2.1 Prometheus + Grafana
yaml
# 在 values.yaml 中启用监控
metrics:
enabled: true
serviceMonitor:
enabled: true
# 部署 Prometheus Operator(如果未安装)
helm install prometheus prometheus-community/kube-prometheus-stack -n monitoring
# 导入 Milvus Grafana Dashboard
# 从 https://github.com/milvus-io/milvus/blob/master/deployments/monitor/grafana-dashboards
# 导入 JSON 配置
关键监控指标:
milvus_proxy_search_latency_bucket:搜索延迟分布milvus_querynode_entity_num:查询节点加载的实体数milvus_datanode_flush_buffer_size:数据节点缓冲区大小milvus_msgstream_consume_msg_count_rate:消息消费速率
4.2.2 Loki 日志聚合
yaml
# 启用 Loki 收集 Milvus 日志
logging:
level: info
format: json
file:
rootPath: /var/lib/milvus/logs
maxSize: 300MB
maxAge: 10d
maxBackups: 20
4.3 生态集成
4.3.1 LangChain 集成
python
from langchain_community.vectorstores import Milvus
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
# 初始化 Milvus 存储
vector_store = Milvus(
embedding_function=embeddings,
collection_name="langchain_demo",
connection_args={"host": "localhost", "port": "19530"},
vector_field="embedding",
text_field="content",
metadata_field="metadata"
)
# 添加文档
from langchain_core.documents import Document
docs = [
Document(page_content="Milvus 支持十亿级向量", metadata={"source": "docs"}),
Document(page_content="HNSW 索引适合高召回场景", metadata={"source": "blog"})
]
vector_store.add_documents(docs)
# 检索
retriever = vector_store.as_retriever(search_kwargs={"k": 5})
results = retriever.invoke("向量数据库索引")
4.3.2 LlamaIndex 集成
python
from llama_index.core import VectorStoreIndex, StorageContext
from llama_index.vector_stores.milvus import MilvusVectorStore
vector_store = MilvusVectorStore(
uri="http://localhost:19530",
collection_name="llamaindex",
dim=1536,
overwrite=True # 重建集合
)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)
query_engine = index.as_query_engine()
response = query_engine.query("Milvus 的架构特点")
4.3.3 Spark 批量导入
scala
// 使用 Spark 将大规模数据批量导入 Milvus
import io.milvus.spark.connector._
df.write
.mode("append")
.milvus(
host = "milvus-cluster",
port = 19530,
collectionName = "spark_import",
vectorField = "embedding"
)
五、生产环境最佳实践
5.1 性能调优指南
python
# ==================== 写入优化 ====================
# 1. 批量插入:每批次 1000-5000 条
# 2. 多客户端并行写入:根据 shard 数调整并发(通常 shard 数 = 2 * dataNode 数)
# 3. 调整段大小:避免过小段导致查询碎片化
collection.set_properties({
"segment.maxSize": 1024 # MB,默认 512,增大减少段数量
})
# ==================== 查询优化 ====================
# 1. 索引选择:
# - 高召回 + 低延迟:HNSW(内存充足时)
# - 超大规模 + 成本敏感:DISKANN 或 IVF_PQ
# - GPU 加速:GPU_IVF_FLAT(批量查询场景)
# 2. 预加载:避免冷启动延迟
collection.load(_resource_groups=["rg1"]) # 指定资源组
# 3. 缓存预热
collection.warmup() # 2.4+ 支持,预加载热数据
# 4. 查询参数调优
search_params = {
"HNSW": {"ef": 128}, # ef 越大召回越高,延迟增加
"IVF_FLAT": {"nprobe": 128}, # nprobe 越大越精确,越慢
"DISKANN": {"search_list": 100}
}
# ==================== 内存管理 ====================
# Query Node 内存估算公式:
# 内存 = (向量数 * 维度 * 4字节) * 索引系数 + 标量数据 + 缓存
# HNSW 系数约 2.0,IVF_FLAT 系数约 1.0,IVF_PQ 约 0.1
# 配置资源组隔离
utility.create_resource_group("online_rg", config={
"requests": {"node_num": 4},
"limits": {"node_num": 8}
})
collection.load(_resource_groups=["online_rg"])
5.2 高可用架构
┌─────────────────────────────────────────┐
│ 负载均衡层 (Nginx/ALB) │
│ SSL 终止 + 请求分发 │
└─────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│Proxy 1│ │Proxy 2│ │Proxy 3│ ← 多活无状态
└───┬───┘ └───┬───┘ └───┬───┘
│ │ │
└───────────────┼───────────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│Query │ │Query │ │Query │ ← 查询节点池
│Node 1 │ │Node 2 │ │Node N │
└───┬───┘ └───┬───┘ └───┬───┘
│ │ │
└──────────┼──────────┘
▼
┌─────────────────────┐
│ 对象存储 (S3) │ ← 共享存储,多可用区
│ Pulsar 集群 │ ← 消息队列,跨 AZ 复制
│ etcd 集群 │ ← 元数据,奇数节点
└─────────────────────┘
故障恢复策略:
- Query Node 故障:Coord 自动将段迁移到其他节点,秒级恢复
- Coord 故障:主备切换,通常 5-10 秒
- etcd 故障:保持多数派可用,避免脑裂
- 存储故障:对象存储多副本保障,Pulsar BookKeeper 冗余
5.3 备份与恢复
bash
# 使用 milvus-backup 工具
# 1. 安装
wget https://github.com/zilliztech/milvus-backup/releases/download/v0.4.0/milvus-backup_Linux_x86_64.tar.gz
tar -xzf milvus-backup_Linux_x86_64.tar.gz
# 2. 配置 backup.yaml
cat > backup.yaml <<EOF
milvus:
address: localhost
port: 19530
authorizationEnabled: false
minio:
address: localhost
port: 9000
accessKeyID: minioadmin
secretAccessKey: minioadmin
useSSL: false
bucketName: a-bucket
rootPath: files
backup:
rootPath: /var/lib/milvus-backup
maxSegmentGroupSize: 2G
EOF
# 3. 创建备份
./milvus-backup create -n my_backup_20240115
# 4. 列出备份
./milvus-backup list
# 5. 恢复
./milvus-backup restore -n my_backup_20240115 -s _restore
六、故障排查与常见问题
6.1 连接与认证问题
python
# 错误:Fail connecting to server
# 排查:
# 1. 检查服务器状态
!docker ps | grep milvus # 或 kubectl get pods
# 2. 检查防火墙/安全组
telnet localhost 19530
# 3. 检查版本兼容性
# PyMilvus 版本必须与 Milvus 服务器版本匹配
pip show pymilvus # 查看客户端版本
utility.get_server_version() # 查看服务器版本
# 错误:permission deny
# 解决:检查 RBAC 配置
from pymilvus import Role
role = Role("read_only")
role.grant("Collection", "*", "Search")
role.add_user("app_user")
6.2 性能问题诊断
python
# 现象:查询延迟高
# 诊断步骤:
# 1. 检查是否已加载
print(collection.load_state()) # 应为 Loaded
# 2. 检查索引是否构建
utility.wait_for_index_building_complete(collection_name)
# 3. 检查资源是否充足
# 查看 Grafana:querynode_cpu_usage, querynode_memory_usage
# 4. 检查段数量(过多段导致查询碎片化)
stats = collection.get_stats()
print(f"段数量: {len(stats['row_segments'])}")
# 优化:手动触发 compaction
collection.compact()
utility.wait_for_compaction_completed(collection_name)
# 5. 检查查询参数
# ef/nprobe 是否过大?top_k 是否过大?
6.3 数据一致性问题
python
# Milvus 提供四种一致性级别:
from pymilvus import ConsistencyLevel
# Strong: 最强一致,写入立即可见(延迟最高)
# Bounded Staleness: 允许一定时间内的不一致(默认)
# Session: 同一客户端会话内强一致
# Eventual: 最终一致,延迟最低
# 查询时指定一致性
results = collection.search(
data=[vector],
anns_field="embedding",
limit=10,
consistency_level=ConsistencyLevel.Strong # 强一致
)
七、Milvus vs Pinecone 选型对比
| 维度 | Milvus | Pinecone |
|---|---|---|
| 部署模式 | 开源自托管 / Zilliz Cloud 托管 | 全托管 SaaS |
| 运维成本 | 高(需 K8s expertise) | 低(零运维) |
| 扩展性 | 无限水平扩展(自研) | 自动扩展(受限于云服务) |
| 成本控制 | 灵活(自建硬件/GPU) | 按量付费,可预测 |
| 定制化 | 极高(可改源码) | 低(仅 API 参数) |
| GPU 支持 | 原生支持(GPU_IVF* 索引) | 不支持 |
| 多模态 | 稠密+稀疏+二进制+全文 | 主要稠密向量 |
| 社区生态 | 活跃开源社区 | 商业支持为主 |
| 适用场景 | 大规模生产、定制需求、成本敏感 | 快速启动、中小规模、无运维团队 |
八、总结与下一篇预告
Milvus 作为开源向量数据库的标杆,其核心价值在于:
- 云原生分布式架构:支撑从单机到千亿级向量的平滑扩展
- 异构计算能力:CPU/GPU 混合加速,适应多样硬件环境
- 深度可定制:从索引算法到存储后端均可调优
- 丰富功能集:多向量联合检索、分组搜索、稀疏向量等高级特性
适用场景:
- ✅ 大规模生产环境(>1亿向量)
- ✅ 需要 GPU 加速的高吞吐场景
- ✅ 深度定制需求(特定索引算法、存储后端)
- ✅ 成本敏感且具备运维团队
不适用场景:
- ❌ 快速原型验证(选择 Pinecone/Zilliz Cloud)
- ❌ 无 K8s 运维能力的小团队
- ❌ 简单场景(<100万向量,选择 pgvector 更轻量)