DNS 域名解析
#网络 · #DNS · #域名 · #CDN · #HTTPDNS · #CoreDNS
DNS(Domain Name System)是互联网的"电话簿"——将人类可读的域名转换为机器可路由的 IP 地址。从根域名服务器到本地缓存,从传统 UDP 查询到 HTTPDNS,DNS 的性能和安全性直接决定了用户体验。
DNS 解析流程
mermaid
sequenceDiagram
participant Browser as 浏览器
participant Local as 本地 DNS 缓存
participant Resolver as 递归解析器
participant Root as 根域名服务器
participant TLD as .com 顶级域
participant Auth as 权威 DNS
Browser->>Local: www.example.com 的 IP?
Note over Local: hosts 文件 → 系统缓存 → 路由器缓存
Local-->>Browser: 未命中
Browser->>Resolver: 递归查询 www.example.com
Resolver->>Root: www.example.com?
Root-->>Resolver: .com 的 NS 地址
Resolver->>TLD: www.example.com?
TLD-->>Resolver: example.com 的 NS 地址
Resolver->>Auth: www.example.com?
Auth-->>Resolver: 1.2.3.4 (TTL=300)
Resolver-->>Browser: 1.2.3.4
Note over Browser: 缓存 TTL 秒查询类型
| 类型 | 说明 | 示例 |
|---|---|---|
| 递归查询 | 客户端要求 DNS 服务器必须返回最终结果 | 客户端 → 本地 DNS |
| 迭代查询 | DNS 服务器返回"我不知道,但你可以问 xxx" | 本地 DNS → 根/顶级域 |
DNS 记录类型
| 类型 | 含义 | 示例 | 用途 |
|---|---|---|---|
| A | IPv4 地址 | www IN A 1.2.3.4 | 域名 → IPv4 |
| AAAA | IPv6 地址 | www IN AAAA 2001:db8::1 | 域名 → IPv6 |
| CNAME | 别名/规范名 | www IN CNAME example.com | 域名别名 |
| MX | 邮件服务器 | @ IN MX 10 mail.example.com | 邮件路由 |
| NS | 域名服务器 | @ IN NS ns1.example.com | 指定权威 DNS |
| TXT | 文本记录 | @ IN TXT "v=spf1 ..." | SPF/DKIM 验证 |
| SRV | 服务定位 | _http._tcp IN SRV 0 5 80 www | 服务发现(如 K8s) |
| PTR | 反向解析 | 4.3.2.1.in-addr.arpa IN PTR www | IP → 域名 |
| SOA | 授权起始 | 包含主NS/管理员邮箱/序列号 | 区域传输控制 |
DNS 解析性能
延迟来源
一次完整 DNS 解析(冷启动):
根服务器: ~30-100ms(通常有缓存)
顶级域: ~20-50ms
权威DNS: ~10-30ms
网络RTT: ~5-50ms
─────────────────────
总计: ~50-200ms缓存策略
| 层级 | 缓存位置 | TTL | 命中率 |
|---|---|---|---|
| 浏览器 | Chrome DNS 缓存 | 1分钟 | 高 |
| 操作系统 | /etc/hosts → systemd-resolved | TTL 决定 | 很高 |
| 路由器 | 家用路由器 DNS 缓存 | TTL | 中 |
| ISP 递归解析器 | 运营商 DNS (114.114.114.114) | TTL | 极高 |
| CDN | DNS 调度层缓存 | 较短 (30-120s) | 高 |
DNS 安全问题
1. DNS 劫持
正常:www.example.com → 1.2.3.4(真实 IP)
劫持:www.example.com → 5.6.7.8(恶意 IP)| 劫持方式 | 说明 |
|---|---|
| 本地 hosts 篡改 | 恶意软件修改 /etc/hosts |
| 路由器劫持 | 修改路由器 DNS 设置 |
| ISP 劫持 | 运营商植入广告/统计页面 |
| DNS 缓存投毒 | 伪造 DNS 响应污染缓存 |
2. DNS 污染(GFW)
GFW 检测到敏感域名 → 向 DNS 请求方发送伪造响应(虚假IP)
→ 真正的响应到达时,客户端的查询端口已收到"回复"而丢弃3. DNSSEC
通过数字签名验证 DNS 响应真实性:
DNS 记录 + RRSIG(数字签名)
↓ 验证
DNSKEY(区域公钥)← DS(父区域哈希)← 信任锚(根)问题:部署率低(<30%),增加响应体积,不能防 DDoS。
DoT / DoH / DoQ
| 协议 | 传输 | 端口 | 隐私 | 防篡改 | 性能 |
|---|---|---|---|---|---|
| 传统 DNS | UDP/TCP 明文 | 53 | ❌ | ❌ | 最快 |
| DoT (DNS over TLS) | TCP + TLS | 853 | ✅ | ✅ | 中 |
| DoH (DNS over HTTPS) | HTTP/2 + TLS | 443 | ✅✅ | ✅ | 中 |
| DoQ (DNS over QUIC) | QUIC | 853 | ✅ | ✅ | 快 |
| DNSCrypt | 自定义加密协议 | 自定义 | ✅ | ✅ | 中 |
DoH 示例
bash
# curl 使用 DoH 解析
curl --doh-url https://1.1.1.1/dns-query https://example.com
# 主流 DoH 服务商
# Cloudflare: https://1.1.1.1/dns-query
# Google: https://dns.google/dns-query
# 阿里: https://dns.alidns.com/dns-query
# DNSPod: https://doh.pub/dns-queryHTTPDNS
传统 DNS 的问题
- 域名劫持:运营商可能篡改
- 调度不准确:LDNS IP ≠ 用户 IP,CDN 调度偏差
- 解析延迟:递归链路长
- TTL 生效慢:中间缓存不遵守 TTL
HTTPDNS 原理
客户端 → HTTP API → HTTPDNS 服务端 → 权威 DNS
↑
看到真实客户端 IP
精准调度
HTTPS 防劫持GET /d?dn=www.example.com&ip=client_ip HTTP/1.1
Host: httpdns-api.aliyuncs.com
响应:
{
"host": "www.example.com",
"ips": ["1.2.3.4", "5.6.7.8"],
"ttl": 120,
"origin_ttl": 300
}接入方式
java
// Android OkHttp + HTTPDNS
OkHttpClient client = new OkHttpClient.Builder()
.dns(new Dns() {
@Override
public List<InetAddress> lookup(String hostname) {
// 优先 HTTPDNS
List<String> ips = HttpDnsService.getIpsByHost(hostname);
if (ips != null && !ips.isEmpty()) {
return ips.stream().map(ip -> {
try { return InetAddress.getByName(ip); }
catch (Exception e) { return null; }
}).collect(Collectors.toList());
}
// 降级到系统 DNS
return Dns.SYSTEM.lookup(hostname);
}
})
.build();CDN 完整工作原理
CDN DNS 调度
用户 → 本地 DNS → CDN DNS 调度系统
↓
根据 LDNS IP 判断用户位置
↓
返回最近的边缘节点 IP
↓
用户连接边缘节点智能调度策略
| 策略 | 说明 |
|---|---|
| GeoDNS | 根据 LDNS IP 地理库返回就近 IP |
| IP Anycast | 多个节点使用同一 IP,BGP 路由到最近 |
| 基于 RTT | 探测各节点延迟,返回最快的 |
| 基于负载 | 返回负载最低的节点 |
| 分线路 | 电信/联通/移动返回不同 IP |
CDN 多级缓存架构
CDN 并非只有一层缓存,而是分层回源以减少源站压力:
mermaid
flowchart TB
User["用户"] --> Edge["L1: 边缘节点<br/>离用户最近"]
Edge -->|"缓存未命中"| Parent["L2: 区域父节点<br/>覆盖一片地区"]
Parent -->|"缓存未命中"| Origin["源站<br/>真正的服务器"]
Edge -->|"命中"| User
Parent -->|"命中"| Edge| 层级 | 节点数 | 覆盖范围 | 缓存命中率 |
|---|---|---|---|
| L1 边缘节点 | 数千 | 城市/省份 | 90-95% |
| L2 父节点 | 数十 | 全国/大区 | 95-99% |
| 源站 | 1-3 | 全球 | — |
回源机制
text
回源路径:
用户请求 → 边缘节点 (miss) → 父节点 (miss) → 源站
控制回源的策略:
1. 缓存 TTL (Time To Live): 根据文件类型设置
- 图片/字体: 7-30 天
- CSS/JS: 1-7 天(版本号控制更新)
- HTML: 0-5 分钟(动态页面不缓存或短 TTL)
2. 304 Not Modified: 协商缓存
边缘节点携带 If-None-Match/If-Modified-Since 向源站验证
源站返回 304 → 继续用缓存,不传输 body
3. 缓存锁定 (Cache Lock):
同一文件同时只有 1 个请求回源,其他排队等待
→ 防止"缓存击穿"导致大量并发回源回源击穿防护
text
问题场景: 一个热文件 cache 到期 → 边缘节点删除 → 数千用户同时请求
→ 数千请求同时回源 → 源站被打垮
Nginx proxy_cache_lock on: 同一资源同时只有 1 个请求回源nginx
# Nginx 缓存锁配置
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:256m;
proxy_cache my_cache;
proxy_cache_lock on; # 同一 key 只允许一个请求回源
proxy_cache_lock_timeout 5s; # 等 5s 还没拿到也放行
proxy_cache_valid 200 1h; # 200 缓存 1 小时CDN 缓存刷新与预热
| 操作 | 用途 | 生效时间 |
|---|---|---|
| 缓存刷新 (Purge) | 主动删除边缘节点旧缓存 | 数秒-数分钟 (全网) |
| URL 刷新 | 删除指定 URL 的缓存 | 最快 ~几秒 |
| 目录刷新 | 删除整个目录缓存 | 数分钟 |
| 缓存预热 (Prefetch) | 提前加载热点文件到 CDN | — |
| 预热场景 | 大促前预热商品图片/首页 | 提前推送到边缘节点 |
CDN 与 HTTPS
text
CDN 部署 HTTPS 的两种模式:
方案 1: CDN 代持证书 (最常用)
用户 ←→ CDN: HTTPS (CDN 持有域名证书)
CDN ←→ 源站: HTTPS 或 HTTP
优点: 用户就近完成 TLS 握手(低延迟)
风险: CDN 能看到明文(需信任 CDN 厂商)
方案 2: 源站自有证书
用户 ←→ CDN: HTTPS (CDN 代持)
CDN ←→ 源站: HTTPS (源站证书,端到端加密)
优点: CDN 看不到明文
代价: 源站需承担 TLS 开销CDN 常见问题排查
| 现象 | 可能原因 | 诊断方法 |
|---|---|---|
| 部分地区访问慢 | 该地区边缘节点异常 | 多地区 ping/traceroute |
| 缓存不生效 | TTL 设置错误、header 冲突 | 检查 Cache-Control、Expires |
| 回源率过高 | TTL 太短、缓存规则遗漏 | 监控 CDN 回源流量 |
| 命中后内容不一致 | 未刷新缓存 | 对比 CDN 返回 vs 源站内容 |
| HTTPS 证书错误 | 证书过期/域名不匹配 | 检查证书有效期和 SAN |
调试工具
bash
# A 记录查询
dig www.example.com A
# 跟踪解析链(迭代查询)
dig +trace www.example.com
# 指定 DNS 服务器
dig @8.8.8.8 www.example.com
# 反向解析
dig -x 1.2.3.4
# 查询 SOA(检查权威 DNS)
dig example.com SOA
# nslookup 简单查询
nslookup www.example.com
# 查看本地 DNS 缓存
# macOS: sudo dscacheutil -cachedump
# Linux: systemd-resolve --statistics实战排查三步法
DNS 问题是"网站打不开"的常见罪魁祸首,排查三步法:
bash
# 第 1 步:本地缓存正常吗?
nslookup www.example.com
# 如果超时 → DNS 服务器不可达(检查 /etc/resolv.conf)
# 第 2 步:权威 DNS 正常吗?
dig +short @ns1.example.com www.example.com
# 如果正常 → 是 LDNS 或运营商的问题
# 如果也失败 → 域名配置有问题
# 第 3 步:是否存在劫持?
dig +short @8.8.8.8 www.example.com # Google DNS
dig +short @1.1.1.1 www.example.com # Cloudflare DNS
dig +short @114.114.114.114 www.example.com # 国内 DNS
# 如果权威返回的 IP 和 LDNS 不同 → 可能被劫持
# 如果国内外返回不同 IP → 正常(CDN 分地区调度)DNS 安全
DNS 最初设计于 1983 年,当时互联网规模很小、参与者都是信任的。它天生缺乏加密和认证——这意味着攻击者可以偷看(隐私泄漏)、篡改(DNS 劫持)甚至伪造(DNS 投毒)你的 DNS 请求。
| 方案 | 原理 | 隐私 | 防篡改 | 防劫持 | 端口 | 部署难度 |
|---|---|---|---|---|---|---|
| DNSSEC | 对 DNS 记录签名,递归验证签名链 | ❌(明文) | ✅ | ❌(不防) | 53 UDP | 🟡 |
| DoT | DNS over TLS(公网标准端口 853) | ✅ | ✅ | ✅ | 853 TCP | 🟢 |
| DoH | DNS over HTTPS(伪装成普通 HTTPS) | ✅✅ | ✅ | ✅✅ | 443 | 🟢 |
| 传统 UDP | 明文走 53 端口 | ❌ | ❌ | ❌ | 53 UDP | — |
关键认知:DNSSEC 只验证记录未被篡改,不加密。DoT/DoH 提供加密但需要信任 DNS 服务商。两者结合才是完整方案。
HTTPDNS — 绕过运营商 DNS
HTTPDNS 的核心思路是:不再通过运营商 LDNS,直接通过 HTTPS 向 DNS 服务商查询。运营商无法拦截 HTTPS 请求,因此无法劫持:
传统链路:
App → 运营商 LDNS (可能劫持/缓存不准) → 权威 DNS → 返回可能被篡改的 IP
HTTPDNS 链路:
App → HTTPS → HTTPDNS 服务商 (用客户端真实 IP 查) → 权威 DNS → 返回正确 IPGo 实现思路:通过 net.Resolver 自定义 DNS 解析,用 HTTP API 查询代替系统 DNS:
go
// 使用自定义 Resolver 实现 HTTPDNS
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
// address 通常是 "8.8.8.8:53",这里替换为 HTTPDNS 逻辑
d := net.Dialer{Timeout: 3 * time.Second}
return d.DialContext(ctx, "tcp", "your-httpdns-api.com:443")
},
}
// 配合 http.Client 使用
http.DefaultClient.Transport = &http.Transport{
DialContext: resolver.DialContext,
}CoreDNS 与 Kubernetes DNS
K8s DNS 架构
在 Kubernetes 中,DNS 是服务发现的核心机制。CoreDNS 作为集群 DNS 服务器,为 Pod 提供 Service 名称解析:
mermaid
flowchart TB
subgraph Pod["Pod (应用容器)"]
App["应用进程"]
Resolv["/etc/resolv.conf<br/>nameserver 10.96.0.10<br/>search default.svc.cluster.local"]
end
subgraph CoreDNS["CoreDNS (kube-system)"]
CD1["CoreDNS Pod 1"]
CD2["CoreDNS Pod 2"]
end
subgraph K8sAPI["Kubernetes API"]
SVC["Service 对象"]
EP["Endpoints 对象"]
end
App -->|"DNS 查询<br/>my-svc.default.svc.cluster.local"| CD1
CD1 -->|"Watch"| K8sAPI
CD1 -->|"返回 ClusterIP<br/>10.96.1.100"| App
subgraph External["外部 DNS"]
Upstream["上游 DNS<br/>(8.8.8.8)"]
end
CD1 -->|"非集群域名<br/>转发到上游"| UpstreamDNS 记录类型
K8s 自动创建的 DNS 记录:
Service (ClusterIP):
my-svc.default.svc.cluster.local → 10.96.1.100 (A 记录)
Service (Headless, clusterIP=None):
my-svc.default.svc.cluster.local → Pod IP 列表 (A 记录)
pod-name.my-svc.default.svc.cluster.local → 单个 Pod IP
StatefulSet Pod:
web-0.nginx.default.svc.cluster.local → Pod IP (稳定 DNS 名)
SRV 记录:
_http._tcp.my-svc.default.svc.cluster.local → 端口 + 目标CoreDNS Corefile 配置
.:53 {
errors # 错误日志
health { # 健康检查端点
lameduck 5s
}
ready # 就绪探针
kubernetes cluster.local in-addr.arpa ip6.arpa { # K8s 插件
pods insecure # Pod DNS 记录
fallthrough in-addr.arpa ip6.arpa
ttl 30 # TTL 30 秒
}
prometheus :9153 # 暴露 Prometheus 指标
forward . /etc/resolv.conf # 非集群域名转发到上游
cache 30 # 缓存 30 秒
loop # 检测转发循环
reload # 配置热加载
loadbalance # DNS 轮询负载均衡
}CoreDNS 性能调优
| 问题 | 现象 | 解决 |
|---|---|---|
| DNS 解析慢 | Pod 启动慢、请求超时 | 增加 CoreDNS 副本数 |
| DNS 5s 超时 | 偶发 5s 延迟 | 设置 ndots:2 减少搜索域 |
| CoreDNS OOM | 大集群 Service 多 | 增大内存限制 |
| 缓存命中率低 | 频繁查询上游 | 增大 cache TTL |
yaml
# 优化 Pod 的 DNS 配置(减少无效查询)
apiVersion: v1
kind: Pod
spec:
dnsConfig:
options:
- name: ndots
value: "2" # 默认 5,改为 2 减少搜索域尝试
- name: single-request-reopen
value: "" # 避免 conntrack 竞争导致 5s 超时DNS 性能基准数据
DNS 查询延迟基准(典型值):
┌────────────────────────────────────────────────────┐
│ 场景 │ 延迟 │ 说明 │
├─────────────────────────┼─────────────┼────────────┤
│ 本地 hosts 文件 │ < 0.1ms │ 无网络开销 │
│ 系统 DNS 缓存命中 │ < 0.5ms │ 内存查找 │
│ CoreDNS 缓存命中 │ 0.5-2ms │ Pod→CoreDNS │
│ CoreDNS 缓存未命中 │ 2-10ms │ 查 K8s API │
│ ISP DNS 缓存命中 │ 1-5ms │ 局域网 RTT │
│ ISP DNS 递归解析 │ 20-100ms │ 多跳查询 │
│ 跨国 DNS 解析 │ 100-300ms │ 跨洋 RTT │
│ HTTPDNS │ 10-50ms │ HTTPS 开销 │
│ DoH (DNS over HTTPS) │ 20-80ms │ TLS 握手 │
└────────────────────────────────────────────────────┘
优化建议:
- 本地缓存 > 集群缓存 > 递归解析
- K8s 中设置合理的 ndots 避免无效查询
- 高频域名预解析(DNS Prefetch)
- 连接池复用避免重复解析DNS 工程实践:客户端一次请求,常常先慢在解析阶段
0. 一眼看懂:DNS 会怎样拖慢整个请求
mermaid
flowchart LR
A["本地缓存 / CoreDNS 未命中"] --> B["递归解析变慢"]
B --> C["connect 更晚开始"]
C --> D["TLS / 业务预算被压缩"]
D --> E["timeout / retry / RT 抖动"]| 场景 | 优先看什么 | 常见误判 |
|---|---|---|
| connect 慢 | DNS 拆分耗时、缓存命中率 | 一上来只怪网络 |
| K8s 偶发 5s | ndots、CoreDNS、副本和上游 DNS | 只怪应用卡顿 |
| 外部依赖抖动 | resolver、DoH/HTTPDNS、降级路径 | 只怪对方服务不稳 |
| QPS 高时超时变多 | 是否频繁重复解析、短连接多 | 只怪服务容量不够 |
1. DNS 慢的危害,不只是多几十毫秒,而是直接吞掉整条请求预算
在客户端视角里,一次 HTTP / RPC 请求经常先经历:
- 域名解析
- TCP connect
- TLS 握手
- 请求发送
- 等待首包
所以如果 DNS 先慢了 50ms~200ms,后面的 connect、TLS、业务处理预算都会被压缩。
2. 很多“接口偶发超时”,根因其实不是服务端慢,而是解析链不稳定
典型原因包括:
- 本地 DNS / CoreDNS 缓存未命中
- 上游递归解析器抖动
- K8s
ndots导致多次无效搜索域查询 - DoH / HTTPDNS 本身超时或降级路径有问题
- 客户端没有复用连接,导致频繁重复解析
因此“服务端 RT 正常、客户端却超时”的问题,常常要先排除 DNS。
3. DNS 对客户端的影响,经常被误算成 connect 或请求耗时
mermaid
flowchart LR
A["DNS 解析慢"] --> B["connect 开始得更晚"]
B --> C["TLS 握手预算被压缩"]
C --> D["业务请求更容易超时"]
D --> E["用户只看到接口慢 / timeout"]所以如果监控里只看总请求时间、不拆 DNS / connect / TLS / TTFB,很容易误判慢点位置。
4. K8s / 容器环境里,DNS 抖动往往比单机环境更常见
原因常见于:
ndots:5带来多次搜索域尝试- CoreDNS 副本不足
- CoreDNS 到上游 DNS 网络不稳
- 大量短生命周期 Pod 带来缓存命中率下降
这也是为什么很多服务在本地正常、上集群后偶发多出 100ms~5s 抖动。
5. 客户端如果没有连接复用,会把 DNS 问题放大很多倍
每次新建连接都可能触发:
- 重新解析域名
- 重新 connect
- 重新 TLS 握手
因此短连接多的系统里,DNS 抖动会被放大成:
- P99 RT 明显变差
- 短时间大量超时
- 某些外部依赖调用特别不稳定
6. 一个典型故障链:client -> dns -> upstream
mermaid
sequenceDiagram
participant C as Client
participant D as DNS Resolver/CoreDNS
participant U as Upstream Service
C->>D: 查询域名
Note over D: 缓存未命中 / 上游递归抖动 / ndots放大
D-->>C: 响应变慢
C->>U: 开始 connect
Note over C,U: connect/TLS 预算被压缩
U-->>C: 最终请求超时或RT升高7. 常见误判
| 现象 | 容易误判为 | 实际可能是 |
|---|---|---|
| connect 慢 | 网络差 | DNS 先慢了 |
| 服务端日志很快 | 监控错了 | 客户端慢在解析和建连前置阶段 |
| K8s 里偶发 5s | 应用卡顿 | ndots、CoreDNS、上游 DNS 超时 |
| 外部接口抖动 | 对方服务不稳定 | 本地 DNS / DoH / HTTPDNS 解析链不稳 |
| QPS 一高就超时 | 服务容量不够 | 短连接过多导致重复解析和建连放大 |
8. 排障顺序
| 现象 | 优先看什么 |
|---|---|
| 请求总耗时高 | 是否拆分 DNS / connect / TLS / TTFB |
| K8s 中偶发 5s | ndots、CoreDNS 指标、上游 DNS RT |
| 外部依赖抖动 | 本地 resolver、缓存命中率、DNS 降级路径 |
| 短连接系统超时多 | 是否复用连接、是否频繁重复解析 |
| 跨地域请求慢 | 递归解析路径、CDN 调度、DoH/HTTPDNS 策略 |
9. 一个实战原则
text
先把 DNS 看成"请求入口阶段的第一跳延迟",
而不是一个独立的小模块;
只有这样,才能解释很多 connect 慢、TLS 慢、接口偶发超时的问题。10. K8s 环境 DNS 故障专题
10.1 ndots:5 — K8s DNS 放大的根源
K8s 中 /etc/resolv.conf 默认配置 ndots:5,这是大量 DNS 问题的根因:
text
K8s Pod 默认 /etc/resolv.conf:
nameserver 10.96.0.10 ← CoreDNS Service IP
search default.svc.cluster.local svc.cluster.local cluster.local
ndots:5
作用: 如果域名中 "." 的数量 < 5 → 先尝试拼接 search 域
实际效果:
应用查询 "redis" (0 个点 < 5):
→ redis.default.svc.cluster.local ← 第一次查询 (失败)
→ redis.svc.cluster.local ← 第二次查询 (失败)
→ redis.cluster.local ← 第三次查询 (失败)
→ redis ← 第四次查询 (成功)
→ 总共 4 次 DNS 查询!
应用查询 "www.baidu.com" (2 个点 < 5):
→ www.baidu.com.default.svc.cluster.local ← 第一次查询 (失败)
→ ... (同上)
→ 额外 3 次无用的 DNS 查询影响量化:
text
场景: 一个请求需要 3 次外部 API 调用,每次触发 DNS 解析
ndots:5 → 每个 DNS 可能 4 次查询 = 3×4 = 12 次 DNS 查询
如果每次失败查询 5ms → 额外 45ms 延迟
P50: +5ms (缓存命中)
P99: +50-100ms (缓存未命中 + ndots 放大)修复:
bash
# 方案1: 调低 ndots (推荐)
# Pod spec 中设置 dnsConfig:
dnsConfig:
options:
- name: ndots
value: "1" # 只对 0 个点的域名做 search 拼接
# 方案2: 用 FQDN (完全限定域名)
redis.default.svc.cluster.local. # 末尾的 "." 阻止 search
# 方案3: 外部 API 用完整域名 + 本地缓存10.2 DNS 递归查询完整时序
mermaid
sequenceDiagram
participant App as 应用
participant Local as 本地 DNS 栈<br/>(glibc/nscd/systemd-resolved)
participant CoreDNS as K8s CoreDNS
participant Up as 上游 DNS<br/>(VPC / 公共 DNS)
App->>Local: getaddrinfo("api.example.com")
Local->>Local: 检查 /etc/hosts
Local->>Local: 检查本地缓存
alt 缓存命中
Local-->>App: 直接返回
else 缓存未命中
Local->>CoreDNS: DNS 查询
CoreDNS->>CoreDNS: 检查 CoreDNS cache
alt CoreDNS 缓存命中
CoreDNS-->>Local: 返回
else 缓存未命中
CoreDNS->>CoreDNS: 检查 CoreDNS hosts 插件
CoreDNS->>Up: 递归查询
Up-->>CoreDNS: A 记录
CoreDNS->>CoreDNS: 写入缓存 (TTL)
CoreDNS-->>Local: 返回
end
Local->>Local: 写入本地缓存
Local-->>App: 返回
end10.3 DNS 缓存层级
| 层级 | 位置 | 缓存策略 | 排查命令 |
|---|---|---|---|
| 应用层 | JVM DNS cache / Go net.Resolver | 语言特定 | go: GODEBUG=netdns=1 |
| glibc | nscd (Name Service Cache Daemon) | TTL 跟随 DNS 响应 | nscd -g |
| systemd-resolved | 系统 DNS stub resolver | TTL 跟随 | resolvectl statistics |
| CoreDNS | K8s 集群 DNS | TTL + CoreDNS cache 插件 | CoreDNS metrics |
| 上游 DNS | VPC DNS / 公共 DNS (如 8.8.8.8) | 按 TTL | 通常不可控 |
10.4 Java vs Go DNS 缓存差异
| 特性 | Java | Go |
|---|---|---|
| 默认缓存 | ✅ 有(networkaddress.cache.ttl) | ❌ 无内置缓存 |
| 默认 TTL | 30s(安全策略限制为 -1=永久) | N/A |
| 刷新策略 | 固定 TTL 后重新解析 | 每次解析走 OS |
| 影响 | 修改 DNS 后可能 30s 不生效 | 每次查询走 OS resolver |
| 调优 | -Dsun.net.inetaddr.ttl=5 | 用 自定义 Resolver 加缓存 |
go
// Go 中给 DNS 加缓存
var resolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{Timeout: 2 * time.Second}
return d.DialContext(ctx, network, "10.96.0.10:53")
},
}
// 可以用 github.com/mercari/go-dnscache 或自实现 TTL 缓存
登录后即可发表评论 👇