Go高并发数据采集的核心在于合理使用goroutine、channel和context构建流水线,而非框架选择;需用带缓冲channel分发任务、绑定rate.Limiter与context超时、复用HTTP client、无锁收集结果并关闭Response.Body。

Go 框架处理高并发数据采集,核心不在选哪个框架(Gin/Echo/Zero),而在于如何用好 goroutine、channel 和 context 构建可伸缩的采集流水线。盲目堆 worker 数或加中间件反而容易触发 Goroutine 泄漏、连接耗尽或上下文未取消。
用 channel 做任务分发,别用全局 map 或 slice 管理 URL 队列
常见错误是把所有待抓取 URL 存进一个 []string,然后用 for range 启一堆 goroutine —— 这会导致内存无法释放、无法动态增减并发数、失败任务无法重入队。
- 正确做法:用带缓冲的
chan string作为生产者-消费者边界,每个 worker 从 channel 取 URL,失败时可选择重发回 channel(需加重试计数)或丢给 error channel 单独处理 - 注意缓冲区大小:设太小(如
make(chan string, 10))会卡住生产者;设太大(如make(chan string, 100000))等于把内存当队列,失去背压能力 - 不要在循环里反复
close(chan)或新建 channel —— 每次创建都触发调度器,压测时性能断崖下跌
rate.Limiter 必须和 context.WithTimeout 绑定使用
只用 rate.NewLimiter() 控制 QPS,但不配合 ctx,会导致超时请求仍占用 goroutine 和 HTTP 连接,最终堆积阻塞。
- 每次
limiter.Wait(ctx)前,确保ctx已带超时(如context.WithTimeout(parentCtx, 10*time.Second)) - HTTP client 的
Timeout字段只是连接+读写超时,不覆盖rate.Limiter等待时间 —— 两者必须分开控制 - 别用
time.Sleep替代limiter.Wait:它不感知上下文取消,会卡死整个 goroutine
结果收集必须用无锁通道 + 定长结构体,避免 sync.Mutex 竞态
多个 worker 往同一个 map[string]int 写结果?十有八九 panic 或数据错乱。用 mutex 保护又会成为吞吐瓶颈。
立即学习“go语言免费学习笔记(深入)”;
- 定义统一结果结构体:
type CrawlResult struct { URL string; Status int; ContentLength int; Error error },固定内存布局,利于 GC - 结果通道声明为
chan CrawlResult(不是chan *CrawlResult),避免指针逃逸和 GC 扫描压力 - 集中 collector goroutine 负责接收并写入 Redis/DB/文件 —— 它是唯一写入点,天然无锁
- 如果要聚合统计(如状态码分布),用
atomic.Int64替代 map + mutex
HTTP client 必须复用 Transport,且 MaxIdleConnsPerHost 要调大
每次请求 new http.Client?连接池失效,短连接风暴直接打爆目标站或本机端口耗尽。
- 全局复用一个
*http.Client,自定义Transport并设置:MaxIdleConns: 200、MaxIdleConnsPerHost: 100、IdleConnTimeout: 30 * time.Second - 别忽略
Response.Body.Close():不关会导致连接无法归还池中,netstat -an | grep :80 | wc -l会迅速飙到几千 - 对京东这类接口,记得加
User-Agent和随机Referer,否则http.StatusTooManyRequests会来得比限流还快
真正卡住高并发采集的,往往不是 goroutine 数量,而是 HTTP 连接没复用、context 没穿透到底层 I/O、或者结果通道没做容量控制导致内存暴涨。这些点不抠清楚,换再 fancy 的框架也扛不住真实流量。


















