Milvus向量数据库部署与使用技术方案


1. 引言

1.1 文档目的

随着大语言模型(LLM)和生成式AI(AIGC)的快速发展,向量数据库已成为RAG(检索增强生成)、推荐系统、语义搜索等AI应用的核心基础设施。Milvus作为全球最流行的开源向量数据库,因其云原生架构高性能丰富的生态,被广泛应用于各类生产环境。

本文档旨在提供一份从零到生产的Milvus部署与使用指南,涵盖:

  • 架构原理的通俗解读
  • 多场景部署方案(开发/测试/生产)—— 含完整命令
  • 完整的开发实践(Python SDK)—— 含可运行代码
  • 性能调优与运维最佳实践

1.2 适用范围

  • 即将采用向量数据库的AI应用开发团队
  • 需要将Milvus落地到生产环境的架构师和运维工程师
  • 对向量检索技术栈进行技术选型的决策者

1.3 阅读对象

角色关注章节
产品经理/架构师第2章(架构)、第3章(部署选型)
后端开发工程师第4章(使用指南含代码)、第5章(调优)
运维/SRE工程师第3章(部署含命令)、第6章(监控)、第7章(最佳实践)

1.4 术语表

术语通俗解释
向量AI模型"读懂"一段文字/一张图片后生成的一串数字数组
Collection(集合)类似于MySQL中的"表",存储向量数据的容器
Entity(实体)集合中的一行数据,包含一个向量和若干辅助字段
Index(索引)类似书的"目录",将搜索从全表扫描加速到毫秒级
相似度度量衡量两个向量"距离"的数学方式,如余弦相似度、内积

2. 系统架构概述

2.1 整体架构图

Milvus 2.x 版本采用云原生、存算分离的架构设计:将"计算"(搜索、索引构建)与"存储"(数据持久化)分离,两者可以独立扩展。

存储层

计算层

接入层

客户端层

Python/Java/Go SDK

可视化工具 Attu

Proxy(代理节点)
• 请求路由
• 结果聚合

Query Node(查询节点)
• 加载索引到内存
• 执行向量搜索

Data Node(数据节点)
• 处理写入请求
• 数据持久化

Index Node(索引节点)
• 异步构建索引
• 索引文件管理

Meta Store(元数据)
基于Etcd

Object Storage(对象存储)
支持 MinIO / S3

2.2 核心组件说明

组件通俗解释核心职责
Proxy系统的"门卫"和"调度员"接收客户端请求,判断请求类型并分发给下游组件
Query Node系统的"检索员"将索引加载到内存中,实际执行向量相似性搜索
Data Node系统的"档案管理员"处理数据写入,将数据持久化到对象存储
Index Node系统的"图书编目员"异步为数据构建索引,保存索引文件到对象存储
Meta Store系统的"大脑"存储所有元数据(集合、Schema、索引参数),基于Etcd
Object Storage系统的"仓库"存储所有数据文件,支持MinIO/S3等

2.3 数据写入流程

Index NodeObject StorageMeta StoreData NodeProxy客户端 (SDK)Index NodeObject StorageMeta StoreData NodeProxy客户端 (SDK)异步流程(不阻塞写入)1. 发送插入请求2. 路由到Data Node3. 验证数据格式4. 记录写入日志5. 数据持久化6. 返回写入成功7. 返回插入结果8. 数据量达阈值,触发索引构建9. 保存索引文件10. 更新索引元数据

2.4 数据查询流程

Object StorageQuery NodeMeta StoreProxy客户端Object StorageQuery NodeMeta StoreProxy客户端alt[索引已在内存][索引未加载]1. 发送搜索请求2. 查询集合元数据3. 分发请求给Query Node4a. 直接在内存中执行搜索4b. 从对象存储加载索引4c. 加载完成后执行搜索5. 返回Top-K结果6. 聚合多个结果7. 返回最终结果

3. 部署方案

3.1 部署方式选型决策树

< 10万条

10万 - 1000万

> 1000万

开始部署选型

数据量预估

Milvus Lite
• 本地开发
• 快速原型验证

对高可用有要求?

分布式集群
• K8s + Operator
• 水平扩展

单机版 (Standalone)
• Docker部署
• 运维简单

部署完成

3.2 三种部署方式对比

部署方式类比适用场景核心特点
Milvus Lite本地笔记本单机版个人学习、功能验证pip install即可,功能有裁剪
Standalone服务器完整版中小规模生产Docker一键启动,功能完整
Distributed云端分布式集群大规模生产(千万级以上)高可用、弹性扩缩容

3.3 Milvus Lite 部署(开发测试)

适用场景:本地开发、快速原型验证、学习测试。

3.3.1 安装
# 安装Milvus Lite(包含PyMilvus)
pip install pymilvus>=2.4.0

# 或单独安装
pip install milvus-lite
3.3.2 验证安装
from pymilvus import MilvusClient

# 使用本地文件作为存储
client = MilvusClient(uri="./milvus_demo.db")

# 验证连接
print(client.list_collections())  # 输出: []

3.4 单机版部署(Standalone)

适用场景:中小规模生产环境、功能完整的测试环境。

3.4.1 环境准备
依赖项版本要求说明
Docker Engine≥ 20.10容器运行时
Docker Compose≥ 2.0可选,用于多容器编排
磁盘空间≥ 50GB根据数据量规划
3.4.2 Docker快速部署
# 1. 拉取并启动Milvus单机容器
docker run -d \
  --name milvus-standalone \
  -p 19530:19530 \
  -p 9091:9091 \
  -v /data/milvus:/var/lib/milvus \
  milvusdb/milvus:v2.4.3

# 2. 检查容器运行状态
docker ps | grep milvus-standalone

# 3. 查看启动日志(确认无报错)
docker logs -f milvus-standalone

# 4. 测试端口连通性
curl http://localhost:9091/healthz
# 期望返回: {"status":"ok"}

参数说明

参数说明
-p 19530:19530gRPC服务端口,客户端连接使用
-p 9091:9091健康检查与监控指标端口
-v /data/milvus:/var/lib/milvus数据持久化挂载,生产环境务必配置
3.4.3 Docker Compose部署(推荐用于生产单机)

使用Docker Compose可以更好地管理Milvus及其依赖组件(Etcd、MinIO)。

步骤1:创建docker-compose.yml文件

# 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
    volumes:
      - /data/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
    network_mode: host

  minio:
    container_name: milvus-minio
    image: minio/minio:RELEASE.2023-03-20T20-16-18Z
    environment:
      MINIO_ACCESS_KEY: minioadmin
      MINIO_SECRET_KEY: minioadmin
    volumes:
      - /data/minio:/minio_data
    command: minio server /minio_data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3
    network_mode: host

  standalone:
    container_name: milvus-standalone
    image: milvusdb/milvus:v2.4.3
    command: ["milvus", "run", "standalone"]
    environment:
      ETCD_ENDPOINTS: localhost:2379
      MINIO_ADDRESS: localhost:9000
    volumes:
      - /data/milvus:/var/lib/milvus
    ports:
      - "19530:19530"
      - "9091:9091"
    depends_on:
      - etcd
      - minio
    network_mode: host

步骤2:启动服务

# 启动所有容器
docker-compose up -d

# 查看容器状态
docker-compose ps

# 查看日志
docker-compose logs -f standalone

# 停止服务
docker-compose down

# 停止并删除数据卷(谨慎操作)
docker-compose down -v

3.5 集群版部署(Distributed on Kubernetes)

适用场景:千万级以上向量、高可用与水平扩展有强需求的大规模生产环境。

3.5.1 前置条件
# 确认Kubernetes集群版本(需≥1.20)
kubectl version --short

# 确认已安装kubectl
kubectl cluster-info
3.5.2 使用Milvus Operator部署(推荐)

步骤1:安装cert-manager

kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.12.0/cert-manager.yaml

# 等待cert-manager就绪
kubectl wait --for=condition=available --timeout=300s deployment/cert-manager -n cert-manager

步骤2:安装Milvus Operator

# 克隆Milvus Operator仓库
git clone https://github.com/zilliztech/milvus-operator.git
cd milvus-operator

# 安装Operator到K8s集群
make deploy
# 或者直接应用
kubectl apply -f config/deployments/operator.yaml

# 验证Operator是否正常运行
kubectl get pods -n milvus-operator

步骤3:创建Milvus集群配置文件

# milvus-cluster.yaml
apiVersion: milvus.io/v1beta1
kind: Milvus
metadata:
  name: milvus-cluster
  namespace: default
spec:
  mode: cluster
  dependencies:
    etcd:
      inCluster:
        values:
          replicaCount: 3
          persistence:
            enabled: true
            size: 20Gi
    minio:
      inCluster:
        values:
          mode: distributed
          replicas: 4
          persistence:
            enabled: true
            size: 100Gi
  components:
    proxy:
      replicas: 2
      serviceType: LoadBalancer
    queryNode:
      replicas: 3
      resources:
        requests:
          memory: "8Gi"
          cpu: "4"
        limits:
          memory: "16Gi"
          cpu: "8"
    dataNode:
      replicas: 2
    indexNode:
      replicas: 2

步骤4:部署Milvus集群

# 应用配置
kubectl apply -f milvus-cluster.yaml

# 查看部署状态
kubectl get milvus -n default
kubectl get pods -n default | grep milvus

# 查看部署进度
kubectl describe milvus milvus-cluster -n default

# 获取服务访问地址
kubectl get svc -n default | grep milvus-cluster-proxy
3.5.3 使用Helm部署(备选方案)
# 添加Milvus Helm仓库
helm repo add milvus https://zilliztech.github.io/milvus-helm/
helm repo update

# 部署集群(使用自定义配置)
helm install my-milvus milvus/milvus \
  --set mode=cluster \
  --set proxy.replicas=2 \
  --set queryNode.replicas=3 \
  --set dataNode.replicas=2 \
  --set indexNode.replicas=2 \
  --set persistence.enabled=true \
  --set etcd.replicaCount=3 \
  --set minio.mode=distributed \
  --set minio.replicas=4

# 查看部署状态
helm status my-milvus
kubectl get pods | grep my-milvus

3.6 部署验证

无论采用哪种部署方式,部署完成后使用以下方法验证:

# 方法1:通过健康检查接口
curl http://localhost:9091/healthz
# 期望返回: {"status":"ok"}

# 方法2:使用Python客户端验证
python3 -c "
from pymilvus import MilvusClient
client = MilvusClient(uri='http://localhost:19530')
print('连接成功!当前集合列表:', client.list_collections())
"

预期输出

连接成功!当前集合列表: []

4. 核心使用指南(含完整代码)

4.1 开发流程总览

1. 建立连接

2. 设计Schema

3. 创建集合

4. 构建索引

5. 插入数据

6. 加载集合

7. 执行搜索

8. 管理/清理

4.2 客户端SDK安装

# 安装PyMilvus(必需)
pip install pymilvus>=2.4.0

# 如需使用Embedding模型(RAG场景推荐)
pip install sentence-transformers

# 如需处理数据(可选)
pip install pandas numpy

4.3 连接数据库

from pymilvus import MilvusClient

# ---------- 不同部署方式的连接方式 ----------

# 方式1:连接本地Docker单机版
client = MilvusClient(uri="http://localhost:19530")

# 方式2:连接远程Milvus服务器
# client = MilvusClient(uri="http://192.168.1.100:19530")

# 方式3:连接开启认证的Milvus(用户名/密码)
# client = MilvusClient(
#     uri="http://localhost:19530",
#     token="root:Milvus"  # 格式:用户名:密码
# )

# 方式4:连接Milvus Lite(本地文件)
# client = MilvusClient(uri="./milvus_demo.db")

# 方式5:Kubernetes集群内连接
# client = MilvusClient(uri="http://milvus-cluster-proxy.default.svc.cluster.local:19530")

# 方式6:使用特定数据库(v2.4+支持多数据库)
# client = MilvusClient(uri="http://localhost:19530", db_name="production_db")

print("连接成功!")

4.4 管理数据库(多数据库功能)

# 列出所有数据库
databases = client.list_databases()
print(f"可用数据库: {databases}")

# 创建新数据库
client.create_database(db_name="production_db")

# 切换到指定数据库
client.using_database(db_name="production_db")

# 删除数据库(谨慎操作)
# client.drop_database(db_name="test_db")

4.5 管理集合(Collection)

4.5.1 创建集合(含Schema和索引)
from pymilvus import DataType

# 定义常量
COLLECTION_NAME = "knowledge_base"
DIMENSION = 768  # 向量维度(以BERT-base为例)

# ---------- 步骤1:创建Schema(表结构) ----------
# Schema定义了集合的字段结构
schema = client.create_schema(
    auto_id=True,              # 主键自动生成
    enable_dynamic_field=False # 是否允许动态字段(建议关闭以保持性能)
)

# 添加字段
schema.add_field("id", DataType.INT64, is_primary=True)           # 主键
schema.add_field("embedding", DataType.FLOAT_VECTOR, dim=DIMENSION) # 向量字段
schema.add_field("title", DataType.VARCHAR, max_length=256)       # 标题
schema.add_field("content", DataType.VARCHAR, max_length=65535)   # 内容
schema.add_field("category", DataType.VARCHAR, max_length=64)     # 分类
schema.add_field("publish_date", DataType.INT64)                  # 发布时间(时间戳)
schema.add_field("author", DataType.VARCHAR, max_length=128)      # 作者

# ---------- 步骤2:准备索引参数 ----------
# 索引类似数据库的"目录",用于加速搜索
index_params = client.prepare_index_params()

# 为向量字段添加索引
index_params.add_index(
    field_name="embedding",
    index_type="HNSW",              # 索引类型:HNSW(图索引,查询快)
    metric_type="COSINE",           # 相似度度量:余弦相似度
    params={
        "M": 16,                    # 每个节点的最大连接数(推荐16-64)
        "efConstruction": 200       # 构建时的搜索宽度(推荐200-500)
    }
)

# 为标量字段添加索引(加速过滤查询)
index_params.add_index(
    field_name="category",
    index_type="INVERTED"           # 倒排索引,用于加速标量过滤
)

# 也可以为其他标量字段添加索引
index_params.add_index(
    field_name="publish_date",
    index_type="STL_SORT"           # 有序索引,用于范围查询
)

# ---------- 步骤3:创建集合 ----------
client.create_collection(
    collection_name=COLLECTION_NAME,
    schema=schema,
    index_params=index_params,
    consistency_level="Bounded"     # 一致性级别:Strong/Bounded/Session/Eventually
)

print(f"集合 '{COLLECTION_NAME}' 创建成功!")
4.5.2 集合管理操作
# 列出所有集合
collections = client.list_collections()
print(f"所有集合: {collections}")

# 检查集合是否存在
exists = client.has_collection(COLLECTION_NAME)
print(f"集合存在: {exists}")

# 查看集合详情
stats = client.describe_collection(COLLECTION_NAME)
print(f"集合统计: {stats}")

# 获取索引信息
index_info = client.describe_index(COLLECTION_NAME)
print(f"索引信息: {index_info}")

# 获取集合统计数据(行数等)
count = client.get_collection_stats(COLLECTION_NAME)
print(f"集合统计: {count}")

# 删除集合(谨慎操作!)
# client.drop_collection(COLLECTION_NAME)

4.6 数据写入操作

4.6.1 插入单条/批量数据
import numpy as np
import time

# 模拟生成向量(实际应用中使用Embedding模型)
def generate_embedding(text, dim=DIMENSION):
    """实际使用时应调用真实的Embedding模型"""
    # 这里用随机数模拟,实际请替换为模型调用
    return np.random.randn(dim).tolist()

# 准备示例数据
data = [
    {
        "embedding": generate_embedding("人工智能发展历史"),
        "title": "人工智能简史",
        "content": "人工智能从1956年达特茅斯会议开始,经历了多次浪潮...",
        "category": "AI",
        "publish_date": int(time.time()),
        "author": "张教授"
    },
    {
        "embedding": generate_embedding("向量数据库技术原理"),
        "title": "向量数据库技术综述",
        "content": "向量数据库是AI应用的关键基础设施,用于存储和检索向量数据...",
        "category": "Database",
        "publish_date": int(time.time()),
        "author": "李博士"
    },
    {
        "embedding": generate_embedding("大语言模型应用"),
        "title": "LLM应用开发实践",
        "content": "大语言模型在RAG、Agent等场景中有广泛应用...",
        "category": "AI",
        "publish_date": int(time.time()),
        "author": "王工程师"
    }
]

# 批量插入
insert_result = client.insert(
    collection_name=COLLECTION_NAME,
    data=data
)

print(f"插入成功,主键ID: {insert_result['ids']}")
print(f"插入状态: {insert_result['status']}")
print(f"共插入 {len(insert_result['ids'])} 条数据")
4.6.2 批量大数据写入(分批处理)
import pandas as pd
from tqdm import tqdm

def batch_insert_from_dataframe(client, df, collection_name, batch_size=1000):
    """
    分批从DataFrame导入数据
    
    Args:
        client: MilvusClient实例
        df: pandas DataFrame,需包含所有字段
        collection_name: 集合名
        batch_size: 每批次大小
    """
    total_rows = len(df)
    success_count = 0
    
    print(f"开始导入 {total_rows} 条数据,批次大小: {batch_size}")
    
    for start in tqdm(range(0, total_rows, batch_size), desc="插入进度"):
        end = min(start + batch_size, total_rows)
        batch_df = df.iloc[start:end]
        
        # 将DataFrame行转换为Milvus数据格式
        batch_data = []
        for _, row in batch_df.iterrows():
            # 注意:embedding字段需要是list类型
            embedding = row["embedding"]
            if isinstance(embedding, str):
                # 如果是字符串,尝试解析为列表
                import json
                embedding = json.loads(embedding)
            
            batch_data.append({
                "embedding": embedding,
                "title": row["title"],
                "content": row["content"],
                "category": row["category"],
                "publish_date": row["publish_date"],
                "author": row.get("author", "")
            })
        
        try:
            result = client.insert(
                collection_name=collection_name,
                data=batch_data
            )
            success_count += len(result['ids'])
        except Exception as e:
            print(f"批次 {start//batch_size} 插入失败: {e}")
            continue
    
    print(f"导入完成,成功插入 {success_count} 条数据")
    return success_count

# 使用示例
# df = pd.read_csv("knowledge_data.csv")
# success = batch_insert_from_dataframe(client, df, COLLECTION_NAME)
4.6.3 数据更新(Upsert)
# Upsert:如果ID存在则更新,不存在则插入
upsert_data = [
    {
        "id": 1,  # 指定主键ID
        "title": "人工智能简史(更新版)",
        "content": "更新后的内容..."
    }
]

result = client.upsert(
    collection_name=COLLECTION_NAME,
    data=upsert_data
)
print(f"Upsert结果: {result}")
4.6.4 数据删除
# 方式1:根据ID删除
delete_result = client.delete(
    collection_name=COLLECTION_NAME,
    ids=[1, 2, 3]  # 删除指定ID的数据
)

# 方式2:根据条件表达式删除
delete_result = client.delete(
    collection_name=COLLECTION_NAME,
    filter="category == 'AI' and publish_date < 1700000000"
)

print(f"删除了 {delete_result['delete_count']} 条数据")

4.7 相似性搜索

4.7.1 加载集合到内存
# ⚠️ 重要:搜索前必须先将集合加载到内存
# 检查加载状态
load_state = client.get_load_state(collection_name=COLLECTION_NAME)
print(f"加载状态: {load_state}")

# 如果未加载,执行加载
if load_state.get('state') != 'Loaded':
    client.load_collection(collection_name=COLLECTION_NAME)
    print("集合加载完成")

# 如果需要释放内存,可以卸载
# client.release_collection(collection_name=COLLECTION_NAME)
4.7.2 基础向量搜索
# 准备查询向量
query_text = "机器学习的未来趋势"
query_vector = generate_embedding(query_text)

# 执行搜索
search_results = client.search(
    collection_name=COLLECTION_NAME,
    data=[query_vector],              # 支持批量搜索,传入多个向量
    limit=5,                          # 返回Top-K结果
    output_fields=["title", "content", "category", "publish_date", "author"],
    search_params={
        "metric_type": "COSINE",      # 相似度度量,需与索引一致
        "params": {
            "ef": 64                  # HNSW搜索宽度(值越大召回率越高,但速度越慢)
        }
    },
    # 超时设置(毫秒)
    timeout=10000
)

# 解析并打印结果
print(f"查询: {query_text}")
print("=" * 60)

for i, result in enumerate(search_results):
    print(f"结果 {i+1}:")
    for hit in result:
        print(f"  ID: {hit['id']}")
        print(f"  相似度: {hit['distance']:.4f}")
        print(f"  标题: {hit['entity']['title']}")
        print(f"  分类: {hit['entity']['category']}")
        print(f"  作者: {hit['entity']['author']}")
        print(f"  内容预览: {hit['entity']['content'][:100]}...")
        print("  ---")
4.7.3 带标量过滤的混合搜索
# 在搜索时同时使用标量过滤条件
search_results = client.search(
    collection_name=COLLECTION_NAME,
    data=[query_vector],
    limit=5,
    filter="category == 'AI' and publish_date > 1600000000",  # 过滤条件
    output_fields=["title", "content", "category", "publish_date"],
    search_params={
        "metric_type": "COSINE",
        "params": {"ef": 64}
    }
)

print("带过滤条件的搜索结果:")
for hit in search_results[0]:
    print(f"  {hit['entity']['title']} (分类: {hit['entity']['category']}) 相似度: {hit['distance']:.4f}")
4.7.4 批量搜索
# 同时搜索多个查询向量
query_texts = ["深度学习进展", "自然语言处理应用"]
query_vectors = [generate_embedding(text) for text in query_texts]

batch_results = client.search(
    collection_name=COLLECTION_NAME,
    data=query_vectors,           # 传入多个向量
    limit=3,
    output_fields=["title"],
    search_params={"metric_type": "COSINE", "params": {"ef": 64}}
)

# batch_results[0] 对应第一个查询
# batch_results[1] 对应第二个查询
for i, results in enumerate(batch_results):
    print(f"查询 '{query_texts[i]}' 的结果:")
    for hit in results:
        print(f"  {hit['entity']['title']} (相似度: {hit['distance']:.4f})")
4.7.5 范围搜索(指定搜索范围)
# 使用半径搜索,只返回距离小于阈值的向量
search_results = client.search(
    collection_name=COLLECTION_NAME,
    data=[query_vector],
    limit=10,
    search_params={
        "metric_type": "COSINE",
        "params": {
            "ef": 64,
            "radius": 0.7           # 只返回相似度 > 0.7的结果
        }
    },
    output_fields=["title"]
)

print(f"找到 {len(search_results[0])} 条相似度大于0.7的结果")

4.8 分区管理(高级功能)

分区可以将数据按逻辑分组,提高查询效率。

# ---------- 创建分区 ----------
partition_name = "partition_2024"

# 检查分区是否存在
partitions = client.list_partitions(COLLECTION_NAME)
print(f"现有分区: {partitions}")

if partition_name not in partitions:
    client.create_partition(
        collection_name=COLLECTION_NAME,
        partition_name=partition_name
    )
    print(f"分区 '{partition_name}' 创建成功")

# ---------- 插入数据到指定分区 ----------
partition_data = [
    {
        "embedding": generate_embedding("2024年AI发展报告"),
        "title": "2024 AI趋势报告",
        "content": "2024年AI技术的主要趋势...",
        "category": "AI",
        "publish_date": int(time.time()),
        "author": "AI研究院"
    }
]

client.insert(
    collection_name=COLLECTION_NAME,
    data=partition_data,
    partition_name=partition_name
)

# ---------- 在指定分区中搜索 ----------
search_results = client.search(
    collection_name=COLLECTION_NAME,
    data=[query_vector],
    partition_names=[partition_name],  # 只搜索该分区
    limit=5,
    output_fields=["title"]
)

print(f"在分区 '{partition_name}' 中找到 {len(search_results[0])} 条结果")

# ---------- 删除分区(谨慎操作) ----------
# client.drop_partition(COLLECTION_NAME, partition_name)

4.9 完整RAG应用示例

这是一个完整的RAG(检索增强生成)应用,整合了Embedding、Milvus检索和LLM生成。

from sentence_transformers import SentenceTransformer
import requests
import json

class MilvusRAGSystem:
    """
    基于Milvus的RAG系统
    """
    def __init__(self, milvus_uri, collection_name, embedding_model_name="all-MiniLM-L6-v2"):
        """
        初始化RAG系统
        
        Args:
            milvus_uri: Milvus服务地址
            collection_name: 集合名称
            embedding_model_name: Embedding模型名称
        """
        self.client = MilvusClient(uri=milvus_uri)
        self.collection_name = collection_name
        self.embedding_model = SentenceTransformer(embedding_model_name)
        self._ensure_collection()
        print(f"RAG系统初始化完成,使用模型: {embedding_model_name}")
    
    def _ensure_collection(self):
        """确保集合存在,如果不存在则创建"""
        if not self.client.has_collection(self.collection_name):
            # 获取模型向量维度
            dim = self.embedding_model.get_sentence_embedding_dimension()
            
            # 创建Schema
            schema = self.client.create_schema(auto_id=True)
            schema.add_field("id", DataType.INT64, is_primary=True)
            schema.add_field("embedding", DataType.FLOAT_VECTOR, dim=dim)
            schema.add_field("text", DataType.VARCHAR, max_length=65535)
            schema.add_field("source", DataType.VARCHAR, max_length=256)
            schema.add_field("timestamp", DataType.INT64)
            
            # 创建索引
            index_params = self.client.prepare_index_params()
            index_params.add_index(
                "embedding",
                index_type="HNSW",
                metric_type="COSINE",
                params={"M": 16, "efConstruction": 200}
            )
            
            # 创建集合
            self.client.create_collection(
                self.collection_name,
                schema=schema,
                index_params=index_params
            )
            print(f"集合 '{self.collection_name}' 创建成功,向量维度: {dim}")
    
    def add_documents(self, documents, source="unknown"):
        """
        批量添加文档
        
        Args:
            documents: 文档文本列表
            source: 文档来源
        """
        import time
        
        if not documents:
            return 0
        
        # 生成向量
        print(f"正在为 {len(documents)} 个文档生成向量...")
        embeddings = self.embedding_model.encode(documents)
        
        # 准备数据
        data = []
        for text, embedding in zip(documents, embeddings):
            data.append({
                "embedding": embedding.tolist(),
                "text": text,
                "source": source,
                "timestamp": int(time.time())
            })
        
        # 插入数据
        result = self.client.insert(self.collection_name, data)
        print(f"成功添加 {len(result['ids'])} 个文档")
        return len(result['ids'])
    
    def search(self, query, top_k=5):
        """
        搜索相似文档
        
        Args:
            query: 查询文本
            top_k: 返回结果数量
        
        Returns:
            list: 搜索结果列表
        """
        # 生成查询向量
        query_embedding = self.embedding_model.encode(query).tolist()
        
        # 加载集合
        self.client.load_collection(self.collection_name)
        
        # 执行搜索
        results = self.client.search(
            self.collection_name,
            data=[query_embedding],
            limit=top_k,
            output_fields=["text", "source", "timestamp"],
            search_params={
                "metric_type": "COSINE",
                "params": {"ef": 64}
            }
        )
        
        # 格式化结果
        formatted_results = []
        for hit in results[0]:
            formatted_results.append({
                "text": hit['entity']['text'],
                "similarity": hit['distance'],
                "source": hit['entity']['source'],
                "timestamp": hit['entity']['timestamp']
            })
        
        return formatted_results
    
    def answer(self, query, top_k=3, llm_api_url=None):
        """
        RAG问答
        
        Args:
            query: 用户问题
            top_k: 检索文档数量
            llm_api_url: LLM API地址(可选)
        
        Returns:
            dict: 包含回答和引用的结果
        """
        # 1. 检索相关知识
        contexts = self.search(query, top_k=top_k)
        
        if not contexts:
            return {
                "answer": "未找到相关信息,请换个问题试试。",
                "references": []
            }
        
        # 2. 构建上下文
        context_text = "\n\n---\n\n".join([
            f"[来源: {ctx['source']}] {ctx['text']}"
            for ctx in contexts
        ])
        
        # 3. 构建Prompt
        prompt = f"""请基于以下参考信息回答用户的问题。如果参考信息中没有相关内容,请如实告知。

【参考信息】
{context_text}

【用户问题】
{query}

【要求】
- 回答要准确、简洁
- 请在回答末尾列出引用来源

【回答】"""

        # 4. 调用LLM生成回答
        if llm_api_url:
            try:
                response = requests.post(
                    llm_api_url,
                    json={"prompt": prompt, "max_tokens": 500},
                    timeout=30
                )
                answer = response.json().get("answer", prompt[:100] + "...")
            except Exception as e:
                answer = f"LLM调用失败: {e}\n基于检索到的信息,相关文档内容如下:\n{context_text[:500]}..."
        else:
            # 如果没有配置LLM API,直接返回检索到的上下文
            answer = f"基于检索到的 {len(contexts)} 条相关文档:\n\n{context_text}"
        
        return {
            "answer": answer,
            "references": [
                {
                    "text": ctx['text'][:200] + "...",
                    "similarity": ctx['similarity'],
                    "source": ctx['source']
                }
                for ctx in contexts
            ]
        }

# ---------- 使用示例 ----------
if __name__ == "__main__":
    # 1. 初始化RAG系统
    rag = MilvusRAGSystem(
        milvus_uri="http://localhost:19530",
        collection_name="rag_docs"
    )
    
    # 2. 添加文档
    documents = [
        "深度学习是机器学习的一个分支,使用多层神经网络进行特征学习。",
        "向量数据库专门用于存储和查询高维向量数据,广泛应用于相似性搜索。",
        "RAG(检索增强生成)结合了信息检索和LLM,能有效减少幻觉问题。",
        "Transformer模型自2017年提出以来,已成为自然语言处理的主流架构。"
    ]
    rag.add_documents(documents, source="技术博客")
    
    # 3. 执行问答
    question = "什么是RAG?"
    result = rag.answer(question, top_k=2)
    
    print(f"问题: {question}")
    print("=" * 60)
    print(f"回答: {result['answer']}")
    print("\n引用来源:")
    for ref in result['references']:
        print(f"  - 相似度: {ref['similarity']:.4f}, 来源: {ref['source']}")
        print(f"    片段: {ref['text']}")

代码说明

  • 第55-61行:展示如何批量添加文档并自动生成向量
  • 第72-93行:实现语义搜索功能
  • 第103-154行:实现完整的RAG问答,包括检索、上下文构建和LLM调用
  • 第160-181行:完整的使用示例,包含初始化、数据添加和问答

5. 性能调优

5.1 索引类型选型决策

< 5万

> 5万

查询速度最快

平衡性价比

存储空间有限

选择索引类型

数据量大小?

FLAT
• 暴力搜索
• 100%精确
• 无需构建时间

首要追求什么?

HNSW
• 图索引
• 查询毫秒级
• 内存消耗大

IVF_FLAT
• 聚类索引
• 最常用方案

IVF_SQ8
• 量化压缩
• 节省磁盘

End

5.2 索引参数配置详解

索引类型参数推荐值说明
HNSWM16-64每个节点的最大连接数,越大召回率越高,内存消耗也越大
efConstruction200-500构建时的搜索宽度,越大索引质量越高
ef(搜索时)64-200搜索时的探索范围,越大召回率越高
IVF_FLATnlistsqrt(n)~4*sqrt(n)聚类中心数,n为数据总量
nprobe(搜索时)1-64搜索时查询的聚类数,建议为nlist的1%-10%

代码示例

# HNSW索引配置(查询速度优先)
index_params.add_index(
    field_name="embedding",
    index_type="HNSW",
    metric_type="COSINE",
    params={
        "M": 32,                    # 推荐16-64
        "efConstruction": 400       # 推荐200-500
    }
)

# 搜索时调优
search_params = {
    "metric_type": "COSINE",
    "params": {
        "ef": 200                   # 值越大召回率越高
    }
}

# IVF_FLAT索引配置(平衡方案)
index_params.add_index(
    field_name="embedding",
    index_type="IVF_FLAT",
    metric_type="COSINE",
    params={
        "nlist": 1024               # 聚类数
    }
)

# 搜索时调优
search_params = {
    "metric_type": "COSINE",
    "params": {
        "nprobe": 16                # 探测聚类数
    }
}

5.3 系统级性能优化清单

优化维度具体措施代码示例
批量写入每次插入1000-10000条使用batch_insert_from_dataframe函数
连接复用全局单例MilvusClient在应用启动时创建一次,全局使用
内存管理只加载需要查询的集合load_collection / release_collection
索引构建时机在业务低峰期触发通过create_index手动触发
# 内存管理示例
# 查询前加载
client.load_collection("collection_a")

# 执行搜索...

# 查询完后释放
client.release_collection("collection_a")

# 如果需要加载其他集合
client.load_collection("collection_b")

5.4 性能测试脚本

import time
import numpy as np
from pymilvus import MilvusClient

def performance_test(client, collection_name, test_rounds=100):
    """简单性能测试"""
    
    # 准备测试向量
    test_vectors = [np.random.randn(768).tolist() for _ in range(10)]
    
    # 加载集合
    start = time.time()
    client.load_collection(collection_name)
    load_time = time.time() - start
    print(f"集合加载耗时: {load_time:.3f}s")
    
    # 测试搜索延迟
    latencies = []
    for i in range(test_rounds):
        vector = test_vectors[i % len(test_vectors)]
        start = time.time()
        client.search(
            collection_name,
            data=[vector],
            limit=10,
            search_params={"metric_type": "COSINE", "params": {"ef": 64}}
        )
        latencies.append(time.time() - start)
    
    print(f"搜索延迟统计 ({test_rounds}次):")
    print(f"  平均: {np.mean(latencies)*1000:.2f}ms")
    print(f"  P50: {np.percentile(latencies, 50)*1000:.2f}ms")
    print(f"  P95: {np.percentile(latencies, 95)*1000:.2f}ms")
    print(f"  P99: {np.percentile(latencies, 99)*1000:.2f}ms")

6. 监控告警

6.1 监控架构

管理工具

可视化

指标采集

Milvus集群

:9091

:9091

:9091

:9091

HTTP

Proxy

Query Node

Data Node

Index Node

Prometheus

Grafana Dashboard

AlertManager

邮件/钉钉

Attu GUI

6.2 健康检查命令

# 基础健康检查
curl http://localhost:9091/healthz
# 期望返回: {"status":"ok"}

# 查看服务就绪状态
curl http://localhost:9091/ready
# 期望返回: {"status":"ok"}

# 获取版本信息
curl http://localhost:9091/version
# 返回示例: {"version":"v2.4.3"}

6.3 Attu可视化工具部署

# Docker部署Attu
docker run -d \
  --name attu \
  -p 8000:3000 \
  -e MILVUS_URL=localhost:19530 \
  zilliz/attu:latest

# 访问地址: http://localhost:8000

6.4 关键监控指标

指标类别关键指标告警阈值说明
服务可用性/healthz状态连续失败3次服务不可用
查询性能P99查询延迟> 500ms用户体验下降
写入性能写入延迟> 200ms数据入库瓶颈
资源使用内存使用率> 80%有OOM风险
索引状态未索引数据量> 10000条索引构建延迟

7. 最佳实践与避坑指南

7.1 数据生命周期管理

数据写入

自动构建索引

查询

冷数据归档

过期数据清理

释放未使用集合

7.2 常见陷阱与解决方案

陷阱现象解决方案
未加载集合就搜索搜索返回空结果搜索前调用 client.load_collection()
向量维度不一致插入时报维度错误检查所有数据的向量长度是否与dim一致
索引参数不当导致OOMQuery Node容器重启使用IVF_SQ8代替HNSW
主键冲突插入失败使用auto_id=True
生产环境未持久化容器重启数据丢失Docker挂载-v卷;K8s使用PVC
忘记创建索引搜索性能极差创建集合时指定index_params

7.3 生产环境检查清单

# 1. 检查数据持久化
docker inspect milvus-standalone | grep Mounts
# 确认有Volume挂载

# 2. 检查健康状态
curl http://localhost:9091/healthz

# 3. 检查集合状态
python3 -c "
from pymilvus import MilvusClient
c = MilvusClient('http://localhost:19530')
for col in c.list_collections():
    stats = c.get_collection_stats(col)
    print(f'{col}: {stats}条记录')
"

# 4. 检查内存使用
docker stats milvus-standalone --no-stream

# 5. 备份配置
docker cp milvus-standalone:/milvus/configs/milvus.yaml ./milvus_backup.yaml

8. 总结与建议

8.1 技术选型决策总结

你的团队情况

是否有专业的
K8s运维能力?

数据量
是否 < 100万?

采用 K8s + Milvus Operator
部署分布式集群

Milvus Lite 或 Standalone
开发测试用

Milvus Standalone + Docker Compose
中小规模生产

落地生产

8.2 核心建议

  1. 从小开始,逐步演进:先用Milvus Lite或Standalone快速验证,确认需求后再规划集群迁移
  2. 索引是性能之魂:理解不同索引类型的原理和参数,能解决80%的性能问题
  3. 监控先行:部署时就配置好监控告警
  4. 善用官方工具:Attu可视化工具可极大降低运维和调试复杂度
  5. 代码版本管理:将Schema定义和索引参数纳入版本管理

8.3 快速命令速查

# 部署
docker run -d --name milvus -p 19530:19530 -v /data/milvus:/var/lib/milvus milvusdb/milvus:v2.4.3

# 健康检查
curl http://localhost:9091/healthz

# 查看日志
docker logs -f milvus-standalone

# 进入容器调试
docker exec -it milvus-standalone /bin/bash

# 停止/启动/删除
docker stop milvus-standalone
docker start milvus-standalone
docker rm -f milvus-standalone

# Attu管理界面
docker run -d -p 8000:3000 -e MILVUS_URL=localhost:19530 zilliz/attu:latest

9. 参考资源

资源类型链接/说明
官方文档https://milvus.io/docs
GitHub仓库https://github.com/milvus-io/milvus
PyMilvus SDKhttps://github.com/milvus-io/pymilvus
Milvus Operatorhttps://github.com/zilliztech/milvus-operator
社区支持Slack: https://milvus.io/slack
Attu GUIhttps://github.com/zilliztech/attu

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐