一、先搞懂向量数据库是干什么的
想象你去图书馆找书。传统数据库的工作方式像这样的对话:你问"有没有讲机器学习的书?",图书馆员只会找书名里带"机器学习"三个字的书。如果一本书叫《统计学习方法》,即使内容全是机器学习,他也找不到,因为名字不一样。
向量数据库就不一样了。它把人话转成一串数字(叫向量),然后比较这些数字的"远近"。"机器学习"和"统计学习方法"意思相近,向量就离得近;"机器学习"和"红烧肉"意思不相干,向量就离得远。所以它能按"意思"搜索,而不是按关键词。
Milvus 就是目前最主流的开源向量数据库。Zilliz 公司开发,2019 年开源,Linux Foundation AI 基金会项目。十亿级数据几十毫秒就能找出最相似的内容。
二、Spring AI 集成 Milvus 的整体流程
Spring AI 对 Milvus 的支持走的是 VectorStore 接口——和 JDBC 一个思路:统一接口,切换底层实现不用改业务代码。整体链路是这样的:
1. Embedding 模型把文档转成向量
2. Milvus 存这些向量(Collection + Index)
3. 用户提问时,同样的 Embedding 模型把问题也转成向量
4. Spring AI 调用 Milvus 做相似度搜索,返回最相关的文档片段
5. 把这些片段拼成 Prompt,发给大模型,得到最终回答
三、实战:搭一个 RAG 知识库
3.1 引入依赖
<!-- Spring AI Milvus 向量存储 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-milvus-store</artifactId> </dependency> <!-- Embedding 模型(用通义千问) --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-dashscope</artifactId> </dependency>
3.2 配置 Milvus 连接
spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen3 vectorstore: milvus: uri: ${MILVUS_URI:http://localhost:19530} username: ${MILVUS_USERNAME:} password: ${MILVUS_PASSWORD:} database-name: default collection-name: knowledge_base index-type: HNSW metric-type: COSINE embedding-dimension: 768几个配置项的含义:
index-type:索引类型。HNSW 精度高、速度快,适合在线检索。如果是超大数据量图省内存可以用 IVF_FLAT。
metric-type:距离计算方式。文本搜索一般用 COSINE(余弦相似度),因为不受向量长度影响。
embedding-dimension:你用的 Embedding 模型输出的向量维度。bge-base-zh 是 768,text-embedding-3 是 1536,要和模型对齐。
3.3 文档入库
Spring AI 自动注入了 VectorStore Bean,直接注入就能用:
@Service public class KnowledgeService { private final VectorStore vectorStore; public KnowledgeService(VectorStore vectorStore) { this.vectorStore = vectorStore; } /** * 把文档存进 Milvus * Spring AI 会自动调用 DashScope Embedding 模型把文本转成向量 */ public void addDocument(String content, String source) { Document doc = new Document(content, Map.of("source", source, "timestamp", Instant.now().toString())); vectorStore.add(List.of(doc)); } /** * 批量入库 */ @Transactional public void batchAdd(List<String> contents, String source) { List<Document> docs = contents.stream() .map(c -> new Document(c, Map.of("source", source))) .collect(Collectors.toList()); vectorStore.add(docs); } }3.4 语义搜索 + RAG
用户提一个问题,先在 Milvus 里找到相关文档,再送给大模型生成回答:
@Service public class RagService { private final VectorStore vectorStore; private final ChatClient chatClient; public RagService(VectorStore vectorStore, ChatClient.Builder builder) { this.vectorStore = vectorStore; this.chatClient = builder.build(); } public String ask(String question) { // 1. 在 Milvus 中找到最相关的 3 个文档片段 List<Document> docs = vectorStore .similaritySearch(SearchRequest.query(question) .withTopK(3)); // 2. 把文档片段拼成上下文 String context = docs.stream() .map(d -> "【" + d.getMetadata().get("source") + "】" + d.getContent()) .collect(Collectors.joining("\n\n")); // 3. 发给大模型,生成回答 return chatClient.prompt() .user(u -> u.text(""" 基于以下知识回答问题。 如果知识不够,直接说不知道。 知识库内容: {context} 问题:{question} """) .param("context", context) .param("question", question)) .call() .content(); } }3.5 加一个 Controller
@RestController @RequestMapping("/knowledge") public class KnowledgeController { private final KnowledgeService knowledgeService; private final RagService ragService; @PostMapping("/add") public Result add(@RequestBody AddDocDTO dto) { knowledgeService.addDocument(dto.content(), dto.source()); return Result.success(); } @PostMapping("/ask") public Result ask(@RequestBody AskDTO dto) { String answer = ragService.ask(dto.question()); return Result.success(answer); } }四、Milvus 部署与注意事项
部署方式
开发测试用 Milvus Standalone,一个 docker-compose.yml 搞定:
# docker-compose.yml version: '3.5' services: etcd: image: quay.io/coreos/etcd:v3.5.16 environment: ETCD_AUTO_COMPACTION_MODE: revision ETCD_AUTO_COMPACTION_RETENTION: '1000' minio: image: minio/minio:RELEASE.2024-11-17T00-39-20Z standalone: image: milvusdb/milvus:v2.5.0 ports: - 19530:19530 # gRPC - 9091:9091 # HTTP depends_on: [etcd, minio]
几个坑
Embedding 维度要对齐:配置文件里的 embedding-dimension 必须和实际使用的模型一致,否则存进去搜不出来。
索引要显式创建:Spring AI 创建 Collection 时不会自动建索引,需要先调 createIndex() 或等数据量够了再建。也可以在配置里把 index-type 配好,Spring AI 会自动处理。
内存规划:向量数据在内存中检索最快。HNSW 索引的内存大约是数据量的 1.5-2 倍。如果是 10 亿条 768 维向量,大约需要 800GB 内存——这时候就用 DiskANN 或者上 GPU。
元数据过滤:Milvus 支持标量字段过滤(相当于 SQL 的 WHERE 条件),对常用过滤字段建标量索引能大幅提升混合检索性能。
五、进阶:自定义 Embedding 模型
如果你的场景需要特定的 Embedding 模型(比如中文效果更好的 bge-large-zh-v1.5),可以实现 EmbeddingModel 接口:
@Configuration public class MilvusConfig { @Bean public VectorStore milvusVectorStore(MilvusServiceClient milvusClient, EmbeddingModel embeddingModel) { return new MilvusVectorStore(milvusClient, embeddingModel, MilvusVectorStoreConfig.builder() .withCollectionName("knowledge_base") .withIndexType(IndexType.HNSW) .withMetricType(MetricType.COSINE) .build()); } }这样就能灵活切换不同的 Embedding 模型,不受 starter 自动配置的限制。
常见面试题与参考答案
1. Milvus 为什么比 ES 更适合向量检索?
参考答案:Elasticsearch 的向量检索插件(如 dense_vector)本质上还是暴力搜索或简单的 HNSW,性能和可扩展性有限。Milvus 从底层就是为向量设计的:支持多种 ANN 索引(IVF、HNSW、DiskANN)、支持 GPU 加速、有专门的调度和分片策略。在十亿级数据上,Milvus 的检索速度比 ES 快 2-3 个数量级。但 ES 在全文搜索和日志分析场景仍然不可替代,两个数据库通常是配合使用。
2. Spring AI 的 VectorStore 接口怎么切换不同的向量数据库?
参考答案:Spring AI 的 VectorStore 是顶层抽象接口,定义了 add()、similaritySearch()、delete() 等方法。Milvus、Pinecone、Weaviate、Pgvector 等都有各自的实现类。切换时只需换依赖和配置,业务代码不需要改动。比如从 Milvus 切到 Pgvector:把 spring-ai-milvus-store 换成 spring-ai-pgvector-store,修改 yml 中的连接配置,代码里的 VectorStore 注入不用动。
3. 向量检索的召回率(Recall)怎么保证?
参考答案:向量检索本质是近似搜索(ANN),不是精确搜索,所以召回率不可能 100%。影响召回率的关键因素:索引类型(HNSW 召回率高于 IVF)、索引参数(HNSW 的 efConstruction 和 ef、IVF 的 nprobe)、向量质量(Embedding 模型的好坏)。提召回率的常用方法:提高 ef/nprobe 参数(但会降低速度)、使用集成多索引投票、或者结合 BM25 关键词搜索做混合检索。