libcurl 的 CURLOPT_DNS_SHUFFLE_ADDRESSES打乱 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导读当 curl/libcurl 解析一个主机名并获得多个 IP 地址时默认会按照系统解析器返回的顺序逐个尝试连接这在多 IP多 A/AAAA 记录场景下容易造成流量始终集中在列表前部的服务器上。CURLOPT_DNS_SHUFFLE_ADDRESSES是 libcurl 从 7.60.0 起提供的一个布尔型选项它让 libcurl 在拿到解析结果后随机打乱所有地址的使用顺序从而在每次重新解析时实现连接目标的随机化。本文以 docs/libcurl/opts/CURLOPT_DNS_SHUFFLE_ADDRESSES.md 为骨架结合本仓库lib/vdns/dnscache.c的实现与tests/unit/unit1608.c的单元测试讲解该选项的用法、底层 Fisher-Yates 洗牌实现、与 DNS 缓存选项的配合方式及注意事项。读完本文你将能正确地在自己的 libcurl 应用中启用地址打乱并理解它对连接行为、IPv4/IPv6 优先级和性能的实际影响。选项概览与原型CURLOPT_DNS_SHUFFLE_ADDRESSES通过curl_easy_setopt设置作用于整个 easy handle 的 DNS 解析流程适用于所有协议文档 Protocol 字段为 All。#include curl/curl.h CURLcode curl_easy_setopt(CURL *handle, CURLOPT_DNS_SHUFFLE_ADDRESSES, long onoff);参数类型long取值1L启用0L禁用默认值0禁用引入版本7.60.0协议所有协议错误返回CURLE_OK或在选项不被支持时返回CURLE_UNKNOWN_OPTION在选项表中该符号在 include/curl/curl.h 中定义为CURLOPT(CURLOPT_DNS_SHUFFLE_ADDRESSES, CURLOPTTYPE_LONG, 275)编号为 275属于 LONG 类型选项。功能说明什么时候发生打乱根据原文档启用该选项后当一个主机名被解析且返回了多个IP 地址时libcurl 会打乱所有返回地址的顺序使它们以随机顺序被使用。这与传统gethostbyname函数多数现代平台上已不再使用的历史行为类似。从源码看这个行为发生在 lib/vdns/dnscache.c 的dnsc_entry_assign_addr()函数中——当新的解析结果被写入 DNS 缓存条目之前#ifndef CURL_DISABLE_SHUFFLE_DNS /* shuffle addresses if requested */ if(data-set.dns_shuffle_addresses dns-addr) { CURLcode result dns_shuffle_addr(data, dns-addr); if(result) { /* free without lock, we are the sole owner */ dnscache_entry_free(dns); dns NULL; goto out; } } #else (void)data; #endif需要注意的关键点打乱发生在新的解析结果生成并进入缓存的时机而不是每次从缓存命中时。选项状态存放在data-set.dns_shuffle_addresses中lib/urldata.h 中以位域BIT(dns_shuffle_addresses)声明由 lib/setopt.c 中的case CURLOPT_DNS_SHUFFLE_ADDRESSES分支写入。整个功能受编译宏CURL_DISABLE_SHUFFLE_DNS控制禁用该宏编译时setopt分支、打乱逻辑以及对应的单元测试都会被#ifndef屏蔽。为什么打乱只发生在解析时很重要因为打乱发生在解析结果写入缓存的那一刻之后所有命中缓存的使用都会拿到同一次打乱后的顺序。也就是说第一次解析 example.com → 得到 4 个 IP → 打乱 → 缓存后续连接如果在缓存有效期内都会按这次打乱后的顺序使用因此如果你希望在多次连接之间频繁改变地址使用顺序就必须配合缩短或禁用 DNS 缓存这正是原文档强调的CURLOPT_DNS_CACHE_TIMEOUT的作用详见下文与 DNS 缓存选项的配合一节。底层实现Fisher-Yates 洗牌算法打乱的核心实现在 lib/vdns/dnscache.c 的dns_shuffle_addr()函数中。函数注释说明它通过重新链接链表来打乱Curl_addrinfo结构中地址的顺序并且特意不声明为 static仅为便于单元测试使用unittest 1608。实现分为三步计数并转为数组先用num_addresses()遍历Curl_addrinfo链表统计地址个数若num_addrs 1直接返回单地址无需打乱。随后把链表节点指针拷入一个临时数组nodes[]。Fisher-Yates 洗牌从随机源Curl_rand()获取足够的随机字节填充rnd[]然后从数组末尾向前遍历for(i num_addrs - 1; i 0; i--) { swap_tmp nodes[rnd[i] % (unsigned int)(i 1)]; nodes[rnd[i] % (unsigned int)(i 1)] nodes[i]; nodes[i] swap_tmp; }对每个位置i从[0, i]范围内随机选一个下标并交换保证每个排列等概率出现。随机数取自 libcurl 的随机子系统Curl_rand()失败时返回非 OK 结果。重新链接链表按洗牌后的数组顺序重建ai_next指针链末节点指向NULL并更新*addr指向新的头节点。内存分配失败时返回CURLE_OUT_OF_MEMORY并由调用方dnsc_entry_assign_addr()释放缓存条目后向上传播。单元测试验证仓库在 tests/unit/unit1608.c 中提供了专门的单元测试unittest 1608构造一个 8 节点的Curl_addrinfo静态链表addrs[8]ai_next依次串联通过curl_easy_setopt(easy, CURLOPT_DNS_SHUFFLE_ADDRESSES, 1L)启用选项并断言返回CURLE_OK反复调用dns_shuffle_addr()最多 10 次只要链表头节点发生变化addrhead ! addrs即判定地址被重排若 10 次内始终未变化则测试失败断言信息为addresses are not being reordered。该测试同时验证了选项接口setopt与洗牌算法本身是理解本功能行为的最直接的可运行证据。整个测试同样被CURL_DISABLE_SHUFFLE_DNS宏包裹说明该功能可在编译期整体裁掉。完整使用示例原文档给出的最小示例int main(void) { CURL *curl curl_easy_init(); if(curl) { CURLcode result; curl_easy_setopt(curl, CURLOPT_URL, https://example.com); curl_easy_setopt(curl, CURLOPT_DNS_SHUFFLE_ADDRESSES, 1L); result curl_easy_perform(curl); /* always cleanup */ curl_easy_cleanup(curl); } }一个更贴近实战的组合用法是启用打乱的同时缩短 DNS 缓存让每次请求都有较大概率重新解析并重新打乱地址CURL *curl curl_easy_init(); if(curl) { curl_easy_setopt(curl, CURLOPT_URL, https://example.com); /* 启用地址打乱使多次连接分散到不同 IP */ curl_easy_setopt(curl, CURLOPT_DNS_SHUFFLE_ADDRESSES, 1L); /* 缩短 DNS 缓存时间让下一次 perform 尽快重新解析并重新打乱 */ curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, 2L); curl_easy_perform(curl); /* 两秒后再次 perform将重新解析并得到新的随机地址顺序 */ curl_easy_cleanup(curl); }编译时链接-lcurl即可运行。也可在命令行使用curl --dns-shuffle-addresses仓库命令行工具对应的选项文档见 docs/cmdline-opts/dns-shuffle-addresses.md。与 DNS 缓存选项的配合原文档明确说明如果名称解析是通过 DNS 缓存完成的地址不会被重新打乱。因为缓存命中直接复用已打乱或未打乱的旧结果不会触发新的洗牌。为此文档建议配合CURLOPT_DNS_CACHE_TIMEOUTdocs/libcurl/opts/CURLOPT_DNS_CACHE_TIMEOUT.md使用该选项接受以秒为单位的long值设置解析结果在内存中保留的时长设为0可完全禁用缓存——每次连接都重新解析、重新打乱设为-1则缓存条目永久保留默认值为 60 秒即解析结果默认被缓存一分钟。文档同时提醒不建议随意调整缓存超时过大的值在主机名很多时会让缓存体积显著增长。此外缓存还有一个硬性上限无论超时设置为何值当缓存条目超过30,000条时 libcurl 都会进行剪枝8.1.0 起。从 8.16.0 起失败的解析也会按超时时间的一半存入缓存从 8.22.0 起仅当解析器明确应答名称不存在时才缓存失败结果瞬时或本地解析错误不再缓存。影响与注意事项原文档指出由于返回的地址被随机重排地址顺序不再符合 RFC 3484 或系统解析器产生的任何确定性排序。这带来两方面影响IPv4/IPv6 优先级不再受控可能造成 IPv4 在 IPv6 之前被使用或反之。如果你的应用对地址族顺序有硬性要求例如必须优先 IPv6应改用CURLOPT_IPRESOLVEdocs/libcurl/opts/CURLOPT_IPRESOLVE.md显式限定CURL_IPRESOLVE_V4、CURL_IPRESOLVE_V6或CURL_IPRESOLVE_WHATEVER而不是依赖打乱后的随机顺序。可能的性能影响系统解析器返回的顺序通常是经过策略优化如就近原则、权重的随机化可能使连接落到更远或质量较差的地址上。在负载均衡类场景中这通常可以用随机化带来的分散收益抵消但对于单主机连接建议评估后再启用。适用场景小结场景建议多 IP 主机多个 A/AAAA 记录希望连接流量分散到各地址启用本选项并适当缩短/禁用 DNS 缓存以频繁重打乱对 IPv4/IPv6 使用顺序有硬性要求不要依赖随机顺序改用CURLOPT_IPRESOLVE显式指定依赖解析器返回顺序做最优路由保持默认禁用因为随机化可能带来性能损失编译期希望裁剪该功能定义CURL_DISABLE_SHUFFLE_DNS对应代码路径与单元测试都会被移除参考与延伸阅读选项官方文档docs/libcurl/opts/CURLOPT_DNS_SHUFFLE_ADDRESSES.md洗牌实现与调用点lib/vdns/dnscache.c选项解析lib/setopt.c、选项定义 include/curl/curl.h状态存储lib/urldata.h单元测试tests/unit/unit1608.c关联选项DNS 缓存超时 CURLOPT_DNS_CACHE_TIMEOUT、地址族选择 CURLOPT_IPRESOLVE【免费下载链接】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),仅供参考
