我的服务用域名连下游,对方机器扩缩容换了 IP 之后,我的服务却死活还在连那台早已下线的旧机器、疯狂报连接失败,我排查了大半天才发现是 DNS 被永久缓存了的复盘

我的服务用域名连下游,下游扩缩容换了 IP、更新了 DNS 记录,我的服务却死活还连那台早已下线的旧 IP、疯狂报 connection refused/超时;可 dig 查那个域名明明早就是新 IP 了,而且重启我的服务就好、下次换 IP 又犯。深挖才懂是 DNS 缓存:我的 JVM 把启动时解析到的旧 IP 永久缓存了——networkaddress.cache.ttl 在装了 SecurityManager 时默认是 -1(永久缓存),JVM 第一次解析后就记一辈子、整个进程再不重新解析,所以下游换 IP 它压根不知道,重启才会清缓存重解析。这篇从 DNS 缓存与 TTL、解析链路各层缓存讲起,到设短 JVM DNS 缓存 TTL/连接池配 max-lifetime/运维切 IP 前调小记录 TTL 的正解、对比"程序连的IP"和"dig的IP"的排查路径、各层缓存速查、用域名通信被忽略的动态性问题、实用排查命令,以及那句最戳心的——几乎每个让你省心的缓存背后都藏着数据可能过期的代价,享受缓存的快但永远记得它可能是旧的。

我的服务用域名连下游,对方机器扩缩容换了 IP 之后,我的服务却死活还在连那台早已下线的旧机器、疯狂报连接失败,我排查了大半天才发现是 DNS 被永久缓存了的复盘

这是一个让我对 DNS 缓存刻骨铭心的故事。我有一个服务,通过域名去调用一个下游服务(比如 api.internal.com)。它一直跑得好好的。直到有一天,下游服务做了一次扩缩容/迁移,换了一批机器、IP 也变了(他们更新了 DNS 记录,把域名指向了新 IP)。从那一刻起,我的服务就出问题了:死活还在连那台早已下线的旧 IP,疯狂地报 连接被拒绝(connection refused)/ 连接超时;可我用 dig/nslookup 去查那个域名,解析出来明明已经是新 IP啊!更诡异的是,这个问题,重启一下我的服务,就好了;可下次下游再换 IP,又会复现。

我当时非常困惑:DNS 记录明明早就更新成新 IP 了,我的服务为什么还顽固地连旧 IP?它是从哪儿拿到这个旧 IP 的?我顺着这个现象深挖,才终于揭开真相,补上了我对 DNS 一个最致命的认知漏洞:问题的核心,是"DNS 缓存(DNS caching)"——更准确地说,是我的服务(一个 JVM 应用),把那个域名解析出来的旧 IP,给"永久地缓存"了下来。我一直想当然地以为,"程序每次用域名发请求,都会去重新解析一次、拿到最新的 IP";可真相是:解析 DNS 是有成本的(要走网络去问 DNS 服务器),所以各个层级,都会缓存解析结果以提速;而 JVM,有一个臭名昭著的默认行为:对于解析成功的 DNS 结果,它的默认缓存策略,在很多配置下,是"永久缓存(TTL = -1)"——也就是说,一旦它第一次api.internal.com 解析成了某个 IP,它就会把这个 IP 记一辈子,在整个进程生命周期内,都再也不会去重新解析!所以,当下游换了 IP,我的 JVM 压根不知道——它还死抱着启动时解析到的那个旧 IP 不放,自然就一直去连那台已经下线的旧机器,而重启服务之所以能"治好",正是因为重启会清空缓存、重新解析一次,拿到了新 IP。我这才痛彻地明白:"用域名,就一定能自动连到最新的机器"是一个危险的幻觉;DNS 解析结果,在从客户端到操作系统的每一层,都可能被缓存;而缓存的存在,就意味着"变更的延迟"——你的程序看到的 IP,可能是"过期的、陈旧的"。在一个机器会动态扩缩容、IP 频繁变化的云原生时代,理解并正确控制 DNS 缓存的 TTL,是保证服务能"跟得上"下游变化的、一道不起眼却致命的关卡

故障现场:JVM 永久缓存了 DNS 解析结果

我把这个"顽固连旧 IP"的现场,摊开给你看:

# ✗ 灾难: JVM 永久缓存 DNS 解析结果, 下游换 IP 后还连旧的

# 时间线:
#   T0: 我的服务启动, 第一次解析 api.internal.com → 1.1.1.1, 缓存!
#   T1: 下游扩缩容, 换机器, 更新 DNS: api.internal.com → 2.2.2.2
#   T2: dig api.internal.com  → 2.2.2.2 (DNS 记录确实更新了)
#   T3: 但我的 JVM 还在用缓存里的 1.1.1.1 → 连 1.1.1.1(已下线) → 失败!
#   T4: 重启我的服务 → 重新解析 → 拿到 2.2.2.2 → 恢复正常。

# 根源: JVM 的 DNS 缓存策略(InetAddress)
#   - networkaddress.cache.ttl: 解析"成功"结果的缓存秒数。
#     * 默认值在装了 SecurityManager 时是 -1 = 永久缓存(整个进程不再重解析)!
#     * 即使没装, 不同版本/配置下默认缓存时间也可能很长。
#   - networkaddress.cache.negative.ttl: 解析"失败"结果的缓存秒数(默认 10)。

# 为什么会有缓存?
#   - DNS 解析要走网络问 DNS 服务器, 有延迟和开销。
#   - 缓存能避免每次请求都解析 → 提速。但代价是: 看不到"变更"。

# 哪些层级都会缓存 DNS?
#   - 应用层(JVM InetAddress / 各语言/库的解析缓存)。
#   - 操作系统(nscd / systemd-resolved 等本地 DNS 缓存)。
#   - DNS 服务器自身的 TTL(权威记录设的过期时间)。
#   → 任何一层缓存过久, 都会让你看到"过期的 IP"。

# 现象: 下游换 IP 后, 我的服务持续连旧 IP, connection refused/timeout;
#       dig 查到的是新 IP, 但重启前我的服务就是不认。

# 根因: JVM(及各层)缓存 DNS 解析结果, 默认 TTL 过长甚至永久,
#   下游 IP 变更后, 缓存未过期 → 持续连旧 IP。

看着这条时间线,我才算彻底想明白了这场"顽固连旧 IP"的根源。问题的核心,是 JVM 的 DNS 缓存策略:networkaddress.cache.ttl 控制"解析成功"结果的缓存秒数,而它的默认值,在装了 SecurityManager 时是 -1(永久缓存,整个进程都不再重新解析);即使没装,不同版本/配置下默认缓存时间也可能很长于是时间线就清楚了:我的服务启动时把域名解析成了旧 IP 1.1.1.1 并永久缓存;下游换成 2.2.2.2 后,dig 查到的是新 IP,但我的 JVM 还死抱着缓存里的 1.1.1.1,持续去连那台已下线的旧机器、失败;重启服务会清空缓存、重新解析,所以能"治好"。为什么会有缓存?因为 DNS 解析要走网络问 DNS 服务器、有开销,缓存能提速;但代价是看不到"变更"而且,缓存 DNS 的层级,不止 JVM:应用层(各语言/库)、操作系统(nscd/systemd-resolved)、DNS 服务器自身的 TTL——任何一层缓存过久,都会让你看到"过期的 IP"归根结底:JVM(及各层)缓存 DNS 解析结果,默认 TTL 过长甚至永久,下游 IP 变更后缓存没过期,就会持续连旧 IP——这,就是根源。

第一件事:搞懂 DNS 缓存与 TTL

定位到根源,我必须把"DNS 缓存到底是怎么回事"从根上彻底搞清楚:

DNS 缓存: 解析结果被各层缓存以提速, 但代价是"变更有延迟"

# 域名 → IP 的解析链路:
#   程序 → 应用层缓存 → 操作系统缓存 → 本地DNS → 递归DNS → 权威DNS
#   任何一层有缓存命中, 就直接返回, 不再往下问。

# TTL(Time To Live): 缓存能存多久
#   - 权威 DNS 给每条记录设了 TTL(如 60s/300s/3600s)。
#   - 理论上各层缓存应遵守这个 TTL, 过期就重新解析。
#   - 但: 客户端/应用层可能"自作主张"用自己的缓存策略, 无视记录 TTL!

# JVM 的坑(本文重点):
#   - InetAddress 的 networkaddress.cache.ttl:
#     * 装 SecurityManager: 默认 -1 = 永久缓存(最坑!)。
#     * 不装: 默认实现会缓存约 30 秒(版本相关), 也未必读记录 TTL。
#   - 即程序的缓存, 不一定听 DNS 记录 TTL 的话!

# "变更延迟"的本质:
#   - 你改了 DNS 记录, 不等于全世界立刻看到新 IP。
#   - 必须等"各层缓存都过期", 新 IP 才完全生效。
#   - 所以切 IP 前要把 TTL 提前调小, 切完观察一段时间。

# 怎么验证是哪一层在缓存?
#   - dig 看权威/递归返回的 IP 和剩余 TTL。
#   - 对比"你程序实际连的 IP"(抓包/日志) vs "dig 的 IP"。
#   - 不一致 → 是你程序/OS 这层缓存了旧的。

# 关键认知: 用域名 ≠ 总连到最新 IP; 缓存让你可能看到过期 IP。
#   - 动态扩缩容时代, 要主动控制 DNS 缓存 TTL, 让程序跟得上变更。

# 核心: DNS 各层都缓存解析结果(按 TTL), 但客户端(尤其 JVM)可能用
#   自己的过长/永久缓存策略, 无视记录 TTL, 导致 IP 变更后还连旧的。

原理终于清晰了。域名到 IP 的解析链路是:程序 → 应用层缓存 → 操作系统缓存 → 本地 DNS → 递归 DNS → 权威 DNS,任何一层缓存命中,就直接返回、不再往下问TTL 控制缓存能存多久:权威 DNS 给每条记录设了 TTL,理论上各层应遵守它、过期就重新解析;但客户端/应用层可能"自作主张"用自己的缓存策略,无视记录 TTL!JVM 的坑正在于此:networkaddress.cache.ttlSecurityManager 时默认 -1(永久缓存,最坑),不装时默认也缓存约 30 秒、且未必读记录 TTL——即程序的缓存,不一定听 DNS 记录 TTL 的话这就引出了"变更延迟"的本质:你改了 DNS 记录,不等于全世界立刻看到新 IP;必须等各层缓存都过期,新 IP 才完全生效;所以切 IP 前要提前把 TTL 调小怎么验证是哪一层在缓存?dig 看权威/递归返回的 IP 和剩余 TTL,对比"你程序实际连的 IP"(抓包/日志)——不一致,就是你程序/OS 这层缓存了旧的由此,我刻下一个关键认知:用域名 ≠ 总连到最新 IP;缓存让你可能看到过期 IP;动态扩缩容时代,要主动控制 DNS 缓存 TTL,让程序跟得上变更。归根结底:DNS 各层都按 TTL 缓存解析结果,但客户端(尤其 JVM)可能用自己过长/永久的缓存策略、无视记录 TTL,导致 IP 变更后还连旧的。

第二件事:正解——设置合理的 DNS 缓存 TTL

搞懂了原理,正解就清晰了:把 JVM(及各层)那个过长甚至永久的 DNS 缓存 TTL,改成一个合理的、较短的值,让程序能定期重新解析、跟上 IP 变更

// ✓ 正解一: 显式设置 JVM 的 DNS 缓存 TTL(改成较短, 如 30~60 秒)
// 方式A: 在代码最早期(main 入口)设置
java.security.Security.setProperty("networkaddress.cache.ttl", "30");      // 成功缓存 30 秒
java.security.Security.setProperty("networkaddress.cache.negative.ttl", "5"); // 失败缓存 5 秒

// 方式B: 在 $JAVA_HOME/jre/lib/security/java.security 文件里配置
//   networkaddress.cache.ttl=30
//   networkaddress.cache.negative.ttl=5

// 方式C: 启动参数(JVM 系统属性, 注意优先级与上面不同, 视版本)
//   -Dsun.net.inetaddr.ttl=30

// ✓ 正解二: 不要在程序启动时"解析一次就长期持有 IP"
//   - 别把 InetAddress / 解析出的 IP 缓存成全局变量长期复用。
//   - 让 HTTP 客户端在"每次新建连接时"重新解析域名(多数客户端默认如此, 但要确认)。

// ✓ 正解三: 连接池要能感知 DNS 变更
//   - 有些连接池会长期持有到旧 IP 的连接 → 配置连接最大存活时间, 强制定期重建。
//   - 如 OkHttp/HikariCP 等设置 connection max-lifetime, 让旧连接定期淘汰。

// ✓ 正解四: 切 IP 前, 先把权威 DNS 记录的 TTL 调小(运维侧)
//   - 切换前几小时把 TTL 从 3600 调到 60, 切完观察, 再调回。

// 核心: 把 JVM DNS 缓存 TTL 设为合理的较短值(如30~60s), 别长期持有解析的 IP,
//   连接池配最大存活时间定期重建, 运维侧切 IP 前先调小记录 TTL。

修复的方向,是"让缓存别那么顽固"正解一,显式设置 JVM 的 DNS 缓存 TTL:把 networkaddress.cache.ttl 从默认的永久/过长,改成一个合理的较短值(如 30~60 秒)——可以在 main 入口用 Security.setProperty 设置,或在 java.security 文件里配置;同时把失败缓存 negative.ttl 也设短一点(默认才 10 秒,一般够)。正解二,别在启动时"解析一次就长期持有 IP":不要把解析出的 IP 缓存成全局变量长期复用,让 HTTP 客户端在每次新建连接时重新解析域名正解三,连接池要能感知 DNS 变更:有些连接池会长期持有到旧 IP 的连接,要配置连接最大存活时间(max-lifetime),让旧连接定期被淘汰、重建正解四(运维侧),切 IP 前先把权威 DNS 记录的 TTL 调小:切换前几小时把 TTL 从 3600 调到 60,切完观察、再调回——让变更能更快地在全网生效归根结底:把 JVM DNS 缓存 TTL 设为合理的较短值(如 30~60s)、别长期持有解析的 IP、连接池配最大存活时间定期重建、运维侧切 IP 前先调小记录 TTL。

第三件事:DNS 问题的排查路径

这次踩坑也让我沉淀了一套排查"连不上/连错机器"时,判断"是不是 DNS 缓存问题"的方法:

DNS 问题排查: 对比"程序连的 IP" 和 "DNS 实际解析的 IP"

# 1. 确认 DNS 记录现在解析成什么(权威/递归)
dig api.internal.com          # 看 ANSWER 段的 IP 和 TTL
dig +trace api.internal.com   # 看完整解析链路(排查哪一级返回旧的)
nslookup api.internal.com

# 2. 确认"你的程序实际在连哪个 IP"
#    - 抓包: tcpdump -i any host api.internal.com 或 port 443
#    - 看连接: ss -tnp | grep <你的进程>  → 看 peer IP
#    - 应用日志里打印解析到的 IP

# 3. 对比第1步和第2步:
#    - 程序连的 IP == dig 的 IP  → 不是 DNS 缓存问题, 查别的。
#    - 程序连的 IP != dig 的 IP(连的是旧 IP) → 你程序/OS 缓存了旧的!

# 4. 缩小到哪一层缓存:
#    - 重启程序就好 → 应用层(JVM)缓存(本文)。
#    - 重启程序没好, 但清 OS 缓存(systemd-resolve --flush-caches)好了 → OS 层。
#    - 都没好, dig 自己都返回旧 IP → 上游 DNS 还没更新/TTL 没到。

# 5. 验证 JVM 的实际 TTL 设置:
#    - 看 java.security 配置 / 启动参数 / 是否装了 SecurityManager。

# 经验:
#   - "重启就好、过段时间又犯" 是 DNS 缓存问题的典型特征。
#   - 域名连下游 + 下游会换 IP 的场景, 都要警惕 DNS 缓存。

# 核心: 排查时对比"程序连的IP"和"dig解析的IP", 不一致就是缓存了旧的;
#   按"重启/清OS缓存/dig"逐层定位是哪层缓存, 再针对性设 TTL。

这套排查路径,让我面对"连不上/连错机器"时,能快速判断是不是 DNS 缓存的锅核心方法,是对比两个 IP:第一,用 dig/nslookup 看"DNS 现在实际解析成什么 IP"(dig +trace 还能看完整解析链路);第二,用 tcpdump 抓包或 ss -tnp 看"你的程序实际在连哪个 IP";第三,对比二者——如果程序连的 IP 和 dig 的不一致(连的是旧 IP),那就是你程序/OS 缓存了旧的接着缩小到哪一层:重启程序就好 → 应用层(JVM)缓存(本文);重启没好但清 OS 缓存(systemd-resolve --flush-caches)好了 → OS 层;都没好、连 dig 自己都返回旧 IP → 上游 DNS 还没更新我也总结了经验:"重启就好、过段时间又犯",是 DNS 缓存问题的典型特征;域名连下游 + 下游会换 IP 的场景,都要警惕 DNS 缓存归根结底:排查时对比"程序连的 IP"和"dig 解析的 IP",不一致就是缓存了旧的;按"重启/清 OS 缓存/dig"逐层定位是哪层缓存,再针对性地设 TTL。

下面这张图,是这次"DNS 缓存"事故的成因与解法:

第四件事:各层 DNS 缓存的速查对照

这次踩坑后,我把"DNS 解析链路上各层的缓存",整理成一张速查表,排查时能快速锁定是哪一层。

缓存层 谁在缓存 怎么控制/清除
应用层(JVM) InetAddress 缓存 networkaddress.cache.ttl 设短; 重启清空
应用层(其他语言/库) 各 HTTP 客户端/解析库 看库的 DNS 缓存配置, 多数较短或不缓存
操作系统 nscd / systemd-resolved systemd-resolve --flush-caches
本地/递归 DNS 运营商/内网 DNS 服务器 遵守记录 TTL, 等过期或刷新
权威 DNS 域名记录本身的 TTL 改记录 + 等 TTL 过期
连接池 已建立到旧IP的长连接 设 max-lifetime 定期重建

这张表,让"DNS 缓存在哪一层"变得一目了然。排查时,从离程序最近的层往外查:应用层(JVM 的 InetAddress 缓存,本文的元凶)→ 操作系统(nscd/systemd-resolved)→ 本地/递归 DNS → 权威 DNS 记录本身;此外还有一个容易被忽略的"隐形缓存"——连接池:就算 DNS 重新解析了,连接池里那些已经建立到旧 IP 的长连接,如果不淘汰,照样在连旧机器每一层都有对应的控制/清除手段:JVM 设 cache.ttl、OS 用 flush-caches、递归 DNS 等 TTL 过期、连接池设 max-lifetime它给我的最大启发是:"DNS 解析"这个看似简单的"域名变 IP"的动作,背后其实是一条横跨应用、系统、网络的多级缓存链路;任何一级"缓存得太久",都会让"变更"卡在半路。理解了这条完整的链路,你才能在 IP 变更出问题时,精准地定位到"是哪一级缓存在作祟",而不是无头苍蝇般乱撞、最后只会"重启大法"

第五件事:用域名通信时,那些被忽略的"动态性"问题

顺着这次的教训,我把"用域名做服务间通信"时,其他容易被忽略的、和"动态变化"相关的问题,系统梳理了一遍。

问题 表现 应对
DNS 缓存过久 下游换IP后还连旧的(本文) 设短 TTL + 连接池定期重建
只用了解析的第一个 IP 域名多 IP 时没负载均衡/容错 轮询/随机用所有解析出的 IP
负缓存太久 下游短暂故障后, 恢复了还连不上 调小 negative.ttl
长连接不感知后端变化 下游缩容了, 连接还在打到该走的机器 连接 max-lifetime + 健康检查
DNS 解析阻塞主流程 DNS 慢时拖慢整个请求 设解析超时, 或异步/缓存解析
直接硬编码 IP IP 变了就全线崩, 无法迁移 用域名/服务发现, 别硬编码 IP

这张表,让我对"用域名通信"这件看似简单的事,有了远超"填个域名就行"的认识。它们的共同主题,是"动态性":在云原生时代,下游的机器、IP、状态,都是动态变化;而我们的客户端,如果抱着"它一直不变"的静态假设,就会在各种"变化"面前出问题。无论是 DNS 缓存过久(本文)、只用第一个解析 IP 而没负载均衡负缓存太久导致下游恢复了还连不上长连接不感知后端缩容、还是 DNS 解析阻塞主流程——它们都在提醒我同一件事:客户端不能假设下游是"静态"的,必须设计成能持续地、动态地感知并适应下游的变化而最该警惕的反面,是那个"图省事直接硬编码 IP"的做法:它一旦 IP 变了就全线崩、无法迁移,是把"静态假设"做到了极致的危险写法。它给我的最大启发是:写分布式系统的客户端,要有一种"拥抱变化"的心态:不要假设你依赖的任何东西(IP、机器、可用性)是一成不变的,而要主动去设计"当它们变化时,我如何能及时、平滑地跟上"。在一个万物皆动态的时代,"对变化的适应能力",本身就是系统健壮性的一部分

第六件事:用域名连下游时,我现在会怎么决策

现在,每当我准备用域名去连一个下游服务,脑子里都会过一遍这张决策图——核心就一问:下游的 IP 会变吗?我跟得上吗?

这张图的灵魂,是那个必问的问题:下游的 IP 会变吗?我跟得上吗?如果下游 IP 基本不变,风险低(但仍建议设合理 TTL);如果会动态变化(扩缩容/迁移/漂移),就必须保证能跟上变更:设 JVM/客户端的 DNS 缓存 TTL 为较短值连接池配 max-lifetime 定期淘汰旧连接;用了长连接的,还要加健康检查、剔除连到旧/坏机器的连接;用短连接的,确保每次新连接都重新解析更进一步:别硬编码 IP;复杂场景上服务发现/SLB(让一个稳定的入口,去屏蔽后端的动态变化)。最后,也是我以前最缺的一步:做演练——主动模拟一次"下游换 IP",验证我的服务确实能自动跟上,而不是等真出事了才发现跟不上。

我立下的几条规矩

这场"顽固连旧 IP"的事故,换来了我做服务间通信时,刻进骨子里的几条铁律:

  1. JVM 默认 DNS 缓存可能是永久的,必须显式设短。networkaddress.cache.ttl 设成 30~60 秒,别让它死抱启动时的 IP。
  2. 用域名 ≠ 自动连到最新机器。解析结果在应用/OS/DNS 各层都会被缓存,缓存意味着变更有延迟。
  3. 连接池要配 max-lifetime。否则就算重新解析了,池里到旧 IP 的长连接还在打到下线的机器。
  4. 切 IP 前先把 DNS 记录 TTL 调小。运维侧提前调小、切完观察再调回,让变更更快全网生效。
  5. 排查时对比"程序连的 IP"和"dig 的 IP"。不一致就是缓存了旧的;按重启/清 OS 缓存/dig 逐层定位。
  6. 别硬编码 IP。用域名/服务发现,把"动态变化"屏蔽在稳定的抽象之后。
  7. 对下游保持"拥抱变化"的心态。不假设 IP/机器/可用性不变,主动设计成能及时跟上变更。

附:验证与排查 DNS 缓存的实用命令

纸上得来终觉浅。下面这组命令,能帮你亲手验证 DNS 缓存问题、并定位到是哪一层,排查时照着敲一遍即可:

# ===== 1. 看 DNS 现在到底解析成什么 =====
dig api.internal.com +short          # 只看 IP, 最简洁
dig api.internal.com                 # 看 ANSWER 段的 IP 和剩余 TTL
dig +trace api.internal.com          # 看完整解析链路(定位哪一级返回旧的)

# ===== 2. 看你的程序实际连的是哪个 IP =====
# 看进程当前的 TCP 连接和对端 IP:
ss -tnp | grep <你的进程名或pid>      # ESTAB 那行的 peer 地址就是实连 IP
# 抓包确认:
sudo tcpdump -i any -n host api.internal.com    # 看实际握手到哪个 IP

# ===== 3. 对比 =====
#   dig 的 IP == ss/tcpdump 的 IP  → 不是缓存问题
#   dig 的 IP != 程序连的 IP(连旧的) → 程序/OS 缓存了旧 IP

# ===== 4. 逐层定位/清除缓存 =====
# OS 层(systemd-resolved):
resolvectl flush-caches              # 清 OS DNS 缓存
resolvectl statistics                # 看缓存命中情况
# nscd(若用):
sudo systemctl restart nscd

# ===== 5. 验证 JVM 的 DNS 缓存设置 =====
# 看 java.security 里的配置:
grep networkaddress $JAVA_HOME/conf/security/java.security
# 在程序里临时打印验证(最早期设置后):
#   System.out.println(Security.getProperty("networkaddress.cache.ttl"));

# ===== 6. 验证 TTL 改动是否生效(最直接) =====
#   改短 TTL 重启服务后, 模拟下游换 IP(改 hosts 或测试域名),
#   等 TTL 秒数后看程序是否自动连到新 IP → 自动跟上 = 生效。

# 核心: dig 看"该连的IP", ss/tcpdump 看"实连的IP", 二者对比锁定缓存问题;
#   resolvectl flush / 重启程序 逐层定位是哪层缓存, 改短 TTL 后演练验证。

这组命令,是我现在排查"连不上/连错机器"的标准动作它的核心逻辑,就是前面反复强调的那句:对比"DNS 该解析成的 IP"(dig)和"程序实际连的 IP"(ss/tcpdump)——这两个 IP 一不一致,直接决定了"是不是 DNS 缓存的锅"。一旦确认是缓存问题,再逐层定位:清 OS 缓存(resolvectl flush-caches)、重启程序、看 JVM 的 java.security 配置,一层层缩小范围,直到找到那个"缓存得太久"的元凶。而最有价值的,是最后那个"演练验证":改短 TTL、重启服务后,主动模拟一次下游换 IP(改 hosts 或用测试域名),看程序是否能在 TTL 秒数后自动连到新 IP——只有亲眼看到它"自动跟上了",才算真正确认问题被解决了。这,正是我想用这组命令,留给每一个后端工程师的最后一课:对于 DNS 缓存这种"看不见、摸不着、平时还总是对"的东西,不要靠脑补和猜测,而要digtcpdump 这些工具,把它"缓存了什么、连了哪里"实实在在地看出来能把一个"玄学问题",变成一个"可观测、可验证的工程问题",你就已经解决它一大半了

写在最后

回头看,这场由"DNS 被永久缓存"引发的、顽固连接旧 IP 的事故,真正教给我的,是一个比"设置 TTL"本身更深的道理:几乎每一个让我们"省心"的缓存,背后都藏着一个"数据可能过期"的代价;而缓存最危险的地方,恰恰在于它"大部分时候都对",从而让你彻底忘记了它的存在,也忘记了它"会过期"这件事DNS 缓存,默默地为我加速了无数次请求,我享受着它的便利,却从未意识到它的存在;直到下游换了 IP——那个"缓存与现实出现分歧"的时刻——它才以一种让我措手不及的方式,暴露了自己。这正是"缓存一致性"这个计算机科学的经典难题,在 DNS 上的一次具体上演:缓存,是用"可能过期的旧数据",去换取"不必每次都获取的性能";而这笔交易的风险,就是当源头变化时,你的缓存,还活在过去。所以,面对系统里的任何一层缓存(DNS 缓存、HTTP 缓存、应用缓存、CDN……),都要时刻保持一份清醒:问自己——"这份缓存,多久会过期?当源头变了,它能多快感知到?如果它没及时过期,会发生什么?"真正驾驭缓存的人,不仅会用它来加速,更始终对它"可能陈旧"这一面,保持着警惕和掌控享受缓存的快,但永远记得它可能是"旧"的——这,是我用一次"连接旧 IP"的事故,换来的、关于 DNS、也关于一切缓存的、最朴素也最深刻的领悟。如果这篇复盘,能让你在下一次依赖某个缓存时,多问一句"它会过期吗,多久",那我对着那个顽固的旧 IP 熬的这大半天,就值了。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

我的转账接口在高峰期偶尔会报 Deadlock found、事务莫名其妙被回滚,我对着这个时有时无的数据库死锁排查了大半天才搞懂加锁顺序的复盘

2026-6-2 2:46:48

技术教程

我的服务某天凌晨突然全线崩溃、各种写入都报错,登上去一看磁盘被日志撑到了 100%,我对着这个被日志活活塞满的硬盘排查了大半天的复盘

2026-6-2 2:59:10

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索