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') 看解析顺序------十分钟定位,不用猜。
相关推荐
分布式存储与RustFS6 小时前
群晖 NAS 被 C3RB3R 锁了:本地备份一起被加密,RustFS 对象锁为什么删不掉
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
真好啊又活了一天6 小时前
服务器和虚机配置IPV6
运维·服务器·github
zixu_star6 小时前
定时任务是什么?从日常自动化到 AI 应用,软件如何自动执行任务?
运维·人工智能·自动化
AR-26710-7 小时前
Linux Day33——定时任务+lvm
linux·运维
润滑卫士7 小时前
液压系统工作粘度窗口等级选型:泵允许粘度范围与油温-粘度匹配计算
运维
每月一号准时摆烂10 小时前
服务器运维笔记(一)——使用shell编写实时查看BMC功率的脚本
运维·服务器·笔记
时间售卖铺11 小时前
Chrony与SSH实战:精准时间同步与安全远程登录
运维·安全·云原生·ssh
微订校园外卖系统11 小时前
校园代取快递系统选型:现成系统与定制开发的技术核对项
大数据·运维·人工智能·外卖系统·本地生活·微订
QXWZ_IA11 小时前
管道GIS测绘如何实现自动化成图:三种成图方式怎么选
运维·人工智能·安全·自动化
一条破秋裤11 小时前
【1112】Linux中断【整理】
linux·运维·服务器