Milvus 核心概念与架构

Milvus 核心概念

这是理解 Milvus 的基石,它们定义了 Milvus 处理的对象和基本单位。

  • 非结构化数据 (Unstructured Data) :指那些不符合预定义数据模型、不便于用数据库二维逻辑表来表现的数据,如图像、视频、音频和自然语言文本等。传统数据库难以高效处理这类数据的相似性检索,这正是 Milvus 擅长解决的问题。
  • 向量 (Vector) :非结构化数据的“数学翻译”。它本质上是一个由浮点数或二进制数组成的列表(数组),用于描述原始数据的特征。
    • 密集向量 (Dense Vector) :维度通常为数百,每个位置都有非零值,适合表达包含丰富语义的文本或图像整体特征。
    • 稀疏向量 (Sparse Vector) :维度可能高达数十万,但大部分位置为0,适合用于关键词匹配或在推荐系统中表达用户长尾兴趣等场景。
  • 嵌入 (Embedding) :指将非结构化数据通过模型(如BERT、ResNet)转化为向量的过程。Milvus 2.6.x 引入了嵌入函数 (Embedding Function) 功能,你只需提供原始文本,它便能直接调用 OpenAI、Hugging Face 等模型自动完成向量化,并存储到指定向量字段,整个过程更加平滑。
  • 实体 (Entity) :类似于关系数据库中的“行”。它由一个主键和一组字段(包含标量字段和向量字段)组成,构成了Collection中的一条完整数据记录。
  • 字段 (Field) :实体的组成部分,类似于关系数据库中的“列”。Milvus 2.6.x 的字段分为两类:
    • 标量字段 (Scalar Field) :存储元数据(如 authorprice),用于数据过滤和查询。2.6.x版本大幅增强了标量字段能力,支持动态添加新字段而无需重建集合,并新增了GEOMETRY(地理空间) 类型,用于存储和查询空间数据。
    • 向量字段 (Vector Field) :核心数据所在,存储由嵌入生成的向量。
  • 主键 (Primary Key) :实体的唯一标识符,类似于关系数据库的主键列。数据类型可以是 INT64VARCHAR
    • AutoID :是主键的一个属性。若在创建集合时设置 auto_id=True,则插入数据时无需指定主键值,Milvus会自动生成并管理它。
  • 集合 (Collection) :类似于关系数据库中的“表”。它是所有实体和字段的容器,我们正是通过在集合上定义结构(Schema)、建立索引和发起搜索来完成工作。

数据建模与管理

这部分涉及如何为数据设计蓝图,并进行物理分区管理。

  • 模式 (Schema) :数据结构的“蓝图”,定义了Collection包含哪些字段、字段类型及约束。在Milvus 2.6.x中,你可以随时向已有Schema添加新字段,实现不停机的模式演进。
    • 动态模式 (Dynamic Schema) :一种灵活的Schema设计,允许你在插入数据时,定义Schema之外的字段。Milvus 会自动将这些未定义的字段放入一个名为 $meta 的保留字段中存储。这在处理JSON等半结构化数据时非常有用。
  • 索引 (Index) :为加速字段(尤其是向量字段)搜索而构建的数据结构。Milvus 支持IVF_FLAT、HNSW等多种索引。
    • 自动索引 (Auto-Index) :一种智能索引策略。Milvus会根据数据分布自动选择最合适的索引类型和参数,免去手动调参的繁琐。例如,配置向量字段自动索引会根据经验自动调整配置以优化性能。
  • 度量类型 (Metric Type) :衡量两个向量间相似度或距离的方法,需与索引类型匹配。Milvus 2.6.x 支持的主要类型如下:
    • 欧氏距离 (L2) :计算两点间的直线距离,值越小越相似。
    • 内积 (IP) :推荐系统中的常用指标,值越大越相似。
    • 余弦相似度 (COSINE) :关注方向而非大小,值越大越相似。
  • 分区 (Partition) :Collection内部的逻辑划分。将实体按一定规则(如日期、类别)放入不同分区,查询时指定分区可显著提升效率。
  • 分片 (Shard) :用于横向扩展的物理分割。一个Collection的数据会被均分到多个分片,分布在多个计算节点上,以实现并行写入和存储。
    • 分区键 (Partition Key) :从 is_partition_key=True的字段取值,自动将记录路由到对应分区。将分片、分区、分区键结合,可在不同粒度上实现数据隔离,常用于多租户场景。

核心操作

这部分是Milvus为用户提供的基本数据操作能力。

  • 插入 (Insert) :向指定Collection中添加一条或多条实体。
    • 批量写入工具 (Bulk Writer) :一种辅助工具,用于将原始数据集(如JSON、NumPy文件)高效转换成Milvus可导入的格式。
    • 批量插入 (Bulk Insert) :一个API接口,允许你通过单个请求导入多个文件(如JSON、NumPy),从而高效地将海量数据一次性写入Milvus。批量插入需要先将数据文件上传到对象存储。
    • Upsert :一种数据操作,若记录不存在则插入(Insert),若存在则更新(Update)。
  • 搜索 (Search) :Milvus的核心操作,通过查询向量,找到Collection中与之最相似的Top-K向量( 近似最近邻搜索 ,ANN),并返回其主键和标量字段值。
  • 查询 (Query) :基于标量字段条件的精确查询,与SQL的 SELECT语句类似。此操作不涉及向量相似度计算。
  • 过滤搜索 (Filtered Search) :最常见的搜索模式。在向量搜索中加入标量过滤条件 expr,实现语义匹配与元数据过滤的结合。
  • 范围搜索 (Range Search) :向量搜索的一个变种。不限定返回结果数量(K),而是搜索查询向量距离在指定半径内的所有向量。
  • 混合搜索 (Hybrid Search) :多向量检索与重排序。2.6.x版本极大增强了混合检索能力,核心功能如下:
    • hybrid_search() API :支持在多个向量字段上同时发起ANN搜索(多路召回),然后通过RRF、Weighted Ranker等策略对结果进行重排序
    • 文本+向量混合检索 :原生集成了基于BM25的全文检索和向量检索能力,支持短语匹配等精确度控制。
    • 空间+向量混合检索 :原生支持 几何搜索 ,如上例所示,能在一个查询中完成空间范围过滤(地理围栏)和语义相关性搜索。

Milvus核心架构

客户端

各 SDK 官方地址:

SDK 语言 官方仓库 / 文档
PyMilvus Python https://github.com/milvus-io/pymilvuspip install pymilvus
Milvus Go SDK Go https://github.com/milvus-io/milvus-sdk-gogo get github.com/milvus-io/milvus-sdk-go/v2
Milvus Java SDK Java https://github.com/milvus-io/milvus-sdk-java(Maven / Gradle 引入)
Milvus Node.js SDK JavaScript https://github.com/milvus-io/milvus-sdk-nodenpm install @zilliz/milvus2-sdk-node
RESTful API 通用 内嵌于 Proxy 组件,通过 HTTP 端口访问,SDK 中以 uri="http://..."方式连接

RESTful API 并非独立仓库,而是由 Proxy 进程内部启动的 HTTP Gateway 提供。

所有语言 SDK 底层均通过 gRPC(默认端口 19530)与 Proxy 通信,Proxy 同时对外暴露 RESTful 端点(默认端口 9091),方便非 gRPC 语言直接通过 HTTP 调用。

各 SDK 共享同一套 Protobuf 定义:https://github.com/milvus-io/milvus-proto

访问层(Access Layer)

访问层是Milvus系统的最前端,负责接收和处理所有客户端请求。它是用户与Milvus系统交互的入口点,扮演着至关重要的网关角色。

从 Docker 容器认识访问层

在 Milvus Standalone 部署中,访问层(Proxy)作为 Milvus 容器内的核心组件运行。通过 Docker 命令可以直接观察到 Proxy 的端口监听、请求处理和服务状态。

# 1. 查看 Milvus 容器及其端口映射
$ docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}"

# 关键发现:
#   端口 19530 → gRPC 端口(SDK 必须连接这个端口!)
#   端口 9091  → HTTP 监控/管理端口
#   etcd 的端口(2379-2380/tcp)

image-20260715171626297

Proxy 启动日志

# windows
docker logs milvus-standalone 2>&1 | findstr /i "proxy access layer"
# linux
docker logs milvus-standalone 2>&1 | grep -i "proxy\|access layer"

image.png

访问层核心职责

1) 请求接收与路由

访问层通过统一的API接口(gRPC/HTTP)接收来自客户端的请求,并根据请求类型将其路由到相应的协调器组件。当客户端发起连接时,访问层首先验证连接的有效性,然后根据操作类型(插入、搜索、查询等)将请求分发到对应的处理模块。

ChatGPT Image 2026年7月15日 17_28_34

2) 认证与授权

访问层集成了完整的用户认证和权限管理机制。Milvus支持基于角色的访问控制(RBAC),可以为不同的用户或用户组分配不同的权限级别。认证过程采用业界标准的JWT令牌机制,确保只有经过验证的请求才能访问系统资源。

flowchart LR
    classDef client fill:#E3F2FD,stroke:#1976D2,color:#000;
    classDef auth fill:#FFF3E0,stroke:#F57C00,color:#000;
    classDef store fill:#E8F5E9,stroke:#43A047,color:#000;
    classDef jwt fill:#F3E5F5,stroke:#8E24AA,color:#000;
    classDef result fill:#E1F5FE,stroke:#0288D1,color:#000;

    Client["客户端请求"]:::client
    Auth["访问层<br/>认证模块"]:::auth
    Etcd["etcd<br/>用户库"]:::store
    Result["验证结果"]:::result
    JWT["JWT Token<br/>验证"]:::jwt

    Client --> Auth
    Auth --> Etcd
    Etcd --> Result

    Auth --> JWT
    Result --> JWT

3) 负载均衡

访问层(Proxy)实现了智能负载均衡策略,能够根据各工作节点的当前负载情况,将请求分发到最合适的节点处理。Milvus 支持多种负载均衡策略,确保系统在高并发场景下仍能保持稳定的性能表现。

负载均衡的策略类型:

Proxy 内部在路由查询请求到 Query Node 时,通过 LoadBalancer 接口选择目标节点。源码中实现了以下策略(位于 internal/proxy/ 目录):

策略 类名 工作原理
轮询(Round Robin) RoundRobinBalancer 依次轮流选择可用 Query Node,每个节点轮流承担请求
最少代价(Least Cost) LeastCostBalancer 实时采集各 Query Node 的 CPU/内存/请求数指标,选择当前负载最轻的节点
旁路(Look Aside) LookAsideBalancer 查询时先检查本地缓存的路由表,命中则直接转发,未命中则查询 QueryCoord 获取最新拓扑

配置方式(milvus.yaml):

负载均衡策略通过 Milvus 配置文件中的 Proxy 和 QueryCoord 相关参数进行配置:

# milvus.yaml — Proxy 负载均衡相关配置
proxy:
  port: 19530                    # gRPC 端口
  http:
    port: 9091                   # HTTP/RESTful 端口
    enabled: true
  timeTickInterval: 200          # 时间滴答间隔(毫秒)
  # 查询节点选择相关参数
  queryNode:
    selector: "RoundRobin"       # 节点选择策略:RoundRobin / LeastCost
    failoverRetryTimes: 3        # 故障转移重试次数

# QueryCoord 相关配置
queryCoord:
  autoBalance: true              # 是否开启自动负载均衡
  balanceIntervalSeconds: 60     # 负载均衡检查间隔(秒)
  overloadedMemoryWatermark: 0.85  # 内存过载水位线(超过 85% 触发均衡)
  # Segment 分配策略
  globalRowCountFactor: 0.25     # 全局行数因子
  scoreBasedBalance: true        # 基于评分的均衡(CPU+内存综合打分)

场景举例:

场景 1:多 Query Node 轮询分发

假设有 3 个 Query Node(QN1、QN2、QN3),配置 selector: "RoundRobin":

请求序列:
  请求1 → QN1    请求4 → QN1    请求7 → QN1
  请求2 → QN2    请求5 → QN2    请求8 → QN2
  请求3 → QN3    请求6 → QN3    请求9 → QN3

适用场景:各节点配置相同、数据均质分布

场景 2:基于实际负载的智能分发(LeastCost)

假设 3 个 Query Node 当前状态:
  QN1: CPU 85%,内存 90%,当前排队请求 120  ← 负载高
  QN2: CPU 30%,内存 45%,当前排队请求 20   ← 负载低
  QN3: CPU 50%,内存 60%,当前排队请求 50   ← 负载中

配置 selector: "LeastCost" 时:
  新请求 → QN2(代价最低,优先选择)

适用场景:节点配置不均、数据热点分布、弹性扩缩容过程中

场景 3:自动负载再均衡(QueryCoord 触发)

当某 Query Node 内存超过 overloadedMemoryWatermark (85%):

  1. QueryCoord 检测到 QN1 内存 90% > 85% 水位线
  2. 触发自动再均衡(balanceIntervalSeconds: 60s 周期检查)
  3. 将 QN1 上的部分 Sealed Segment 迁移到 QN2/QN3
  4. 迁移完成后,QN1 内存降至 60%,恢复健康状态

无需人工介入,全程自动完成

生产环境建议:

场景 推荐策略 原因
节点同配、数据均匀 RoundRobin 简单高效,无额外采集开销
节点异构、负载波动大 LeastCost 动态感知负载,避免”热点节点”
高并发 + 低延迟要求 LeastCost+autoBalance: true 配合自动再均衡持续优化分布
调试/测试环境 RoundRobin 行为可预测,便于排查问题
# 客户端侧无法直接配置 Proxy 负载均衡策略(策略在服务端 milvus.yaml 中配置),
# 但可以通过以下方式影响负载分布:

from pymilvus import connections, Collection

connections.connect(alias="default", address="localhost:19530")

# 方式1:创建多个副本(replica),Proxy 会自动将查询负载分散到各副本
collection = Collection("my_collection")
collection.load(replica_number=3)  # 3 个内存副本 = 3 个 Query Node 可并行查询

# 方式2:合理使用 Partition Key,将数据分散到不同分区
# 不同分区可能由不同 Query Node 负责,天然分散查询负载

# 方式3:连接池(SDK 侧)
# PyMilvus 内部维护 gRPC 连接池,并发请求会自动多路复用
# 无需额外配置,SDK 默认开启

协调器组件详解

协调器是 Milvus 的”大脑”,负责调度和管理所有工作节点。四种协调器各司其职,共同组成系统的控制平面(Control Plane)。

协调器 代码位置 操作对象 核心职责 对外接口
Root Coord internal/rootcoord/ 元数据(DB/Collection/Partition) DDL 操作、TSO 时间戳分配 CreateCollectionDropCollection
Query Coord internal/querycoordv2/ Query Node 集群 查询分发、结果合并、Segment 分配 SearchQueryHybridSearch
Data Coord internal/datacoord/ Data Node 集群 数据持久化调度、Segment 生命周期 InsertDeleteFlush
Index Coord internal/indexcoord/ Index Node 集群 索引构建调度、进度监控 CreateIndexDropIndex

技术实现与类比:

flowchart LR

subgraph Kitchen["餐厅后厨"]
    K1["👨‍🍳 厨师长<br/>(分配桌子)"]
    K2["🚶 传菜主管<br/>(调度传菜)"]
    K3["🔪 切配主管<br/>(管理食材)"]
    K4["🍽️ 摆盘主管<br/>(成品把关)"]
end

subgraph Milvus["Milvus 协调器"]
    M1["Root Coord<br/>管理全局菜单(元数据)"]
    M2["Query Coord<br/>调度 Query Node"]
    M3["Data Coord<br/>调度 Data Node"]
    M4["Index Coord<br/>调度 Index Node"]
end

K1 <--> M1
K2 <--> M2
K3 <--> M3
K4 <--> M4

classDef kitchen fill:#FFF8E1,stroke:#F9A825,stroke-width:2px;
classDef milvus fill:#E3F2FD,stroke:#1E88E5,stroke-width:2px;

class K1,K2,K3,K4 kitchen;
class M1,M2,M3,M4 milvus;

1) Root Coordinator(根协调器)—— 全局总管

Root Coord 是唯一拥有 TSO(Timestamp Oracle)能力的组件,所有时间戳由它统一分配,是整个系统时序一致性的根基

请求路径(以创建 Collection 为例):

graph TD
    %% 定义节点样式
    classDef component fill:#e1f5fe,stroke:#01579b,stroke-width:2px;
    classDef database fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;
    classDef action fill:#fff3e0,stroke:#e65100,stroke-width:1px,stroke-dasharray: 5 5;

    %% 节点定义
    Client([Client]):::component
    Proxy([Proxy]):::component
    RootCoord([Root Coord]):::component
    Etcd[(etcd)]:::database
    TSO[分配时间戳 TSO]:::action
    DataCoord[通知 Data Coord 初始化 Segment]:::action

    %% 连线关系
    Client --> Proxy
    Proxy --> RootCoord
    
    %% Root Coord 的水平/垂直操作
    RootCoord --> |写入元数据| Etcd
    RootCoord --> TSO
    TSO --> DataCoord

2) Query Coordinator(查询协调器)—— 查询调度员

Query Coord 负责将查询请求路由到最优的 Query Node,并合并多个节点的搜索结果。它监听各 Query Node 的负载指标,在节点间动态迁移 Segment 以实现负载均衡。请求路径(以 Search 为例):

graph TD
    Client --> Proxy
    Proxy --> QueryCoord[Query Coord]
    
    QueryCoord --> Step1[查询 localMetaCache<br>哪个 QN 持有目标 Segment?]
    QueryCoord --> Step2[选择负载最低的 Query Node]
    
    Step1 --> QueryNode[Query Node]
    Step2 --> QueryNode
    
    QueryNode --> Search[执行向量搜索]
    Search --> Return[返回结果]
    
    Return --> Merge[Query Coord 合并多个 QN 的结果]

3) Data Coordinator(数据协调器)—— 数据大管家

Data Coord 负责决定”数据写到哪里”。它通过分配 vchannel(虚拟通道)和物理 Segment 来控制数据的流向和落地位置。请求路径(以 Insert 为例):

graph TD
    Client --> Proxy
    Proxy --> DataCoord[Data Coord]
    
    DataCoord --> VChannel[分配 vchannel<br>数据路由通道]
    DataCoord --> Segment[分配 Growing Segment<br>可写入的数据段]
    
    VChannel --> DataNode[Data Node]
    Segment --> DataNode
    
    DataNode --> WAL[写 WAL]
    WAL --> Storage[(落盘到对象存储)]

4) Index Coordinator(索引协调器)—— 索引调度员

Index Coord 监控所有”尚未建索引”的 Sealed Segment,为它们创建索引构建任务,并分发给 Index Node 执行。请求路径(以 CreateIndex 为例):

graph TD
    Client --> Proxy
    Proxy --> RootCoord[Root Coord]
    RootCoord --> IndexCoord[Index Coord]
    
    IndexCoord --> Scan[扫描待建索引的 Segment 列表]
    IndexCoord --> Generate[生成索引构建任务]
    
    Scan --> IndexNode[Index Node]
    Generate --> IndexNode
    
    IndexNode --> Build[构建索引]
    Build --> Storage[(写入对象存储)]
    
    Storage --> Notify[通知 Query Coord<br>'索引就绪,可加载到查询节点']

协调器相关配置(milvus.yaml):

# 根协调器配置
rootCoord:
  address: localhost:53100
  port: 53100
  dmlChannelNum: 16          # DML 通道数
  maxPartitionNum: 4096       # 最大分区数

# 查询协调器配置
queryCoord:
  address: localhost:19531
  port: 19531
  autoBalance: true           # 自动 Segment 负载均衡
  balanceIntervalSeconds: 60  # 均衡检查周期
  overloadedMemoryWatermark: 0.85  # 内存过载水位线

# 数据协调器配置
dataCoord:
  address: localhost:13333
  port: 13333
  segment:
    maxSize: 1024            # 单个 Segment 最大容量(MB)
    sealProportion: 0.12     # Sealed Segment 比例
  compaction:
    enableAutoCompaction: true  # 自动压缩合并

# 索引协调器配置
indexCoord:
  address: localhost:31000
  port: 31000
  bindIndexNodeMode:
    enable: false            # 是否绑定固定 Index Node

协调器健康状态监控:

# 通过 SDK 检查协调器管理的资源状态
from pymilvus import connections, Collection, utility

connections.connect(alias="default", address="localhost:19530")

# 查看 Root Coord 管理的资源
print(f"数据库数量: {len(utility.list_databases())}")
print(f"集合数量: {len(utility.list_collections())}")

# 查看 Index Coord 管理的索引状态
for coll_name in utility.list_collections():
    collection = Collection(coll_name)
    for idx in collection.indexes:
        print(f"集合 '{coll_name}' 字段 '{idx.field_name}' 索引就绪")

# 查看 Data Coord 管理的分区状态
collection = Collection(utility.list_collections()[0])
for partition in collection.partitions:
    print(f"分区 '{partition.name}': {partition.num_entities} 条数据")

connections.disconnect("default")

以上要点总结:

维度 Root Coord Query Coord Data Coord Index Coord
类比 数据库 DBA 查询调度员 存储管理员 索引管理员
处理的操作 Create/Drop/Describe Search/Query Insert/Delete/Flush CreateIndex/DropIndex
调度对象 全局元数据 Query Node Data Node Index Node
核心机制 TSO 时间戳 Segment 负载均衡 vchannel 分配 异步任务队列
如果挂了 无法创建/删除集合 无法执行搜索 无法写入新数据 新数据无法建索引

工作节点(Worker Nodes)

工作节点是 Milvus 中真正”干活”的角色——写入数据、构建索引、执行搜索都由它们完成。协调器只管”调度”,工作节点负责”执行”。工作节点采用无状态设计,支持水平扩展。

从 Docker 容器认识工作节点

以 Milvus Standalone(Docker Compose 部署)为例,所有组件运行在单个容器内,但内部进程模型与分布式一致。通过 Docker 命令可以直观看到各组件的真实面貌。

Milvus Standalone 的 docker-compose.yml 示例:

# 单机版 Milvus Standalone 的 Docker Compose 配置
version: '3.5'
services:
  etcd:
    container_name: milvus-etcd
    image: quay.io/coreos/etcd:v3.5.5
    # ...

  minio:
    container_name: milvus-minio
    image: minio/minio:RELEASE.2023-03-20T20-16-18Z
    # ...

  standalone:
    container_name: milvus-standalone
    image: milvusdb/milvus:v2.4.0
    ports:
      - "19530:19530"   # gRPC 端口(SDK 连接用)
      - "9091:9091"     # HTTP 监控端口
    depends_on:
      - etcd
      - minio
    # 在这个容器内部,Proxy、Coordinator、Worker Node 全部以 Go 协程方式运行

查看运行中的 Milvus 容器:

# 1. 查看 Docker 容器列表
$ docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"

NAMES               IMAGE                                      STATUS                       PORTS
attu                zilliz/attu:v3.0.0-beta.6                  Up 59 minutes                0.0.0.0:3000->3000/tcp, [::]:3000->3000/tcp
milvus-standalone   milvusdb/milvus:v2.6.17                    Up About an hour (healthy)   0.0.0.0:9091->9091/tcp, [::]:9091->9091/tcp, 0.0.0.0:19530->19530/tcp, [::]:19530->19530/tcp
milvus-minio        minio/minio:RELEASE.2024-12-18T13-15-44Z   Up About an hour (healthy)   0.0.0.0:9000-9001->9000-9001/tcp, [::]:9000-9001->9000-9001/tcp
milvus-etcd         quay.io/coreos/etcd:v3.5.25                Up About an hour (healthy)   2379-2380/tcp


# 2. 进入 Milvus 容器查看内部进程
$ docker exec -it milvus-standalone /bin/bash

# 3. 容器内查看 Milvus 进程(Standalone 模式下所有组件在同一进程内)
$ ps aux | grep milvus
root           1  0.0  0.2  19656  7772 ?        Ssl  08:52   0:00 /tini -- milvus run standalone
root           8  5.7 10.0 2150216 374464 ?      Sl   08:52   3:39 milvus run standalone

# 4. 查看 Milvus 日志 —— 日志中会打印各组件的角色标识
$ docker logs --tail 20 milvus-standalone

[INFO] "Welcome to Milvus"
[INFO] "Proxy successfully started"            # ← Access Layer
[INFO] "RootCoord successfully started"        # ← Coordinator
[INFO] "QueryCoord successfully started"       # ← Coordinator
[INFO] "DataCoord successfully started"        # ← Coordinator
[INFO] "IndexCoord successfully started"       # ← Coordinator
[INFO] "QueryNode successfully started"        # ← Worker Node ← 本章主角
[INFO] "DataNode successfully started"         # ← Worker Node ← 本章主角
[INFO] "IndexNode successfully started"        # ← Worker Node ← 本章主角
[INFO] "Milvus is ready"

关键认知: 从日志可以看到,Standalone 模式下每行 “successfully started” 都是一个 Go 协程/子模块,它们在同一个操作系统进程内通过 gRPC loopback 互相通信。在分布式模式下,每个 “Node” 独立部署为单独的 Pod/容器。

Docker 容器内部组件通信示意图:

graph TD
    subgraph Standalone["Milvus Standalone 进程 (单机模式)"]
        Proxy["Go 协程:Proxy          ← 监听 0.0.0.0:19530 (gRPC)"]
        RootCoord["Go 协程:RootCoord      ← 与 etcd 通信 (milvus-etcd)"]
        QueryCoord["Go 协程:QueryCoord     ← 调度 QueryNode"]
        DataCoord["Go 协程:DataCoord      ← 调度 DataNode"]
        IndexCoord["Go 协程:IndexCoord     ← 调度 IndexNode"]
        QueryNode["Go 协程:QueryNode      ← 加载索引,执行搜索"]
        DataNode["Go 协程:DataNode       ← 写入 WAL,持久化数据"]
        IndexNode["Go 协程:IndexNode      ← 构建向量索引"]
        
        Note>📢 所有协程通过本地 gRPC 通信(127.0.0.1),无需跨容器]
        
        %% 使用隐式连线强制内部节点垂直整齐排列
        Proxy ~~~ RootCoord ~~~ QueryCoord ~~~ DataCoord ~~~ IndexCoord ~~~ QueryNode ~~~ DataNode ~~~ IndexNode ~~~ Note
    end

    Etcd[(milvus-etcd<br>元数据存储)]
    Minio[(milvus-minio<br>对象存储)]

    %% 从整个进程容器指向外部存储依赖
    Standalone --> Etcd
    Standalone --> Minio

写节点 / 流节点(Stream Node / Write Node)

写节点(Milvus 内部称为 Stream Node)负责处理所有数据写入请求。在分布式部署中它是独立可扩展的组件,在 Standalone 模式下由 DataNode 协程兼任。

流节点的 Docker 视角:

# 分布式部署中,流节点/写节点作为独立组件运行
$ docker ps | grep stream
milvus-streamnode-1    milvusdb/milvus:v2.4.0   "milvus run streamnode"

# 查看写节点日志 —— 观察数据写入行为
$ docker logs --tail 10 milvus-streamnode-1
[INFO] "StreamNode register success"
[INFO] "Receive insert request, channel=by-dev-rootcoord-dml_0, size=1024"
[INFO] "Write to WAL completed, msgID=18446744073709551615"
[INFO] "Flush segment 440000000000000001 to MinIO"

# 在 Standalone 模式下,查看数据写入的实时日志
$ docker logs -f milvus-standalone | grep -E "DataNode|Flush|Insert"
[INFO] "DataNode received insert, collection=my_demo, rows=1000"
[INFO] "DataNode buffer flush triggered, size=128KB"
[INFO] "Segment sealed and persisted to MinIO"

流节点工作流程:

graph TD
    Client[客户端] --> Proxy[Proxy]
    Proxy --> DataNode["DataNode<br>(StreamNode)"]
    
    DataNode --> Step1["1. 数据写入内存 Buffer"]
    DataNode --> Step2["2. 同步写 WAL<br>(Pulsar/Kafka)"]
    DataNode --> Step3["3. Buffer 满或定时触发 Flush"]
    DataNode --> Step4["4. 数据落盘到对象存储<br>(MinIO/S3)"]
    
    %% 用虚线表示这四步的先后逻辑顺序,让图表更具可读性
    Step1 -.-> Step2 -.-> Step3 -.-> Step4
    
    %% 为存储组件添加不同的样式区分
    classDef storage fill:#f9f2f4,stroke:#d37295,stroke-width:2px;
    class Step2,Step4 storage;

查看 WAL 和 Flush 状态:

  docker exec milvus-standalone ls -la /var/lib/milvus/data/
  docker exec milvus-minio ls -la /data/

写节点核心特性:

特性 描述 Docker 验证方式
异步写入 数据先入内存,异步持久化 docker logs milvus-standalone | grep Flush
批量处理 支持批量插入,减少网络 IO 观察日志中 rows=N的变化
WAL 保障 崩溃后从 WAL 恢复未持久化数据 查看 /var/lib/milvus/wal/目录
流量控制 内置写入速率限制 查看 milvus.yamldataNode.flowGraph.maxQueueLength

查询节点(Query Node)

查询节点是搜索性能的关键。它从对象存储加载索引到本地内存,在内存中执行向量相似度计算。

查询节点的 Docker 视角:

# 分布式部署中查看 Query Node 容器
$ docker ps | grep querynode
milvus-querynode-1   milvusdb/milvus:v2.4.0   "milvus run querynode"

# 查看 Query Node 加载的 Segment 信息
$ docker logs milvus-querynode-1 2>&1 | grep -E "load|segment|memory"
[INFO] "QueryNode loading segment 440000000000000002, size=128MB"
[INFO] "Segment loaded into memory, collection=my_demo"
[INFO] "QueryNode memory usage: 256MB / 1024MB"

# 查看容器内存使用(观察加载索引前后的变化)
$ docker stats milvus-standalone --no-stream
CONTAINER            CPU %    MEM USAGE / LIMIT     MEM %
milvus-standalone    12.5%    512.3MiB / 2GiB       25.01%

# 在 Python 中 load 数据后再看
$ docker stats milvus-standalone --no-stream
CONTAINER            CPU %    MEM USAGE / LIMIT     MEM %
milvus-standalone    25.0%    890.5MiB / 2GiB       43.48%    ← 内存涨了!

教学技巧: 上面的 docker stats 对比是课堂上最直观的演示——让学生先记下 load 前的内存,再 load 后看内存增长,立即理解 “Query Node 是将索引加载到内存做搜索” 这句话的含义。

查询节点搜索流程:

graph TD
    Request([查询请求]) --> QueryCoord[Query Coord]
    
    %% 定义 Query Node 集群子图
    subgraph Cluster [Query Node 集群]
        direction LR
        QN1["QN-1<br>Seg: A, B<br>Mem: 30%"]
        QN2["QN-2<br>Seg: C, D<br>Mem: 55%"]
        QN3["QN-3<br>Seg: E, F<br>Mem: 85%<br>(Load 高会被绕过)"]
    end
    
    %% 下发查询请求(路由与绕过)
    QueryCoord -->|分发| QN1
    QueryCoord -->|分发| QN2
    QueryCoord -.->|绕过不分发| QN3
    
    Merge[结果合并与排序]
    
    %% 结果汇总
    QN1 --> Merge
    QN2 --> Merge
    %% QN3 被绕过所以没有返回箭头
    
    Merge --> Output([返回 Top-K 结果])
    
    %% 样式区分(为高负载节点添加虚线边框标注)
    style QN3 stroke:#f44336,stroke-width:2px,stroke-dasharray: 5 5

索引节点(Index Node)

索引节点是 CPU 密集型组件,负责计算和构建向量索引。在大规模数据场景下,索引构建可能占用大量 CPU 时间。

索引节点的 Docker 视角:

# 观察索引构建时的 CPU 飙升
$ docker stats milvus-standalone --no-stream
CONTAINER    CPU %    MEM USAGE / LIMIT
milvus-sa    8.2%     412MiB / 2GiB

# Python 代码创建索引后立即观察
# >>> collection.create_index("vector", {"index_type": "HNSW", ...})

$ docker stats milvus-standalone --no-stream
CONTAINER    CPU %    MEM USAGE / LIMIT
milvus-sa    145.3%   890MiB / 2GiB    ← CPU 飙升,正在构建索引!

# 索引构建完成后 CPU 回落
$ docker stats milvus-standalone --no-stream
CONTAINER    CPU %    MEM USAGE / LIMIT
milvus-sa    6.5%     780MiB / 2GiB    ← 索引构建完成,CPU 恢复正常

# 查看索引文件是否已写入 MinIO
$ docker exec milvus-minio ls -la /data/default/my_collection/
-rw-r--r--  index_440000000000000001.hnsw  24567890  ← HNSW 索引文件已生成

数据节点(Data Node)

数据节点是数据的”管家”,管理数据从内存到磁盘的完整生命周期。它的核心工作单元是 Segment(数据段)

数据节点的 Docker 视角:

# 查看 DataNode 管理的 Segment 信息
$ docker exec milvus-standalone ls /var/lib/milvus/data/
440000000000000001/   # ← 这是一个 Sealed Segment 的本地缓存目录
440000000000000002/

# 查看 DataNode 的日志 —— 追踪 Segment 的生命周期
$ docker logs milvus-standalone 2>&1 | grep -E "Segment|seal|compact"
[INFO] "DataNode created growing segment 440000000000000003"
[INFO] "Segment 440000000000000003 sealed, size=512MB"
[INFO] "Compaction start: merging segments [440000000000000001, 440000000000000002]"
[INFO] "Compaction done: new segment 440000000000000004 created"

# 查看 MinIO 中持久化的 Segment 文件
$ docker exec milvus-minio ls -laR /data/default/my_collection/
/data/default/my_collection/:
  insert_log/           # DML 日志(原始向量数据)
  index_files/          # 索引文件
  binlog/               # Binlog 文件(数据快照)

Segment 生命周期(DataNode 视角):

flowchart LR
    %% 定义卡片节点内容
    Growing["<b>Growing Segment(可写入)</b><br/>━━━━━━━━━━━━━━<br/>• 新数据持续写入<br/>• 驻留在 DataNode 内存<br/>• 同时写 WAL"]
    
    Sealed["<b>Sealed Segment(已封存)</b><br/>━━━━━━━━━━━━━━<br/>• 不再接受写入<br/>• 等待 IndexNode 建索引<br/>• 已持久化到对象存储"]
    
    Compacted["<b>合并后的大 Segment</b><br/>━━━━━━━━━━━━━━<br/>• 减少文件碎片<br/>• 提高查询效率"]

    %% 定义包含换行文本的横向流转箭头
    Growing -->|"满了<br>(seal)"| Sealed
    Sealed -->|"多个小 Segment 合并<br>(Compaction)"| Compacted
    
    %% 让文本靠左对齐,样式更像卡片
    classDef default fill:#f8fcff,stroke:#87bdec,stroke-width:2px,text-align:left;

工作节点综合

以下是一个完整的课堂演示流程,使用 Docker 命令配合 Python SDK,让学生直观感受各组件的协作:

# ==================== Shell 终端 ====================

# 步骤1: 启动前记录基线
docker stats milvus-standalone --no-stream

# 步骤2: 观察插入数据时 DataNode 的日志
docker logs -f milvus-standalone &
# ==================== Python 终端 ====================
from pymilvus import MilvusClient, Collection
import numpy as np

client = MilvusClient(uri="http://localhost:19530")

# 步骤3: 创建集合 + 插入数据 —— 触发 DataNode
client.create_collection("demo", dimension=128, metric_type="L2")
client.insert("demo", [
    {"id": i, "vector": np.random.rand(128).tolist()}
    for i in range(10000)
])
client.flush("demo")
print("数据写入完成,查看 Docker 日志应看到 Flush 记录")
# ==================== Shell 终端 ====================

# 步骤4: 查看 DataNode 的 Flush 日志
docker logs milvus-standalone --tail 20 | grep -i "flush\|seal"
# 预期输出: "Segment sealed and persisted to MinIO"

# 步骤5: 查看 MinIO 中是否有文件生成
docker exec milvus-minio ls -la /data/default/demo/
# 预期输出: insert_log/ binlog/ 目录
# ==================== Python 终端 ====================

# 步骤6: 创建索引 —— 触发 IndexNode,观察 CPU 飙升
client.create_index("demo", "vector", {
    "index_type": "HNSW",
    "metric_type": "L2",
    "params": {"M": 16, "efConstruction": 200}
})
# ==================== Shell 终端 ====================

# 步骤7: 索引构建时观察 CPU(应立即执行)
docker stats milvus-standalone --no-stream
# 预期: CPU > 100%,说明 IndexNode 正在密集计算

# 步骤8: 查看索引文件
docker exec milvus-minio ls -la /data/default/demo/index_files/
# 预期: .hnsw 索引文件
# ==================== Python 终端 ====================

# 步骤9: Load 数据 —— 触发 QueryNode 加载索引到内存
client.load_collection("demo")
# ==================== Shell 终端 ====================

# 步骤10: 立即观察内存增长(触目惊心的对比!)
docker stats milvus-standalone --no-stream
# 内存应从 ~400MB 增长到 ~800MB+

# 步骤11: 执行搜索,观察 QueryNode 工作
docker logs milvus-standalone --tail 5 | grep -i "search\|query"
# ==================== Python 终端 ====================

# 步骤12: 最终搜索验证
results = client.search("demo", [np.random.rand(128).tolist()], limit=5)
print("搜索成功!", [(r["id"], round(r["distance"], 4)) for r in results[0]])

以上代码注意如下几点:

  • **Docker 日志是”活教材”**:每个操作在日志中都有对应记录,让学生”看见”架构不再是抽象概念。
  • docker stats 对比法:插入前后、建索引前后、Load 前后的 CPU/内存对比,比任何文字描述都有说服力。
  • 写节点写入:使用批量写入可以显著提高写入性能,日志中的 rows=N 能直观反映批量大小。
  • 查询节点搜索:搜索参数(如 nprobeef)直接影响搜索性能,可通过日志观察耗时。
  • 索引节点构建:索引构建是异步过程,日志中会打印 Index building progress 状态更新。
  • 数据节点段管理:理解 Growing/Sealed/Compacted 三种 Segment 状态,是性能调优的基础。

存储层(Storage)

存储层是 Milvus 的”地基”,所有数据最终都落在这里。它由三个核心组件构成:元存储(etcd)、对象存储(MinIO/S3)和 WAL 存储(Pulsar/Kafka/RocksMQ)。

从 Docker 容器认识存储层

在 Milvus Standalone 部署中,存储层的每个组件都运行在独立 Docker 容器中,可以直接进入容器查看内部数据。

存储层 Docker 容器概览:

# 查看存储层相关容器
$ docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}" \
    | grep -E "etcd|minio|pulsar|milvus"

NAMES               IMAGE                              STATUS          PORTS
milvus-standalone   milvusdb/milvus:v2.4.0             Up 3 hours      19530, 9091
milvus-minio        minio/minio:RELEASE.2023-03-20     Up 3 hours      9000-9001
milvus-etcd         quay.io/coreos/etcd:v3.5.5         Up 3 hours      2379-2380

# 查看各存储容器的磁盘占用
$ docker system df -v | grep milvus
milvus-minio        2.5GB      # ← 向量数据 + 索引文件(最大的那个!)
milvus-etcd         45MB       # ← 元数据(很小)
milvus-standalone   380MB      # ← 本地缓存(Segment 临时数据)

存储层总览示意图(容器视角):

flowchart LR
    %% 定义节点内部左对齐的卡片样式,并按组件给不同颜色
    classDef standalone fill:#e3f2fd,stroke:#1e88e5,stroke-width:2px,text-align:left;
    classDef etcd fill:#e8f5e9,stroke:#43a047,stroke-width:2px,text-align:left;
    classDef minio fill:#fff3e0,stroke:#fb8c00,stroke-width:2px,text-align:left;

    %% 1. 核心运行容器
    Standalone["<b>milvus-standalone 容器</b><br/>━━━━━━━━━━━━━━━━━━<br/>• Proxy / Coordinators<br/>• QueryNode / DataNode<br/>• IndexNode<br/>• (本地缓存: Segment 暂存)"]:::standalone

    %% 2. 元数据存储容器 (使用空格树状展现路径)
    Etcd["<b>milvus-etcd 容器</b><br/>━━━━━━━━━━━━━━━━━━<br/>/milvus/<br/>&nbsp;&nbsp;├─ collections/<br/>&nbsp;&nbsp;├─ partitions/<br/>&nbsp;&nbsp;├─ segments/<br/>&nbsp;&nbsp;└─ coordinators/"]:::etcd

    %% 3. 对象存储容器 (使用空格树状展现路径)
    Minio["<b>milvus-minio 容器</b><br/>━━━━━━━━━━━━━━━━━━<br/>/data/default/<br/>&nbsp;&nbsp;├─ collection_1/<br/>&nbsp;&nbsp;│&nbsp;&nbsp;├─ insert_log/&nbsp;&nbsp;(向量数据)<br/>&nbsp;&nbsp;│&nbsp;&nbsp;├─ index_files/ (索引文件)<br/>&nbsp;&nbsp;│&nbsp;&nbsp;└─ binlog/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(数据快照)<br/>&nbsp;&nbsp;└─ collection_2/"]:::minio

    %% 定义容器间的交互关系
    Standalone <-->|"读写元数据"| Etcd
    Standalone -->|"数据落盘与加载"| Minio

元存储(Meta Store)—— etcd

etcd 是 Milvus 的”配置中心 + 注册中心 + 元数据库”。它不存向量数据,只存”描述数据的数据”(Schema、集合定义、节点状态等)。

从 Docker 直接查看 etcd 中的元数据:

# 1. 进入 etcd 容器
$ docker exec -it milvus-etcd /bin/sh

# 2. 查看 Milvus 写入的元数据 key
$ etcdctl get --prefix "/milvus/" --keys-only

/milvus/collections/454444359156859000          # 集合元数据 ID
/milvus/partitions/454444359156859001            # 分区元数据
/milvus/segments/454444359156859002              # Segment 元数据
/milvus/rootcoord/session                        # Root Coord 注册信息
/milvus/querycoord/session                       # Query Coord 注册信息
/milvus/datacoord/session                        # Data Coord 注册信息
/milvus/indexcoord/session                       # Index Coord 注册信息

# 3. 查看某个集合的具体 Schema(解码 Protobuf 后的人类可读格式)
$ etcdctl get /milvus/collections/454444359156859000
# 输出包含:collection_name, fields[], primary_key, dimension, metric_type 等

# 4. 查看当前活跃的协调器节点(服务发现功能)
$ etcdctl get /milvus/rootcoord/session
# 输出包含:ServerID, Address=localhost:53100, LeaseID, 心跳时间

etcd 中存储的关键数据:

/milvus/
├── collections/{coll_id}     # 每个集合的 Schema 定义(字段名、类型、维度)
├── partitions/{partition_id}  # 分区元数据
├── segments/{segment_id}      # Segment 状态:Growing / Sealed / Flushed / Dropped
├── rootcoord/session          # Root Coord 的注册 session + 心跳
├── querycoord/session         # Query Coord 的注册 session
├── datacoord/session          # Data Coord 的注册 session
├── indexcoord/session         # Index Coord 的注册 session
├── querynodes/{node_id}       # 各 Query Node 的负载信息
├── datanodes/{node_id}        # 各 Data Node 的 channel 分配信息
└── credentials/               # 用户名/加密密码(RBAC)

元存储核心特性:

特性 描述 Docker 验证方式
高可用 Raft 协议,3 节点集群 etcdctl endpoint health
强一致 线性一致性读 etcdctl get保证读到最新
低延迟 内存级访问 etcdctl check perf
服务发现 各组件注册 session + 心跳续约 etcdctl get /milvus/rootcoord/session
# 演示元存储的元数据管理功能
from pymilvus import connections, Collection, utility

connections.connect(alias="default", address="localhost:19530")

print("=" * 60)
print("【Python API 视角 vs Docker 视角对比】")
print("=" * 60)

# Python SDK 查看(等价于 etcdctl get /milvus/collections/)
print("\n【集合列表】")
collections = utility.list_collections()
for coll in collections:
    print(f"  - {coll}")
    # 查看集合详细信息
    collection = Collection(coll)
    schema = collection.schema
    for field in schema.fields:
        print(f"    字段: {field.name} | 类型: {field.dtype} | 主键: {field.is_primary}")

connections.disconnect("default")

# 此时让学员在另一个终端执行:
#   docker exec milvus-etcd etcdctl get --prefix "/milvus/collections/" --keys-only
# 对比发现:Python API 看到的集合 = etcd 中存储的集合元数据

对象存储(Object Storage)—— MinIO / S3

对象存储是 Milvus 最大的存储消费者,存放向量原始数据索引文件。在 Standalone 部署中默认使用 MinIO。

从 Docker 直接查看 MinIO 中的文件:

# 1. 查看 MinIO 容器状态
$ docker ps | grep minio
milvus-minio   minio/minio:RELEASE.2023-03-20   Up 3 hours   0.0.0.0:9000-9001->9000-9001/tcp

# 2. 进入 MinIO 容器,查看 Milvus 写入的数据
$ docker exec -it milvus-minio /bin/sh

# 3. 查看 MinIO 存储目录结构
$ ls -la /data/
drwxr-xr-x  a-bucket/          # Milvus 使用的默认 bucket

# 4. 查看某个集合的存储文件(最直观的"数据库文件在哪"的答案)
$ find /data/a-bucket/ -type f | head -20

/data/a-bucket/default/my_collection/
├── insert_log/
│   ├── 454444359156860000/
│   │   ├── 454444359156860001  # 向量数据文件(每条记录的向量值)
│   │   ├── 454444359156860002
│   │   └── 454444359156860003
├── index_files/
│   ├── 454444359156860100/
│   │   ├── 454444359156860101  # HNSW 索引文件
│   │   └── 454444359156860102
├── binlog/
│   ├── 454444359156860200/
│   │   ├── 454444359156860201  # 数据快照文件
│   │   └── 454444359156860202
└── delta_log/
    ├── 454444359156860300/
    │   └── 454444359156860301  # 删除记录文件

# 5. 查看文件大小 —— 理解"向量数据库占多少磁盘"
$ du -sh /data/a-bucket/default/my_collection/
insert_log/     1.2GB     # 原始向量数据(128维 × Float32 × 4字节 × 行数)
index_files/    856MB     # 索引文件(HNSW 额外索引结构)
binlog/         1.2GB     # 数据快照
delta_log/      2.1MB     # 删除记录(几乎可以忽略)
Total:          ~3.2GB

# 6. 通过浏览器访问 MinIO 管理界面(更直观)
# 浏览器打开: http://localhost:9001
# 默认账号: minioadmin / minioadmin
# 进入 milvus bucket → 看到所有文件,可直接下载查看

MinIO Web 控制台是最佳的教学辅助工具。登录 http://localhost:9001 后,让学生亲眼看到 insert_log/index_files/binlog/ 这些目录和他们刚刚写入的数据一一对应。

MinIO 控制台操作演示(课堂截图级内容):

浏览器访问 http://localhost:9001 → 登录
  → Buckets → a-bucket → default/ → my_collection/
      → insert_log/    ← "老师,这就是我们刚 insert 的数据吗?"(是的!)
      → index_files/   ← "create_index 后出现的"(正确!)
      → binlog/        ← "flush 后持久化的快照"
      → delta_log/     ← "delete 操作的记录"

对象存储后端对比:

存储后端 适用场景 优点 缺点 Docker 部署
MinIO 本地/私有部署 部署简单,与 S3 兼容 需自行运维 minio/minio(Standalone 默认)
AWS S3 AWS 云 全托管,无限扩展 云厂商绑定 云端,通过 milvus.yaml配置
GCS GCP 云 全托管 云厂商绑定 云端
Azure Blob Azure 云 企业级 配置复杂 云端
阿里云 OSS 国内云 国内速度快 云厂商绑定 云端
腾讯 COS 腾讯云 低成本 云厂商绑定 云端
# 演示向量数据在对象存储中的概念
from pymilvus import connections, Collection
import numpy as np

connections.connect(alias="default", address="localhost:19530")

collection_name = "object_storage_demo"
if collection_name in utility.list_collections():
    utility.drop_collection(collection_name)

fields = [
    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True),
    FieldSchema(name="vector", dtype=DataType.FLOAT_VECTOR, dim=128),
    FieldSchema(name="title", dtype=DataType.VARCHAR, max_length=200)
]
schema = CollectionSchema(fields=fields, description="对象存储演示")
collection = Collection(name=collection_name, schema=schema)

batch_count = 500
print(f"准备存储 {batch_count} 条向量数据...")

entities = [{
    "id": i,
    "vector": np.random.rand(128).astype(np.float32).tolist(),
    "title": f"文档_{i}"
} for i in range(batch_count)]

insert_result = collection.insert(entities)
print(f"写入成功!共写入 {len(insert_result.primary_keys)} 条数据")

# Flush 后,数据从 WAL 持久化到 MinIO
collection.flush()
print("Flush 完成,数据已写入 MinIO")
print(f"  此时在 MinIO 控制台应能看到 insert_log/ 目录下出现新文件")
print(f"  存储大小估算: {batch_count * 128 * 4} 字节 ≈ {batch_count * 128 * 4 / 1024 / 1024:.2f} MB (未压缩)")

connections.disconnect("default")

WAL存储(Write-Ahead Log)—— Pulsar / Kafka / RocksMQ

WAL 是数据写入的”安全气囊”——写入请求先在 WAL 中记录(写日志极快),再异步批量写入对象存储(写文件较慢)。如果系统崩溃,可以从 WAL 恢复未持久化的数据。

WAL 存储的 Docker 视角:

# 1. Standalone 默认使用 RocksMQ(内置在 milvus 进程中,无需独立容器)
#    分布式部署通常使用 Pulsar 或 Kafka

# 2. 查看 Standalone 模式下 RocksMQ 的存储位置
$ docker exec milvus-standalone ls -la /var/lib/milvus/rocksmq/
-rw-r--r-- 100MB  rocksmq_data.db     # RocksMQ 持久化文件
-rw-r--r-- 4KB    MANIFEST

# 3. 如果使用 Pulsar(分布式部署)
$ docker ps | grep pulsar
milvus-pulsar    apachepulsar/pulsar:2.8.2   Up 3 hours

$ docker exec milvus-pulsar ls -la /pulsar/data/bookkeeper/
# BookKeeper 的 ledger 文件 = Milvus 的 WAL 记录

# 4. 观察 WAL 的写入和消费(站在消息队列角度)
#    Producer 端:DataNode 把数据写入 WAL
#    Consumer 端:IndexNode 从 WAL 读取数据建索引
$ docker logs milvus-standalone 2>&1 | grep -i "channel\|publish\|subscribe"

[INFO] "DataNode publish to channel by-dev-rootcoord-dml_0, msg count=1000"
[INFO] "IndexNode subscribe channel by-dev-rootcoord-dml_0"
[INFO] "QueryNode subscribe channel by-dev-rootcoord-dml_0, consume 1000 msgs"

WAL 端到端工作原理:

flowchart TD
    subgraph 正常写入流程
        A[Client 写入请求] --> B[Proxy 转发]
        B --> C[DataNode 写 WAL]
        C --> D[返回 Client OK]
        D --> E[异步 Flush 到 MinIO]
    end

    subgraph 崩溃发生
        E -.->|未完成| F[⚠️ 系统宕机]
        F --> G[数据在 WAL 中确认<br/>但未持久化到 MinIO]
    end

    subgraph 重启恢复流程
        G --> H[Milvus 重启]
        H --> I[DataNode 读取 WAL<br/>未消费消息]
        I --> J[重放 Replay 写入操作]
        J --> K[数据写入 MinIO]
        K --> L[✅ 数据完整,一致]
    end

关键理解:

  • 写 WAL 是同步的(一定要等 WAL 确认)
  • 写 MinIO 是异步的(批量做,效率高)
  • 崩溃后从 WAL 恢复:回放所有”已写 WAL” 但 “未写 MinIO” 的数据

WAL 存储核心特性:

特性 描述 Docker 验证方式
数据持久化保证 写入 WAL 即视为已持久化 查看 RocksMQ 文件大小变化:ls -la /var/lib/milvus/rocksmq/
故障恢复 崩溃后从 WAL 回放未完成的操作 查看启动日志中的 recovery/replay关键字
异步处理 WAL 写入(同步)+ MinIO 写入(异步) 对比 insert 返回速度和 MinIO 文件出现时间差
多后端支持 Pulsar / Kafka / RocksMQ Standalone 默认 RocksMQ(内置),分布式推荐 Pulsar
# 演示 WAL 的数据一致性保证
from pymilvus import connections, Collection, utility

connections.connect(alias="default", address="localhost:19530")

collection_name = "wal_demo"
if collection_name in utility.list_collections():
    utility.drop_collection(collection_name)

fields = [
    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True),
    FieldSchema(name="vector", dtype=DataType.FLOAT_VECTOR, dim=128)
]
schema = CollectionSchema(fields=fields, description="WAL演示集合")
collection = Collection(name=collection_name, schema=schema)

print("写入数据——此时数据已写 WAL,客户端收到 OK")
entities = [{"id": i, "vector": np.random.rand(128).astype(np.float32).tolist()}
            for i in range(100)]
insert_result = collection.insert(entities)
print(f"写入完成,返回 ID: {insert_result.primary_keys[:5]}...")
print(f"此时数据已安全记录在 WAL 中,即使系统崩溃也不会丢失")

# Flush —— 将 WAL 中的数据批量写入 MinIO
print("Flush 数据到 MinIO...")
collection.flush()

# 加载并验证
collection.load()
loaded = collection.query(expr="id >= 0", output_fields=["id"])
print(f"查询到 {len(loaded)} 条数据 —— 一致性验证: {'通过' if len(loaded) == 100 else '失败'}")

connections.disconnect("default")

存储层综合实验(Docker 实操)

以下实验让学生亲眼看到三层存储的数据流转:

# ==================== 实验:追踪一条数据的存储路径 ====================

# 步骤1: 查看插入前的 MinIO 状态
docker exec milvus-minio du -sh /data/a-bucket/default/

# 步骤2: Python 插入 10000 条数据并 flush
# (执行 Python 代码)

# 步骤3: 查看 MinIO 文件增长
docker exec milvus-minio du -sh /data/a-bucket/default/
# 预期:明显增大(insert_log/ 和 binlog/ 目录有新文件)

# 步骤4: 创建索引后再次查看
# (执行 Python collection.create_index)

docker exec milvus-minio du -sh /data/a-bucket/default/
# 预期:再次增大(index_files/ 目录出现 .hnsw 文件)

# 步骤5: 查看 etcd 中的集合注册
docker exec milvus-etcd etcdctl get --prefix "/milvus/collections/" --keys-only
# 预期:看到集合 ID key

# 步骤6: 查看 RocksMQ 中的消息(Standalone 模式)
docker exec milvus-standalone ls -lh /var/lib/milvus/rocksmq/
# 预期:rocksmq_data.db 文件存在

# 步骤7: 查看总磁盘占用
docker system df -v | grep milvus
# 综合报告:MinIO(最大) + etcd(小) + RocksMQ(中)

实验结论:三层存储各有其责

存储层 容器 存储内容 大小特征
元存储 milvus-etcd Schema、分区、Segment 状态、节点注册 KB~MB 级(极小)
对象存储 milvus-minio 向量数据、索引文件、Binlog 快照 GB~TB 级(最大)
WAL 存储 RocksMQ(内置)或 milvus-pulsar 待持久化的写入记录 MB~GB 级

以上代码注意如下几点:

  • 元存储高可用:etcd 的强一致性保证了元数据的可靠访问,生产环境建议部署 etcd 3 节点集群。
  • 对象存储扩展性:MinIO 数据量 > 其余所有组件之和,根据数据量选择合适的存储后端。
  • WAL 数据安全:这是防止数据丢失的最后一道防线,insert 返回成功 = 数据已安全写入 WAL。
  • Docker exec 教学法:让学生亲手进入容器查看文件,比任何 PPT 都更能建立对”存算分离”的直观理解。
--- 本文结束 The End ---