导读: 你的 AI 应用上线了,用户量上来了,FAISS 开始喘不上气了……
本文带你认识 Milvus——一个生产级向量数据库,让 AI 的"记忆"也能扛住大流量。
零基础友好 | 附完整代码 | 含动图讲解


一、从一次"翻车"说起

想象你开发了一个 AI 知识助手,用 FAISS 存了几万条向量,跑得飞快,自我感觉良好。

然后有一天,Boss 说:“我们要上线,预计日活几十万。”

你微笑着打开代码……然后发现:

  • FAISS 是单机的,不能水平扩展 😶
  • 重启服务要重新加载全部向量到内存,半分钟起步 😶
  • 没有权限管理,没有数据持久化,没有监控 😶

这时候你需要的不是"向量存储工具",而是一个向量数据库

而 Milvus,就是这个领域的扛把子。

在这里插入图片描述


二、Milvus 是什么?三句话说清楚

💡 一句话定义:Milvus 是一个开源的、专为海量向量相似性搜索设计的云原生数据库,天生为生产环境而生。

你可以把它理解成这样:

  • FAISS = 你书桌上的便利贴堆 → 找东西快,但一多就乱,也没法共享
  • Milvus = 一座现代化图书馆 → 有馆员(调度器)、有书架标签(索引)、有借阅记录(持久化),还支持多人同时进馆(分布式)

从诞生背景看,Milvus 是由 Zilliz 公司开源,现在是 LF AI & Data 基金会的顶级项目,背书够硬,社区也够活跃。

官网https://milvus.io/


三、装起来!5 分钟跑通 Milvus

不折腾理论了,先把它跑起来。Milvus 官方推荐用 Docker 部署单机版,步骤超简单。

第一步:下载配置文件

# macOS / Linux
wget https://github.com/milvus-io/milvus/releases/download/v2.5.14/milvus-standalone-docker-compose.yml -O docker-compose.yml
# Windows PowerShell
Invoke-WebRequest -Uri "https://github.com/milvus-io/milvus/releases/download/v2.5.14/milvus-standalone-docker-compose.yml" -OutFile "docker-compose.yml"

第二步:一键启动

docker compose up -d

Docker 会帮你拉起三个容器:

容器名 职责
milvus-standalone 核心服务,处理所有请求
milvus-etcd 元数据仓库(记住你有哪些集合、字段等)
milvus-minio 对象存储(持久化向量文件)

等容器都变成 Up 状态,Milvus 就在 19530 端口等你了。🎉

停止服务docker compose down(保留数据)
彻底清除docker compose down -v(数据也删)


四、核心概念:Milvus 的"图书馆哲学"

这是本文最重要的一节。Milvus 的设计哲学一点都不神秘,用"图书馆"类比,5 分钟全懂。

在这里插入图片描述

动图:Collection → Partition → Entity 的层级关系逐层展开

在这里插入图片描述

动图:Milvus 数据层级结构——从图书馆到一本书的逐层拆解(慢速,8秒)

4.1 Collection(集合)≈ 一座图书馆

Collection(集合) 是 Milvus 里最顶层的数据容器,相当于关系型数据库里的"一张表"。

每个 Collection 有一个 Schema(模式),就像图书馆的"入馆规则"——来的数据必须符合规定的字段格式。

Schema 通常包含三类字段:

  • 🔑 主键字段:每条数据的唯一 ID(相当于书的 ISBN 号)
  • 🧮 向量字段:核心!存放 Embedding 向量(相当于书的"语义坐标")
  • 📝 标量字段:附加元数据,如书名、作者、价格(用于过滤检索)

4.2 Partition(分区)≈ 图书馆的分区

一个 Collection 可以划分为多个 Partition(分区),就像图书馆分了"科技区"、“小说区”。

为什么分区? 查询时只搜特定分区,扫描的数据量大幅减少,速度自然快。

最多支持 1024 个分区,合理使用是性能优化的重要手段。

4.3 Alias(别名)≈ 动态的推荐书单

Alias(别名) 是给 Collection 取的"外号"。这个功能听起来鸡肋,但在生产中超级实用:

场景:你需要更新 Collection 里的全量数据(比如重新索引),怎么做到"不停服更新"?

答案就是别名:

  1. 建一个新的 collection_v2,导入并索引好新数据
  2. 把指向旧集合的别名 my_app 切换到 collection_v2
  3. 用户完全无感,零停机 ✅

五、索引:检索速度的秘密武器

光有数据还不够,向量检索之所以快,靠的是索引(Index)

索引就像图书馆的"检索目录"——不是挨本翻书,而是先查目录定位区域,再精确找书。

在这里插入图片描述

Milvus 提供了 4 种主要向量索引类型:

索引类型 核心原理 优点 缺点 适用场景
FLAT 暴力搜索,全量对比 100% 召回率 速度慢,内存大 数据量小、要求精准
IVF 系列 先聚类分桶,再桶内搜 速度快,吞吐高 召回率 <100% 通用,大规模高吞吐
HNSW 多层邻近图,层层定位 速度极快,召回率高 内存占用大 实时推荐、低延迟
DiskANN 图索引 + SSD 优化 支持超大规模数据 延迟略高于内存 数十亿级向量

怎么选? 记住这个口诀:

  • 追求准确率,数据不大 → FLAT
  • 追求性能平衡,通用场景 → IVF_FLAT / IVF_SQ8
  • 追求极速低延迟,内存够用 → ✅ HNSW(大多数场景首选)
  • 数据超出内存上限DiskANN

动图:4 种索引查找方式对比——FLAT 逐一扫描 vs HNSW 图中飞速跳跃(慢速,10秒)

在这里插入图片描述


六、检索进阶:不止找"最相似的"

Milvus 的检索能力远不止"找 Top-K 相似向量",还有几个杀手级功能:

6.1 过滤检索:语义 + 条件,双管齐下

场景:找"和这双鞋最像的商品,但价格要低于300元,且有库存"

这就是过滤检索(Filtered Search)——先用标量字段筛出候选集,再在候选集里做向量检索。

比单纯向量检索精准了不止一个档次。

6.2 范围检索:设定相似度阈值

场景:人脸识别系统,找所有"相似度 > 0.9"的人脸

范围检索(Range Search) 不关心排名,只关心"距离是否在区间内"。在安全验证、异常检测场景非常好用。

6.3 混合检索:多向量一起上

这是 Milvus 最强的功能之一。

现代 RAG 应用常见模式:同时用密集向量(捕捉语义) + 稀疏向量(精确关键词),两路并发检索,结果用 RRF(互惠排名融合) 算法合并——效果比任何一路单独检索都好。

多模态场景同理:用户输入文字 + 图片,系统同时检索文本向量和图像向量,最终融合排序。


七、代码实战:从零构建多模态图片检索

说了这么多理论,来跑代码。本节演示一个完整的图文多模态检索引擎——给定一张图片 + 一段文字,从图库中找出最匹配的图片。

用到的模型:Visualized-BGE(能同时理解图片和文字,输出统一的向量)

动图:多模态检索完整流程——图片+文字输入 → 编码为向量 → Milvus 检索 → 相似图片返回(慢速,12秒)
在这里插入图片描述

步骤一:初始化工具

# === 步骤1:导入库,定义常量 ===
import os
from tqdm import tqdm       # 进度条,让你知道跑到哪了
from glob import glob       # 批量匹配文件路径
import torch
from visual_bge.visual_bge.modeling import Visualized_BGE  # 多模态编码模型
from pymilvus import MilvusClient, FieldSchema, CollectionSchema, DataType
import numpy as np
import cv2
from PIL import Image

# -------------------------------------------------------
# 🔧 【按你自己的环境修改这里】
# -------------------------------------------------------

# 模型名称:Hugging Face 上的基础语言模型 ID,不用改
# 它是 BGE 的文本理解部分,Visualized_BGE 内部会用到它
MODEL_NAME = "BAAI/bge-base-en-v1.5"

# 模型权重文件路径:Visualized_BGE 的视觉部分权重(.pth 文件)
# 📥 下载地址:https://huggingface.co/BAAI/bge-visualized
# 改成你本地实际存放 .pth 文件的路径
MODEL_PATH = "./models/Visualized_base_en_v1.5.pth"

# 图片数据目录:存放待检索图片的文件夹路径
# 目录下需要有一个 dragon/ 子文件夹,里面放若干 .png 图片
# 改成你本地的图片目录
DATA_DIR = "./data/dragon_images"

# Collection 名称:相当于数据库里的"表名",随意起,英文+下划线即可
COLLECTION_NAME = "multimodal_demo"

# Milvus 服务地址:本地 Docker 启动后默认是这个,不用改
# 如果你用云托管 Milvus(Zilliz Cloud),改成对应的 URI
MILVUS_URI = "http://localhost:19530"

# -------------------------------------------------------


# === 步骤2:封装编码器 ===
class Encoder:
    """
    把图片/文字变成向量的工具人

    原理:Visualized_BGE 是一个多模态模型,
    它能把图片和文字都"翻译"成同一个向量空间里的坐标,
    这样图片和文字就可以直接比较相似度了。
    """
    def __init__(self, model_name: str, model_path: str):
        # 加载模型:model_name 是文本理解骨干,model_path 是视觉权重
        self.model = Visualized_BGE(model_name_bge=model_name, model_weight=model_path)
        self.model.eval()  # 切换到推理模式(关闭 dropout 等训练专用层)

    def encode_query(self, image_path: str, text: str) -> list[float]:
        """
        图文混合编码(用于查询时)
        同时输入一张图 + 一段文字,输出一个融合了两者语义的向量
        """
        with torch.no_grad():  # 不计算梯度,推理时不需要,可以省内存
            query_emb = self.model.encode(image=image_path, text=text)
        return query_emb.tolist()[0]  # 返回 Python list,方便存入 Milvus

    def encode_image(self, image_path: str) -> list[float]:
        """
        纯图片编码(用于入库时)
        只输入图片,输出该图片的语义向量
        """
        with torch.no_grad():
            query_emb = self.model.encode(image=image_path)
        return query_emb.tolist()[0]

步骤二:创建 Collection

# === 步骤3:连接 Milvus,设计 Schema ===

# 初始化编码器,加载模型(第一次运行会比较慢,耐心等待)
encoder = Encoder(MODEL_NAME, MODEL_PATH)

# 连接本地 Milvus 服务(确保 Docker 容器已启动)
milvus_client = MilvusClient(uri=MILVUS_URI)

# 如果同名集合已存在,先删掉重建(开发调试时方便重跑)
# 生产环境中要小心,别误删真实数据!
if milvus_client.has_collection(COLLECTION_NAME):
    milvus_client.drop_collection(COLLECTION_NAME)
    print(f"⚠️  已删除旧集合:{COLLECTION_NAME}")

# 随机取一张图片编码,自动探测向量维度
# BGE base 模型输出 768 维,large 模型输出 1024 维
# 这样写的好处:换模型时维度自动适配,不用手动改
image_list = glob(os.path.join(DATA_DIR, "*.png"))  # 获取目录下所有 .png 图片
dim = len(encoder.encode_image(image_list[0]))
print(f"📐 检测到向量维度:{dim}")

# 定义 Schema:告诉 Milvus 每条数据长什么样
# 类比:相当于建一张有三列的表
fields = [
    # 主键:自增 ID,唯一标识每一条数据(Milvus 自动生成,无需手动赋值)
    FieldSchema(name="id",         dtype=DataType.INT64,         is_primary=True, auto_id=True),
    # 向量字段:核心!存放图片的 Embedding,dim 是向量维度
    FieldSchema(name="vector",     dtype=DataType.FLOAT_VECTOR,  dim=dim),
    # 标量字段:存图片的文件路径,方便检索到结果后能找到原图
    # max_length=512 表示路径字符串最长 512 个字符
    FieldSchema(name="image_path", dtype=DataType.VARCHAR,        max_length=512),
]
schema = CollectionSchema(fields, description="多模态图文检索")

# 创建集合(相当于在数据库里建表)
milvus_client.create_collection(collection_name=COLLECTION_NAME, schema=schema)
print(f"✅ Collection '{COLLECTION_NAME}' 创建成功,向量维度:{dim}")

运行结果:

✅ Collection 'multimodal_demo' 创建成功,向量维度:768

步骤三:插入数据 + 创建索引

# === 步骤4:把图片编码后写入 Milvus ===

# 遍历所有图片,逐一编码成向量
# tqdm 会显示进度条,图片多的时候很有用
data_to_insert = []
for image_path in tqdm(image_list, desc="生成图像嵌入"):
    vector = encoder.encode_image(image_path)   # 图片 → 768 维向量
    # 每条数据是一个字典,key 对应 Schema 里的字段名
    # 注意:id 字段不需要填,Milvus 会自动生成
    data_to_insert.append({"vector": vector, "image_path": image_path})

# 批量写入 Milvus(比逐条插入效率高很多)
result = milvus_client.insert(collection_name=COLLECTION_NAME, data=data_to_insert)
print(f"✅ 插入 {result['insert_count']} 条数据")

# === 步骤5:创建 HNSW 索引,加载到内存 ===

# 准备索引参数
index_params = milvus_client.prepare_index_params()
index_params.add_index(
    field_name="vector",        # 对哪个字段建索引(向量字段)
    index_type="HNSW",          # 索引类型:多层图结构,速度快、召回率高,推荐首选
    metric_type="COSINE",       # 距离度量:余弦相似度(值越接近1表示越相似)
    params={
        "M": 16,                # 每个节点最多连接 16 个邻居,越大越准但越占内存
        "efConstruction": 256,  # 建索引时的搜索范围,越大索引质量越高但越慢
    }
)

# 构建索引(类比:给书架建目录,首次建立需要一点时间)
milvus_client.create_index(collection_name=COLLECTION_NAME, index_params=index_params)

# 加载到内存(Milvus 只有 load 之后才能被检索)
# 类比:图书馆开门前把常用书区"搬到"前台备查
milvus_client.load_collection(collection_name=COLLECTION_NAME)
print("✅ 索引创建完成,Collection 已加载到内存,可以开始检索了")

运行结果:

生成图像嵌入: 100%|████████████████| 5/5 [00:03<00:00,  1.52it/s]
✅ 插入 5 条数据
✅ 索引创建完成,Collection 已加载到内存,可以开始检索了

步骤四:执行多模态检索

# === 步骤6:图文混合查询 ===

# 查询图片路径:放一张你想以图搜图的图片
# 这里用 DATA_DIR 目录下的 query.png 作为查询图
query_image_path = os.path.join(DATA_DIR, "query.png")

# 查询文字:描述你在找什么,和图片一起送进编码器
# 图+文的组合查询,比单独用图或单独用文字效果更好
query_text = "一条龙"

# 把图片+文字一起编码成一个查询向量
# 这个向量同时包含了图片的视觉信息和文字的语义信息
query_vector = encoder.encode_query(image_path=query_image_path, text=query_text)

# 在 Milvus 里搜索最相似的 5 张图
search_results = milvus_client.search(
    collection_name=COLLECTION_NAME,  # 在哪个集合里搜
    data=[query_vector],              # 查询向量(列表格式,支持批量查询)
    output_fields=["image_path"],     # 搜到结果后,额外返回哪些字段(这里要图片路径)
    limit=5,                          # 返回最相似的前 5 个结果(即 Top-K 的 K=5)
    search_params={
        "metric_type": "COSINE",      # 距离度量,要和建索引时保持一致
        "params": {"ef": 128}         # HNSW 检索时的搜索范围,越大越准但越慢,128 是个好默认值
    }
)[0]  # search 返回的是二维列表(支持批量),[0] 取第一个查询的结果

print("检索结果:")
for i, hit in enumerate(search_results):
    # hit['distance'] 是余弦相似度,范围 0~1,越接近 1 越相似
    # hit['entity'] 是我们在 output_fields 里指定要返回的字段
    print(f"  Top {i+1}: 相似度={hit['distance']:.4f}  路径={hit['entity']['image_path']}")

运行结果:

检索结果:
  Top 1: 相似度=0.9411  路径='...dragon/dragon01.png'
  Top 2: 相似度=0.5818  路径='...dragon/dragon02.png'
  Top 3: 相似度=0.5731  路径='...dragon/dragon05.png'
  Top 4: 相似度=0.4894  路径='...dragon/dragon04.png'
  Top 5: 相似度=0.4100  路径='...dragon/dragon03.png'

Top 1 的相似度高达 0.9411,正是查询图片本身——说明整个检索流程完全正确!🎉

步骤五:清理资源

# === 步骤7:用完释放内存,删掉 Collection ===
milvus_client.release_collection(collection_name=COLLECTION_NAME)
milvus_client.drop_collection(COLLECTION_NAME)
print("✅ 资源已释放")

在这里插入图片描述


八、总结

学完本文,你已经掌握了:

  • Milvus 是什么:生产级向量数据库,云原生、高可用、可扩展
  • 怎么理解它:Collection(图书馆)→ Partition(分区)→ Schema(规则)→ Entity(数据)
  • 索引怎么选:大多数场景选 HNSW,超大数据量选 DiskANN
  • 怎么用代码:从创建集合到插入数据到多模态检索,一套流程跑通

进阶路线:

阶段 方向 要掌握的
入门 本地单机 MilvusClient + HNSW + 基础 ANN 检索
进阶 混合检索 密集+稀疏向量 + RRF 重排
生产 分布式部署 Milvus Distributed + 分区策略 + 监控

在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐