当前位置: 首页 > news >正文

milvus学习笔记

本文主要由AI生成,请注意自己查看源代码校验。

Milvus v2.4+ 系统架构概览

Milvus 采用分布式微服务架构,将计算层(Proxy、QueryCoord、QueryNode、IndexCoord、DataCoord、DataNode 等)与存储层(Pulsar、MinIO/S3、etcd)分离,实现高并发、高可用和水平扩展。在查询场景下,查询请求首先经过前端 Proxy,由 Proxy 调度到后端查询服务,再由 QueryNode 在数据分片(segment)上并行执行向量检索,最后汇总结果返还给客户端。总体架构示意如图所示,重点标出了 Proxy、QueryCoord、QueryNode、DataCoord 等模块及它们之间通过 gRPC 和消息队列(如 Pulsar)通信的路径。

图1:Milvus 存算分离架构示意(Proxy -> 消息队列 -> DataNode -> 对象存储 -> QueryNode)

整体而言,Milvus 将写流程和读流程分离:写入时,客户端经由 Proxy 将数据写入日志流(Pulsar),DataNode 消费写日志并生成 segment(上传到对象存储),DataCoord/QueryCoord 收集和管理 segment;查询时,客户端经由 Proxy 发起检索请求,Proxy 将查询信息作为消息放入查询通道(QueryChannel),各个 QueryNode 订阅该通道接收查询。在查询前,QueryCoord 会根据 DataCoord 提供的 segment 信息对 QueryNode 做负载均衡(按 segment 或通道分配)。整个系统中,etcd 用于存储元数据和服务发现,Pulsar 用作日志 broker(可替换为 Kafka 等),MinIO/S3 作为大文件存储(segment、索引等)。

Proxy 模块源码结构

Proxy 作为客户端访问层,负责接收各种客户端请求(DML、查询、DDL 等),做校验和预处理后分发给后端服务。其核心结构体在 internal/proxy 包中定义,包含如下主要成员:

type Server struct {ctx                context.Contextproxy              types.ProxyComponent      // 实现 proxy 功能的接口(对外暴露 gRPC 服务)rootCoordClient    types.RootCoordClient    // gRPC 客户端:RootCoorddataCoordClient    types.DataCoordClient    // gRPC 客户端:DataCoordqueryCoordClient   types.QueryCoordClient   // gRPC 客户端:QueryCoord// ... 其他成员 ...
}

其中,types.ProxyComponent 接口由 internal/proxy/proxy.go 中的 Proxy 结构体实现。ServerRun() 方法启动 gRPC 服务并注册各类任务处理入口。对于向量检索(Search)请求,Proxy 内部会生成一个 SearchTask(位于 internal/proxy/task_search.go),将用户的查询参数(如集合 ID、向量、Top-K、过滤表达式等)封装成任务,然后将该任务发送给调度器或下游组件。在新版 Milvus 中,Proxy 并不直接执行搜索逻辑,而是根据查询信息将查询消息发往 Pulsar 的查询通道(QueryChannel)。同时,Proxy 维护对 RootCoord/DataCoord/QueryCoord 的客户端,用于获取集合元数据、segment 分布、时间戳等信息。例如,在 LoadCollection 操作中,Proxy 会先调用 RootCoord 创建加载任务,再通过 QueryCoord 获取需要加载的 segment 信息。总之,Proxy 起到“入口和汇聚”作用,无状态地对外提供统一服务。

QueryCoord 模块源码结构

QueryCoord 是查询协调节点,负责管理整个查询集群的拓扑和负载均衡,以及查询使用的 segment 分配和增量数据(Growing Segment)切换。其核心结构体位于 internal/querycoord 包中,主要成员包括:

type Server struct {queryCoord   types.QueryCoordComponent  // 实现 querycoord 功能的接口dataCoord    types.DataCoordClient      // gRPC 客户端:DataCoordrootCoord    types.RootCoordClient      // gRPC 客户端:RootCoord// ... 其他成员 ...
}

其中,types.QueryCoordComponent 接口由 internal/querycoord/query_coord.go 中的 QueryCoord 结构体实现。Server.Run() 启动 gRPC 服务,处理来自 Proxy 或客户端的查询加载等请求。典型的功能包括:LoadCollection(预加载指定集合到查询集群)和Search(调度查询任务)等。当用户调用 collection.load() 时,Proxy 会将加载请求转发给 QueryCoord。QueryCoord 首先通过 dataCoord.GetRecoveryInfo() 获取集合在存储中的已封存段(Sealed Segment)及对应的检查点;然后根据配置选择“按段分配”或“按通道分配”策略,将不同的封存段 (或日志通道) 分配到各个 QueryNode 上。QueryCoord 的任务调度器 (segment allocatorchannel allocator) 负责这一步骤。此外,QueryCoord 还负责监控 QueryNode 的负载,触发流式到封存段的转换(handoff),以及均衡重分配。官方文档指出:“QueryCoord 管理查询节点的拓扑和负载平衡,并处理从增长段到封存段的切换”。在代码层面,internal/querycoord 下的 querycoordsegment_allocatorchannel_allocator 等包实现了以上逻辑。

QueryNode 模块源码结构

QueryNode 是实际执行查询计算的工作节点,负责在加载到本地的 segment 数据上运行向量检索。其核心代码位于 internal/querynodev2(Milvus 2.x 采用 v2 版本实现)。一个 QueryNode 进程启动时,会初始化以下主要组件:流图(FlowGraph)用于处理增量数据,索引服务用于处理已封存的数据检索,和 Segment Manager 管理加载的 segment。Milvus 文档中描述:“QueryNode 订阅日志代理获取增量日志,将其转换为增长段,并从对象存储加载历史数据,在向量和标量数据之间运行混合搜索”。

在实现上,QueryNode 包括:server.go 定义了 QueryNode 结构体(实现 types.QueryNodeComponent 接口),负责启动 gRPC 服务和管理子模块;flowgraph 子包实现了增长段(Growing Segment)的流式数据接收与过滤;delegate 子包负责封装和分发具体的搜索请求到适当的 segment 或索引;segcore(SegCore)则提供 C++ 的向量检索核心调用。QueryNode 的主要方法包括 Search() (接受 QueryChannel 的查询消息)、LoadSegments()(加载指定 segment 的数据到内存),以及定期从 DataCoord 读取全局时间戳和消费位置,实现读取增量数据并触发持久化/封存。在并发执行向量检索时,QueryNode 会对本地所有加载的封存段并行搜索,然后与增长段(当前写入的数据)一起做融合,最后本地归约(去重)输出结果。可见,QueryNode 相对复杂,涉及流式处理和检索执行两大功能。

一次完整的向量检索流程

下面按步骤说明客户端一次查询请求在 Milvus 中的流转路径:

  1. 客户端请求 -> Proxy:客户端通过 SDK 发起 Search 请求到 Proxy(gRPC)。Proxy 接收后,将请求信息(包括集合 ID、查询向量、搜索参数等)封装成一个查询消息,并写入日志中间件(Pulsar)的查询通道。同时,Proxy 可从 RootCoord/DataCoord 获取集合的元数据和可用 segment 列表,为后续的调度做准备。

  2. (预先)Load Collection -> QueryCoord:在发起搜索前,如果用户调用了 collection.load(),Proxy 会触发 QueryCoord 的加载操作。QueryCoord 向 DataCoord 查询集合所有已封存段(和各段的消费检查点),然后执行负载分配。例如,按段分配时将不同封存段分配给不同 QueryNode;按通道分配时让 QueryNode 订阅不同的 DMChannel。在此步骤结束后,相关 QueryNode 已从对象存储加载了对应的历史数据段(封存段),并订阅了增量日志通道(收到新的写入数据)。

  3. QueryCoord 分配 -> QueryNode 订阅:QueryCoord 调度后,各 QueryNode 会执行对应的 LoadSegmentsWatchChannels 操作,准备好查询环境。每个 QueryNode 都在其本地维持了若干封存段(Sealed Segment)和对应的增长段(Growing Segment)(参见图2)。

图2:QueryCoord 为每个 QueryNode 分配封存段和日志通道示意。QueryNode1 加载了 S1、S3 等历史段,并订阅了 DMChannel1;QueryNode2 加载了 S2、S4,并订阅 DMChannel2。最终每个节点在本地同时拥有历史数据和增量流数据。

  1. QueryProxy 将查询推送至查询通道:客户端的查询请求已经写入 Pulsar 查询通道后,各 QueryNode 会监听此通道并取出查询消息。在消息中包含了执行时间戳等信息。Milvus 首先比较当前服务时间戳(service_ts)与查询消息中的保证时间戳(guarantee_ts)。只有当 service_ts >= guarantee_ts 时,才执行查询;否则该查询消息会暂时“悬挂”直到达到条件。

  2. QueryNode 执行检索并归约:当查询消息符合执行条件后,每个 QueryNode 并行地在本地的历史数据和增量数据上执行搜索。这包括对已经加载的封存段(离线历史数据)和正接收写入的增长段(在线流数据)分别进行搜索。由于两者可能重叠,QueryNode 内部会做一次“本地归约”(Local Reduce)去重。搜索完成后,各 QueryNode 将本地结果发布到结果通道(ResultChannel)。此时,结果消息中包含了本节点所搜索的封存段和通道信息。

  3. Proxy 聚合返回结果:Proxy 订阅结果通道,收集来自所有 QueryNode 的结果集。收到后,Proxy 会做一次全局归约(Global Reduce),去除不同节点间的重复结果。为确保结果完整性,Proxy 通过结果消息中的字段跟踪是否所有封存段和通道的数据都已返回。当条件满足后,Proxy 将最终合并排序后的 Top-K 结果返回给客户端。整个流程中,Proxy 仅负责路由和汇总;QueryCoord 只在查询准备阶段调度;真正的搜索计算由各 QueryNode 执行。

模块间调用链示意

以下伪代码描述了上述交互的主要调用顺序(忽略错误处理和并发细节):

// Proxy 接收到客户端的搜索请求
func ProxyHandleSearch(req QueryRequest) {// 1. 向 RootCoord 请求集合元数据(例如分片、索引信息)collectionInfo := RootCoord.GetCollectionInfo(req.CollectionID)// 2. 将查询信息封装为消息写入 Pulsar 查询通道Pulsar.Publish(QueryChannel, req)// 3. 监听结果通道,收集 QueryNode 返回结果results := collectResultsFromChannel(ResultChannel)// 4. 对所有 QueryNode 的结果做归约并返回merged := globalReduce(results)return merged
}// QueryCoord 预加载流程(LoadCollection)
func QueryCoordLoad(collectionID) {// 从 DataCoord 获取所有已封存段和检查点segments := DataCoord.GetRecoveryInfo(collectionID)// 选择分配策略(按段或按通道)// 将 segment 列表分配给不同的 QueryNodeassignments := allocateSegmentsToNodes(segments)// 通知各 QueryNode 加载数据或订阅通道for each node, segs in assignments:node.LoadSegments(segs)
}// QueryNode 搜索处理
func QueryNodeOnQueryMessage(msg QueryRequest) {if serviceTS < msg.GuaranteeTS {// 不满足时间条件,等待return}// 在本地封存段和增长段上并行检索resultsOffline := searchSealedSegments(msg.Vector, req)resultsStream := searchGrowingSegments(msg.Vector, req)localResults := localReduce(resultsOffline, resultsStream)// 发布到结果通道Pulsar.Publish(ResultChannel, localResults)
}

该调用链中,关键的是 Proxy、QueryCoord、QueryNode 三层协作:Proxy 负责接收请求与返回结果,QueryCoord 负责查询准备与调度,QueryNode 负责具体检索执行。

代码目录与主要文件(参考)

Milvus 仓库 internal 目录下包含各组件的实现子目录,其中与查询相关的主要路径有:

  • internal/proxy/:Proxy 服务实现,包括 proxy.go(Proxy 结构体、接口)、task_search.go(SearchTask 实现)等。

  • internal/querycoord/:QueryCoord 服务实现,包括 query_coord.go(QueryCoord 结构体、接口)、segment_allocator/channel_allocator/ 等调度逻辑。

  • internal/querynodev2/:QueryNode 服务实现,包括 server.go(QueryNode 结构体、接口)、flowgraph/(流式处理)、delegate/(查询代理)、segments/(本地 segment 管理)等。

下图为 Milvus 源码(部分)目录结构示意:

internal/
├── proxy/
│   ├── proxy.go          # ProxyComponent 实现(types.ProxyComponent)
│   ├── task_search.go    # SearchTask 逻辑
│   └── ...              
├── querycoord/
│   ├── query_coord.go    # QueryCoordComponent 实现(types.QueryCoordComponent)
│   ├── segment_allocator/
│   └── channel_allocator/
│   └── ...
├── querynodev2/
│   ├── server.go         # QueryNodeComponent 实现(types.QueryNodeComponent)
│   ├── flowgraph/        # 增量数据流式处理
│   ├── delegate/         # 查询请求分发
│   ├── segments/         # 本地 Segment 管理
│   └── ...
└── datacoord/            # DataCoord 服务(管理存储相关)└── ...

从以上目录可见,每个组件都用一个 Server 结构体启动 GRPC 服务,并持有对应的客户端接口,如 ProxyServer 包含 rootCoordClientqueryCoordClient 等。组件间通过 gRPC 和 Pulsar 消息进行协同,如 Proxy 调用 QueryCoord.LoadCollection 获取 segment 信息,QueryCoord 调用 DataCoord.GetRecoveryInfo 获取段信息,QueryNode 从 Pulsar 查询通道接收 Search 请求等。

参考资料

  • Milvus 官方文档与博客:《Milvus 架构概览》《QueryCoord 相关配置》《实时查询解析》等。

  • Zilliz/Milvus GitHub 代码:internal/proxyinternal/querycoordinternal/querynodev2 包。

  • 开源社区文章:Milvus 源码解析博客(见 “SearchTask”、“实时查询” 部分)。

相关文章:

  • 虚拟来电 4.3.0 |集虚拟来电与短信于一体,解锁VIP优雅脱身
  • Android从单体架构迁移到模块化架构。你会如何设计模块划分策略?如何处理模块间的通信和依赖关系
  • 从实模式到保护模式
  • 每日算法刷题Day8 5.15:leetcode滑动窗口4道题,用时1h
  • 使用Python实现简单的人工智能聊天机器人
  • 【基础】Windows开发设置入门6:Scoop开发者完全指南(AI整理)
  • AXI-LITE slave读写时序
  • MySQL 与 FastAPI 交互教程
  • 589. N叉树的前序遍历迭代法:null指针与栈的巧妙配合
  • Crowdfund Insider聚焦:CertiK联创顾荣辉解析Web3.0创新与安全平衡之术
  • 职坐标AIoT技能培训课程实战解析
  • base64加密为何可以直接找三方网站解密
  • Unity:场景管理系统 —— SceneManagement 模块
  • Reactive与Ref的故事
  • day22-数据结构之 栈队列
  • RAGFlow升级到最新0.18.0新手指南
  • APIfox参数化配置
  • AI 赋能 Copula 建模:大语言模型驱动的相关性分析革新
  • 操作系统-锁/内存/中断/IO
  • c++20引入的三路比较操作符<=>
  • 网警打谣:传播涉刘国梁不实信息,2人被处罚
  • 精品消费“精”在哪?多在体验上下功夫
  • 外交部部长助理兼礼宾司司长洪磊接受美国新任驻华大使递交国书副本
  • 媒体评欧阳娜娜遭民进党当局威胁:艺人表达国家认同是民族大义
  • 师爷、文士、畸人:会稽范啸风及其著述
  • 多地举办演唱会等吸引游客刺激消费,经济日报:引导粉丝经济理性健康发展