一组对比数据:迁移前后到底差在哪
先把话挑明。迁移前一周的监控看板:日均请求 187 万次,成功率 61.2%,P99 延迟 3.4 秒,403 和 429 占比合计 29%。迁移后同一业务、同一时段:成功率 99.3%,P99 降到 780 毫秒,限流类错误压到 0.4% 以下。
我们做的是票务抢购系统的数据侧——不是帮用户下单,是给风控和余票预测模块跑实时抓取。这个场景对代理的要求比普通采集苛刻得多:开票瞬间并发从日常 200 QPS 拉到 8000 QPS,请求集中在 3 到 5 秒的窗口里,任何一次超时都意味着一条余票信息作废。
之前用的是免费代理池加少量机房 IP 拼凑。说白了就是省钱。结果开票当晚业务方在群里刷屏,我凌晨两点爬起来重启调度器,那是第三次了。
| 指标 | 迁移前(免费+机房IP) | 迁移后(住宅代理IP) |
|---|
| 请求成功率 | 61.2% | 99.3% |
| P99 延迟 | 3400ms | 780ms |
| 403/429 占比 | 29.0% | 0.4% |
| 单次抢购平均耗时 | 4.7s | 1.1s |
根因分析:不是带宽不够,是IP段被标记了
我一开始以为是并发调度写得烂。抓了两周日志才反应过来——请求发出去了,是被服务端主动拒的。
机房 IP 的 ASN 归属太集中。票务平台的风控只要识别出某个 C 段来自 IDC,整段降权,你的请求再快也是白送。免费代理更离谱,那些 IP 早被几十万个爬虫轮过,黑名单命中率高得吓人。
做了个简单统计:迁移前 31% 的失败请求,目标站点返回的响应体里带了风控标记字段,根本不是网络层的问题。结论很直接,这个场景要的是 IP 的“出身”,不是带宽。
住宅代理IP的地址来自真实家庭宽带,归属三大运营商,ASN 分散。票务平台没法一刀切封段,只能按行为做单 IP 限频。这就给了我们调度腾挪的空间。
迁移方案:灰度切换与会话保持调参
没敢直接全量切。先用 5% 流量灰度跑三天,观察纯净度和延迟分布,确认没有异常再按 20%、50%、100% 逐级放。
- 按业务时段拆分代理池:日常低峰用动态短效,开票高峰前 10 分钟预热长会话池。
- 单 IP 请求上限设为 8 次/分钟,超过就主动轮换,避免触发单 IP 维度的限频。
- 开票瞬间用隧道模式保持连接复用,省掉每次握手的 RTT 开销。
- 失败请求做指数退避重试,第一次 200ms、第二次 500ms,最多三次。
这里有个坑我踩了三次:住宅代理IP的响应时间方差比机房大。同一个池子里,有的 IP 20 毫秒回包,有的要 400 毫秒。所以调度器必须做延迟感知——我加了个滑动窗口统计,把 P95 以上的慢 IP 临时踢出轮换队列,效果立竿见影。
def pick_ip(pool, window=60):
now = time.time()
cands = [ip for ip in pool if ip.last_fail_ts < now - window]
# 按近60秒P95延迟升序,取前30%作为候选
cands.sort(key=lambda x: x.p95_latency)
top = cands[:max(1, len(cands) * 3 // 10)]
return random.choice(top)
接入方式上我们用了 API 提取加白名单双通道。白名单兜底,API 做动态补充,避免认证服务抖动时整池不可用。
验证效果与边界承认
全量切完跑满一个开票周期,成功率稳定在 99% 以上,业务方总算不半夜找我了。成本这块,动态代理单价约 0.0022 元/IP,隧道模式按天算更划算,我们这种有明确高峰窗口的场景,隧道加动态混用,单次抢购的 IP 成本压到了 0.3 元以内。
得承认边界:这套方案在我们日均百万级、峰值 8000 QPS 的量级够用。如果你要跑到千万级日请求、或者对单 IP 会话时长有极端要求,得重新算池子规模和预算,别直接照搬。
最后给个排查清单,迁移后如果成功率还是上不去,按顺序查:
- 目标站返回体里有没有风控标记字段,别只看状态码
- 单 IP 请求频次是否超了对方阈值
- 池子里慢 IP 有没有被及时剔除
- 会话保持时长是否和业务窗口对齐
我们后来把池子固定在蚂蚁代理这边,主要是它的城市级粒度和 365+ 城市覆盖,跟票务的地域性余票分布对得上,调度起来省心。具体参数和接入文档在 mayihttp.com 上,自己按业务量算账更靠谱。