Milvus 向量数据库完整教程

Go 版本 + Ollama bge-m3 向量模型


目录


git地址:https://gitee.com/os-lee/ai 查看milvus-demo

系统架构

┌─────────────────────────────────────────────────────────────────────────────┐
│                              整体系统架构                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌──────────────┐                                                          │
│   │   用户查询    │                                                          │
│   │ "学Python"   │                                                          │
│   └──────┬───────┘                                                          │
│          │                                                                  │
│          ▼                                                                  │
│   ┌──────────────────────────────────────────────────────────────────┐     │
│   │                        Ollama 本地推理引擎                         │     │
│   │  ┌─────────────────────┐    ┌─────────────────────────────────┐  │     │
│   │  │  BGE-M3 向量模型                                                │  │     │
│   │  │  (Embedding Model)                                              │  │     │
│   │  │  文本 → 1024维向量                                               │  │     │
│   │  └──────────┬──────────────────────────────────────────────────────┘  │     │
│   └─────────────┼───────────────────────────────────────────────────────────┘     │
│                 │                                                                 │
│                 ▼                                                                 │
│   ┌──────────────────────────────────────┐                                        │
│   │          Milvus 向量数据库            │                                        │
│   │  ┌──────────────────────────────┐    │                                        │
│   │  │   Collection: book_embeddings │    │                                        │
│   │  ├──────────────────────────────┤    │                                        │
│   │  │ book_id   │ INT64 (PK)       │    │                                        │
│   │  │ book_name │ VARCHAR(256)     │    │                                        │
│   │  │ author    │ VARCHAR(128)     │    │                                        │
│   │  │ category  │ VARCHAR(64)      │    │                                        │
│   │  │ book_intro│ VARCHAR(1024)    │    │                                        │
│   │  │ embedding │ VECTOR(1024)     │    │                                        │
│   │  └──────────────────────────────┘    │                                        │
│   │           │                          │                                        │
│   │           │ ANN 搜索 (Top K)         │                                        │
│   │           ▼                          │                                        │
│   │  ┌──────────────────────────────┐    │                                        │
│   │  │   IVF_FLAT 索引 (L2 距离)    │    │                                        │
│   │  └──────────────────────────────┘    │                                        │
│   └──────────────────────────────────────┘                                        │
│                 │                                                                 │
│                 ▼                                                                 │
│   ┌──────────────────────────────────────┐                                        │
│   │       搜索结果 (按距离排序)           │                                        │
│   │  距离越小 = 语义越相似                │                                        │
│   └──────────────────────────────────────┘                                        │
│                                                                             │
│   ┌──────────────────────────────────────┐                                 │
│   │      Attu 可视化管理界面              │                                 │
│   │      http://localhost:18000          │                                 │
│   └──────────────────────────────────────┘                                 │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

核心组件介绍

1. Milvus 向量数据库

Milvus 是一个开源的向量数据库,专为嵌入向量(Embedding Vector)相似性搜索设计。

核心能力
能力说明
📥 海量存储支持数十亿级向量数据存储
🔍 毫秒搜索ANN(近似最近邻)算法实现毫秒级检索
📊 混合查询向量搜索 + 标量过滤组合使用
📈 多种索引FLAT、IVF_FLAT、HNSW 等多种索引类型
🔄 分布式支持水平扩展,应对大规模数据
应用场景
场景说明示例
🔍 语义搜索理解查询意图而非关键词匹配“想学编程” → 推荐 Python 教程
🖼️ 图像搜索以图搜图上传商品图 → 找相似商品
🤖 RAG 应用为 LLM 提供知识增强ChatGPT + 企业知识库
👤 推荐系统基于相似度的推荐喜欢 A 书 → 推荐相似书籍
🎵 音视频检索内容特征匹配哼唱旋律 → 找到歌曲
Milvus 核心概念
// ==================== Collection(集合)====================
// 类似关系数据库的"表",是数据存储的容器
// 
// 概念对比:
//   Collection = Table(表)
//   Schema     = CREATE TABLE(表结构)
//   Entity     = Row(一行数据)

// ==================== 字段类型 ====================

// 主键字段(必须有一个)
entity.FieldTypeInt64    // 整型主键,支持自增
entity.FieldTypeVarChar  // 字符串主键

// 标量字段(用于过滤条件)
entity.FieldTypeVarChar  // 字符串,需指定 max_length
entity.FieldTypeInt64    // 整型
entity.FieldTypeBool     // 布尔
entity.FieldTypeFloat    // 浮点数
entity.FieldTypeJSON     // JSON 类型

// 向量字段(必须有一个,用于相似度搜索)
entity.FieldTypeFloatVector   // 浮点向量(最常用)
entity.FieldTypeBinaryVector  // 二进制向量(节省空间)
索引类型详解
索引类型特点适用场景参数
FLAT暴力搜索,100% 精确小数据量(< 10万)
IVF_FLAT倒排索引,平衡速度与精度中等数据量(本教程使用)nlist(聚类数)
IVF_SQ8量化压缩,节省内存内存受限场景nlist
HNSW图索引,极速搜索追求低延迟M, efConstruction
距离度量(Metric Type)
类型Go 常量说明适用场景
L2entity.L2欧氏距离,越小越相似通用场景(本教程使用)
IPentity.IP内积,越大越相似归一化向量
COSINEentity.COSINE余弦相似度,越大越相似文本相似度

2. Ollama 本地推理引擎

Ollama 是一个轻量级的本地大模型运行框架,支持运行各种开源模型。

核心特点
特性说明
🏠 本地运行无需联网,数据不出本地
🚀 简单部署一行命令拉取模型
💰 完全免费无 API 调用费用
📦 模型丰富支持 LLM、Embedding、Reranker 等
🔧 CPU 友好无需 GPU 也能运行
API 接口
# Embedding API - 获取文本向量
POST http://localhost:11434/api/embeddings
{
  "model": "bge-m3",
  "prompt": "要转换的文本"
}
# 响应: { "embedding": [0.123, -0.456, ...] }  # 1024 维向量

# 模型管理
ollama list           # 列出已安装模型
ollama pull <model>   # 拉取模型
ollama rm <model>     # 删除模型
ollama serve          # 启动服务

3. BGE-M3 向量模型

BGE-M3(BAAI General Embedding - Multi-Lingual, Multi-Functionality, Multi-Granularity)是由北京智源人工智能研究院(BAAI)开发的开源向量模型。

模型特点
特性说明
🌍 多语言支持 100+ 语言,中英文效果优异
📐 向量维度1024 维(本教程使用)
🎯 多功能同时支持稠密向量、稀疏向量、多向量检索
📏 长文本支持最长 8192 tokens
🏆 效果领先MTEB 排行榜多项第一
工作原理
┌─────────────────────────────────────────────────────────────────────────┐
│                        BGE-M3 向量化过程                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   输入文本                      模型处理                    输出向量    │
│                                                                         │
│   "一本面向初学者的    ──────►  Transformer  ──────►  [0.12, 0.45,     │
│    Python教程"                   编码器                  -0.23, ...]    │
│                                                          (1024 维)     │
│                                                                         │
│   语义相似的文本会得到相近的向量:                                      │
│                                                                         │
│   "Python入门教程"    ───────────────►  向量 A                         │
│   "学习Python编程"    ───────────────►  向量 B  ← 距离很近!            │
│   "深度学习神经网络"  ───────────────►  向量 C  ← 距离较远              │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
Go 代码实现
// OllamaEmbeddingRequest Ollama embedding 请求结构
type OllamaEmbeddingRequest struct {
    Model  string `json:"model"`  // 模型名称: "bge-m3"
    Prompt string `json:"prompt"` // 要转换的文本
}

// OllamaEmbeddingResponse Ollama embedding 响应结构
type OllamaEmbeddingResponse struct {
    Embedding []float64 `json:"embedding"` // 1024 维向量
}

// getEmbedding 调用 Ollama API 获取文本的向量表示
func getEmbedding(text string) ([]float32, error) {
    reqBody := OllamaEmbeddingRequest{
        Model:  "bge-m3",
        Prompt: text,
    }

    jsonData, _ := json.Marshal(reqBody)
    resp, err := http.Post(
        "http://localhost:11434/api/embeddings",
        "application/json",
        bytes.NewBuffer(jsonData),
    )
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var result OllamaEmbeddingResponse
    json.NewDecoder(resp.Body).Decode(&result)

    // float64 → float32(Milvus 使用 float32)
    embedding := make([]float32, len(result.Embedding))
    for i, v := range result.Embedding {
        embedding[i] = float32(v)
    }
    return embedding, nil
}
与其他模型对比
模型维度中文效果英文效果模型大小特点
BGE-M31024⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐2.2GB多语言、效果最佳
bge-large-zh1024⭐⭐⭐⭐⭐⭐⭐⭐1.3GB中文专优
nomic-embed-text768⭐⭐⭐⭐⭐⭐⭐550MB轻量级
all-MiniLM-L6-v2384⭐⭐⭐⭐⭐⭐80MB超轻量

4. Attu 可视化管理界面

Attu 是 Milvus 官方的图形化管理工具,提供直观的 Web 界面。

核心功能
功能说明
📊 Collection 管理创建、删除、查看 Collection
🔍 数据浏览查看和搜索存储的数据
📈 索引管理创建、删除索引
📝 Schema 查看查看字段结构
🔧 性能监控查看系统状态
访问地址
  • URL: http://localhost:18000
  • Milvus 地址: standalone:19530(Docker 内部网络)


环境准备

1. 部署 Ollama 和模型

# 1. 安装 Ollama
# Linux/Mac
curl -fsSL https://ollama.com/install.sh | sh

# Windows: 下载安装包 https://ollama.com/download

# 2. 启动 Ollama 服务
ollama serve

# 3. 拉取向量模型(约 2.2GB)
ollama pull bge-m3

# 4. 验证模型
ollama list
# 输出:
# NAME              SIZE
# bge-m3:latest     2.2 GB

2. 部署 Milvus(Docker Compose)

创建 docker-compose.yaml

version: '3.5'
services:
  etcd:
    container_name: milvus-etcd
    image: quay.io/coreos/etcd:v3.5.5
    environment:
      - ETCD_AUTO_COMPACTION_MODE=revision
      - ETCD_AUTO_COMPACTION_RETENTION=1000
      - ETCD_QUOTA_BACKEND_BYTES=4294967296
      - ETCD_SNAPSHOT_COUNT=50000
    volumes:
      - etcd_data:/etcd
    command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd

  minio:
    container_name: milvus-minio
    image: minio/minio:RELEASE.2023-03-20T20-16-18Z
    environment:
      MINIO_ACCESS_KEY: minioadmin
      MINIO_SECRET_KEY: minioadmin
    volumes:
      - minio_data:/minio_data
    command: minio server /minio_data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3

  standalone:
    container_name: milvus-standalone
    image: milvusdb/milvus:v2.4.0
    command: ["milvus", "run", "standalone"]
    environment:
      ETCD_ENDPOINTS: etcd:2379
      MINIO_ADDRESS: minio:9000
    volumes:
      - milvus_data:/var/lib/milvus
    ports:
      - "19530:19530"  # Milvus gRPC 端口
      - "9091:9091"    # 健康检查端口
    depends_on:
      - etcd
      - minio

  attu:
    container_name: milvus-attu
    image: zilliz/attu:v2.4
    environment:
      MILVUS_URL: standalone:19530
    ports:
      - "18000:3000"   # Attu Web 管理界面
    depends_on:
      - standalone

volumes:
  etcd_data:
  minio_data:
  milvus_data:

启动服务:

docker-compose up -d

3. 验证部署

服务地址说明
Milvuslocalhost:19530gRPC 接口
Attuhttp://localhost:18000Web 管理界面
Ollamahttp://localhost:11434API 接口

4. 安装 Go SDK

go get github.com/milvus-io/milvus-sdk-go/v2

快速开始

示例文件说明

本教程包含两个示例程序:

示例文件说明适用场景
tutorial.go基础教程,演示 Milvus 完整操作流程入门学习、书籍数据
article/article.go文章向量库示例,支持短篇整体向量化和长篇分块向量化知识库、文章推荐

运行基础教程

cd test/milvus-demo

# 如果还没有 go.mod
go mod init milvus-demo

# 安装依赖
go get github.com/milvus-io/milvus-sdk-go/v2

# 运行基础教程(书籍向量库)
go run tutorial.go

运行文章向量库示例

cd test/milvus-demo/article

# 运行文章示例(短篇 + 长篇分块)
go run article.go

配置说明

tutorial.go 配置:

const (
    // Milvus 连接配置
    MilvusHost     = "localhost"
    MilvusPort     = "19530"
    CollectionName = "book_embeddings_go"
    VectorDim      = 1024  // bge-m3 输出 1024 维向量

    // Ollama 配置
    OllamaHost = "http://localhost:11434"

    // 向量模型:bge-m3
    // 多语言、多功能的向量模型,输出 1024 维向量
    EmbeddingModel = "bge-m3"
)

article/article.go 配置:

const (
    // Milvus 连接配置
    ArticleMilvusHost     = "localhost"
    ArticleMilvusPort     = "19530"
    ArticleCollectionName = "article_embeddings"       // 短篇文章集合
    ChunkedCollectionName = "article_chunks_embeddings" // 长篇文章分块集合
    ArticleVectorDim      = 1024

    // Ollama 配置
    ArticleOllamaHost = "http://localhost:11434"
    ArticleEmbedModel = "bge-m3"

    // 分块配置
    ChunkSize    = 500 // 每块约 500 字
    ChunkOverlap = 50  // 重叠 50 字
)

教程代码详解

一、tutorial.go - 基础教程

tutorial.go 包含 10 个核心步骤:

步骤概览

步骤功能核心方法说明
1连接 Milvusclient.NewClient()建立 gRPC 连接
2创建 Collectionc.CreateCollection()定义数据结构
3插入数据c.Insert() + c.Flush()使用 BGE-M3 生成向量
4创建索引c.CreateIndex()IVF_FLAT 索引
5加载到内存c.LoadCollection()搜索前必须执行
6向量搜索c.Search()语义相似度搜索
7带过滤搜索c.Search() + expr标量过滤
8标量查询c.Query()不涉及向量
9删除数据c.Delete()按条件删除
10释放资源c.ReleaseCollection()释放内存

详细说明

步骤 1:连接 Milvus
// step1Connect 建立与 Milvus 的 gRPC 连接
func step1Connect(ctx context.Context) client.Client {
    milvusClient, err := client.NewClient(ctx, client.Config{
        Address: fmt.Sprintf("%s:%s", MilvusHost, MilvusPort),
        // 如果启用了认证:
        // Username: "root",
        // Password: "Milvus",
    })
    if err != nil {
        log.Fatalf("连接 Milvus 失败: %v", err)
    }
    return milvusClient
}
步骤 2:创建 Collection
// step2CreateCollection 创建 Collection 并定义 Schema
func step2CreateCollection(ctx context.Context, c client.Client) {
    schema := &entity.Schema{
        CollectionName: CollectionName,
        Description:    "书籍向量数据库 - Go 版本",
        AutoID:         true,  // 主键自动生成
        Fields: []*entity.Field{
            // 主键字段
            {
                Name:       "book_id",
                DataType:   entity.FieldTypeInt64,
                PrimaryKey: true,
                AutoID:     true,
            },
            // 标量字段
            {
                Name:     "book_name",
                DataType: entity.FieldTypeVarChar,
                TypeParams: map[string]string{"max_length": "256"},
            },
            {
                Name:     "author",
                DataType: entity.FieldTypeVarChar,
                TypeParams: map[string]string{"max_length": "128"},
            },
            {
                Name:     "category",
                DataType: entity.FieldTypeVarChar,
                TypeParams: map[string]string{"max_length": "64"},
            },
            {
                Name:     "book_intro",
                DataType: entity.FieldTypeVarChar,
                TypeParams: map[string]string{"max_length": "1024"},
            },
            // 向量字段
            {
                Name:     "embedding",
                DataType: entity.FieldTypeFloatVector,
                TypeParams: map[string]string{"dim": "1024"},  // BGE-M3 维度
            },
        },
    }
    
    err := c.CreateCollection(ctx, schema, entity.DefaultShardNumber)
}

字段结构:

字段名类型说明
book_idINT64 (PK)主键,自动生成
book_nameVARCHAR(256)书名
authorVARCHAR(128)作者
categoryVARCHAR(64)分类(用于过滤)
book_introVARCHAR(1024)简介(用于生成向量)
embeddingVECTOR(1024)BGE-M3 向量
步骤 3:插入数据
// step3InsertData 使用 BGE-M3 生成向量并插入数据
func step3InsertData(ctx context.Context, c client.Client) {
    // 准备数据
    bookNames := []string{"Python编程", "深度学习", ...}
    authors := []string{"Eric Matthes", "Ian Goodfellow", ...}
    categories := []string{"编程", "AI", ...}
    intros := []string{"一本面向初学者的Python教程", ...}
    
    // 使用 BGE-M3 生成向量
    // 关键:将书籍简介转换为 1024 维向量
    embeddings, err := getEmbeddings(intros)
    
    // 构建列数据
    bookNameColumn := entity.NewColumnVarChar("book_name", bookNames)
    authorColumn := entity.NewColumnVarChar("author", authors)
    categoryColumn := entity.NewColumnVarChar("category", categories)
    introColumn := entity.NewColumnVarChar("book_intro", intros)
    embeddingColumn := entity.NewColumnFloatVector("embedding", VectorDim, embeddings)
    
    // 执行插入
    _, err = c.Insert(ctx, CollectionName, "",
        bookNameColumn, authorColumn, categoryColumn, introColumn, embeddingColumn)
    
    // 刷新到磁盘(确保持久化)
    c.Flush(ctx, CollectionName, false)
}
步骤 4:创建索引
// step4CreateIndex 创建 IVF_FLAT 索引
func step4CreateIndex(ctx context.Context, c client.Client) {
    // IVF_FLAT 索引参数
    // - L2: 欧氏距离(越小越相似)
    // - 128: nlist 聚类中心数量
    idx, err := entity.NewIndexIvfFlat(entity.L2, 128)
    
    // 在 embedding 字段上创建索引
    err = c.CreateIndex(ctx, CollectionName, "embedding", idx, false)
}

索引参数说明:

参数说明
索引类型IVF_FLAT倒排索引,平衡速度与精度
距离度量L2欧氏距离,越小越相似
nlist128聚类中心数量
步骤 5:加载到内存
// step5LoadCollection 加载 Collection 到内存
func step5LoadCollection(ctx context.Context, c client.Client) {
    // 必须加载后才能搜索!
    err := c.LoadCollection(ctx, CollectionName, false)
}

⚠️ 注意:搜索前必须执行 LoadCollection,否则会报错!

步骤 6:向量搜索
// step6VectorSearch 执行向量相似度搜索
func step6VectorSearch(ctx context.Context, c client.Client) {
    queryText := "想学习 Python 编程入门"
    
    // 1. 使用 BGE-M3 将查询转换为向量
    queryEmb, _ := getEmbedding(queryText)
    queryVector := []entity.Vector{entity.FloatVector(queryEmb)}
    
    // 2. 搜索参数
    sp, _ := entity.NewIndexIvfFlatSearchParam(16)  // nprobe = 16
    
    // 3. 执行向量搜索
    results, _ := c.Search(
        ctx,
        CollectionName,
        nil,                                  // partitions
        "",                                   // filter expression
        []string{"book_name", "author", "category"}, // output fields
        queryVector,
        "embedding",
        entity.L2,
        5,  // topK
        sp,
    )
    
    // 4. 输出搜索结果
    for _, result := range results {
        for i := 0; i < result.ResultCount; i++ {
            bookName, _ := result.Fields.GetColumn("book_name").GetAsString(i)
            distance := result.Scores[i]
            fmt.Printf("书名: %s, 距离: %.4f\n", bookName, distance)
        }
    }
}

向量搜索流程图:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  用户查询    │ ──→ │ BGE-M3 向量化│ ──→ │ Milvus 搜索 │ ──→ Top K 结果
│ "学Python"  │     │ (1024 维)   │     │ (ANN 算法)  │
└─────────────┘     └─────────────┘     └─────────────┘
步骤 7:带过滤条件的搜索
// step7SearchWithFilter 带标量过滤的向量搜索
func step7SearchWithFilter(ctx context.Context, c client.Client) {
    queryText := "深度学习神经网络"
    queryEmb, _ := getEmbedding(queryText)
    queryVector := []entity.Vector{entity.FloatVector(queryEmb)}
    sp, _ := entity.NewIndexIvfFlatSearchParam(16)
    
    // 过滤表达式:只在 AI 类书籍中搜索
    filterExpr := `category == "AI"`
    
    results, _ := c.Search(
        ctx,
        CollectionName,
        nil,
        filterExpr,  // 关键:添加过滤表达式
        []string{"book_name", "author", "category"},
        queryVector,
        "embedding",
        entity.L2,
        5,
        sp,
    )
}

常用过滤表达式:

// 精确匹配
`category == "AI"`

// 多值匹配
`category in ["AI", "编程", "数据库"]`

// 数值比较
`book_id > 100`
`price >= 50 && price <= 100`

// 字符串前缀匹配
`book_name like "Python%"`

// 组合条件
`category == "AI" && price < 100`
`(category == "AI" || category == "编程") && in_stock == true`
步骤 8:标量查询(Query)
// step8QueryData 标量查询(不涉及向量)
func step8QueryData(ctx context.Context, c client.Client) {
    // Query 类似 SQL SELECT ... WHERE
    results, _ := c.Query(
        ctx,
        CollectionName,
        nil,                                   // partitions
        `category == "数据库"`,                // 过滤表达式
        []string{"book_id", "book_name", "author"},  // 返回字段
    )
    
    // 解析结果
    idCol := results.GetColumn("book_id")
    nameCol := results.GetColumn("book_name")
    for i := 0; i < idCol.Len(); i++ {
        id, _ := idCol.GetAsInt64(i)
        name, _ := nameCol.GetAsString(i)
        fmt.Printf("ID: %d, 书名: %s\n", id, name)
    }
}
步骤 9:删除数据
// step9DeleteData 按条件删除数据
func step9DeleteData(ctx context.Context, c client.Client) {
    // 删除所有运维类书籍
    err := c.Delete(ctx, CollectionName, "", `category == "运维"`)
}
步骤 10:释放资源
// step10Cleanup 释放资源
func step10Cleanup(ctx context.Context, c client.Client) {
    // 从内存释放(数据仍在磁盘)
    c.ReleaseCollection(ctx, CollectionName)
    
    // 可选:删除整个 Collection
    // c.DropCollection(ctx, CollectionName)
    
    // 关闭连接
    c.Close()
}

二、article/article.go - 文章向量库示例

article/article.go 演示了两种文章向量化场景:

场景说明向量化方式
短篇文章≤ 2000 字的文章整体向量化:标题 + 摘要 + 内容 → 1 个向量
长篇文章> 2000 字的文章分块向量化:按 500 字分块 + 50 字重叠 → N 个向量
功能概览
┌─────────────────────────────────────────────────────────────────────────────┐
│                     article.go 功能架构                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │                    短篇文章向量库 (article_embeddings)               │  │
│   │                                                                     │  │
│   │   • 12 篇示例文章(技术、生活、科学、历史、文化)                    │  │
│   │   • 整体向量化:标题 + 摘要 + 内容 → 1024 维向量                    │  │
│   │   • 支持语义搜索、分类过滤搜索                                      │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │               长篇文章分块向量库 (article_chunks_embeddings)          │  │
│   │                                                                     │  │
│   │   • 1 篇 2000+ 字长文章(Go 语言完整教程)                          │  │
│   │   • 分块向量化:500 字/块 + 50 字重叠                               │  │
│   │   • 每块带原文章 UUID,支持结果溯源                                 │  │
│   │   • 支持块级语义搜索                                                │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
短篇文章 Collection Schema
schema := &entity.Schema{
    CollectionName: "article_embeddings",
    Fields: []*entity.Field{
        {Name: "article_id", DataType: entity.FieldTypeInt64, PrimaryKey: true, AutoID: true},
        {Name: "title", DataType: entity.FieldTypeVarChar, TypeParams: map[string]string{"max_length": "256"}},
        {Name: "author", DataType: entity.FieldTypeVarChar, TypeParams: map[string]string{"max_length": "64"}},
        {Name: "category", DataType: entity.FieldTypeVarChar, TypeParams: map[string]string{"max_length": "32"}},
        {Name: "tags", DataType: entity.FieldTypeVarChar, TypeParams: map[string]string{"max_length": "256"}},
        {Name: "summary", DataType: entity.FieldTypeVarChar, TypeParams: map[string]string{"max_length": "512"}},
        {Name: "content", DataType: entity.FieldTypeVarChar, TypeParams: map[string]string{"max_length": "4096"}},
        {Name: "create_time", DataType: entity.FieldTypeVarChar, TypeParams: map[string]string{"max_length": "32"}},
        {Name: "publish_time", DataType: entity.FieldTypeVarChar, TypeParams: map[string]string{"max_length": "32"}},
        {Name: "embedding", DataType: entity.FieldTypeFloatVector, TypeParams: map[string]string{"dim": "1024"}},
    },
}
长篇文章分块 Collection Schema
schema := &entity.Schema{
    CollectionName: "article_chunks_embeddings",
    Fields: []*entity.Field{
        {Name: "chunk_id", DataType: entity.FieldTypeInt64, PrimaryKey: true, AutoID: true},
        {Name: "article_uuid", DataType: entity.FieldTypeVarChar, TypeParams: map[string]string{"max_length": "64"}},
        {Name: "title", DataType: entity.FieldTypeVarChar, TypeParams: map[string]string{"max_length": "256"}},
        {Name: "chunk_index", DataType: entity.FieldTypeInt32},   // 块索引:0, 1, 2, ...
        {Name: "chunk_total", DataType: entity.FieldTypeInt32},   // 总块数
        {Name: "chunk_content", DataType: entity.FieldTypeVarChar, TypeParams: map[string]string{"max_length": "2048"}},
        {Name: "embedding", DataType: entity.FieldTypeFloatVector, TypeParams: map[string]string{"dim": "1024"}},
    },
}
分块算法实现
// ChunkConfig 分块配置
type ChunkConfig struct {
    ChunkSize    int // 每块目标大小(字符数)
    ChunkOverlap int // 重叠大小(字符数)
}

// DefaultChunkConfig 推荐配置
var DefaultChunkConfig = ChunkConfig{
    ChunkSize:    500, // 每块约 500 字
    ChunkOverlap: 50,  // 重叠 50 字(10%)
}

// splitToChunks 将长文本拆分为多个块
func splitToChunks(content string, config ChunkConfig) []string {
    runes := []rune(content)
    var chunks []string

    for i := 0; i < len(runes); {
        end := i + config.ChunkSize
        if end > len(runes) {
            end = len(runes)
        }

        chunk := string(runes[i:end])
        chunks = append(chunks, chunk)

        // 下一块起始位置 = 当前位置 + 块大小 - 重叠大小
        i += config.ChunkSize - config.ChunkOverlap

        if end == len(runes) {
            break
        }
    }

    return chunks
}
长篇文章插入流程
func insertLongArticle(ctx context.Context, c client.Client, article LongArticle) {
    // 1. 生成文章 UUID
    articleUUID := uuid.New().String()

    // 2. 将内容分块
    chunks := splitToChunks(article.Content, DefaultChunkConfig)
    chunkTotal := len(chunks)

    // 3. 为每个块生成向量并插入
    for i, chunk := range chunks {
        // 每个块带上标题信息,增强语义关联
        textForEmbedding := fmt.Sprintf("%s - 第%d部分: %s", article.Title, i+1, chunk)
        embedding, _ := getArticleEmbedding(textForEmbedding)

        // 插入到分块 Collection
        // article_uuid: 用于关联同一篇文章的所有块
        // chunk_index: 块索引,用于排序
        // chunk_total: 总块数,用于判断完整性
    }
}
分块搜索与结果合并
// 搜索时返回块级结果
results, _ := c.Search(ctx, "article_chunks_embeddings", ...)

// 根据 article_uuid 可以:
// 1. 获取匹配块的上下文(前后块)
// 2. 聚合同一文章的多个匹配块
// 3. 定位到原文的具体位置

Go SDK API 参考

连接管理

// 创建连接
c, err := client.NewClient(ctx, client.Config{
    Address:  "localhost:19530",
    Username: "root",      // 可选
    Password: "Milvus",    // 可选
})

// 关闭连接
c.Close()

Collection 操作

// 列出所有 Collection
collections, err := c.ListCollections(ctx)

// 检查是否存在
has, err := c.HasCollection(ctx, "name")

// 获取信息
collection, err := c.DescribeCollection(ctx, "name")

// 获取数据量
stats, err := c.GetCollectionStatistics(ctx, "name")

// 删除
err := c.DropCollection(ctx, "name")

数据操作

// 插入
result, err := c.Insert(ctx, collectionName, partitionName, columns...)

// 刷新到磁盘
err := c.Flush(ctx, collectionName, false)

// 删除
err := c.Delete(ctx, collectionName, partitionName, expr)

索引操作

// 创建不同类型的索引
idxFlat, _ := entity.NewIndexFlat(entity.L2)
idxIvf, _ := entity.NewIndexIvfFlat(entity.L2, 128)
idxHnsw, _ := entity.NewIndexHNSW(entity.L2, 16, 256)

// 创建索引
err := c.CreateIndex(ctx, collectionName, fieldName, index, false)

// 获取索引信息
indexes, err := c.DescribeIndex(ctx, collectionName, fieldName)

// 删除索引
err := c.DropIndex(ctx, collectionName, fieldName)

搜索参数

// IVF 类索引
sp, _ := entity.NewIndexIvfFlatSearchParam(nprobe)

// HNSW 索引
sp, _ := entity.NewIndexHNSWSearchParam(ef)

// FLAT 索引(无参数)
sp, _ := entity.NewIndexFlatSearchParam()

常用操作示例

批量插入大数据

// 分批插入,每批 10000 条
batchSize := 10000
for i := 0; i < totalCount; i += batchSize {
    end := i + batchSize
    if end > totalCount {
        end = totalCount
    }
    
    // 准备这一批的数据...
    c.Insert(ctx, collectionName, "", columns...)
}
c.Flush(ctx, collectionName, false)

分区操作

// 创建分区
err := c.CreatePartition(ctx, collectionName, "2024")

// 插入到指定分区
c.Insert(ctx, collectionName, "2024", columns...)

// 在指定分区搜索
c.Search(ctx, collectionName, []string{"2024"}, "", ...)

// 删除分区
c.DropPartition(ctx, collectionName, "2024")

进阶:重排序技术

在实际应用中,可以在向量搜索后添加**重排序(Reranking)**步骤,进一步提升搜索精度。

什么是重排序

重排序是一种两阶段检索(Two-Stage Retrieval)架构:

┌─────────────────────────────────────────────────────────────────────────┐
│                         两阶段检索流程                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ┌─────────────┐                                                       │
│   │  用户查询    │                                                       │
│   │ "学Python"  │                                                       │
│   └──────┬──────┘                                                       │
│          │                                                              │
│          ▼                                                              │
│   ┌─────────────────────────────────────────────────────────┐          │
│   │             第一阶段:向量召回(Retrieval)               │          │
│   │                                                         │          │
│   │   1. 使用 BGE-M3 将查询转换为 1024 维向量               │          │
│   │   2. 在 Milvus 中执行 ANN 搜索                          │          │
│   │   3. 返回 Top 10 候选结果                               │          │
│   │                                                         │          │
│   │   特点:速度快(毫秒级),可处理百万级数据               │          │
│   └───────────────────────┬─────────────────────────────────┘          │
│                           │                                             │
│                           ▼                                             │
│   ┌─────────────────────────────────────────────────────────┐          │
│   │             第二阶段:重排序(Reranking)                 │          │
│   │                                                         │          │
│   │   1. 对 Top 10 候选重新计算相关性分数                   │          │
│   │   2. 按相关性分数重新排序                               │          │
│   │   3. 返回最终 Top 5 结果                                │          │
│   │                                                         │          │
│   │   特点:精度更高,提升搜索质量                          │          │
│   └───────────────────────┬─────────────────────────────────┘          │
│                           │                                             │
│                           ▼                                             │
│   ┌─────────────────────────────────────────────────────────┐          │
│   │                     最终结果                             │          │
│   └─────────────────────────────────────────────────────────┘          │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
为什么需要两阶段?
对比项仅向量搜索向量搜索 + 重排序
召回量直接返回 Top 5召回 Top 10 → 精排 Top 5
精度较高更高(提升 5-15%)
速度最快略慢(增加重排序时间)
适用场景对精度要求不高RAG、问答系统等对精度敏感场景

重排序算法介绍

BM25 算法

BM25(Best Matching 25)是一个经典的文本相关性排序算法,无需模型,纯算法实现。

特性说明
🚀 无需模型纯数学公式,无需下载任何模型
速度极快微秒级计算,无 API 调用开销
🏆 经典可靠被 Elasticsearch、Lucene 等采用
🔄 互补效果与向量搜索形成语义+关键词双重匹配

BM25 算法原理:

BM25 公式:
score(D, Q) = Σ IDF(qi) × (f(qi,D) × (k1+1)) / (f(qi,D) + k1×(1-b+b×|D|/avgdl))

其中:
• Q: 查询,由多个词 q1, q2, ... 组成
• D: 文档
• f(qi, D): 词 qi 在文档 D 中的出现频率(词频 TF)
• |D|: 文档 D 的长度(词数)
• avgdl: 所有文档的平均长度
• k1, b: 可调参数(通常 k1=1.5, b=0.75)
• IDF(qi): 逆文档频率,衡量词的重要性

Go 实现示例:

// BM25Parameters BM25 算法参数
type BM25Parameters struct {
    K1 float64 // 词频饱和参数,通常取 1.2-2.0
    B  float64 // 文档长度归一化参数,通常取 0.75
}

// calculateBM25Score 计算单个文档的 BM25 分数
func calculateBM25Score(queryTokens, docTokens []string, idf map[string]float64, 
                        avgDocLen float64, params BM25Parameters) float64 {
    score := 0.0
    docLen := float64(len(docTokens))
    
    // 统计文档中每个词的频率
    termFreq := make(map[string]int)
    for _, token := range docTokens {
        termFreq[token]++
    }
    
    // 计算每个查询词的贡献
    for _, queryToken := range queryTokens {
        idfScore := idf[queryToken]
        tf := float64(termFreq[queryToken])
        if tf == 0 {
            continue
        }
        
        // BM25 公式
        numerator := tf * (params.K1 + 1)
        denominator := tf + params.K1*(1-params.B+params.B*docLen/avgDocLen)
        score += idfScore * numerator / denominator
    }
    
    return score
}

重排序模型介绍

Cross-Encoder 重排序模型

与 BM25 算法不同,重排序模型使用深度学习来计算查询和文档的相关性。

模型说明模型大小
bge-reranker-v2-m3BAAI 开发的多语言重排序模型~1.1GB
Qwen3-Reranker阿里云开发的重排序模型系列0.6B-8B
cohere-rerankCohere 的商业重排序 API-
BM25 vs 重排序模型
对比项BM25 算法Reranker 模型
依赖无需额外模型需要下载模型(~1GB)
速度极快(微秒级)较慢(需模型推理)
资源几乎不占资源需要 CPU/GPU 算力
效果关键词匹配准确语义理解更深
适用场景通用场景对语义要求极高的场景
Ollama 调用重排序模型示例
// 使用 Ollama 调用 Cross-Encoder 重排序模型
func rerankWithModel(query string, docs []string) ([]float64, error) {
    scores := make([]float64, len(docs))
    
    for i, doc := range docs {
        // 构造 prompt
        prompt := fmt.Sprintf("Query: %s\nDocument: %s\nRelevance:", query, doc)
        
        // 调用 Ollama API
        resp, err := http.Post(
            "http://localhost:11434/api/generate",
            "application/json",
            bytes.NewBuffer([]byte(fmt.Sprintf(`{
                "model": "qllama/bge-reranker-v2-m3",
                "prompt": "%s",
                "stream": false
            }`, prompt))),
        )
        if err != nil {
            return nil, err
        }
        defer resp.Body.Close()
        
        // 解析响应获取相关性分数...
        scores[i] = parseScore(resp)
    }
    
    return scores, nil
}

💡 提示:本教程代码中未包含重排序功能,如需使用可参考上述示例自行实现。


进阶主题

1. 切换向量模型

// 使用 OpenAI
import "github.com/sashabaranov/go-openai"

client := openai.NewClient("your-api-key")
resp, _ := client.CreateEmbeddings(ctx, openai.EmbeddingRequest{
    Model: openai.SmallEmbedding3,
    Input: []string{"要转换的文本"},
})
embedding := resp.Data[0].Embedding

// 注意:需要将 VectorDim 改为 1536

2. 连接池与并发

// SDK 内部已实现连接池,支持并发操作
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        c.Search(ctx, ...)
    }()
}
wg.Wait()

3. 错误处理最佳实践

result, err := c.Search(ctx, ...)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        // 超时处理
    }
    log.Printf("搜索失败: %v", err)
    return
}

文章向量化策略

在将文章写入向量库时,需要根据文章长度选择合适的向量化策略。

策略选择标准

┌─────────────────────────────────────────────────────────────────────────────┐
│                         文章向量化策略决策流程                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│                        ┌─────────────────┐                                 │
│                        │   文章总长度     │                                 │
│                        │ (标题+摘要+内容) │                                 │
│                        └────────┬────────┘                                 │
│                                 │                                           │
│              ┌──────────────────┼──────────────────┐                       │
│              │                  │                  │                       │
│              ▼                  ▼                  ▼                       │
│     ┌────────────────┐  ┌────────────────┐  ┌────────────────┐            │
│     │   ≤ 512 字     │  │ 512 ~ 2000 字  │  │   > 2000 字    │            │
│     │   (短文章)      │  │   (中等长度)    │  │   (长文章)     │            │
│     └───────┬────────┘  └───────┬────────┘  └───────┬────────┘            │
│             │                   │                   │                      │
│             ▼                   ▼                   ▼                      │
│     ┌────────────────┐  ┌────────────────┐  ┌────────────────┐            │
│     │  整体向量化 ✓   │  │  整体向量化 ✓   │  │  分块向量化 ✓   │            │
│     │  (不拆分)       │  │  (可选拆分)     │  │  (必须拆分)     │            │
│     │                │  │                │  │                │            │
│     │  1 篇 = 1 向量  │  │  1 篇 = 1 向量  │  │ 1 篇 = N 向量   │            │
│     └────────────────┘  └────────────────┘  └────────────────┘            │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

长度阈值说明

文章长度策略说明适用场景
≤ 512 字整体向量化直接将 标题 + 摘要 + 内容 合并生成一个向量新闻摘要、产品描述、FAQ
512 ~ 2000 字整体向量化(推荐)BGE-M3 支持最长 8192 tokens,2000 字完全可以处理博客文章、技术文档、知识库
> 2000 字分块向量化按段落或固定长度切分,每块独立向量化论文、书籍章节、长篇报告

💡 为什么是 512 和 2000 字?

  • 512 字:大多数向量模型的最佳输入长度,语义表达最完整
  • 2000 字:考虑到中文约 1.5-2 tokens/字,2000 字约 3000-4000 tokens,在 BGE-M3 的 8192 限制内留有余量

策略一:整体向量化(本示例采用)

适用于短篇文章(≤ 2000 字)

// 将 标题 + 摘要 + 内容 合并为一个文本,生成单一向量
textForEmbedding := fmt.Sprintf("%s %s %s", article.Title, article.Summary, article.Content)
embedding, err := getArticleEmbedding(textForEmbedding)

优点:

  • ✅ 实现简单,每篇文章一条记录
  • ✅ 搜索结果直接是完整文章
  • ✅ 无需处理分块边界问题

缺点:

  • ⚠️ 长文章语义信息可能被稀释
  • ⚠️ 无法定位到文章中的具体段落

策略二:分块向量化(Chunking)

适用于长篇文章(> 2000 字)

分块方法对比
分块方法说明优点缺点
固定长度分块按字符数切分(如每 500 字)实现简单可能切断语义
段落分块按段落(换行符)切分保持语义完整段落长度不均
句子分块按句子切分后组合语义边界清晰实现复杂
重叠分块相邻块有重叠部分避免边界信息丢失存储冗余
语义分块使用 NLP 识别语义边界效果最好计算成本高
推荐:重叠分块实现
// ChunkConfig 分块配置
type ChunkConfig struct {
    ChunkSize    int // 每块目标大小(字符数)
    ChunkOverlap int // 重叠大小(字符数)
}

// 推荐配置
var DefaultChunkConfig = ChunkConfig{
    ChunkSize:    500, // 每块约 500 字
    ChunkOverlap: 50,  // 重叠 50 字(10%)
}

// splitArticleToChunks 将长文章拆分为多个块
func splitArticleToChunks(content string, config ChunkConfig) []string {
    runes := []rune(content)
    var chunks []string
    
    for i := 0; i < len(runes); {
        end := i + config.ChunkSize
        if end > len(runes) {
            end = len(runes)
        }
        
        chunk := string(runes[i:end])
        chunks = append(chunks, chunk)
        
        // 下一块起始位置 = 当前位置 + 块大小 - 重叠大小
        i += config.ChunkSize - config.ChunkOverlap
        
        if end == len(runes) {
            break
        }
    }
    
    return chunks
}

// 使用示例
func insertLongArticle(article Article) {
    // 判断是否需要分块
    totalLen := len([]rune(article.Title + article.Summary + article.Content))
    
    if totalLen <= 2000 {
        // 短文章:整体向量化
        text := fmt.Sprintf("%s %s %s", article.Title, article.Summary, article.Content)
        embedding := getArticleEmbedding(text)
        // 插入单条记录...
    } else {
        // 长文章:分块向量化
        chunks := splitArticleToChunks(article.Content, DefaultChunkConfig)
        
        for i, chunk := range chunks {
            // 每个块都带上标题信息,增强语义关联
            text := fmt.Sprintf("%s - 第%d部分: %s", article.Title, i+1, chunk)
            embedding := getArticleEmbedding(text)
            // 插入每个块,记录 chunk_index...
        }
    }
}
分块后的数据结构

对于长文章分块,建议扩展 Collection Schema:

// 分块文章的 Schema 字段
{
    Name:     "chunk_index",
    DataType: entity.FieldTypeInt32,
    // 块索引:0, 1, 2, ...
},
{
    Name:     "chunk_total",
    DataType: entity.FieldTypeInt32,
    // 总块数
},
{
    Name:     "article_uuid",
    DataType: entity.FieldTypeVarChar,
    TypeParams: map[string]string{"max_length": "64"},
    // 原文章唯一标识,用于关联同一篇文章的所有块
},

策略选择总结

┌─────────────────────────────────────────────────────────────────────────────┐
│                           向量化策略最佳实践                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   📌 短文章(≤ 512 字)                                                      │
│      └─→ 整体向量化,简单高效                                                │
│                                                                             │
│   📌 中等文章(512 ~ 2000 字)                                               │
│      └─→ 整体向量化(推荐),BGE-M3 完全能处理                               │
│      └─→ 如需精准定位段落,可选择分块                                        │
│                                                                             │
│   📌 长文章(> 2000 字)                                                     │
│      └─→ 必须分块向量化                                                      │
│      └─→ 推荐 500 字/块 + 50 字重叠                                          │
│      └─→ 每块带上标题信息增强关联                                            │
│                                                                             │
│   📌 特殊场景                                                                │
│      └─→ 问答系统:分块 + 按相关性返回具体段落                               │
│      └─→ 文章推荐:整体向量化 + 返回完整文章                                 │
│      └─→ 知识库检索:分块 + 支持上下文扩展                                   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
场景推荐策略块大小重叠
新闻/摘要整体向量化--
博客文章整体向量化--
技术文档分块(可选)500 字50 字
论文/书籍分块(必须)500 字100 字
FAQ 问答整体向量化--
法律合同分块 + 段落保持按段落1-2 句

Milvus 高可用架构

Milvus 通过分布式架构设计多重容错机制来保证高可用性。

四层架构分离

Milvus 采用分层架构,各组件职责分离,减少单点故障:

┌─────────────────────────────────────────────────────────────────────────────┐
│                           Milvus 分布式架构                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │                      访问层 (Access Layer)                           │  │
│   │   ┌─────────┐  ┌─────────┐  ┌─────────┐                            │  │
│   │   │  Proxy  │  │  Proxy  │  │  Proxy  │  ← 无状态,水平扩展         │  │
│   │   └────┬────┘  └────┬────┘  └────┬────┘                            │  │
│   └────────┼────────────┼────────────┼──────────────────────────────────┘  │
│            │            │            │                                      │
│   ┌────────┴────────────┴────────────┴──────────────────────────────────┐  │
│   │                    协调者层 (Coordinator)                            │  │
│   │   ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐           │  │
│   │   │RootCoord │  │QueryCoord│  │DataCoord │  │IndexCoord│           │  │
│   │   │ (Active) │  │ (Active) │  │ (Active) │  │ (Active) │           │  │
│   │   │ Standby  │  │ Standby  │  │ Standby  │  │ Standby  │           │  │
│   │   └────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘           │  │
│   └────────┼─────────────┼─────────────┼─────────────┼──────────────────┘  │
│            │             │             │             │                      │
│   ┌────────┴─────────────┴─────────────┴─────────────┴──────────────────┐  │
│   │                    工作节点层 (Worker Nodes)                         │  │
│   │   ┌───────────┐      ┌───────────┐      ┌───────────┐              │  │
│   │   │ QueryNode │      │ DataNode  │      │ IndexNode │              │  │
│   │   │   x N     │      │    x N    │      │    x N    │              │  │
│   │   │ (搜索查询) │      │ (数据写入) │      │ (索引构建) │              │  │
│   │   └─────┬─────┘      └─────┬─────┘      └─────┬─────┘              │  │
│   └─────────┼──────────────────┼──────────────────┼─────────────────────┘  │
│             │                  │                  │                        │
│   ┌─────────┴──────────────────┴──────────────────┴─────────────────────┐  │
│   │                       存储层 (Storage)                               │  │
│   │   ┌─────────────┐  ┌─────────────┐  ┌─────────────┐                │  │
│   │   │    etcd     │  │    MinIO    │  │   Pulsar    │                │  │
│   │   │  (元数据)    │  │ (对象存储)   │  │  (消息队列)  │                │  │
│   │   │  3+ 副本     │  │  分布式冗余  │  │   WAL 日志   │                │  │
│   │   └─────────────┘  └─────────────┘  └─────────────┘                │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
层级组件职责HA 特性
访问层Proxy请求入口、验证、结果汇聚无状态,水平扩展
协调者层Coordinator集群拓扑、任务调度、元数据管理Active-Standby 模式
工作节点层DataNode / QueryNode / IndexNode数据处理、搜索、索引构建无状态,水平扩展
存储层etcd / MinIO / Pulsar元数据 / 对象存储 / 日志分布式冗余存储

工作节点详解

QueryNode(查询节点)

职责:执行向量搜索和标量查询

特性说明
核心功能向量相似度搜索(ANN)、标量查询
数据来源从对象存储加载 Segment 到内存
状态有状态(内存中缓存数据)
扩展性水平扩展,更多节点 = 更大吞吐量
DataNode(数据节点)

职责:处理数据写入和持久化

特性说明
核心功能数据插入、更新、删除、持久化
工作方式订阅消息队列,消费写入日志
输出Segment 文件(存储到 MinIO/S3)
状态局部状态,可水平扩展
IndexNode(索引节点)

职责:构建向量索引

特性说明
核心功能构建向量索引(IVF_FLAT、HNSW 等)
触发时机数据持久化后,由 IndexCoord 调度
输出索引文件(加速搜索)
状态无状态,任务完成即释放
三者协作流程
┌─────────────────────────────────────────────────────────────────────────────┐
│                           数据生命周期流程                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   1. 插入数据                                                               │
│   ┌──────────┐    ┌──────────┐    ┌───────────┐    ┌──────────┐           │
│   │  Insert  │ →  │  Proxy   │ →  │ DataNode  │ →  │  MinIO   │           │
│   │   请求   │    │  路由    │    │ 持久化    │    │ Segment  │           │
│   └──────────┘    └──────────┘    └───────────┘    └──────────┘           │
│                                                                             │
│   2. 创建索引                                                               │
│   ┌──────────┐    ┌───────────┐    ┌───────────┐    ┌──────────┐          │
│   │ Segment  │ →  │IndexCoord │ →  │ IndexNode │ →  │  MinIO   │          │
│   │  文件    │    │  调度     │    │ 构建索引  │    │ Index文件│          │
│   └──────────┘    └───────────┘    └───────────┘    └──────────┘          │
│                                                                             │
│   3. 加载到内存                                                             │
│   ┌──────────┐    ┌───────────┐    ┌───────────┐                          │
│   │  Load    │ →  │QueryCoord │ →  │ QueryNode │  (Segment + Index)       │
│   │   请求   │    │  调度     │    │ 加载内存  │                          │
│   └──────────┘    └───────────┘    └───────────┘                          │
│                                                                             │
│   4. 执行搜索                                                               │
│   ┌──────────┐    ┌──────────┐    ┌───────────┐    ┌──────────┐           │
│   │  Search  │ →  │  Proxy   │ →  │ QueryNode │ →  │  结果    │           │
│   │   请求   │    │  路由    │    │ ANN 搜索  │    │  返回    │           │
│   └──────────┘    └──────────┘    └───────────┘    └──────────┘           │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

协调者高可用:Active-Standby 模式

协调者是集群的"大脑",Milvus 2.2.3+ 引入了 Active-Standby 机制:

┌─────────────────────────────────────────────────────────────────────────────┐
│                     Coordinator Active-Standby 机制                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│                        ┌─────────────────────────┐                         │
│                        │      etcd (租约机制)     │                         │
│                        │    ┌───────────────┐    │                         │
│                        │    │   Lease 租约   │    │                         │
│                        │    └───────┬───────┘    │                         │
│                        └────────────┼────────────┘                         │
│                                     │                                       │
│                    ┌────────────────┼────────────────┐                     │
│                    ▼                                 ▼                     │
│           ┌──────────────────┐             ┌──────────────────┐           │
│           │   Coordinator    │             │   Coordinator    │           │
│           │    (Active) ✓    │             │   (Standby) ⏳   │           │
│           │                  │             │                  │           │
│           │  • 处理请求       │             │  • 监听租约状态   │           │
│           │  • 更新元数据     │             │  • 等待接管       │           │
│           │  • 任务调度       │             │                  │           │
│           └────────┬─────────┘             └────────┬─────────┘           │
│                    │                                │                      │
│                    │     Active 失效                │                      │
│                    │ ─────────────────────────────► │                      │
│                    │                                │                      │
│                    │                       ┌────────┴─────────┐           │
│                    │                       │   Coordinator    │           │
│                    ▼                       │  (New Active) ✓  │           │
│           ┌──────────────────┐             │                  │           │
│           │    (Failed) ✗    │             │  • 从 etcd 加载   │           │
│           └──────────────────┘             │    最新元数据     │           │
│                                            │  • 接管服务       │           │
│                                            └──────────────────┘           │
│                                                                             │
│   切换时间 ≈ etcd lease timeout(默认 60 秒)                              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
  • 多个 Coordinator 实例同时运行,仅一个为 Active
  • 使用 etcd 租约(lease)机制进行选主
  • Active 失效后,Standby 自动接管(切换时间约 60 秒)

元数据与日志保障

组件作用HA 机制
etcd存储元数据、Schema、节点状态分布式一致性,强一致读写,建议 3+ 副本
WAL (Write Ahead Log)所有写操作先写日志故障恢复时重放日志,支持 Pulsar/Kafka
对象存储持久化向量数据、索引文件MinIO/S3/Azure Blob 自带冗余

故障恢复时间

故障类型恢复机制预计恢复时间
Proxy/QueryNode 故障K8s Pod 重启几秒 ~ 几十秒
Coordinator 故障Active-Standby 切换~60 秒(etcd 租约超时)
etcd 单节点故障Raft 自动选主几秒
存储故障依赖 MinIO/S3 冗余取决于存储系统

生产环境高可用部署

推荐使用 Kubernetes + Milvus Operator 部署:

# Milvus 高可用集群配置示例
apiVersion: milvus.io/v1beta1
kind: Milvus
metadata:
  name: milvus-cluster
spec:
  mode: cluster
  
  # 依赖组件配置
  dependencies:
    etcd:
      inCluster:
        values:
          replicaCount: 3           # etcd 3 副本
          persistence:
            enabled: true
            storageClass: "fast-ssd"
    
    storage:
      inCluster:
        values:
          mode: distributed         # MinIO 分布式模式
          replicas: 4
    
    pulsar:
      inCluster:
        values:
          components:
            autorecovery: true
  
  # Milvus 组件配置
  components:
    # 访问层
    proxy:
      replicas: 2                   # Proxy 2 副本
      resources:
        requests:
          cpu: "1"
          memory: "2Gi"
    
    # 工作节点
    queryNode:
      replicas: 3                   # QueryNode 3 副本
      resources:
        requests:
          cpu: "2"
          memory: "8Gi"
    
    dataNode:
      replicas: 2
    
    indexNode:
      replicas: 2
    
    # 协调者(开启 Active-Standby)
    rootCoord:
      replicas: 2                   # 1 Active + 1 Standby
    
    queryCoord:
      replicas: 2
    
    dataCoord:
      replicas: 2
    
    indexCoord:
      replicas: 2

高可用最佳实践

实践项建议说明
etcd 副本数≥ 3保证元数据存储可靠性
Coordinator 副本≥ 2启用 Active-Standby
QueryNode 副本≥ 2查询高可用
对象存储使用云存储或分布式 MinIO数据持久化可靠性
监控告警配置 Prometheus + Grafana及时发现问题
定期备份使用 Milvus Backup 工具灾难恢复

高可用设计总结

┌─────────────────────────────────────────────────────────────────────────────┐
│                        Milvus 高可用核心设计                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌─────────────────┐                                                       │
│   │  1. 分层解耦     │  各层独立扩展,互不影响                               │
│   └─────────────────┘                                                       │
│                                                                             │
│   ┌─────────────────┐                                                       │
│   │  2. 无状态设计   │  Proxy/DataNode/IndexNode 可随意扩缩容               │
│   └─────────────────┘                                                       │
│                                                                             │
│   ┌─────────────────┐                                                       │
│   │  3. Active-Standby │  协调者故障自动切换                                │
│   └─────────────────┘                                                       │
│                                                                             │
│   ┌─────────────────┐                                                       │
│   │  4. WAL + etcd  │  数据一致性和故障恢复保障                              │
│   └─────────────────┘                                                       │
│                                                                             │
│   ┌─────────────────┐                                                       │
│   │  5. 分布式存储   │  etcd、MinIO 多副本冗余                               │
│   └─────────────────┘                                                       │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

参考资源

Milvus

BGE 模型系列

Ollama


许可证

MIT License

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐