Milvus 实战教程(Go 版本 + Ollama bge-m3 向量模型)

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 核心概念
go 复制代码
// ==================== 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 常量 说明 适用场景
L2 entity.L2 欧氏距离,越小越相似 通用场景(本教程使用)
IP entity.IP 内积,越大越相似 归一化向量
COSINE entity.COSINE 余弦相似度,越大越相似 文本相似度

2. Ollama 本地推理引擎

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

核心特点
特性 说明
🏠 本地运行 无需联网,数据不出本地
🚀 简单部署 一行命令拉取模型
💰 完全免费 无 API 调用费用
📦 模型丰富 支持 LLM、Embedding、Reranker 等
🔧 CPU 友好 无需 GPU 也能运行
API 接口
bash 复制代码
# 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 代码实现
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-M3 1024 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 2.2GB 多语言、效果最佳
bge-large-zh 1024 ⭐⭐⭐⭐⭐ ⭐⭐⭐ 1.3GB 中文专优
nomic-embed-text 768 ⭐⭐⭐ ⭐⭐⭐⭐ 550MB 轻量级
all-MiniLM-L6-v2 384 ⭐⭐ ⭐⭐⭐⭐ 80MB 超轻量

4. Attu 可视化管理界面

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

核心功能
功能 说明
📊 Collection 管理 创建、删除、查看 Collection
🔍 数据浏览 查看和搜索存储的数据
📈 索引管理 创建、删除索引
📝 Schema 查看 查看字段结构
🔧 性能监控 查看系统状态
访问地址


环境准备

1. 部署 Ollama 和模型

bash 复制代码
# 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

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:

启动服务:

bash 复制代码
docker-compose up -d

3. 验证部署

服务 地址 说明
Milvus localhost:19530 gRPC 接口
Attu http://localhost:18000 Web 管理界面
Ollama http://localhost:11434 API 接口

4. 安装 Go SDK

bash 复制代码
go get github.com/milvus-io/milvus-sdk-go/v2

快速开始

示例文件说明

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

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

运行基础教程

bash 复制代码
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

运行文章向量库示例

bash 复制代码
cd test/milvus-demo/article

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

配置说明

tutorial.go 配置:

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 配置:

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 连接 Milvus client.NewClient() 建立 gRPC 连接
2 创建 Collection c.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
go 复制代码
// 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
go 复制代码
// 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_id INT64 (PK) 主键,自动生成
book_name VARCHAR(256) 书名
author VARCHAR(128) 作者
category VARCHAR(64) 分类(用于过滤)
book_intro VARCHAR(1024) 简介(用于生成向量)
embedding VECTOR(1024) BGE-M3 向量
步骤 3:插入数据
go 复制代码
// 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:创建索引
go 复制代码
// 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 欧氏距离,越小越相似
nlist 128 聚类中心数量
步骤 5:加载到内存
go 复制代码
// step5LoadCollection 加载 Collection 到内存
func step5LoadCollection(ctx context.Context, c client.Client) {
    // 必须加载后才能搜索!
    err := c.LoadCollection(ctx, CollectionName, false)
}

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

步骤 6:向量搜索
go 复制代码
// 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:带过滤条件的搜索
go 复制代码
// 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,
    )
}

常用过滤表达式:

go 复制代码
// 精确匹配
`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)
go 复制代码
// 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:删除数据
go 复制代码
// step9DeleteData 按条件删除数据
func step9DeleteData(ctx context.Context, c client.Client) {
    // 删除所有运维类书籍
    err := c.Delete(ctx, CollectionName, "", `category == "运维"`)
}
步骤 10:释放资源
go 复制代码
// 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
go 复制代码
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
go 复制代码
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"}},
    },
}
分块算法实现
go 复制代码
// 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
}
长篇文章插入流程
go 复制代码
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: 总块数,用于判断完整性
    }
}
分块搜索与结果合并
go 复制代码
// 搜索时返回块级结果
results, _ := c.Search(ctx, "article_chunks_embeddings", ...)

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

Go SDK API 参考

连接管理

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

// 关闭连接
c.Close()

Collection 操作

go 复制代码
// 列出所有 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")

数据操作

go 复制代码
// 插入
result, err := c.Insert(ctx, collectionName, partitionName, columns...)

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

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

索引操作

go 复制代码
// 创建不同类型的索引
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)

搜索参数

go 复制代码
// IVF 类索引
sp, _ := entity.NewIndexIvfFlatSearchParam(nprobe)

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

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

常用操作示例

批量插入大数据

go 复制代码
// 分批插入,每批 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)

分区操作

go 复制代码
// 创建分区
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 实现示例:

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-m3 BAAI 开发的多语言重排序模型 ~1.1GB
Qwen3-Reranker 阿里云开发的重排序模型系列 0.6B-8B
cohere-rerank Cohere 的商业重排序 API -
BM25 vs 重排序模型
对比项 BM25 算法 Reranker 模型
依赖 无需额外模型 需要下载模型(~1GB)
速度 极快(微秒级) 较慢(需模型推理)
资源 几乎不占资源 需要 CPU/GPU 算力
效果 关键词匹配准确 语义理解更深
适用场景 通用场景 对语义要求极高的场景
Ollama 调用重排序模型示例
go 复制代码
// 使用 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. 切换向量模型

go 复制代码
// 使用 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. 连接池与并发

go 复制代码
// 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. 错误处理最佳实践

go 复制代码
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 字)

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

优点:

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

缺点:

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

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

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

分块方法对比
分块方法 说明 优点 缺点
固定长度分块 按字符数切分(如每 500 字) 实现简单 可能切断语义
段落分块 按段落(换行符)切分 保持语义完整 段落长度不均
句子分块 按句子切分后组合 语义边界清晰 实现复杂
重叠分块 相邻块有重叠部分 避免边界信息丢失 存储冗余
语义分块 使用 NLP 识别语义边界 效果最好 计算成本高
推荐:重叠分块实现
go 复制代码
// 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:

go 复制代码
// 分块文章的 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 部署:

yaml 复制代码
# 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

相关推荐
bing.shao1 小时前
Golang 在OPC领域的应用
开发语言·后端·golang
laplace01232 小时前
向量库 Qdrant + 图数据库Neo4j+Embedding阿里百炼text-embedding-v3
数据库·embedding·agent·neo4j
云边有个稻草人2 小时前
从痛点到落地:金仓时序数据库核心能力拆解
数据库·时序数据库·kingbasees·金仓数据库·数据库安全防护
霍格沃兹测试学院-小舟畅学2 小时前
Playwright数据库断言:测试前后数据验证
数据库·oracle
REDcker2 小时前
C86 架构详解
数据库·微服务·架构
世人万千丶2 小时前
Day 5: Flutter 框架 SQLite 数据库进阶 - 在跨端应用中构建结构化数据中心
数据库·学习·flutter·sqlite·harmonyos·鸿蒙·鸿蒙系统
源代码•宸2 小时前
大厂技术岗面试之一面(准备自我介绍、反问)
经验分享·后端·算法·面试·职场和发展·golang·反问
学编程的小程3 小时前
从“单模冲锋”到“多模共生”——2026 国产时序数据库新物种进化图谱
数据库·时序数据库
卓怡学长3 小时前
m111基于MVC的舞蹈网站的设计与实现
java·前端·数据库·spring boot·spring·mvc