DNS查询默认选用UDP传输,并不是早期设计者随意敲定的方案,核心考量就三点:延迟更低、资源开销小、能够承载海量并发请求。
只要你打开网页、启动App或是发送邮件,背后就会触发DNS查询动作。全球范围内每天的DNS查询请求规模达到万亿级别。假设每一次查询都要先行建立TCP连接,网络链路很快就会被大量握手请求挤占堵塞。
TCP建立连接需要走完三次握手流程。客户端发送SYN报文,服务端回传SYN-ACK,客户端再回复ACK报文,整个过程至少消耗1.5个往返时延。如果服务器物理距离较远,单次往返耗时50毫秒,单单握手环节就要占用75毫秒。反观DNS查询本身的数据体量极小,请求报文一般只有几十字节,响应报文大多几百字节。UDP不需要预先握手,报文可以直接发出,服务器收到后直接返回结果,仅一次往返就能完成交互。省下的这部分时间,就是我们浏览页面时少等待的加载转圈时间。

服务器侧的负载压力同样是关键因素。TCP属于有状态协议,每一条连接都会在服务器内存占用资源,持续维护序列号、窗口大小、重传队列等信息。UDP则是无状态的,服务器收到请求后完成域名检索,直接返回响应,不会留存任何会话相关记录。同一台DNS服务器使用UDP能够承载的并发查询数量,如果换成TCP,往往需要几十台服务器才能支撑。对于根服务器、顶级域名服务器这类承载全球流量的节点来说,UDP是唯一具备可行性的选择。
DNS的交互模式本身也适配UDP。它就是简单的一问一答模型:客户端询问example.com对应的IP地址,服务器返回93.184.216.34。不存在持续的数据流传输,也没有复杂的多轮交互,TCP自带的流量控制、拥塞控制、有序交付这些特性,放在DNS场景下全部属于多余的性能负担。
但UDP存在一个无法回避的短板:无法保障传输可靠性。数据包有可能丢失、乱序或是重复送达。DNS依靠应用层自身的重试机制处理这类问题。客户端发出查询请求后,如果等待一段时间没有收到响应,就会重新发送查询,一般重试两到三次,并且逐步拉长重试间隔。多次重试依旧失败之后,才会切换服务器或者抛出报错。这套机制并不完美,但足够满足业务需求。DNS报文体积很小,丢包概率偏低,重试产生的额外成本也很低。TCP的重传逻辑更加精细,可在DNS这种海量请求的场景下,为换取可靠性付出的连接开销并不划算。
TCP在DNS体系里也不是完全没有用处,有几类场景必须启用TCP。
第一种是响应报文体积超标。UDP承载的DNS响应默认上限为512字节。一旦返回数据超出该限制,服务器会截断响应内容,客户端识别到截断标记后,会改用TCP重新发起查询。后续推出EDNS0扩展,允许客户端声明自身可接收更大的UDP数据包,常见上限为1232字节或者4096字节。但就算启用扩展,超出上限之后依旧要切换TCP。DNSSEC签名数据、长段TXT记录、多条MX记录,都很容易触发报文超限。
第二种是区域数据同步。主DNS服务器和从服务器之间同步完整区域数据,使用AXFR或者IXFR协议,数据体量巨大,必须依托TCP传输。这个场景对数据可靠性要求极高,UDP无法胜任。
第三种情况来自防火墙策略。部分网络环境直接封禁UDP 53端口,仅放行TCP 53端口,这种环境下DNS查询只能走TCP通道。
所以准确描述应该是:DNS以UDP作为默认传输方案,TCP作为备选方案。这套1983年确定的设计,四十多年来基本没有改动。行业内部也曾经有过讨论,研究DNS over TCP能否替代UDP,但多次测算对比之后,UDP在延迟和吞吐能力上的优势依旧非常突出。DoH和DoT属于另外一类技术,它们是将DNS查询封装在HTTPS或者TLS协议内传输,底层有可能使用TCP,但这类方案解决的是加密与隐私保护问题,并不会替换传统DNS里UDP的基础定位。
归根结底,DNS选用UDP,是把低延迟与低资源消耗放在了绝对可靠传输的前面。互联网多年的实际运行经验,也证明了这个取舍是合理的。