1. 当Spring遇上AI:为什么我们需要向量数据库?
在传统Spring应用开发中,我们处理的数据大多是结构化的——用户信息存MySQL、商品数据放MongoDB、缓存丢Redis。但当我们要处理非结构化数据(如PDF文档、图片、语音)时,传统数据库就像用螺丝刀切菜,完全不对路子。这就是向量数据库的用武之地。
向量数据库的核心能力是将文本、图像等数据转换为高维向量(一组数字),然后通过计算向量间的距离(如余弦相似度)来评估相似性。举个例子:当用户搜索"如何解决Spring事务不回滚",系统不仅能匹配关键词,还能找到"@Transactional失效的5种情况"这类语义相关但字面不匹配的内容。
注意:向量维度通常为768或1024维,OpenAI的text-embedding-ada-002模型输出就是1536维向量。维度越高表征能力越强,但计算成本也指数级增长。
2. RAG架构在Spring中的落地实践
RAG(Retrieval-Augmented Generation)的核心理念是:先检索相关知识,再生成回答。这比直接让大模型胡编乱造靠谱得多。一个典型的Spring AI集成方案如下:
// 伪代码展示核心流程 @RestController public class AIController { @Autowired private VectorStore vectorStore; @Autowired private AiClient aiClient; @PostMapping("/ask") public String answerQuestion(@RequestBody QuestionDto question) { // 1. 将问题转换为向量 float[] embedding = aiClient.embed(question.getText()); // 2. 向量数据库检索Top3相关文档 List<Document> docs = vectorStore.similaritySearch(embedding, 3); // 3. 将检索结果作为上下文喂给大模型 String prompt = buildPrompt(question, docs); return aiClient.generate(prompt); } }2.1 向量数据库选型对比
目前主流的开源向量数据库有:
| 数据库 | 语言 | 特点 | Spring集成难度 |
|---|---|---|---|
| Milvus | Go | 分布式架构,适合大规模数据 | 中等 |
| Qdrant | Rust | 轻量级,HTTP API友好 | 简单 |
| Chroma | Python | 开发友好,但生产部署较复杂 | 较难 |
| Weaviate | Go | 内置向量生成模块 | 中等 |
对于Java技术栈团队,个人推荐Qdrant:
- 原生支持gRPC接口(比HTTP更快)
- 单机版用Docker一行命令就能跑起来
- 有Spring Data风格的客户端库
# application.yml配置示例 qdrant: host: localhost port: 6334 collection: spring_docs3. 实战:构建Spring技术文档智能问答系统
假设我们要开发一个能回答Spring相关技术问题的AI助手,关键步骤如下:
3.1 知识库预处理
收集原始资料:
- Spring官方PDF文档
- StackOverflow精选问答
- 公司内部技术Wiki
文本分块策略:
- 按Markdown标题拆分(###为分隔符)
- 每块不超过500字符
- 重叠50字符避免语义断裂
# 使用LangChain进行文本分块(即使主项目是Java) from langchain.text_splitter import MarkdownHeaderTextSplitter headers_to_split_on = [("###", "Section")] splitter = MarkdownHeaderTextSplitter(headers_to_split_on) docs = splitter.split_text(markdown_content)3.2 向量化与存储
使用Spring AI的EmbeddingClient抽象层:
@Bean public VectorStore vectorStore(EmbeddingClient embeddingClient) { return new QdrantVectorStore( embeddingClient, new QdrantVectorStoreConfig("spring_docs") ); } // 批量存储文档 void storeDocuments(List<Document> docs) { vectorStore.add(docs.stream() .map(doc -> new Embedding(doc.getContent(), doc.getMetadata())) .toList()); }踩坑提醒:批量插入时建议每100条提交一次,Qdrant的gRPC接口默认超时时间是5秒。
4. 性能优化与生产级考量
4.1 混合检索策略
纯向量搜索在精确匹配场景(如类名、方法名)反而不如关键词检索。解决方案:
List<Document> hybridSearch(String query) { // 并行执行两种检索 CompletableFuture<List<Document>> vectorSearch = CompletableFuture.supplyAsync( () -> vectorStore.similaritySearch(embeddingClient.embed(query), 5)); CompletableFuture<List<Document>> keywordSearch = CompletableFuture.supplyAsync( () -> elasticsearchTemplate.search(query, Document.class)); // 合并结果并去重 return Stream.concat(vectorSearch.join().stream(), keywordSearch.join().stream()) .distinct() .toList(); }4.2 缓存设计
高频问题没必要每次都走完整流程:
- 用问题文本的MD5作缓存Key
- 两级缓存:本地Caffeine + 分布式Redis
- 当源文档更新时,通过Spring事件机制清除相关缓存
@EventListener public void handleDocumentUpdate(DocumentUpdateEvent event) { cacheManager.getCache("aiAnswers").evictIfPresent( event.getDocument().getTitle()); }5. 避坑指南:从Demo到生产的血泪教训
维度灾难:不同Embedding模型产出向量维度不同,切换模型必须重建整个向量库。我们曾因从Ada切换到BGE导致所有向量失效。
中文分词的坑:默认的英文分词器会把"Spring事务"切成["spring", "事", "务"],解决方案:
@Bean public EmbeddingClient embeddingClient() { return new OpenAiEmbeddingClient( new OpenAiApi("https://your-proxy.com"), "text-embedding-3-large", // 专门优化过中文 1536); }相似度阈值设定:当最相似文档的score<0.7时,宁可返回"我不知道",也别让AI瞎编。我们在日志中发现过AI把Spring Security和Spring Cloud混为一谈的惨案。
监控指标:
- 平均检索耗时(P99应<300ms)
- 缓存命中率(建议>60%)
- 用户满意度(通过👍/👎按钮收集)
这套系统在我们团队落地后,技术支持的工单量下降了73%。最意外的收获是:新员工通过这个问答系统学习Spring的速度比看文档快2倍。现在每次Spring发布新版本,我们第一件事就是更新向量数据库——这或许就是AI时代的"文档即代码"吧。