Milvus 是一款开源的向量数据库,专为海量向量数据的高效检索设计,超级适合构建智能搜索引擎(如语义检索、图像类似性搜索等)。本文将手把手教你用 Spring Boot 整合 Milvus,从零搭建一个基于向量检索的智能搜索引擎。
一、前置准备
1. 环境要求
- JDK 11+(Milvus Java SDK 推荐)
- Spring Boot 2.7.x / 3.x
- Milvus 2.3+(推荐用 Docker 快速部署)
- Maven/Gradle
- 向量生成工具(示例用 OpenAI Embedding 或本地模型如 Sentence-BERT)

2. 部署 Milvus 单机版
用 Docker Compose 快速启动 Milvus(推荐):
# docker-compose.yml
version: '3.5'
services:
etcd:
container_name: milvus-etcd
image: quay.io/coreos/etcd:v3.5.5
environment:
- ETCD_AUTO_COMPACTION_MODE=revision
- ETCD_AUTO_COMPACTION_RETENTION=1000
- ETCD_QUOTA_BACKEND_BYTES=4294967296
- ETCD_SNAPSHOT_COUNT=50000
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd
command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
minio:
container_name: milvus-minio
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
environment:
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data
command: minio server /minio_data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
milvus-standalone:
container_name: milvus-standalone
image: milvusdb/milvus:v2.3.2
command: ["milvus", "run", "standalone"]
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
ports:
- "19530:19530" # Milvus 主端口
- "9091:9091"
depends_on:
- "etcd"
- "minio"
启动命令:
docker-compose up -d
二、Spring Boot 项目搭建
1. 引入依赖
在 pom.xml 中添加 Milvus Java SDK 和 Spring Boot 核心依赖:
<!-- Spring Boot 基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Milvus Java SDK -->
<dependency>
<groupId>io.milvus</groupId>
<artifactId>milvus-sdk-java</artifactId>
<version>2.3.2</version>
</dependency>
<!-- 向量生成:示例用 OpenAI Embedding(可选) -->
<dependency>
<groupId>com.theokanning.openai-gpt3-java</groupId>
<artifactId>service</artifactId>
<version>0.10.0</version>
</dependency>
<!-- JSON 工具 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.32</version>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2. 配置 Milvus 连接
在 application.yml 中添加 Milvus 配置:
spring:
application:
name: milvus-search-demo
# Milvus 配置
milvus:
host: 127.0.0.1 # Milvus 服务地址
port: 19530 # Milvus 端口
database-name: default # 默认数据库
collection-name: document_vector # 向量集合名称
dim: 1536 # 向量维度(OpenAI Embedding 是 1536,Sentence-BERT 可自定义)
3. 封装 Milvus 配置类
创建配置类读取 Milvus 配置:
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "milvus")
public class MilvusProperties {
private String host;
private Integer port;
private String databaseName;
private String collectionName;
private Integer dim; // 向量维度
}

4. 初始化 Milvus 客户端
创建 Milvus 客户端配置类,初始化连接并创建向量集合:
import io.milvus.client.MilvusServiceClient;
import io.milvus.param.ConnectParam;
import io.milvus.param.collection.*;
import io.milvus.param.index.CreateIndexParam;
import io.milvus.param.index.IndexType;
import io.milvus.param.index.MetricType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MilvusConfig {
@Autowired
private MilvusProperties milvusProperties;
// 初始化 Milvus 客户端
@Bean
public MilvusServiceClient milvusServiceClient() {
ConnectParam connectParam = ConnectParam.newBuilder()
.withHost(milvusProperties.getHost())
.withPort(milvusProperties.getPort())
.build();
MilvusServiceClient client = new MilvusServiceClient(connectParam);
// 检查连接
if (client.getConnectState()) {
System.out.println("Milvus 连接成功!");
// 创建向量集合(如果不存在)
createCollection(client);
// 创建索引
createIndex(client);
} else {
throw new RuntimeException("Milvus 连接失败!");
}
return client;
}
// 创建向量集合
private void createCollection(MilvusServiceClient client) {
String collectionName = milvusProperties.getCollectionName();
// 检查集合是否存在
HasCollectionParam hasParam = HasCollectionParam.newBuilder()
.withCollectionName(collectionName)
.build();
boolean exists = client.hasCollection(hasParam).getData();
if (exists) {
System.out.println("集合 " + collectionName + " 已存在");
return;
}
// 定义字段:主键 ID + 文本内容 + 向量字段
FieldType idField = FieldType.newBuilder()
.withName("id")
.withDataType(DataType.Int64)
.withPrimaryKey(true)
.withAutoID(false) // 手动指定 ID
.build();
FieldType contentField = FieldType.newBuilder()
.withName("content")
.withDataType(DataType.VarChar)
.withMaxLength(4096) // 文本最大长度
.build();
FieldType vectorField = FieldType.newBuilder()
.withName("vector")
.withDataType(DataType.FloatVector)
.withDimension(milvusProperties.getDim()) // 向量维度
.build();
CreateCollectionParam createParam = CreateCollectionParam.newBuilder()
.withCollectionName(collectionName)
.withDescription("文档向量集合")
.addFieldType(idField)
.addFieldType(contentField)
.addFieldType(vectorField)
.build();
client.createCollection(createParam);
System.out.println("集合 " + collectionName + " 创建成功");
}
// 创建向量索引(提升检索效率)
private void createIndex(MilvusServiceClient client) {
CreateIndexParam createIndexParam = CreateIndexParam.newBuilder()
.withCollectionName(milvusProperties.getCollectionName())
.withFieldName("vector")
.withIndexType(IndexType.IVF_FLAT) // 索引类型(IVF_FLAT 适合中小数据量)
.withMetricType(MetricType.COSINE) // 类似度计算方式(余弦类似度)
.withExtraParam("{"nlist": 1024}") // 索引参数
.build();
client.createIndex(createIndexParam);
System.out.println("向量索引创建成功");
// 加载集合到内存(必须加载才能检索)
LoadCollectionParam loadParam = LoadCollectionParam.newBuilder()
.withCollectionName(milvusProperties.getCollectionName())
.build();
client.loadCollection(loadParam);
}
}
三、核心功能实现
1. 向量生成工具(示例用 OpenAI Embedding)
创建向量生成服务,将文本转换为向量(如果没有 OpenAI 密钥,可替换为本地模型如 Sentence-BERT):
import com.theokanning.openai.OpenAiService;
import com.theokanning.openai.embedding.Embedding;
import com.theokanning.openai.embedding.EmbeddingRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class EmbeddingService {
// 替换为你的 OpenAI API Key
@Value("${openai.api.key:your-api-key}")
private String openaiApiKey;
// 将文本转换为向量
public float[] getEmbedding(String text) {
// 初始化 OpenAI 服务
OpenAiService service = new OpenAiService(openaiApiKey);
// 构建 Embedding 请求
EmbeddingRequest request = EmbeddingRequest.builder()
.model("text-embedding-ada-002") // OpenAI Embedding 模型
.input(text)
.build();
// 获取向量结果
List<Embedding> embeddings = service.createEmbeddings(request).getData();
List<Double> vector = embeddings.get(0).getEmbedding();
// 转换为 float 数组(Milvus 要求 float 类型)
return vector.stream()
.mapToFloat(Double::floatValue)
.toArray();
}
}
2. 文档入库(向量插入)
创建服务类,实现文档文本转向量并插入 Milvus:
import io.milvus.client.MilvusServiceClient;
import io.milvus.param.dml.InsertParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Service
public class DocumentService {
@Autowired
private MilvusServiceClient milvusClient;
@Autowired
private MilvusProperties milvusProperties;
@Autowired
private EmbeddingService embeddingService;
// 插入文档(文本转向量后入库)
public void insertDocument(Long id, String content) {
// 1. 生成文本向量
float[] vector = embeddingService.getEmbedding(content);
// 2. 构建插入数据
List<InsertParam.Field> fields = new ArrayList<>();
// 主键 ID
fields.add(new InsertParam.Field("id", List.of(id)));
// 文本内容
fields.add(new InsertParam.Field("content", List.of(content)));
// 向量字段
fields.add(new InsertParam.Field("vector", List.of(vector)));
// 3. 插入 Milvus
InsertParam insertParam = InsertParam.newBuilder()
.withCollectionName(milvusProperties.getCollectionName())
.addFields(fields)
.build();
milvusClient.insert(insertParam);
log.info("文档插入成功,ID: {}, 内容: {}", id, content);
}
}
3. 语义检索(向量类似度搜索)
实现核心检索功能,根据查询文本的向量匹配最类似的文档:
import io.milvus.client.MilvusServiceClient;
import io.milvus.param.dml.SearchParam;
import io.milvus.response.SearchResultsWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
public class SearchService {
@Autowired
private MilvusServiceClient milvusClient;
@Autowired
private MilvusProperties milvusProperties;
@Autowired
private EmbeddingService embeddingService;
// 语义检索:返回 Top K 类似文档
public List<Map<String, Object>> search(String queryText, int topK) {
// 1. 生成查询文本的向量
float[] queryVector = embeddingService.getEmbedding(queryText);
// 2. 构建搜索参数
List<String> outputFields = List.of("id", "content"); // 返回的字段
SearchParam searchParam = SearchParam.newBuilder()
.withCollectionName(milvusProperties.getCollectionName())
.withMetricType(MetricType.COSINE) // 余弦类似度
.withTopK(topK)
.withVectors(List.of(queryVector))
.withVectorFieldName("vector")
.withOutputFields(outputFields)
.withParams("{"nprobe": 10}") // 检索参数(nprobe 越大越准但越慢)
.build();
// 3. 执行搜索
SearchResultsWrapper resultsWrapper = new SearchResultsWrapper(milvusClient.search(searchParam).getData());
// 4. 解析结果
List<Map<String, Object>> resultList = new ArrayList<>();
for (SearchResultsWrapper.IDScore idScore : resultsWrapper.getIDScore(0)) {
// 获取文档字段和类似度分数
Map<String, Object> fields = resultsWrapper.getFieldData(idScore.getLongID(), outputFields);
fields.put("score", idScore.getScore()); // 类似度分数(余弦类似度:越接近 1 越类似)
resultList.add(fields);
}
log.info("检索完成,查询文本: {}, 匹配结果数: {}", queryText, resultList.size());
return resultList;
}
}
四、接口封装与测试
1. 控制器层(REST API)
创建接口供外部调用:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/search")
public class SearchController {
@Autowired
private DocumentService documentService;
@Autowired
private SearchService searchService;
// 插入文档
@PostMapping("/insert")
public String insertDocument(@RequestParam Long id, @RequestParam String content) {
documentService.insertDocument(id, content);
return "插入成功";
}
// 语义检索
@GetMapping("/query")
public List<Map<String, Object>> search(
@RequestParam String query,
@RequestParam(defaultValue = "5") int topK) {
return searchService.search(query, topK);
}
}
2. 测试验证
步骤 1:启动项目
确保 Milvus 已启动,运行 Spring Boot 应用。
步骤 2:插入测试文档
调用接口插入测试数据:
# 插入文档 1
curl -X POST "http://localhost:8080/api/search/insert?id=1&content=Java 是一种面向对象的编程语言,广泛用于后端开发"
# 插入文档 2
curl -X POST "http://localhost:8080/api/search/insert?id=2&content=Spring Boot 是基于 Spring 的快速开发框架,简化了配置"
# 插入文档 3
curl -X POST "http://localhost:8080/api/search/insert?id=3&content=Milvus 是开源的向量数据库,用于高效检索向量数据"
步骤 3:语义检索测试
调用检索接口,测试类似性搜索:
curl "http://localhost:8080/api/search/query?query=Spring Boot 简化 Java 开发&topK=2"
预期返回结果(包含类似度分数):
[
{
"id": 2,
"content": "Spring Boot 是基于 Spring 的快速开发框架,简化了配置",
"score": 0.98
},
{
"id": 1,
"content": "Java 是一种面向对象的编程语言,广泛用于后端开发",
"score": 0.85
}
]
五、进阶优化
1. 索引优化
- 中小数据量:使用 IVF_FLAT 索引(精准但性能一般)
- 大数据量:使用 HNSW 索引(高性能,参数 efConstruction 越大越准)
- 调整 nlist/nprobe:nlist 是聚类数,nprobe 是检索的聚类数,需根据数据量调优
2. 批量操作
批量插入 / 检索可大幅提升性能,示例:
// 批量插入
List<Long> ids = List.of(1L, 2L, 3L);
List<String> contents = List.of("文本1", "文本2", "文本3");
List<float[]> vectors = contents.stream().map(embeddingService::getEmbedding).collect(Collectors.toList());
fields.add(new InsertParam.Field("id", ids));
fields.add(new InsertParam.Field("content", contents));
fields.add(new InsertParam.Field("vector", vectors));
3. 数据管理
- 定期清理过期数据:使用 delete 接口
- 集合分区:按业务维度分区,提升检索效率
- 数据备份:Milvus 支持备份 / 恢复 API
4. 本地向量模型替换
如果不想用 OpenAI,可使用开源的 Sentence-BERT:
<!-- Sentence-BERT 依赖 -->
<dependency>
<groupId>com.hankcs</groupId>
<artifactId>sentence-transformers</artifactId>
<version>1.0.0</version>
</dependency>
替换 EmbeddingService:
import com.hankcs.hanlp.mining.word2vec.Vector;
import com.hankcs.sentencetransformers.SentenceTransformer;
@Service
public class EmbeddingService {
private final SentenceTransformer model;
public EmbeddingService() {
// 加载本地模型(需提前下载)
model = new SentenceTransformer("all-MiniLM-L6-v2");
}
public float[] getEmbedding(String text) {
Vector vector = model.encode(text);
return vector.getValues();
}
}
六、总结
本文实现了 Spring Boot 与 Milvus 的完整整合,核心流程为:
- 文本 → 向量(Embedding)
- 向量入库(Milvus 插入)
- 查询文本 → 向量 → 向量类似度检索 → 返回匹配文档
基于这个基础框架,你可以扩展:
- 多模态检索(图片 / 音频转向量)
- 检索结果排序优化
- 分布式 Milvus 部署
- 结合 RAG(检索增强生成)打造智能问答系统
Milvus 为向量检索提供了高效的底层支持,结合 Spring Boot 的快速开发能力,可快速落地各类智能搜索场景!
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...