尧图精选

基于langchain4j的Spring Boot集成Milvus向量检索实战

🕒 发布时间:2026/10/2 8:53:35 📁 来源:尧图网络
最近不少 Java 技术栈的朋友来问Spring Boot 项目要接 Milvus 做向量检索怎么搞网上一搜教程基本被 Python 和 pymilvus“霸屏”Java 生态的资料不是太老就是太碎。很多人卡在第一步就懵了到底是直接用官方 milvus-sdk-java还是走 langchain4j连接串到底怎么写为什么本地把./data/milvus.db塞进去会报错这篇文章就是我最近把一个 Spring Boot 服务接入 Milvus 的完整记录用的是 langchain4j 这套集成方案。我会直接从选型逻辑讲到 Docker 部署、核心代码、检索链路最后附上这段时间踩过的坑。目标很明确让你照着这篇文章能从一个空项目开始把 Spring Boot 连接 Milvus 跑通并且知道每一步为什么这么干。适合准备做 RAG、知识库检索、语义搜索但主力语言是 Java 的开发者。1. 为什么我用 langchain4j 而不是直接调 Milvus SDK先聊一个容易被忽略的问题既然 Milvus 官方提供了 Java SDK为什么还要绕一层 langchain4j我的判断是如果你只是想在 Spring Boot 里“连上”Milvus用官方milvus-sdk-java完全没问题但如果你想做的是“检索增强生成”这类应用那直接用 SDK 会非常痛苦。你自己得维护 embedding 调用、向量存储、相似度检索、上下文拼接……这些琐碎但高频的功能本质上是 LLM 应用的基础设施不是业务代码。langchain4j 把这些东西抽象好了它对标的是 Python 生态里的 LangChain在 Java 世界里算是最成熟的方案之一。拿官方 SDK 和 langchain4j 做个直观对比更清楚对比项官方 milvus-sdk-javalangchain4j-milvus 集成连接管理手动创建 MilvusServiceClient处理 channel、超时通过 EmbeddingStore builder 封装内部管理Schema 定义手动建 Collection、字段、索引、度量方式自动建 Collection配置化完成Embedding 接入自己调用模型服务自己拼向量与各类 EmbeddingModel 无缝集成相似度检索手动构造 QueryParam、OutputField一行similaritySearch自动处理向量化与 Spring Boot 整合要自己写配置类、封装服务天然面向 Spring 场景可以直接注册 Bean最关键的一点langchain4j 里的MilvusEmbeddingStore本身还是基于官方 SDK 封装的所以底层通信能力没有缩水只是把脏活累活给隐藏了。我在生产环境里实测下来连接稳定性和检索性能都够用。我见过有团队坚持用官方 SDK 手搓检索服务结果代码里塞满了向量维度校验、字段映射、结果解析这种重复代码。不是不能跑是没必要。提示如果你的项目目标只是“往 Milvus 里灌向量”而不关心后续的语义检索那用官方 SDK 反而更轻。但如果你做的是知识库问答、智能客服、文档检索强烈建议直接用 langchain4j省下的维护时间不是一点半点。2. 先把 Milvus 跑起来Mac 上 Docker 部署的实操细节说实话Milvus 服务的启动本身不难难在环境细节。我开发机是 Mac这里就以 Mac Docker 为例讲Linux 服务器上的部署思路完全一样只是路径和防火墙略有差异。2.1 Docker Compose 部署 standalone 模式官方推荐的生产模式是分布式但本地开发用 standalone 就够了。我是用 Docker Compose 一次性拉起 etcd、minio、milvus 三个组件。很多人看到三个服务就头大其实 standalone 模式下它们就是 Milvus 的三个内部依赖一个 Compose 文件全部搞定version: 3.5 services: etcd: container_name: milvus-etcd image: quay.io/coreos/etcd:v3.5.5 environment: - ETCD_AUTO_COMPACTION_MODErevision - ETCD_AUTO_COMPACTION_RETENTION1000 - ETCD_QUOTA_BACKEND_BYTES4294967296 - ETCD_SNAPSHOT_COUNT50000 volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd command: etcd -advertise-client-urlshttp://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: container_name: milvus-standalone image: milvusdb/milvus:v2.4.9 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 - 9091:9091 depends_on: - etcd - minio这里有个很容易踩的坑镜像版本尽量指定不要用latest。我试过在 Mac 上拉最新版镜像偶尔会遇到与本地 Docker 版本不兼容导致的启动失败报错信息还特别含糊。指定v2.4.9这类具体版本至少你能确定这组依赖是官方测试过的。部署命令就是docker compose up -d然后看日志确认启动成功docker logs -f milvus-standalone如果能看到milvus server started之类的日志说明服务起来了。2.2 健康检查与可视化工具服务起来之后先验证监听端口是否正常。Milvus 的 gRPC 端口是 19530我习惯用 curl 快速探测一下curl -X GET http://localhost:9091/healthz返回正常就说明 Milvus 的健康检查接口通了。接下来建议装一个 Attu这是 Milvus 的可视化管理界面对排查数据特别有用。一行命令启动docker run -p 3000:3000 -e MILVUS_URLhost.docker.internal:19530 zilliz/attu:latest浏览器打开http://localhost:3000填入host.docker.internal:19530就能连上。有了 Attu集合里的数据、索引、检索结果都可视化调试效率高很多。2.3 本地文件模式与 Docker 模式别搞混热词里提到的milvus_uri: str ./data/milvus.db这是 Python 专用里的本地文件模式很多人在 Python 教程里看到后想在 Java 里照抄结果发现根本没有milvus.db这个文件生成直接报错。这个必须说清楚Java SDK 和 langchain4j 连接 Milvus走的都是 gRPC 网络协议连接串是host:port的形式不是本地文件路径。想用本地模式只有 Python 的 pymilvus 在特定场景下支持而且它本质上是面向测试和单机小数据量的不是常规连接方式。在 Spring Boot 里就别惦记这个了老老实实连 Docker 里的服务。注意在 Mac 上如果用localhost:19530连接失败先检查 Docker Desktop 是否在运行再检查端口映射是否生效。docker ps看不到容器的话大概率是 Compose 文件里的环境变量没配好或者端口被宿主机其他进程占了。3. Spring Boot 工程初始化依赖版本和配置的讲究Milvus 服务部署好了接下来就是搭建 Spring Boot 工程。这部分看起来简单实际上版本坑最多。3.1 pom.xml 依赖怎么加才不打架我建了一个普通的 Maven 项目Java 版本用的 17。Spring Boot 版本选择有个原则别追最新选一个 langchain4j 明确兼容过的版本。我自己一开始用了 Spring Boot 3.3.x结果和某个 langchain4j 旧版本存在依赖冲突启动报错降级到 3.2.x 就好了。后来我直接用 Spring Boot 3.2.x一路顺畅。核心依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent properties java.version17/java.version langchain4j.version0.36.2/langchain4j.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version${langchain4j.version}/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-milvus/artifactId version${langchain4j.version}/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version${langchain4j.version}/version /dependency /dependencies注意这里我只加了langchain4j-milvus和langchain4j-open-ai没有单独引官方 milvus Java SDK因为langchain4j-milvus会把需要的 SDK 依赖带进来。如果你有自定义 embedding 模型需求可以在后面自己替换langchain4j-open-ai为其他实现。3.2 配置项连接参数和集合参数Spring Boot 项目里我习惯把 Milvus 相关参数统一放到application.yml方便切换环境。核心配置结构如下langchain4j: milvus: host: localhost port: 19530 collection-name: my_kb dimension: 1536 index-type: AUTOINDEX metric-type: COSINE consistency-level: STRONG这里几个参数背后都有讲究dimension必须和你的 embedding 模型输出维度一致。我用的是 OpenAI 的text-embedding-3-small模型输出 1536 维所以配置里写的 1536。如果你用其他模型一定要先查清楚维度不然后面插入数据会报错。metric-type我用的 COSINE适合做文本语义相似度。如果你做的业务对欧氏距离或者内积更敏感可以换 EUCLIDEAN 或 IP。这个要和检索场景匹配选错了效果差别很大。consistency-level默认用 STRONG 就好开发阶段避免出现“写进去了查不到”这种诡异问题。生产环境可以按需调成 BOUNDED 降低一致性开销。3.3 为什么我不用./data/milvus.db做连接配置这其实呼应 2.3 节。Spring Boot 里如果你看到网上有资料让你把milvus.db当作本地加载路径直接忽略。Java 这边的连接方式永远是网络连接。application.yml里的host和port在 Mac 本地开发时就是localhost:19530在服务器上就是你远程部署 Milvus 的 IP 和端口。4. 核心代码从连接、建集到写入和检索4.1 MilvusEmbeddingStore 的构造方式langchain4j 里连接 Milvus 的核心类是MilvusEmbeddingStore。它是EmbeddingStore接口的一个实现内部封装了建集合、插入向量、检索相似度这些能力。我写了一个配置类专门用来创建这个 Beanpackage com.example.milvusdemo.config; import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class MilvusConfig { Value(${langchain4j.milvus.host}) private String host; Value(${langchain4j.milvus.port}) private Integer port; Value(${langchain4j.milvus.collection-name}) private String collectionName; Value(${langchain4j.milvus.dimension}) private Integer dimension; Bean public MilvusEmbeddingStore milvusEmbeddingStore() { return MilvusEmbeddingStore.builder() .host(host) .port(port) .collectionName(collectionName) .dimension(dimension) .build(); } }这段代码里有个容易忽略的细节MilvusEmbeddingStore.builder()是流式构造每次启动都会检查集合是否存在不存在就自动建。所以我前面application.yml里的collection-name必须是全局唯一的业务集合名避免多个环境共用同一个 Milvus 时互相干扰。4.2 构建 EmbeddingModel有了存储还得有 embedding 模型才能把文本变成向量。我用OpenAiEmbeddingModel来举例它支持 OpenAI 官方接口也支持兼容 OpenAI 协议的自建服务package com.example.milvusdemo.config; import dev.langchain4j.model.openai.OpenAiEmbeddingModel; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class EmbeddingModelConfig { Value(${embedding.base-url:https://api.openai.com}) private String baseUrl; Value(${embedding.api-key:}) private String apiKey; Value(${embedding.model-name:text-embedding-3-small}) private String modelName; Bean public OpenAiEmbeddingModel openAiEmbeddingModel() { return OpenAiEmbeddingModel.builder() .baseUrl(baseUrl) .apiKey(apiKey) .modelName(modelName) .build(); } }提示如果你用的是国产模型或者企业内部模型只要它提供 OpenAI 兼容的 embedding 接口即便不用baseUrl这个配置名也行。关键是确认它返回的向量维度并同步修改dimension。4.3 文本写入 Milvus 的完整服务存储和模型都有了接下来就是把他们组合起来。我写了一个VectorKnowledgeService负责接收一段文档分块后写入知识库并提供检索接口。这里直接演示最常用的两条链路。先看写入package com.example.milvusdemo.service; import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.splitter.DocumentSplitters; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.model.embedding.EmbeddingModel; import org.springframework.stereotype.Service; import java.util.List; Service public class VectorKnowledgeService { private final EmbeddingStoreTextSegment embeddingStore; private final EmbeddingModel embeddingModel; public VectorKnowledgeService(EmbeddingStoreTextSegment embeddingStore, EmbeddingModel embeddingModel) { this.embeddingStore embeddingStore; this.embeddingModel embeddingModel; } public void saveDocument(String documentContent) { Document document Document.from(documentContent); ListTextSegment segments DocumentSplitters.recursive(300, 50) .split(document); for (TextSegment segment : segments) { embeddingStore.add(embeddingModel.embed(segment.text()).content(), segment); } } }这段代码做了三件事把文档字符串封装成Document用递归分割器按 300 字符一块、50 字符重叠切成TextSegment逐段生成向量后写入 Milvus。注意它依赖的embeddingStoreBean 是MilvusEmbeddingStore因为EmbeddingStore是接口Spring 会自动注入我们前文配置的实现类。切块参数300 / 50不是随便写的。300 这个块大小是我在中文场景下做了多次效果对比后选择的太小则语义不完整太大则检索粒度太粗。重叠的 50 个字符则是为了保留跨块上下文。如果你的文档偏英文或者结构化明显可以适当调大块大小。4.4 检索从问题到答案的链路写入不是目的能查出来才是。检索的核心方法是similaritySearch它接受一个查询向量返回 Milvus 中最相近的 TopK 个文本片段public ListString search(String query, int topK) { var queryEmbedding embeddingModel.embed(query).content(); var relevant embeddingStore.search(queryEmbedding, topK); return relevant.stream() .map(match - match.embedded().text()) .toList(); }我在实际项目里会把topK设为 4把返回的片段拼起来作为 LLM 回答的上下文。如果你只想验证 Milvus 连接是否通可以先写一个简单的 Controller 调一下search方法直接看返回的文本片段是不是和 query 语义相关。完整的控制器如下package com.example.milvusdemo.controller; import com.example.milvusdemo.service.VectorKnowledgeService; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/kb) public class KnowledgeController { private final VectorKnowledgeService knowledgeService; public KnowledgeController(VectorKnowledgeService knowledgeService) { this.knowledgeService knowledgeService; } PostMapping(/documents) public void save(RequestBody String content) { knowledgeService.saveDocument(content); } GetMapping(/search) public ListString search(RequestParam String q, RequestParam(defaultValue 4) int topK) { return knowledgeService.search(q, topK); } }到这里一个最小闭环已经跑通了往/kb/documents发一段文本文本被切片向量化后写入 Milvus再通过/kb/search发一个查询就能拿到最相关的片段列表。后面接大模型做生成就是顺理成章的事。5. 我踩过的几个典型坑连接失败、维度不一致、版本冲突这部分是我最想写的内容因为网上教程通常只讲到“跑通 Demo”就停了真正的麻烦往往在 Demo 之后。5.1 连接失败Connection refused的排查链路我第一次启动项目时控制台直接抛Connection refused。当时的第一反应是代码写错了后来排查发现是 Docker Compose 里的 Milvus 容器根本没起来。排查链路其实有规律先看 Docker 容器状态docker ps -a确认milvus-standalone是否在运行。看日志docker logs milvus-standalone如果日志停在某个依赖服务超时多半是 etcd 或 minio 没就绪。检查端口占用lsof -i :19530确认没有其他进程占用。最后才看代码里的 host 和 port 是否拼错。还有一次我换了台机器部署把localhost硬编码在配置里结果服务部署在其他机器上访问不到。正确做法是配置可注入的 IP或者用环境变量覆盖。5.2 插入向量报错维度不匹配是最常见的问题另一个高频报错是类似“collection dimension is 1536, but data dimension is 768”这样的错误。这个错误基本只有一种原因application.yml里的dimension和实际 embedding 模型输出维度不一致。我一开始用的本地 embedding 模型输出是 768 维但配置里还留着 1536结果插入第一条数据就崩了。后来我把配置改成 768问题立刻消失。给个经验法则先确认模型再写配置。任何情况下代码里嵌入的模型输出维度都需要和 Milvus 集合的维度保持一致。如果后续换了模型哪怕只是换了个版本也可能导致维度变化。你需要在换模型时重建集合并重新灌数据这不是改个配置就能解决的问题。5.3 langchain4j 版本与 Spring Boot 版本的兼容性版本冲突问题藏得比较深。我遇到过项目启动时NoSuchMethodError的情况排查到最后发现是 langchain4j 传递依赖的某个类与 Spring Boot 内置的版本不一致。建议是Spring Boot 用 3.2.xlangchain4j 用 0.36.x这是目前最稳的组合。如果你用 Spring Boot 3.4 或更高版本先查一下 langchain4j 官方 release notes 里有没有声明兼容性确认之后再升级。不要盲目追新尤其是这种依赖链比较长的集成库。如果已经出现冲突最快的解决方式是统一版本mvn dependency:tree | grep langchain4j查看所有 langchain4j 相关模块是否同一版本如果有模块版本不一致在 pom 的dependencyManagement里强制指定统一版本号。5.4 集合自动创建与幂等性MilvusEmbeddingStore的 builder 会自动创建集合这个功能很方便但它在重复插入时会带来一个问题你重复跑保存文档的方法文档不会自动去重而是每次都往集合末尾追加。实测下来如果我写了个测试脚本反复插入同一段文本集合里会出现多条完全一样的向量检索时返回的相似片段就会重复。解决方案有两个维护文档 ID插入前先查询存在就跳过或用add方法更新langchain4j 提供了带 ID 的add方法。干脆建一个清理定时任务开发环境下定期删除整个集合并重建。我在开发环境用的是第二种简单粗暴正式环境里我会为每篇文档生成一个 hash 作为 ID避免重复写入。6. 一点工程化建议从 Demo 走向可用跑通基础链路之后有几个事情值得提前想清楚。第一连接管理。MilvusEmbeddingStore默认会管理自己的连接但长时间运行后如果出现连接假死可以考虑定时发送心跳请求或者定期重建连接。我在做压测时遇到过偶发超时重启应用就好了说明连接状态并不是百分百稳定。这部分官方文档写得比较少建议你在生产环境加一层监控。第二数据生命周期。Milvus 里的数据不会自动清理集合会一直膨胀。如果你的知识库内容经常变动最好给每条数据打上业务标签比如通过TextSegment的自定义 metadata 存一个category字段。检索时可以按标签过滤避免全量集合上的无效搜索。第三检索质量调优。similaritySearch返回相关片段只是第一步真正决定用户体验的是 TopK 怎么选、片段怎么拼、大模型怎么用。我在实践中发现固定 TopK4、把返回的文本按相似度排序拼接回答效果通常好于盲目调大 TopK。TopK 太大会引入噪音太小会丢失关键上下文这个需要根据文档粒度慢慢试。第四安全与合规。Milvus 默认没有开启认证如果部署在公网服务器一定要通过安全组限制 19530 端口只对应用服务器开放或者开启用户名密码认证。知识库里的内容也建议做访问控制避免通过检索接口把所有数据暴露出去。我在实际使用中的体会是Spring Boot 接 Milvus 这件事技术上不复杂复杂度全在细节里。用 langchain4j 能帮你挡住大部分底层琐事但连接配置、维度一致性、版本兼容这些关键参数还是得自己心里有数。最后再分享一个小技巧开发阶段把application.yml里的consistency-level调成 STRONG配合 Attu 的可视化界面你能很直观地看到数据写的时机和查的结果排查问题会轻松非常多。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →