找回密码
 会员注册
查看: 16|回复: 0

为什么我们经常遇到curl域名解析超时?

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
65016
发表于 2024-10-9 21:00:28 | 显示全部楼层 |阅读模式
为什么我们经常遇到curl域名解析超时? 为什么我们经常遇到curl域名解析超时? 王元波 贝壳产品技术 贝壳产品技术 “贝壳产品技术公众号”作为贝壳官方产品技术号,致力打造贝壳产品、技术干货分享平台,面向互联网/O2O开发/产品从业者,每周推送优质产品技术文章、技术沙龙活动及招聘信息等。欢迎大家关注我们。 242篇内容 2021年06月11日 12:26 前言在日益成熟的微服务架构中,一个服务经常需要从其他的服务获取数据,或者向其他服务提交数据,因此服务间的相互依赖和RPC调用,变得越来越常见,而HTTP接口调用,是RPC调用中最常见的方式。而随着依赖的服务增多,为了保证服务的稳定性,当依赖方出现问题时能够及时发现,大家通常会为自己的服务添加监控和报警。而在这其中,比较常见的一种报警,就是RPC调用超时了,如下图,我们可能会经常收到类似这样的超时报警通知。问题我们注意到,超时的报警中的错误信息,通常会有三种,Resolving timed out,Connection timed out,以及 Operation timed out。而且在实际运维过程中,我们发现,当机器的负载很高时,超时经常会集中发生,尤其是会大量的出现“Resolving timed out”,这看起来似乎有点奇怪,Resolving timed out,应该是域名解析超时,难道机器负载升高,会影响域名解析的时间吗?分析问题之前在讨论负载与超时的关系之前,先来解释一下,为什么常见的超时报警,会分为三种。由于基于http的rpc请求,大多数都会使用到libcurl库,所以这些错误信息,其实是由libcurl库返回的。那么,就来结合curl的源码看看,这三种超时分别是什么意思。从下面libcurl的源码中(lib/multi.c 中的multi_runsingle方法,是libcurl执行http请求过程中的主要函数),关于超时部分的处理,可以看出,curl是不停的在检查请求的执行状态和时间,当设置好的超时时间已经用完,但是curl的状态还没有到达“已完成”的情况下(下图中1590-1592行), 就会发出超时的报错,而根据当时的状态,如果还在等待域名解析,就会提示“Resolving timed out”,如果在等待连接,就会提示“Connection timed out”,而其他状态下,提示“Operation timed out”。实验接下来,就开始验证机器负载,与域名解析超时之间的关系。我在自己的虚拟机上做了一组实验。首先,用curl命令做http请求,然后打印出请求中,到域名解析结束所用的时间,和整体请求的耗时。然后,启动了20个node空循环的进程,这个时候系统负载上升到了20左右,看看这个域名解析和整体请求时间有没有变化。随后继续将node进程数增加到100个,系统负载超过了90,再看看请求的耗时情况。实验结果如下,符合刚才的预期,随着系统负载的上升,curl请求的耗时明显上升了,尤其是域名解析的时间出现了非常大幅的增加。的确,在高负载的环境下,curl的域名解析耗时更长。寻求优化方案初步确认了问题之后,我们来进一步拓展一下这个实验,加入一些对比项,同时看看有没有相关的解决或者缓解问题的方案吧。最终,我做了如下三个对比数据项:1 在调用curl时,使用host参数,直接跳过域名解析步骤2 设置系统host,让域名解析过程不经过dns服务器3 开启curl的asyn-ares模式,使用异步解析域名前两点比较简单,我来说说第3点,asyn-ares是怎么回事。先来结合下图,看看curl中域名解析的过程。当curl执行到 CURLM_STATE_CONNECT 这个状态时会发起连接请求 Curl_connect, 解析域名的调用逻辑就封装在这个方法里面。libcurl的域名解析默认都是异步的方式,但具体实现有两种:asyn-thread 和 asyn-ares。而curl默认的是使用asyn-thread,asyn-ares需要重编curl库,并且打开USE_ARES这个编译选项,所以我就想对比试验一下两种方式的不同,在本机重新编译了一个打开ares开关版本的curl,并保留系统原先的curl,来做对比。好了,先来看看实验数据吧,我同样是在系统负载基本为0的时候,负载为20左右的时候,以及负载超过90的时候,用上述的三种方式,以及原始的方式,分别做了50次请求,记录了每一种情况下,50个请求的平均的域名解析时间和整体请求的平均耗时。实验结果如下:从实验结果可以看出以下几点:1 随着系统负载上升,所有的方式的耗时都会上升。2 使用系统host对于高负载时,域名解析耗时的影响不大。3 使用host参数,域名解析时间直接为0,但是实际上,连接的时间更长了(我中间有再打印出连接耗时对比,发现它虽然把域名解析时间标记为0,但是连接时长明显比其他情况要长一些),不过整体效果依然最好。4 使用c-ares,的确可以明显降低高负载情况下,域名解析的时间。那么我们就再来看看,是什么造成了asyn-thread 和 asyn-ares这两种解析方式域名解析的耗时的差别。两者说起来都是异步解析域名,前者是curl的默认方式,就是开启了一个线程然后调用系统的域名解析API,而后者是基于C-ares这个库实现的异步域名解析。两者最大的区别在于C-ares没有使用平台的系统API,而是自己实现了一套跨平台的完整DNS解析过程,这样带来的好处是,它可以通过暴露callback方法,让使用者和c-ares可以共享socket端口的状态变更,真正实现了DNS的非阻塞异步解析。结合部分源码来看一下。首先,在curl对于域名解析做初始化的时候,asyn-ares在curl_resolver_init方法中,会给ares_channel设置一个回调sock_state_cb,sock_state_cb方法会在channel的状态发生变化时,根据变化后的可读写状态,决定是否关闭socket对应的描述符。之后,我们在之前的时序图中,可以看到,在等待执行结果的过程中, curl会循环调用Curl_resolver_is_resolved方法检查域名解析的状态。这一步中,由于asyn-ares在之前注册了回调函数,它可以直接通过ares暴露的一些方法直接获取ares的运行状态。在Curl_resolver_is_resolved方法的实现中,就是利用一个waitperform方法,一次性获取了所有委托给ares执行的任务的状态,判断一下超时逻辑,然后通过调用ares_process_fd方法通知ares关闭相关socket的fd(文件描述符)。之后把所有任务状态返回给Curl_resolver_is_resolved方法做下一步的状态判断。这样整个过程中的状态判断,包括终止任务等命令,都直接依赖ares暴露出来的方法来完成。而在asyn-thread的实现中,则是先会通过init_resolve_thread方法,创建一个子线程,专门来处理域名解析。而在Curl_resolver_is_resolved方法中,则需要先通过互斥锁,来同步线程的状态,如果已经执行完成,或者未完成但是已经超时,都需要自己主动销毁线程。这些线程操作在CPU负载较高的时候,就会表现出比ares方式更差的性能。除了libcurl和gevent外,libevent,wireshark,以及node.js都在使用C-ares,也正是这个原因。这个C-ares的作者,其实也就是curl的作者Daniel Stenberg以及他背后,来自瑞典的haxx团队。总结总结一下,经过上面的一系列实验可以看出:1 机器负载高时,对于curl的域名解析性能的确是有很大的影响2 最好的解决方案就是直接使用host,指定ip请求,跳过域名解析,不过这样顶多是一个度过非常时期的临时方案,否则如果能够指定ip的话,谁还会用域名呢?3 打开curl的ares开关,至少可以很大程度缓解这个问题。当然,通过提升机器的性能,减少服务器负载过高的情况,能够更好的从根本上解决这个问题。希望这篇关于机器负载与curl超时的内容能对您有所帮助。祝大家服务越来越稳定。 预览时标签不可点 后端27后端 · 目录#后端上一篇从操作系统的角度理解Goroutine – Go 协程设计系列(1/2)下一篇HyperLogLog原理及Redis实现分析关闭更多小程序广告搜索「undefined」网络结果
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-29 12:57 , Processed in 0.479963 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表