代理 IP 测试正常、上业务却失败?403、429、连接超时和重试成本怎么排查

1 阅读8分钟

摘要:代理测试接口返回 200,只能证明当时能够完成一次基础请求,不能证明真实业务一定可用。业务请求中的鉴权、Cookie、目标接口限流、连接复用和超时配置都会改变结果。本文用一组最小探测代码,把 403、429、连接超时和重试放到同一套日志中分析。

代理 IP 的连通性测试经常很简单:请求一个公开检测地址,看到出口地址正确、状态码为 200,就认为可以接入业务。真正切换到已获授权的 API、企业后台或自动化任务后,却可能出现三类结果:

  • 测试地址正常,业务接口持续返回 403
  • 单次请求成功,并发提高后开始返回 429
  • 前几次请求很快,随后出现 ConnectTimeoutReadTimeout

这不一定说明代理资源失效。测试请求与业务请求在目标域名、认证信息、会话状态、请求频率和响应体大小上都可能不同。排查的第一步不是立即更换出口,而是把两类请求放进相同的观测口径。

一次 200 为什么不能代表业务可用

下面两次请求即使使用相同代理,实际验证的内容也不同:

代理测试:客户端 → 代理接入 → 测试地址 → 返回出口信息
业务请求:客户端 → 代理接入 → DNS/TLS → 业务网关
         → 身份认证 → 权限与限流 → 业务服务

基础测试主要覆盖代理认证和网络连通性。业务链路还可能经过 API 网关、WAF、账户配额、Cookie 会话、签名校验以及上游服务。因此,需要记录“在哪一层获得了什么证据”,不能把所有失败合并为一个失败率。

建议至少记录以下字段:

字段用途
target区分测试地址和业务接口
status保留 403、407、429、5xx 原始分类
elapsed_ms观察端到端耗时分布
error_type区分连接超时、读取超时和连接错误
retry_after判断服务端是否给出退避时间
attempt计算重试次数与请求放大

用最小脚本生成可对照的日志

以下代码只用于企业自有接口或已获授权的测试目标。代理地址和认证信息使用环境变量,不写入源码。

import os
import time
import requests

PROXY = os.environ["HTTPS_PROXY"]
TARGETS = {
    "probe": "https://YOUR_PROBE_HOST/health",
    "business": "https://YOUR_API_HOST/v1/status",
}

session = requests.Session()
session.proxies.update({"http": PROXY, "https": PROXY})

for name, url in TARGETS.items():
    for attempt in range(1, 4):
        started = time.perf_counter()
        try:
            response = session.get(url, timeout=(3, 8))
            elapsed = (time.perf_counter() - started) * 1000
            print(name, attempt, response.status_code,
                  f"{elapsed:.1f}ms",
                  response.headers.get("Retry-After", "-"))
        except requests.RequestException as error:
            elapsed = (time.perf_counter() - started) * 1000
            print(name, attempt, type(error).__name__,
                  f"{elapsed:.1f}ms", "-")

这里把连接超时和读取超时分别设置为 3 秒、8 秒。Requests 官方文档说明,timeout=(connect, read) 中的两个值含义不同;timeout 也不是整个响应下载的绝对总时长。

示例输出如下,仅用于说明日志格式:

probe    1 200            312.6ms -
business 1 403            428.1ms -
business 2 429            391.7ms 30
business 3 ConnectTimeout 3006.4ms -

这组日志不能直接得出“代理不可用”的结论,但已经把问题拆成三个不同分支。

403:请求到达了服务端,但条件没有满足

RFC 9110 将 403 Forbidden 定义为服务端理解请求、但拒绝完成请求。它可能与接口权限、令牌范围、签名、Cookie、来源约束或业务策略有关。

排查时应使用相同的 URL、方法、请求头和身份信息做对照:

  1. 同一业务请求直连是否也返回 403;
  2. 令牌是否具有对应接口权限,签名时间是否正确;
  3. User-AgentContent-TypeOrigin 等必要请求头是否缺失;
  4. Session 中的 Cookie 是否与登录流程一致;
  5. 服务端日志能否给出具体拒绝原因。

403 通常不适合无条件自动重试。请求内容和权限未发生变化时,重复发送同一请求只会增加成本。

429:先处理限流,再讨论出口

RFC 6585 规定,429 Too Many Requests 表示用户在一定时间内发送了过多请求,响应中可以包含 Retry-After。标准并未规定服务端必须按 IP 统计,也可能按账户、Cookie、接口或资源计数。

因此,看到 429 后应先记录:

  • 同一时间窗的请求量和并发数;
  • Retry-After 以及其他配额响应头;
  • 账户、令牌和接口维度的限制;
  • 多个出口是否仍共享同一业务账户;
  • 重试任务是否与首轮任务同时运行。

如果服务端给出了等待时间,应优先遵守;没有明确说明时,也应采用有上限的指数退避并加入随机抖动。持续换出口、立即重发可能继续消耗同一账户配额。

连接超时与读取超时不是同一问题

ConnectTimeout 表示连接阶段未在限定时间内完成,排查方向包括代理接入、DNS、TCP 建连、TLS 握手和线路质量。ReadTimeout 则表示连接建立后,在限定时间内没有收到数据,更应关注目标服务处理时间、响应体大小和读取超时设置。

可用以下顺序缩小范围:

代理接入是否成功
  ↓
测试地址与业务域名是否都能完成 TLS
  ↓
连接超时还是读取超时
  ↓
是否只在特定并发、时段或目标地区出现
  ↓
直连与代理对照是否存在稳定差异

Requests 默认不会为每个失败场景自动执行符合业务语义的重试。即使使用 HTTPAdapter,也需要明确允许重试的方法、状态码、退避规则和次数。对于非幂等写请求,自动重试还可能造成重复提交。

重试成本要单独计算

假设首轮发出 1000 个业务请求,其中 20% 失败;失败请求重试一次后仍有 25% 失败;再重试一次。实际请求数为:

首轮请求:1000
第一次重试:1000 × 20% = 200
第二次重试:200 × 25% = 50
总请求数:1000 + 200 + 50 = 1250
请求放大系数:1250 / 1000 = 1.25

最终成功率可能提高,但代理流量、目标端请求量、连接池占用和任务时长同时增加了 25%。如果失败原因是稳定的 403,重试几乎不会提高成功率;如果是 429,无视退避的重试还可能延长限流窗口。

建议同时观察四个指标:

指标计算口径
首轮成功率首次请求成功数 ÷ 业务请求数
最终成功率重试结束后的成功任务数 ÷ 业务请求数
重试率重试请求数 ÷ 首轮请求数
请求放大系数实际总请求数 ÷ 首轮请求数

作为实际数据参考,在已有的日常 HTTP 请求采样记录中,IPdodo 的业务成功率为 98.5%–99.4%,平均响应时间为 0.5–2.3 秒,丢包率参考区间为 0.3%–0.9%

这组数据只对应采样时的接口、出口地区、请求节奏、并发和测试周期,不代表所有节点或业务目标的固定表现,也不应直接外推到其他协议和负载条件。正式接入时仍应使用本文的日志字段,在自己的业务接口上重新计算首轮成功率、重试率、超时分布和请求放大系数。

只报告最终成功率,会掩盖系统依靠多少额外请求才获得结果。

排查矩阵

现象优先证据首要动作是否直接重试
403响应体、权限、签名、服务端日志修正请求条件或权限通常否
407Proxy-Authenticate、代理凭据检查认证方式和有效期修正后再试
429Retry-After、并发、账户配额降速并按规则退避有条件
ConnectTimeoutDNS、TCP/TLS、线路探测区分接入端与目标端有上限
ReadTimeout服务处理耗时、响应大小调整查询和读取超时视幂等性
5xx服务端日志、时间窗、接口状态确认上游是否临时异常有上限

结论

代理测试正常,只能证明基础链路在某个时刻可用。真实业务能否稳定完成,还取决于目标服务的鉴权、限流、会话和响应行为。

更可靠的排查方式是:用同一客户端记录测试地址与业务地址的原始状态码、超时类型、Retry-After 和尝试次数;先判断失败发生在哪一层,再决定是否重试。最终报告同时给出首轮成功率、最终成功率、重试率和请求放大系数,才能看清“业务成功”背后的真实成本。

参考资料

  • RFC 9110,HTTP Semantics:https://www.rfc-editor.org/rfc/rfc9110.html
  • RFC 6585,Additional HTTP Status Codes:https://www.rfc-editor.org/rfc/rfc6585.html
  • Requests 官方文档,Timeouts 与 Exceptions:https://requests.readthedocs.io/en/latest/user/quickstart/#timeouts
  • Requests 官方文档,Transport Adapters:https://docs.python-requests.org/en/stable/api/#requests.adapters.HTTPAdapter