RAG 入门-向量存储与企业级向量数据库 milvus

张开发
2026/4/16 8:20:03 15 分钟阅读

分享文章

RAG 入门-向量存储与企业级向量数据库 milvus
你的知识库达到万级、百万级甚至亿级时你会面临检索效率塌方计算量过大传统数据库查找数据是“相等判断”而向量搜索需要计算空间距离。如果你有 100 万条数据每搜一次都要全表扫一遍并计算相似度O(n) 复杂查询一次可能要几秒甚至几分钟。向量数据库通过ANN近似最近邻算法如 HNSW将搜索效率提升到了毫秒级O(log n) 复杂度。维度灾难内存压力一个 1536 维的向量OpenAI 标准看起来不大但几百万条叠在一起会瞬间撑爆普通的服务器内存。向量数据库专门优化了数据的压缩与加载策略。工程化缺失向量数据库不仅仅是“存数据”它还提供了成熟的 CRUD、多租户隔离、数据备份和水平扩展Scale-out能力这些是简单的内存库如 FAISS难以胜任的。核心对比传统数据库 vs 向量数据库传统数据库查找的是精准的数据而向量数据库则更适合模糊搜索。传统数据库 (MySQL)你问“有没有编号为1024的商品”他秒回。你问“有没有长得像苹果的手机”他直接罢工”。向量数据库 (Milvus)他会把所有数据转化为空间坐标告诉你“虽然我没找到完全一样的但有几个神似的数据。”代码层面的直观感受传统数据库MySQL侧重“准”-- 精确查询差一个字都搜不到 SELECT * FROM products WHERE category 手机 AND brand 苹果; -- 范围查询基于确定的数值边界 SELECT * FROM products WHERE price BETWEEN 5000 AND 8000;特点擅长精确匹配基于 B-Tree 索引查询条件极其明确非黑即白。向量数据库Milvus侧重“像”# 相似度查询基于“语义距离” results client.search( collection_nameproducts, data[query_vector], # 搜索“长得像苹果的手机”对应的向量 limit10 )特点擅长模糊搜索基于向量索引HNSW、IVF返回的是 Top-K 个“最相似”的候选者。向量数据库横向对比目前主流的向量数据库有很多我们来对比一下主流向量数据库对比数据库类型开源部署方式性能生态推荐度Milvus专业向量库是本地/云5/55/55/5Qdrant专业向量库是本地/云4/54/54/5Weaviate专业向量库是本地/云4/54/54/5Pinecone云服务否仅云5/54/53/5Chroma轻量级是本地3/53/53/5FAISS库非数据库是本地5/53/53/5pgvectorPostgreSQL 插件是本地/云3/54/53/5详细对比1. Milvus优点性能强大支持亿级向量索引算法丰富HNSW、IVF、DiskANN支持混合检索向量 标量过滤云原生架构易扩展社区活跃文档完善国内团队开发中文支持好缺点部署稍复杂需要 etcd、MinIO资源占用较高适用场景生产环境大规模数据建议百万级以上使用需要高性能和稳定性2. Qdrant优点Rust 编写性能优秀部署简单单二进制文件API 设计优雅支持 payload 过滤缺点社区相对较小中文文档较少大规模数据性能不如 Milvus适用场景中小规模项目快速原型开发喜欢 Rust 生态3. Weaviate优点内置 GraphQL API支持多模态搜索自带向量化模块语义搜索能力强缺点学习曲线陡峭资源占用高配置复杂适用场景知识图谱应用多模态搜索需要 GraphQL4. Pinecone优点完全托管无需运维性能优秀易用性好缺点闭源不可自部署费用较高数据在国外延迟问题适用场景不想自己运维预算充足海外用户5. Chroma优点极简设计易上手轻量级适合开发与 LangChain 集成好缺点性能一般不适合大规模生产功能相对简单适用场景学习和原型开发小规模应用10 万条快速验证想法6. FAISS优点Facebook 出品算法先进性能极强支持 GPU 加速缺点不是数据库只是库没有持久化需自己实现没有分布式支持适用场景研究和实验需要 GPU 加速自己实现存储层7. pgvector优点PostgreSQL 插件易集成利用现有 PG 生态事务支持缺点性能不如专业向量库索引算法有限大规模数据吃力适用场景已有 PostgreSQL数据量不大100 万需要事务支持为什么选择 Milvus综合考虑选择 Milvus 作为本教程的向量数据库原因如下1. 性能强大# Milvus 可以轻松处理亿级向量 # 查询延迟毫秒级 # QPS数千到数万2. 索引算法丰富索引类型适用场景性能FLAT小数据集完美准确率2/5IVF_FLAT中等数据集平衡4/5IVF_PQ大数据集内存受限4/5HNSW高性能查询5/5DiskANN超大规模磁盘存储4/53. 功能完善混合检索向量 标量过滤分区管理Partition多租户支持Database动态字段Dynamic Field全文检索BM25范围搜索Range Search分组搜索Group Search4. 生态完善# 与主流框架无缝集成 from langchain_milvus import Milvus from llama_index.vector_stores import MilvusVectorStore5. 国内支持好中文文档完善社区活跃国内团队国内访问速度快技术支持响应快6. 云原生架构Milvus 架构 ┌─────────────────────────────────────┐ │ Milvus Coordinator │ 协调层 ├─────────────────────────────────────┤ │ Query Node │ Data Node │ Index │ 计算层 ├─────────────────────────────────────┤ │ etcd │ MinIO │ Pulsar │ 存储层 └─────────────────────────────────────┘存储计算分离易于水平扩展高可用性7. 面试现在很多企业也是用的这套方案我们学习这些不就是奔着找工作去的吗直接朝着目标前进不好吗快速搭建 MilvusDocker Compose推荐文件docker-compose.ymlversion: 3.8 services: # ---------------------------------------------------------------- # 1. etcd - 元数据存储 # ---------------------------------------------------------------- etcd: image: quay.io/coreos/etcd:v3.5.5 container_name: milvus-etcd environment: - ETCD_AUTO_COMPACTION_MODErevision - ETCD_AUTO_COMPACTION_RETENTION1000 - ETCD_QUOTA_BACKEND_BYTES4294967296 - ETCD_SNAPSHOT_COUNT50000 volumes: - ./milvus_data/etcd:/etcd command: etcd -advertise-client-urlshttp://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 # ---------------------------------------------------------------- # 2. MinIO - 对象存储 # ---------------------------------------------------------------- minio: image: minio/minio:RELEASE.2023-03-20T20-16-18Z container_name: milvus-minio environment: MINIO_ACCESS_KEY: minioadmin MINIO_SECRET_KEY: minioadmin ports: - 9001:9001 - 9000:9000 volumes: - ./milvus_data/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 # ---------------------------------------------------------------- # 3. Milvus Standalone - 向量数据库 # ---------------------------------------------------------------- milvus-standalone: image: milvusdb/milvus:v2.5.4 container_name: milvus-standalone command: [milvus, run, standalone] security_opt: - seccomp:unconfined environment: ETCD_ENDPOINTS: etcd:2379 MINIO_ADDRESS: minio:9000 # ---------------------------------------------------------------- # 开启认证设置密码 # ---------------------------------------------------------------- # 取消下面两行注释即可开启用户认证 # COMMON_SECURITY_AUTHORIZATIONENABLED: true # 开启认证 # COMMON_SECURITY_DEFAULTROOTPASSWORD: your_password_here # 设置 root 用户密码 # # 开启后Python 连接时需要提供 token # client MilvusClient( # urihttp://localhost:19530, # tokenroot:your_password_here # ) volumes: - ./milvus_data/milvus:/var/lib/milvus healthcheck: test: [CMD, curl, -f, http://localhost:9091/healthz] interval: 30s start_period: 90s timeout: 20s retries: 3 ports: - 19530:19530 - 9091:9091 depends_on: - etcd - minio启动步骤# 1. 进入目录 cd rag/08-向量存储/ # 2. 启动所有服务 docker compose up -d # 3. 查看服务状态 docker compose ps # 4. 查看日志等待启动完成 docker compose logs -f milvus-standalone # 看到这行说明启动成功 # [INFO] Milvus Proxy successfully started验证安装from pymilvus import MilvusClient # 连接 Milvus client MilvusClient(urihttp://localhost:19530) # 列出数据库 databases client.list_databases() print(数据库列表:, databases) # [default] print(✓ Milvus 连接成功)停止服务# 停止服务保留数据 docker compose stop # 停止并删除容器保留数据 docker compose down # 停止并删除所有数据 docker compose down -v rm -rf milvus_data/服务端口说明服务端口说明Milvus19530gRPC 接口主要Milvus9091HTTP 监控接口MinIO9000对象存储 APIMinIO9001Web 控制台etcd2379元数据存储访问 MinIO 控制台浏览器打开http://localhost:9001用户名minioadmin密码minioadmin可以看到 Milvus 存储的向量数据。Milvus 核心概念1. Database数据库类比图书馆的不同楼层# 创建数据库 client.create_database(db_namemy_rag_system) # 切换数据库 client.use_database(db_namemy_rag_system) # 列出所有数据库 databases client.list_databases()用途隔离不同业务多租户管理2. Collection集合类比图书馆的书架# 创建集合快速模式 client.create_collection( collection_namedocuments, dimension128 # 向量维度 )特点必须包含向量字段类似 MySQL 的表3. Schema表结构类比书籍的目录格式from pymilvus import CollectionSchema, FieldSchema, DataType # 定义字段 fields [ FieldSchema(nameid, dtypeDataType.INT64, is_primaryTrue, auto_idTrue), FieldSchema(namevector, dtypeDataType.FLOAT_VECTOR, dim128), FieldSchema(nametext, dtypeDataType.VARCHAR, max_length1000), FieldSchema(namesource, dtypeDataType.VARCHAR, max_length100), ] # 创建 Schema schema CollectionSchema( fieldsfields, description文档集合, enable_dynamic_fieldTrue # 允许动态字段 ) # 创建集合 client.create_collection( collection_namedocuments, schemaschema )4. Partition分区类比书架的不同层# 创建分区 client.create_partition( collection_namedocuments, partition_nametech_docs ) # 插入数据到指定分区 client.insert( collection_namedocuments, data[...], partition_nametech_docs ) # 只在指定分区搜索速度更快 results client.search( collection_namedocuments, data[query_vector], partition_names[tech_docs] )优势提高搜索速度方便数据管理节省内存5. Index索引后续的文章会介绍如何选择索引类型和距离度量方式。类比图书馆的索引系统# 创建索引 index_params client.prepare_index_params() index_params.add_index( field_namevector, index_typeHNSW, # 索引类型 metric_typeL2, # 距离度量 params{ M: 16, efConstruction: 200 } ) client.create_index( collection_namedocuments, index_paramsindex_params )常用索引FLAT小数据集暴力搜索IVF_FLAT中等数据集HNSW高性能DiskANN超大规模6. Load/Release加载/释放类比从仓库搬到阅览室# 加载到内存必须 client.load_collection(collection_namedocuments) # 查询从内存读取快 results client.search(...) # 释放内存 client.release_collection(collection_namedocuments)为什么需要 load向量搜索需要大量计算从内存读取比磁盘快 100 倍兼顾持久化和性能实战示例完整示例黑神话悟空妖怪数据库文件a-working-sample.pyimport logging import pandas as pd from pymilvus import MilvusClient, DataType, FieldSchema, CollectionSchema from sentence_transformers import SentenceTransformer from tqdm import tqdm # 隐藏日志 logging.getLogger(pymilvus).setLevel(logging.CRITICAL) # —————————————— # 1. 准备数据 # —————————————— data_records [ { monster_id: BM001, monster_name: 虎先锋, location: 竹林关隘, difficulty: High, synonyms: 猛虎妖, 虎妖, description: 在竹林关卡中出现的猛虎型妖怪力量强大。 }, { monster_id: BM002, monster_name: 火猿, location: 火山洞窟, difficulty: Low, synonyms: 烈焰猿, 炎猿, description: 生活在火山洞窟的猿类妖怪只是插科打诨的小兵。 }, ] df pd.DataFrame(data_records) # —————————————— # 2. 连接 Milvus # —————————————— client MilvusClient(urihttp://localhost:19530) collection_name Wukong_Monsters # —————————————— # 3. 加载 Embedding 模型 # —————————————— print(加载 embedding 模型...) embedding_model SentenceTransformer(BAAI/bge-small-zh-v1.5) sample_embedding embedding_model.encode([示例文本])[0] vector_dim len(sample_embedding) print(f向量维度: {vector_dim}) # —————————————— # 4. 定义 Schema 并创建集合 # —————————————— fields [ FieldSchema(nameid, dtypeDataType.INT64, is_primaryTrue, auto_idTrue), FieldSchema(namevector, dtypeDataType.FLOAT_VECTOR, dimvector_dim), FieldSchema(namemonster_id, dtypeDataType.VARCHAR, max_length50), FieldSchema(namemonster_name, dtypeDataType.VARCHAR, max_length100), FieldSchema(namelocation, dtypeDataType.VARCHAR, max_length100), FieldSchema(namedifficulty, dtypeDataType.VARCHAR, max_length20), FieldSchema(namesynonyms, dtypeDataType.VARCHAR, max_length200), FieldSchema(namedescription, dtypeDataType.VARCHAR, max_length500), ] schema CollectionSchema( fields, descriptionWukong Monsters, enable_dynamic_fieldTrue ) if not client.has_collection(collection_name): client.create_collection(collection_namecollection_name, schemaschema) # —————————————— # 5. 创建索引 # —————————————— index_params client.prepare_index_params() index_params.add_index( field_namevector, index_typeAUTOINDEX, metric_typeL2 ) client.create_index(collection_namecollection_name, index_paramsindex_params) # —————————————— # 6. 插入数据 # —————————————— for start_idx in tqdm(range(len(df)), desc插入数据): row df.iloc[start_idx] # 准备向量文本 doc_parts [str(row[monster_name])] if row[synonyms]: doc_parts.append(f(别名{row[synonyms]})) if row[location]: doc_parts.append(f场景{row[location]}) if row[description]: doc_parts.append(f描述{row[description]}) doc_text .join(doc_parts) # 生成向量 embedding embedding_model.encode([doc_text])[0] # 插入数据 data_to_insert [{ vector: embedding.tolist(), monster_id: str(row[monster_id]), monster_name: str(row[monster_name]), location: str(row[location]), difficulty: str(row[difficulty]), synonyms: str(row[synonyms]), description: str(row[description]) }] client.insert(collection_namecollection_name, datadata_to_insert) # —————————————— # 7. 加载到内存 # —————————————— print(\n加载 Collection 到内存...) client.load_collection(collection_namecollection_name) print(✓ Collection 已加载) # —————————————— # 8. 向量搜索 # —————————————— search_query 高难度妖怪 search_embedding embedding_model.encode([search_query])[0] search_result client.search( collection_namecollection_name, data[search_embedding.tolist()], limit3, output_fields[monster_name, location, difficulty, synonyms] ) print(f\n搜索结果 {search_query}:) for hits in search_result: for hit in hits: print(f - {hit[entity]}) # —————————————— # 9. 条件查询 # —————————————— query_result client.query( collection_namecollection_name, filterdifficulty Low, output_fields[monster_name, location, difficulty, synonyms] ) print(f\n难度为 Low 的妖怪) for result in query_result: print(f - {result})运行结果加载 embedding 模型... 向量维度: 512 插入数据: 100%|████████████| 2/2 [00:0000:00, 10.5it/s] 加载 Collection 到内存... ✓ Collection 已加载 搜索结果 高难度妖怪: - {monster_name: 虎先锋, location: 竹林关隘, difficulty: High, synonyms: 猛虎妖, 虎妖} 难度为 Low 的妖怪 - {monster_name: 火猿, location: 火山洞窟, difficulty: Low, synonyms: 烈焰猿, 炎猿}

更多文章