一、项目背景与技术选型
核心目标:
通过Milvus向量数据库实现AI知识库的高维向量存储与语义检索,结合MinIO对象存储管理原始数据,构建可扩展的AI知识管理系统。
技术栈:
- Milvus:高维向量数据库,负责向量数据的存储、索引与检索
- MinIO:分布式对象存储,托管非结构化数据(如文章原文)
- Docker:容器化部署,确保环境一致性
- Java SDK:实现业务系统与Milvus的交互
二、语义知识库检索工作流程
- 数据预处理:
- 读取文件如word内容将内容给ai模型转化为768维向量
- 将向量存入Milvus,源文件存入minio,文件url存入Milvus对应的向量
- 检索流程:
- 用户提问 →ai模型转化→ 生成查询向量
- Milvus执行近似最近邻(ANN)搜索
- 根据返回的语义最相似的文章的url从MinIO获取原文
- AI整合回答:
- 将检索到的Top-K知识片段与原始问题拼接
- 语义检索直接展示url的minio的文件,知识库提问提取文件知识和问题投给ai
1.前置环境省略补充:
1.Linux操作系统部署Centos7:Linux操作系统的下载与安装(保姆级教程)_linux系统下载-CSDN博客
2.docker安装部署可以直接拉最新的:docker安装(完整详细版)-CSDN博客
2.这里展示博主当前网上找到的可用的国内镜像:
1.这里知识cat展示,你们修改可以使用vi或vim编辑(应该都知道这个是干嘛用的吧)
cat /etc/docker/daemon.json
2.vim的操作: vim /etc/docker/daemon.json → i →鼠标中键粘贴→esc→:wq(保存退出)。
{
"registry-mirrors": [
"https://docker.sunzishaokao.com",
"https://docker.xuanyuan.me",
"https://docker.1ms.run",
"https://docker.1panel.live",
"https://hub.rat.dev",
"https://docker.wanpeng.top",
"https://doublezonline.cloud",
"https://docker.mrxn.net",
"https://docker.anyhub.us.kg",
"https://dislabaiot.xyz",
"https://docker.fxxk.dedyn.io",
"https://docker-mirror.aigc2d.com",
"https://ypfjyq04.mirror.aliyuncs.com",
"https://docker.m.daocloud.io",
"https://docker.zhai.cm",
"https://a.ussh.net",
"https://aicarbon.xyz",
"https://docker.yomansunter.com",
"https://666860.xyz",
"https://1ms.run",
"https://docker.mybacc.com",
"https://dytt.online",
"https://lispy.org",
"http://docker.xiaogenban1993.com"
]
}
3. Docker Compose 部署前置环境
根据官方文档使用Docker Compose快速部署安装Milvus,这里补充安装过程
(1)下载 Docker Compose v2.23.0(兼容 Docker 26.x)
sudo curl -L "https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose
补充:如果拉不下来这里提供Git地址和csdn的博客绑定文件【如果安装不下来,可以手动安装后传到指定目录】
git clone https://gitee.com/TheListKnight/dy-milvus.git
(2) 赋予执行权限
sudo chmod +x /usr/local/bin/docker-compose
(3)最后异步校验验证安装
docker-compose --version
输出应类似:
Docker Compose version v2.23.0
4.安装部署Milvus使用2.3.5
博主遇到的问题:避坑2.3.3!
刚开始我使用的是2.3.3,不知道谁说的稳定版,maven依赖也使用了java-sdk2.3.3的连接Milvus,但是2.3.3的版本有问题,容器总是会自动关闭,goroutine阻塞和gRPC连接问题等,所以这里建议使用2.3.5问题已经解决。
1.下载Milvus 的docker-compose.yml文件这里提供官网的下载指令
wget https://github.com/milvus-io/milvus/releases/download/v2.3.5/milvus-standalone-docker-compose.yml -O docker-compose.yml
补充:博主这边是拉下来了,如果拉不下来这里提供git地址和绑定文件最顶上
git clone https://gitee.com/TheListKnight/dy-milvus.git
2.生成容器(成功后查看容器会有三个,是Milvus的核心不要管)
sudo docker compose up -d
3.主要连接使用的是这个:确保启动状态
5.提供Milvus可视化的界面工具
1.使用 Attu(社区版 Web UI)安装方式(注意ip改你自己的)
docker run -d -p 8000:3000 -e MILVUS_URL=你的虚拟机ip:19530 --name attu zilliz/attu:v2.3.4
2.浏览器访问:
http://你的虚拟机ip:8000/
6.java连接Milvus增删改查使用
已经将连接方法写成了工具类,先测试类展示工具类中的方法,最后展示工具类。
1.依赖导入
<!-- Milvus Java SDK -->
<dependency>
<groupId>io.milvus</groupId>
<artifactId>milvus-sdk-java</artifactId>
<version>2.3.3</version>
</dependency>
调用工具类初始化:
MilvusUtils milvusUtils = new MilvusUtils("虚拟机的ip地址"
,19530
,128
,10);
2.创建集合
@Test
void 创建集合(){
//连接初始化
milvusUtils.initMilvusClient();
连接后创建officePro集合
milvusUtils.createCollection("officePro");
}
3.创建分区
@Test
void 创建分区(){
milvusUtils.initMilvusClient();
milvusUtils.createPartition("officePro","text");
}
4.创建索引
@Test
void 创建索引(){
milvusUtils.initMilvusClient();
milvusUtils.createIndex("officePro");
}
5.插入向量数据(向量为空会报错,报错正常因为这里我写的空的)
@Test
void 插入向量数据(){
milvusUtils.initMilvusClient();
List<Float> queryVector = null;
milvusUtils.insertData("officePro"
,"text",queryVector
,"baidu.com"
,"测试数据"
,"string");
}
7.搜索相似向量(向量为空会报错,报错正常因为这里我写的空的)
@Test
void 向量检索(){
milvusUtils.initMilvusClient();
List<Float> queryVector = null;
milvusUtils.searchSimilarVectors("officePro","text",queryVector);
}
8.删除集合
@Test
void 删除集合(){
milvusUtils.initMilvusClient();
milvusUtils.dropCollection("officePro");
}
写好的工具类:MilvusUtils
package com.example.util;
import io.milvus.client.MilvusServiceClient;
import io.milvus.param.ConnectParam;
import io.milvus.param.R;
import io.milvus.param.collection.CreateCollectionParam;
import io.milvus.param.collection.DropCollectionParam;
import io.milvus.param.collection.LoadCollectionParam;
import io.milvus.param.partition.CreatePartitionParam;
import io.milvus.param.index.CreateIndexParam;
import io.milvus.param.dml.InsertParam;
import io.milvus.param.dml.SearchParam;
import io.milvus.grpc.DataType;
import io.milvus.grpc.SearchResults;
import io.milvus.param.IndexType;
import io.milvus.param.MetricType;
import io.milvus.param.collection.FieldType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class MilvusUtils {
private static final Logger logger = LoggerFactory.getLogger(MilvusUtils.class);
private final String host;
private final int port;
private final int vectorDim;
private final int topK;
private MilvusServiceClient milvusClient;
/**
* 构造函数
* @param host Milvus服务器地址
* @param port Milvus服务器端口
* @param vectorDim 向量维度
* @param topK 搜索时返回的最相似结果数量
*/
public MilvusUtils(String host, int port, int vectorDim, int topK) {
this.host = host;
this.port = port;
this.vectorDim = vectorDim;
this.topK = topK;
}
/**
* 初始化Milvus客户端连接
* @return 是否连接成功
*/
public boolean initMilvusClient() {
try {
ConnectParam connectParam = ConnectParam.newBuilder()
.withHost(host)
.withPort(port)
.build();
milvusClient = new MilvusServiceClient(connectParam);
logger.info("Milvus客户端初始化完成,连接地址: {}:{}", host, port);
return true;
} catch (Exception e) {
logger.error("初始化Milvus客户端失败: {}", e.getMessage());
return false;
}
}
/**
* 创建集合
* @param collectionName 集合名称
* @return 是否创建成功
*/
public boolean createCollection(String collectionName) {
try {
FieldType idField = FieldType.newBuilder()
.withName("id")
.withDataType(DataType.Int64)
.withPrimaryKey(true)
.withAutoID(true)
.build();
FieldType vectorField = FieldType.newBuilder()
.withName("vector_field")
.withDataType(DataType.FloatVector)
.withDimension(vectorDim)
.build();
FieldType fileUrlField = FieldType.newBuilder()
.withName("file_url")
.withDataType(DataType.VarChar)
.withMaxLength(500)
.build();
FieldType fileNameField = FieldType.newBuilder()
.withName("file_name")
.withDataType(DataType.VarChar)
.withMaxLength(255)
.build();
FieldType fileTypeField = FieldType.newBuilder()
.withName("file_type")
.withDataType(DataType.VarChar)
.withMaxLength(50)
.build();
CreateCollectionParam createCollectionReq = CreateCollectionParam.newBuilder()
.withCollectionName(collectionName)
.withDescription("文档向量存储集合")
.addFieldType(idField)
.addFieldType(vectorField)
.addFieldType(fileUrlField)
.addFieldType(fileNameField)
.addFieldType(fileTypeField)
.build();
milvusClient.createCollection(createCollectionReq);
logger.info("创建集合成功: {}", collectionName);
return true;
} catch (Exception e) {
logger.error("创建集合失败: {}", e.getMessage());
return false;
}
}
/**
* 创建分区
* @param collectionName 集合名称
* @param partitionName 分区名称
* @return 是否创建成功
*/
public boolean createPartition(String collectionName, String partitionName) {
try {
CreatePartitionParam createPartitionReq = CreatePartitionParam.newBuilder()
.withCollectionName(collectionName)
.withPartitionName(partitionName)
.build();
milvusClient.createPartition(createPartitionReq);
logger.info("创建分区成功: {}", partitionName);
return true;
} catch (Exception e) {
logger.error("创建分区失败: {}", e.getMessage());
return false;
}
}
/**
* 创建索引
* @param collectionName 集合名称
* @return 是否创建成功
*/
public boolean createIndex(String collectionName) {
try {
CreateIndexParam createIndexReq = CreateIndexParam.newBuilder()
.withCollectionName(collectionName)
.withFieldName("vector_field")
.withIndexType(IndexType.IVF_FLAT)
.withMetricType(MetricType.L2)
.withExtraParam("{\"nlist\":1024}")
.withSyncMode(true)
.build();
milvusClient.createIndex(createIndexReq);
logger.info("创建索引成功");
return true;
} catch (Exception e) {
logger.error("创建索引失败: {}", e.getMessage());
return false;
}
}
/**
* 插入向量数据
* @param collectionName 集合名称
* @param partitionName 分区名称
* @param vector 向量数据
* @param fileUrl 文件URL
* @param fileName 文件名
* @param fileType 文件类型
* @return 是否插入成功
*/
public boolean insertData(String collectionName, String partitionName,
List<Float> vector, String fileUrl,
String fileName, String fileType) {
try {
List<InsertParam.Field> fields = new ArrayList<>();
fields.add(new InsertParam.Field("vector_field", Collections.singletonList(vector)));
fields.add(new InsertParam.Field("file_url", Collections.singletonList(fileUrl)));
fields.add(new InsertParam.Field("file_name", Collections.singletonList(fileName)));
fields.add(new InsertParam.Field("file_type", Collections.singletonList(fileType)));
InsertParam insertParam = InsertParam.newBuilder()
.withCollectionName(collectionName)
.withPartitionName(partitionName)
.withFields(fields)
.build();
milvusClient.insert(insertParam);
logger.info("插入数据成功");
return true;
} catch (Exception e) {
logger.error("插入数据失败: {}", e.getMessage());
return false;
}
}
/**
* 搜索相似向量
* @param collectionName 集合名称
* @param partitionName 分区名称
* @param queryVector 查询向量
* @return 搜索结果
*/
public SearchResults searchSimilarVectors(String collectionName, String partitionName, List<Float> queryVector) {
try {
// 先加载集合
LoadCollectionParam loadCollectionParam = LoadCollectionParam.newBuilder()
.withCollectionName(collectionName)
.build();
milvusClient.loadCollection(loadCollectionParam);
List<List<Float>> queryVectors = Collections.singletonList(queryVector);
SearchParam searchParam = SearchParam.newBuilder()
.withCollectionName(collectionName)
.withPartitionNames(Collections.singletonList(partitionName))
.withMetricType(MetricType.L2)
.withVectors(queryVectors)
.withVectorFieldName("vector_field")
.withTopK(topK)
.withOutFields(Arrays.asList("file_url", "file_name", "file_type"))
.build();
R<SearchResults> searchResultsResponse = milvusClient.search(searchParam);
if (searchResultsResponse != null && searchResultsResponse.getData() != null) {
logger.info("搜索成功完成");
return searchResultsResponse.getData();
}
} catch (Exception e) {
logger.error("搜索数据失败: {}", e.getMessage());
}
return null;
}
/**
* 删除集合
* @param collectionName 集合名称
* @return 是否删除成功
*/
public boolean dropCollection(String collectionName) {
try {
DropCollectionParam dropCollectionParam = DropCollectionParam.newBuilder()
.withCollectionName(collectionName)
.build();
milvusClient.dropCollection(dropCollectionParam);
logger.info("删除集合成功: {}", collectionName);
return true;
} catch (Exception e) {
logger.error("删除集合失败: {}", e.getMessage());
return false;
}
}
}
有任何问题可以评论