凌晨3点告警:旅游比价平台的代理IP宕机事件
凌晨3点17分,手机震醒了我。告警信息显示:旅游比价平台的数据采集模块已经连续15分钟抓取失败,累计丢失了3000多条价格数据。我爬起来查日志,发现代理IP池中80%的IP都在30秒内返回超时,剩下20%的可用IP被几十个爬虫任务争抢,导致队列堆积。那是我第一次意识到,靠单个代理IP服务商提供的简单切换逻辑,根本撑不住旅游比价这种高并发、多地域需求的场景。
后来复盘,问题出在三个地方:一是没有故障转移——所有任务都绑定在同一个代理IP池,一旦该池子出问题就是全挂;二是负载不均——轮询调度导致某些IP被频繁使用而被目标网站封禁,而其他IP闲置;三是监控告警形同虚设——失败率阈值设得太低,告警来的时候业务已经崩了半小时。
这次事件之后,我用了两周时间重新设计了代理IP调度架构。今天就把这套方案分享出来,重点解决SEO代理IP在爬虫场景下的高可用和成本问题,尤其适合像旅游比价这种需要同时模拟多地用户、查询多家OTA的平台。
架构设计:三层调度实现故障转移与负载均衡
整个架构分为三层:提取层、调度层、应用层。每一层都扛住单点故障,同时通过算法保证IP利用率最大化。
提取层:多数据源冗余接入
不再只依赖一家代理IP供应商。我接入了三家服务商,比如蚂蚁代理(mayihttp.com)提供动态隧道代理,另一家提供静态住宅代理,第三家作为备用。每家的提取API都封装成统一接口,并在本地缓存一定数量的IP。当一家提取失败或质量下降时,自动切换到另一家。实测这个冗余设计让IP提取成功率从97.3%提升到99.8%。
调度层:加权评分+动态池管理
这是核心模块。每个IP被赋予一个评分,基于三个指标:延迟(30%权重)、可用率(50%)、地域命中率(20%)。调度器每5分钟对所有IP进行一次健康检测,更新评分。分配时采用加权随机算法,评分高的IP被选中的概率更大。同时,每个IP设置最大并发数和最大连续失败次数,超过阈值立即降权并回收替换。
这里我踩过一个坑:一开始用简单的轮询,结果同一个IP被多次分配到同一目标站点,导致被封。改成加权随机后,封IP率降低了60%。另外,故障转移策略是:当某个IP连续失败3次时,立即暂停使用,并从备用池中补一个新IP进来,整个过程对应用层透明。
监控告警层:分级预警与自动恢复
我设置了三个级别的告警:Level 1(警告):可用率低于90%持续1分钟 → 触发邮件通知。Level 2(严重):可用率低于70%持续3分钟 → 短信+电话告警。Level 3(灾难):整体抓取失败率高于50%持续5分钟 → 自动切换整个池子到备用供应商,并启动全量任务重试。
这套分级让团队很少再被凌晨告警吓醒,因为大多数异常在Level 1阶段就被自动调度解决了。
代码示例:Python实现的核心调度逻辑
以下是一个简化版本的核心调度器,展示了评分更新和加权随机分配的关键步骤。实际生产环境还需加锁、异步IO等。
import random, requests, time
class IPPool:
def __init__(self):
self.ips = [] # 每个IP为dict: {'ip':..., 'port':..., 'score':..., 'fails':...}
self.max_fails = 3
def update_scores(self):
for ip_info in self.ips:
latency, ok = self._check(ip_info)
if not ok:
ip_info['fails'] += 1
else:
ip_info['fails'] = 0
# 评分计算公式: 延迟影响、可用率和地域
latency_score = max(0, 1 - latency/1000) if latency else 0
avail_score = 1 - min(ip_info['fails'] / self.max_fails, 1)
ip_info['score'] = 0.3*latency_score + 0.5*avail_score + 0.2*ip_info.get('region_score', 0.5)
def get_best_ip(self):
scores = [i['score'] for i in self.ips]
total = sum(scores)
if total == 0:
return None
r = random.uniform(0, total)
cum = 0
for i in self.ips:
cum += i['score']
if r <= cum:
return i
return self.ips[-1]
调用时,调度器从该服务商的API提取一批IP,初始化到池中,然后每5分钟运行一次update_scores。当某个IP连续失败超过max_fails时,调用该服务商的换IP接口替换它。注意替换时要先释放旧IP,避免资源泄漏。
实测效果:故障率降低85%,成本反而下降了
架构上线后,我跑了30天的对比测试。之前单池轮询的方案,日均故障次数约12.3次,平均每次故障影响800+条数据。新架构下,日均故障次数降到1.8次,且每次影响不超过50条。更重要的是,整体代理IP成本下降了23%——因为IP复用率更高,不再需要频繁提新IP,而且精准的地域路由减少了无效请求。
具体数据对比如下:
| 指标 | 旧方案(单池轮询) | 新方案(三层调度) |
|---|
| 日均故障次数 | 12.3次 | 1.8次 |
| 平均故障恢复时间 | 23分钟 | 2分钟 |
| 单个IP平均可用寿命 | 4.7小时 | 11.3小时 |
| 月度IP费用(元) | 1850 | 1425 |
就我个人经验,SEO代理IP调度不只是关乎稳定性,还直接影响爬虫数据质量。如果你的场景对延迟和可用率要求很高(比如旅游比价需要秒级响应),强烈建议不要省架构上的投入。该服务商(官网)的动态隧道代理在我的方案中作为主要数据源,其延迟稳定在<10ms、可用率99.9%的承诺在实测中基本达标,配合冗余池后几乎没有出现过整体中断。
最后想说,架构没有银弹。如果你的任务量级小(几十个IP),简单轮询就够了。但一旦到了旅游比价这种需要同时监控十几家OTA、覆盖全国多地IP的情况,这套调度架构能省下你未来无数个凌晨的告警电话。