当我们在客户端(以浏览器为例)使用域名发送一个HTTP请求的时候,DNS协议会尝试将域名解析为对应的IP。(我们知道每一个台服务器都可以通过ip和端口直接被访问。但是,用户对于由数字组成的ip是很难有记忆点的,所以就出现了域名,域名可以映射到ip。那么域名是如何与ip进行映射的,就需要DNS协议的帮忙
)
1.OS会先检查自己本地的hosts文件是否有这个网址映射关系,如果有,就先调用这个IP地址映射,完成域名解析。
Linux下默认hosts文件位置是/etc/hosts
Windows下默认是c:\windows\system32\drivers\etc\hosts
2.如果hosts里没有这个域名的映射,则查找本地DNS解析器缓存,是否有这个网址映射关系,如果有,直接返回,完成域名解析。
3.如果hosts与本地DNS解析器缓存都没有相应的网址映射关系,会在本地DNS服务器查询,此服务器收到查询时,如果要查询的域名,包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析,此解析具有权威性。
在Linux下,DNS客户机配置文件为/etc/resolv.conf
Linux下的DNS服务器在进行DNS解析的时候,默认的解析超时时间是5秒,默认的重试次数是两次。我们可以看到,当DNS解析慢的时候,我们可能在DNS解析的过程中发费10秒钟,这在有些时候是很致命的。
在我们使用HttpCient等工具请求第三方接口(比如百度的定位接口)的时候,会出现超时非常严重的情况,我们在排查的时候发现我们已经设置各种超时时间,读超时,链接超时等等,但是还是出现了超时很严重的情况,这是为什么呢,因为HttpClient并没有提供一个线程运行总时长的超时时间配置,这导致了DNS解析可能花了10秒才完成,导致了调用时间非常长。那么如何去解决DNS解析失败的问题呢?在使用OKHTTP时,我们可以使用HTTPDNS来解决,这样就会变成IP直连,省去了DNS解析的步骤,但是这样会有一个问题,就是当你变成了IP直连的时候,连接无法复用了,这个时候是有问题的,但是好在OKHTTP在底层帮我们实现了链接复用。而对于HTTPClient,也可以使用HTTPDNS解决DNS解析失败,但是是否会帮我们实现链接复用,还没有测试过。
在Windows下,DNS的配置就不多说了。在“控制面板”-“网络”中配置。
4.如果要查询的域名,不由本地DNS服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析,此解析不具有权威性。
5、如果本地DNS服务器本地区域文件与缓存解析都失效,则根据本地DNS服务器的设置(是否设置转发器)进行查询,如果未用转发模式,本地DNS就把请求发至13台根DNS,根DNS服务器收到请求后会判断这个域名(.com)是谁来授权管理,并会返回一个负责该顶级域名服务器的一个IP。本地DNS服务器收到IP信息后,将会联系负责.com域的这台服务器。这台负责.com域的服务器收到请求后,如果自己无法解析,它就会找一个管理.com域的下一级DNS服务器地址(qq.com)给本地DNS服务器。当本地DNS服务器收到这个地址后,就会找qq.com域服务器,重复上面的动作,进行查询,直至找到
www.qq.com主机。
6、如果用的是转发模式,此DNS服务器就会把请求转发至上一级DNS服务器,由上一级服务器进行解析,上一级服务器如果不能解析,或找根DNS或把转请求转至上上级,以此循环。不管是本地DNS服务器用是是转发,还是根提示,最后都是把结果返回给本地DNS服务器,由此DNS服务器再返回给客户机。
从客户端到本地DNS服务器是属于递归查询,而DNS服务器之间的交互查询就是迭代查询。
而在如今的后端架构中,要做到高可用,服务器都是多个节点的。通过DNS解析出来的IP其实一般都不是服务器的IP,而是反向代理服务器的IP,当前求到达反向代理服务器的时候,反向代理服务器会根据负载均衡策略将请求分发到相应的服务器上。
但是随之有一个问题,如果代理服务器挂掉了,那么就会导致所有的请求都到不了服务器,所以我们要做到代理服务器的高可用。而要做到代理服务器高可用,对于我们常用的代理服务器NG来说,我们可以采用keepalived结合NG来做到高可用。
keepalived分别安装在主机A和备机B上,双方启动以后,主机A就会向局域网内发送arp响应包,该arp响应包的ip地址被设为vip,mac地址被设为macA,所有接收到此报文的电脑就会将这个对应关系写入自己的ARP缓存表中,下次访问vip时,就会根据对应的mac地址访问到主机A当备机B监听到主机A挂了的时候,就会向局域网内发送arp响应包,并将arp响应包的ip地址设为vip,mac地址设为macB,所有接收的此报文的电脑就会将这个对应关系写入自己的ARP缓存表中,下次访问vip时,就会根据对应的mac地址访问到备机B这样就实现了高可用。
从上述分析中,我们可以得知,一个客户端请求要到达服务器,可能会经过层层转发,那么就会有一个问题,我们要怎么去获取用户的真实IP呢?
在业界中,我们会使用X-Forwarded-For这个请求头来帮助我们获取到客户端真实IP.它最开始是由 Squid 这个缓存代理软件引入,用来表示 HTTP 请求端真实 IP。如今它已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用,并被写入RFC 7239(Forwarded HTTP Extension)标准之中。
请求头的格式如下
X-Forwarded-For: client, proxy1, proxy2
1
最开始的ip代表最远端的客户端IP,接下来依次是每层代理的IP。
当服务器要获取客户端真实IP的时候,我们只需要获取X-Forwarded-For的第一个IP即可。
一般代理服务器处理X-Forwarded-For这个请求头都是这样的。
获取上一层传递过来的X-Forwarded-For请求头
通过获取remote_addr获取上一层的真实ip,将上一层的真实ip加入到X-Forwarded-For请求头尾部,然后再将X-Forwarded-For请求体转发给下一层。
但是正因为这样的机制,导致了我们只要在客户端伪造X-Forwarded-For请求头,就会导致服务器拿不到正确的客户端IP。
我们要如何应对伪造X-Forwarded-For请求头的情况呢?
我们知道Remote Address是无法伪造的,这个IP一定是和目标主机完成TCP连接的地址。
因为建立 TCP 连接需要三次握手,如果伪造了源 IP,无法建立 TCP 连接,更不会有后面的 HTTP 请求。在正常的TCP/IP通信中,是可以伪造数据包来源 IP 的,但这会让发送出去的数据包返回到伪造的IP上,无法实现正常的通信。所以我们可以说Remote Address一定是真实的。
因此,我们可以将首层代理将握手 IP 设置为 HTTP 请求头的 X-Real-IP(个人更建议是一个更生僻的请求头) 中向后传递。后面的每一层原样传递下去(有则原样传递,无则设置为握手 IP )