Qdrant 连接失败排查:localhost 解析到 __1,而 Docker 只绑了 IPv4


📝 本文首发于 栏轩·阁

欢迎访问阅读原文,获取更好的阅读体验。


Qdrant 连不上:localhost 把我带到了 IPv6,Docker 却只住在 IPv4

背景

项目是 Spring Boot 4.1.1 + Spring AI 2.0.1(Java 21),向量库用 Qdrant,跑在 Docker 里,端口映射是:

  • 16334:6334:gRPC,Java 客户端走这个
  • 16333:6333:HTTP REST,Web 控制台走这个

测试里往向量库写一批文档,13 个批次全部失败,每个批次都报同一段:

复制代码
io.grpc.StatusRuntimeException: UNAVAILABLE: io exception
Caused by: io.grpc.netty.shaded.io.netty.channel.AbstractChannel$AnnotatedConnectException:
Connection refused: getsockopt: localhost/[0:0:0:0:0:0:0:1]:16334

启动时还有一条不痛不痒但可疑的 WARN:

复制代码
QdrantGrpcClient - Failed to obtain server version. Unable to check client-server compatibility.

最诡异的是:浏览器打开 http://localhost:16333/dashboard 一点问题没有;MySQL(localhost:9006)、Minio(localhost:9090)在应用里也一直好用。唯独 Qdrant 的 gRPC 死死地连不上。

原因初步分析

先从地址下手。配置文件(args-dev.yaml)里写的是:

yaml 复制代码
qdrant:
  host: "localhost"
  port: 16334

问题几乎一定出在 "localhost" 这个词上,而不是 Qdrant 没起来。用 Test-NetConnection 反复探测,结果非常分裂:

powershell 复制代码
Test-NetConnection -ComputerName 127.0.0.1 -Port 16334 -InformationLevel Quiet   # True
Test-NetConnection -ComputerName ::1       -Port 16334 -InformationLevel Quiet   # False

IPv4 回环通,IPv6 回环不通。再看 localhost 到底解析成什么:

powershell 复制代码
[System.Net.Dns]::GetHostAddresses('localhost')
# 依次返回 ::1(IPv6)和 127.0.0.1(IPv4),::1 排在最前面

Docker 那边:md-com.yml 里端口映射写的是 "127.0.0.1:16334:6334",Docker Desktop 把宿主端口只发布到了 IPv4 的 127.0.0.1 上,IPv6 的 ::1 上没有任何人监听。

到这里原因就清晰了:localhost 同时解析出 ::1 和 127.0.0.1 两个地址,IPv6 排前面;客户端拿第一个地址去连 ::1:16334,而服务只住在 127.0.0.1:16334。两边各连各的,永远碰不上。

顺带一提,报错内容还随容器状态变脸:容器在跑时是 Connection timed out(请求被静默丢弃),容器停掉后变成 Connection refused(没人应答)。表现不同,根因是同一个。

验证

先确认问题不在 Qdrant 配置,而在"谁来连接"。把三个服务在两种回环上的监听情况全探了一遍:

服务 端口 127.0.0.1 ::1
Qdrant gRPC 16334 通 不通
Qdrant HTTP 16333 通 不通
MySQL 9006 通 不通
Minio 9090 通 不通

结论很意外也很解气:所有 Docker 发布的端口都只绑 IPv4,无一例外。 MySQL、Minio 和 Qdrant 并没有任何区别------区别全在客户端:

  • 浏览器(Chrome/Edge/Firefox)实现了 Happy Eyeballs(RFC 6555):拿到多个地址后同时/先后发起连接,::1 拒连几乎立即切到 127.0.0.1,所以控制台秒开;
  • MySQL 的 JDBC 驱动、Minio 用的 OkHttp:遍历解析出的所有地址,第一个失败就试下一个,所以应用里一直正常;
  • Java 的 gRPC(Netty)客户端:只取地址列表里的第一个(::1),失败不轮换到第二个,所以 Qdrant 必死。

之前在 HikariCP 日志里能看到 MySQL 连接成功、Minio 上传成功------这些"看起来正常"其实都是客户端在帮我擦 ::1 的屁股,只是擦得无声无息。

解决方法

把开发配置里的 host 从 "localhost" 显式改成 IPv4 地址:

yaml 复制代码
qdrant:
  host: "127.0.0.1"
  port: 16334

改完重跑,启动时不再出现版本检查告警、批次不再报 Connection refused,链路就通了。Docker 部署用的 args.yaml 本来就写的是容器名 md-qdrant:6334,在 compose 网络里直连容器、不经过 localhost,保持原样即可。

如果坚持用 localhost,也有 JVM 参数 -Djava.net.preferIPv4Stack=true 这种全局方案,但它把所有库的地址解析一起压成 IPv4,属于把问题盖住而不是修掉,本地连 Docker 端口不值得这么干。

总结

这次排查留下的印象:

  • "localhost 能用"不代表配置没问题,只代表你的客户端会自动降级。同一台机器上,浏览器、JDBC、OkHttp 都在替你兜 IPv6 的底,只有 gRPC 不兜。
  • 本地连 Docker 发布的端口,别写 localhost,直接写 127.0.0.1,少一层解析就少一类问题。
  • 排查"服务明明在监听却连不上"最快的一步,是分别探测 127.0.0.1 和 ::1 两个回环,再加一条 [System.Net.Dns]::GetHostAddresses('localhost') 看解析顺序------十分钟定位,不用猜。
相关推荐
IT大白鼠7 小时前
彭大帅的AI运维助手——怎么给AI写运维SOP:提示词设计实战
运维·人工智能·彭大帅
Canmag 兴隆磁性14 小时前
中大型钕铁硼产品的磁通测量与自动分选:一条产线自动化的工程实现思路
运维·学习·自动化·永磁材料
xuefeiniao16 小时前
PHP2Shell漏洞修复
运维·服务器·php
便利店102417 小时前
系统活动一抓留存!深入 Linux C/C++ 调试工具之 sysdig
linux·运维·服务器·sysdig
智能运维指南18 小时前
企业 IT 运维自动化平台怎么选?从场景匹配到 POC 验证的完整评估框架
运维·自动化·自动化运维·嘉为蓝鲸
小马同学-18 小时前
K8s 控制器详解 — Deployment、ReplicaSet、DaemonSet 与 Job
云原生·容器·kubernetes
TTATTC19 小时前
单体应用如何支撑千所机构独立测评?拆解 sg-exam 行级多租户隔离与动态组卷内核
docker·开源
toooooop819 小时前
CentOS 7 内核 3.10.0-1160.119.1 升级指南
linux·运维·centos
TTATTC19 小时前
每加一个登录端都要改 Controller?Spring Boot 泛型策略工厂与模板切面演进
docker·开源
云飞云共享云桌面19 小时前
广东东莞制造工厂10人共享一台三维设计服务器
运维·服务器·3d·自动化·制造