Milvus快速入门以及用 Java 操作 Milvus

目录

Milvus 核心概念

向量数据库

  • 可以把 Milvus 理解成一个专门处理"向量"这种特殊数据的数据库。传统数据库(如 MySQL)擅长处理数字、文字这类结构化数据,并按精确条件查询(比如 WHERE age > 18)。而向量数据库的核心是相似性搜索,它通过计算向量之间的"距离"或"相似度",来找到最相近的结果。
  • "向量"在这里,通常是由各种 AI 模型(如 BERT、ResNet)从文本、图片、音频等非结构化数据中提取出的特征表示。这些向量本身是一串数字,在高维空间中代表原始数据的意义。

核心概念

  • 集合 (Collection) :这是 Milvus 中最顶层的概念,类似于关系型数据库中的"表"。一个 Collection 用来存储具有相同结构的实体。
  • 字段 (Field)​​:定义了 Collection 的结构,类似于表中的"列"。Milvus 支持多种数据类型,但对于向量搜索,最重要的就是 FloatVector 类型的字段。
  • 向量维度 (Dimension)​​:这是向量的一个关键属性。它定义了向量中数字的个数。例如,一个由 BERT-base 模型生成的句子向量可能是 768 维,这就意味着它是一个包含 768 个浮点数的数组。在定义向量字段时,必须指定其维度。
  • 索引 (Index) :为了在亿级数据中快速搜索,Milvus 不会进行暴力比对,而是为向量数据构建索引。不同的索引类型在构建速度、内存占用、查询速度和精度之间有不同的权衡,常见的索引类型有:
​​索引类型 说明
FLAT 暴力搜索,精度100%但速度慢,适合小数据集或验证结果
​​IVF_FLAT​​ 先通过聚类将数据分桶(nlist 参数决定桶数),搜索时只查询最近的几个桶(nprobe 参数控制),大大提升速度
​​HNSW​​ 基于图论的索引,适合高精度、低延迟的搜索场景,但内存消耗较高
  • 度量类型(Metric Type)​​:定义了计算向量之间距离或相似度的方式。常见的有:
​​度量类型 说明
​​L2​​ (欧氏距离) 距离越小,向量越相似。
​​IP​​ (内积) 内积越大,向量越相似。
​​​​COSINE​​ (余弦相似度) 忽略向量大小,只比较方向,相似度值越接近 1 越相似。这在自然语言处理中非常常用

Milvus 的基本工作流程

上面这个图是Milvus的核心系统架构图,和mysql、pg等数据库的系统架构很像,其核心工作流程可以概括为以下几个步骤:

  1. 连接 Milvus​​:你的应用程序通过 Milvus 提供的 SDK(如 Python 或 Java SDK)连接到 Milvus 服务。
  2. 创建集合:定义一个 Collection,并指定它的字段结构(例如,一个主键字段 id ,一个向量字段 embedding和对应的原始数据字段,一般一个Collection最少要包含这三个字段)。
  3. 插入数据 :将你的数据(向量和相关的元数据,包括原始数据)插入到 Collection 中。
  4. 创建索引:在向量字段上构建索引(例如 IVF_FLAT)。这一步至关重要,只有在构建索引后,才能进行高效的向量搜索。
  5. 加载集合​​:将 Collection 从存储层加载到内存中,以便执行查询。
  6. 向量搜索​​ :提供一个查询向量,让 Milvus 在 Collection 中搜索最相似的向量,并返回结果,值得注意的是,一般情况下返回的是最相似向量对应的原始数据或者自定义的元数据,而不是返回embedding
  7. 下面这个流程图是Milvus 的核心工作流程图,很清晰地展示了 Milvus 如何通过向量查找内容的工作过程

用 Java 操作 Milvus

可以总结为,在boot项目中将Milvus客户端bean注入IOC容器,以便后续通过客户端对象操作Milvus服务器,包括创建集合、插入数据、创建索引、搜索向量等。主要是核心代码片段,有boot项目经验的很容易掌握。

引入依赖

xml 复制代码
<dependency>
    <groupId>io.milvus</groupId>
    <artifactId>milvus-sdk-java</artifactId>
    <version>2.4.4</version> <!-- 请查看GitHub更新最新版本 -->
</dependency>

springBoot中注入Milvus客户端bean

java 复制代码
@Configuration
public class MilvusConfig {
    @Value("${milvus.host}")
    private String host;

    @Value("${milvus.port}")
    private Integer port;

    @Bean
    public MilvusServiceClient milvusServiceClient() {
        ConnectParam connectParam = ConnectParam.newBuilder()
                .withHost(host)
                .withPort(port)
                .build();
        return new MilvusServiceClient(connectParam);
    }
}

创建集合

  • 一般三个字段,主键字段,embedding字段,元数据(原始数据)字段
java 复制代码
import io.milvus.param.collection.FieldType;
import io.milvus.param.collection.CreateCollectionParam;
import io.milvus.param.collection.FieldType;
import static io.milvus.param.Constant.*;

// 定义主键id字段
FieldType idField = FieldType.newBuilder()
        .withName("id")
        .withDataType(DataType.Int64)
        .withPrimaryKey(true)
        .withAutoID(false) // 是否由Milvus自动生成ID
        .build();
// 定义embedding字段
FieldType vectorField = FieldType.newBuilder()
        .withName("embedding")
        .withDataType(DataType.FloatVector)
        .withDimension(768) // 重要:指定向量维度,必须与你生成的向量一致
        .build();
// 定义字段:原始文本字段(用于绑定)
FieldType textField = FieldType.newBuilder()
        .withName("original_text")
        .withDataType(DataType.VarChar)
        .withMaxLength(65535) // 设置足够长度存储文本
        .build();

// 构建创建集合的请求
CreateCollectionParam createCollectionParam = CreateCollectionParam.newBuilder()
        .withCollectionName("my_book_collection") // 集合名称
        .withDescription("Test collection for book vectors")
        .addFieldType(idField)
        .addFieldType(vectorField)
        .addFieldType(textField) // 将原始文本字段加入Schema
        .build();

// 执行创建
milvusClient.createCollection(createCollectionParam);
System.out.println("Collection created successfully!");

插入数据

插入数据包含主键字段、向量数组、和对应的原始文本数组​​,并确保它们的顺序一致,这样每条向量就与它对应的文本通过相同的数组索引关联起来了

java 复制代码
import io.milvus.param.collection.InsertParam;
import java.util.Arrays;
import java.util.List;

// 准备数据。假设我们有3本书的向量和ID
List<Long> ids = Arrays.asList(1L, 2L);
List<List<Float>> vectors = Arrays.asList(
    Arrays.asList(0.12f, 0.23f, 0.45f, ...), // 768维的向量
    Arrays.asList(0.56f, 0.67f, 0.78f, ...),
);
// 对应的原始文本列表,顺序必须与vectors一致!
List<String> originalTexts = Arrays.asList(
    "这是第一段文本的原始内容", // 对应vectors中的第一个向量
    "这是第二段文本的原始内容"  // 对应vectors中的第二个向量
);

// 构建插入请求
InsertParam insertParam = InsertParam.newBuilder()
        .withCollectionName("my_book_collection")
        .addField("id", ids)
        .addField("embedding", vectors)
        .addField("original_text", originalTexts) // 插入原始文本
        .build();

// 执行插入
milvusClient.insert(insertParam);
System.out.println("Data inserted successfully!");

创建索引

java 复制代码
import io.milvus.param.index.CreateIndexParam;
import io.milvus.param.index.IndexType;
import io.milvus.param.index.MetricType;

CreateIndexParam createIndexParam = CreateIndexParam.newBuilder()
        .withCollectionName("my_book_collection")
        .withFieldName("embedding") // 在哪个向量字段上建索引
        .withIndexType(IndexType.IVF_FLAT) // 选择索引类型
        .withMetricType(MetricType.COSINE) // 选择度量类型
        .withExtraParam("{\"nlist\":1024}") // 索引参数,nlist是聚类中心数
        .build();

milvusClient.createIndex(createIndexParam);
System.out.println("Index created successfully!");

检索并获取原始数据

java 复制代码
import io.milvus.param.dml.SearchParam;
import io.milvus.response.SearchResultsWrapper;
import java.util.Collections;

// 1. 将查询语句转换为向量(省略模型推理步骤)
List<Float> queryVector = Arrays.asList(0.34f, 0.25f, 0.67f, ...);

// 2. 构建搜索参数,特别指定要返回的字段
SearchParam searchParam = SearchParam.newBuilder()
        .withCollectionName("your_collection_name")// 指定要搜索的集合
        .withVectorFieldName("embedding")// 指定要搜索的向量字段名
        // 传入查询向量,将查询向量包装成一个列表传入。Milvus 支持一次性传入多个查询向量进行批量搜索,这里只用了一个
        .withVectors(Collections.singletonList(queryVector))
        .withTopK(5) // 返回最相似的5个结果
        .withMetricType(MetricType.COSINE)// 设置相似度度量类型(如COSINE, L2)
        .addOutField("original_text") // 指定要返回的元数据字段:原始文本
        .addOutField("id") // 指定要返回的元数据字段:主键ID
        .build();

// 3. 执行搜索
SearchResultsWrapper results = milvusClient.search(searchParam).getData();

// 4. 处理结果:提取ID和原始文本
//results.getRowCount(0): 获取第一个(索引为 0)查询向量的返回结果数量(应该等于或小于 topK)
for (int i = 0; i < results.getRowCount(0); i++) {
    // 获取原始文本
    String originalText = results.getFieldData(0, i, "original_text", String.class);
    // 获取对应的向量ID
    Long id = results.getFieldData(0, i, "id", Long.class);
    
    System.out.println("ID: " + id + ", Original Text: " + originalText);
}

工程建议

  1. 索引选择​​:如果你的数据量巨大(十亿级)且对查询速度要求极高,可以考虑 HNSW 索引。如果对内存敏感,可以考虑量化索引如 IVF_PQ
  2. 参数调优:nprobe(对于 IVF 系列索引)和 ef(对于 HNSW 索引)等参数对搜索性能和精度有直接影响。通常需要在你的数据集上进行实验,找到平衡点
  3. 硬件加速:Milvus 支持 GPU 加速索引构建和查询,如果你的环境有 GPU,可以显著提升性能
  4. 官方文档 https://milvus.io/docs
相关推荐
失散134 小时前
分布式专题——2 深入理解Redis线程模型
java·数据库·redis·分布式·架构
彭于晏Yan5 小时前
Spring Boot中策略模式结合依赖注入的实现方式
spring boot·策略模式
王伯安呢5 小时前
Java开发环境配置入门指南
java·开发语言·jvm·eclipse·环境搭建·新手
计算机毕业设计木哥5 小时前
Python毕业设计推荐:基于Django的饮食计划推荐与交流分享平台 饮食健康系统 健康食谱计划系统
开发语言·hadoop·spring boot·后端·python·django·课程设计
rockmelodies5 小时前
Java安全体系深度研究:技术演进与攻防实践
java·开发语言·安全
风象南5 小时前
SpringBoot 实现无痕调试注入器,线上问题定位的新利器
spring boot·后端
代码栈上的思考6 小时前
深入解析 Java 内存可见性问题:从现象到 volatile 解决方案
java·开发语言
切糕师学AI6 小时前
如何建立针对 .NET Core web 程序的线程池的长期监控
java·前端·.netcore
零千叶7 小时前
【面试】AI大模型应用原理面试题
java·设计模式·面试