引言:冷启动问题何时浮出水面
千万 QPS 的系统不是一夜之间建成的。从第一个用户到千万级并发,DNS 缓存会随着业务增长自然地被"养热"——LocalDNS 逐层建立缓存,应用内 DNS 缓存也趋于稳定。在这个渐进过程中,你几乎感受不到 DNS 的存在。
然而,当系统规模跨过某个临界点后,以下场景会让 DNS 冷启动问题突然暴露出来:
- 大规模滚动发布:千万 QPS 系统通常有数万个服务实例,全量发布意味着数万个进程重启,每个进程启动时都需要重新解析 DNS
- 故障恢复:机房级故障恢复后,所有实例的内存 DNS 缓存全部丢失
- 弹性扩容:大促前快速扩容数千台机器,新实例没有任何 DNS 缓存
- 灾备切换:主备切换后,备用集群的 DNS 缓存可能是完全冷的
这些场景在百万 QPS 时代可能只是监控图上的一个小毛刺,但在千万 QPS 时代,它们会变成足以影响系统稳定性的风险点。
本质差异:冷启动查询量的量级跃迁
理解这个问题,需要先建立一个简化模型。
假设一个后端服务需要访问 5 个下游依赖(数据库、缓存、消息队列等),每个依赖对应一个域名。在进程冷启动时,这 5 个域名都需要解析。
看起来只是 10 倍的差异?问题在于时间窗口。
百万 QPS 系统做滚动发布,2,000 个实例分 20 批,每批 100 个,间隔 30 秒。DNS 查询被均匀分散到 10 分钟内,峰值查询量约 500 次/批 × 2 批/分钟 = 1,000 QPS,企业内部 DNS 服务轻松承受。
千万 QPS 系统呢?20,000 个实例分 20 批,每批 1,000 个。即使同样的发布节奏,峰值查询量变成 5,000 次/批 × 2 批/分钟 = 10,000 QPS。如果发布窗口压缩(业务压力下很常见),这个数字还会成倍增长。
更棘手的是级联效应:当内部 DNS 服务响应变慢,应用启动超时,触发重试,DNS 压力进一步放大。在极端情况下,这种正反馈可能导致整个发布流程卡住。
预热策略:三层防线
DNS 预热的本质是将瞬时的查询压力分散到更长的时间窗口。根据缓存层级的不同,可以建立三层防线:
第一层:应用进程内缓存预热
大多数语言的 DNS 解析库都有内存缓存,但默认行为差异很大:
- Go 的
net.Resolver默认不缓存 - Java 的
InetAddress缓存 30 秒(可配置) - Node.js 的
dns模块默认不缓存
在千万 QPS 系统中,推荐在应用启动阶段主动预热关键域名。
关键点:预热应在流量接入之前完成,避免首批请求承受 DNS 解析延迟。
第二层:本地 DNS 代理缓存
在每台机器上部署轻量级 DNS 代理(如 dnsmasq、CoreDNS),可以实现:
- 进程间共享 DNS 缓存,避免同一台机器上多个进程重复查询
- 自定义 TTL 策略,延长热点域名的缓存时间
- 故障时提供 stale 缓存兜底
配置示例(dnsmasq):
最小缓存时间 300 秒,即使上游返回更短 TTL
min-cache-ttl=300
缓存条目数量
cache-size=10000
第三层:集中式 DNS 缓存集群
对于超大规模系统,可以在数据中心层面部署专用的 DNS 缓存集群:
这一层的关键价值是:在大规模冷启动前,可以通过预热脚本提前将热点域名加载到缓存集群。
预热时序控制:发布编排的艺术
仅有缓存还不够,发布时序的精细控制同样重要。以下是一个千万 QPS 系统发布前的 DNS 预热时序:
几个关键设计点:
- 预热先行:在第一个实例重启前 10 分钟完成缓存预热
- 批次间隔:确保上一批实例完全启动后再启动下一批,避免叠加
- 缓存续期:发布过程中持续刷新缓存,避免发布中途缓存过期
- 健康检查门控:只有通过健康检查的实例才允许接流量,避免 DNS 解析失败的实例被暴露
百万 vs 千万:预热策略对比
小结
DNS 预热从"可选优化"变成"必选流程",是千万 QPS 系统的一个典型特征。这不是简单的规模放大,而是系统运维模式的质变:
- 百万 QPS:DNS 是透明的基础设施,几乎不需要关注
- 千万 QPS:DNS 是需要精细管理的关键资源,预热是发布流程的标准环节
当你发现团队开始讨论"发布前要不要做 DNS 预热"时,说明系统可能正在跨越这个临界点。这是一个好信号——也是一个需要认真对待的信号。