实战案例|“5秒”延迟魔咒

科技   科技   2024-09-05 12:39   上海  


我们遇到的许多系统故障案例,常常会发现一些具有共性的现象,例如,5秒延迟。这在多个客户的不同的业务应用系统中都发生过,然而它们为何会出现如此一致的延迟?本文将深入分析这一现象背后的原因,并探讨如何优化系统配置,避免这种延迟对业务造成影响。






案例回顾


曾有一家银行的三方互联应用,系统管理员发现,每笔交易都精准地延迟了5秒左右,仿佛系统时钟出现了问题。而另有一家银行,收单业务系统同样出现了类似问题,收到诸多客户投诉,称收单的响应速度慢,总是要等待5秒钟,影响了购物体验。与此同时,互联网上很多技术博主也在Kubernetes集群中遇到了相同的5秒延迟问题,困扰不已。






缘何5秒


通过对多个案例的分析,我们发现这些不同场景中的5秒延迟实际上源于相同的触发点——DNS域名解析超时。


在业务请求发出时,首先需要完成的步骤之一是DNS域名解析。而这些5秒延迟的根源可以追溯到Linux系统中的一个设置:DNS解析的默认超时时间设定为5秒,最大可设置30秒。这个设置并非随意,而是基于RFC 1123中的指导原则。


RFC 1123建议,当DNS服务器或解析器在进行UDP查询重试时,应采用指数回退算法,并设定上下限。如果没有其他数据可用,系统默认的重传时间间隔不应少于5秒。这一指导原则直接影响了Linux系统的实现,使得在DNS解析过程中,默认的超时时间正是5秒。


因此,无论是在银行的三方互联、收单系统中,还是在复杂的Kubernetes集群中的应用延迟,这一DNS解析的默认超时时间“5秒”,成为共同的表象。






触发DNS 5秒超时的三个案例


尽管5秒延迟的根源在于DNS解析超时,但不同场景中触发超时的原因有所不同。接下来,我们将分别探讨在银行三方互联应用、银行收单系统,以及Kubernetes集群中的具体情况。


案例一. 三方外联应用查询 DNS Server 无响应


先来看一个简单的案例,应用管理员反馈某访问互联网第三方的业务系统突然变慢,每笔交易大概都有5秒左右延迟。这个案例的DNS网络报文交互过程:



显而易见,客户端在向DNS服务器(219.10.10.10)发出域名解析请求后,没有立即得到响应。经过整整5秒钟的等待,系统才开始向备用DNS服务器(202.15.15.15)发起第二次请求。备用服务器迅速响应并成功解析了域名。然而,这5秒的等待时间,正是造成用户体验延迟的根本原因。



图1:主DNS服务器无响应,触发DNS 5秒超时



要理解这一现象,我们需要了解Linux系统在配置多个DNS名称服务器时的工作机制。通常,系统在以下两种情况下使用第二个DNS服务器:


  • 第一个服务器超时:当主DNS服务器在指定的超时时间内没有响应时,系统会尝试使用备用DNS服务器。Linux系统的这个超时时间通常设定为5秒。这意味着,如果主DNS服务器无法在5秒内做出回应,系统就会转向备用服务器进行解析。
  • 轮询机制:如果在DNS配置文件/etc/resolv.conf中使用了rotate选项,系统会轮流向配置的多个DNS服务器发送请求,即使第一个服务器是可用的,部分请求也可能被分配到第二个服务器,以实现负载均衡。

显然,在这个案例中,系统是因为第一种情况——主DNS服务器超时——而选择了备用DNS服务器。在默认配置下,Linux系统会等待5秒钟后才尝试使用第二个服务器进行解析,而正是这5秒的默认超时时间导致了整个业务请求的延迟。


案例二:收单系统的DNS解析阻塞


在这个案例遇到的问题现象,是银行某个收单系统的NGINX服务调用互联网支付API延迟了5秒钟,NGINX服务器操作系统使用CentOS6。遇事先抓包,我们来看关键的DNS网络报文交互过程:



过程如下:


  • 客户端首先向 DNS 服务器(221.10.10.10)发出了两个查询请求,分别是 IPv4 地址查询(A 记录)和 IPv6 地址查询(AAAA 记录),用于解析域名 api.pay.efgh.com;

  • 服务器迅速响应了 IPv4 地址查询,返回了A记录 IP 地址;

  • 在接下来的过程中,客户端在约 5 秒后再次发起了对同一域名的 IPv4 查询请求,并紧接着再次发起了 IPv6 查询请求;

  • 对比案例一的DNS 交互过程,可以发现一个显著的差异:

    1. 在第一个案例中,客户端在初始 DNS 查询后等待了整整 5 秒才切换到备用 DNS 服务器进行解析,最终得到了响应。

    2. 在第二个案例中,尽管 DNS 服务器很快返回了初始的 IPv4 地址查询结果,客户端却在接下来的 4.995 秒内重复发起了相同的查询请求。


图2:等待AAAA记录,触发DNS 5秒超时


在这个过程中,正常返回的A 记录只有一条,并且是在很短时间内返回的。然而,API 的调用却是在 5 秒后,即 DNS 查询重试之后发出的。这说明 NGINX 系统获取到返回的 A 记录是在 5 秒超时之后。


笔者在网上的文档中,找到了一种与此现象匹配的解释:“CentOS 6 中的 DNS 解析器对于 IPv4 和 IPv6 都使用同一个 socket 接口,在同时发出 IPv4 和 IPv6 解析请求后,只会收到一个 IPv4 的解析响应,此时 socket 将处于‘等待’模式,等待 IPv6 的解析响应,从而导致解析缓慢。”


然而这个解释过于笼统,我们来追根溯源。


首先,glibc 2.9 引入了 DNS Parallel Query 机制,A(IPv4)和 AAAA(IPv6)DNS 查询会被同时发送(见Linux Manual Page)。这种机制旨在通过减少等待顺序响应的时间来加快 DNS 解析过程。然而,这种方法可能会导致某些 DNS 服务器无法正确处理并发查询,从而引发超时或响应丢失。让我们来看一下这个具体过程。


DNS Parallel Query 机制通过 getaddrinfo() 函数实现。


它的一个处理逻辑是,当属性 ai_family 被设置为 AF_UNSPEC 时,getaddrinfo 会尝试解析主机名,并返回与之相关的所有地址,包括 IPv4 和 IPv6 地址。至于是否必须等到 IPv4 和 IPv6 地址都返回,取决于底层实现和网络环境。


  • 如果系统支持并配置了 IPv6:getaddrinfo 会并行查询 IPv4 和 IPv6 地址,并在两者都返回后组合结果。如果查询 IPv6 地址失败,可能会延迟返回结果,直到确认 IPv6 不可用或返回错误。

  • 如果系统不支持 IPv6 或未配置:getaddrinfo 可能只会返回 IPv4 地址,通常会很快返回结果。


大多数情况下,函数会等待所有查询完成后才返回结果,如果某种协议的地址不可用(如IPv6),则只返回可用的地址(如 IPv4)。


而CentOS6,默认启用ipv6,同时glibc的版本是 2.12,已经使用Parallel DNS Queries 机制。


至此,还原第二个案例的整个过程如下:


  • 在这家银行的扫描收单业务交易处理链条上,NGINX 服务器调用互联网 API 服务时,首先发起 DNS 请求解析域名,然后发出 API 调用请求;

  • 在 DNS 解析 API 域名时,触发了“并发机制”,用相同的UDP端口,同时发出 A 记录和 AAAA 记录的查询请求;

  • A 记录很快获得响应,但由于某种原因,AAAA 记录未获得响应;

  • getaddrinfo() 函数工作在 “AF_UNSPEC” 模式下,此时进入等待状态,直到 5 秒超时,才返回 A 记录;

  • NGINX 服务器随后发起 API 调用请求。


至于,为何AAAA(ipv6)记录没有返回,笔者也查到一种解释:DNS解析过程使用相同的源端口同时发送A记录和AAAA记录的查询请求时,防火墙可能会将这两个请求视为同一会话。当A记录的响应先到达时,防火墙可能会认为会话已经完成,从而关闭连接,导致后续的AAAA记录响应被丢弃。


如果你的实际环境中,存在这样的结构,可以在防火墙前后抓包对比的方式进行验证。


案例三:容器环境中的DNS解析竞争


在容器环境中,传统环境中存在的许多问题依然存在,甚至由于环境变得更复杂而被放大。互联网上有大量文章探讨了容器环境中的 DNS 5 秒延迟问题,并对此进行了深入分析。


一篇早期对此问题解读较为深入的博客文章《Racy conntrack and DNS lookup timeouts》 ,由 Weave Works 的工程师 Martynas Pumputis 撰写。他详细解释了在容器环境中DNS Parallel Query如何触发了conntrack的竞态条件,进而触发DNS 5秒超时。


 图3:conntrack 条目创建过程(引用自参考资料1)


而其中涉及的Linux 系统下的 conntrack 机制,在中文博客文章《连接跟踪(conntrack): 原理、应用及 Linux 内核实现》中有详细的底层原理分析。


通过阅读上述文档,结合笔者的理解,在容器环境下 DNS Parallel Query 引发 conntrack 条目冲突的关键过程可以概述如下:


  • 客户端并发请求A(IPv4 地址)和 AAAA(IPv6 地址)记录的DNS报文1和报文2,使用相同的UDP源端口;

  • DNS是无状态的UDP协议,因此在 connect系统调用之后,不会发送任何报文,因此也不会创建conntrack条目。仅当第一个UDP报文发送时,conntrack条目才被创建;

  • 在并发发出DNS报文1和DNS报文2时,netfilter为它们分别创建了处于unconfirm状态的conntrack表项;

  • DNS报文1,在DNS报文2进入 get_unique_tuple 阶段之前,率先进入confirm状态。也就是在conntrack 表中增加了一条confirm状态的UDP四元组(源 IP、目的 IP、源端口、目的端口)记录;

  • DNS报文B,随后进入 get_unique_tuple 阶段时,内核发现它的UDP连接四元组已经被占用,遂丢弃该报文,insert_failed 计数器加1;

  • DNS 5秒超时后重试,如果成功,就是我们观察到的5秒延迟;

  • 容器环境内的DNS 5秒超时类案例互联网已有很多案例,就不展开详细内容了。如果有兴趣,推荐大家阅读本小节开头提到的两篇博客文章,不但解答疑惑,还能深入理解linux系统的底层机制。






结论与建议



以上就是5秒延迟的三个典型场景,5秒延迟只是表象,但究其原因各有不同。且不仅只有三个场景,如果你耐心搜索互联网,会发现有更多触发DNS 5秒超时的不同场景和原因,在不同的系统和环境中普遍存在,其根源在于底层机制和技术在漫长演变过程中与多样化的运行环境之间的冲突。


虽然这些延迟在某些情况下可能是不可避免的,但通过识别原因,优化系统配置,我们可以显著减少这种延迟对业务的影响。


所以,一旦你管理的系统呈现出规律性的5秒延迟,而且处理过程要查询DNS,那么脑子里的小铃铛就要响起来了。


排除DNS环节的问题,抓包分析不失为一个好的方式,通过DNS查询和后续APP调用的网络通信行为,可以直观判断是否为DNS的影响。


如果已确认是DNS 5秒超时,那么可以根据实际场景,考虑以下常用处理方法:

  • 对应DNS服务器端的问题。例如,无响应,可根据日常DNS响应时间,适当调整DNS解析超时阈值:这一超时设置可以通过修改/etc/resolv.conf文件中的timeout选项来调整。例如,将超时时间从默认的5秒缩短到1秒,可以显著减少系统切换到备用DNS服务器的等待时间,从而降低整体的延迟。当然,如果你有日常的DNS监控指标数据可参考,那么可以根据实际的均值、最大值,更精细的设置阈值,以获得更优效果。

  • 如果问题是客户端DNS解析阻塞或触发了竞争机制,可尝试禁用DNS Parallel Query,在/etc/resolv.conf中增加single-request-reopen或者single-request选项:

·single-request-reopen (since glibc 2.9)

·single-request (since glibc 2.10)

    对应效果是:

·single-request-reopen发送A类型请求和AAAA类型请求使用不同的源端口。这样两个请求在conntrack表中不占用同一个表项,从而避免冲突。

·single-request避免并发,改为串行发送A类型和AAAA类型请求。没有了并发,从而也避免了冲突。


以上只是常见处理方法,互联网上还有更多不同场景和对应方法:使用TCP、弃用conntrack专用其它替代、Kubernetes官方的NodeLocal DNSCache等。






互动讨论:你的经验如何?



读到这里,你可能会联想到自己遇到过的类似问题。那么在你的工作中,有遇到过5秒延迟的问题吗?你是如何解决的?欢迎在评论区分享你的经验和看法。


参考资料与阅读推荐


本文分析过程中参考了以下技术文档与博客文章,供读者进一步阅读:

1.Linux 连接跟踪(conntrack)机制,推荐阅读 Arthur Chiao 的:《连接跟踪(conntrack): 原理、应用及 Linux 内核实现》 https://arthurchiao.art/blog/conntrack-design-and-implementation-zh/#36-nf_conntrack_in进入连接跟踪

2.容器环境中的 conntrack 竞争: 

1).英文原文推荐 Weave Works 工程师 Martynas Pumputis 的《Racy conntrack and DNS lookup timeouts》 https://lambda.lt/blog/2018/racy_conntrack.html

2).中文译文及译者实践,推荐汪震(Alex Wong)的《Kubernetes上和DNS相关的问题》 https://blog.gmem.cc/dns-problems-on-k8s

3.Linux 系统 DNS 参数,可查阅《Linux Man Pages》的 resolv.conf 部分:https://man7.org/linux/man-pages/man5/resolv.conf.5.html

4.DNS 技术规范,可查阅 RFC 1123 的 Domain Name Translation 章节:https://datatracker.ietf.org/doc/html/rfc1123#page-72




天旦Netis
上海天旦网络科技发展有限公司是国际领先的业务与网络性能管理领域的软件产品企业,针对关键业务保障、交易分析、大数据采集和挖掘等方面提供专业的产品和解决方案。