译|getaddrinfo with round robin DNS and happy eyeballs


这不是新闻。这只是一些事实,但似乎仍然有许多人不知道,所以我想帮助记录这些内容,以帮助教育世界。我首先会通过提供完整的背景信息来绕着主题转一转……

轮询基础

轮询 DNS一直以来都是实现粗略且廉价的负载均衡和将访问者分散到多个主机上的方法,当他们尝试使用具有静态内容的单个主机/服务时。通过在 DNS 区域中设置一条 A 记录来解析为多个 IP 地址,客户端将以半随机的方式获得不同的结果,从而在不同时间访问不同服务器:

server  IN  A  192.168.0.1
server  IN  A  10.0.0.1
server  IN  A  127.0.0.1

例如,如果是一个小型开源项目,那么它是一种完美的方式来提供分布式服务,该服务以单一名称出现,但由互联网上的多个分布式独立服务器托管。它也被高端网络服务器使用,例如 www.google.comwww.yahoo.com

主机名解析

如果您是一名老派黑客,如果您从 Stevens 的原著中学习了套接字和 TCP/IP 编程,如果您是在 BSD unix 环境长大,您就会知道可以使用 gethostbyname()等方法来解析主机名。这是一个 POSIX 和单一 UNIX 规范,基本上一直存在。当对给定的循环主机名调用 gethostbyname() 时,该函数返回一个地址数组。该地址列表将以看似随机的顺序排列。如果应用程序只是按照接收到的顺序遍历列表并连接它们,则轮询概念非常有效。

但 gethostbyname 不够好

gethostbyname() 只适用 IPv4,涉及 IPv6 就崩溃了。它必须被更好的东西取代。getaddrinfo () 加入,也是 POSIX(在 RFC 3943定义,并在 RFC 5014再次更新)。支持 IPv6 和更多功能的现代函数。这是世界所需要的闪亮之物!

不是直接替代品

因此,(世界好的部分)将所有调用 gethostbyname() 替换为调用 getaddrinfo() ,现在一切都支持 IPv6,一切都很好?不完全如此。因为其中涉及微妙之处。比如函数返回地址的顺序。2003 年,IETF 人员发布了 RFC 3484,详细说明了 _Internet 协议版本 6 的默认地址选择_,并以此为指导,大多数(全部?)实现现在已改为按该顺序返回地址列表。然后它将成为按“首选”顺序排列的主机​​列表。突然间,应用程序将按照“从 IPv6 升级路径的角度来看很聪明的顺序”,同时遍历 IPv4 和 IPv6 地址,。

getaddrinfo 没有轮询

因此,相比旧的轮询 DNS 的方法:多个地址(无论是 IPv4 或 IPv6 或两者)。随着如何返回地址的新想法,这种负载平衡方式不再有效。现在 getaddrinfo() 每次调用基本上都返回相同的顺序。我在 2005 年注意到这一点,并在 glibc 黑客邮件列表上发布了一个问题:http://www.cygwin.com/ml/libc-alpha/2005-11/msg00028.html正如您所看到的,我的问题被愉快地忽略了,并没有人回应过。顺序似乎主要由上述 RFC 和本地 /etc/gai.conf 文件决定,但如果您的目标是获得良好的轮询,两者都无济于事。其他人注意到了这个缺陷 有些人激烈争辩说这是一件坏事,当然也有相反的人声称这是正确的行为,并且无论如何,像这样做轮询 DNS 一开始就是一个坏主意。对大量常见实用程序的影响很简单,当它们启用 IPv6 时,也会同时禁用循环 DNS

没有合适的方案

由于 getaddrinfo() 现在已经这样工作了近十年,我们可以忘掉“修复”它。。由于 gai.conf 需要本地编辑来提供不同的函数响应,因此它不是答案。但也许更糟糕的是,由于 getaddrinfo() 现在以某种优先顺序返回地址,,因此很难在顶部“粘贴”一个简单洗牌返回结果的层。洗牌需要考虑 IP 版本等因素。而且它将变得特定于应用程序,因此必须一次作用于一个程序。流行的浏览器似乎不太受到 getaddrinfo 的影响。。我的猜测是,因为他们致力于进行异步名称解析,以便名称解析不会阻塞进程,它们采取了不同的方法,因此拥有自己的代码。在 curl 情况下,即使支持IPv6,它也可以使用 c-ares 作为解析器后端构建,并且 c-ares 不提供 getaddrinfo的排序功能,因此在这些情况下,curl 将更像使用 gethostbyname 时那样与轮询 DNS 一起工作。

替代方案

我所知道的所有替代方案的缺点是它们并没有充分利用朴素 DNS。为了避免我提到的问题,您可以调整 DNS 服务器以对不同的用户做出不同的响应。这样,您既可以随机以轮询的方式响应不同的地址,也可以尝试通过 PowerDNS 的 geobackend 功能等使其变得更加智能。当然,我们都知道 A) geoip粗糙且经常错误,B) 现实世界地理位置与网络拓扑并不匹配。

happy eyeballs

在此期间,另一个与连接相关的问题出现了。事实上,IPv6 连接通常作为双栈计算机的第二个选项,而且事实上 IPv6 如今主要出现在双栈中。这可悲地惩罚了 IPv6 的早期采用者(是的,不幸的是,IPv6 仍然必须被视为早期),因为这些服务将比旧的纯 IPv4 服务慢。

对于克服这个问题的方法似乎有一个普遍的共识:happy eyeballs 方法。简而言之,它建议同时尝试两个(或所有)选项,响应最快的获胜并被使用。这就需要同时解析 A 和 AAAA 名称,如果两者都得到响应,就连接到 IPv4 和 IPv6 地址,看看哪一个连接速度最快。

这当然不仅仅是替换一两个函数的问题。要实施这种方法,您需要做一些全新的事情。例如,仅执行 getaddrinfo() + 循环地址并尝试 connect() 根本不起作用。您基本上要么启动两个线程,并在一个线程中执行 IPv4-only 路由,并在另一个线程中执行 IPv6 路由,_或者 _您必须发出非阻塞解析器调用以在同一线程中并行执行 A 和 AAAA 解析,并且当第一个响应到达时,您会触发非阻塞 connect() …

我的观点是,无论如何,在您良好的旧套接字应用程序中引入 Happy Eyeballs 都需要进行一些相当大的改造。这样做很可能还会影响您的应用程序处理轮询 DNS 的方式,因此现在您有机会重新考虑您的选择和代码!

原文: getaddrinfo with round robin DNS and happy eyeballs

本文作者 : cyningsun
本文地址https://www.cyningsun.com/09-07-2023/getaddrinfo-with-round-robin-dns-and-happy-eyeballs-cn.html
版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!