FAISS 和 Chroma 都可以直接嵌入 Python 程序。Milvus Standalone 不同,它是一个独立运行的向量数据库服务,应用通过网络端口访问它。
这一篇会在 M1 Max 上真实启动 Milvus 2.6.9 Standalone,使用 PyMilvus 2.6.9 创建 Collection,写入本地 Qwen3 Embedding 向量,再完成主键读取、标量过滤和 COSINE Top-K 搜索。
本文只使用 Milvus 原生 MilvusClient,不使用 LangChain、VectorStore、Retriever 或 RAG。
1. Milvus 的三种部署形态
Milvus 2.6 提供三种部署方式:
| 形态 | 特点 | 适合场景 |
|---|---|---|
| Lite | 嵌入 Python 进程,使用本地文件 | 学习、快速实验、小规模数据 |
| Standalone | 单机数据库服务 | 本地开发、单机服务、完整功能验证 |
| Distributed | 多节点分布式集群 | 更大数据规模和生产负载 |
本篇选择 Standalone。原因不是 4 条演示数据需要三个容器,而是要完整理解数据库服务、持久化依赖、健康检查和重启恢复。若只想在 Python 进程中做小规模实验,Lite 的使用成本更低。
2. Standalone 由哪些组件组成

Milvus v2.6.9 的 Standalone Compose 会启动三个容器:
| 容器 | 作用 |
|---|---|
| milvus-standalone | 对外提供 Collection、写入、查询、索引和 WebUI |
| milvus-etcd | 保存 Collection Schema、Segment 状态等元数据 |
| milvus-minio | 保存日志快照、标量与向量数据文件、索引文件等对象数据 |
本篇 Compose 还设置了 MQ_TYPE=woodpecker。Woodpecker 是 Milvus 2.6 使用的 WAL 实现,负责记录有序写入并支持故障恢复;它不是第四个独立容器,而是运行在 Milvus Standalone 内部,并复用 MinIO 对象存储。
主要端口为:
19530 -> PyMilvus 连接端口
9091 -> 健康检查和 Milvus WebUI
9000 -> MinIO API,仅供本地组件与排查使用
9001 -> MinIO Console,仅供本地排查使用
Compose 文件把这四个端口都绑定到 127.0.0.1。这是本地教学环境,不应把默认未启用鉴权的 Milvus 或使用演示账号的 MinIO 暴露到局域网和公网。
Milvus 的数据层次可以先理解为:
Database
└── Collection
└── Partition
└── Entity
Collection 类似表,Entity 类似一行记录。本篇使用默认 Database 和默认 Partition。每个容器的数据目录都映射到项目内的 volumes/,停止容器不会删除这些目录;只有主动删除目录或执行带清理数据的命令才会丢失演示数据。
3. 为什么创建独立的 Colima profile
本机默认 Colima 只有 6 GiB 内存,而且已经运行其他项目的多个容器。Milvus Standalone 官方要求至少准备 8 GiB 内存,因此不应该停止、重配或挤占默认实例。
这里新建名为 milvus 的独立 profile:
colima start milvus \
--cpus 4 \
--memory 12 \
--disk 60 \
--runtime docker \
--activate=false
本机启动后的真实配置为:
PROFILE STATUS ARCH CPUS MEMORY DISK RUNTIME
default Running aarch64 4 6GiB 100GiB docker
milvus Running aarch64 4 12GiB 60GiB docker
–activate=false 不修改当前默认 Docker context。之后所有 Milvus 命令都显式指定:
docker --context colima-milvus ...
这样不会把 Milvus 容器误启动到默认 Colima,也不会停止默认实例中的现有服务。
4. 启动 Milvus Standalone
项目保存了 Milvus v2.6.9 官方 Standalone Compose 文件:
rag/p07_milvus_native/docker-compose.yml
启动服务:
cd source/_posts/llm_learning
docker --context colima-milvus compose \
-f rag/p07_milvus_native/docker-compose.yml \
up -d
查看容器:
docker --context colima-milvus compose \
-f rag/p07_milvus_native/docker-compose.yml \
ps
三个容器完全启动后的真实状态为:
NAME STATUS PORTS
milvus-etcd Up (healthy) 2379-2380/tcp
milvus-minio Up (healthy) 127.0.0.1:9000-9001->9000-9001/tcp
milvus-standalone Up (healthy) 127.0.0.1:9091->9091/tcp, 127.0.0.1:19530->19530/tcp
也可以直接检查 9091:
curl --noproxy '*' http://127.0.0.1:9091/healthz
返回:
OK
浏览器打开下面的地址可以查看内置管理页:
http://127.0.0.1:9091/webui/

截图展示的是 Milvus 内置 WebUI。它适合观察 Collection、Segment、Channel、任务和配置等运行信息,但本文的写入与查询仍以 PyMilvus 返回结果为准,WebUI 不代替代码验证。
5. 准备 PyMilvus 环境
本篇继续使用 Chroma 文章创建的 .venv_vector_db:
source .venv_vector_db/bin/activate
python -m pip install \
-r rag/p07_milvus_native/requirements.txt
python -m pip check
依赖中固定了:
pymilvus==2.6.9
sentence-transformers==5.1.2
transformers==4.57.6
本机 pip check 返回:
No broken requirements found.
6. 检查 Milvus 服务
代码文件为 01_check_milvus_server.py:
# 这个文件连接 Milvus Standalone,检查服务版本、数据库和 Collection。
from pymilvus import MilvusClient, __version__ as pymilvus_version
client = MilvusClient(uri="http://127.0.0.1:19530")
print(f"PyMilvus 版本:{pymilvus_version}")
print(f"Milvus 服务版本:{client.get_server_version()}")
print(f"数据库:{client.list_databases()}")
print(f"default 数据库中的 Collection:{client.list_collections()}")
在全新的 volumes/ 数据目录中第一次运行:
python rag/p07_milvus_native/01_check_milvus_server.py
真实输出为:
PyMilvus 版本:2.6.9
Milvus 服务版本:2.6.9
数据库:['default']
default 数据库中的 Collection:[]
此时服务已连接成功,但还没有创建 Collection。
7. 创建 Collection 并写入向量
写入和搜索是两条方向相反的数据链路:文档先生成向量并写入 Collection;查询文本再生成同模型、同维度的查询向量,由 Milvus 计算 COSINE 相似度并返回 Top-K。

代码文件为 02_create_collection_and_insert.py:
# 这个文件创建 Milvus Collection,并写入 Qwen3 生成的向量、文本和分类字段。
from pathlib import Path
import torch
from pymilvus import MilvusClient
from sentence_transformers import SentenceTransformer
PROJECT_ROOT = Path(__file__).resolve().parents[2]
MODEL_PATH = PROJECT_ROOT / "embedding_model"
COLLECTION_NAME = "life_notes"
documents = [
{"id": 1, "text": "西湖边有步道和树荫,很适合周末散步。", "category": "travel"},
{"id": 2, "text": "苹果富含膳食纤维,是常见的健康水果。", "category": "food"},
{"id": 3, "text": "汽车需要定期更换机油并检查轮胎。", "category": "car"},
{"id": 4, "text": "杭州植物园环境安静,适合慢慢游览。", "category": "travel"},
]
device = "mps" if torch.backends.mps.is_available() else "cpu"
model = SentenceTransformer(str(MODEL_PATH), device=device)
vectors = model.encode(
[item["text"] for item in documents],
normalize_embeddings=True,
).tolist()
client = MilvusClient(uri="http://127.0.0.1:19530")
if client.has_collection(COLLECTION_NAME):
client.drop_collection(COLLECTION_NAME)
# 快速创建方式会生成主键 id、1024 维 vector 字段,并允许写入动态字段。
client.create_collection(
collection_name=COLLECTION_NAME,
dimension=1024,
metric_type="COSINE",
consistency_level="Strong",
)
rows = []
for item, vector in zip(documents, vectors):
rows.append(
{
"id": item["id"],
"vector": vector,
"text": item["text"],
"category": item["category"],
}
)
result = client.upsert(collection_name=COLLECTION_NAME, data=rows)
client.flush(collection_name=COLLECTION_NAME)
stats = client.get_collection_stats(collection_name=COLLECTION_NAME)
print(f"运行设备:{device}")
print(f"Collection:{COLLECTION_NAME}")
print(f"写入数量:{result['upsert_count']}")
print(f"Collection 统计:{stats}")
7.1 快速创建 Collection
create_collection() 接收两个关键参数:
dimension=1024
metric_type="COSINE"
维度必须与 Qwen3-Embedding-0.6B 的输出一致。快速创建方式默认创建 id 主键、vector 向量字段、向量索引并加载 Collection;未在 Schema 中声明的 text、category 会被保存到预留的 $meta 动态字段中。
动态字段适合快速验证。生产项目通常会显式定义经常查询和过滤的字段类型、长度与索引,避免把重要业务结构长期隐藏在 $meta 中。
7.2 upsert、flush 和 stats
upsert -> 主键不存在时插入,存在时使用新记录覆盖原记录
flush -> 将 growing segment 封存并刷入对象存储
stats -> 查看 Collection 统计信息
Milvus 会自动触发 flush,正常写入也会先进入 WAL,因此不需要在每次 upsert() 后都手动调用。这里数据量很小,调用一次 flush() 是为了在教学演示中立即得到稳定的行数和 sealed segment;高频手动 flush 会制造大量小 Segment,不适合照搬到生产代码。
运行代码:
python rag/p07_milvus_native/02_create_collection_and_insert.py
真实输出为:
运行设备:mps
Collection:life_notes
写入数量:4
Collection 统计:{'row_count': 4}
8. 主键查询、标量过滤和向量搜索
代码文件为 03_search_query_and_filter.py:
# 这个文件演示 Milvus 的主键查询、标量过滤和向量 Top-K 搜索。
from pathlib import Path
import torch
from pymilvus import MilvusClient
from sentence_transformers import SentenceTransformer
PROJECT_ROOT = Path(__file__).resolve().parents[2]
MODEL_PATH = PROJECT_ROOT / "embedding_model"
COLLECTION_NAME = "life_notes"
client = MilvusClient(uri="http://127.0.0.1:19530")
print("按主键读取 id=1:")
print(client.get(collection_name=COLLECTION_NAME, ids=[1], output_fields=["text", "category"]))
print("\n标量过滤 category == travel:")
filtered_rows = client.query(
collection_name=COLLECTION_NAME,
filter='category == "travel"',
output_fields=["id", "text", "category"],
)
for row in filtered_rows:
print(row)
device = "mps" if torch.backends.mps.is_available() else "cpu"
model = SentenceTransformer(str(MODEL_PATH), device=device)
query = "周末想找一个适合散步的地方"
query_vector = model.encode(
[query],
prompt_name="query",
normalize_embeddings=True,
).tolist()
results = client.search(
collection_name=COLLECTION_NAME,
data=query_vector,
anns_field="vector",
limit=3,
output_fields=["text", "category"],
# 搜索使用的度量必须与 Collection 的向量索引保持一致。
search_params={"metric_type": "COSINE"},
)
print(f"\n向量查询:{query}")
for rank, hit in enumerate(results[0], start=1):
entity = hit["entity"]
print(
f"{rank}. score={hit['distance']:.4f} id={hit['id']} "
f"category={entity['category']} text={entity['text']}"
)
这里分别使用了三个接口:
| 接口 | 用途 |
|---|---|
| get() | 按主键读取记录 |
| query() | 按标量表达式过滤记录 |
| search() | 按向量相似度返回 Top-K |
运行结果为:
按主键读取 id=1:
data: ["{'id': 1, 'text': '西湖边有步道和树荫,很适合周末散步。', 'category': 'travel'}"], extra_info: {}
标量过滤 category == travel:
{'id': 1, 'text': '西湖边有步道和树荫,很适合周末散步。', 'category': 'travel'}
{'id': 4, 'text': '杭州植物园环境安静,适合慢慢游览。', 'category': 'travel'}
向量查询:周末想找一个适合散步的地方
1. score=0.6498 id=1 category=travel text=西湖边有步道和树荫,很适合周末散步。
2. score=0.4660 id=4 category=travel text=杭州植物园环境安静,适合慢慢游览。
3. score=0.2183 id=2 category=food text=苹果富含膳食纤维,是常见的健康水果。
PyMilvus 的命中结果仍使用 distance 作为字段名,但在 COSINE 模式下它表示余弦相似度,分数越大越接近。本文显式传入 search_params={“metric_type”: “COSINE”},避免阅读代码时误以为搜索采用了其他度量。
RAG 系列 5 的 FAISS 使用归一化向量和 IndexFlatIP。归一化向量的内积等于余弦相似度,因此这 4 条数据的排序与分数一致。这是当前度量、归一化方式和小规模精确检索共同产生的结果,并不意味着 FAISS 与 Milvus 在所有索引和参数下都必然返回完全相同的结果。
9. 验证重启后的持久化
重启三个容器:
docker --context colima-milvus compose \
-f rag/p07_milvus_native/docker-compose.yml \
restart
等待三个容器恢复 healthy 后重新运行检查脚本,真实结果为:
PyMilvus 版本:2.6.9
Milvus 服务版本:2.6.9
数据库:['default']
default 数据库中的 Collection:['life_notes']
再次运行搜索脚本仍返回相同的四条数据和 Top-3 排序,说明 etcd、MinIO 与 Milvus 的三个挂载目录都被保留,容器重启没有清空 Collection。
10. 查看容器资源占用
三个容器进入 healthy 后,可以记录一次资源占用:
docker --context colima-milvus stats --no-stream \
milvus-standalone milvus-etcd milvus-minio
本文只写入 4 条数据,实测结果如下:
NAME CPU % MEM USAGE / LIMIT MEM %
milvus-standalone 8.93% 304.9MiB / 11.66GiB 2.55%
milvus-etcd 1.83% 53.84MiB / 11.66GiB 0.45%
milvus-minio 0.06% 162.3MiB / 11.66GiB 1.36%
三个容器在这次采样中合计使用约 521 MiB 内存。CPU 百分比是命令执行时的瞬时值,内存也会随着数据量、索引、并发和后台任务变化,因此这组数据只用于确认本机测试环境能够正常运行,不能代替正式部署的容量规划。
11. 停止和重新启动
测试完成后只停止独立 profile:
colima stop milvus
这不会影响默认 Colima。需要再次启动独立实例时执行:
colima start milvus --activate=false
docker --context colima-milvus compose \
-f rag/p07_milvus_native/docker-compose.yml \
up -d
12. 常见问题
12.1 连接 19530 失败
依次检查:
colima list
docker --context colima-milvus ps
curl --noproxy '*' http://127.0.0.1:9091/healthz
只有 milvus-standalone 进入 healthy 后再运行 Python。
12.2 启动后内存不足
Milvus Standalone 不适合放进只有 6 GiB 的默认虚拟机。本篇独立 profile 分配 12 GiB。不要为了启动 Milvus 停止其他项目的容器,也不要直接调整正在承载现有服务的默认 profile。
12.3 端口被占用
检查 19530、9091、9000 和 9001:
lsof -nP -iTCP:19530 -sTCP:LISTEN
lsof -nP -iTCP:9091 -sTCP:LISTEN
12.4 Collection 维度错误
本文 Collection 是 1024 维,写入其他维度会失败。修改 Embedding 模型后,需要创建匹配的新 Collection,不能只替换模型路径。
12.5 Docker 命令操作了错误的实例
始终检查命令中是否包含:
--context colima-milvus
本篇所有 Milvus 命令都指向 colima-milvus,没有对默认 Colima 执行启动、停止或重启命令。测试期间默认实例中的三个 HBL worker 被其自身的外部机制重建,因此不能把“容器 ID 全程不变”写成 Milvus 隔离验证结果;可以确认的是 Milvus 操作没有使用默认 Docker context。
12.6 将本地端口暴露到局域网
官方 Compose 示例通常使用 19530:19530 这类端口映射,Docker 可能监听所有网卡。本篇改为 127.0.0.1:19530:19530,其余三个端口同样只绑定回环地址。若需要提供远程访问,应先配置 Milvus 鉴权、网络访问控制和 TLS,而不是直接删除 127.0.0.1。
13. 小结
| 检查项 | 结果 |
|---|---|
| Milvus Server | 2.6.9 |
| PyMilvus | 2.6.9 |
| 部署模式 | Standalone |
| 隔离环境 | Colima milvus profile,4 CPU / 12 GiB / 60 GiB |
| 容器 | Milvus、etcd、MinIO,全部 healthy |
| SDK 端口 | 19530 |
| 端口绑定 | 19530、9091、9000、9001 均只监听 127.0.0.1 |
| WebUI 与健康检查 | 9091 |
| Collection | life_notes |
| 向量维度与度量 | 1024 / COSINE |
| 写入数量 | 4 |
| 主键查询 | 成功 |
| 标量过滤 | category == “travel” 返回 2 条 |
| Top-K 搜索 | 成功,归一化向量下的排序与 RAG 系列 5 的 FAISS 案例一致 |
| 容器重启持久化 | 成功 |
| 本次测试内存 | 三个容器合计约 521 MiB |
| 默认 Colima | Milvus 流程未对其执行变更命令 |
到这里,FAISS、Chroma 和 Milvus 已经分别通过原生 API 完成真实向量检索。三者的核心都是“保存向量并返回相近记录”,但部署方式、元数据管理和服务边界明显不同。后续再把这些基础能力接入 LangChain 时,就能分清问题来自 Embedding、数据库还是上层框架。