.NET Framework 是否有 OS-independent 全局 DNS 缓存?
Does .NET Framework have an OS-independent global DNS cache?
简介
首先,我已经尝试了 C# DNS-related SO 线程和其他互联网文章的所有建议 - 搞乱 ServicePointManager/ServicePoint 设置,通过 HTTP 设置自动请求连接关闭 headers,更改连接租用时间 - 没有任何帮助。似乎所有这些设置都是为了修复 long-running 进程(如网络服务)中的 DNS 问题。如果一个进程拥有自己的 DNS 缓存以最小化 DNS 查询或 OS DNS 缓存读取,这甚至是有意义的。但我不是这样。
问题
我们的生产基础设施使用 HA(高可用性)DNS 在维护或功能问题期间交换服务器节点。它的构建方式使得在某些地方我们有多个 CNAME-records 实际上指向同一个 HA A-record 就像这样:
- eu.site1.myprodserver.com (CNAME) > eu.ha.myprodserver.com (A)
- eu.site2.myprodserver.com (CNAME) > eu.ha.myprodserver.com (A)
所有这些记录的 TTL 都是 60 秒。因此,当欧洲节点出现故障或维护时,A-record 切换到其他节点的 IP 地址。
然后我们有一个监控实用程序,它每 5 分钟执行一次,同时使用 site1 和 site2。为了使其正常工作,两个名称必须指向同一个 DC,因为 DC 之间的数据同步不会那么快发生。由于这两个 CNAME 实际上都链接到同一个 A-record 且 TTL 很短,乍一看似乎不会出错。但事实证明可以。
该实用程序是用 C# 为 .NET Framework 4.7.2 编写的,并使用 HttpClient class for performing requests to both sites. Yeah, it's him 。
我们注意到,当发生服务器节点切换时,该实用程序通常开始运行,就好像 site1 和 site2 位于不同的 DC 中一样。它在这种时刻的行为模式是严格确定的,所以它不会在过程中间的某个地方感到困惑——它从一开始就错误地解析了一个或两个地址。
我制作了另一个更简单的实用程序,它只发送一个 GET-request 到 site1,然后开始有意地打开和关闭节点,并 运行 启用此实用程序以查看哪个 DC 将满足其请求.结果非常令人沮丧。
尽管 Windows DNS 缓存已经被更新(通过 ipconfig
和 Get-DnsClientCache
cmdlet 检查)并且尽管总记录的 TTL 为 60 秒,但 HttpClient 继续向旧 IP 地址有时会再保留 15-20 分钟。即使我已经完全关闭“过时的”应用程序服务器 - 该实用程序仍在尝试连接它,所以即使连接失败也不会唤醒它。
如果您在实用程序 运行 之间启动 运行 宁 ipconfig /flushdns
,它会变得更加令人沮丧。有时在 flushdns 之后实用程序意识到 IP 已经改变。但是一旦你创建另一个 flushdns(或者这甚至不需要 - 我还没有 100% 清楚地弄清楚这一点)并再次 运行 实用程序 - 它会回到旧地址!难以置信!
并增加更多的挫败感。如果您在调用 HttpClient 之前使用 Dns.GetHostEntry method (which uses cache as per this comment) 从同一个实用程序中解析 IP 地址,则解析结果将是正确的......但是 HttpClient 无论如何都会连接到似乎是他自己独立选择的 IP 地址.所以 HttpClient 似乎不依赖于 built-in .NET Framework DNS 解析。
所以问题是:
- 新创建的 .NET Framework 进程从哪里获取那些缓存的 DNS 结果?
- 即使有某种神秘的全局 .NET-specific DNS 缓存,那为什么它绝对忽略 TTL?
- 在它已经“了解”地址已更改后,它怎么可能恢复到过时的旧 IP 地址?
P.S。我通过实现自定义 HttpClientHandler which performs DNS queries on each hostname's first usage thus it's independent from external DNS caches (except for caching at intermediate DNS servers which also affects things to some extent). But that was a little tricky in terms of TLS certificates validation and the final solution does not seem to be production ready - but we use it for monitoring only so for us it's OK. If anyone is interested in this, I'll show the class code which somewhat resembles .
来解决这个问题
更新 2021-10-08
该实用程序在公司代理后面运行。实际上有多个代理用于负载平衡。所以我现在也在验证这个:
- 如果 DNS 解析由代理执行并且它们不遵守 TTL,或者如果它们通过主机名缓存(保持活动状态)TCP 连接 - 这将解释整个问题
- 如果有可能不同的代理在不同的 运行 实用程序上处理 HTTP 请求 - 这将回答最令人沮丧的问题 #3
2021-10-15更新
“.NET Framework 是否具有 OS-independent 全局 DNS 缓存?”的答案否。 HttpClient class 或 .NET Framework 通常与所有这些无关。将我的调查结果发布为已接受的答案。
HttpClient,请见谅!这不是你的错!
好吧,这项调查 庞大 。而且我必须将答案分成两部分,因为结果是两个不相关的问题。
1。代理服务器问题
正如我所说,该实用程序正在公司代理后面进行测试。如果您在使用代理服务器时不知道(就像我直到最近几天才知道),那不是您的机器在执行 DNS 查询 - 这是代理服务器为您做的。
我进行了一些测量,以了解实用程序在 DNS 记录切换后保持连接到错误 DC 的时间。答案是 非常精确的 30 分钟 。这个实验也清楚地表明本地 Windows DNS 缓存与它无关:这 30 分钟恰好在代理服务器 唤醒 的时间点开始(是终于开始向正确的 DC 发送 HTTP 请求了。
30 分钟的确切数字帮助我们的一位管理员最终弄清楚代理服务器具有最小 DNS TTL 配置参数,默认设置为 1800 秒。所以代理有自己的 DNS 缓存。这些是硬件 Cisco 代理,管理员还注意到此参数“隐藏得很深”,甚至在用户手册中都没有提及。
一旦最小代理的 DNS TTL 从 1800 秒更改为 1 秒(是的,管理员毫不留情),问题就不再在我的机器上重现。
但是如果“忘记”刚刚理解的正确 IP 地址并回退到旧 IP 地址呢?
嗯。正如我也说过的,有几个代理。有一个公司代理 DNS 名称,但如果您为它 运行 nslookup
- 它会显示其背后的多个 IP。每次解析代理服务器的 IP 地址时(例如,当本地缓存过期时)- 您很有可能会跳到另一个代理服务器上。
这正是 ipconfig /flushdns
对我所做的。当我开始使用代理服务器使用它们的直接 IP 地址而不是它们的通用 DNS 名称时,我发现不同的代理可以轻松地将相同的请求路由到不同的 DC。那是因为其中一些具有 30 分钟缓存的 DNS 记录,而其他则必须执行解析。
不幸的是,在代理理论被证明之后,又传来了一个消息:生产监控服务器被放置在公司网络之外,他们没有使用任何代理服务器。那么我们开始吧...
2。短 TTL 和 public DNS 服务器问题
监控服务器配置为使用 8.8.8.8 和 8.8.4.4 Google 的 DNS 服务器。解析来自这些服务器的短暂 DNS 记录的响应有些奇怪:
- CNAME 记录的 returned TTL 在 1 小时左右摆动。它会逐渐减少几分钟,然后跳回 3600 秒 - 依此类推。
- 根 A 记录的 returned TTL 几乎总是恰好 60 秒。我偶尔会收到小于 60 的各种数字,但没有任何明显的人类可感知的逻辑。因此,这些 IP 地址似乎实际上指向平衡器,这些平衡器在多个彼此不同步的相似 DNS 服务器之间分发请求(并且每个服务器都有自己的缓存)。
Windows 并不愚蠢,根据我的实验,它不关心 CNAME 的 TTL,只关心根 A 记录 TTL,因此即使是 CNAME 记录的客户端缓存也从未分配 TTL高于 60 秒。
但是由于 Google 的服务器 return(不可预测的 0-60 秒)的 A 记录 TTL 的不一致(或者在某种意义上过度一致?) Windows 本地缓存变得混乱。有两个事实证明了这一点:
- 在几分钟内多次调用 site1 和 site2 的
Resolve-DnsName
并在它们之间随机暂停最终导致 Get-ClientDnsCache
显示两个站点名称的本地缓存 TTL 在 最多 15 秒。这是一个足够大的差异,有时会把事情搞砸。这只是我的简短实验,所以我很确定它实际上可能会变大。
- 每 3-5 秒对每个站点一个接一个地执行
Invoke-WebRequest
,同时切换 DNS 记录,这让我两次遇到请求转到不同 DC 的情况。
The latter experiment had one strange detail I can't explain. Calling Get-DnsClientCache
after Invoke-WebRequest
shows no records appear in the local cache for the just-requested site names. But anyway the problem clearly has been reproduced.
结论?
我的实时 DNS 解析解决方法是否会带来任何改进还需要时间。不幸的是,我不相信它会 - 生产中使用的 DNS 服务器(最终将被监视实用程序用于实时 IP 解析)是 public Google DNS,这在我的情况。
比间歇性失败的监控实用程序更糟糕的一件事是,现实世界的用户也依赖 public DNS 服务器,他们在我们的维护工作或重大故障期间肯定会遇到问题。
那么我们从这一切中学到了什么吗?
- 也许短的 DNS TTL 通常是一种不好的做法?
- 也许我们应该安装额外的路由器,为它们分配静态 IP,将 DNS 名称附加到它们,然后在我们的 DC 之间在内部路由流量以最终停止依赖 DNS 记录更改?
- 或者 public DNS 服务器做得不好?
- 或许技术奇点比我们想象的更近?
我不知道。但很可能“是”是所有这些问题的正确答案。
但是我们确实学到了一件事:网络硬件制造商应该更好地编写他们的文档。
简介
首先,我已经尝试了 C# DNS-related SO 线程和其他互联网文章的所有建议 - 搞乱 ServicePointManager/ServicePoint 设置,通过 HTTP 设置自动请求连接关闭 headers,更改连接租用时间 - 没有任何帮助。似乎所有这些设置都是为了修复 long-running 进程(如网络服务)中的 DNS 问题。如果一个进程拥有自己的 DNS 缓存以最小化 DNS 查询或 OS DNS 缓存读取,这甚至是有意义的。但我不是这样。
问题
我们的生产基础设施使用 HA(高可用性)DNS 在维护或功能问题期间交换服务器节点。它的构建方式使得在某些地方我们有多个 CNAME-records 实际上指向同一个 HA A-record 就像这样:
- eu.site1.myprodserver.com (CNAME) > eu.ha.myprodserver.com (A)
- eu.site2.myprodserver.com (CNAME) > eu.ha.myprodserver.com (A)
所有这些记录的 TTL 都是 60 秒。因此,当欧洲节点出现故障或维护时,A-record 切换到其他节点的 IP 地址。
然后我们有一个监控实用程序,它每 5 分钟执行一次,同时使用 site1 和 site2。为了使其正常工作,两个名称必须指向同一个 DC,因为 DC 之间的数据同步不会那么快发生。由于这两个 CNAME 实际上都链接到同一个 A-record 且 TTL 很短,乍一看似乎不会出错。但事实证明可以。
该实用程序是用 C# 为 .NET Framework 4.7.2 编写的,并使用 HttpClient class for performing requests to both sites. Yeah, it's him
我们注意到,当发生服务器节点切换时,该实用程序通常开始运行,就好像 site1 和 site2 位于不同的 DC 中一样。它在这种时刻的行为模式是严格确定的,所以它不会在过程中间的某个地方感到困惑——它从一开始就错误地解析了一个或两个地址。
我制作了另一个更简单的实用程序,它只发送一个 GET-request 到 site1,然后开始有意地打开和关闭节点,并 运行 启用此实用程序以查看哪个 DC 将满足其请求.结果非常令人沮丧。
尽管 Windows DNS 缓存已经被更新(通过 ipconfig
和 Get-DnsClientCache
cmdlet 检查)并且尽管总记录的 TTL 为 60 秒,但 HttpClient 继续向旧 IP 地址有时会再保留 15-20 分钟。即使我已经完全关闭“过时的”应用程序服务器 - 该实用程序仍在尝试连接它,所以即使连接失败也不会唤醒它。
如果您在实用程序 运行 之间启动 运行 宁 ipconfig /flushdns
,它会变得更加令人沮丧。有时在 flushdns 之后实用程序意识到 IP 已经改变。但是一旦你创建另一个 flushdns(或者这甚至不需要 - 我还没有 100% 清楚地弄清楚这一点)并再次 运行 实用程序 - 它会回到旧地址!难以置信!
并增加更多的挫败感。如果您在调用 HttpClient 之前使用 Dns.GetHostEntry method (which uses cache as per this comment) 从同一个实用程序中解析 IP 地址,则解析结果将是正确的......但是 HttpClient 无论如何都会连接到似乎是他自己独立选择的 IP 地址.所以 HttpClient 似乎不依赖于 built-in .NET Framework DNS 解析。
所以问题是:
- 新创建的 .NET Framework 进程从哪里获取那些缓存的 DNS 结果?
- 即使有某种神秘的全局 .NET-specific DNS 缓存,那为什么它绝对忽略 TTL?
- 在它已经“了解”地址已更改后,它怎么可能恢复到过时的旧 IP 地址?
P.S。我通过实现自定义 HttpClientHandler which performs DNS queries on each hostname's first usage thus it's independent from external DNS caches (except for caching at intermediate DNS servers which also affects things to some extent). But that was a little tricky in terms of TLS certificates validation and the final solution does not seem to be production ready - but we use it for monitoring only so for us it's OK. If anyone is interested in this, I'll show the class code which somewhat resembles
更新 2021-10-08
该实用程序在公司代理后面运行。实际上有多个代理用于负载平衡。所以我现在也在验证这个:
- 如果 DNS 解析由代理执行并且它们不遵守 TTL,或者如果它们通过主机名缓存(保持活动状态)TCP 连接 - 这将解释整个问题
- 如果有可能不同的代理在不同的 运行 实用程序上处理 HTTP 请求 - 这将回答最令人沮丧的问题 #3
2021-10-15更新
“.NET Framework 是否具有 OS-independent 全局 DNS 缓存?”的答案否。 HttpClient class 或 .NET Framework 通常与所有这些无关。将我的调查结果发布为已接受的答案。
HttpClient,请见谅!这不是你的错!
好吧,这项调查 庞大 。而且我必须将答案分成两部分,因为结果是两个不相关的问题。
1。代理服务器问题
正如我所说,该实用程序正在公司代理后面进行测试。如果您在使用代理服务器时不知道(就像我直到最近几天才知道),那不是您的机器在执行 DNS 查询 - 这是代理服务器为您做的。
我进行了一些测量,以了解实用程序在 DNS 记录切换后保持连接到错误 DC 的时间。答案是 非常精确的 30 分钟 。这个实验也清楚地表明本地 Windows DNS 缓存与它无关:这 30 分钟恰好在代理服务器 唤醒 的时间点开始(是终于开始向正确的 DC 发送 HTTP 请求了。
30 分钟的确切数字帮助我们的一位管理员最终弄清楚代理服务器具有最小 DNS TTL 配置参数,默认设置为 1800 秒。所以代理有自己的 DNS 缓存。这些是硬件 Cisco 代理,管理员还注意到此参数“隐藏得很深”,甚至在用户手册中都没有提及。
一旦最小代理的 DNS TTL 从 1800 秒更改为 1 秒(是的,管理员毫不留情),问题就不再在我的机器上重现。
但是如果“忘记”刚刚理解的正确 IP 地址并回退到旧 IP 地址呢?
嗯。正如我也说过的,有几个代理。有一个公司代理 DNS 名称,但如果您为它 运行 nslookup
- 它会显示其背后的多个 IP。每次解析代理服务器的 IP 地址时(例如,当本地缓存过期时)- 您很有可能会跳到另一个代理服务器上。
这正是 ipconfig /flushdns
对我所做的。当我开始使用代理服务器使用它们的直接 IP 地址而不是它们的通用 DNS 名称时,我发现不同的代理可以轻松地将相同的请求路由到不同的 DC。那是因为其中一些具有 30 分钟缓存的 DNS 记录,而其他则必须执行解析。
不幸的是,在代理理论被证明之后,又传来了一个消息:生产监控服务器被放置在公司网络之外,他们没有使用任何代理服务器。那么我们开始吧...
2。短 TTL 和 public DNS 服务器问题
监控服务器配置为使用 8.8.8.8 和 8.8.4.4 Google 的 DNS 服务器。解析来自这些服务器的短暂 DNS 记录的响应有些奇怪:
- CNAME 记录的 returned TTL 在 1 小时左右摆动。它会逐渐减少几分钟,然后跳回 3600 秒 - 依此类推。
- 根 A 记录的 returned TTL 几乎总是恰好 60 秒。我偶尔会收到小于 60 的各种数字,但没有任何明显的人类可感知的逻辑。因此,这些 IP 地址似乎实际上指向平衡器,这些平衡器在多个彼此不同步的相似 DNS 服务器之间分发请求(并且每个服务器都有自己的缓存)。
Windows 并不愚蠢,根据我的实验,它不关心 CNAME 的 TTL,只关心根 A 记录 TTL,因此即使是 CNAME 记录的客户端缓存也从未分配 TTL高于 60 秒。
但是由于 Google 的服务器 return(不可预测的 0-60 秒)的 A 记录 TTL 的不一致(或者在某种意义上过度一致?) Windows 本地缓存变得混乱。有两个事实证明了这一点:
- 在几分钟内多次调用 site1 和 site2 的
Resolve-DnsName
并在它们之间随机暂停最终导致Get-ClientDnsCache
显示两个站点名称的本地缓存 TTL 在 最多 15 秒。这是一个足够大的差异,有时会把事情搞砸。这只是我的简短实验,所以我很确定它实际上可能会变大。 - 每 3-5 秒对每个站点一个接一个地执行
Invoke-WebRequest
,同时切换 DNS 记录,这让我两次遇到请求转到不同 DC 的情况。
The latter experiment had one strange detail I can't explain. Calling
Get-DnsClientCache
afterInvoke-WebRequest
shows no records appear in the local cache for the just-requested site names. But anyway the problem clearly has been reproduced.
结论?
我的实时 DNS 解析解决方法是否会带来任何改进还需要时间。不幸的是,我不相信它会 - 生产中使用的 DNS 服务器(最终将被监视实用程序用于实时 IP 解析)是 public Google DNS,这在我的情况。
比间歇性失败的监控实用程序更糟糕的一件事是,现实世界的用户也依赖 public DNS 服务器,他们在我们的维护工作或重大故障期间肯定会遇到问题。
那么我们从这一切中学到了什么吗?
- 也许短的 DNS TTL 通常是一种不好的做法?
- 也许我们应该安装额外的路由器,为它们分配静态 IP,将 DNS 名称附加到它们,然后在我们的 DC 之间在内部路由流量以最终停止依赖 DNS 记录更改?
- 或者 public DNS 服务器做得不好?
- 或许技术奇点比我们想象的更近?
我不知道。但很可能“是”是所有这些问题的正确答案。
但是我们确实学到了一件事:网络硬件制造商应该更好地编写他们的文档。