news 2026/9/10 3:13:49

curl/libcurl 的 CURLOPT_DNS_CACHE_TIMEOUT:DNS 缓存生命周期控制与底层实现剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
curl/libcurl 的 CURLOPT_DNS_CACHE_TIMEOUT:DNS 缓存生命周期控制与底层实现剖析

curl/libcurl 的 CURLOPT_DNS_CACHE_TIMEOUT:DNS 缓存生命周期控制与底层实现剖析

【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl

导读

CURLOPT_DNS_CACHE_TIMEOUT是 libcurl 中用于控制主机名解析结果(DNS 缓存条目)在内存中保留时长的核心选项。它直接决定了一次域名解析结果能被多少个连接复用、在多少秒内不被重新查询,是调整连接建立速度、减少 DNS 查询压力、以及规避临时性解析故障的重要旋钮。读完本文,你将掌握该选项的完整语义(默认 60 秒、0禁用、-1永久缓存)、三种取值边界下的行为差异,并能够结合 lib/vdns/dnscache.c 与 lib/setopt.c 的源码理解其修剪、负缓存与容量上限等底层机制,进而在真实业务中做出正确的配置决策。

选项总览:作用、原型与适用协议

CURLOPT_DNS_CACHE_TIMEOUT在官方文档中的定义为 "life-time for DNS cache entries"(DNS 缓存条目的生存时间),其接口原型如下(见 docs/libcurl/opts/CURLOPT_DNS_CACHE_TIMEOUT.md):

#include <curl/curl.h> CURLcode curl_easy_setopt(CURL *handle, CURLOPT_DNS_CACHE_TIMEOUT, long age);
  • 参数类型long,单位为秒,设置的是缓存超时时间。
  • 适用协议:全部(All)。无论走 HTTP、HTTPS、FTP、SMTP 还是其他协议,只要涉及主机名到地址的解析,该选项都生效。
  • 引入版本:Added-in 7.9.3,即从 2001 年发布的 curl 7.9.3 起便存在,属于历史悠久的稳定选项。
  • 默认值:60(秒),见文档DEFAULT一节。

在选项注册表中,它被登记为CURLOT_LONG类型(见 lib/easyoptions.c),即接受一个长整型数值。

三种取值模式:默认、禁用与永久缓存

文档对参数的语义描述非常明确:传入一个 long 值表示缓存超时的秒数,名称解析结果会被保存在内存中并在该秒数内被复用。三种典型取值对应三种截然不同的行为模式:

取值行为适用场景(按文档建议引申)
60(默认)解析结果在内存中保留 60 秒绝大多数常规场景,默认即合理
0完全禁用缓存,每次连接都重新解析域名频繁变更、需要实时感知 DNS 变化的场景;代价是每次连接多一次解析开销
-1缓存条目永久保留,永不因超时被修剪长期运行且域名固定的应用,可显著减少重复解析;但要注意缓存可能长期持有过期地址

值得强调的是,-1只是"不因超时被修剪",并不代表条目永远不会被删除——文档明确指出,当缓存条目数超过 30,000 时,libcurl 无论如何都会执行修剪(该行为自 8.1.0 起加入)。

源码视角:选项如何进入 libcurl 内部

理解该选项的底层语义,需要从参数入口开始追踪。

1. setopt 入口:秒转毫秒与 -1 的特殊处理

在 lib/setopt.c 中,CURLOPT_DNS_CACHE_TIMEOUT的处理逻辑为:

case CURLOPT_DNS_CACHE_TIMEOUT: if(arg != -1) return setopt_set_timeout_sec(&s->dns_cache_timeout_ms, arg); s->dns_cache_timeout_ms = -1; break;

这段代码揭示了两个关键事实:

  • -1是特例:只有精确传入-1才会将内部字段直接置为-1(表示永久缓存),而其他任何非负值都走setopt_set_timeout_sec路径。
  • 内部以毫秒存储setopt_set_timeout_sec(见 lib/setopt.c)会把传入的秒数乘以 1000 转换为毫秒后存入data->set.dns_cache_timeout_ms,并做了溢出保护(当秒数超过TIMEDIFF_T_MAX / 1000时直接钳制为TIMEDIFF_T_MAX)。因此,如果你传入一个负数(如-5)而不是-1setopt_set_timeout_sec会返回CURLE_BAD_FUNCTION_ARGUMENT拒绝该参数——只有-1这个特定值才表示"永久"。

内部存储字段定义于 lib/urldata.h:timediff_t dns_cache_timeout_ms; /* DNS cache timeout (milliseconds) */

2. 默认值的初始化

默认值 60 秒在 lib/url.c 中以毫秒形式初始化:

set->dns_cache_timeout_ms = 60000; /* Timeout every 60 seconds by default */

这与文档中DEFAULT 60的声明完全一致,也再次印证了"选项对外是秒、对内是毫秒"的设计。

3. 缓存的实际存放位置

解析结果并非存在 easy handle 上,而是存放在共享层级结构中。从 lib/vdns/dnscache.c 的dnscache_get()可以看出优先顺序:

static struct Curl_dnscache *dnscache_get(struct Curl_easy *data) { if(data->share &&>void Curl_dnscache_prune(struct Curl_easy *data) { struct Curl_dnscache *dnscache = dnscache_get(data); /* the timeout may be set -1 (forever) */ timediff_t timeout_ms =>if(dns && (data->set.dns_cache_timeout_ms != -1)) { ... if(dnscache_entry_is_stale(&user, dns)) { infof(data, "Hostname in DNS cache was stale, zapped"); dns = NULL; Curl_hash_delete(&dnscache->entries, key.data, key.len); } }

这确保了即使两次修剪之间间隔较长,应用也绝不会使用超龄的缓存地址——命中过期条目会被立即"zap"(删除)并触发一次新的解析。同时注意,-1模式下此检查同样被跳过,缓存条目会一直被信任。

负缓存:失败解析也进缓存

自 curl 8.16.0 起,失败的名称解析也会被写入 DNS 缓存,但只保留设定超时时间的一半;自 8.22.0 起,该行为进一步收紧——只有解析器明确回答"域名不存在"(NXDOMAIN)时才会缓存失败结果,瞬时性或本地解析器故障不会被缓存。这一点在源码中有两处呼应:

  • 负缓存条目的写入入口为Curl_dnscache_add_negative()(见 lib/vdns/dnscache.c);
  • 在过期判定dnscache_entry_is_stale()中,无地址条目(负缓存)的年龄会被加倍计算:if(!dns->addr) age *= 2; /* negative entries age twice as fast */(见 lib/vdns/dnscache.c)。

"年龄翻倍"意味着负缓存条目会以正常条目两倍的速度"老化",配合"只保留一半超时时间"的策略,实际效果就是失败结果比成功结果更快失效,避免一次瞬时故障长时间阻塞后续请求。

为什么不建议随意修改:文档与源码共同给出的警告

文档在 DESCRIPTION 中给出了明确建议:除非绝对必要,否则不要修改该选项。其理由在源码与系统层面都能找到依据:

  1. 大值导致缓存膨胀:如果超时时间内访问了大量不同主机名,缓存条目会显著增多。虽然 30,000 条上限兜底,但在达到上限前,内存占用与哈希遍历开销都会上升。文档特别提醒"使用大值时要小心"。
  2. libc 解析函数不主动重读服务器信息:文档指出,多数 libc 的解析函数(如res_init(3)涉及的底层机制)在被显式通知之前不会重新读取名称服务器配置。即使 DHCP 更新了 DNS 服务器信息,libcurl 可能仍在使用旧服务器——这在表象上很像"DNS 缓存问题",实则是系统解析层的行为,单纯调整本选项无法解决。
  3. TTL 无关性:DNS 记录本身带有 TTL(生存时间)属性,但 libcurl不使用 TTL。文档明确说明该缓存超时完全是一种"推测性"设计——假设一个名字在未来一小段时间内仍解析到相同地址。因此,把超时设得比权威 TTL 更长,也不会自动纠正,只会持有更久的旧地址。

实战示例:控制短生命周期域名的解析时效

官方文档给出的示例(见 docs/libcurl/opts/CURLOPT_DNS_CACHE_TIMEOUT.md)展示了如何在代码中把缓存窗口压到极短:

int main(void) { CURL *curl = curl_easy_init(); if(curl) { CURLcode result; curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/foo.bin"); /* only reuse addresses for a short time */ curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, 2L); result = curl_easy_perform(curl); /* in this second request, the cache is not be used if more than two seconds have passed since the previous name resolve */ result = curl_easy_perform(curl); curl_easy_cleanup(curl); } }

该示例的两个要点:

  • 将超时设为2L秒后,第一次curl_easy_perform()解析的地址只会被复用约 2 秒;
  • 若两次请求间隔超过 2 秒,第二次请求会触发全新的名称解析(源码层面表现为fetch_addr()中该条目被判 stale 并删除)。

这一模式适合域名记录变更频繁、希望客户端尽快感知新地址的场景。注意若curl_easy_init()失败(返回 NULL),示例中直接跳过设置——这也是 libcurl 编程的常规防御写法。

返回值与错误处理

文档的RETURN VALUE一节说明:curl_easy_setopt()返回CURLcode指示成功或失败,CURLE_OK(0)表示一切正常,非零表示发生错误,具体错误码可参考libcurl-errors(3)

从源码看,该选项可能返回的错误包括:

  • CURLE_BAD_FUNCTION_ARGUMENT:传入-1以外的负数(例如-5),由setopt_set_timeout_sec()if(secs < 0)分支触发(见 lib/setopt.c);
  • 其余合法取值(任意非负 long 或-1)均返回CURLE_OK

与相关选项的协同关系

该选项并非孤立存在,官方文档的 See-also 列表(见 docs/libcurl/opts/CURLOPT_DNS_CACHE_TIMEOUT.md 头部元数据)指向了完整的相关能力矩阵:

  • CURLOPT_CONNECTTIMEOUT_MS:连接超时,与 DNS 缓存协同决定"建立连接的总耗时上限";
  • CURLOPT_DNS_SERVERS:指定使用的 DNS 服务器,影响解析结果来源;
  • CURLOPT_DNS_USE_GLOBAL_CACHE:全局共享缓存开关(注意其默认关闭),与本文所述的 multi/share 级缓存机制相关联;
  • CURLOPT_MAXAGE_CONN:连接复用的最大存活时间,控制的是"连接"而非"解析结果"的复用窗口;
  • CURLOPT_RESOLVE:手动指定主机名到地址的映射(可视为对缓存内容的直接注入);
  • CURLMOPT_NETWORK_CHANGED:通知 multi 句柄网络环境已变化,配合Curl_dnscache_clear()(见 lib/vdns/dnscache.c)可主动清空缓存。

实践中常见的组合思路是:对动态环境(移动网络、容器 IP 频繁漂移)将CURLOPT_DNS_CACHE_TIMEOUT调小或配合网络变更通知主动清缓存;对地址长期稳定的内部服务,则可适度放大缓存窗口以降低解析延迟与 DNS 服务器压力。但无论哪种取向,都应遵循文档的忠告——在明确需求的前提下再动这个选项,并在大值场景下持续观察缓存规模。

小结

CURLOPT_DNS_CACHE_TIMEOUT虽是一个单一 long 参数的简单选项,其背后却串联起 libcurl 的名称解析缓存体系的全部关键环节:秒级 API 与毫秒级内部存储的转换、-1永久模式与0禁用模式的特殊分支、multi/share 级的共享缓存、基于时间戳的修剪与惰性过期检查、30,000 条容量上限的强制收敛,以及 8.16.0/8.22.0 之后引入的负缓存半衰期策略。理解了这些源码层面的实现,你就能在"减少 DNS 查询"与"及时感知地址变化"之间做出有依据的权衡,而不是盲目调大或调小这个看似不起眼的超时值。

【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 3:12:47

哪些学校在用华宸AI智评?2026年9月核实的40所高校名单和各校要求!

哪些学校在用华宸AI智评&#xff1f;2026年9月核实的40所高校名单和各校要求&#xff01; 先把标题里的两个问题直接回答了。哪些学校在用&#xff1a;2026年9月我逐条核实到明确要求学生使用华宸AI智评的高校有40所&#xff0c;从中国农业大学、兰州大学、郑州大学这类老牌本…

作者头像 李华
网站建设 2026/9/10 3:12:47

CANN/ge设计文档模板

Introduction 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFlow 前…

作者头像 李华
网站建设 2026/9/10 3:12:39

净收入与GMV相差近200亿:拆解霸王茶姬加盟供应链模式与单店模型

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 3:12:39

APF有源电力滤波器实战:LCL与T型三电平拓扑设计

APF有源电力滤波器这几年在工业现场、数据中心、充电桩集群的项目里出镜率越来越高。很多工程师一上来就抄经典电路&#xff0c;LCL并网电感配两电平桥臂&#xff0c;能跑但总觉得差点意思——开关频率太低谐波压不下去&#xff0c;电感发热严重&#xff0c;IGBT损耗又大。后来…

作者头像 李华
网站建设 2026/9/10 3:11:42

论文的双检标准怎么吃透?一篇讲透查重与AIGC的阈值逻辑

查重与AIGC检测并称双检&#xff0c;已是本科、硕博与期刊投稿常见的定稿门槛。本文先划清边界&#xff1a;不展开送检时点、工具清单与报告核验流程&#xff0c;只把双检标准里的阈值逻辑讲透。全文回答五个问题——两条线各测什么、线由谁划、常见线如何定位、同稿跨平台为何…

作者头像 李华
网站建设 2026/9/10 3:11:17

微软本月补丁紧急发布:AI攻击浪潮将至

随着AI辅助攻击的预期激增&#xff0c;微软本月发布了大量安全补丁。安全专家警告&#xff0c;AI正在降低网络攻击门槛&#xff0c;企业必须加快修补速度。本文编译Ars Technica报道&#xff0c;深入分析本次补丁的严峻性及行业应对策略。 本周&#xff0c;微软发布了2026年9月…

作者头像 李华