【码动四季】夏|SpringBoot 集成 Qdrant 报 UNAVAILABLE: No servers found 完整踩坑修复方案

📅 2026/7/18 16:33:39 👁️ 阅读次数 📝 编程学习
【码动四季】夏|SpringBoot 集成 Qdrant 报 UNAVAILABLE: No servers found 完整踩坑修复方案

SpringBoot集成Qdrant报UNAVAILABLE: No servers found 完整踩坑修复方案

一、问题现象

本地单元测试单独调用Qdrant客户端完全正常,SpringBoot项目启动后,业务代码调用Qdrant向量库接口直接抛出gRPC异常:

io.grpc.StatusRuntimeException:UNAVAILABLE:Noservers foundfor127.0.0.1:6334

网络层面验证:
Qdrant 服务正常监听0.0.0.0:6333/6334端口;
Windows 命令行Test-NetConnection 127.0.0.1 -Port 6334返回 TCP 连接成功;
无 xds、服务发现、round_robin 负载均衡相关自定义配置。

二、问题根因

1.双 Netty 传输依赖冲突
项目同时存在两套 gRPC Netty 实现:

  • SpringBoot gRPC 组件传递引入 io.grpc:grpc-netty(原生 netty);
  • Qdrant 客户端依赖 io.grpc:grpc-netty-shaded:1.51.0(隔离版 netty)。

二者 SPI 优先级相同,打包后优先加载原生 netty,且 Qdrant 依赖排除原生 netty 类,导致通道创建成功但无法建立连接。
单元测试无 SpringBoot gRPC 依赖,仅存在 shaded 包,因此测试正常。

2. Windows 下 gRPC DNS 解析器兼容问题
即便强制使用 shaded 通道,Windows 环境默认DnsNameResolver解析纯 IP 地址不稳定,会返回空地址列表,gRPC 判定无可用服务节点,抛出No servers found。

三、最终修复方案:自定义静态 NameResolver

核心思路:抛弃默认 DNS 解析逻辑,预解析 IP 生成固定地址列表,绕过不稳定解析器;同时显式使用 shaded Netty 构建通道,隔离两套 netty 实现。

importio.grpc.*;importio.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importio.qdrant.client.QdrantClient;importjava.net.InetAddress;importjava.net.InetSocketAddress;importjava.net.UnknownHostException;importjava.util.Collections;importjava.util.List;@ConfigurationpublicclassQdrantConfig{@Value("${qdrant.host:127.0.0.1}")privateStringhost;@Value("${qdrant.port:6334}")privateintport;@BeanpublicQdrantClientqdrantClient()throwsUnknownHostException{// 预解析IP地址InetAddressaddress=InetAddress.getAllByName(host)[0];List<EquivalentAddressGroup>addressGroups=Collections.singletonList(newEquivalentAddressGroup(newInetSocketAddress(address,port)));// 自定义静态地址解析工厂NameResolver.FactoryfixedNameResolverFactory=newNameResolver.Factory(){@OverridepublicNameResolvernewNameResolver(URItargetUri,NameResolver.Argsargs){returnnewNameResolver(){@OverridepublicStringgetServiceAuthority(){returnhost+":"+port;}@Overridepublicvoidstart(Listener2listener){System.out.println("Qdrant静态解析地址列表:"+addressGroups);listener.onAddresses(addressGroups,Attributes.EMPTY);}@Overridepublicvoidshutdown(){}};}@OverridepublicStringgetDefaultScheme(){return"fixed";}};// 使用shaded netty构建通道,绑定自定义解析器与pick_first负载均衡ManagedChannelchannel=NettyChannelBuilder.forTarget("fixed://"+host+":"+port).nameResolverFactory(fixedNameResolverFactory).defaultLoadBalancingPolicy("pick_first").usePlaintext().keepAliveTime(10,TimeUnit.SECONDS).build();returnnewQdrantClient(channel);}}

2. Service 层规范改造

采用 lombok 构造注入,清理冗余注解与无用导入:

importlombok.RequiredArgsConstructor;importio.qdrant.client.QdrantClient;importorg.springframework.stereotype.Service;@Service@RequiredArgsConstructorpublicclassQdrantServiceImpl{privatefinalQdrantClientqdrantClient;// 向量集合创建、检索、写入等业务方法}

四、验证效果

项目启动日志输出静态地址:Qdrant静态解析地址列表:[/127.0.0.1:6334];
调用 Qdrant 所有接口不再抛出No servers found异常;
单元测试、SpringBoot 运行环境完全兼容;
项目内其他 gRPC 服务不受改动影响,传输层完全解耦。

五、方案取舍说明

不推荐全局排除原生 grpc-netty
直接排除grpc-netty虽能统一 shaded 包,但 SpringBoot gRPC 服务端构建器强依赖原生 netty,排除后会导致内部 gRPC 服务启动失败,改动范围大、风险高。本方案仅针对 Qdrant 客户端单独隔离,最小侵入业务。

长期统一 netty 优化方案
若后续项目全量使用 shaded netty,可在父工程依赖管理统一管控 gRPC 版本,通过配置grpc.netty.server.mode=shaded切换全局传输实现,可作为后续独立优化项。

六、开发踩坑总结

No servers found核心根源优先定位NameResolver地址解析结果,而非 Netty 线程池;
Windows 本地开发,gRPC 默认 DNS 解析器对纯 IP 存在兼容性缺陷;
Classpath 同时存在两套 gRPC Netty 依赖时,典型特征为单测正常、运行报错;
TCP 连通不代表 gRPC 服务可用,gRPC 拥有独立地址解析、负载均衡链路;
网络通用 Netty 冲突方案不一定适配向量库客户端,请勿直接复制通用代码。

七、application.yml 配置

qdrant:host:127.0.0.1port:6334