Go语言分布式爬虫调度核心是协程不崩、任务不丢、节点不哑;需区分调度与执行并发,用带缓冲channel、信号量限流、context超时控制、Redis持久化队列、Lua原子状态更新、带负载指标的心跳机制保障高可用。

Go 语言实现分布式爬虫调度,核心不在“分布”,而在「协程如何不崩、任务如何不丢、节点如何不哑」——这三点没理清,光搭个 Master-Worker 框架只是画饼。
goroutine 启动数量不是越多越好,而是要匹配调度器吞吐能力
常见错误是看到 go fetch(url) 就无脑循环启动,结果调度器还没把任务分发完,Worker 就因 goroutine 泄漏或 channel 阻塞卡死。本质是没区分「调度并发」和「执行并发」。
-
taskCh的缓冲区大小必须显式设置,比如make(chan string, 100),否则一旦消费者(Worker)来不及拉取,生产者(Scheduler)就会阻塞 - Worker 内部的 goroutine 数量应受信号量控制,例如用
sem := make(chan struct{}, 10)限制同时活跃的抓取数,避免 DNS 耗尽或连接池打满 - 不要在 goroutine 里直接调用
http.DefaultClient.Do(),它默认无超时;必须用带context.WithTimeout()的 client,否则失败请求会永久挂起 goroutine
任务队列不能只靠内存 channel,必须落地到可重入存储
本地 chan 只能用于单机 Worker 内部调度,跨节点或重启后任务就丢了。真正的分布式调度必须把「待派发」和「进行中」状态外置。
- 用 Redis 的
LPUSH/BRPOP做 FIFO 队列,比自建 TCP 服务更轻量且自带持久化;但要注意BRPOP timeout设置过短会导致空轮询,过长则任务响应延迟升高 - 任务状态需原子更新:用 Lua 脚本封装
GET + SET操作,避免多个 Worker 同时抢同一个任务导致重复执行 - 失败任务不能直接丢弃,应写入 Redis 的
zset并带上 retry_count 和 next_retry_at,由独立的 retry worker 定时扫描
Worker 心跳不是可选功能,而是调度器判断存活的唯一依据
很多实现用简单 HTTP ping,结果网络抖动就误判节点下线,频繁触发任务迁移。心跳必须携带负载指标,且不能依赖单次响应。
立即学习“go语言免费学习笔记(深入)”;
- 心跳接口返回值至少包含
pending_task_count和cpu_load,调度器据此做加权分发,避免把新任务压给已满载的节点 - 心跳间隔设为 5 秒,但连续 3 次超时才标记为离线;超时阈值不能低于网络 RTT 的 2 倍,否则容易误杀
- Worker 启动时必须先注册再拉任务,注册信息存 etcd 或 Consul 的 key-value,路径如
/workers/{host:port},带 TTL 自动过期
真正难的不是写通流程,而是当 200 个 Worker 同时上报心跳、5000 个任务在 Redis 队列里堆积、某个节点突然 GC 卡顿 3 秒时,系统还能不丢任务、不重复、不雪崩——这时候你才会发现,chan 和 goroutine 只是工具,而调度逻辑的幂等性、状态一致性、退火策略,才是骨架。


















