
昨天线上遇到一个非常经典的DNS踩坑问题:
我在Linux服务器修改了 /etc/resolv.conf 更换DNS nameserver,但是已经在运行的Go程序,新DNS配置完全不生效,只有手动重启进程之后,新DNS才会正常使用。
我一开始理所当然地以为:系统resolv.conf是全局动态配置,改完文件,所有正在运行的程序会自动感知并使用新DNS。
实际现象狠狠打脸,顺着这个问题深挖,发现Go、Java在这块的行为还不一样,很多后端开发都会有这个认知误区,于是整理成这篇笔记。
重要区分两个概念(绝大多数人混淆在这里) 1. DNS服务器地址(nameserver):去哪里发起DNS查询(
resolv.conf里的nameserver配置) 2. 域名解析结果缓存:域名解析得到的IP,程序在内存缓存多久
这两个完全独立,不要混在一起。本次问题是nameserver不自动刷新,不是域名IP缓存。
Linux下修改 /etc/resolv.conf 的nameserver:
已经启动运行的进程,默认不会自动重新加载resolv.conf,不会自动切换到新DNS服务器;必须重启进程,进程启动瞬间才会读取这份配置。
resolv.conf 不是实时监听的全局热配置,它是进程启动时一次性读取的配置文件。
Go分两种解析模式,取决于编译参数CGO_ENABLED。
/etc/resolv.conf,存入内存// 强制重新加载 resolv.conf,刷新nameserver
net.DefaultResolver.DialContext = nil
net.DefaultResolver.LoadResolvConf()可以封装成一个管理接口,例如/admin/reload-dns,修改DNS配置后调用接口刷新,无需重启Go应用。查看当前Go编译使用的CGO配置go env CGO_ENABLED
OpenJDK在Linux上默认调用glibc getaddrinfo,和Go开启CGO的行为一致。
res_init(),属于hack方案,生产环境不推荐。networkaddress.cache.ttl:解析成功的IP缓存时间。无SecurityManager默认30s;开启SecurityManager会变成永久缓存(-1)networkaddress.cache.negative.ttl:域名解析失败的缓存时间,默认10s⚠️ 重点提醒:关闭JVM DNS缓存(设置为0)只能让每次查询都去DNS服务器请求,不能刷新nameserver。 nameserver地址如果没更新,就算关闭缓存,请求依旧发往旧DNS服务器。
启动示例:
java -Dnetworkaddress.cache.ttl=0 -Dnetworkaddress.cache.negative.ttl=0 -jar app.jar项目 | Go(CGO=0 netgo) | Go(CGO=1) | Java OpenJDK(Linux默认) |
|---|---|---|---|
修改系统nameserver,运行进程自动生效 | ❌ 不会自动监听 | ❌ 不会自动监听 | ❌ 不会自动监听 |
是否支持代码动态重载nameserver | ✅ 原生 | ❌ 依赖glibc,无原生方案 | ❌ 无官方API,只能JNA hack |
是否自带域名IP内存缓存 | ❌ net包默认不缓存解析结果 | ❌ 由glibc控制 | ✅ JVM自带独立DNS内存缓存 |
云服务器很多默认启用systemd-resolved / NetworkManager。
你手动修改resolv.conf,过一段时间系统自动覆盖回旧配置。
解决方案:不要直接编辑resolv.conf,修改网卡/network配置。
服务器上跑了systemd-resolved、dnsmasq、nscd。
就算应用读到新nameserver,域名查询可能被本地缓存拦截,拿到旧解析结果。
宿主机修改DNS不会影响容器内部。
容器内resolv.conf是容器创建时注入的配置。只重启容器内应用进程没用,必须重建Pod/容器,新容器才会加载新DNS配置。
修改DNS配置 → 重启业务进程。一次性刷新nameserver + 应用内部DNS缓存,稳定无副作用。
在Go服务增加管理接口,调用LoadResolvConf(),修改resolv.conf后调用接口刷新DNS配置,实现无重启切换DNS服务器。
Java不推荐这种方案,实现复杂、稳定性差。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。