17-向量数据库选型与集成

8 阅读7分钟

本文将深入探讨如何为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️⃣ 五大向量数据库对比

特性InMemoryChromaMilvuspgvectorPinecone
部署方式内存(无需安装)本地/Docker分布式集群PostgreSQL插件云服务
数据规模<10万条<100万条亿级以上<1000万条无限扩展
性能快(内存)中等极高中等
持久化❌ 需手动保存✅ 自动✅ 分布式存储✅ PostgreSQL✅ 云存储
成本免费免费(开源)开源(需服务器)免费(需PG)$70+/月
运维复杂度零运维中(依赖PG)零运维
索引类型暴力搜索HNSWIVF/HNSW/DiskANNIVF专有索引
多租户✅ Collection✅ Partition✅ Schema✅ Namespace
过滤查询代码实现Metadata过滤标量过滤SQL WHEREMetadata过滤
适合场景开发测试小型项目企业级生产已有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高性能、分布式、支持大规模
快速上线SaaSPinecone全托管,专注业务逻辑
混合搜索需求pgvector结合SQL全文搜索
多租户SaaSMilvus (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
    }
}

🎓 小结

通过本文,你已经掌握:

  1. 向量数据库核心概念 - 为什么需要它,它解决什么问题
  2. 五大方案对比 - InMemory、Chroma、Milvus、pgvector、Pinecone的特点
  3. 集成实战 - 从零到一集成各种向量数据库
  4. 选型决策 - 根据业务场景做出正确选择
  5. 性能优化 - 索引、批处理、缓存等优化手段

向量数据库是RAG应用的核心组件,选对工具事半功倍。记住:

  • 开发阶段用InMemory,快速迭代
  • 小项目用Chroma,简单够用
  • 有PG用pgvector,降低成本
  • 大规模用Milvus,性能保证
  • 无运维用Pinecone,专注业务


💡 专家建议:不要过早优化!先用InMemory快速验证想法,确认方向后再选择合适的生产级方案。大多数应用在初期用Chroma就足够了。