欢迎光临
我们一直在努力

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实践对比

目录

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

前言

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

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

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

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

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


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

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

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

传统数据库的局限性

// ❌ 传统方式:将向量存储在 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

向量数据库的优势

// ✅ 向量数据库:使用 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 中的体现:

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

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

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

// 余弦相似度(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)

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

// 场景:只搜索"技术部"员工的简历,且相似度 > 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)持久化与扩展性

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

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

2.1 核心特点

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

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

劣势:

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

2.2 完整代码示例

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 依赖

<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 默认不持久化,但你可以通过序列化实现数据保存:

// 保存向量库到文件
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 完整代码示例

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 依赖

<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 部署

docker run -d \\
–name chroma \\
-p 8000:8000 \\
-v /data/chroma:/chroma/chroma \\
chromadb/chroma:latest

方式 2:Python 包部署

pip install chromadb
chroma run –host 0.0.0.0 –port 8000

方式 3:云服务(Chroma Cloud)

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

3.5 元数据过滤示例

// 添加带元数据的文档
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 完整代码示例

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 依赖

<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 部署

docker run -d \\
–name qdrant \\
-p 6333:6333 \\
-p 6334:6334 \\
-v /data/qdrant:/qdrant/storage \\
qdrant/qdrant:latest

方式 2:Kubernetes 部署

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: qdrantpvc

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

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

4.5 高级过滤示例

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 完整代码示例

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 依赖

<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 部署

version: '3.5'

services:
etcd:
container_name: milvusetcd
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 advertiseclienturls=http://127.0.0.1:2379 listenclienturls http://0.0.0.0:2379 datadir /etcd

minio:
container_name: milvusminio
image: minio/minio:RELEASE.20230320T201618Z
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: milvusstandalone
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

启动命令:

docker-compose up -d

方式 2:Kubernetes Helm 部署

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(托管服务)

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

5.5 高级配置示例

// 自定义索引类型和参数
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 功能对比矩阵

特性InMemoryChromaQdrantMilvus
架构 单机内存 单机/客户端-服务器 单机/集群 分布式
编程语言 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

测试结果

指标InMemoryChromaQdrantMilvus
平均延迟 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 输出的维度与向量库配置的维度不一致。

解决方案:

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

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

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

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

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

解决方案:

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

❌ 问题 3:Testcontainers 启动失败

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

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

解决方案:

# 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 依赖组件未完全启动。

解决方案:

# 检查所有组件状态
docker-compose ps

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

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

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

现象:java.lang.OutOfMemoryError: Java heap space

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

解决方案:

# 增加 JVM 堆内存
java -Xmx8g -Xms4g -jar your-app.jar

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


八、性能优化最佳实践

8.1 批量插入优化

// ❌ 低效:逐条插入
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 索引参数调优

// 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 缓存热点查询

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,可以使用分区提升性能:

// 按业务维度分区
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 浪潮已至,愿与你同行。

赞(0)
未经允许不得转载:171主机测评 » LangChain4j Java AI 应用开发实战(十二):向量数据Chroma/Qdrant/Milvus实践对比
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址