接上一篇《DNS中有哪些值得学习的优秀设计》留下的最后两道题。DNS是基于UDP协议的?从抓包可以看出,DNS在传输层使用的是UDP协议,那么是不是只使用UDP呢??DNS中只有13个IPV4根域名,其中很多部署在美丽的国家。那是不是说只要他们不高兴,切断我们的访问,我们的网络就会瘫痪?让我们把今天的话题展开。DNS是基于UDP的应用层协议吗?当我们执行digwww.baidu.com时,操作系统会发送一个dns请求,询问www.baidu.com域名对应的IP是多少。$挖www.baidu.com;<<>>DiG9.10.6<<>>www.baidu.com;;全局选项:+cmd;;得到答案:;;->>HEADER<<-操作码:QUERY,状态:NOERROR,id:61559;;标志:qrrdra;查询:1,答案:3,权限:0,附加:1;;选择伪部分:;EDNS:版本:0,标志:;UDP:4000;;问题部分:;www.baidu.com。在一个;;答案部分:www.baidu.com。298CNAMEwww.a.shifen.com.www.a.shifen.com.298在180.101.49.12www.a.十分网。298INA180.101.49.11此时从抓包的角度来看,DNS是一个应用层协议,传输层确实使用了UDP协议。然而,传输层使用UDP协议,实际上在RFC5966中提到过。#https://www.rfc-editor.org/rfc/rfc5966本文档更新了支持TCP作为DNS实施的传输协议的要求。也就是说,虽然我们看到DNS在大多数情况下使用的是UDP,但实际上DNS也是支持TCP的。当我们在dig命令中添加+tcp选项时,我们可以强制DNS查询使用TCP协议进行数据传输。$dig+tcpwww.baidu.com;<<>>DiG9.10.6<<>>+tcpwww.baidu.com;;全局选项:+cmd;;得到答案:;;->>HEADER<<-操作码:QUERY,状态:NOERROR,id:28411;;标志:qrrdra;查询:1,答案:3,权限:0,附加:1;;选择伪部分:;EDNS:版本:0,标志:;UDP:4000;;问题部分:;www.baidu.com。在一个;;答案部分:www.baidu.com。600INCNAMEwww.a.shifen.com.www.a.shifen.com.600INA180.101.49.11www.a.shifen.com。600INA180.101.49.12此时再次抓包。传输层使用TCP协议。可以发现,在传输层,DNS使用的是TCP协议。那么问题来了。有UDP为什么还要用TCP?我们知道,网络传输就像是在一定的管道中传输数据包。这条管道有一定的厚度,称为MTU。如果超过了MTU,就会在发送端的网络层进行分段,然后在接收端的网络层重新组装。而重组需要一个缓冲区,这个缓冲区的大小有一个最小值,即576Byte。IP层分片后传输会增加丢包的概率,而IP层本身不具备重传的功能,所以要尽量避免IP层分片。如果在传输过程中真的发生了分片,需要尽量保证接收端的顺利重组,所以最安全的情况下,将MTU设置为576。(有些过于谨慎,现在大多数场景下MTU=1500)。基于这个前提,MTU长度不包括IP头和UDP头,剩下大约512Byte。这就是为什么在RFC1035中提到在UDP场景下,DNS报文长度不能超过512字节。如果超过,它将被截断。那么数据包不完整,可能导致下游无法正常解析数据。但是难免总会有需要传输大量数据的场景。怎么做?然后改用TCP。因为TCP本身会被分段,所以分段的长度恰好小于等于MTU的长度。并且丢包后会重传,保证数据的正常传输。因此,当数据包长度大于512时,DNS需要使用TCP协议进行传输。既然TCP这么好,为什么不全部使用TCP呢?我们可以对比上面UDP和TCP的两张图,我们会发现TCP场景下除了DNS请求和响应包之外,还多了三个握手包和四个wave包。乍一看,似乎并不过分。我们回头看看通过DNS协议查询域名对应IP的过程。如果把查询过程细分的话,可以分为迭代查询和递归查询。什么是迭代查询和递归查询?迭代查询就是DNS下发后,如果对方不知道域名的IP是多少,就告诉我可能知道的机器IP,我知道就问。机,重复直到问出结果。递归查询是指发送DNS请求后,要求对方进行校验,直接给出最终结果。递归查询看似很方便,但实际上是将查询过程传递给了其他DNS服务器。这么多时候,两者是同时存在的。例如。比如迭代查询和递归查询,还是查询www.baidu.com对应的IP。当本机发出DNS请求时,会要求距离最近的DNS服务器查询结果,并返回给本机(step1),所以这次是需要递归查询。这台机器很容易,但最近的DNS服务器(可能是您的家庭路由器)需要忙碌。它需要使用迭代查询。最坏情况下需要:step2:查询根域名服务器step3:获取根域名服务器返回的一级域名(com)服务器IP,step4:查询一级域名(com)服务器step5:获取二级域(baidu)服务器IPstep6:查询二级域(baidu)服务器step7:获取三级域(www)服务器IPstep8:查询三级域(www)服务器step9:获取www.baidu.com服务器的IP此时DNS服务器将结果放入缓存机后返回结果(step10)。可以看出,迭代查询和递归查询在这个场景下其实是同时存在的。迭代查询和递归查询的报文特征也体现在DNS头中。DNS报文中我们需要注意的是Flags字段中的RD和RA字段。RD(ResursionDesired)是指客户端期望的查询方式。?0:表示迭代查询?1:表示递归查询RA(RecursionAvailable)是指服务器实际使用的查询方式,只会出现在响应包中。?0:表示迭代查询?1:表示递归查询。迭代查询和递归查询的影响又回到了为什么DNS不全部使用TCP的问题上。我们可以看到,DNS请求中涉及到的服务器其实有很多。如果使用TCP,则需要三次握手建立连接,四次挥手断开连接。对于递归查询这边,其实还可以,因为只会建立一个连接,发送一个请求,接收一个响应。但是对于迭代查询端来说,需要和很多服务器反复建立和断开连接。性能会受到很大影响。这个时候大家估计都会想问了。那是不是说TCP连接不会断开,下次再用呢?不太好。因为大部分url涉及的域名服务器是不同的,比如www.baidu.com和www.xiaobaidebug.top涉及的一级、二级、三级域名服务器是不同的,所以不需要维护TCP长链接做多路复用。所以相比之下,在数据量小的场景下,使用UDP可以节省握手和挥手的消耗,所以UDP是更好的方案。DNS是否只有13个IPV4根域?的确。问题又来了。为什么有13个IP,可以加吗?这纯粹是出于历史原因。上面提到基于UDP的DNS报文不能超过512Byte。如果去掉DNS本身的头部信息,大概可以容纳13个IP(IPV4)。具体的计算过程不重要,就省略了。如果您对计算过程感兴趣,可以阅读本文底部的参考资料。虽然现在大部分机器MTU=1500,但由于可能还有MTU=576的机器,需要向前兼容,所以不建议随意调整。但问题来了。退一步讲,即使所有机器的MTU都达到1500,难道没有这个限制吗?嗯,从这个角度来说,确实可以加,但是没有必要。我们需要思考为什么要加上它?是不是觉得13个IP对应13台服务器,压力太大?还是因为其他无法解释的因素?比如很久以前看电视的时候,一位瓦工提到“全世界只有13台DNS根服务器,其中x台部署在美丽的国度,只要他们断网,我们的网络就会受到影响巴拉巴拉”。但实际上,13个IP并不代表只有13台服务器。准确的说应该是13组服务器,每组可以无限扩展服务器数量,多台服务器共享同一个IP。这其实涉及到一个叫做anycast的技术。我们知道什么是任播。在传输过程中,一台机器向另一台机器发送消息,称为单播。单播一台机器,向本地网段内的所有机器发送消息,称为广播。广播两者都很常见,应该没问题。向所有符合条件的目标机器之一发送消息的机器称为任播。Anycast我们知道,全世界的网络设备都放在一起,形成一个网络结构,这也是网络名称的由来。我们假设有这样一个路由器,它要访问某个IP的机器。从路由器到目的机器有很多条路径,路由器可以通过跳数等信息计算每条路径的代价,从而得到最优路径。将最优路径汇总成一张表,也就是我们常说的路由表。例如下图中,绿线和红线可以到达相同的目的地,但显然,绿色的路径更短,所以路由表记录了成本更低的绿色路由。路由表记录了较短的路径。现在假设我们将此网状结构中两点的网络IP设置为相同。路由器实际上并不知道这是两台不同的机器。对它来说,这只是两台不同的机器。路径,但都指向相同的IP。不同的路径到达相同的IP地址。这两条路径可以到达同一个IP,这样你打任何一个服务都能得到想要的信息,从而实现anycast。现在再补充一个条件,路由器和其中一台机器都在国内,另一台机器在国外。对于路由器来说,由于国内机器距离近,传输成本低,而国外机器距离远,传输成本高,所以路由器生成的最优路由是到达国内机器。基于这个思路,我们只需要将一个国外的DNS域名服务器信息镜像到国内的机房即可。我们不再需要请求外国服务器。因此,即使其他国家的根域名服务器宕机,也不会对我们造成任何影响。其实国内已经有很多镜像服务器了,非常稳定。让我们稍微扩展一下。假设在上海和广东设置了相同IP的镜像服务,那么对于上海的用户,他们的路由器会优先向上海的镜像服务发送请求。广东地区用户将优先呼叫广东地区的机器,实现就近接入。如果上海的镜像服务宕机,相应上海用户的路由器中的路由表会更新到广东镜像机的路径。上海用户的请求将被发送到广东的镜像服务。从而达到高可用(或容灾)。看来使用anycast不仅可以实现负载均衡,还可以实现高可用,这点和nginx很像。那么,问题来了。既然有了anycast技术,为什么还要用nginx呢?nginx作为一个普通的反向代理服务器,它后面可以连接N台服务器。当客户端要请求后端时,客户端不需要知道是哪个服务器在为其提供服务,只需要在最后得到nginx返回的结果即可。像这样屏蔽特定服务器的代理方式就是所谓的反向代理。正因为反向代理不知道自己后面有哪些服务器,所以可以无限扩展,另外一个挂了也可以补上,从而实现负载均衡和高可用。之前写过一篇《为什么有HTTP协议,还要有websocket协议?》,提到对于网络游戏场景,需要服务端主动向客户端推送数据。由于nginx会在客户端和服务端之间建立一个TCP长连接,客户端收到服务端的消息后,可以沿着这个连接响应服务端。而如果此时不使用nginx,单纯使用anycast,那么在服务端主动推送消息给客户端后,当客户端响应时,并不保证消息会回传给原来的服务端。毕竟“anycast”的意思是,只要你能访问任何服务器。所以anycast无法替代nginx。当然,这两个东西不在一个次元,也不宜拿来比较。我只是举了一个反例来帮助大家理清两者的区别。小结DNS在传输层可以同时使用UDP和TCP协议。当传输的数据量小于512Byte时将使用UDP,否则将使用TCP。虽然根域只有13个IP,但不代表只有13台服务器。准确的说应该是13组服务器,每组服务器共享同一个IP。国内已经有很多镜像服务器了。使用任播技术,只需访问附近的其中一个。如果国内外都有相同IP的目的机器,那么对于路由器来说,到达同一个目的无外乎两条路,一条远一点,一条近一点。根据成本,将更近的路径放入路由表中。Anycast技术虽然在一定程度上也可以实现负载均衡和高可用,但它不是nginx的一个维度,不能替代nginx。参考《Why 13 DNS root servers?》https://miek.nl/2013/november/10/why-13-dns-root-servers/
