Spring AI 技术细节:VectorStore 多库统一抽象
前置知识
- 了解 Spring 框架的 Bean 管理和依赖注入机制
- 知道向量数据库的基本操作:存储(add)、删除(delete)、相似度检索(similaritySearch)
- 熟悉策略模式(Strategy Pattern)的设计思想
- (扩展)了解 EmbeddingModel 的工作机制和向量化过程
核心概念
核心问题
市面上有多种向量数据库(Qdrant、Milvus、PGVector、Redis、Neo4j),每种都有独特的客户端 API。Spring AI 如何用统一的接口屏蔽底层差异,让业务代码无需修改就能切换不同存储后端?
生活类比
Type-C 统一接口:不同品牌的手机(各种向量数据库)都可以用同一根 Type-C 充电线(VectorStore 接口)充电,充电器内部自动适配不同协议。开发者只需面向统一接口编程,底层更换对用户无感。
核心思想
通过策略模式定义 VectorStore 统一接口,各向量库提供 Adapter 实现类;业务代码依赖接口而非实现,通过 Spring Bean 注入切换底层存储,实现开闭原则(OCP)和数据库可移植性。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
一、VectorStore 接口设计
Spring AI 通过 VectorStore 接口统一了向量数据库的操作,让开发者可以用相同的 API 操作不同的向量存储后端(Qdrant、Milvus、PGVector、Redis、Neo4j 等)。这种策略模式(Strategy Pattern)使得切换向量数据库时业务代码无需任何修改,只需调整配置中的 Bean 定义即可。
text
┌───────────────────────────────────────────────────────────────────────────┐
│ 业务代码层 │
│ │
│ @Service │
│ public class SearchService { │
│ private final VectorStore vectorStore; // 依赖接口,而非具体实现 │
│ │
│ public List<Document> search(String q) { │
│ return vectorStore.similaritySearch(q); // 多态调用 │
│ } │
│ } │
└────────────────────────────┬─────────────────────────────────────────────┘
│ VectorStore 接口
▼
┌───────────────────────────────────────────────────────────────────────────┐
│ VectorStore (统一抽象接口) │
│ │
│ + add(List<Document>): void │
│ + delete(List<String>): void │
│ + similaritySearch(SearchRequest): List<Document> │
│ + default similaritySearch(String): List<Document> │
└──────┬──────────────┬──────────────┬──────────────┬──────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ QdrantVS │ │ MilvusVS │ │ PGVector │ │ Neo4jVS │
│ (gRPC/HTTP) │ │ (REST/gRPC) │ │ (JDBC) │ │ (Bolt) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
接口设计的演进与版本兼容
Spring AI 的 VectorStore 接口并非一成不变,随着版本迭代也在不断丰富。早期的版本只有 add、delete、similaritySearch 三个核心方法,后续版本增加了:
java
// 1.7.0+ 版本新增的批量操作和配置能力
public interface VectorStore {
// ... 核心方法 ...
/**
* 批量写入,支持并行处理
* @since 1.7.0
*/
default void batchAdd(List<Document> documents,
BatchOptions options) {
// 默认实现:简单循环
documents.forEach(this::add);
}
/**
* 获取存储后端的状态和统计信息
* @since 1.8.0
*/
default StoreStats getStats() {
return StoreStats.UNKNOWN;
}
/**
* 执行原生查询(绕过统一抽象,用于特殊场景)
*/
default <T> T executeNativeQuery(Object nativeQuery,
Class<T> responseType) {
throw new UnsupportedOperationException(
"Native query not supported by this implementation");
}
}
设计权衡 :接口的扩展遵循"最小可用原则"------只暴露所有后端都支持的能力。对于某特定数据库的独有特性(如 Qdrant 的 Payload 索引、Milvus 的 Partition Key),可通过
executeNativeQuery绕过抽象层直接调用,或通过metadata字段传递额外参数。
二、Document 模型与 VectorStore 接口
Spring AI 的 VectorStore 围绕 Document 模型设计,文档包含内容、元数据和可选的预计算 Embedding:
java
/**
* Spring AI Document 模型(完整版)
*/
@Getter
@Builder
public class Document {
private String id;
private String content;
private Map<String, Object> metadata;
private float[] embedding; // 可选预计算的嵌入向量
// 扩展字段
private Float score; // 相似度分数(检索结果中填充)
private String collection; // 所属集合(多租户/多命名空间支持)
}
Document 的深层设计考量
1. Embedding 字段的语义与使用模式
embedding 字段是 float[] 类型,允许两种使用模式:
- 自动计算模式 :调用
add()时,VectorStore 内部通过注入的EmbeddingModel自动生成向量,此时embedding字段可为null。 - 预计算模式 :客户端提前计算好向量,传入已填充
embedding的 Document。这在以下场景非常有用:- 使用外部专用的 Embedding 服务(如通过 HTTP 调用独立的模型服务)
- 需要对同一个文本使用不同 Embedding 模型生成多个版本
- 缓存 Embedding 结果以节省计算资源
java
// 预计算模式示例
@Service
public class CachedEmbeddingService {
private final EmbeddingModel model;
private final Cache<String, float[]> embeddingCache;
public Document createWithCachedEmbedding(String text) {
float[] cached = embeddingCache.get(text, () -> model.embed(text));
return Document.builder()
.content(text)
.embedding(cached) // 使用缓存的向量
.metadata(Map.of("cached", true))
.build();
}
}
2. Metadata 的类型系统与序列化
元数据支持任意 Map<String, Object>,但不同后端对数据类型的支持有差异:
| 后端 | 支持的类型 | 特殊说明 |
|---|---|---|
| PGVector | JSONB | 支持任意 JSON 结构,可索引嵌套字段 |
| Redis | 原生类型 + Tag | 支持 String、Numeric、Geo、Tag |
| Qdrant | Payload JSON | 支持任意 JSON,可配置索引策略 |
| Milvus | Schema 预定义 | 需提前定义字段类型,不支持任意 Map |
跨后端兼容性建议:
- 元数据中仅使用 String、Number、Boolean、Instant(自动转时间戳)
- 避免嵌套过深的 JSON 结构
- 如需复杂查询,确保对应的字段在后端已建立索引
java
// 元数据类型安全的构建器
public class MetadataBuilder {
private final Map<String, Object> metadata = new LinkedHashMap<>();
public MetadataBuilder text(String key, String value) {
metadata.put(key, value);
return this;
}
public MetadataBuilder number(String key, Number value) {
metadata.put(key, value);
return this;
}
public MetadataBuilder tag(String key, String... values) {
// 特定后端支持 Tag 类型(如 Redis)
metadata.put(key, String.join(",", values));
return this;
}
public MetadataBuilder timestamp(String key, Instant time) {
metadata.put(key, time.toEpochMilli());
return this;
}
}
SearchRequest 的扩展参数
java
/**
* 完整版 SearchRequest 参数
*/
@Builder
public class SearchRequest {
private String query; // 查询文本
private float[] queryVector; // 预计算查询向量(与 query 二选一)
private int topK = 4; // 返回数量
private double similarityThreshold; // 相似度阈值
private String filterExpression; // 元数据过滤表达式
// 扩展参数
private int offset = 0; // 分页偏移(用于滚动检索)
private List<String> includeFields; // 仅返回指定字段(减少数据传输)
private List<String> excludeFields; // 排除指定字段
private String sortBy; // 排序字段(非相似度排序)
private String consistencyLevel; // 一致性级别(强一致/最终一致)
}
三、多存储后端配置
通过配置切换不同的向量存储实现,零业务代码修改:
java
@Configuration
public class VectorStoreConfig {
/**
* PGVector 实现:基于 PostgreSQL 的 pgvector 扩展
* 适合已有 PG 数据库的团队
*/
@Bean
@ConditionalOnProperty(name = "spring.ai.vectorstore.type",
havingValue = "pgvector")
public VectorStore pgVectorStore(JdbcTemplate jdbcTemplate,
EmbeddingModel embeddingModel) {
return PgVectorStore.builder(jdbcTemplate, embeddingModel)
.tableName("vector_documents")
.dimensions(1536)
.initializeSchema(true) // 自动建表
.schemaValidation(true) // 启动时校验 schema
.maxDocumentBatchSize(10000)
.build();
}
}
各后端实现深度对比
1. PGVector 实现细节
java
// PGVector 的核心 SQL 生成逻辑(简化)
public class PgVectorStore implements VectorStore {
private final JdbcTemplate jdbcTemplate;
private final String tableName;
private final int dimensions;
@Override
public void add(List<Document> documents) {
// 1. 调用 EmbeddingModel 生成向量
List<Object[]> batchArgs = documents.stream()
.map(doc -> {
float[] embedding = doc.getEmbedding();
if (embedding == null) {
embedding = embeddingModel.embed(doc.getContent());
}
return new Object[]{
doc.getId(),
doc.getContent(),
PGvector.fromArray(embedding), // pgvector 专用类型
Jsonb.fromMap(doc.getMetadata())
};
})
.collect(Collectors.toList());
// 2. 批量 UPSERT(使用 ON CONFLICT 处理重复)
String sql = """
INSERT INTO %s (id, content, embedding, metadata)
VALUES (?, ?, ?, ?)
ON CONFLICT (id) DO UPDATE SET
content = EXCLUDED.content,
embedding = EXCLUDED.embedding,
metadata = EXCLUDED.metadata
""".formatted(tableName);
jdbcTemplate.batchUpdate(sql, batchArgs);
}
@Override
public List<Document> similaritySearch(SearchRequest request) {
float[] queryVector = resolveQueryVector(request);
// 使用 pgvector 的 <=> 操作符计算余弦距离
String sql = """
SELECT id, content, metadata, 1 - (embedding <=> ?) AS score
FROM %s
WHERE metadata @> ?::jsonb -- 元数据过滤
ORDER BY score DESC
LIMIT ?
""".formatted(tableName);
return jdbcTemplate.query(sql,
new Object[]{ queryVector, filterJson, request.getTopK() },
this::mapRowToDocument
);
}
}
PGVector 的高级特性:
- 支持 IVFFlat 和 HNSW 两种索引,适用于不同数据量和精度要求
- 可结合 PostgreSQL 的 RLS(行级安全)实现细粒度权限控制
- 支持向量维度动态变化(但建议固定)
生产级配置:
yaml
spring:
ai:
vectorstore:
pgvector:
index-type: HNSW # 或 IVFFlat
index-lists: 100 # IVFFlat 的列表数
index-ops: vector_cosine_ops # 索引操作类
hnsw-m: 16 # HNSW 每层最大连接数
hnsw-ef-construction: 200 # HNSW 构建时动态列表大小
similarity-metric: COSINE # COSINE / EUCLIDEAN / DOT_PRODUCT
2. Redis 实现细节
java
// Redis 向量存储的特殊处理
public class RedisVectorStore implements VectorStore {
private final RedisClient redisClient;
private final String indexName;
private final String prefix;
@Override
public void add(List<Document> documents) {
// Redis 使用 RedisHash 存储每个文档
List<RedisCommand> commands = documents.stream()
.map(doc -> {
Map<String, Object> fields = new HashMap<>();
fields.put("content", doc.getContent());
fields.put("embedding", floatArrayToByteArray(doc.getEmbedding()));
fields.put("metadata", JsonUtils.toJson(doc.getMetadata()));
fields.put("_id", doc.getId());
// 使用 HSET 存储
return RedisCommand.hset(prefix + doc.getId(), fields);
})
.collect(Collectors.toList());
redisClient.send(commands);
}
@Override
public List<Document> similaritySearch(SearchRequest request) {
// 使用 Redis FT.SEARCH 命令
String query = String.format(
"(%s)=>[KNN %d @embedding $vec AS score]",
buildFilterExpression(request.getFilterExpression()),
request.getTopK()
);
// 执行 FT.SEARCH
return redisClient.ftSearch(indexName, query,
Map.of("params", Map.of("vec", queryVector)),
Map.of("dialect", 2)
);
}
}
Redis 独有特性:
- 支持向量和元数据的实时更新,延迟极低(亚毫秒级)
- 通过 Redis Stack 的 JSON 模块支持复杂元数据结构
- 支持向量检索与全文检索(RediSearch)的混合查询
3. Qdrant 实现细节
java
public class QdrantVectorStore implements VectorStore {
private final QdrantGrpcClient grpcClient;
private final String collectionName;
@Override
public void add(List<Document> documents) {
UpsertPointsRequest.Builder builder = UpsertPointsRequest.newBuilder()
.setCollectionName(collectionName);
documents.forEach(doc -> {
PointStruct point = PointStruct.newBuilder()
.setId(PointId.newBuilder()
.setUuid(doc.getId()).build())
.setVector(floatArrayToQdrantVector(doc.getEmbedding()))
.putAllPayload(metadataToPayload(doc.getMetadata()))
.build();
builder.addPoints(point);
});
grpcClient.upsert(builder.build());
}
}
Qdrant 高级特性:
- 支持多向量(Multi-Vector)和稀疏向量(Sparse Vector)
- 支持向量量化和压缩(Scalar Quantization、Binary Quantization)
- 支持分片(Sharding)和副本(Replication)的水平扩展
配置切换的最佳实践
使用配置中心动态切换
java
@Configuration
public class DynamicVectorStoreConfig {
@Bean
@ConditionalOnMissingBean
public VectorStore vectorStore(
ApplicationContext context,
VectorStoreProperties properties) {
// 从配置中心(如 Apollo / Nacos)动态读取
String storeType = properties.getType();
return switch (storeType) {
case "pgvector" -> context.getBean("pgVectorStore", VectorStore.class);
case "redis" -> context.getBean("redisVectorStore", VectorStore.class);
case "qdrant" -> context.getBean("qdrantVectorStore", VectorStore.class);
default -> throw new IllegalArgumentException(
"Unsupported vector store type: " + storeType);
};
}
}
多数据源并存(读写分离/灰度发布)
java
@Configuration
public class MultiVectorStoreConfig {
@Bean
@Primary // 主写入
public VectorStore primaryVectorStore() {
return pgVectorStore();
}
@Bean
@Qualifier("readonly")
public VectorStore readonlyVectorStore() {
// 从库只读副本,用于大查询场景
return pgVectorStoreReadReplica();
}
@Bean
@Qualifier("cache")
public VectorStore cacheVectorStore() {
// Redis 作为缓存层,TTL 较短
return redisVectorStore();
}
}
@Service
public class HybridSearchService {
private final VectorStore primaryStore;
private final VectorStore cacheStore;
public List<Document> searchWithCache(String query) {
// 先查缓存(Redis),miss 后查主库(PGVector)
List<Document> cached = cacheStore.similaritySearch(query);
if (!cached.isEmpty()) {
return cached;
}
List<Document> results = primaryStore.similaritySearch(query);
// 异步写入缓存
CompletableFuture.runAsync(() -> cacheStore.add(results));
return results;
}
}
四、VectorStore 的过滤检索
基于元数据的过滤检索是生产环境的核心需求,Spring AI 提供类 SQL 的过滤表达式:
java
@Service
public class FilteredVectorSearchService {
private final VectorStore vectorStore;
/**
* 元数据过滤检索示例
* 仅检索特定租户和分类下的文档
*/
public List<Document> searchWithFilter(String query,
String tenantId,
String category) {
String filterExpr = String.format(
"tenant_id == '%s' && category == '%s'",
tenantId, category
);
SearchRequest request = SearchRequest.builder()
.query(query)
.topK(10)
.similarityThreshold(0.65)
.filterExpression(filterExpr)
.build();
return vectorStore.similaritySearch(request);
}
}
过滤表达式的语法规范与跨后端兼容性
Spring AI 定义了一套统一的过滤表达式语法,底层适配器会将其转换为各后端原生的查询语言:
| 表达式语法 | 含义 | PGVector 转换 | Redis 转换 | Qdrant 转换 |
|---|---|---|---|---|
field == 'value' |
等值比较 | metadata @> '{"field":"value"}' |
@field:{value} |
field:value |
field != 'value' |
不等比较 | NOT (metadata @> '{"field":"value"}') |
-@field:{value} |
-field:value |
num > 100 |
大于 | (metadata->>'num')::numeric > 100 |
@num:[(100 inf] |
num > 100 |
field IN ['a','b'] |
包含 | metadata ? 'field' AND metadata->>'field' IN ('a','b') |
`@field:{a | b}` |
field CONTAINS 'keyword' |
字符串包含 | metadata->>'field' LIKE '%keyword%' |
@field:(keyword) |
field: keyword* |
| `(A && B) | C` | 逻辑组合 | 对应 SQL AND/OR |
注意 :filterExpression 的具体语法与每个后端的支持程度有关。Spring AI 提供了一套通用语法,但某些高级特性(如正则表达式、地理距离查询)可能只在特定后端支持。
复杂过滤表达式的构建
java
// 使用构建器模式构造复杂过滤条件
@Service
public class AdvancedFilterService {
public SearchRequest buildComplexFilter() {
// 条件:租户为 'tenant-123' 且 (分类为 'tech' 或 'product') 且 创建时间在最近7天内
String filter = FilterExpression.builder()
.and("tenant_id == 'tenant-123'")
.and(
FilterExpression.builder()
.or("category == 'tech'")
.or("category == 'product'")
.build()
)
.and("created_at >= " + Instant.now().minus(7, ChronoUnit.DAYS).toEpochMilli())
.build()
.toString();
return SearchRequest.builder()
.query("AI 技术趋势")
.topK(20)
.filterExpression(filter)
.build();
}
}
检索结果的后处理与重排序
java
@Service
public class RerankingSearchService {
private final VectorStore vectorStore;
private final CrossEncoderModel reranker; // 交叉编码器用于重排序
public List<Document> searchWithRerank(String query, int topK) {
// 1. 先召回 2倍数量的候选(保留排序空间)
List<Document> candidates = vectorStore.similaritySearch(
SearchRequest.builder()
.query(query)
.topK(topK * 2)
.similarityThreshold(0.6)
.build()
);
// 2. 使用 Cross-Encoder 重新评分(更准确但更慢)
List<ScoredDocument> reranked = reranker.rerank(query, candidates);
// 3. 截断并返回
return reranked.stream()
.limit(topK)
.map(ScoredDocument::getDocument)
.collect(Collectors.toList());
}
}
分页与滚动检索
对于大规模数据集,单次检索可能返回海量结果,需要分页支持:
java
@Service
public class PaginatedSearchService {
/**
* 使用 offset/limit 分页
*/
public SearchResult<Document> searchPage(String query, int page, int size) {
SearchRequest request = SearchRequest.builder()
.query(query)
.topK(size)
.offset(page * size) // 注意:offset 可能导致性能问题
.build();
List<Document> results = vectorStore.similaritySearch(request);
long total = countTotalMatches(query); // 需要额外统计
return new SearchResult<>(results, total, page, size);
}
/**
* 使用游标滚动(更适合大数据量导出)
*/
public List<Document> scrollSearch(String query, String cursor, int batchSize) {
SearchRequest request = SearchRequest.builder()
.query(query)
.topK(batchSize)
.filterExpression("score > " + cursor) // 基于上一页最后一条的分数
.build();
return vectorStore.similaritySearch(request);
}
}
五、写入与删除操作管理
VectorStore 的文档生命周期管理实践:
java
@Service
public class DocumentManagementService {
private final VectorStore vectorStore;
private final EmbeddingModel embeddingModel;
private final DocumentTransformer transformer; // 文档预处理
public void ingestDocuments(List<Document> documents) {
// 1. 文档预处理:清洗、分块、富化
List<Document> processed = transformer.process(documents);
// 2. 批量写入
vectorStore.add(processed);
}
}
写入性能优化
1. 批量大小调优
各后端推荐的批量大小:
| 后端 | 推荐批量大小 | 原因 |
|---|---|---|
| PGVector | 500 - 2000 | 受 JDBC batch 限制,过大导致内存溢出 |
| Redis | 1000 - 5000 | 管道(Pipeline)模式可高效处理 |
| Qdrant | 100 - 500 | gRPC 单次请求有大小限制,需平衡网络开销 |
| Milvus | 1000 - 10000 | 原生支持大批量导入 |
java
@Service
public class BatchIngestionService {
private final int BATCH_SIZE = 1000;
public void ingestLargeDataset(List<Document> allDocs) {
// 分批处理,避免单次请求过大
int total = allDocs.size();
for (int i = 0; i < total; i += BATCH_SIZE) {
int end = Math.min(i + BATCH_SIZE, total);
List<Document> batch = allDocs.subList(i, end);
// 异步提交,控制并发数
CompletableFuture.runAsync(() -> {
vectorStore.add(batch);
log.info("Processed batch {}-{}", i, end);
});
}
}
}
2. 并行写入与事务边界
java
@Service
public class ParallelIngestionService {
private final ExecutorService executor = Executors.newFixedThreadPool(4);
public void parallelIngest(List<Document> documents) {
// 按租户分片,并行写入
Map<String, List<Document>> shards = documents.stream()
.collect(Collectors.groupingBy(
doc -> doc.getMetadata().get("tenant_id").toString()
));
List<CompletableFuture<Void>> futures = shards.entrySet().stream()
.map(entry -> CompletableFuture.runAsync(
() -> vectorStore.add(entry.getValue()),
executor
))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.join(); // 等待所有分片完成
}
}
3. 写入失败的重试策略
java
@Service
public class ResilientIngestionService {
private final VectorStore vectorStore;
private final RetryTemplate retryTemplate;
public void ingestWithRetry(List<Document> documents) {
retryTemplate.execute(context -> {
try {
vectorStore.add(documents);
return null;
} catch (DataAccessException e) {
// 记录失败的文档ID,便于后续补偿
log.warn("Batch write failed, retry count: {}",
context.getRetryCount());
throw e;
}
});
}
}
删除策略与数据生命周期管理
1. 软删除模式
java
@Service
public class SoftDeleteService {
public void softDelete(String id) {
// 不真正删除,而是标记为已删除
Document doc = Document.builder()
.id(id)
.metadata(Map.of("deleted", true, "deleted_at", Instant.now()))
.build();
vectorStore.add(List.of(doc)); // 覆盖更新
}
public List<Document> searchActive(String query) {
// 检索时过滤掉已删除的文档
return vectorStore.similaritySearch(
SearchRequest.builder()
.query(query)
.filterExpression("deleted == false")
.build()
);
}
}
2. 基于时间窗口的数据过期
java
@Component
public class DataRetentionScheduler {
private final VectorStore vectorStore;
private final Duration retentionPeriod = Duration.ofDays(90);
@Scheduled(cron = "0 0 3 * * ?") // 每天凌晨3点执行
public void purgeExpiredData() {
Instant cutoff = Instant.now().minus(retentionPeriod);
// 先检索所有过期的文档ID
List<Document> expired = vectorStore.similaritySearch(
SearchRequest.builder()
.filterExpression("created_at < " + cutoff.toEpochMilli())
.topK(10000) // 每次最多删除1万条
.build()
);
if (!expired.isEmpty()) {
List<String> ids = expired.stream()
.map(Document::getId)
.collect(Collectors.toList());
vectorStore.delete(ids);
log.info("Purged {} expired documents", ids.size());
}
}
}
变更数据捕获(CDC)与向量同步
java
@Service
public class VectorSyncService {
private final VectorStore vectorStore;
private final ApplicationEventPublisher eventPublisher;
@EventListener
public void onDocumentUpdated(DocumentUpdatedEvent event) {
// 当业务数据库中的文档更新时,自动同步到向量库
Document doc = convertToDocument(event.getSource());
vectorStore.add(List.of(doc));
}
@EventListener
public void onDocumentDeleted(DocumentDeletedEvent event) {
vectorStore.delete(List.of(event.getId()));
}
}
六、异常处理与监控
扩展:向量存储操作的标准异常层次
java
// Spring AI 定义的异常层次
public class VectorStoreException extends RuntimeException {
private final String storeType;
private final String operation;
private final String errorCode;
}
// 具体子类
public class VectorStoreConnectionException extends VectorStoreException {}
public class VectorStoreIndexException extends VectorStoreException {}
public class VectorStoreValidationException extends VectorStoreException {}
public class VectorStoreTimeoutException extends VectorStoreException {}
操作拦截与监控指标
java
@Component
@Aspect
public class VectorStoreMonitor {
private final MeterRegistry meterRegistry;
@Around("execution(* org.springframework.ai.vectorstore.VectorStore.*(..))")
public Object monitorVectorStore(ProceedingJoinPoint joinPoint) throws Throwable {
String method = joinPoint.getSignature().getName();
Timer.Sample sample = Timer.start(meterRegistry);
try {
Object result = joinPoint.proceed();
meterRegistry.counter("vectorstore.operations",
"method", method, "status", "success").increment();
return result;
} catch (Exception e) {
meterRegistry.counter("vectorstore.operations",
"method", method, "status", "error",
"error_type", e.getClass().getSimpleName()).increment();
throw e;
} finally {
sample.stop(Timer.builder("vectorstore.duration")
.tag("method", method)
.register(meterRegistry));
}
}
}
健康检查
java
@Component
public class VectorStoreHealthIndicator implements HealthIndicator {
private final VectorStore vectorStore;
@Override
public Health health() {
try {
// 执行一个轻量级查询验证连通性
vectorStore.similaritySearch(
SearchRequest.builder()
.query("health check")
.topK(1)
.build()
);
return Health.up()
.withDetail("store", vectorStore.getClass().getSimpleName())
.build();
} catch (Exception e) {
return Health.down(e)
.withDetail("error", e.getMessage())
.build();
}
}
}
七、总结
本章深入 Spring AI VectorStore 的多库统一抽象层。通过单一接口支持 PGVector、Redis、Qdrant、Milvus、Neo4j 等多种后端,实现了向量存储层的供应商过滤和零代码切换。Document 模型和 SearchRequest 提供了元数据过滤、相似度阈值和分页等丰富的检索能力。
各后端选型建议
| 场景 | 推荐后端 | 理由 |
|---|---|---|
| 中小规模项目,已有 PostgreSQL | PGVector | 复用现有基础设施,维护成本低 |
| 大规模生产环境(>100万向量) | Qdrant / Milvus | 专用优化,支持水平扩展和高级索引 |
| 高吞吐、低延迟实时场景 | Redis | 内存存储,毫秒级响应 |
| 图数据库场景(知识图谱) | Neo4j | 向量检索与图查询结合 |
| AWS 生态 | Aurora pgvector | 云原生托管,免运维 |
最佳实践总结
- 接口依赖 :业务代码始终依赖
VectorStore接口,而非具体实现类 - 批量操作:使用批量 API 而非单条操作,显著提升吞吐量
- 索引策略:根据数据量选择合适的索引类型(IVFFlat vs HNSW)
- 元数据设计:保持元数据扁平化,避免过度嵌套
- 监控告警:集成指标监控和健康检查,及时发现存储层问题
- 降级策略:为 VectorStore 操作设置超时和重试机制
- 数据版本管理:使用元数据字段追踪文档版本,支持回滚
- 灰度切换:生产环境中使用多数据源逐步切换,降低变更风险