DNS 解析基础原理:从输入网址到拿到 IP

你每次在 Android 里用 Retrofit 发请求,底层都会先做一件事:把域名变成 IP 地址。这个过程就叫 DNS 解析。别小看这一步,我优化过几十个 App 的网络性能,发现很多慢请求的根因就是在 DNS 上卡了几百毫秒甚至几秒。

这一章咱们就把 DNS 解析的底层逻辑拆清楚。我不是要给你背 RFC 文档,而是结合我自己踩过的坑,让你真正理解它为什么慢、以及后续怎么优化。

1.1 DNS 查询流程:一个请求的完整旅行

想象你 App 里调用了 Retrofit 的 GET https://api.example.com/users。系统这时候会调用 getaddrinfo 或类似的 API,开始 DNS 查询。整个流程通常是这样的:

  1. 检查本地缓存 —— 先看本地 hosts 文件,再看系统 DNS 缓存。我见过某些国产 ROM 把缓存策略改得非常激进,甚至会导致域名解析到旧 IP。
  2. 询问本地 DNS 服务器 —— 如果缓存没命中,系统会向配置的 DNS 服务器(比如运营商提供的 114.114.114.114 或 8.8.8.8)发起请求。
  3. 递归查询或迭代查询 —— 本地 DNS 服务器会代替你完成整条链路查询:从根 DNS 服务器,到顶级域(.com),再到权威 DNS 服务器,最后把 IP 返回给你。

关键耗时节点: 第 3 步是最耗时的。一次完整的递归查询,可能需要经历 4~8 次网络往返。你想想看,每个 RTT 如果是 30ms,那光 DNS 就花掉 200ms 以上。这在移动网络下尤其痛苦。

我记得有一次在项目里调试一个海外用户的慢请求,抓包发现每次 DNS 查询都要花 1.2 秒。后来检查发现,用户配置的 DNS 服务器是某家小运营商自建的,缓存命中率极低,每次都要遍历全部递归链路。换到公共 DNS 后,直接降到 80ms。

1.2 递归查询 vs 迭代查询:两种不同的干活方式

你可能听过这两个词,但不太清楚它们的区别。说白了,区别就在于「谁去跑腿找答案」。

维度 递归查询 迭代查询
谁承担工作量 本地 DNS 服务器全包 客户端自己逐级问
客户端复杂度 低,只发一个请求 高,需要处理多个响应
实际使用场景 大多数移动端、家庭宽带 权威 DNS 服务器之间的交互
典型耗时 取决于远端链路,通常 100~500ms 多个单跳,累加可能更慢

你平常在 Android 里调用 InetAddress.getByName(),走的基本是递归查询。你的 App 只需要扔一个请求给本地 DNS,剩下的全由它搞定。听起来挺省心对吧?但这也意味着你完全失控了——你没法控制本地 DNS 用哪台根服务器、缓存刷新策略是什么。

我曾经踩过的坑: 有一次在弱网环境下测试,App 的 DNS 解析时间波动极大。后来发现,某些运营商 DNS 对不常见的后缀(比如 .io、.dev)的递归查询,会先走「根→顶级域→权威」的完整路径,而不会缓存中间结果。换句话说,每次请求都做了一轮完整递归。解决方案是换成 HTTP DNS,直接从自己的服务器拿 IP。

1.3 TTL:DNS 缓存的「保质期」

TTL(Time To Live)是 DNS 记录里的一个字段,单位是秒。它告诉接收方:「这个 IP 地址的有效性是 300 秒,超时了要重新查。」

  • TTL 太短:比如 30 秒,那么你的 App 每半分钟就要重新解析一次,流量和耗时都浪费。
  • TTL 太长:比如一天,如果后端换 IP,你的客户端可能半天都访问不到新地址。

我个人习惯在优化 DNS 时,先检查目标域名的 TTL。怎么查?用 dig 命令:

dig api.example.com +short
# 返回示例:93.184.216.34
dig api.example.com +noall +answer
# 看 TTL 列,比如 "300" 表示 5 分钟

我见过一个案例:某电商 App 的接口域名 TTL 被设成了 10 秒。服务器压力一大就切换 IP,但 10 秒的 TTL 导致 App 几乎每次请求都要重新解析,DNS 的流量占到了总网络流量的 15%。后来业务方把 TTL 改为 120 秒,配合服务端提前预热,问题就解决了。

避坑指南: Android 系统默认会尊重 TTL,但有些定制 ROM(比如 MIUI、EMUI)会强行把 TTL 拉长到 10 分钟以上,目的是省电省流量。这会导致你的 App 在切换网络或域名变动后解析不到新 IP。检测方法:在 App 内自己记录 System.nanoTime() 对比 DNS 返回的时间戳,看实际存活时长是否超过 TTL。

1.4 一张图看懂 DNS 解析全过程

DNS 解析流程:递归+迭代混合模型 Android App 递归查询 (单一请求) 本地 DNS 服务器 迭代查询(逐级询问) 根 DNS 服务器 指向 .com 服务器 顶级域 DNS (.com) 指向 example.com 权威 权威 DNS 服务器 返回 IP: 93.184.216.34 递归返回最终 IP 缓存机制:每一级 DNS 服务器和客户端都会根据 TTL 缓存记录,避免重复查询

1.5 简单总结一下你需要记住的点

  • DNS 解析是网络请求的第一道关卡,通常耗时在 50ms~1s 之间,极端情况下更长。
  • 递归查询对客户端友好但依赖远程 DNS 的性能,迭代查询更灵活但客户端需要自己处理多步结果。
  • TTL 不是摆设,太短浪费流量、太长导致脏缓存。建议后端设置 120~600 秒,同时客户端自己做一层本地缓存兜底。

这一章讲的是最基础的东西,但也是后续一切优化的出发点。下一部分我们会聚焦在 Android 上如何用 OkHttp 和 Retrofit 拦截 DNS,直接控制解析行为。把那部分搞明白,你的 App 网络性能才能真的起飞。