本文将深入探讨如何为LangChain4J RAG应用选择和集成合适的向量数据库,包括五大主流方案的对比、实战集成和性能优化。
时间:30分钟 | 难度:⭐⭐⭐ | Week 3 Day 17
📚 学习目标
- 理解向量数据库的核心价值和工作原理
- 掌握五大向量数据库的特点和适用场景
- 学会使用LangChain4J集成InMemory、Chroma、Milvus、pgvector
- 理解向量索引和性能优化策略
- 能够根据业务需求做出正确的选型决策
- 掌握向量数据库的最佳实践
🚀 快速入门:为什么需要向量数据库?
传统数据库 vs 向量数据库
传统关系型数据库(精确匹配)
┌─────────────────────────────────┐
│ SELECT * FROM docs │
│ WHERE title = "Java教程" │ ← 必须完全匹配
└─────────────────────────────────┘
↓
只能找到标题完全是"Java教程"的文档
向量数据库(语义相似度搜索)
┌─────────────────────────────────┐
│ findRelevant( │
│ embedding("Java学习资料"), │ ← 语义理解
│ maxResults = 5 │
│ ) │
└─────────────────────────────────┘
↓
能找到:
- "Java教程"
- "Java编程指南"
- "Java入门手册"
- "学习Java的最佳实践"
- "Java开发者必读"
向量检索的核心流程
用户查询:"如何学习Java?"
↓
文本向量化 (Embedding)
↓
[0.23, -0.45, 0.67, ..., 0.12] ← 1536维向量
↓
向量数据库搜索(余弦相似度)
↓
返回最相似的K个文档
↓
1. "Java学习路线图" (相似度: 0.92)
2. "Java入门教程" (相似度: 0.88)
3. "Java编程指南" (相似度: 0.85)
📖 深度讲解
1️⃣ 五大向量数据库对比
| 特性 | InMemory | Chroma | Milvus | pgvector | Pinecone |
|---|---|---|---|---|---|
| 部署方式 | 内存(无需安装) | 本地/Docker | 分布式集群 | PostgreSQL插件 | 云服务 |
| 数据规模 | <10万条 | <100万条 | 亿级以上 | <1000万条 | 无限扩展 |
| 性能 | 快(内存) | 中等 | 极高 | 中等 | 高 |
| 持久化 | ❌ 需手动保存 | ✅ 自动 | ✅ 分布式存储 | ✅ PostgreSQL | ✅ 云存储 |
| 成本 | 免费 | 免费(开源) | 开源(需服务器) | 免费(需PG) | $70+/月 |
| 运维复杂度 | 零运维 | 低 | 高 | 中(依赖PG) | 零运维 |
| 索引类型 | 暴力搜索 | HNSW | IVF/HNSW/DiskANN | IVF | 专有索引 |
| 多租户 | ❌ | ✅ Collection | ✅ Partition | ✅ Schema | ✅ Namespace |
| 过滤查询 | 代码实现 | Metadata过滤 | 标量过滤 | SQL WHERE | Metadata过滤 |
| 适合场景 | 开发测试 | 小型项目 | 企业级生产 | 已有PG用户 | 快速上线 |
| LangChain4J支持 | ✅ 原生 | ✅ 官方 | ✅ 官方 | ✅ 官方 | ✅ 官方 |
2️⃣ InMemoryEmbeddingStore - 最简单的起点
基本使用
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
// 创建内存向量存储
EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
// 添加单个文档
TextSegment segment = TextSegment.from("LangChain4J是一个Java AI框架");
Embedding embedding = embeddingModel.embed(segment).content();
embeddingStore.add(embedding, segment);
// 批量添加
List<TextSegment> segments = List.of(
TextSegment.from("Java是一门面向对象的编程语言"),
TextSegment.from("Spring Boot简化了Java开发"),
TextSegment.from("Docker是容器化技术的代表")
);
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
embeddingStore.addAll(embeddings, segments);
语义搜索
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
// 初始化嵌入模型
EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("text-embedding-3-small")
.build();
// 查询向量化
String query = "如何学习Java编程?";
Embedding queryEmbedding = embeddingModel.embed(query).content();
// 查找最相关的5个结果
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
queryEmbedding,
5, // 返回Top-5
0.7 // 最小相似度阈值(可选)
);
// 处理搜索结果
for (EmbeddingMatch<TextSegment> match : matches) {
System.out.printf("相似度: %.3f | 内容: %s%n",
match.score(), // 相似度分数 (0-1)
match.embedded().text() // 原始文本
);
}
持久化到文件
import java.nio.file.Path;
// 保存到文件
InMemoryEmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>();
// ... 添加数据 ...
store.serializeToFile(Path.of("embeddings.json"));
// 从文件加载
InMemoryEmbeddingStore<TextSegment> loadedStore =
InMemoryEmbeddingStore.fromFile(Path.of("embeddings.json"));
带元数据的存储
import dev.langchain4j.data.document.Metadata;
// 创建带元数据的文本段
TextSegment segment = TextSegment.from(
"LangChain4J 0.35版本发布",
Metadata.from("source", "官网")
.put("date", "2024-01-15")
.put("category", "release")
);
embeddingStore.add(embedding, segment);
// 搜索后访问元数据
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(queryEmbedding, 3);
for (EmbeddingMatch<TextSegment> match : matches) {
Metadata metadata = match.embedded().metadata();
System.out.println("来源: " + metadata.getString("source"));
System.out.println("日期: " + metadata.getString("date"));
}
3️⃣ Chroma集成 - 本地开发的最佳选择
Docker启动Chroma
# 使用Docker运行Chroma服务
docker run -d \
--name chromadb \
-p 8000:8000 \
-v chroma-data:/chroma/chroma \
chromadb/chroma:latest
# 验证服务运行
curl http://localhost:8000/api/v1/heartbeat
Maven依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-chroma</artifactId>
<version>0.35.0</version>
</dependency>
基本集成
import dev.langchain4j.store.embedding.chroma.ChromaEmbeddingStore;
import java.time.Duration;
// 创建Chroma存储
EmbeddingStore<TextSegment> embeddingStore = ChromaEmbeddingStore.builder()
.baseUrl("http://localhost:8000") // Chroma服务地址
.collectionName("langchain4j-docs") // 集合名称(类似表名)
.timeout(Duration.ofSeconds(30)) // 请求超时
.logRequests(true) // 启用请求日志
.logResponses(true) // 启用响应日志
.build();
// 后续使用方式与InMemoryEmbeddingStore完全相同
多集合管理
// 为不同类型的文档创建不同集合
EmbeddingStore<TextSegment> techDocs = ChromaEmbeddingStore.builder()
.baseUrl("http://localhost:8000")
.collectionName("tech-docs")
.build();
EmbeddingStore<TextSegment> userManuals = ChromaEmbeddingStore.builder()
.baseUrl("http://localhost:8000")
.collectionName("user-manuals")
.build();
EmbeddingStore<TextSegment> faqStore = ChromaEmbeddingStore.builder()
.baseUrl("http://localhost:8000")
.collectionName("faq")
.build();
元数据过滤查询
import dev.langchain4j.store.embedding.filter.Filter;
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.*;
// 创建带丰富元数据的文档
TextSegment doc1 = TextSegment.from(
"Java 17新特性介绍",
Metadata.from("category", "tutorial")
.put("language", "java")
.put("level", "intermediate")
.put("year", 2021)
);
// 元数据过滤搜索
Filter filter = metadataKey("category").isEqualTo("tutorial")
.and(metadataKey("language").isEqualTo("java"))
.and(metadataKey("year").isGreaterThanOrEqualTo(2020));
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
queryEmbedding,
5,
0.7,
filter // 应用过滤器
);
完整的RAG示例
import dev.langchain4j.chain.ConversationalRetrievalChain;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.retriever.EmbeddingStoreRetriever;
import dev.langchain4j.retriever.Retriever;
public class ChromaRAGExample {
public static void main(String[] args) {
// 1. 初始化组件
EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.build();
EmbeddingStore<TextSegment> embeddingStore = ChromaEmbeddingStore.builder()
.baseUrl("http://localhost:8000")
.collectionName("knowledge-base")
.build();
ChatLanguageModel chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4")
.build();
// 2. 文档摄入
Document document = Document.from("LangChain4J是一个强大的Java AI框架...");
DocumentSplitter splitter = DocumentSplitters.recursive(500, 50);
List<TextSegment> segments = splitter.split(document);
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
embeddingStore.addAll(embeddings, segments);
// 3. 创建检索器
Retriever<TextSegment> retriever = EmbeddingStoreRetriever.from(
embeddingStore,
embeddingModel,
5, // maxResults
0.7 // minScore
);
// 4. 构建RAG链
ConversationalRetrievalChain chain = ConversationalRetrievalChain.builder()
.chatLanguageModel(chatModel)
.retriever(retriever)
.build();
// 5. 对话查询
String answer = chain.execute("LangChain4J有什么特点?");
System.out.println(answer);
}
}
4️⃣ Milvus集成 - 企业级分布式方案
Docker Compose启动Milvus
# docker-compose.yml
version: '3.5'
services:
etcd:
image: quay.io/coreos/etcd:v3.5.5
environment:
- ETCD_AUTO_COMPACTION_MODE=revision
- ETCD_AUTO_COMPACTION_RETENTION=1000
volumes:
- etcd-data:/etcd
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
milvus:
image: milvusdb/milvus:v2.3.3
command: ["milvus", "run", "standalone"]
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
volumes:
- milvus-data:/var/lib/milvus
ports:
- "19530:19530"
- "9091:9091"
depends_on:
- etcd
- minio
volumes:
etcd-data:
minio-data:
milvus-data:
# 启动Milvus集群
docker-compose up -d
# 查看服务状态
docker-compose ps
Maven依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-milvus</artifactId>
<version>0.35.0</version>
</dependency>
基本配置
import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore;
// 创建Milvus存储
EmbeddingStore<TextSegment> embeddingStore = MilvusEmbeddingStore.builder()
.host("localhost")
.port(19530)
.collectionName("documents")
.dimension(1536) // 向量维度(必须与嵌入模型匹配)
.indexType("IVF_FLAT") // 索引类型
.metricType("COSINE") // 距离度量(COSINE/L2/IP)
.build();
高级索引配置
// HNSW索引 - 高性能近似搜索
EmbeddingStore<TextSegment> hnswStore = MilvusEmbeddingStore.builder()
.host("localhost")
.port(19530)
.collectionName("fast-search")
.dimension(1536)
.indexType("HNSW")
.metricType("COSINE")
.build();
// IVF_PQ索引 - 内存优化
EmbeddingStore<TextSegment> pqStore = MilvusEmbeddingStore.builder()
.host("localhost")
.port(19530)
.collectionName("large-dataset")
.dimension(1536)
.indexType("IVF_PQ")
.metricType("L2")
.build();
分区管理(多租户)
import io.milvus.client.MilvusServiceClient;
import io.milvus.param.ConnectParam;
import io.milvus.param.partition.CreatePartitionParam;
// 创建客户端
MilvusServiceClient milvusClient = new MilvusServiceClient(
ConnectParam.newBuilder()
.withHost("localhost")
.withPort(19530)
.build()
);
// 创建分区(例如按用户ID分区)
milvusClient.createPartition(
CreatePartitionParam.newBuilder()
.withCollectionName("documents")
.withPartitionName("user_123")
.build()
);
// 在特定分区搜索
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
queryEmbedding,
5,
0.7,
Filter.metadataKey("partition").isEqualTo("user_123")
);
批量导入优化
import java.util.ArrayList;
import java.util.List;
public class MilvusBulkImporter {
private static final int BATCH_SIZE = 1000;
public void bulkImport(List<Document> documents,
EmbeddingModel embeddingModel,
EmbeddingStore<TextSegment> embeddingStore) {
List<TextSegment> batchSegments = new ArrayList<>();
for (int i = 0; i < documents.size(); i++) {
TextSegment segment = TextSegment.from(documents.get(i).text());
batchSegments.add(segment);
// 每1000条批量写入
if ((i + 1) % BATCH_SIZE == 0 || i == documents.size() - 1) {
List<Embedding> embeddings = embeddingModel.embedAll(batchSegments).content();
embeddingStore.addAll(embeddings, batchSegments);
System.out.printf("已导入 %d/%d 条文档%n", i + 1, documents.size());
batchSegments.clear();
}
}
}
}
5️⃣ pgvector集成 - PostgreSQL用户的福音
安装pgvector扩展
-- PostgreSQL 12+
CREATE EXTENSION IF NOT EXISTS vector;
-- 创建向量表
CREATE TABLE embeddings (
id BIGSERIAL PRIMARY KEY,
content TEXT NOT NULL,
metadata JSONB,
embedding vector(1536) -- 1536维向量
);
-- 创建HNSW索引加速查询
CREATE INDEX ON embeddings USING hnsw (embedding vector_cosine_ops);
-- 或使用IVFFlat索引
CREATE INDEX ON embeddings USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
Maven依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-pgvector</artifactId>
<version>0.35.0</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.1</version>
</dependency>
基本集成
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
// 创建pgvector存储
EmbeddingStore<TextSegment> embeddingStore = PgVectorEmbeddingStore.builder()
.host("localhost")
.port(5432)
.database("mydb")
.user("postgres")
.password("password")
.table("embeddings")
.dimension(1536)
.build();
使用连接池
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
// 配置HikariCP连接池
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
config.setUsername("postgres");
config.setPassword("password");
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
DataSource dataSource = new HikariDataSource(config);
// 使用连接池创建存储
EmbeddingStore<TextSegment> embeddingStore = PgVectorEmbeddingStore.builder()
.dataSource(dataSource)
.table("embeddings")
.dimension(1536)
.build();
SQL级别的元数据过滤
// pgvector支持强大的SQL WHERE子句
String sqlFilter = "metadata->>'category' = 'technical' AND " +
"metadata->>'year'::int >= 2023";
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
queryEmbedding,
5,
0.7,
Filter.expression(sqlFilter)
);
混合搜索(全文+向量)
-- 创建全文搜索索引
CREATE INDEX ON embeddings USING gin(to_tsvector('english', content));
-- 混合搜索查询
SELECT
content,
1 - (embedding <=> '[0.1, 0.2, ...]'::vector) AS similarity,
ts_rank(to_tsvector('english', content), query) AS text_rank
FROM embeddings, plainto_tsquery('english', 'java programming') query
WHERE to_tsvector('english', content) @@ query
ORDER BY (similarity * 0.7 + text_rank * 0.3) DESC
LIMIT 10;
Spring Boot集成示例
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class EmbeddingStoreConfig {
@Bean
public EmbeddingStore<TextSegment> embeddingStore(DataSource dataSource) {
return PgVectorEmbeddingStore.builder()
.dataSource(dataSource) // 复用Spring的DataSource
.table("embeddings")
.dimension(1536)
.createTable(true) // 自动建表
.build();
}
@Bean
public EmbeddingModel embeddingModel() {
return OpenAiEmbeddingModel.builder()
.apiKey("${openai.api.key}")
.build();
}
}
🎯 选型决策流程
开始选型
↓
正在开发/测试阶段?
├─ 是 → InMemoryEmbeddingStore
│ - 零配置,立即开始
│ - 适合POC和单元测试
└─ 否 ↓
已经在使用PostgreSQL?
├─ 是 → pgvector
│ - 无需额外数据库
│ - 利用现有PG生态(备份、监控)
│ - 支持事务和复杂SQL
└─ 否 ↓
预期数据量?
├─ <100万 → Chroma
│ - Docker一键启动
│ - 运维简单
│ - 适合中小项目
│
├─ 100万-1000万 → pgvector 或 Chroma
│ - 看团队技术栈
│ - 看是否需要SQL能力
│
└─ >1000万 ↓
需要分布式/高可用?
├─ 是 → Milvus
│ - 支持亿级数据
│ - 水平扩展
│ - 企业级功能
└─ 否 ↓
团队是否有运维能力?
├─ 无 → Pinecone (云服务)
│ - 全托管,零运维
│ - 按使用量付费
│ - 快速上线
└─ 有 → Milvus
- 开源免费
- 完全可控
- 适合长期运营
选型矩阵
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 快速原型开发 | InMemory | 无需配置,代码即启动 |
| 个人项目/博客 | Chroma | 免费、轻量、易部署 |
| 中小企业应用 | pgvector + PostgreSQL | 利用现有基础设施,降低成本 |
| 大型企业系统 | Milvus | 高性能、分布式、支持大规模 |
| 快速上线SaaS | Pinecone | 全托管,专注业务逻辑 |
| 混合搜索需求 | pgvector | 结合SQL全文搜索 |
| 多租户SaaS | Milvus (Partition) | 原生支持分区隔离 |
| 边缘计算/离线 | Chroma + 持久化 | 轻量级,可嵌入应用 |
⚡ 性能优化最佳实践
1️⃣ 索引类型选择
索引类型对比
┌──────────────────────────────────────────────────┐
│ 索引类型 │ 速度 │ 内存 │ 精度 │ 适用场景 │
├──────────────────────────────────────────────────┤
│ FLAT │ 慢 │ 高 │ 100% │ <10万,要求│
│ │ │ │ │ 精确结果 │
├──────────────────────────────────────────────────┤
│ IVF_FLAT │ 中 │ 中 │ 98% │ 10万-100万 │
│ │ │ │ │ 平衡选择 │
├──────────────────────────────────────────────────┤
│ HNSW │ 快 │ 高 │ 97% │ <1000万, │
│ │ │ │ │ 要求低延迟 │
├──────────────────────────────────────────────────┤
│ IVF_PQ │ 快 │ 低 │ 95% │ >1000万, │
│ │ │ │ │ 内存受限 │
└──────────────────────────────────────────────────┘
Milvus索引配置示例
// 小数据集 - FLAT索引(精确搜索)
EmbeddingStore<TextSegment> flatStore = MilvusEmbeddingStore.builder()
.indexType("FLAT")
.metricType("COSINE")
.build();
// 中等数据集 - IVF_FLAT索引
EmbeddingStore<TextSegment> ivfStore = MilvusEmbeddingStore.builder()
.indexType("IVF_FLAT")
.metricType("COSINE")
.build();
// 大数据集,要求快速 - HNSW索引
EmbeddingStore<TextSegment> hnswStore = MilvusEmbeddingStore.builder()
.indexType("HNSW")
.metricType("COSINE")
.build();
// 超大数据集,内存受限 - IVF_PQ索引
EmbeddingStore<TextSegment> pqStore = MilvusEmbeddingStore.builder()
.indexType("IVF_PQ")
.metricType("L2")
.build();
2️⃣ 批量操作优化
// ❌ 低效:逐条插入
for (TextSegment segment : segments) {
Embedding embedding = embeddingModel.embed(segment).content();
embeddingStore.add(embedding, segment); // 每次都是一次网络请求
}
// ✅ 高效:批量插入
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
embeddingStore.addAll(embeddings, segments); // 一次批量操作
智能批处理器
public class BatchEmbeddingProcessor {
private final int batchSize;
private final EmbeddingModel embeddingModel;
private final EmbeddingStore<TextSegment> embeddingStore;
public BatchEmbeddingProcessor(int batchSize,
EmbeddingModel embeddingModel,
EmbeddingStore<TextSegment> embeddingStore) {
this.batchSize = batchSize;
this.embeddingModel = embeddingModel;
this.embeddingStore = embeddingStore;
}
public void process(List<TextSegment> segments) {
for (int i = 0; i < segments.size(); i += batchSize) {
int end = Math.min(i + batchSize, segments.size());
List<TextSegment> batch = segments.subList(i, end);
// 批量生成嵌入
List<Embedding> embeddings = embeddingModel.embedAll(batch).content();
// 批量存储
embeddingStore.addAll(embeddings, batch);
System.out.printf("处理进度: %d/%d%n", end, segments.size());
}
}
}
// 使用
BatchEmbeddingProcessor processor = new BatchEmbeddingProcessor(
100, // 每批100条
embeddingModel,
embeddingStore
);
processor.process(allSegments);
3️⃣ 连接池配置
// pgvector连接池最佳实践
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(20); // 最大连接数
config.setMinimumIdle(5); // 最小空闲连接
config.setConnectionTimeout(30000); // 连接超时30秒
config.setIdleTimeout(600000); // 空闲超时10分钟
config.setMaxLifetime(1800000); // 连接最大存活30分钟
config.setLeakDetectionThreshold(60000); // 连接泄漏检测
DataSource dataSource = new HikariDataSource(config);
4️⃣ 查询优化
// ❌ 返回过多结果
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
queryEmbedding,
100 // 返回100条,但通常只用前5条
);
// ✅ 只请求需要的数量
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
queryEmbedding,
5, // 只返回5条
0.7 // 设置最小相似度,过滤低质量结果
);
相似度阈值调优
public class SimilarityThresholdTuner {
public void tune(EmbeddingStore<TextSegment> store,
EmbeddingModel model,
List<String> testQueries) {
double[] thresholds = {0.5, 0.6, 0.7, 0.8, 0.9};
for (double threshold : thresholds) {
System.out.println("测试阈值: " + threshold);
for (String query : testQueries) {
Embedding queryEmbedding = model.embed(query).content();
List<EmbeddingMatch<TextSegment>> matches = store.findRelevant(
queryEmbedding,
10,
threshold
);
System.out.printf(" 查询: %s → %d个结果%n",
query, matches.size());
}
System.out.println();
}
}
}
5️⃣ 向量维度选择
// 不同嵌入模型的维度对比
// text-embedding-3-small: 1536维 (性能好,成本低)
EmbeddingModel smallModel = OpenAiEmbeddingModel.builder()
.apiKey(apiKey)
.modelName("text-embedding-3-small") // 1536维
.build();
// text-embedding-3-large: 3072维 (精度高,成本高)
EmbeddingModel largeModel = OpenAiEmbeddingModel.builder()
.apiKey(apiKey)
.modelName("text-embedding-3-large") // 3072维
.build();
// 权衡:
// - 1536维:存储减半,搜索更快,成本更低
// - 3072维:语义表达更精确,适合复杂场景
6️⃣ 缓存策略
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.time.Duration;
public class CachedEmbeddingStore implements EmbeddingStore<TextSegment> {
private final EmbeddingStore<TextSegment> delegate;
private final Cache<String, List<EmbeddingMatch<TextSegment>>> cache;
public CachedEmbeddingStore(EmbeddingStore<TextSegment> delegate) {
this.delegate = delegate;
this.cache = Caffeine.newBuilder()
.maximumSize(1000) // 最多缓存1000个查询
.expireAfterWrite(Duration.ofMinutes(10)) // 10分钟过期
.build();
}
@Override
public List<EmbeddingMatch<TextSegment>> findRelevant(
Embedding referenceEmbedding,
int maxResults,
double minScore) {
String cacheKey = generateKey(referenceEmbedding, maxResults, minScore);
return cache.get(cacheKey, key ->
delegate.findRelevant(referenceEmbedding, maxResults, minScore)
);
}
private String generateKey(Embedding embedding, int maxResults, double minScore) {
// 简化版:实际应该用更好的哈希算法
return embedding.vector()[0] + "_" + maxResults + "_" + minScore;
}
// 实现其他方法...
}
💡 最佳实践总结
1. 开发流程建议
阶段1: 原型开发
└─ 使用 InMemoryEmbeddingStore
- 快速验证想法
- 无需配置
阶段2: 本地开发
└─ 切换到 Chroma
- Docker一键启动
- 持久化存储
- 接近生产环境
阶段3: 生产部署
├─ 数据量<100万 → Chroma
├─ 已有PostgreSQL → pgvector
├─ 需要分布式 → Milvus
└─ 无运维能力 → Pinecone
2. 安全建议
// ✅ 使用环境变量存储敏感信息
String apiKey = System.getenv("OPENAI_API_KEY");
String dbPassword = System.getenv("DB_PASSWORD");
// ❌ 不要硬编码密钥
String apiKey = "sk-xxxxxxxxxxxxx"; // 危险!
3. 监控指标
public class EmbeddingStoreMetrics {
private final MeterRegistry registry;
public void recordSearch(long durationMs, int resultCount) {
registry.timer("embedding.search.duration").record(durationMs, TimeUnit.MILLISECONDS);
registry.counter("embedding.search.results", "count", String.valueOf(resultCount)).increment();
}
public void recordInsert(int batchSize, long durationMs) {
registry.timer("embedding.insert.duration").record(durationMs, TimeUnit.MILLISECONDS);
registry.counter("embedding.insert.count").increment(batchSize);
}
}
🏋️ 练习题
练习1:对比测试(30分钟)
实现一个对比测试程序,分别使用InMemory、Chroma和pgvector存储相同的1000条文档,测试:
- 插入性能
- 查询性能
- 内存占用
public class BenchmarkTest {
public void benchmark(EmbeddingStore<TextSegment> store, String name) {
// TODO: 实现基准测试
// 1. 测试插入1000条文档的时间
// 2. 测试100次查询的平均响应时间
// 3. 打印结果对比
}
}
练习2:智能选型工具(20分钟)
编写一个选型决策工具,根据用户输入的参数推荐最适合的向量数据库:
public class DatabaseSelector {
public String recommend(int estimatedDocs,
boolean hasPostgres,
boolean needDistributed,
boolean hasOpsTeam) {
// TODO: 实现选型逻辑
// 返回推荐的数据库名称和理由
}
}
练习3:混合存储(40分钟)
实现一个混合存储方案,开发时使用InMemory,测试/生产环境自动切换到Chroma:
@Configuration
public class EmbeddingStoreConfig {
@Bean
public EmbeddingStore<TextSegment> embeddingStore(
@Value("${app.env}") String env) {
// TODO: 根据环境变量返回不同的EmbeddingStore
// dev → InMemoryEmbeddingStore
// test/prod → ChromaEmbeddingStore
}
}
🎓 小结
通过本文,你已经掌握:
- 向量数据库核心概念 - 为什么需要它,它解决什么问题
- 五大方案对比 - InMemory、Chroma、Milvus、pgvector、Pinecone的特点
- 集成实战 - 从零到一集成各种向量数据库
- 选型决策 - 根据业务场景做出正确选择
- 性能优化 - 索引、批处理、缓存等优化手段
向量数据库是RAG应用的核心组件,选对工具事半功倍。记住:
- 开发阶段用InMemory,快速迭代
- 小项目用Chroma,简单够用
- 有PG用pgvector,降低成本
- 大规模用Milvus,性能保证
- 无运维用Pinecone,专注业务
- 延伸阅读:
- Milvus官方文档:milvus.io/docs
- pgvector性能调优:github.com/pgvector/pg…
- Chroma使用指南:docs.trychroma.com
💡 专家建议:不要过早优化!先用InMemory快速验证想法,确认方向后再选择合适的生产级方案。大多数应用在初期用Chroma就足够了。