Milvus Ollama 实战5分钟搭建本地文本搜索引擎Java版在当今信息爆炸的时代快速准确地检索文本内容已成为开发者面临的常见挑战。传统的关键词匹配搜索方式往往难以满足语义相似度的需求而基于向量搜索的技术正逐渐成为解决这一问题的利器。本文将带你使用Milvus向量数据库和Ollama文本嵌入工具通过Java快速构建一个本地文本搜索引擎。1. 环境准备与工具介绍搭建本地文本搜索引擎前我们需要准备两个核心组件和一个开发环境Milvus专为向量搜索优化的开源数据库支持亿级向量的毫秒级检索Ollama本地运行的文本嵌入模型服务可将文本转换为高维向量Java开发环境推荐JDK 11和Maven 3.6安装过程非常简单# 安装Milvus使用Docker方式 docker pull milvusdb/milvus:v2.4.0 docker run -d --name milvus -p 19530:19530 milvusdb/milvus:v2.4.0 # 安装OllamaMac/Linux curl -fsSL https://ollama.com/install.sh | sh ollama pull nomic-embed-text提示Windows用户可通过WSL2运行OllamaMilvus安装方式与Linux一致2. 项目初始化与依赖配置创建一个标准的Maven项目添加以下关键依赖到pom.xmldependencies !-- Milvus Java SDK -- dependency groupIdio.milvus/groupId artifactIdmilvus-sdk-java/artifactId version2.4.0/version /dependency !-- HTTP客户端 -- dependency groupIdcom.squareup.okhttp3/groupId artifactIdokhttp/artifactId version4.12.0/version /dependency !-- JSON处理 -- dependency groupIdcom.google.code.gson/groupId artifactIdgson/artifactId version2.10.1/version /dependency /dependencies基础配置类应包含以下常量public class Config { // Milvus配置 public static final String MILVUS_HOST localhost; public static final int MILVUS_PORT 19530; public static final String MILVUS_TOKEN root:Milvus; // Ollama配置 public static final String OLLAMA_URL http://localhost:11434/v1/embeddings; public static final String EMBEDDING_MODEL nomic-embed-text; // 集合配置 public static final String COLLECTION_NAME text_search; public static final int VECTOR_DIMENSION 768; }3. 核心功能实现3.1 向量存储模块首先创建Milvus集合定义包含三个字段的schemaid主键字段Int64content原始文本VarCharvector文本向量FloatVectorpublic class VectorStorage { private final MilvusClientV2 client; public VectorStorage() { ConnectConfig connectConfig ConnectConfig.builder() .uri(http:// Config.MILVUS_HOST : Config.MILVUS_PORT) .token(Config.MILVUS_TOKEN) .build(); this.client new MilvusClientV2(connectConfig); } public void createCollection() { // 构建字段schema CreateCollectionReq.CollectionSchema schema client.createSchema(); schema.addField(AddFieldReq.builder() .fieldName(id) .dataType(DataType.Int64) .isPrimaryKey(true) .autoID(true) .build()); schema.addField(AddFieldReq.builder() .fieldName(content) .dataType(DataType.VarChar) .maxLength(1000) .build()); schema.addField(AddFieldReq.builder() .fieldName(vector) .dataType(DataType.FloatVector) .dimension(Config.VECTOR_DIMENSION) .build()); // 创建索引 IndexParam vectorIndex IndexParam.builder() .fieldName(vector) .indexType(IndexParam.IndexType.AUTOINDEX) .metricType(IndexParam.MetricType.COSINE) .build(); // 创建集合 CreateCollectionReq createReq CreateCollectionReq.builder() .collectionName(Config.COLLECTION_NAME) .collectionSchema(schema) .indexParams(Collections.singletonList(vectorIndex)) .build(); client.createCollection(createReq); } }3.2 文本向量化服务通过Ollama API将文本转换为向量public class TextEmbedder { private static final Gson gson new Gson(); private final OkHttpClient httpClient; public TextEmbedder() { this.httpClient new OkHttpClient(); } public float[] embedText(String text) throws IOException { JsonObject requestBody new JsonObject(); requestBody.add(input, gson.toJsonTree(new String[]{text})); requestBody.addProperty(model, Config.EMBEDDING_MODEL); Request request new Request.Builder() .url(Config.OLLAMA_URL) .post(RequestBody.create( requestBody.toString(), okhttp3.MediaType.parse(application/json) )) .build(); try (Response response httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException(请求失败: response.code()); } JsonObject jsonResponse gson.fromJson( response.body().string(), JsonObject.class ); JsonArray embedding jsonResponse.getAsJsonArray(data) .get(0).getAsJsonObject() .getAsJsonArray(embedding); float[] vector new float[embedding.size()]; for (int i 0; i embedding.size(); i) { vector[i] embedding.get(i).getAsFloat(); } return vector; } } }3.3 数据导入与搜索实现实现文本数据的批量导入和相似度搜索功能public class SearchEngine { private final VectorStorage storage; private final TextEmbedder embedder; public SearchEngine() { this.storage new VectorStorage(); this.embedder new TextEmbedder(); } public void indexDocuments(ListString documents) { ListJsonObject insertData new ArrayList(); for (String doc : documents) { try { float[] vector embedder.embedText(doc); JsonObject item new JsonObject(); item.addProperty(content, doc); item.add(vector, gson.toJsonTree(vector)); insertData.add(item); } catch (IOException e) { System.err.println(文本向量化失败: e.getMessage()); } } InsertReq insertReq InsertReq.builder() .collectionName(Config.COLLECTION_NAME) .data(insertData) .build(); storage.getClient().insert(insertReq); } public ListString searchSimilar(String query, int topK) { try { float[] queryVector embedder.embedText(query); SearchReq searchReq SearchReq.builder() .collectionName(Config.COLLECTION_NAME) .data(Collections.singletonList(new FloatVec(queryVector))) .topK(topK) .outputFields(Collections.singletonList(content)) .build(); SearchResp resp storage.getClient().search(searchReq); return resp.getSearchResults().stream() .flatMap(Collection::stream) .map(result - result.getEntity().get(content).getAsString()) .collect(Collectors.toList()); } catch (IOException e) { System.err.println(搜索失败: e.getMessage()); return Collections.emptyList(); } } }4. 实战应用与性能优化4.1 完整使用示例下面是一个从初始化到搜索的完整流程public class Demo { public static void main(String[] args) { // 初始化引擎 SearchEngine engine new SearchEngine(); // 创建集合首次运行 engine.getStorage().createCollection(); // 准备测试数据 ListString documents Arrays.asList( Milvus是一个开源的向量数据库, Ollama可以在本地运行大语言模型, 向量搜索比传统关键词搜索更理解语义, Java是最流行的编程语言之一 ); // 导入数据 engine.indexDocuments(documents); System.out.println(数据导入完成); // 执行搜索 String query 有哪些好用的向量数据库; ListString results engine.searchSimilar(query, 2); System.out.println(与 query 最相似的结果); results.forEach(System.out::println); } }4.2 性能优化技巧根据实际测试经验以下方法可以显著提升系统性能批量处理将多个文本组合成一个batch发送给Ollamapublic Listfloat[] batchEmbed(ListString texts) { // 将多个文本组合成一个请求 JsonObject requestBody new JsonObject(); requestBody.add(input, gson.toJsonTree(texts)); // ...其余处理逻辑 }连接池配置优化Milvus客户端连接ConnectConfig connectConfig ConnectConfig.builder() .uri(milvusUri) .token(token) .connectTimeout(10, TimeUnit.SECONDS) // 连接超时 .keepAliveTimeout(30, TimeUnit.SECONDS) // 保持连接 .build();索引调优根据数据规模选择合适的索引类型数据规模推荐索引类型适用场景10万FLAT精确搜索10-100万IVF_FLAT平衡精度与速度100万HNSW大规模数据4.3 常见问题排查遇到问题时可以按照以下步骤检查连接问题确认Milvus服务是否运行docker ps | grep milvus检查端口是否开放telnet localhost 19530向量维度不匹配确保Ollama输出的维度与Milvus集合定义一致不同模型输出维度参考nomic-embed-text: 768all-minilm: 384性能瓶颈使用time命令测量各阶段耗时考虑在Milvus中启用分区功能处理大数据集