第10天:Vector Store 集成——Milvus / Redis / PostgreSQL 全面解析

系列文章:Spring AI Alibaba 技术博客系列
难度等级:⭐⭐⭐⭐
前置知识:Embedding 向量嵌入基础、Spring Boot 基础
本篇目标:深入理解 Vector Store 在 AI 应用中的角色,掌握 Milvus、Redis、PostgreSQL 三种主流向量数据库的集成方式与选型策略


目录

  1. 为什么需要 Vector Store?
  2. Vector Store 在 Spring AI Alibaba 中的抽象设计
  3. Milvus 集成实战
  4. Redis 向量检索集成实战
  5. PostgreSQL (pgvector) 集成实战
  6. 三种 Vector Store 对比与选型指南
  7. 最佳实践与性能优化
  8. 常见问题与排查

1. 为什么需要 Vector Store?

在上一篇文章中,我们学习了 Embedding 向量嵌入——将文本转换为高维向量表示。但一个核心问题随之而来:这些向量如何高效存储和检索?

传统的关系型数据库擅长精确匹配和范围查询,但对于”语义相似度搜索”这种模糊匹配场景,效率极低。想象一下,如果你有100万条文档的向量(每条768维),要找到与查询向量最相似的Top-K条记录,全表扫描的复杂度是 O(N×D),N为向量数,D为维度数——这在实时场景下是不可接受的。

Vector Store(向量数据库)就是为这个问题而生的。它通过以下核心技术实现高效检索:

  • ANN(Approximate Nearest Neighbor)算法:如 HNSW、IVF、PQ 等,将搜索复杂度降至 O(log N) 级别
  • 向量索引结构:在向量空间上构建索引,支持快速相似度计算
  • 元数据过滤:在向量检索的同时支持条件过滤(如按时间、标签、用户ID等)
  • 分布式扩展:支持海量向量数据的水平扩展

Vector Store 在 RAG 架构中的位置

┌─────────────────────────────────────────────────────────┐
│                    RAG 整体架构                          │
│                                                         │
│  用户提问 ──→ Prompt 工程 ──→ LLM ──→ 回答              │
│                  ↑                                      │
│              检索结果                                    │
│                  ↑                                      │
│  查询向量 ←── Embedding Model                           │
│                  ↑                                      │
│         ┌────────┴────────┐                             │
│         │  Vector Store    │ ←── 向量 + 元数据           │
│         │  (Milvus/Redis/  │                             │
│         │   PostgreSQL)    │                             │
│         └─────────────────┘                             │
└─────────────────────────────────────────────────────────┘

2. Vector Store 在 Spring AI Alibaba 中的抽象设计

Spring AI Alibaba 基于 Spring AI 的标准接口,对 Vector Store 进行了统一的抽象设计。核心接口如下:

2.1 VectorStore 接口

package org.springframework.ai.vectorstore;

public interface VectorStore {
    
    /**
     * 添加文档(自动将文档内容通过 EmbeddingModel 转为向量后存储)
     */
    void add(List<Document> documents);
    
    /**
     * 删除文档
     */
    boolean delete(List<String> idList);
    
    /**
     * 相似度搜索
     */
    List<Document> similaritySearch(SearchRequest request);
    
    /**
     * 搜索请求构建器
     */
    interface SearchRequest {
        static SearchRequest query(String query) { ... }
        SearchRequest withTopK(int topK);
        SearchRequest withSimilarityThreshold(double threshold);
        SearchRequest withFilterExpression(String filterExpression);
    }
}

2.2 设计哲学

Spring AI Alibaba 的 Vector Store 设计遵循以下原则:

  1. 透明化 Embedding:开发者只需传入 Document 对象,底层自动调用 EmbeddingModel 进行向量化,无需手动处理向量转换
  2. 统一的搜索接口:无论底层使用哪种向量数据库,搜索 API 保持一致
  3. 可插拔实现:通过 Spring Boot 的自动配置机制,切换 Vector Store 只需修改依赖和配置

2.3 Document 数据结构

public class Document {
    private String id;              // 文档唯一标识
    private String text;            // 文档文本内容
    private Map<String, Object> metadata;  // 元数据(用于过滤)
    private MediaType mediaType;    // 媒体类型
    // ...
}

metadata 字段是 Vector Store 过滤能力的核心。例如:

Document doc = new Document(
    "doc-001",
    "Spring Boot 3.0 引入了虚拟线程支持...",
    Map.of(
        "source", "spring-boot-docs",
        "category", "framework",
        "version", "3.0",
        "created_at", "2026-05-04"
    )
);

3. Milvus 集成实战

Milvus 是目前最流行的开源向量数据库之一,由 Zilliz 公司开发,支持十亿级向量检索,具有出色的性能和丰富的索引类型。

3.1 Milvus 简介

Milvus 的核心特性:

  • 多种索引类型:IVF_FLAT、IVF_PQ、HNSW、DISKANN、SCANN 等
  • 多种距离度量:L2、IP(内积)、COSINE(余弦相似度)
  • 混合搜索:向量检索 + 标量过滤
  • 分布式架构:支持水平扩展,适合生产环境
  • 多语言 SDK:Java、Python、Go、Node.js 等

3.2 环境准备

使用 Docker 快速部署 Milvus 单机版:

# 下载 docker-compose 配置文件
wget https://github.com/milvus-io/milvus/releases/download/v2.4.0/milvus-standalone-docker-compose.yml -O docker-compose.yml

# 启动 Milvus
docker-compose up -d

启动后,Milvus 会在 19530 端口提供服务,管理界面在 9121 端口。

3.3 添加 Maven 依赖

<dependencies>
    <!-- Spring AI Alibaba -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter</artifactId>
        <version>1.0.0-M6.1</version>
    </dependency>
    
    <!-- Milvus Vector Store -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-milvus-store-spring-boot-starter</artifactId>
    </dependency>
</dependencies>

<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots><enabled>false</enabled></snapshots>
    </repository>
</repositories>

3.4 配置文件

spring:
  ai:
    # DashScope Embedding 配置
    dashscope:
      api-key: ${DASHSCOPE_API_KEY}
      embedding:
        options:
          model: text-embedding-v3
          dimensions: 1024
    
    # Milvus Vector Store 配置
    vectorstore:
      milvus:
        host: localhost
        port: 19530
        databaseName: default
        collectionName: spring_ai_documents
        indexType: HNSW          # 索引类型
        metricType: COSINE       # 距离度量方式
        embeddingDimension: 1024 # 向量维度(需与 Embedding 模型一致)
        username: ""             # 可选
        password: ""             # 可选

3.5 核心代码示例

3.5.1 向量存储配置类

@Configuration
public class VectorStoreConfig {

    @Bean
    public CommandLineRunner initVectorStore(VectorStore vectorStore) {
        return args -> {
            log.info("Vector Store 初始化完成,类型: {}", vectorStore.getClass().getSimpleName());
        };
    }
}

3.5.2 文档入库服务

@Service
@Slf4j
@RequiredArgsConstructor
public class DocumentIngestionService {

    private final VectorStore vectorStore;

    /**
     * 批量导入文档到向量库
     */
    public void ingestDocuments(List<String> texts, String source, String category) {
        List<Document> documents = texts.stream()
            .map(text -> new Document(
                UUID.randomUUID().toString(),
                text,
                Map.of(
                    "source", source,
                    "category", category,
                    "ingested_at", LocalDateTime.now().toString()
                )
            ))
            .toList();

        vectorStore.add(documents);
        log.info("成功导入 {} 条文档到 Vector Store", documents.size());
    }

    /**
     * 从文件导入(简化版)
     */
    public void ingestFromFile(Path filePath, String source) throws IOException {
        String content = Files.readString(filePath);
        
        // 实际项目中应该使用 TextSplitter 进行分块
        // 这里简化处理,按段落分割
        List<String> chunks = Arrays.asList(content.split("\n\n"));
        
        ingestDocuments(chunks, source, "file");
    }
}

3.5.3 向量检索服务

@Service
@Slf4j
@RequiredArgsConstructor
public class VectorSearchService {

    private final VectorStore vectorStore;

    /**
     * 基础相似度搜索
     */
    public List<Document> search(String query, int topK) {
        return vectorStore.similaritySearch(
            SearchRequest.query(query)
                .withTopK(topK)
                .withSimilarityThreshold(0.7)
        );
    }

    /**
     * 带过滤条件的搜索
     */
    public List<Document> searchWithFilter(String query, int topK, 
                                            String category, String source) {
        // 构建过滤表达式
        StringBuilder filter = new StringBuilder();
        if (category != null) {
            filter.append("category == '").append(category).append("'");
        }
        if (source != null) {
            if (!filter.isEmpty()) filter.append(" && ");
            filter.append("source == '").append(source).append("'");
        }

        return vectorStore.similaritySearch(
            SearchRequest.query(query)
                .withTopK(topK)
                .withSimilarityThreshold(0.7)
                .withFilterExpression(filter.toString())
        );
    }

    /**
     * 删除文档
     */
    public boolean deleteDocuments(List<String> documentIds) {
        boolean result = vectorStore.delete(documentIds);
        log.info("删除文档结果: {}, IDs: {}", result, documentIds);
        return result;
    }
}

3.5.4 REST API 控制器

@RestController
@RequestMapping("/api/vector")
@RequiredArgsConstructor
@Slf4j
public class VectorStoreController {

    private final DocumentIngestionService ingestionService;
    private final VectorSearchService searchService;

    /**
     * 搜索接口
     */
    @PostMapping("/search")
    public ResponseEntity<List<Map<String, Object>>> search(
            @RequestBody SearchRequestDTO request) {
        
        List<Document> results;
        
        if (request.hasFilter()) {
            results = searchService.searchWithFilter(
                request.getQuery(),
                request.getTopK(),
                request.getCategory(),
                request.getSource()
            );
        } else {
            results = searchService.search(request.getQuery(), request.getTopK());
        }

        List<Map<String, Object>> response = results.stream()
            .map(doc -> Map.of(
                "id", doc.getId(),
                "text", doc.getText(),
                "metadata", doc.getMetadata(),
                "score", doc.getMetadata().get("score")
            ))
            .toList();

        return ResponseEntity.ok(response);
    }

    /**
     * 批量导入接口
     */
    @PostMapping("/ingest")
    public ResponseEntity<String> ingest(@RequestBody IngestRequest request) {
        ingestionService.ingestDocuments(
            request.getTexts(),
            request.getSource(),
            request.getCategory()
        );
        return ResponseEntity.ok("导入成功");
    }

    // DTO 类
    public record SearchRequestDTO(
        String query,
        int topK,
        String category,
        String source
    ) {
        public boolean hasFilter() {
            return category != null || source != null;
        }
    }

    public record IngestRequest(
        List<String> texts,
        String source,
        String category
    ) {}
}

3.6 Milvus 索引类型选择

Milvus 提供多种索引类型,选择合适的索引对性能至关重要:

索引类型适用场景内存占用搜索速度构建速度
FLAT小数据量(<1万),精确搜索
IVF_FLAT中等数据量,均衡型
IVF_PQ大数据量,内存受限
HNSW高召回率要求,内存充足
DISKANN超大数据量(亿级),磁盘存储极低

选择建议

  • 数据量 < 10万:HNSW(M=16, efConstruction=64)
  • 数据量 10万~100万:IVF_FLAT(nlist=1024)
  • 数据量 > 100万:IVF_PQ 或 DISKANN

4. Redis 向量检索集成实战

Redis 7.2+ 版本通过 RediSearch 模块原生支持向量检索。如果你的项目已经在使用 Redis,集成向量检索是非常自然的选择。

4.1 Redis 向量检索简介

Redis 向量检索的特点:

  • 一站式存储:向量、文档、元数据、缓存共存于 Redis
  • 低延迟:内存数据库,响应时间通常在毫秒级
  • HNSW + FLAT 索引:支持两种索引算法
  • 混合查询:向量搜索 + 全文搜索 + 精确过滤
  • Redis Stack:通过 Redis Stack 一键部署所有模块

4.2 环境准备

# 使用 Redis Stack(包含 RediSearch、RedisJSON 等模块)
docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 \
  redis/redis-stack:latest

4.3 添加 Maven 依赖

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-redis-store-spring-boot-starter</artifactId>
</dependency>

4.4 配置文件

spring:
  ai:
    dashscope:
      api-key: ${DASHSCOPE_API_KEY}
      embedding:
        options:
          model: text-embedding-v3
          dimensions: 1024
    
    vectorstore:
      redis:
        host: localhost
        port: 6379
        index: spring_ai_index
        prefix: "doc:"
        initialize-schema: true    # 自动创建索引

4.5 Redis Vector Store 核心代码

Redis Vector Store 的使用方式与 Milvus 基本一致,体现了 Spring AI 统一抽象的优势:

@Service
@Slf4j
@RequiredArgsConstructor
public class RedisVectorService {

    private final VectorStore vectorStore;

    /**
     * 入库
     */
    public void addDocuments(List<Document> documents) {
        vectorStore.add(documents);
        log.info("添加 {} 条文档到 Redis Vector Store", documents.size());
    }

    /**
     * 搜索 - 注意 Redis 的过滤表达式语法
     */
    public List<Document> search(String query, int topK, String category) {
        SearchRequest request = SearchRequest.query(query)
            .withTopK(topK)
            .withSimilarityThreshold(0.6);

        if (category != null) {
            // Redis 的过滤语法使用 @field:value 格式
            request = request.withFilterExpression("@category:{" + category + "}");
        }

        return vectorStore.similaritySearch(request);
    }
}

4.6 Redis 向量索引的底层原理

Redis 使用 FT.CREATE 命令创建向量索引:

FT.CREATE spring_ai_index 
  ON HASH 
  PREFIX 1 "doc:"
  SCHEMA
    $.vector AS vector VECTOR HNSW 6 
      TYPE FLOAT32 
      DIM 1024 
      DISTANCE_METRIC COSINE
    $.text AS text TEXT
    $.category AS category TAG
    $.source AS source TAG

关键参数解释:

  • HNSW:使用 HNSW 索引算法
  • TYPE FLOAT32:向量数据类型
  • DIM 1024:向量维度
  • DISTANCE_METRIC COSINE:余弦相似度度量
  • 6:后续参数数量(TYPE, DIM, DISTANCE_METRIC 各占2个参数)

4.7 Redis 作为 Vector Store 的适用场景

适合

  • 已有 Redis 基础设施,想快速增加向量检索能力
  • 数据量不大(百万级以下)
  • 对延迟敏感(毫秒级响应)
  • 需要同时做缓存、会话存储、向量检索

不适合

  • 数据量超大规模(十亿级)
  • 需要复杂的多租户隔离
  • 需要多种索引类型的灵活切换

5. PostgreSQL (pgvector) 集成实战

pgvector 是 PostgreSQL 的向量扩展,让你在关系型数据库中直接进行向量检索。这是目前最轻量、最容易部署的向量存储方案。

5.1 pgvector 简介

pgvector 的核心特点:

  • 零额外组件:只需在现有 PostgreSQL 上安装扩展
  • SQL 原生支持:可以用 SQL 进行向量查询、JOIN、聚合
  • 事务支持:向量操作可以在事务中进行
  • IVFFlat + HNSW 索引:PostgreSQL 16+ 支持 HNSW
  • 混合查询:向量搜索 + 关系型查询完美结合

5.2 环境准备

# 使用官方 pgvector 镜像
docker run -d --name pgvector \
  -e POSTGRES_PASSWORD=postgres \
  -p 5432:5432 \
  pgvector/pgvector:pg16

# 连接并启用扩展
docker exec -it pgvector psql -U postgres -c "CREATE EXTENSION vector;"

5.3 添加 Maven 依赖

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
</dependency>

<!-- PostgreSQL JDBC 驱动 -->
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>

5.4 配置文件

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/vector_db
    username: postgres
    password: postgres
    driver-class-name: org.postgresql.Driver
  
  ai:
    dashscope:
      api-key: ${DASHSCOPE_API_KEY}
      embedding:
        options:
          model: text-embedding-v3
          dimensions: 1024
    
    vectorstore:
      pgvector:
        index-type: HNSW           # 索引类型(IVFFLAT 或 HNSW)
        distance-type: COSINE_DISTANCE  # 距离类型
        initialize-schema: true    # 自动建表
        dimensions: 1024           # 向量维度

5.5 底层 SQL 解析

pgvector 在数据库中创建如下表结构:

-- 创建向量表
CREATE TABLE vector_store (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    content TEXT,
    metadata JSON,
    embedding vector(1024)  -- pgvector 类型
);

-- 创建 HNSW 索引
CREATE INDEX ON vector_store 
  USING hnsw (embedding vector_cosine_ops) 
  WITH (m = 16, ef_construction = 64);

搜索时执行的 SQL:

SELECT id, content, metadata, 
       embedding <=> '[0.1, 0.2, ...]' AS distance  -- 余弦距离
FROM vector_store 
WHERE metadata->>'category' = 'framework'
ORDER BY embedding <=> '[0.1, 0.2, ...]'
LIMIT 5;

<=> 是 pgvector 的余弦距离运算符。其他运算符:

  • <->:欧氏距离(L2)
  • <#>:负内积(用于最大内积搜索)

5.6 PostgreSQL Vector Store 核心代码

@Service
@Slf4j
@RequiredArgsConstructor
public class PgVectorService {

    private final VectorStore vectorStore;
    private final JdbcTemplate jdbcTemplate;

    /**
     * 入库
     */
    public void addDocuments(List<Document> documents) {
        vectorStore.add(documents);
    }

    /**
     * 利用 JdbcTemplate 执行自定义查询
     * 展示 pgvector 的优势:可以直接写 SQL
     */
    public List<Map<String, Object>> customSearch(String query, 
            int topK, String category) {
        
        // 注意:实际项目中应该通过 EmbeddingModel 将 query 转为向量
        // 这里简化处理
        String sql = """
            SELECT id, content, metadata,
                   embedding <=> ?::vector AS similarity
            FROM vector_store
            WHERE metadata->>'category' = ?
            ORDER BY similarity
            LIMIT ?
            """;
        
        // Spring AI 的 VectorStore 已经封装了 Embedding 过程
        // 实际开发推荐直接使用 VectorStore.similaritySearch()
        return vectorStore.similaritySearch(
            SearchRequest.query(query)
                .withTopK(topK)
                .withSimilarityThreshold(0.5)
                .withFilterExpression("category = '" + category + "'")
        ).stream()
            .map(doc -> Map.of(
                "id", doc.getId(),
                "text", doc.getText(),
                "metadata", doc.getMetadata()
            ))
            .toList();
    }

    /**
     * 利用关系型能力:JOIN 查询
     * 这是 pgvector 独有的优势
     */
    public List<Map<String, Object>> searchWithJoin(String query, int topK) {
        // 假设有 users 表和 document_owners 关联表
        String sql = """
            SELECT vs.id, vs.content, vs.metadata, u.username,
                   vs.embedding <=> ?::vector AS similarity
            FROM vector_store vs
            JOIN document_owners do ON vs.id = do.document_id
            JOIN users u ON do.user_id = u.id
            WHERE u.is_active = true
            ORDER BY similarity
            LIMIT ?
            """;
        // ...
        return List.of();
    }
}

5.7 pgvector 索引优化

IVFFlat 索引

-- 创建 IVFFlat 索引
CREATE INDEX ON vector_store 
  USING ivfflat (embedding vector_cosine_ops) 
  WITH (lists = 100);

-- 搜索时设置 probes(影响精度和速度)
SET ivfflat.probes = 10;
  • lists:聚类中心数,建议 = 数据量 / 1000
  • probes:搜索时探查的聚类数,越大越精确但越慢

HNSW 索引(推荐)

-- 创建 HNSW 索引
CREATE INDEX ON vector_store 
  USING hnsw (embedding vector_cosine_ops) 
  WITH (m = 16, ef_construction = 64);

-- 搜索时设置 ef_search
SET hnsw.ef_search = 40;
  • m:每个节点的最大连接数(默认16),越大越精确但内存越大
  • ef_construction:构建索引时的搜索宽度(默认64)
  • ef_search:查询时的搜索宽度,越大越精确但越慢

6. 三种 Vector Store 对比与选型指南

6.1 功能对比

特性MilvusRedis (RediSearch)PostgreSQL (pgvector)
向量索引类型HNSW/IVF/PQ/DISKANN/SCANNHNSW/FLATHNSW/IVFFlat
最大数据量十亿级百万级(受内存限制)亿级
距离度量L2/IP/COSINE/JACCARD/HAMMINGL2/IP/COSINEL2/IP/COSINE
元数据过滤✅ 支持复杂表达式✅ 支持✅ SQL 原生支持
分布式✅ 原生分布式⚠️ Redis Cluster 有限支持⚠️ 需外部方案
事务支持
部署复杂度中(需要 etcd + MinIO)低(单进程)极低(PG 扩展)
运维成本低(已有 PG 团队)
社区活跃度
许可协议Apache 2.0RSALv2/SSPLv1PostgreSQL License

6.2 选型决策树

你的项目需要向量存储吗?
│
├── 已有 PostgreSQL 且数据量 < 亿级?
│   └── 是 → pgvector(最省事,零额外运维)
│   └── 否 ↓
│
├── 已有 Redis 且数据量 < 百万级?
│   └── 是 → Redis(复用基础设施)
│   └── 否 ↓
│
├── 数据量 > 百万级或需要分布式?
│   └── 是 → Milvus(专业向量数据库)
│   └── 否 ↓
│
└── 需要极简方案?
    └── pgvector(一个扩展搞定)

6.3 实际项目中的常见组合

项目类型推荐方案理由
小型 RAG Demopgvector零额外依赖,快速验证
企业知识库(<50万文档)Redis 或 pgvector运维成本低,已有基础设施
大规模 AI 平台(>百万文档)Milvus分布式、高性能、多租户
已有 Redis 的微服务Redis复用现有 Redis 集群
金融/医疗等需要事务的场景pgvectorACID 事务保证

7. 最佳实践与性能优化

7.1 Embedding 维度选择

spring:
  ai:
    dashscope:
      embedding:
        options:
          # 选择合适的维度
          # text-embedding-v3 支持:
          #   - 1024 维(推荐,精度最高)
          #   - 768 维(兼容性好)
          #   - 512 维(存储节省)
          dimensions: 1024

维度选择原则

  • 维度越高,语义表达能力越强,但存储和计算开销越大
  • text-embedding-v3 的 1024 维是性价比最高的选择
  • 存储节省公式:100万条 × 1024维 × 4字节 = 4GB

7.2 分块策略(Chunking)

向量检索的精度很大程度上取决于文本分块策略:

@Service
public class DocumentSplitterService {

    /**
     * 固定大小分块(最简单)
     */
    public List<String> chunkBySize(String text, int chunkSize, int overlap) {
        List<String> chunks = new ArrayList<>();
        int start = 0;
        while (start < text.length()) {
            int end = Math.min(start + chunkSize, text.length());
            chunks.add(text.substring(start, end));
            start = end - overlap;  // 重叠区域保持上下文连贯
        }
        return chunks;
    }

    /**
     * 按段落分块(推荐用于技术文档)
     */
    public List<String> chunkByParagraph(String text) {
        return Arrays.stream(text.split("\n\n"))
            .filter(p -> !p.trim().isEmpty())
            .toList();
    }

    /**
     * 递归分块(推荐用于长文档)
     * 先按章节分,再按段落分,最后按句子分
     */
    public List<String> recursiveChunk(String text, int maxChunkSize) {
        // 第一层:按章节
        List<String> chunks = splitByPattern(text, "\n#+ ");
        
        // 第二层:对超大块按段落分割
        List<String> result = new ArrayList<>();
        for (String chunk : chunks) {
            if (chunk.length() > maxChunkSize) {
                result.addAll(chunkByParagraph(chunk));
            } else {
                result.add(chunk);
            }
        }
        
        // 第三层:对仍然超大的块按句子分割
        return result.stream()
            .flatMap(c -> {
                if (c.length() > maxChunkSize) {
                    return splitByPattern(c, "[。!?]").stream();
                }
                return Stream.of(c);
            })
            .filter(s -> !s.trim().isEmpty())
            .toList();
    }
}

分块大小建议

  • 512~1024 字符:适合问答系统
  • 1024~2048 字符:适合技术文档
  • 2048~4096 字符:适合长文章摘要

7.3 批量入库优化

@Service
public class BatchIngestionService {

    private final VectorStore vectorStore;
    
    // 批量大小建议:100~500
    private static final int BATCH_SIZE = 200;

    /**
     * 批量入库,带进度回调
     */
    public void batchIngest(List<Document> documents, 
                            Consumer<IngestProgress> progressCallback) {
        
        int total = documents.size();
        int processed = 0;
        
        // 分批处理
        for (int i = 0; i < total; i += BATCH_SIZE) {
            int end = Math.min(i + BATCH_SIZE, total);
            List<Document> batch = documents.subList(i, end);
            
            vectorStore.add(batch);
            processed += batch.size();
            
            progressCallback.accept(
                new IngestProgress(processed, total, 
                    String.format("%.1f%%", processed * 100.0 / total))
            );
        }
    }

    public record IngestProgress(int processed, int total, String percentage) {}
}

7.4 搜索参数调优

public List<Document> optimizedSearch(String query, SearchContext context) {
    // 根据场景动态调整参数
    SearchRequest request = SearchRequest.query(query);
    
    if (context.isStrict()) {
        // 严格模式:高相似度阈值,少结果
        request = request
            .withTopK(3)
            .withSimilarityThreshold(0.85);
    } else if (context.isExploratory()) {
        // 探索模式:低阈值,多结果
        request = request
            .withTopK(10)
            .withSimilarityThreshold(0.5);
    } else {
        // 默认模式
        request = request
            .withTopK(5)
            .withSimilarityThreshold(0.7);
    }
    
    // 添加元数据过滤
    if (context.getCategory() != null) {
        request = request.withFilterExpression(
            "category == '" + context.getCategory() + "'"
        );
    }
    
    return vectorStore.similaritySearch(request);
}

7.5 多 Vector Store 路由

在复杂项目中,可能需要同时使用多种 Vector Store:

@Configuration
public class MultiVectorStoreConfig {

    @Bean
    @Qualifier("milvusStore")
    public VectorStore milvusVectorStore(EmbeddingModel embeddingModel,
                                          MilvusServiceClient milvusClient) {
        return new MilvusVectorStore(MilvusVectorStoreConfig.builder()
            .withCollectionName("production_docs")
            .withDimensions(1024)
            .withIndexType(IndexType.HNSW)
            .withMetricType(MetricType.COSINE)
            .build(), embeddingModel, milvusClient, true);
    }

    @Bean
    @Qualifier("redisStore")
    public VectorStore redisVectorStore(EmbeddingModel embeddingModel,
                                         JedisConnectionFactory connectionFactory) {
        return new RedisVectorStore(RedisVectorStoreConfig.builder()
            .withIndexName("cache_docs")
            .withPrefix("cache:")
            .build(), embeddingModel, connectionFactory, true);
    }
}

@Service
public class VectorStoreRouter {

    private final Map<String, VectorStore> stores;

    public VectorStoreRouter(@Qualifier("milvusStore") VectorStore milvus,
                              @Qualifier("redisStore") VectorStore redis) {
        this.stores = Map.of(
            "production", milvus,
            "cache", redis
        );
    }

    public VectorStore getStore(String type) {
        return stores.getOrDefault(type, stores.get("production"));
    }
}

8. 常见问题与排查

8.1 向量维度不匹配

错误现象dimension mismatch: expected 1024, got 768

原因:Embedding 模型配置的维度与 Vector Store 配置的维度不一致。

解决方案

# 确保两处维度一致
spring:
  ai:
    dashscope:
      embedding:
        options:
          dimensions: 1024  # ← Embedding 模型维度
    vectorstore:
      milvus:
        embeddingDimension: 1024  # ← Vector Store 维度,必须一致

8.2 Milvus 连接失败

错误现象ConnectException: Connection refused

排查步骤

# 1. 检查 Milvus 容器状态
docker ps | grep milvus

# 2. 检查端口
docker port milvus-standalone

# 3. 检查 Milvus 日志
docker logs milvus-standalone

# 4. 测试连接
docker exec -it milvus-standalone milvus-util -host localhost -port 19530

8.3 搜索结果不相关

排查清单

  1. 检查 Embedding 模型选择(text-embedding-v3 效果最好)
  2. 检查向量维度是否匹配
  3. 检查距离度量方式(推荐 COSINE)
  4. 调整相似度阈值(太低会返回不相关结果)
  5. 优化分块策略(块太大或太小都会影响精度)
  6. 检查是否有元数据过滤导致结果被过滤掉

8.4 Redis 索引创建失败

错误现象ERR unknown command FT.CREATE

原因:Redis 版本不支持 RediSearch 模块。

解决方案:使用 Redis Stack 而非标准 Redis。

# 错误:标准 Redis 不支持向量检索
docker run redis:latest

# 正确:使用 Redis Stack
docker run redis/redis-stack:latest

8.5 pgvector 扩展未启用

错误现象type "vector" does not exist

解决方案

-- 连接 PostgreSQL 后执行
CREATE EXTENSION IF NOT EXISTS vector;

-- 验证
SELECT extname FROM pg_extension WHERE extname = 'vector';

总结

本文深入介绍了 Spring AI Alibaba 中 Vector Store 的集成方案,涵盖了 Milvus、Redis、PostgreSQL (pgvector) 三种主流向量数据库。

核心要点回顾

  1. Spring AI 的统一抽象:无论使用哪种 Vector Store,API 接口保持一致,切换只需改配置
  2. Milvus:专业向量数据库,适合大规模生产环境,支持多种索引和分布式部署
  3. Redis:适合已有 Redis 基础设施、数据量不大的场景,毫秒级延迟
  4. pgvector:最轻量方案,一个扩展即可在 PostgreSQL 中获得向量检索能力
  5. 选型核心原则:根据数据量、现有基础设施、运维能力综合判断
  6. 性能优化关键:合理选择 Embedding 维度、分块策略、索引类型和搜索参数

下一篇预告:第11天——RAG 检索增强生成架构,我们将把 Embedding + Vector Store + ChatModel 串联起来,构建完整的 RAG 系统。敬请期待!