ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Milvus实战:Standalone部署、向量检索与踩坑经验全解析

Milvus实战:Standalone部署、向量检索与踩坑经验全解析 做向量检索绕不开Milvus这一年多我把它从Demo一路用到了生产环境踩过不少坑也摸清了一些门道。这篇文章不念官方文档就从一个实际使用者的角度把这套东西的安装、架构、核心操作和常见问题从头到尾捋一遍。无论你是刚听说向量数据库还是已经装了Milvus但搞不懂里面那几个组件各自干嘛的都能在这里找到你想要的答案。我会重点讲Standalone模式的部署细节、余弦相似度在检索里的实际用法以及那些官方FAQ里不会细说的思路。1. 项目概述Milvus到底是什么以及为什么需要它1.1 从传统数据库到向量数据库的必然转变先聊一个最基础的问题为什么传统数据库搞不定向量检索你想想看以前我们查数据靠的是精确匹配——用户名等于张三、订单ID大于1000这类条件。关系型数据库通过索引和B树能在毫秒级找到你要的那一行。但到了图片搜索、语义匹配、推荐系统这类场景数据变成了几百维甚至上千维的浮点数组你问的是哪个向量和我这个向量最相似这里没有等于号只有距离。把两个高维向量做精确比较计算量是O(n*d)数据量一上来传统索引直接失效。向量数据库就是冲着这个场景去的。它做的事情本质上是两件一是用专门的索引结构比如HNSW、IVF把高维向量组织起来避免全量暴力扫描二是用近似最近邻搜索算法牺牲一点点精度换来几个数量级的性能提升。Milvus是这类产品里比较有代表性的开源项目它在云原生架构、多租户支持、GPU加速这些方面做得都比较成熟社区也活跃所以我最终选它做了主力。1.2 Milvus的核心定位不仅仅是向量存储很多刚接触的朋友以为Milvus只存向量其实它远不止如此。作为一款云原生的向量数据库它提供了完整的数据库能力数据的增删改查、元数据管理、异步批量导入、数据持久化和备份恢复。更关键的是它支持标量字段与向量字段混合过滤——我可以先通过商品类目这类普通字段过滤一部分数据再在剩余数据里做向量检索。这个能力在实际业务里太重要了没有它的话所有数据都要过向量搜索性能和精度都很难兼顾。Milvus从1.x演进到2.x架构上几乎是重写的。1.x基于等等架构部署简单但扩展性有限2.x采用了存算分离的分布式架构把数据平面和控制平面拆开引入了消息队列作为日志存储内部的Coordinator组件各自负责一块职责。2.x的学习曲线比1.x陡一些但换来的是真正的水平扩展能力。目前社区主力版本是2.3、2.4、2.5我自己稳定运行的版本是2.4系列。2. Standalone模式的架构拆解2.1 为什么推荐从Standalone模式开始Milvus有两种部署模式Standalone单机和Cluster分布式。有人会觉得生产环境反正最后都要上集群直接搞分布式不就行了我的实际建议是如果数据量在千万级以下QPS要求不是变态地高Standalone完全够用。它部署简单一个Docker Compose文件就能搞定运维成本低很多我第一套线上环境就是Standalone稳定跑了小半年数据量到了3000多万向量也没出什么问题。Standalone模式通过进程内部的协调把Milvus需要的各个功能模块打包在一个进程里但对用户暴露的还是一套完整的数据库接口。它的外部依赖需要Etcd元数据存储和MinIO对象存储这两个组件我后面会详细说。如果你用的是2.3之后的版本Docker Compose文件里默认也包含了这些依赖也就是说使用官方提供的docker-compose.yml启动一次就是一个完整的单机版集群包括Milvus本身和它的依赖。2.2 核心组件各自扮演什么角色要真的玩转Milvus这几个组件必须搞清楚Milvus主进程对外提供gRPC和RESTful API内部通过协调器模式管理数据。它内部有RootCoord、DataCoord、QueryCoord、IndexCoord四个协调器管着数据的写路径、读路径、索引构建和元数据管理。你不需要和这些协调器直接打交道但理解它们的存在有助于排查问题。比如某次查询超时我怀疑QueryCoord负载过高一查日志发现是查询并发太大导致节点排队后来加了副本才解决。Etcd元数据存储。所有集合的Schema、分片分布、索引状态都放在这里。Etcd挂了Milvus就直接废掉所以它必须配独立持久化。很多新手忽略这一点把Etcd数据存在容器里一重启就丢结果集合全没了。MinIO对象存储存的是真正的数据文件包括插入的向量数据、索引文件和日志快照。MinIO数据丢了Etcd里的元数据还在但数据本身没了等于有目录没文件同样严重。所以我建议这两个依赖的持久化目录都要挂到宿主机或者云盘上。2.3 Standalone与Cluster模式的关键差异再对比一下两种模式方便你根据业务场景做选择对比维度StandaloneCluster分布式部署复杂度一个Compose文件Helm/K8s至少需要额外依赖Pulsar/Kafka计算扩展性单节点入库和查询共用资源可拆分查询节点和数据节点按需扩缩容存储依赖Etcd MinIOEtcd MinIO Pulsar/Kafka适用规模千万级以下向量亿级以上向量、高并发在线业务运维成本低单机容器管理高需要监控集群各组件的健康状态从单机迁移到集群Milvus的数据和API是兼容的但你的依赖组件清单要变多尤其是消息队列Pulsar或Kafka会成为新的瓶颈点。所以我的建议是先用Standalone跑通业务逻辑当数据量和访问量到了一定体量再考虑平滑迁移到Cluster模式不要一上来就追求大而全。3. Milvus安装实战Standalone模式的全过程3.1 环境准备硬件与软件要求先列一下我当前这套稳定运行的环境配置可以作为参考服务器8核16GB内存的云主机磁盘200GB SSD数据盘单独挂载操作系统Ubuntu 22.04 LTSDocker版本20.10.21Docker Compose版本v2.12内存这块我要多说一句。很多人以为16GB跑Milvus绰绰有余但实际上你还要算上Etcd、MinIO的开销。如果数据量超过2000万向量128维我建议内存起步32GB。否则HNSW图结构的数据加载容易把内存打爆这是我在测试环境实际遇到过的情况。软件依赖方面你需要先装好Docker和Docker Compose。如果你的服务器在国内Docker镜像拉取可能比较慢建议先给Docker配置好镜像加速器不然后面拉Milvus相关镜像会很痛苦。3.2 使用Docker Compose快速安装Milvus官方提供的standalone docker-compose.yml是经过验证的我建议直接用官方的模板不要自己从头写。第一步创建工作目录并下载配置文件mkdir -p /opt/milvus cd /opt/milvus wget https://github.com/milvus-io/milvus/releases/download/v2.4.13/milvus-standalone-docker-compose.yml -O docker-compose.yml如果你拉取GitHub文件慢也可以在本地把这行内容保存成docker-compose.yml。官方文件里大概包含三个服务standaloneMilvus主服务、etcd、minio。第二步检查配置文件里的数据持久化路径。默认配置下Etcd和MinIO的数据会挂载到宿主机卷上例如minio: command: minio server /minio_data --console-address :9001 volumes: - ${DOCKER_VOLUME_DIRECTORY:-/docker/volumes}/minio:/minio_data注意看这个环境变量DOCKER_VOLUME_DIRECTORY默认是/docker/volumes。我建议显式设置它因为它控制了所有数据的宿主机存储位置不设置的话默认装在根目录下的/docker/volumes里可能把你的系统盘撑满。第三步启动服务export DOCKER_VOLUME_DIRECTORY/data/milvus docker compose up -d启动过程中Docker会拉取三个镜像milvusdb/milvus、quay.io/coreos/etcd、minio/minio。等镜像拉取完成用以下命令确认状态docker compose ps三个服务的状态都应该显示Up。然后检查健康状态Milvus默认在9091端口暴露健康检查接口curl -X GET http://localhost:9091/healthz -v期望看到返回值里有OK字样。3.3 安装验证与常见配置调整启动正常后还有几件必须做的事调整日志等级Milvus默认日志是INFO级别在生产环境日志量非常大。我一般通过环境变量调整在docker-compose.yml的standalone服务下加environment: - MILVUS_LOG_LEVELwarn这样能显著降低日志占用的磁盘空间。如果你要排查问题再临时改成debug。开放防火墙端口如果你跑在云服务器上需要把TCP 9091gRPC和9301RESTful 新版本端口加入安全组。这里有一个坑部分云厂商即使你开了安全组本地的UFW防火墙也可能拦截记得一并放行。我遇到过安全组开了但连不上的情况最后发现是服务器防火墙没放行端口。确认Milvus版本docker exec -it milvus-standalone bash -c milvus version输出版本号和你下载的compose文件版本一致即可。4. 核心操作连接Milvus、建集合、算余弦值4.1 安装并验证pymilvus客户端做开发调试我推荐用Python的pymilvus客户端。安装命令很简单pip install pymilvus2.4.9版本要和Milvus服务端匹配。2.4.x的客户端对应2.4.x的服务端跨大版本可能会遇到接口兼容性问题。验证连接是否成功写个最小脚本from pymilvus import connections connections.connect(host192.168.1.100, port9091) print(连接成功)这里注意host写服务器的内网IP或公网IP不要写localhost除非你pymilvus跑在Milvus同一台机器上。4.2 创建Collection与Schema的设计要点创建Collection之前先想清楚向量维度是多少。以文本匹配为例子用某个Embedding模型生成的向量通常是768维或1024维。维度一旦确定后续就不能改了所以要提前设计好。我一般这样创建from pymilvus import FieldSchema, CollectionSchema, DataType, Collection fields [ FieldSchema(nameid, dtypeDataType.INT64, is_primaryTrue, auto_idFalse), FieldSchema(nametext, dtypeDataType.VARCHAR, max_length1024), FieldSchema(nameembedding, dtypeDataType.FLOAT_VECTOR, dim768), ] schema CollectionSchema(fieldsfields, description文本向量集合) collection Collection(nametext_docs, schemaschema)这里有几个细节is_primaryTrue必须指定Milvus所有集合都要有主键字段。auto_id设为False我习惯自己控制ID方便后续做数据更新和删除如果你不需要精确控制ID可以设为True让Milvus自动生成。DataType.VARCHAR需要指定max_length这个字段是给向量检索做标量过滤用的。如果业务需要按时间过滤记得再加一个INT64或VARCHAR的时间字段。4.3 创建索引HNSW还是IVF_FLAT集合刚创建时还没有索引这时候查询只能暴力扫数量少无所谓数量大了性能不忍直视。所以建完集合的第一件事是建索引。Milvus支持的索引类型挺多我根据不同的场景给一个参考场景推荐索引说明千万级以内精度优先HNSW召回率高内存消耗大亿级数据内存有限IVF_FLAT需要先聚类查询时需要选bucket追求极致性能IVF_PQ向量会被压缩召回率略降小数据集或测试环境FLAT全量精确计算性能随数据量线性下降我生产环境用的是HNSW参数设置如下from pymilvus import Index index_params { metric_type: COSINE, index_type: HNSW, params: {M: 16, efConstruction: 200} } collection.create_index(field_nameembedding, index_paramsindex_params)这里解释一下参数M表示每个节点的最大连接数M越大图的连通性越好召回率越高但内存和构建时间也越大efConstruction是构建时动态搜索范围的参数越大图构建质量越好构建时间越长。官方默认是M8efConstruction200我一般用M16、efConstruction256实测召回率提升明显内存多不了多少。4.4 插入数据与余弦相似度检索实操数据插入前先明确向量相似度的度量方式。Milvus支持三种常用的度量L2欧氏距离、IP内积、COSINE余弦相似度。我用COSINE比较多因为对于文本向量经过归一化之后余弦相似度的语义解释更直观两个向量方向越一致值越接近1表示越相似。插入数据的操作很简单import random # 模拟一批768维向量数据 data [ [i for i in range(10)], [文本内容 str(i) for i in range(10)], [[random.random() for _ in range(768)] for _ in range(10)] ] collection.insert(data)再强调一下insert之后数据默认没有立即落盘。要确保数据能立刻被查询到需要显式调用flushcollection.flush()这个坑我踩过好几次。刚插入的数据不flush就搜索返回结果是空的很多人以为是数据没插进去实际上就是没有落盘。检索时我们传入一个查询向量让Milvus返回最相似的TopK条结果search_params { metric_type: COSINE, params: {ef: 64} } query_embedding [[random.random() for _ in range(768)] for _ in range(1)] results collection.search( dataquery_embedding, anns_fieldembedding, paramsearch_params, limit5, output_fields[id, text] ) for result in results[0]: print(id:, result.id, 距离:, result.distance, 文本:, result.entity.get(text))注意这里的ef是查询时的搜索范围和构建时的efConstruction不同。ef越大搜索越充分召回率越高但延迟也越高。生产环境我一般设为构建时efConstruction的1/4到1/3平衡性能和召回率。还有一点是关于余弦相似度的。Milvus返回的distance值对于COSINE度量来说严格意义上不是两个向量的余弦值而是1 - 余弦相似度。数值越小表示越相似。我一开始没注意这个看到返回0.005还以为是异常值。实际上返回0.005意味着相似度是0.995非常高。在对比阈值时一定要把这个转换关系算清楚。5. 常见问题与排查技巧实录5.1 连接失败防火墙、地址和内存一个都不能少排查顺序先确认Milvus进程是否活着再确认端口是否监听最后检查防火墙。docker compose ps ss -lntp | grep 9091如果进程Up但端口没监听大概率是Milvus启动时依赖没就绪崩溃重启了。看日志docker logs milvus-standalone --tail 200我遇到过Etcd启动失败导致Milvus反复重启的情况原因是Etcd容器里的数据卷权限不对。解决方法是把数据目录权限改成777再重启chmod -R 777 /data/milvus/etcd如果你发现Milvus进程一直restarting八成是内存不足。2.4版本起MinIO和Etcd都比较吃内存尤其是MinIO默认限制512MB但数据量大时会超过这个数值。可以在compose文件里给每个服务增加mem_limit防止互相抢内存etcd: mem_limit: 2g minio: mem_limit: 4g5.2 数据插入了但查询结果为空这个前面提过就是没有flush。再补充两个情况数据量很小少于几千条即便flush了查询也可能返回空。原因是Milvus默认的segment大小阈值是1024条数据量小于一个segment时查询分区搜索可能覆盖不全。解决办法有两种一是调小参数在配置文件里把dataCoord.segment.segmentMaxSize调小但生产环境不推荐二是多插一些测试数据或者用partition key做分区。实际项目里这不是问题数据量很快就上来了。还有一种情况是output_fields里的字段没建索引查询时附加读取会变慢但结果不会为空。要注意这点对性能的影响。5.3 索引构建慢或内存暴涨HNSW构建是内存密集型的。我测试过1000万条768维向量构建HNSW索引峰值内存大约要12GB左右持续几分钟。如果你的机器配置不够可以把efConstruction调低到128M调低到8构建时间能减少三分之一左右召回率损失可以接受。另外建索引时Milvus会默认把构建任务交给独立的索引节点。Standalone模式里这个节点和查询节点共用同一个进程所以建索引时查询性能会受影响。如果你有实时查询需求最好把建索引时间安排在低峰期或者用create_index的index_build_with_batch异步参数避免阻塞。5.4 集合删不掉报错资源冲突这个我在清理测试环境时经常遇到。如果你创建过索引要删集合Milvus要求先删除索引再删集合。不然会报delete collection failed。正确顺序collection.drop_index() collection.drop()如果在删除过程中有其他客户端连接正在对这个集合进行操作也会冲突。先把占用连接的客户端断开再执行删除。我的一个通用做法是删集合前先列出所有活跃客户端通知业务方暂停读写再操作。6. 实操经验总结与后续扩展方向6.1 数据备份与恢复的捷径Milvus集群本身不提供命令行备份工具社区有Milvus Backup这个开源工具。我一直用它做定时备份。备份前要进行一次flush把内存里的数据落盘再用backup工具把MinIO里的数据和Etcd里的元数据打包起来。恢复的时候反过来做。这个工具走的是Milvus内部接口不会影响线上服务比直接拷MinIO文件安全得多。我目前的备份策略是每天凌晨2点做一次完整备份保留最近7天的备份文件。到目前执行了半年多恢复过两次测试库基本都在半小时内恢复。如果你的数据量特别大可以做增量备份但Milvus的增量备份原理是基于binlog的需要额外配置建议先跑通全量再说。6.2 监控与告警的龙骨Milvus官方推荐用Prometheus Grafana做监控。安装部署后Milvus会在默认端口9091暴露/metrics接口拉取就行。我自己关注的指标有四个milvus_query_entity_count查询返回条数、milvus_index_construct_latency索引构建延迟、milvus_kv_backend_opsEtcd读写操作耗时、minio_response_timeMinIO响应时间。MinIO的响应时间别忽视很多时候查询慢了不是Milvus的问题而是MinIO在拖后腿。告警规则我给个粗糙的参考查询耗时P99超过500ms持续5分钟告警索引构建任务积压超过3个告警内存使用率持续超过80%告警。这些规则要根据你的业务体量调整。6.3 从Standalone平滑迁移到分布式的思路数据量涨到临界点后切分布式是必然的。Milvus官方支持从Standalone导出数据再用Cluster导入。实操路径大概是搭建Cluster环境启用对象存储和消息队列用Milvus Backup导出Standalone里的数据在Cluster环境里导入备份并重建集合和索引这里有个迁移期的小技巧先在Cluster上创建一个新集合建好索引然后并行写入新的业务数据历史数据通过备份导入。切流前把两个环境的查询结果做一遍抽样对比确保一致性。切换时用DNS或负载均衡指向Cluster的地址业务基本无感。迁移的复杂度比单机高出不少尤其是Pulsar或Kafka的调优建议先在测试环境完整演练两次把消息积压的排查方法也一并验证了。6.4 后续还能怎么玩我目前在生产环境跑的业务已经超出了简单的相似搜索一个是用Milvus做多模态检索图片特征和文本特征统一映射到同一个向量空间用COSINE做跨模态匹配另一个是把用户行为序列编码成向量做个性化召回线上效果比传统的协同过滤好不少。性能优化方面我下一步准备试试GPU版本的索引Milvus 2.4支持CAGRA索引英伟达GPU加速官方宣传比CPU快一个数量级对高QPS场景吸引力很大。工具链方面现在有LangChain这类框架把Milvus内置成了向量存储后端意味着做LLM应用只要配置一个Milvus连接信息就能直接实现知识库检索。这个方向我建议所有做AI应用的朋友都关注一下。最后再分享一个小技巧信息密度上来之后检索的瓶颈往往不在数据库本身而在你的Embedding模型质量。同一个Milvus用好的模型做向量化召回率能翻倍。所以我现在的原则是先把Milvus的稳定性和性能吃透再回头优化上游的Embedding质量两件事并行推进收益比单纯调参高得多。
返回列表