为什么 DNS 解析失败或缓慢会导致整个 HTTP 请求“卡在初始阶段”

11 阅读4分钟

这是一个非常关键且常见的网络性能问题。下面我将从 HTTP 请求生命周期DNS 解析机制 两个维度,深入分析 为什么 DNS 解析失败或缓慢会导致整个 HTTP 请求“卡在初始阶段”


一、HTTP 请求的完整流程(简化版)

一个典型的 HTTP(S) 请求包含以下关键步骤:

sequenceDiagram
    participant Client as 客户端
    participant DNS as DNS 服务器
    participant Server as 目标服务器

    Client->>DNS: 1. DNS 查询 (example.com → IP?)
    DNS-->>Client: 2. 返回 IP 地址 (如 93.184.216.34)
    Client->>Server: 3. 建立 TCP 连接 (SYN, SYN-ACK, ACK)
    Client->>Server: 4. 发送 HTTP 请求
    Server-->>Client: 5. 返回 HTTP 响应

关键点DNS 解析是 HTTP 请求的第一步,且是阻塞式操作


二、为什么 DNS 是“卡住”的罪魁祸首?

1. DNS 解析发生在应用层之前

  • 当你在浏览器输入 https://example.com 或程序调用 requests.get("https://api.example.com") 时,
  • 底层库(如 Python 的 socket.getaddrinfo())会先解析域名再建立连接
  • 如果 DNS 没返回 IP,TCP 连接根本无法发起

📌 结论:DNS 是“前置依赖”,不完成则后续步骤无法启动。


2. 默认 DNS 超时时间很长

不同系统的默认 DNS 超时策略:

环境默认超时行为
Linux glibc尝试多个 DNS 服务器,总超时可达 15~30 秒
Python requests无独立 DNS 超时,继承系统行为
cURL默认 DNS 超时约 30 秒
浏览器(Chrome)通常 10~60 秒(含重试)

⚠️ 示例:
如果你的 /etc/resolv.conf 配置了两个 DNS 服务器,第一个无响应,第二个有效,
系统可能先等第一个超时(5秒)→ 再试第二个(成功),总耗时 5+ 秒


3. DNS 失败 ≠ 快速失败

  • DNS 服务器无响应(丢包、防火墙拦截)→ 触发重试 + 超时 → 长时间卡住
  • DNS 返回 NXDOMAIN(域名不存在)→ 快速失败(毫秒级)
  • DNS 返回错误 IP → 后续 TCP 连接失败,但DNS 阶段本身很快

🔍 所以,“卡住”通常是因为 DNS 服务器无响应,而非域名不存在。


三、技术底层:操作系统如何处理 DNS?

以 Linux 为例:

  1. 应用调用 getaddrinfo("example.com", ...)
  2. C 库(glibc)读取 /etc/nsswitch.conf → 使用 dns 模块
  3. 读取 /etc/resolv.conf 获取 DNS 服务器列表(如 nameserver 8.8.8.8
  4. 向第一个 DNS 服务器发送 UDP 查询(端口 53)
  5. 如果 5 秒内无响应 → 重试(默认 2 次)→ 切换下一个 DNS 服务器
  6. 所有尝试失败后才报错:Temporary failure in name resolution

🕒 最坏情况耗时 = (timeout + retry) × server_count
例如:(5s × 2次) × 2个服务器 = 20秒


四、实际现象验证

场景:DNS 服务器被屏蔽

# 模拟 DNS 无响应(丢弃所有 DNS 包)
sudo iptables -A OUTPUT -p udp --dport 53 -j DROP

# 尝试请求
time curl -v https://httpbin.org/get

输出

* Could not resolve host: httpbin.org
curl: (6) Could not resolve host: httpbin.org
real    0m20.023s   卡了整整 20 秒!

💡 注意:错误是 “Could not resolve host” ,但耗时极长。


五、如何避免 DNS 导致的“卡死”?

✅ 方案 1:设置合理的 DNS 超时(应用层)

Python 示例(使用 socket 设置超时):

import socket
import requests

# 全局设置 DNS + 连接超时(注意:DNS 超时仍受系统限制)
socket.setdefaulttimeout(5.0)

try:
    resp = requests.get("https://example.com", timeout=5)
except requests.exceptions.Timeout:
    print("Request timed out")

⚠️ 但注意:socket.setdefaulttimeout() 对 DNS 查询无效!
因为 DNS 是同步阻塞调用,发生在 connect() 之前。


✅ 方案 2:使用支持异步 DNS 的库(推荐)

使用 aiohttp + aiodns(异步非阻塞):

import aiohttp
import asyncio

async def fetch():
    timeout = aiohttp.ClientTimeout(total=5)
    async with aiohttp.ClientSession(timeout=timeout) as session:
        async with session.get('https://example.com') as resp:
            return await resp.text()

# DNS 解析在事件循环中进行,可被 timeout 中断
asyncio.run(fetch())

使用 requests + 自定义 Resolver(高级):

通过 urllib3 + dnspython 实现可控 DNS:

import dns.resolver
import requests
from urllib3.util.connection import create_connection

# 自定义解析器(带超时)
def custom_resolver(host, port, timeout=3):
    resolver = dns.resolver.Resolver()
    resolver.timeout = timeout
    resolver.lifetime = timeout
    answers = resolver.resolve(host, 'A')
    ip = answers[0].to_text()
    return create_connection((ip, port), timeout=timeout)

# 注入到 urllib3(复杂,略)

✅ 方案 3:系统级优化

  1. 配置更快的 DNS 服务器

    # /etc/resolv.conf
    nameserver 8.8.8.8      # Google DNS
    nameserver 1.1.1.1      # Cloudflare
    options timeout:1       # 单次查询超时 1 秒
    options attempts:2      # 重试 2 次
    
  2. 使用本地 DNS 缓存(dnsmasq / systemd-resolved)

    sudo apt install dnsmasq
    echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf
    

    → 第二次访问同一域名几乎 0ms 延迟

  3. Hosts 文件硬编码(临时方案)

    echo "93.184.216.34 example.com" | sudo tee -a /etc/hosts
    

六、监控与诊断工具

工具用途
dig example.com查看 DNS 查询耗时
nslookup example.com基础 DNS 测试
tcpdump -i any port 53抓包分析 DNS 请求是否发出/返回
strace -e trace=connect,sendto curl ...跟踪系统调用,确认卡在 DNS

🔍 诊断命令:

time dig @8.8.8.8 google.com +short
# real    0m0.050s → 正常

time dig @192.168.1.1 nonexistent.com +short
# real    0m15.000s → 卡住!

七、总结:为什么 HTTP “卡在初始阶段”?

原因说明
DNS 是前置阻塞操作无 IP 则无法建立 TCP 连接
系统默认超时过长无响应时重试机制导致 10~30 秒延迟
应用未设置 DNS 超时大多数 HTTP 库不暴露 DNS 超时控制
网络中间设备干扰防火墙丢弃 DNS 包(而非拒绝),触发超时

根本解决方案

  1. 系统层:优化 /etc/resolv.conf + 本地 DNS 缓存
  2. 应用层:使用支持异步 DNS 的客户端(如 aiohttp)
  3. 架构层:对关键域名做 IP 缓存或 Hosts 预加载

记住

“DNS 不是网络问题,而是可用性问题。”
一次 DNS 故障,足以让整个服务“看似宕机”。

通过理解 DNS 在请求链中的位置,我们才能设计出真正高可用的系统。