域名解析全链路探秘:从浏览器缓存到操作系统文件,再到DNS递归与迭代查询

0 阅读14分钟

当我在浏览器中输入一个域名时,比如 www.cncfstack.com,是怎么将这个域名转换成 IP 地址呢?

实际域名解析过程不只是简单的DNS解析,而是一个非常复杂的一个过程,特殊情况下的问题分析具有较高的难度,需要对底层原理有深度的理解,本文以 Linux 系统为例,深度剖析域名解析的四大核心阶段:应用层缓存、操作系统解析、DNS递归查询与迭代查询。通过拆解完整的解析链路,带你彻底搞懂 DNS 的底层工作机制,为日常网络故障排查与性能优化提供坚实的理论支撑。。

如图是一个普通域名的组成部分,将其转换成 IP 地址需要经过如下几个阶段:

域名解析过程包含如下四个大阶段:

  • 应用层缓存阶段
  • 操作系统解析阶段
  • 递归查询阶段
  • 迭代查询阶段

本文会对这四个阶段以 Linux 系统为例分别进行探索和介绍。

一、应用层缓存阶段

应用层缓存一般是指应用DNS域名缓存,是应用程序自身对域名解析添加了缓存,典型场景如浏览器 Chrome 应用默认就会对域名进行缓存。

当我们在浏览器(如 Chrome)输入一个域名(如 www.cncfstack.com\[1\] )时,浏览器会首先从 浏览器本地内存缓存 中查找该域名的解析结果,如果本地缓存中存在该域名的解析结果,则直接返回该结果。

例如 Chrome 浏览器的缓存记录可以通过如下命令查看 chrome://net-internals/?#dns

浏览器缓存的域名解析记录时间一般比较短,与域名 TTL 和浏览器策略有关。例如Chrome浏览器,如果 TTL 小于60秒,则缓存有效期为 TTL值,否则有效期默认是 60 秒。

浏览器缓存的域名解析记录是基于内存,所以当浏览器被关闭时,该缓存记录就会丢失。

如果浏览器中没有对应的域名的,那么浏览器底层会调用 getaddrinfo() 函数获取域名解析,该函数属于 C 标准库,如最常用 glibc (Linux) 或 musl 等。

getaddrinfo() 执行过程就是下一个阶段 操作系统解析阶段 的开始。

二、操作系统解析阶段

getaddrinfo() 的功能就是把域名和服务名翻译成 addrinfo 结构体链表,里面包含建立连接所需的 IP 和端口。在操作系统中执行时会先获取 /etc/nsswitch.conf 配置的 DNS 配置。

nsswitch.conf 文件配置了域名解析的默认顺序,如:

[root@cncfstackvm2 ~]# cat /etc/nsswitch.conf |grep "^hosts"
hosts:      files dns myhostname

nsswitch.conf 执行逻辑是:从前到后,如果当前来源查询成功,就立即停止并返回结果;如果失败(未找到或无响应),就继续尝试下一个来源。

这里的含义是对于域名解析(hosts)的配置策略是:先查询本地 file ( hosts 文件,即 /etc/hosts ),如果未找到,再查询 dns 服务器( 即 /etc/resolv.conf 配置的 DNS 服务器 ),如果未找到,再查询 myhostname ( 默认是主机名 )。

file 解析阶段

file解析就是通过本地文件进行域名解析,在 Linux 操作系统中,该文件默认是 /etc/hosts

/etc/hosts 是一个“静态域名映射表”,核心作用就是在不查询 DNS 服务器的前提下,让系统直接知道某个域名对应的 IP 地址。

[root@cncfstackvm2 ~]# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
1.2.3.4     www.example.com

/etc/hosts 文件的格式是每一条记录占一行。最前面是IP地址,IPv4 或 IPv6 都可以(如 127.0.0.1)。后面是主机名/域名,可以配置一个或多个(如:localhost localhost.localdomain localhost4 localhost4.localdomain4)。

/etc/hosts 文件的解析逻辑是:从上到下,如果当前行匹配,就返回该行。如果匹配失败,就继续下一行。

# 开头的行会被忽略,表示注释行。

/etc/hosts 配置文件使用场景非常广泛,如域名没有在DNS服务器配置时本机解析该域名,或者DNS默认返回的域名和预期不符合时手动指定解析记录(如github.com),或者拦截一些软件自动联网的License检查而将域名指向127.0.0.1等场景。

如果在 /etc/hosts 中未找到,那么会继续执行 dns 解析阶段

dns 解析阶段

/etc/nsswitch.conf 中配置的 dns 模块会调用 /etc/resolv.conf 配置的 DNS 服务器进行域名解析。

/etc/resolv.conf 是 Linux 系统中配置 DNS 解析器的核心文件。它告诉系统,当需要进行域名查询时,应该向哪些 DNS 服务器发起请求,以及如何查询。

[root@cncfstackvm2 ~]# cat /etc/resolv.conf
# Generated by NetworkManager
nameserver 100.100.2.136
nameserver 100.100.2.138
search example.com dev.example.com
options timeout:1 attempts:2 rotate

nameserver 用来指定 DNS 服务器,每一行指定一台。nameserver 配置多条时是有优先级的,按照从上到下的顺序轮询,排在最上面的是首选服务器。默认只有前一台超时不可用时,才会尝试下一台(注意这里不是负载均衡)。因此,将最快最稳定的服务器放在最上面是标准做法。glibc 的解析器最多只读取前 3 个 nameserver 配置,后续配置将被忽略。在非轮询(rotate)模式下,它主要使用第一个,故障时才尝试后续的;在轮询模式下,它会在这几个服务器之间分摊查询压力

search 是配置域名搜索后缀,它可以能让你只输入主机名就能访问服务。当你输入一个不完整的主机名(不带点 .)时,系统会自动尝试用这里定义的域后缀拼接成完整域名去查询。可以定义多个后缀,用空格分隔,按顺序拼接。

search 在 Kuberentes 集群中解析 Service 名称时非常典型,示例:

search cluster.local svc.cluster.local ns1.svc.cluster.local

假设在集群中有个服务 dev-db Service 域名需要解析时,会按照如下顺序进行解析:

dev-db.cluster.local
dev-db.svc.cluster.local
dev-db.ns1.svc.cluster.local

只要 servcie 这个服务存在,那么 dev-db.ns1.svc.cluster.local 就可以解析成功。

如果还查不到,才会把 dev-db 当作一个完整的域名去查,这种主机名的方式作为域名解析可能就是解析失败。但是如果查询的是 www.cncfstack.com ,那么最后一个是 www.cncfstack.com.ns1.svc.cluster.local 查询不到后,就会通过 nameserver 的配置进行 www.cncfstack.com 解析查询。

options 是用来精细控制查询行为的配置。常用的选项有:

  • timeout:n : 查询超时时间(秒)。默认值因系统而异,网络不佳时可适当调大,如 timeout:2。
  • attempts:n : 重试次数。对每台服务器尝试查询的次数,默认通常是2或3。
  • rotate : 轮询负载均衡。不加这个选项时,总是优先问第一台服务器。加上后,系统会在所有 nameserver 之间轮询,对分担负载有帮助。
  • ndots:n : ndots 的默认值通常是 1。如果名字中的点数 < ndots(如 dev-db 有0个点 < 1),会先尝试拼接搜索域;如果域名中的点数大于等于 ndots,解析器会优先尝试直接解析该域名(作为绝对域名)。如果直接解析失败,且域名不以点结尾,解析器可能会根据具体实现尝试搜索域,但通常会优先保证直接解析的尝试。

对于 /etc/resolv.conf 配置需要注意管理方式,有时会遇到手动修改完成后自动恢复成默认配置,这是由于有系统服务进行配置管理导致的。如示例中有一行

# Generated by NetworkManager

这一行表示当前文件是由 NetworkManager 自动生成管理的,因此手动修改会被 NetworkManager 服务覆盖。如果需要基于 NetworkManager 进行管理,可以通过 nmcli 命令进行管理。

# 1. 查看确认连接名
[root@cncfstack-vm2 ~]# nmcli connection show
NAME             UUID                                  TYPE      DEVICE
System eth0      5fb06bd0-0bb0-7ffb-45f1-d6edd65f3e03  ethernet  eth0
br-1e1c15e702b6  a21d4908-6b04-4552-a245-4bc360f4a622  bridge    br-1e1c15e702b6
docker0          97452e72-3983-48ae-9144-30ac59ce5bef  bridge    docker0

# 2. 修改DNS
[root@cncfstack-vm2 ~]# nmcli connection modify "System eth0" ipv4.dns "223.5.5.5,119.29.29.29"

# 3. 让修改生效(注意下面两条命令要一起执行,否则会断网)
[root@cncfstack-vm2 ~]# sudo nmcli connection down "System eth0" && sudo nmcli connection up "System eth0"
成功停用连接 "System eth0"(D-Bus 活动路径:/org/freedesktop/NetworkManager/ActiveConnection/1)
连接已成功激活(D-Bus 活动路径:/org/freedesktop/NetworkManager/ActiveConnection/4)

# 4. 验证(查看resolv.conf是否已自动更新)
[root@cncfstack-vm2 ~]# cat /etc/resolv.conf
# Generated by NetworkManager
nameserver 223.5.5.5
nameserver 119.29.29.29
nameserver 100.100.2.136
# NOTE: the libc resolver may not support more than 3 nameservers.
# The nameservers listed below may not be recognized.
nameserver 100.100.2.138

myhostname 解析

myhostname 是最后尝试本地主机名。这是一个特殊的模块,如果前两步都查不到,它会把查询的名字和本机当前设置的主机名进行比对。如果匹配,就返回本机的一个回环地址(127.0.0.1 或 ::1)。如果要求查询的是 localhost 或 localhost.localdomain,也会被它捕获并正确解析。

这是一个安全兜底机制,它确保对本机主机名的解析始终有效,不用非得在 /etc/hosts 里配置。即便网络断连,一些依赖本机主机名才能启动的服务也不会卡住。

/etc/nsswitch.conf 验证

getent 是一个用于获取系统数据库条目的工具,其名字就是 “get entries” 的缩写。它的作用是,严格按照 /etc/nsswitch.conf 中定义的顺序,去查询各种系统数据库,并直接显示查询结果。这使得它成为调试名称解析问题或检查用户、组信息的利器。

[root@cncfstack-vm2 ~]# cat /etc/hosts |grep example.com
1.2.3.4  www.example.com

[root@cncfstack-vm2 ~]#  getent ahosts www.example.com
1.2.3.4         STREAM www.example.com
1.2.3.4         DGRAM
1.2.3.4         RAW

它会按 hosts: files dns myhostname 的顺序去查,直接输出最终结果,比如返回 IP 地址。

系统DNS缓存

操作系统缓存 DNS 域名解析可以为系统上所有的应用加速域名解析服务,但是不同的操作系统的机制不同。

  • Linux 内核和 glibc 的 stub resolver 本身不提供缓存。实际缓存由独立的用户态守护进程实现:传统方案是 nscd,现代方案是 systemd-resolved 或 dnsmasq。
  • macOS 的 DNS 缓存服务默认自动运行,由 mDNSResponder 守护进程统一管理。
  • Windows 的 DNS 缓存服务默认自动运行,由 Dnscache 服务(也叫 DNS Client 服务)负责管理。

操作系统缓存是在 /etc/hosts 静态解析之后,/etc/resolv.conf 之前的阶段。命中:直接返回。未命中:由 /etc/resolve.conf 去查 DNS,拿到结果存入内存缓存,再返回。

在使用 /etc/resolve.conf 中 nameserver 进行域名解析时,会进入到 DNS 递归查询阶段。

三、递归查询阶段

递归查询是 DNS客户端 -> DNS服务器 查询域名解析过程,由DNS服务器获取域名解析并返回结果,DNS客户端不关注DNS服务器是怎么获取到域名解析结果的。

递归查询阶段,会按照 /etc/resolv.conf 中 nameserver 的顺序进行查询,成功或者失败都会返回结果给客户端。

在大多数场景中,DNS客户端(即服务器)选中一个 nameserver 中的 DNS 服务器时,客户端指定的DNS服务器无法解析域名时,就会通过 forward 将解析请求转发到下一个 DNS 服务器进行查询。

其递归查询过程如下:

递归查询是行为模式(我委托你,你给我结果),具体实现可以是服务器自己迭代,也可以是转发给上游。企业内网多级转发本质上是“递归链”,每一跳都是独立的递归查询。

  • 3.1 递归查询,客户端是服务器,服务端是机房-DNS服务器。机房DNS服务器一般就是机器上 /etc/resolv.conf 文件中配置的 nameserver 地址。
  • 3.2 递归查询,客户端是机房-DNS服务器,服务端是公司-DNS服务器。公司内可能会有多个数据中心机房,机房的DNS服务器上游一般是公司内部DNS服务器,机房DNS服务器无法解析时,会通过 forward(转发) 的方式将请求发送给公司 DNS 服务器。。
  • 3.3 递归查询,客户端是公司-DNS服务器,服务端是运营商-DNS服务器。公司DNS服务器无法解析时,会通过 forward 向下一个运营商-DNS服务器进行查询。运营商DNS服务器一般能够提供比较全的 DNS 解析缓存,比如最常用的就是 8.8.8.8 或 114.114.114.114 或 223.5.5.5 等。这些 DNS 服务器如果没有缓存某个域名的解析记录,才会发起迭代查询。

迭代查询的过程对服务器的性能和网络质量要求高,并迭代查询过程需要很多轮比较耗时。因此,一般企业的DNS服务器都是作为客户端的角色进行递归查询,而运营商或云厂商为了保持自身DNS解析的自主可控性,往往会通过迭代查询的方式维护DNS记录,而不是依赖其他运营商或云厂商的DNS服务。

四、迭代查询阶段

迭代查询是 DNS 服务器作为客户端,依次向多个不同的DNS服务器逐步获取更精确的解析结果,直到找到权威服务器返回解析记录的过程。

以 Google 的 8.8.8.8 作为迭代查询客户端,查询 www.cncfstack.com 的完整迭代过程:

  • 4.1 谁管理 . 根域名?Google DNS 服务器读取 named.root 文件(根提示文件),该文件存储了全球 13 组(上千台服务器)根域名服务器的 NS 记录及其 IPv4/IPv6 地址。该文件会在安装 DNS 软件或 dig 等工具时自动生成,也可以通过 www.internic.net/domain/name… URL直接获取。
  • 4.2 谁管理 .com 顶级域名?Google DNS 向其中一台根服务器(如 a.root-servers.net)发起 www.cncfstack.com 的查询请求,根服务器返回负责 .com 顶级域的 DNS 服务器的 NS 记录和 IP 地址。这里的 IP 地址也称为胶水记录,解决了‘先有鸡还是先有蛋’的问题——域名服务器自身也在被解析的域内时,必须额外提供 IP,避免循环依赖”。
  • 4.3 谁管理 .cncfstack.com 二级域名? Google DNS 向管理 .com 顶级域服务器发起 www.cncfstack.com 的查询请求,管理 .com 的服务器返回负责 cncfstack.com 的权威 DNS 服务器的 NS 记录,如 阿里云的dns25.hichina.com
  • 4.4 www.cncfstack.com 的 A 记录是什么?Google DNS 向 dns25.hichina.com(权威 DNS 服务器)发起 www.cncfstack.com 的 A 记录查询请求。 dns25.hichina.com 查询自身的区域文件,返回 www.cncfstack.com 的 IP 地址(如 1.2.3.4),整个迭代查询过程结束。

整个过程中全部由 Google DNS 服务器去依次迭代查询,最终获取到 www.cncfstack.com 的解析结果。

迭代查询完成后的响应

迭代查询结束,将解析结果缓存,后续相同域名的解析会优先使用缓存进行响应,而不需要再次迭代查询。

承接上节示例:

  1. Google DNS 迭代查询结束,将解析结果缓存,后续相同域名的解析会优先使用缓存进行响应,而不需要再次迭代查询。然后将 www.cncfstack.com 的 A 记录 IP 地址给客户端,即公司的DNS服务器。
  2. 公司DNS服务器收到解析结果,将结果缓存,并返回给机房DNS服务器。
  3. 机房DNS服务器收到解析结果,将结果缓存,并返回给服务器。
  4. 服务器收到解析结果,将结果缓存在操作系统(在有缓存进程时),并相应 getaddrinfo() 调用。
  5. 浏览器收到 getaddrinfo() 返回的域名解析结果,将结果缓存(如Chrome)。
  6. 浏览器获取到域名解析的 IP 地址后,与该 IP 建立 TCP/UDP 连接,开始进行 HTTP 请求。

下一个阶段

到这里客户端就可以访问 www.cncfstack.com 了,在与服务器 IP 建立 TCP/UDP 连接后,客户端会向服务器发送 HTTP/HTTPS 请求。

为了保证客户和服务器之间的数据传输安全,目前主流的站点都默认使用 HTTPS 的方式访问,即 www.cncfstack.com 方式。这时就会进行 HTTPS 的证书处理流程,我在这篇文章《一张图了解HTTPS证书的CA、签发、校验和数据加密流程。详解OpenSSL自签证书,并封装自动化脚本[2]》中进行了详细的介绍。

引用链接

[1] www.cncfstack.com: www.cncfstack.com
[2] 一张图了解HTTPS证书的CA、签发、校验和数据加密流程。详解OpenSSL自签证书,并封装自动化脚本: cncfstack.com/b/docs/2025…