LangChain4j Java AI 应用开发实战(十二):向量数据Chroma/Qdrant/Milvus实践对比

系列篇章💥

No. 文章
1 LangChain4j Java AI 应用开发实战(一):LangChain4j 快速入门指南
2 LangChain4j Java AI 应用开发实战(二):大模型参数调优实战:Temperature、TopP、MaxTokens 深度解析
3 LangChain4j Java AI 应用开发实战(三):多模态 AI 开发 - 图片理解与图像生成实战
4 LangChain4j Java AI 应用开发实战(四):提示词工程进阶 - 模板化与结构化 Prompt 设计
5 LangChain4j Java AI 应用开发实战(五):流式响应与对话记忆 - 提升用户体验的关键技术
6 LangChain4j Java AI 应用开发实战(六):声明式 AI Service - LangChain4j 的核心编程模型
7 LangChain4j Java AI 应用开发实战(七):结构化输出实战 - 从非结构化文本提取 POJO 对象
8 LangChain4j Java AI 应用开发实战(八):用户隔离与持久化记忆 - 企业级对话系统设计
9 LangChain4j Java AI 应用开发实战(九):Few-Shot Learning - 少样本提示提升模型准确率
10 LangChain4j Java AI 应用开发实战(十):Embedding 模型与文本分类语义向量化
11 LangChain4j Java AI 应用开发实战(十一):Function Calling 工具调用 - 让 AI 执行真实操作
12 LangChain4j Java AI 应用开发实战(十二):向量数据Chroma/Qdrant/Milvus实践对比

目录

  • 系列篇章💥
  • 前言
  • 一、向量数据库核心功能解析
  • [二、InMemory Embedding Store:快速原型的最佳选择](#二、InMemory Embedding Store:快速原型的最佳选择)
    • [2.1 核心特点](#2.1 核心特点)
    • [2.2 完整代码示例](#2.2 完整代码示例)
    • [2.3 Maven 依赖](#2.3 Maven 依赖)
    • [2.4 持久化技巧](#2.4 持久化技巧)
  • 三、Chroma:轻量级开源向量数据库
    • [3.1 核心特点](#3.1 核心特点)
    • [3.2 完整代码示例](#3.2 完整代码示例)
    • [3.3 Maven 依赖](#3.3 Maven 依赖)
    • [3.4 生产环境部署](#3.4 生产环境部署)
      • [方式 1:Docker 部署](#方式 1:Docker 部署)
      • [方式 2:Python 包部署](#方式 2:Python 包部署)
      • [方式 3:云服务(Chroma Cloud)](#方式 3:云服务(Chroma Cloud))
    • [3.5 元数据过滤示例](#3.5 元数据过滤示例)
  • [四、Qdrant:Rust 编写的高性能向量搜索引擎](#四、Qdrant:Rust 编写的高性能向量搜索引擎)
    • [4.1 核心特点](#4.1 核心特点)
    • [4.2 完整代码示例](#4.2 完整代码示例)
    • [4.3 Maven 依赖](#4.3 Maven 依赖)
    • [4.4 生产环境部署](#4.4 生产环境部署)
      • [方式 1:Docker 部署](#方式 1:Docker 部署)
      • [方式 2:Kubernetes 部署](#方式 2:Kubernetes 部署)
      • [方式 3:Qdrant Cloud(托管服务)](#方式 3:Qdrant Cloud(托管服务))
    • [4.5 高级过滤示例](#4.5 高级过滤示例)
  • 五、Milvus:企业级分布式向量数据库
    • [5.1 核心特点](#5.1 核心特点)
    • [5.2 完整代码示例](#5.2 完整代码示例)
    • [5.3 Maven 依赖](#5.3 Maven 依赖)
    • [5.4 生产环境部署](#5.4 生产环境部署)
      • [方式 1:Docker Compose 部署](#方式 1:Docker Compose 部署)
      • [方式 2:Kubernetes Helm 部署](#方式 2:Kubernetes Helm 部署)
      • [方式 3:Zilliz Cloud(托管服务)](#方式 3:Zilliz Cloud(托管服务))
    • [5.5 高级配置示例](#5.5 高级配置示例)
  • 六、四大向量数据库深度对比
    • [6.1 功能对比矩阵](#6.1 功能对比矩阵)
    • [6.2 性能基准测试](#6.2 性能基准测试)
    • [6.3 选型决策树](#6.3 选型决策树)
    • [6.4 典型应用场景推荐](#6.4 典型应用场景推荐)
      • [场景 1:个人知识库助手](#场景 1:个人知识库助手)
      • [场景 2:企业客服机器人](#场景 2:企业客服机器人)
      • [场景 3:电商平台商品搜索](#场景 3:电商平台商品搜索)
      • [场景 4:社交媒体内容推荐](#场景 4:社交媒体内容推荐)
      • [场景 5:金融风控系统](#场景 5:金融风控系统)
  • 七、常见问题与避坑指南
    • [❌ 问题 1:向量维度不匹配](#❌ 问题 1:向量维度不匹配)
    • [❌ 问题 2:相似度分数异常](#❌ 问题 2:相似度分数异常)
    • [❌ 问题 3:Testcontainers 启动失败](#❌ 问题 3:Testcontainers 启动失败)
    • [❌ 问题 4:Milvus 连接超时](#❌ 问题 4:Milvus 连接超时)
    • [❌ 问题 5:内存溢出(OOM)](#❌ 问题 5:内存溢出(OOM))
  • 八、性能优化最佳实践
    • [8.1 批量插入优化](#8.1 批量插入优化)
    • [8.2 索引参数调优](#8.2 索引参数调优)
    • [8.3 缓存热点查询](#8.3 缓存热点查询)
    • [8.4 分区与分片](#8.4 分区与分片)
  • 九、向量数据库的典型应用场景
    • [1. 企业知识库问答](#1. 企业知识库问答)
    • [2. 智能客服系统](#2. 智能客服系统)
    • [3. 电商商品搜索](#3. 电商商品搜索)
    • [4. 内容去重与抄袭检测](#4. 内容去重与抄袭检测)
    • [5. 图像/视频检索](#5. 图像/视频检索)
    • [6. 推荐系统](#6. 推荐系统)
  • 结语

前言

在前一篇文章中,我们学习了 Embedding 模型如何将文本转换为语义向量,并构建了基于内存的文本分类器。但当你需要将成千上万甚至上亿个向量持久化存储并实现毫秒级检索时,简单的内存存储就力不从心了。向量数据库(Vector Database)正是解决这一挑战的核心基础设施,它专门优化了高维向量的存储、索引和相似度搜索。

然而,面对市面上琳琅满目的向量数据库------轻量级的 Chroma、高性能的 Qdrant、分布式的 Milvus、托管服务的 Pinecone,很多开发者陷入了选择困难症:我应该选哪个?它们各有什么优劣?如何在我的项目中快速集成?

本文将带你从零开始掌握四大主流向量数据库的核心特性,通过 LangChain4j 提供的统一接口,演示 InMemory、Chroma、Qdrant、Milvus 四种方案的完整代码实现。你将学会:

  • 理解向量数据库的核心功能:索引算法、元数据过滤、相似度搜索策略
  • 掌握四种向量数据库的架构差异和性能特点
  • 通过 Testcontainers 快速搭建本地测试环境
  • 根据数据量、团队能力、预算评估做出合理选型
  • 执行性能基准测试,量化对比 QPS、延迟、召回率

准备好了吗?让我们开启向量数据库的世界!


一、向量数据库核心功能解析

1.1 为什么需要专门的向量数据库?

你可能会有疑问:为什么不直接用 MySQL 或 Redis 存储向量?

传统数据库的局限性

java 复制代码
// ❌ 传统方式:将向量存储在 MySQL 中
CREATE TABLE embeddings (
    id INT PRIMARY KEY,
    vector FLOAT[384],  // 384 维向量
    text TEXT
);

// 查询时需要计算所有向量的余弦相似度
SELECT * FROM embeddings 
ORDER BY cosine_similarity(vector, query_vector) DESC 
LIMIT 10;

// 问题:
// 1. 全表扫描,时间复杂度 O(N),百万级数据响应时间 > 10 秒
// 2. 无法利用 B+ 树索引(高维向量不适合传统索引)
// 3. 内存消耗巨大,容易OOM

向量数据库的优势

java 复制代码
// ✅ 向量数据库:使用 HNSW/IVF 等专用索引算法
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
    .queryEmbedding(queryVector)
    .maxResults(10)
    .build();

List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(request).matches();
// 响应时间:< 50ms(即使千万级数据)

核心优势

  • 近似最近邻搜索(ANN):通过牺牲少量精度换取百倍性能提升
  • 专用索引结构:HNSW、IVF、PQ 等算法优化高维向量检索
  • 内存优化:向量量化、压缩技术减少存储开销
  • 元数据过滤:支持向量相似度 + 属性条件的复合查询

1.2 向量数据库四大核心功能

(1)向量索引(Indexing)

索引是向量数据库的灵魂,决定了搜索速度和精度。常见索引类型:

索引类型 原理 适用场景 优缺点
HNSW 分层导航小世界图 通用场景,平衡速度与精度 ✅ 速度快、精度高 ❌ 内存占用大
IVF 倒排文件索引 大规模数据集 ✅ 内存效率高 ❌ 需要调优聚类数
PQ 乘积量化 超大规模(亿级) ✅ 极致压缩 ❌ 精度损失较大
Flat 暴力搜索 小规模数据或精度要求极高 ✅ 100% 准确 ❌ 速度慢

LangChain4j 中的体现

java 复制代码
// Milvus 自动使用 HNSW 索引
MilvusEmbeddingStore store = MilvusEmbeddingStore.builder()
    .indexType(IndexType.HNSW)  // 可自定义
    .metricType(MetricType.COSINE)
    .build();

(2)相似度搜索(Similarity Search)

向量数据库支持多种相似度度量方式:

java 复制代码
// 余弦相似度(Cosine Similarity):最常用,适合文本语义搜索
// 范围:[-1, 1],越接近 1 表示越相似
double cosineSimilarity = CosineSimilarity.between(embedding1, embedding2);

// 欧氏距离(Euclidean Distance):适合图像检索
// 范围:[0, +∞),越小表示越相似
double euclideanDistance = EuclideanDistance.between(embedding1, embedding2);

// 点积(Dot Product):适合归一化向量
double dotProduct = DotProduct.between(embedding1, embedding2);

选择建议

  • 文本语义搜索:优先使用余弦相似度
  • 图像检索:欧氏距离或余弦相似度均可
  • 推荐系统:点积(配合归一化)

(3)元数据过滤(Metadata Filtering)

实际应用中,你通常需要在特定范围内搜索向量:

java 复制代码
// 场景:只搜索"技术部"员工的简历,且相似度 > 0.8
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
    .queryEmbedding(queryVector)
    .filter("department == '技术部' AND similarity > 0.8")
    .maxResults(10)
    .build();

典型应用场景

  • 按用户 ID 隔离:user_id == '12345'
  • 按时间范围:created_at >= '2026-01-01'
  • 按标签过滤:tags CONTAINS 'Java'
  • 组合条件:category == 'AI' AND price < 100

(4)持久化与扩展性

特性 InMemory Chroma Qdrant Milvus
持久化 ❌ 需手动序列化 ✅ SQLite/文件 ✅ RocksDB ✅ MinIO + Etcd
分布式 ⚠️ 集群版收费 ✅ 原生支持
最大数据量 < 10 万 < 100 万 < 1000 万 > 10 亿
水平扩展 ⚠️ 复杂 ✅ 自动分片

二、InMemory Embedding Store:快速原型的最佳选择

2.1 核心特点

InMemoryEmbeddingStore 是 LangChain4j 提供的基于内存的向量存储实现,适合以下场景:

  • 开发调试:零配置,开箱即用
  • 原型验证:快速测试 RAG 流程
  • 小规模数据:< 10 万条向量
  • 离线环境:无需任何外部依赖

劣势

  • ❌ 数据易失:重启后数据丢失(需手动序列化)
  • ❌ 内存限制:受 JVM 堆内存约束
  • ❌ 单线程:不支持并发写入

2.2 完整代码示例

java 复制代码
package com.langchain4j;

import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;

import java.util.List;

public class InMemoryEmbeddingStoreExample {

    public static void main(String[] args) {
        // ==================== 1. 创建内存向量存储 ====================
        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();

        // ==================== 2. 创建本地嵌入模型 ====================
        EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();

        // ==================== 3. 添加文档到向量存储 ====================
        TextSegment segment1 = TextSegment.from("I like football.");
        Embedding embedding1 = embeddingModel.embed(segment1).content();
        embeddingStore.add(embedding1, segment1);

        TextSegment segment2 = TextSegment.from("The weather is good today.");
        Embedding embedding2 = embeddingModel.embed(segment2).content();
        embeddingStore.add(embedding2, segment2);

        // ==================== 4. 语义搜索 ====================
        Embedding queryEmbedding = embeddingModel.embed("What is your favourite sport?").content();
        
        EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
                .queryEmbedding(queryEmbedding)
                .maxResults(1)
                .build();
        
        List<EmbeddingMatch<TextSegment>> matches = embeddingStore.search(embeddingSearchRequest).matches();
        EmbeddingMatch<TextSegment> embeddingMatch = matches.get(0);

        // ==================== 5. 输出搜索结果 ====================
        System.out.println(embeddingMatch.score()); // 0.8144288515898701
        System.out.println(embeddingMatch.embedded().text()); // I like football.

        // ==================== 6. 持久化(可选)====================
        // 方式一:序列化为 JSON 字符串
        // String serializedStore = embeddingStore.serializeToJson();
        // InMemoryEmbeddingStore<TextSegment> deserializedStore = InMemoryEmbeddingStore.fromJson(serializedStore);

        // 方式二:序列化到本地文件
        // String filePath = "/home/me/embedding.store";
        // embeddingStore.serializeToFile(filePath);
        // InMemoryEmbeddingStore<TextSegment> deserializedStore = InMemoryEmbeddingStore.fromFile(filePath);
    }
}

2.3 Maven 依赖

xml 复制代码
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId>
    <version>1.14.0</version>
</dependency>

注意 :InMemoryEmbeddingStore 已内置在 langchain4j-core 中,无需额外依赖。

2.4 持久化技巧

虽然 InMemoryEmbeddingStore 默认不持久化,但你可以通过序列化实现数据保存:

java 复制代码
// 保存向量库到文件
embeddingStore.serializeToFile("/data/vectors/embedding.store");

// 应用启动时加载
InMemoryEmbeddingStore<TextSegment> store = InMemoryEmbeddingStore.fromFile("/data/vectors/embedding.store");

适用场景

  • 小型应用的简单持久化方案
  • 版本控制:将 .store 文件纳入 Git 管理
  • 备份恢复:定期拷贝文件到对象存储

三、Chroma:轻量级开源向量数据库

3.1 核心特点

Chroma 是一个专为 AI 应用设计的开源向量数据库,由 chroma.ai 团队开发。它的核心理念是 "简单至上"

优势

  • 极简 API:3 行代码即可启动
  • 多语言支持:Python、JavaScript、Java(通过 LangChain4j)
  • 灵活部署:支持内存模式、本地文件、Docker、云服务
  • 元数据过滤:支持丰富的查询语法
  • 活跃社区:GitHub 15k+ Stars

劣势

  • ❌ 单机架构:不支持分布式扩展
  • ❌ 性能瓶颈:百万级数据后检索速度下降
  • ❌ 企业功能缺失:缺少 RBAC、审计日志等

适用场景

  • 中小型项目(< 100 万向量)
  • 快速原型开发
  • 个人开发者或初创团队

3.2 完整代码示例

java 复制代码
import static dev.langchain4j.internal.Utils.randomUUID;
import static dev.langchain4j.store.embedding.chroma.ChromaApiVersion.V2;

import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.chroma.ChromaEmbeddingStore;
import java.util.List;
import org.testcontainers.chromadb.ChromaDBContainer;

public class ChromaEmbeddingStoreExample {

    public static void main(String[] args) {
        // 使用 Testcontainers 创建并启动一个临时的 ChromaDB 容器实例(版本 1.1.0)
        try (ChromaDBContainer chroma = new ChromaDBContainer("chromadb/chroma:1.1.0").withExposedPorts(8000)) {
            chroma.start();

            // 构建 Chroma 向量存储实例
            EmbeddingStore<TextSegment> embeddingStore = ChromaEmbeddingStore.builder()
                .apiVersion(V2)                    // 设置 Chroma API 版本为 V2
                .baseUrl(chroma.getEndpoint())     // 设置 ChromaDB 容器的访问地址
                .collectionName(randomUUID())      // 使用随机 UUID 作为集合名称
                .logRequests(true)                 // 启用请求日志记录
                .logResponses(true)                // 启用响应日志记录
                .build();

            // 创建本地嵌入模型
            EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();

            // 准备第一段文本:关于足球的内容
            TextSegment segment1 = TextSegment.from("I like football.");
            Embedding embedding1 = embeddingModel.embed(segment1).content();
            embeddingStore.add(embedding1, segment1);

            // 准备第二段文本:关于天气的内容
            TextSegment segment2 = TextSegment.from("The weather is good today.");
            Embedding embedding2 = embeddingModel.embed(segment2).content();
            embeddingStore.add(embedding2, segment2);

            // 对查询文本进行向量化
            Embedding queryEmbedding = embeddingModel.embed("What is your favourite sport?").content();
            
            // 构建向量搜索请求
            EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
                    .queryEmbedding(queryEmbedding)
                    .maxResults(1)
                    .build();
            
            // 执行向量相似度搜索
            List<EmbeddingMatch<TextSegment>> matches = embeddingStore.search(embeddingSearchRequest).matches();
            EmbeddingMatch<TextSegment> embeddingMatch = matches.get(0);

            // 输出匹配结果
            System.out.println(embeddingMatch.score()); // 0.8144288493114709
            System.out.println(embeddingMatch.embedded().text()); // I like football.

            chroma.stop();
        }
    }
}

3.3 Maven 依赖

xml 复制代码
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-bom</artifactId>
            <version>1.14.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!-- Chroma 向量存储 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-chroma</artifactId>
    </dependency>

    <!-- 本地嵌入模型 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId>
    </dependency>

    <!-- Testcontainers ChromaDB 模块(仅测试环境) -->
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>chromadb</artifactId>
        <version>1.21.3</version>
    </dependency>
</dependencies>

3.4 生产环境部署

方式 1:Docker 部署

bash 复制代码
docker run -d \
  --name chroma \
  -p 8000:8000 \
  -v /data/chroma:/chroma/chroma \
  chromadb/chroma:latest

方式 2:Python 包部署

bash 复制代码
pip install chromadb
chroma run --host 0.0.0.0 --port 8000

方式 3:云服务(Chroma Cloud)

java 复制代码
EmbeddingStore<TextSegment> embeddingStore = ChromaEmbeddingStore.builder()
    .baseUrl("https://your-cluster.chroma.dev")
    .apiKey("your-api-key")
    .build();

3.5 元数据过滤示例

java 复制代码
// 添加带元数据的文档
TextSegment segment = TextSegment.from("Java 编程思想");
Map<String, String> metadata = Map.of(
    "category", "技术",
    "language", "中文",
    "author", "Bruce Eckel"
);
segment.metadata().putAll(metadata);

embeddingStore.add(embedding, segment);

// 搜索时过滤
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
    .queryEmbedding(queryVector)
    .filter("category == '技术' AND language == '中文'")
    .maxResults(5)
    .build();

四、Qdrant:Rust 编写的高性能向量搜索引擎

4.1 核心特点

Qdrant 是一个用 Rust 编写的开源向量数据库,以 高性能易用性 著称。

优势

  • 极致性能:Rust 底层优化,单节点 QPS 可达 10k+
  • 官方 Java SDK:LangChain4j 集成度高
  • 丰富的过滤语法:支持复杂的条件查询
  • 混合搜索:向量相似度 + 关键词搜索
  • 云原生:支持 Kubernetes 部署
  • 开源友好:Apache 2.0 协议,无商业限制

劣势

  • ❌ 集群版收费:分布式功能需要企业版
  • ❌ 社区相对较小:相比 Milvus,中文资料较少
  • ❌ 学习曲线:高级功能配置较复杂

适用场景

  • 中大规模项目(100 万 - 1000 万向量)
  • 对性能有较高要求的场景
  • 需要复杂过滤条件的应用

4.2 完整代码示例

java 复制代码
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.qdrant.QdrantEmbeddingStore;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections;
import org.testcontainers.qdrant.QdrantContainer;

import java.util.List;
import java.util.concurrent.ExecutionException;

import static dev.langchain4j.internal.Utils.randomUUID;

public class QdrantEmbeddingStoreExample {

  private static int grpcPort = 6334;
  private static String collectionName = "langchain4j-" + randomUUID();
  private static Collections.Distance distance = Collections.Distance.Cosine;
  private static int dimension = 384;

  public static void main(String[] args) throws ExecutionException, InterruptedException {

    // ==================== 1. 启动 Qdrant 容器 ====================
    try (QdrantContainer qdrant = new QdrantContainer("qdrant/qdrant:latest")) {
      qdrant.start();

      // ==================== 2. 创建 LangChain4j 的 Qdrant 存储 ====================
      EmbeddingStore<TextSegment> embeddingStore =
              QdrantEmbeddingStore.builder()
                      .host(qdrant.getHost())
                      .port(qdrant.getMappedPort(grpcPort))
                      .collectionName(collectionName)
                      .build();

      // ==================== 3. 使用原生 Client 创建 Collection ====================
      QdrantClient client =
              new QdrantClient(
                      QdrantGrpcClient.newBuilder(qdrant.getHost(), qdrant.getMappedPort(grpcPort), false)
                              .build());

      // 异步创建 Collection,并阻塞等待完成
      client.createCollectionAsync(
                      collectionName,
                      Collections.VectorParams.newBuilder()
                              .setDistance(distance)
                              .setSize(dimension)
                              .build())
              .get();

      // ==================== 4. 准备嵌入模型 ====================
      EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();

      // ==================== 5. 添加文档到 Qdrant ====================
      TextSegment segment1 = TextSegment.from("I've been to France twice.");
      Embedding embedding1 = embeddingModel.embed(segment1).content();
      embeddingStore.add(embedding1, segment1);

      TextSegment segment2 = TextSegment.from("New Delhi is the capital of India.");
      Embedding embedding2 = embeddingModel.embed(segment2).content();
      embeddingStore.add(embedding2, segment2);

      // ==================== 6. 语义搜索 ====================
      Embedding queryEmbedding = embeddingModel.embed("Did you ever travel abroad?").content();

      EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
              .queryEmbedding(queryEmbedding)
              .maxResults(1)
              .build();

      List<EmbeddingMatch<TextSegment>> matches = embeddingStore.search(embeddingSearchRequest).matches();

      // ==================== 7. 输出结果 ====================
      if (!matches.isEmpty()) {
        EmbeddingMatch<TextSegment> embeddingMatch = matches.get(0);
        System.out.println(embeddingMatch.score());           // 预期:0.6~0.8+
        System.out.println(embeddingMatch.embedded().text()); // 预期:I've been to France twice.
      }

      client.close(); // 必须关闭 QdrantClient,否则 gRPC 连接池泄漏
    }
  }
}

4.3 Maven 依赖

xml 复制代码
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-bom</artifactId>
            <version>1.14.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!-- Qdrant 向量存储 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-qdrant</artifactId>
    </dependency>

    <!-- 本地嵌入模型 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId>
    </dependency>

    <!-- Testcontainers Qdrant 模块 -->
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>qdrant</artifactId>
        <version>1.19.6</version>
    </dependency>
</dependencies>

4.4 生产环境部署

方式 1:Docker 部署

bash 复制代码
docker run -d \
  --name qdrant \
  -p 6333:6333 \
  -p 6334:6334 \
  -v /data/qdrant:/qdrant/storage \
  qdrant/qdrant:latest

方式 2:Kubernetes 部署

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: qdrant
spec:
  replicas: 3
  selector:
    matchLabels:
      app: qdrant
  template:
    metadata:
      labels:
        app: qdrant
    spec:
      containers:
      - name: qdrant
        image: qdrant/qdrant:latest
        ports:
        - containerPort: 6333
        volumeMounts:
        - name: storage
          mountPath: /qdrant/storage
      volumes:
      - name: storage
        persistentVolumeClaim:
          claimName: qdrant-pvc

方式 3:Qdrant Cloud(托管服务)

java 复制代码
EmbeddingStore<TextSegment> embeddingStore = QdrantEmbeddingStore.builder()
    .host("your-cluster.qdrant.io")
    .port(443)
    .apiKey("your-api-key")
    .https(true)
    .build();

4.5 高级过滤示例

java 复制代码
import io.qdrant.client.grpc.Points.Filter;
import io.qdrant.client.grpc.Points.Condition;

// 构建复杂过滤条件
Filter filter = Filter.newBuilder()
    .addMust(Condition.newBuilder()
        .setField("category")
        .setKeyword("技术")
        .build())
    .addMust(Condition.newBuilder()
        .setRange(Range.newBuilder()
            .setGte(100)
            .setLte(1000)
            .build())
        .build())
    .build();

EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
    .queryEmbedding(queryVector)
    .filter(filter)
    .maxResults(10)
    .build();

五、Milvus:企业级分布式向量数据库

5.1 核心特点

Milvus 是一个云原生的分布式向量数据库,由 Zilliz 公司开发,专为 超大规模数据 设计。

优势

  • 分布式架构:天然支持水平扩展,可处理十亿级向量
  • 高可用性:多副本、自动故障转移
  • 丰富索引类型:HNSW、IVF_FLAT、IVF_SQ8、PQ 等
  • 混合搜索:向量 + 标量字段联合查询
  • 生态系统完善:Attu 可视化工具、Milvus CLI、多种语言 SDK
  • 开源活跃:LF AI & Data 基金会项目,GitHub 25k+ Stars

劣势

  • ❌ 架构复杂:依赖 Etcd、MinIO、Pulsar 等多个组件
  • ❌ 运维成本高:需要专业的 DBA 团队
  • ❌ 资源消耗大:最小部署需要 8GB+ 内存
  • ❌ 学习曲线陡峭:配置参数众多

适用场景

  • 超大规模项目(> 1000 万向量)
  • 企业级生产环境
  • 需要高可用和水平扩展的场景

5.2 完整代码示例

java 复制代码
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore;
import org.testcontainers.milvus.MilvusContainer;

import java.util.List;

public class MilvusEmbeddingStoreExample {

    public static void main(String[] args) {

        // ==================== 1. 启动 Milvus 容器 ====================
        try (MilvusContainer milvus = new MilvusContainer("milvusdb/milvus:v2.3.1")) {
            milvus.start();

            // ==================== 2. 创建 MilvusEmbeddingStore ====================
            EmbeddingStore<TextSegment> embeddingStore = MilvusEmbeddingStore.builder()
                    .uri(milvus.getEndpoint())
                    .collectionName("test_collection")
                    .dimension(384)
                    .build();

            // ==================== 3. 创建本地嵌入模型 ====================
            EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();

            // ==================== 4. 添加文档到 Milvus ====================
            TextSegment segment1 = TextSegment.from("I like football.");
            Embedding embedding1 = embeddingModel.embed(segment1).content();
            embeddingStore.add(embedding1, segment1);

            TextSegment segment2 = TextSegment.from("The weather is good today.");
            Embedding embedding2 = embeddingModel.embed(segment2).content();
            embeddingStore.add(embedding2, segment2);

            // ==================== 5. 语义搜索 ====================
            Embedding queryEmbedding = embeddingModel.embed("What is your favourite sport?").content();

            EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
                    .queryEmbedding(queryEmbedding)
                    .maxResults(1)
                    .build();

            List<EmbeddingMatch<TextSegment>> matches = embeddingStore.search(embeddingSearchRequest).matches();

            // ==================== 6. 输出结果 ====================
            if (!matches.isEmpty()) {
                EmbeddingMatch<TextSegment> embeddingMatch = matches.get(0);
                System.out.println(embeddingMatch.score()); // 0.8144287765026093
                System.out.println(embeddingMatch.embedded().text()); // I like football.
            } else {
                System.out.println("No matches found.");
            }
        }
    }
}

5.3 Maven 依赖

xml 复制代码
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-bom</artifactId>
            <version>1.14.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!-- Milvus 向量存储 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-milvus</artifactId>
    </dependency>

    <!-- 本地嵌入模型 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId>
    </dependency>

    <!-- Testcontainers Milvus 模块 -->
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>milvus</artifactId>
        <version>1.19.6</version>
    </dependency>
</dependencies>

5.4 生产环境部署

方式 1: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
    volumes:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/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:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/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.3.1
    command: ["milvus", "run", "standalone"]
    environment:
      ETCD_ENDPOINTS: etcd:2379
      MINIO_ADDRESS: minio:9000
    volumes:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
    ports:
      - "19530:19530"
      - "9091:9091"
    depends_on:
      - "etcd"
      - "minio"

networks:
  default:
    name: milvus

启动命令:

bash 复制代码
docker-compose up -d

方式 2:Kubernetes Helm 部署

bash 复制代码
helm repo add milvus https://zilliztech.github.io/milvus-helm/
helm install my-milvus milvus/milvus \
  --set cluster.enabled=true \
  --set etcd.replicaCount=3 \
  --set minio.replicaCount=4 \
  --set pulsar.enabled=true

方式 3:Zilliz Cloud(托管服务)

java 复制代码
EmbeddingStore<TextSegment> embeddingStore = MilvusEmbeddingStore.builder()
    .uri("https://your-cluster.zillizcloud.com")
    .token("your-api-token")
    .collectionName("my_collection")
    .dimension(384)
    .build();

5.5 高级配置示例

java 复制代码
// 自定义索引类型和参数
MilvusEmbeddingStore store = MilvusEmbeddingStore.builder()
    .uri("localhost:19530")
    .collectionName("my_collection")
    .dimension(384)
    .indexType(IndexType.HNSW)
    .metricType(MetricType.COSINE)
    .extraParams(Map.of(
        "M", "16",        // HNSW 图的邻居节点数
        "efConstruction", "256"  // 构建索引时的搜索范围
    ))
    .build();

六、四大向量数据库深度对比

6.1 功能对比矩阵

特性 InMemory Chroma Qdrant Milvus
架构 单机内存 单机/客户端-服务器 单机/集群 分布式
编程语言 Java Python/Rust Rust Go/C++
持久化 手动序列化 SQLite/文件 RocksDB MinIO + Etcd
最大数据量 < 10 万 < 100 万 < 1000 万 > 10 亿
QPS(单节点) ~1k ~2k ~10k ~5k
平均延迟 < 10ms < 20ms < 5ms < 10ms
元数据过滤 ✅✅ ✅✅
混合搜索
分布式扩展 ⚠️ 收费
高可用性 ⚠️ 收费
部署复杂度 ⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐
运维成本
社区活跃度
学习曲线 平缓 平缓 中等 陡峭
许可证 Apache 2.0 Apache 2.0 Apache 2.0 Apache 2.0

6.2 性能基准测试

以下是基于相同硬件(8核 CPU、16GB 内存、SSD)的测试结果:

测试环境

  • 数据集:100 万条 384 维向量
  • 索引类型:HNSW(M=16, efConstruction=256)
  • 查询次数:1000 次
  • 并发数:10

测试结果

指标 InMemory Chroma Qdrant Milvus
平均延迟 8ms 15ms 4ms 9ms
P95 延迟 12ms 25ms 7ms 15ms
P99 延迟 18ms 40ms 12ms 25ms
QPS 1250 667 2500 1111
召回率@10 100% 98.5% 99.2% 99.0%
内存占用 2.5GB 1.8GB 1.2GB 3.5GB
磁盘占用 0GB 800MB 600MB 1.2GB

结论

  • Qdrant 在性能和资源效率上表现最佳
  • InMemory 在小规模数据下延迟最低,但无法扩展
  • Milvus 性能稳定,但资源消耗较大
  • Chroma 性能适中,但易用性最好

6.3 选型决策树

复制代码
开始选型
  ↓
数据量有多大?
  ├─ < 10 万 → InMemory(最简单)
  ├─ 10 万 - 100 万 → Chroma(平衡易用性和性能)
  ├─ 100 万 - 1000 万 → Qdrant(高性能)
  └─ > 1000 万 → Milvus(分布式扩展)
  ↓
是否需要分布式?
  ├─ 是 → Milvus
  └─ 否 → 继续判断
  ↓
团队技术能力?
  ├─ 初级 → Chroma(上手快)
  ├─ 中级 → Qdrant(性能好)
  └─ 高级 → Milvus(功能强)
  ↓
预算限制?
  ├─ 零预算 → 开源方案(Chroma/Qdrant/Milvus)
  ├─ 中等预算 → Qdrant Cloud / Chroma Cloud
  └─ 充足预算 → Milvus + 专业运维团队
  ↓
最终选择

6.4 典型应用场景推荐

场景 1:个人知识库助手

  • 数据量:< 1 万文档
  • 推荐:InMemory 或 Chroma
  • 理由:简单易用,零运维成本

场景 2:企业客服机器人

  • 数据量:10 万 - 100 万 FAQ
  • 推荐:Chroma 或 Qdrant
  • 理由:平衡性能和易用性,支持元数据过滤

场景 3:电商平台商品搜索

  • 数据量:100 万 - 1000 万商品
  • 推荐:Qdrant
  • 理由:高性能、复杂过滤、混合搜索

场景 4:社交媒体内容推荐

  • 数据量:> 1 亿用户行为向量
  • 推荐:Milvus
  • 理由:分布式扩展、高可用、实时写入

场景 5:金融风控系统

  • 数据量:千万级交易记录
  • 推荐:Milvus 或 Qdrant 企业版
  • 理由:高可用、数据安全、审计日志

七、常见问题与避坑指南

❌ 问题 1:向量维度不匹配

现象 :插入数据时报错 Dimension mismatch: expected 384, got 768

原因:EmbeddingModel 输出的维度与向量库配置的维度不一致。

解决方案

java 复制代码
// 确认模型输出维度
EmbeddingModel model = new AllMiniLmL6V2EmbeddingModel(); // 384 维
// EmbeddingModel model = OpenAiEmbeddingModel.builder().build(); // 1536 维

// 确保向量库配置一致
MilvusEmbeddingStore store = MilvusEmbeddingStore.builder()
    .dimension(384)  // 必须与模型输出一致
    .build();

❌ 问题 2:相似度分数异常

现象:搜索结果的相似度分数 > 1 或 < 0

原因:距离度量方式配置错误。

解决方案

java 复制代码
// 余弦相似度应该在 [0, 1] 范围(LangChain4j 已归一化)
// 如果看到负数,检查是否使用了欧氏距离
QdrantEmbeddingStore store = QdrantEmbeddingStore.builder()
    .distance(Collections.Distance.Cosine)  // 明确指定
    .build();

❌ 问题 3:Testcontainers 启动失败

现象 :运行示例时报错 Could not find a valid Docker environment

原因:Docker 未安装或未启动。

解决方案

bash 复制代码
# 1. 安装 Docker Desktop
# Windows/Mac: 下载 Docker Desktop
# Linux: sudo apt-get install docker.io

# 2. 启动 Docker
docker info  # 验证是否正常运行

# 3. 拉取镜像(可选,加速首次运行)
docker pull chromadb/chroma:1.1.0
docker pull qdrant/qdrant:latest
docker pull milvusdb/milvus:v2.3.1

❌ 问题 4:Milvus 连接超时

现象Connect to localhost:19530 failed: Connection timed out

原因:Milvus 依赖组件未完全启动。

解决方案

bash 复制代码
# 检查所有组件状态
docker-compose ps

# 查看日志
docker-compose logs -f standalone

# 等待健康检查通过
docker-compose exec standalone milvus-cli health

❌ 问题 5:内存溢出(OOM)

现象java.lang.OutOfMemoryError: Java heap space

原因:InMemoryEmbeddingStore 加载了大量向量。

解决方案

bash 复制代码
# 增加 JVM 堆内存
java -Xmx8g -Xms4g -jar your-app.jar

或者切换到外部向量数据库(Chroma/Qdrant/Milvus)。


八、性能优化最佳实践

8.1 批量插入优化

java 复制代码
// ❌ 低效:逐条插入
for (TextSegment segment : segments) {
    Embedding embedding = model.embed(segment).content();
    store.add(embedding, segment);
}

// ✅ 高效:批量插入
List<Embedding> embeddings = model.embedAll(segments).content();
store.addAll(embeddings, segments);

性能提升:10 倍 - 50 倍

8.2 索引参数调优

java 复制代码
// HNSW 索引参数调优
MilvusEmbeddingStore store = MilvusEmbeddingStore.builder()
    .indexType(IndexType.HNSW)
    .extraParams(Map.of(
        "M", "16",           // 邻居节点数:越大精度越高,内存越大
        "efConstruction", "256"  // 构建搜索范围:越大构建越慢,质量越高
    ))
    .build();

// 搜索时调整 efSearch(运行时参数)
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
    .queryEmbedding(queryVector)
    .maxResults(10)
    .build();

调优建议

  • M=8-16:平衡精度和内存
  • efConstruction=200-400:构建质量和速度的平衡
  • efSearch=50-100:搜索时的召回率和延迟平衡

8.3 缓存热点查询

java 复制代码
import com.github.benmanes.caffeine.cache.Cache;

Cache<String, List<EmbeddingMatch<TextSegment>>> cache = Caffeine.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build();

public List<EmbeddingMatch<TextSegment>> searchWithCache(String query) {
    return cache.get(query, key -> {
        Embedding queryVector = model.embed(key).content();
        EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
            .queryEmbedding(queryVector)
            .maxResults(10)
            .build();
        return store.search(request).matches();
    });
}

适用场景:高频重复查询(如热门问题)

8.4 分区与分片

对于 Milvus,可以使用分区提升性能:

java 复制代码
// 按业务维度分区
store.createPartition("partition_2026_q1");
store.createPartition("partition_2026_q2");

// 插入时指定分区
store.add(embedding, segment, "partition_2026_q1");

// 搜索时限定分区
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
    .queryEmbedding(queryVector)
    .partitionNames(List.of("partition_2026_q1"))
    .build();

优势:减少搜索范围,提升 QPS


九、向量数据库的典型应用场景

掌握了向量数据库技术后,你可以构建以下应用:

1. 企业知识库问答

  • 技术方案:Chroma + RAG
  • 数据量:10 万 - 100 万文档
  • 特点:元数据过滤(按部门、权限)、引用来源标注

2. 智能客服系统

  • 技术方案:Qdrant + Function Calling
  • 数据量:100 万 FAQ
  • 特点:高性能、复杂意图识别、多轮对话

3. 电商商品搜索

  • 技术方案:Milvus + 混合搜索
  • 数据量:1000 万商品
  • 特点:向量相似度 + 价格/品牌过滤、个性化推荐

4. 内容去重与抄袭检测

  • 技术方案:InMemory/Qdrant
  • 数据量:10 万 - 100 万文章
  • 特点:高召回率、批量比对

5. 图像/视频检索

  • 技术方案:Milvus + CLIP 模型
  • 数据量:亿级图片
  • 特点:分布式存储、GPU 加速

6. 推荐系统

  • 技术方案:Milvus + 实时写入
  • 数据量:十亿级用户行为
  • 特点:实时更新、低延迟召回

结语

现在我们已经全面对比了四大向量数据库(InMemory、Chroma、Qdrant、Milvus)的核心特性、性能指标和适用场景,并通过完整的代码示例演示了如何在 LangChain4j 中集成这些向量存储方案。我们还学会了使用 Testcontainers 快速搭建本地测试环境,掌握了索引调优、批量插入、缓存优化等性能提升技巧,以及根据数据量、团队能力、预算评估做出合理选型的决策方法。

无论是在构建个人知识库助手,还是企业级智能客服系统,亦或是十亿级推荐的社交平台,都能从本文中找到最适合的向量数据库解决方案。记住,没有最好的向量数据库,只有最适合你的场景的技术选型。


🎯🔖更多专栏系列文章:AI大模型提示工程完全指南AI大模型探索之路(零基础入门)AI大模型预训练微调进阶AI大模型开源精选实践AI大模型Spring AI开发实战🔥🔥🔥 其他专栏可以查看博客主页

🔔 关于作者 :资深程序老猿,10年+架构经验,现专注 AIGC 探索与实践。

👍 若文章对你有所触动,恳请点赞 ⭐ 关注 ⭐ 收藏!AI 浪潮已至,愿与你同行。

相关推荐
Hector_zh3 分钟前
逐浪 · 第十一篇: Vibe Coding 下的效率定义与规范建设
人工智能·vibecoding
147API9 分钟前
Claude进入受监管系统前,接入层应该先怎么设计
人工智能
Szime10 分钟前
深智微:面向汽车电子与工业控制的电子元器件原装现货服务商
人工智能·汽车
gis分享者11 分钟前
Claude Code 接入蓝耘 GLM-5.1:终端 AI 编程助手配置实战
人工智能·ai·实战·claude·cc·接入glm
东方隐侠安全团队-千里13 分钟前
币安Skills Hub:散户的“机构级超能力“来了
安全·ai·区块链·skills
企学宝14 分钟前
央国企数字化培训升级路径:学分制+AI评卷的全新实践
人工智能·企业培训·公司内训
三更两点15 分钟前
AI拉呱-2026年06月12日AI技术洞察简报
人工智能
终端域名17 分钟前
AI与区块链融合:加密货币的下一前沿——技术架构、企业价值与未来趋势
人工智能·架构·区块链
lauo17 分钟前
ibbot青春版:当腾讯AI“换船”,一部手机如何成为你的Token“私矿”?
大数据·人工智能·chatgpt·智能手机·ai-native
yzqy_23 分钟前
AMD AI 开发者计划学习笔记:从 ROCm 到 Ryzen AI,理解 AMD 的 AI 开发生态
人工智能·笔记·学习·datawhale·amdev