首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >高并发数据采集下的代理IP架构:从单机到集群,哪一步该加什么

高并发数据采集下的代理IP架构:从单机到集群,哪一步该加什么

原创
作者头像
阿秋数据采集
发布2026-08-06 09:31:56
发布2026-08-06 09:31:56
50
举报

不要一上来就搭集群

我见过不少团队的做法:任务还没跑起来,先花两周搭了一套 Redis 集群 + 消息队列 + 独立调度服务 + 监控大盘的代理架构。结果日均请求量只有几千——架构的运维成本比采集本身还高。

反过来也有团队一直用单机脚本跑,日均请求量已经上了十万、成功率掉到 60%,才开始想"是不是该换架构了"——这时候积累的技术债务已经很重,改造成本远高于从合适的时机做增量演进。

合理的做法是:按请求量级和业务复杂度分阶段演进,每个阶段只解决当前瓶颈,不提前过度设计。

阶段一:单机单脚本(日均 < 5 万请求)

典型场景: 一个 Python 脚本 + requests 库 + 一个代理 IP 列表,跑一个采集任务,目标站 1-2 个。

代理架构: 最简单的形态——代理列表存在本地文件或配置里,脚本启动时加载,按轮询或随机方式使用。

代码语言:javascript
复制
 import random
 import requests
 ​
 proxies_list = [
     "http://ip1:port1",
     "http://ip2:port2",
     # ...
 ]
 ​
 def get_with_proxy(url):
     proxy = random.choice(proxies_list)
     return requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=10)

这个阶段的核心问题:

根本没有 IP 健康管理。某个 IP 挂了,你不知道——它继续被选中,继续失败,拉低整体成功率。

没有失败反馈。脚本里的错误处理通常是 try/except 然后 continue,不会把失败信息反馈给代理选择逻辑。

什么时候该演进: 当你发现成功率不稳定、需要频繁手动删除失效 IP、或者开始有第二个采集任务需要共用代理时。

演进方向: 加一个最简单的代理管理层——不需要 Redis,不需要独立服务,在脚本内部加一个带评分的代理选择器就够了。

代码语言:javascript
复制
 class SimpleProxyPool:
     def __init__(self, proxies):
         self.proxies = {p: 100.0 for p in proxies}  # ip: score
 ​
     def get(self):
         # 按评分加权随机
         total = sum(self.proxies.values())
         if total == 0:
             return None
         r = random.uniform(0, total)
         cumulative = 0
         for proxy, score in self.proxies.items():
             cumulative += score
             if r <= cumulative:
                 return proxy
         return list(self.proxies.keys())[0]
 ​
     def feedback(self, proxy, success):
         if proxy not in self.proxies:
             return
         if success:
             self.proxies[proxy] = min(self.proxies[proxy] + 2, 100)
         else:
             self.proxies[proxy] *= 0.7
             if self.proxies[proxy] < 5:
                 del self.proxies[proxy]  # 淘汰

这个改动只需要几十行代码,但效果显著:坏 IP 会被自动降权和淘汰,好 IP 会被更多地使用。

阶段二:单机 + Redis 代理池(日均 5-30 万请求)

典型场景: 1-3 个采集任务并行跑,可能用了 Scrapy 或 asyncio,单机并发 20-50。代理 IP 数量从几十个增长到几百个。

演进触发条件:

多个采集任务需要共享代理池——脚本 A 和脚本 B 同时跑,如果各自维护独立的代理列表,IP 使用状态不一致。

代理 IP 从上游接口动态获取——不再是固定列表,需要定期刷新,新 IP 要验证后再入池。

开始需要按目标站隔离代理(脚本 A 打目标站 X、脚本 B 打目标站 Y,不想互相干扰)。

代理架构: Redis 成为代理池的中心。Sorted Set 存 IP 和评分,Hash 存元数据,Set 存黑名单。所有采集脚本连同一个 Redis 实例。

这个阶段要做的关键改造:

把代理池从脚本内存搬到 Redis。 所有脚本共享同一个 Redis 代理池,IP 的评分、使用记录、黑名单都在 Redis 里——任何一个脚本发现某个 IP 失效,其他脚本立刻能看到。

加一个独立的健康检测进程。 不要让每个采集脚本自己做健康检测(会重复检测、浪费资源、产生竞态)。单独跑一个进程,每 5-10 分钟扫描 Redis 里所有 IP,更新评分,淘汰坏 IP。

加水位监控。 一个简单的 cron 任务,每分钟检查 Redis 里 ZCARD 的值,低于阈值时触发 IP 补充(调用上游接口获取新 IP、走入池验证流程)。

这个阶段的典型问题:

单机并发上限。Python 的 GIL 限制了 CPU 密集型任务的并发能力。如果你的采集逻辑包含大量 HTML 解析,单进程的并发瓶颈在 30-50 左右。解决方案是用多进程(multiprocessing)或改用 asyncio + aiohttp。但这还是单机范畴。

Redis 单点故障。这个阶段 Redis 通常是单实例,挂了整个代理池不可用。如果业务允许短暂中断(几分钟),Redis 重启后重新跑一次 Fetch + 验证就能恢复。如果不允许中断,加 Redis Sentinel 做主从自动切换。

什么时候该演进: 当单机的 CPU、内存、出口带宽中任何一个成为瓶颈,或者你需要从多个不同地域的出口发请求时。

阶段三:多机 Worker + 中心调度(日均 30-100 万请求)

典型场景: 3-10 台机器组成 Worker 集群,共享 Redis 代理池和请求队列。可能已经用了 Scrapy-Redis 或自建的分布式任务调度。

演进触发条件:

单机出口带宽不够——一台机器的千兆网卡跑满了也只有 ~120MB/s 的下载能力。

需要多地域出口——比如采集任务要求从不同城市或国家发请求,单机只能从一个出口发。

单机故障影响全局——一台机器挂了,所有采集任务停。

代理架构的关键变化:

代理调度要加锁。 多个 Worker 同时从 Redis 代理池取 IP,需要防止同一个 IP 被多个 Worker 同时用在同一个目标站上。用 Redis 的 SET NX EX 做分布式锁,锁的粒度是 ip + domain

代理评分要聚合。 Worker A 报告某个 IP 成功,Worker B 报告同一个 IP 失败——评分更新不能各自为政。两种方案:一是所有 Worker 直接写 Redis(用 ZINCRBY 原子操作,天然支持并发),二是让 Worker 把反馈发到消息队列,由一个独立的评分聚合服务统一更新。方案一简单够用,方案二在 Worker 数量超过 20 台时更可控。

健康检测要防重复。 阶段二的独立检测进程在这里还是跑一个就够——它扫描 Redis 里所有 IP 做检测,和 Worker 数量无关。但要注意:如果你部署了多个检测进程副本(为了高可用),需要用分布式锁保证同一时间只有一个在跑。

代码语言:javascript
复制
 def run_health_check_with_lock(rdb, pool_key):
     lock = rdb.lock("health_check_lock", timeout=600, blocking_timeout=5)
     if lock.acquire(blocking=False):
         try:
             do_health_check(rdb, pool_key)
         finally:
             lock.release()
     else:
         print("Another checker is running, skip.")

这个阶段的典型问题:

Worker 之间的请求分配不均。如果用 Scrapy-Redis,请求分配是"谁先 POP 谁拿",快的 Worker 多干、慢的少干。如果某些 Worker 因为网络环境差导致成功率低,它们会消耗大量代理 IP 但产出少。解决方案:监控每个 Worker 的成功率,成功率低于阈值的 Worker 自动降低请求获取频率。

跨地域部署的时钟同步。如果 Worker 分布在不同地域,Redis 的 TTL 和分布式锁的超时依赖系统时钟。时钟不同步可能导致锁提前释放或过期判断出错。确保所有机器都配置了 NTP。

阶段四:完整的代理网关(日均 > 100 万请求)

典型场景: 大规模数据采集团队,10+ 台 Worker,多个业务线共用代理基础设施,需要精细的流量控制、成本统计、权限管理。

演进触发条件:

需要统一的代理接入层——不同业务线的采集脚本使用不同的语言和框架(有用 Python 的、有用 Go 的、有用 Node.js 的),不可能每种语言都实现一套 Redis 代理调度逻辑。

需要成本核算——按业务线统计代理 IP 的消耗量和成本,谁用了多少、花了多少。

需要限流和权限——业务线 A 每天最多用 50 万个请求的代理配额,超了就排队。

代理架构: 在 Worker 和 Redis 代理池之间加一层 代理网关(Proxy Gateway)——一个独立的 HTTP 服务,Worker 的所有请求先发到网关,网关负责:选 IP、加速率限制、记录使用日志、返回代理。

Worker 侧的改造非常简单:把代理地址从具体的 IP:PORT 改成网关地址。Worker 不再直接操作 Redis,不需要知道代理池的内部逻辑。

代码语言:javascript
复制
 # Worker 只需要知道网关地址
 proxy = "http://proxy-gateway.internal:8080"
 requests.get(target_url, proxies={"http": proxy, "https": proxy})

网关内部的核心模块:

代码语言:javascript
复制
 请求接入 → 鉴权(API Key / 业务线标识)
          → 限流(令牌桶,按业务线独立计数)
          → 选IP(从Redis池取,带评分、隔离、冷却逻辑)
          → 转发请求
          → 接收响应
          → 更新IP评分
          → 记录日志(业务线、目标站、IP、状态码、延迟、成本)
          → 返回响应给Worker

网关可以用 Go 或 OpenResty(Nginx + Lua)实现,性能要求是能扛住所有 Worker 的并发——日均百万请求大约是每秒 12-15 QPS(均摊),峰值可能到 50-100 QPS,Go 的 net/http 或 OpenResty 都轻松撑住。

这个阶段要特别注意的事:

网关本身不能成为单点。至少部署 2 个实例,前面挂负载均衡。

日志量会很大。每天百万条请求日志,存数据库太贵,建议写到文件 + 定期归档,或者写到 ClickHouse 这类列式数据库。

成本统计口径要提前定义。按请求数计费、按 GB 计费、按成功请求计费——不同口径差异很大,提前和业务方对齐。

各阶段速查表

维度

阶段一:单机脚本

阶段二:单机+Redis

阶段三:多机Worker

阶段四:代理网关

日均请求量

< 5万

5-30万

30-100万

> 100万

IP 存储

本地列表/配置

Redis Sorted Set

Redis(可 Sentinel)

Redis Cluster

健康检测

脚本内部

独立进程

独立进程+分布式锁

网关内置+独立巡检

调度方式

轮询/随机

评分加权

评分+分布式锁

网关统一调度

业务隔离

按目标站分池

按目标站分池+域名冷却

按业务线+目标站

监控

print/日志

Redis ZCARD + 简单脚本

成功率+水位+锁竞争

全链路日志+成本统计

演进信号

成功率不稳定

单机瓶颈

需要统一接入层

——

演进过程中最常犯的三个错误

错误一:跳级。 日均 3 万请求就上代理网关。网关的开发和运维成本远高于直接在脚本里加个 SimpleProxyPool 类。每个阶段的方案都是为对应量级优化的,跳级只会增加不必要的复杂度。

错误二:不做水位监控就上多 Worker。 多 Worker 消耗 IP 的速度是单机的 N 倍。如果没有水位监控和自动补充机制,池子会在高峰期迅速被掏空,所有 Worker 同时取不到 IP,采集全部停转。

错误三:只看成功率,不看单位成本。 阶段三以后,成功率 95% 但每个成功请求的代理成本是 0.1 元,和成功率 85% 但单位成本 0.03 元——哪个更好取决于你的业务。演进架构时要同步建立成本统计口径,不能只盯着成功率。

FAQ

Q:从阶段二到阶段三,是不是必须用 Scrapy-Redis?

不是。Scrapy-Redis 是一种方案,但不是唯一的。如果你不用 Scrapy(比如用 asyncio + aiohttp,或者用 Go 写采集器),可以自己实现分布式任务分发——用 Redis List 做队列、用 Pub/Sub 或消息队列做通知。核心是把"任务分发"和"代理调度"两个问题分开解决,不要耦合在一起。

Q:代理网关用 Go 写还是用 OpenResty?

如果团队熟悉 Go,用 Go 的 net/http + httputil.ReverseProxy,开发快、调试方便。如果团队有 Nginx 运维经验,OpenResty 的 Lua 脚本可以热更新,不需要重启网关就能改调度逻辑。日均百万级的流量两种方案都扛得住,选团队更熟悉的。

Q:Redis 在哪个阶段需要上集群?

阶段三通常不需要——Redis 单实例的吞吐(10 万+ QPS)远超代理调度的需求。阶段四如果代理池规模超过 10 万个 IP,或者你把所有业务线的日志也存在 Redis 里,可能需要上 Redis Cluster。但更常见的做法是日志不走 Redis(写 ClickHouse 或文件),Redis 只存代理池状态,这样单实例基本够用到日均千万级。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 不要一上来就搭集群
  • 阶段一:单机单脚本(日均 < 5 万请求)
  • 阶段二:单机 + Redis 代理池(日均 5-30 万请求)
  • 阶段三:多机 Worker + 中心调度(日均 30-100 万请求)
  • 阶段四:完整的代理网关(日均 > 100 万请求)
  • 各阶段速查表
  • 演进过程中最常犯的三个错误
  • FAQ
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档