Milvus系列之01、Spring boot快速集成Milvus

文章目录

前言

博主介绍:✌目前全网粉丝4W+,csdn博客专家、Java领域优质创作者,博客之星、阿里云平台优质作者、专注于Java后端技术领域。

涵盖技术内容:Java后端、大数据、算法、分布式微服务、中间件、前端、运维等。

博主所有博客文件目录索引:博客目录索引(持续更新)

CSDN搜索:长路

视频平台:b站-Coder长路

前置准备

搭建milvus向量数据库。

milvus官方文档:https://milvus.io/docs/zh/schema.md#Collection-schema

知识点介绍

为什么搜索前要加载集合?即下面loadCollection 方法的含义

  1. 作用
    • 将指定的 Milvus 集合从磁盘加载到内存中
    • 这是 Milvus 的一个优化操作,可以显著提高搜索和查询性能
  2. 为什么需要
    • Milvus 默认情况下不会将所有数据常驻内存
    • 当执行搜索操作前加载集合,可以避免每次搜索时的磁盘I/O开销
    • 特别适合高频查询场景
  3. 注意事项
    • 加载集合会占用内存资源
    • 对于大型集合,可能需要较多内存
    • 如果内存不足,可能会导致性能下降或OOM错误
    • 可以使用 releaseCollection 方法释放内存
  4. 最佳实践
    • 在应用启动时加载常用集合
    • 对于不常使用的集合,可以在需要时加载,使用后释放
    • 监控内存使用情况

Milvus数据库

与传统数据库引擎类似,您也可以在 Milvus 中创建数据库,并为某些用户分配管理这些数据库的权限。然后,这些用户就有权管理数据库中的 Collections。一个 Milvus 集群最多支持 64 个数据库。

默认的是default数据库:

Collection集合

在 Milvus 中,可创建多个 Collections 来管理数据,并将数据作为实体插入到 Collections 中。Collections 和实体类似于关系数据库中的表和表数据。

如下进入到指定db中之后,即可查看到collection集合:

Schema 字段模式

字段模式是字段的逻辑定义。在定义集合模式管理集合之前,首先需要定义它。Milvus 只支持在一个 Collection 中使用一个主键字段。

字段模式属性如下:

向量索引

利用存储在索引文件中的元数据,Milvus 以专门的结构组织数据,便于在搜索或查询过程中快速检索所需的信息。

Milvus 提供多种索引类型和指标,可对字段值进行排序,以实现高效的相似性搜索。

下表列出了不同向量字段类型所支持的索引类型和度量。目前,Milvus 支持各种类型的向量数据,包括浮点嵌入(通常称为浮点向量或密集向量)、二进制嵌入(也称为二进制向量)和稀疏嵌入(也称为稀疏向量)。

度量类型说明:

1)L2(欧氏距离, Euclidean Distance):

  • 计算两个向量之间的直线距离。
  • 适用于那些希望找到与查询向量在空间中物理距离最近的点的场景。
  • 当你的数据经过了归一化处理时,L2可能不是最佳选择,因为此时余弦相似度可能是更合适的度量。IP(内积, Inner Product):

2)计算两个向量的内积。

  • 常用于向量已经过归一化处理的情况,此时内积的值与余弦
  • 相似度成正比。
  • 适合需要衡量向量方向相似性而非大小差异的应用场景。

3)COSINE(余弦相似度, Cosine Similarity):

  • 计算两个向量夹角的余弦值。
  • 特别适用于比较文档、词汇等高维度稀疏数据,因为它关注的是向量的方向而非幅度。
  • 在文本挖掘、信息检索等领域非常有用,因为它能够忽略向量长度的影响,专注于方向上的相似性。

选择哪种度量类型主要取决于你的应用需求和数据特点。例如,在图像检索或推荐系统中,可能会根据具体情况选择L2或IP作为度量类型;而在文本分析中,余弦相似度往往是首选。理解你所处理的数据性质以及想要解决的问题,对于选择正确的度量类型至关重要。此外,不同度量类型的性能也可能因具体实现和优化而异,因此在实际部署前进行适当的测试是很重要的。

索引类型说明:

1)平面(FLAT):

  • 不使用任何索引结构,提供精确搜索结果。
  • 适用于数据量较小或对检索精度要求极高的情况。

2)IVF_FLAT:

  • 倒排文件(Inverted File)结构的基础版本,通过聚类将向量分配到不同的桶中以加速搜索。
  • 适合需要在大规模数据集中进行高效搜索但可以接受一定精度损失的情况。

3)IVF_SQ8:

  • 在IVF的基础上采用标量量化(Scalar Quantization),将浮点数压缩为8位整数,减少存储空间并加快查询速度,但会带来一定的精度损失。
  • 适用于希望节省存储空间且对搜索速度有较高要求的场景。

4)IVF_PQ:

  • 使用乘积量化(Product Quantization)进一步压缩向量表示,非常适合处理非常大的数据集。
  • 对于那些资源受限环境下的应用来说是个很好的选择,如移动设备、边缘计算等。

5)GPU_IVF_FLAT:

  • 利用GPU加速的IVF_FLAT索引类型,可显著提升构建索引和查询的速度。
  • 适用于拥有高性能GPU硬件资源,并需要快速处理大规模数据集的场合。

6)GPU_IVF_PQ:

  • 结合了GPU加速与乘积量化技术,提供了更高的性能优势,特别是在处理超大规模数据时。
  • 同样需要具备相应的GPU硬件支持,适用于对速度和效率有极高要求的应用。

7)HNSW (Hierarchical Navigable Small World graphs):

  • 基于层次化可导航小世界网络的索引结构,能够提供快速而准确的最近邻搜索。
  • 非常适合实时性要求高、需要快速响应的在线服务和应用场景。

8)DISKANN:

  • 一种专门设计用于处理超大规模数据集的索引类型,能够在磁盘上有效地存储和搜索向量数据。
  • 适用于数据量极大以至于无法完全加载进内存的情况,旨在平衡查询效率与存储成本。

选择合适的索引类型主要取决于你的具体需求,包括数据规模、查询速度要求、存储限制以及是否愿意接受一定程度的查询精度损失等因素。根据不同的应用场景,可能需要进行适当的测试和调优来确定最佳配置。


实战SpringBoot快速集成Milvus

1、引入pom.xml

首先是版本依赖管理:

xml 复制代码
<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- Spring AI -->
    <spring-ai.version>1.0.0</spring-ai.version>
    <!-- Spring Boot -->
    <spring-boot.version>3.4.0</spring-boot.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>${spring-ai.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

接着是具体引入的依赖:

xml 复制代码
<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <knife4j.version>4.3.0</knife4j.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-milvus-store</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-ollama</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>2.0.57</version>
    </dependency>
    <!-- 前后端分离中的后端接口测试工具 -->
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
        <version>${knife4j.version}</version>
    </dependency>
</dependencies>

2、基础配置引入(包含milvus、ollama向量模型配置、聊天配置)

applicaiton.yaml配置:

yaml 复制代码
server:
  port: 8080

# Milvus数据库配置
milvus:
  host: 127.0.0.1
  port: 19530

spring:
  # ollama相关配置
  ai:
    ollama:
      base-url: http://localhost:11434
      # 聊天配置
      chat:
        options:
          model: llama3.1:8b
          temperature: 0.7
          num-g-p-u: 1
      # 向量模型配置
      embedding:
        options:
          model: bge-m3:latest

3、MilvusConfig配置类实现

配置两个客户端bean实例,分别是MilvusServiceClient、MilvusClientV2,原因是Spring AI 集成了较早的Milvus,导致在Spring AI与Milvus交互时需要使用旧版的客户端,而MilvusClientV2是Milvus当前推荐使用的,包含一些较新的API

java 复制代码
/**
 * @description  Milvus向量数据库配置
 * @author changlu
 * @date 2025/6/6 21:57
 */
@Configuration
public class MilvusConfig {
 
    @Value("${milvus.host}")
    private String host;
 
    @Value("${milvus.port}")
    private Integer port;
 
    @Bean
    public MilvusClientV2 milvusClientV2() {
        String uri = "http://" + this.host + ":" + this.port.toString();
        return new MilvusClientV2(ConnectConfig.builder().uri(uri).build());
    }
 
    @Bean
    public MilvusServiceClient milvusServiceClient() {
        ConnectParam connectParam = ConnectParam.newBuilder()
                .withHost(host)
                .withPort(port)
                .build();
        return new MilvusServiceClient(connectParam);
    }
}

4、MilvusArchiveConstant.java:Milvus常量配置类

提前准备常量类来设置mivlus相关常量:实现私有化知识库,针对集合里设计了5个字段,分别是ID、特征向量、原始文本、上传的文件名、原始文本的元数据。

java 复制代码
/**
 * @description  Milvus 向量数据库业务参数
 * @author changlu
 * @date 2025/6/6 21:57
 */
public class MilvusArchiveConstant {
 
    /**
     * 向量数据库名称
     */
    public static final String DB_NAME = "default";
 
    /**
     * 集合名称
     */
    public static final String COLLECTION_NAME = "rag_collection";
 
    /**
     * 分片数量
     */
    public static final int SHARDS_NUM = 1;
 
    /**
     * 分区数量
     */
    public static final int PARTITION_NUM = 1;
 
    /**
     * 特征向量维度
     */
    public static final Integer FEATURE_DIM = 1024;
 
    /**
     * 字段
     */
    public static class Field {
 
        /**
         * id
         */
        public static final String ID = "id";
 
        /**
         * 文本特征向量
         */
        public static final String FEATURE = "feature";
 
        /**
         * 文本
         */
        public static final String TEXT = "text";
 
        /**
         * 文件名
         */
        public static final String FILE_NAME = "file_name";
 
        /**
         * 元数据
         */
        public static final String METADATA = "metadata";
    }
}

5、MilvusService:向量服务接口 & 实现类

java 复制代码
/**
 * @author changlu
 * @des Milvus服务接口
 * @date 2025/2/25 下午3:09
 */
public interface MilvusService {
    /**
     * 检查集合是否存在,不存在则创建集合
     */
    Boolean hasCollection();

    /**
     * 插入数据
     *
     * @param vectorParam 向量参数
     * @param text        文本
     * @param metadata    元数据
     * @param fileName    文件名
     */
    InsertResp insert(float[] vectorParam, String text, String metadata, String fileName);

    /**
     * 搜索数据
     *
     * @param vectorParam 向量参数
     */
    SearchResp search(float[] vectorParam);

}

MilvusServiceImpl.java:向量实现业务类

其中主要实现了核心两个方法:insert、search

  • insert:为插入指定的文档
  • search:则是根据关键字来进行向量数据库检索。
java 复制代码
/**
 * @description  MilvusServiceImpl 向量业务实现类
 * @author changlu
 * @date 2025/6/7 14:45
 */
@Service
public class MilvusServiceImpl implements MilvusService {

    @Autowired
    private MilvusClientV2 milvusClientV2;

    @Autowired
    private OllamaEmbeddingModel ollamaEmbeddingModel;
 
    /**
     * 检查集合是否存在
     */
    @Override
    public Boolean hasCollection() {
        Boolean b = milvusClientV2.hasCollection(HasCollectionReq.builder().collectionName(MilvusArchiveConstant.COLLECTION_NAME).build());
        if (!b) {
            this.createCollection();
        }
        return b;
    }

    /**
     * 插入数据
     *  vectorParam:向量特征
     *  text:文本内容
     *  metadata:原始文本元数据
     */
    @Override
    public InsertResp insert(float[] vectorParam, String text, String metadata, String fileName) {
        // 校验集合是否存在
        this.hasCollection();
        JsonObject jsonObject = new JsonObject();
        // 数组转换成JsonElement
        jsonObject.add(MilvusArchiveConstant.Field.FEATURE, new Gson().toJsonTree(vectorParam));
        jsonObject.add(MilvusArchiveConstant.Field.TEXT, new Gson().toJsonTree(text));
        jsonObject.add(MilvusArchiveConstant.Field.METADATA, new Gson().toJsonTree(metadata));
        jsonObject.add(MilvusArchiveConstant.Field.FILE_NAME, new Gson().toJsonTree(fileName));
        InsertReq insertReq = InsertReq.builder()
                // 集合名称
                .collectionName(MilvusArchiveConstant.COLLECTION_NAME)
                .data(Collections.singletonList(jsonObject))
                .build();

        return milvusClientV2.insert(insertReq);
    }
 
    /**
     * 创建集合
     */
    public void createCollection() {
        // 创建字段
        CreateCollectionReq.CollectionSchema schema = milvusClientV2.createSchema()
                // 创建主键字段
                .addField(AddFieldReq.builder()
                        // 字段名
                        .fieldName(MilvusArchiveConstant.Field.ID)
                        // 字段描述
                        .description("主键ID")
                        // 字段类型
                        .dataType(DataType.Int64)
                        // 是否为主键
                        .isPrimaryKey(true)
                        // 设置主键自增
                        .autoID(true)
                        .build())
                .addField(AddFieldReq.builder()
                        // 字段名
                        .fieldName(MilvusArchiveConstant.Field.FILE_NAME)
                        // 字段描述
                        .description("文件名")
                        // 字段类型
                        .dataType(DataType.VarChar)
                        // 设置字段为可空
                        .isNullable(true)
                        .build())
                // 创建特征向量字段
                .addField(AddFieldReq.builder()
                        // 字段名
                        .fieldName(MilvusArchiveConstant.Field.FEATURE)
                        // 字段描述
                        .description("特征向量")
                        // 字段类型
                        .dataType(DataType.FloatVector)
                        // 设置向量维度
                        .dimension(MilvusArchiveConstant.FEATURE_DIM)
                        .build())
                .addField(AddFieldReq.builder()
                        // 字段名
                        .fieldName(MilvusArchiveConstant.Field.TEXT)
                        // 字段描述
                        .description("文本")
                        // 字段类型
                        .dataType(DataType.VarChar)
                        // 设置字段为可空
                        .isNullable(true)
                        .build())
                .addField(AddFieldReq.builder()
                        // 字段名
                        .fieldName(MilvusArchiveConstant.Field.METADATA)
                        // 字段描述
                        .description("元数据")
                        // 字段类型
                        .dataType(DataType.VarChar)
                        // 设置字段为可空
                        .isNullable(true)
                        .build());
        // 创建集合
        CreateCollectionReq collectionReq = CreateCollectionReq.builder()
                // 集合名称
                .collectionName(MilvusArchiveConstant.COLLECTION_NAME)
                // 集合描述
                .description("自定义知识库")
                // 集合字段
                .collectionSchema(schema)
                // 分片数量
                .numShards(MilvusArchiveConstant.SHARDS_NUM)
                .build();
        milvusClientV2.createCollection(collectionReq);
 
        // 创建索引,针对向量来实现索引
        IndexParam indexParam = IndexParam.builder()
                // 索引字段名
                .fieldName(MilvusArchiveConstant.Field.FEATURE)
                // 索引类型
                .indexType(IndexParam.IndexType.IVF_FLAT)
                // 索引距离度量
                .metricType(IndexParam.MetricType.COSINE)
                .build();
        CreateIndexReq createIndexReq = CreateIndexReq.builder()
                .collectionName(MilvusArchiveConstant.COLLECTION_NAME)
                .indexParams(Collections.singletonList(indexParam))
                .build();
 
        milvusClientV2.createIndex(createIndexReq);
    }

    /**
     * 搜索数据
     *
     * @param vectorParam 向量参数
     */
    @Override
    public SearchResp search(float[] vectorParam) {
        this.loadCollection();
        FloatVec floatVec = new FloatVec(vectorParam);
        SearchReq searchReq = SearchReq.builder()
                // 集合名称
                .collectionName(MilvusArchiveConstant.COLLECTION_NAME)
                // 搜索距离度量
                .metricType(IndexParam.MetricType.COSINE)
                // 搜索向量
                .data(Collections.singletonList(floatVec))
                // 搜索字段
                .annsField(MilvusArchiveConstant.Field.FEATURE)
                // 返回字段
                .outputFields(Arrays.asList(MilvusArchiveConstant.Field.ID, MilvusArchiveConstant.Field.TEXT, MilvusArchiveConstant.Field.METADATA, MilvusArchiveConstant.Field.FILE_NAME))
                // 搜索数量
                .topK(2)
                .build();
        return milvusClientV2.search(searchReq);
    }

    /**
     * 将集合加载到内存中以提高搜索性能
     */
    private void loadCollection() {
        // 首先检查集合是否存在
        if (this.hasCollection()) {
            try {
                milvusClientV2.loadCollection(
                        LoadCollectionReq.builder()
                                .collectionName(MilvusArchiveConstant.COLLECTION_NAME)
                                .build()
                );
            } catch (Exception e) {
                // 处理加载失败的情况
                throw new RuntimeException("加载集合到内存失败: " + e.getMessage(), e);
            }
        } else {
            throw new RuntimeException("集合不存在: " + MilvusArchiveConstant.COLLECTION_NAME);
        }
    }

    /**
     * 从内存中释放集合
     */
    public void releaseCollection() {
        milvusClientV2.releaseCollection(
                ReleaseCollectionReq.builder()
                        .collectionName(MilvusArchiveConstant.COLLECTION_NAME)
                        .build()
        );
    }

}

6、实现控制器业务类

MilvusController.java:其中包含了两个接口,分别是insert插入以及查询接口

java 复制代码
/**
 * @description  向量数据库实践
 * @author changlu
 * @date 2025/6/6 22:05
 */
@RestController
@RequestMapping("/api/vector")
@Tag(name = "向量数据库实践", description = "向量数据库实践")
public class MilvusController {

    @Autowired
    private OllamaEmbeddingModel ollamaEmbeddingModel;

    @Autowired
    private MilvusService milvusService;
 
 
    @Operation(summary = "添加自定义rag数据")
    @GetMapping("/addCustomRagData")
    public ResponseEntity<InsertResp> addCustomRagData(@RequestParam String text) {
        Assert.notNull(text, "text不能为空");
        // 借助向量模型来完成文本的向量化
        float[] embed = ollamaEmbeddingModel.embed(text);
        // 插入数据
        InsertResp insert = milvusService.insert(embed, text, JSON.toJSONString(new HashMap<>().put("custom", text)), "custom");
        return ResponseEntity.ok(insert);
    }

    @Operation(summary = "搜索")
    @GetMapping("/search")
    public ResponseEntity<SearchResp> search(@RequestParam String text) {
        Assert.notNull(text, "text不能为空");
        float[] embed = ollamaEmbeddingModel.embed(text);
        // 搜索数据
        return ResponseEntity.ok(milvusService.search(embed));
    }
 
    
}

测试验证

我们在一开始引入了knife4j接口测试工具,启动服务之后,直接访问网址即可:http://localhost:8080/doc.html#/home

测试1:测试添加自定义rag数据:

返回为主键key:

测试2:检索信息

我们去检索milvus即可检索返回:

可以看到检索出来的有两条,各自有相应的一个评分,评分越高越匹配。

看下目前向量数据库中的数据:

说明:如果要实现知识库的搭建,单靠输入文本的方式还是满足不了需求,企业知识库会有大量的文档,我们需要将各类文档的内容提取出来,再进行分段分割来完成段落分割之后再上传到向量数据库。


参考文章

1\]. Spring AI应用:利用DeepSeek+嵌入模型+Milvus向量数据库实现检索增强生成--RAG应用(一)(超详细):https://blog.csdn.net/wanganui/article/details/145593847 ## 资料获取 大家点赞、收藏、关注、评论啦\~ 精彩专栏推荐订阅:在下方专栏👇🏻 * [长路-文章目录汇总(算法、后端Java、前端、运维技术导航)](https://blog.csdn.net/cl939974883/category_11568291.html?spm=1001.2014.3001.5482):博主所有博客导航索引汇总 * [开源项目Studio-Vue---校园工作室管理系统(含前后台,SpringBoot+Vue)](https://changlu.blog.csdn.net/article/details/125295334):博主个人独立项目,包含详细部署上线视频,已开源 * [学习与生活-专栏](https://blog.csdn.net/cl939974883/category_10700595.html):可以了解博主的学习历程 * [算法专栏](https://blog.csdn.net/cl939974883/category_11403550.html?spm=1001.2014.3001.5482):算法收录 更多博客与资料可查看👇🏻获取联系方式👇🏻,🍅文末获取开发资源及更多资源博客获取🍅

相关推荐
Dragon Wu1 天前
SpringCloud 多模块下引入独立bom模块的正确架构方案
java·spring boot·后端·spring cloud·架构·springboot
闻哥4 天前
Java虚拟机内存结构深度解析:从底层原理到实战调优
java·开发语言·jvm·python·面试·springboot
hrhcode5 天前
【Netty】五.ByteBuf内存管理深度剖析
java·后端·spring·springboot·netty
hrhcode5 天前
【Netty】三.ChannelPipeline与ChannelHandler责任链深度解析
java·后端·spring·springboot·netty
CCPC不拿奖不改名6 天前
虚拟机基础:在VMware WorkStation上安装Linux为容器化部署打基础
linux·运维·服务器·人工智能·milvus·知识库搭建·容器化部署
知识即是力量ol6 天前
口语八股——Spring 面试实战指南(二):事务管理篇、Spring MVC 篇、Spring Boot 篇、Bean生命周期篇
spring·面试·mvc·springboot·八股·事务管理·bean生命周期
小钻风33666 天前
Knife4j 文件上传 multipart/data 同时接受文件和对象,调试时上传文件失效
java·springboot·knife4j
堕落年代8 天前
SnailJob发布任务类型详解
springboot