分布式爬虫调度层需控连接生命周期、用无锁队列、两级url去重、节点健康反馈闭环;超时须设connecttimeout_ms与low_speed_limit,dns缓存需手动开启,bloomfilter仅作初筛。

libcurl multi接口必须配超时与连接池管理
直接用 curl_easy_perform 写单请求没问题,但分布式爬虫调度层一旦并发升到几百,不控连接生命周期就会秒变 DNS 阻塞 + TIME_WAIT 爆满。核心不是“能不能发”,而是“发完要不要关、什么时候关、关了还用不用重连”。
实操建议:
-
curl_multi_setopt必须设CURLMOPT_MAXCONNECTS(建议 200–500),否则默认无限建新连接 - 每个
CURL*实例在curl_multi_add_handle前要调curl_easy_setopt(h, CURLOPT_FORBID_REUSE, 1L)—— 不是禁复用,而是强制走连接池逻辑,避免 handle 复用时状态污染 - DNS 缓存必须手动开:
curl_easy_setopt(h, CURLOPT_DNS_CACHE_TIMEOUT, 60L),否则每请求都查一次 DNS,在千万级 URL 场景下延迟毛刺明显 - 别信
CURLOPT_TIMEOUT,它只管整个请求周期;真正要控的是CURLOPT_CONNECTTIMEOUT_MS(建议 ≤ 3000)和CURLOPT_LOW_SPEED_LIMIT(比如 1024 字节/秒,低于就断)
任务队列不能只靠 std::queue + mutex
当调度器每秒分发上万 URL,std::queue 加粗粒度锁会成瓶颈:生产者 push、消费者 pop 都抢同一把锁,CPU cache line false sharing 直接拉低吞吐。这不是理论问题,线上实测 8 核机器下吞吐卡在 1.2 万 QPS 就再也上不去。
实操建议:
- 用无锁队列,如
boost::lockfree::queue或自己封装的 ring buffer,push/pop 都是原子操作,无锁竞争 - 任务结构体必须紧凑:
struct Task { uint64_t url_hash; char url[256]; uint8_t depth; }—— 避免指针跳转,cache 友好;url_hash用于去重前置判断,减少字符串比较 - 批量消费:消费者线程每次从队列取 32 或 64 个任务打包处理,而不是一个一个 pop,降低锁/原子操作频次
- 背压信号必须外显:队列长度超过阈值(如 10 万)时,主动丢弃新任务并打日志,而不是让上游疯狂写入导致 OOM
URL 去重不能只靠 BloomFilter
单纯用布隆过滤器做千万级 URL 去重,漏判率会随容量增长不可控——线上实测 1 亿 URL 后,BloomFilter 误判率超 5%,导致重复抓取和存储浪费。它只能当第一道筛子,不能当唯一依据。
实操建议:
- 两级去重:内存层用
BloomFilter快速拒掉 95%+ 重复请求;落地层用磁盘哈希表(如 LevelDB 或自研 mmaped hash table)存url_hash → timestamp,精确查重 -
url_hash别用std::hash<:string></:string>,它在不同编译器/STL 版本下结果不一致;改用xxh3_64bits或murmur3,保证跨进程、跨重启一致性 - 去重键要归一化:去掉 trailing slash、统一小写、decode URL 编码、忽略 query 参数顺序(如
a=1&b=2和b=2&a=1视为相同) - 过期策略必须有:磁盘哈希表里每个 entry 记
last_seen时间戳,后台线程定期 sweep 超过 7 天未出现的 URL,防磁盘无限膨胀
调度决策必须带节点健康反馈闭环
很多 C++ 分布式爬虫调度器只按轮询或一致性哈希分发任务,却没考虑目标节点实际负载。结果就是:某台 worker CPU 已 95%,还在持续塞新任务;另一台空闲,却因哈希槽位没命中而吃不到活。
实操建议:
- worker 主动上报心跳时,必须附带
cpu_usage、mem_used_mb、pending_task_count三项指标,精度到毫秒级采样 - 调度器端不做复杂预测模型,用简单滑动窗口统计最近 30 秒各节点平均
pending_task_count,选最低的 3 个节点组成候选池,再从池里轮询分发 - 失败任务必须标记原因:
HTTP_503、TIMEOUT、PARSE_ERROR,不同错误类型走不同重试策略(比如HTTP_503降权该节点 30 秒,PARSE_ERROR则直接丢弃) - 所有调度决策日志必须结构化输出,字段含
task_id、target_node、decision_score、timestamp,方便用 Loki + Grafana 做实时调度健康看板
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











