摘要:只对成功请求计算平均延迟、P95和P99,只能说明“成功样本有多快”,不能说明代理整体表现。超时和连接失败越多,留下的成功样本反而可能越漂亮。可靠测速应同时报告全量成功率、成功请求延迟、失败类型、重试后完成时间和单位时间有效请求数。
代理测速最容易出现的误判,是看到平均延迟只有两三百毫秒,就认为线路很快。翻一下原始日志,却可能发现一批请求在连接、握手或读取阶段超时,只是这些请求没有进入延迟统计。
问题不一定出在测速工具,而在统计口径。只保留成功请求,相当于让失败样本在报表里集体消失。真正应该比较的不是一个孤立的延迟数字,而是一条完整请求从发出到成功、失败或耗尽重试预算的全过程。
为什么成功请求越少,延迟数据有时反而越好看?
因为失败请求通常更慢,一旦被过滤,剩下的样本天然偏向响应较快的部分。
假设同一代理入口发出1000个请求:
- 800个请求成功,成功样本的P95为420毫秒;
- 150个请求在3秒后读取超时;
- 50个请求在连接阶段失败。
如果脚本只把状态码正常的800个请求写入延迟数组,最终报告可能只剩一句:“P95为420毫秒。”但对业务而言,另外200个请求同样消耗了连接、线程、并发槽位和重试时间。
这类误差可写成一个简单的条件统计:
成功样本平均延迟 = 平均值(请求耗时|请求成功)它回答的是“成功请求通常多快”,而不是“随机发出一个请求后,多久能拿到可用结果”。两者不是同一个问题。
只看P95,为什么也修复不了样本偏差?
P95能处理成功样本内部的长尾,却无法找回已经被删除的失败样本。
如果测速程序先执行“只保留成功请求”,再计算P50、P95和P99,那么所有分位数都建立在同一个偏置样本上。统计方法换得再高级,也只是更精确地描述幸存下来的请求。
下面两组结果就很典型:
| 代理组 | 全量成功率 | 成功请求P95 | 3秒超时占比 | 单看成功延迟的印象 |
|---|---|---|---|---|
| A组 | 96% | 900毫秒 | 3% | 看起来偏慢 |
| B组 | 80% | 320毫秒 | 18% | 看起来很快 |
如果任务更在意一次拿到结果,A组通常更可控;如果只展示成功请求P95,B组却可能被误判为性能更好。延迟指标本身没有错,错的是把它从成功率和失败分布中单独摘了出来。
一次完整的代理测速应该记录哪些结果?
至少要把每次尝试的结果、耗时和重试关系保留下来,再分层计算指标。
可以把测速指标拆成四层:
| 指标层 | 建议记录的内容 | 回答的问题 |
|---|---|---|
| 全量结果 | 总请求数、成功率、各类失败占比 | 随机请求成功的概率多大 |
| 成功延迟 | P50、P95、P99 | 成功请求内部有多快、长尾多长 |
| 失败成本 | 连接失败耗时、读取超时阈值、失败前耗时 | 失败一次会占用多少时间和资源 |
| 任务结果 | 重试后成功率、端到端完成时间、有效吞吐 | 真实任务最终要付出多少成本 |
失败类型不能全部塞进一个“其他错误”。代理鉴权失败、连接超时、TLS握手失败、读取超时和HTTP业务错误的处理动作完全不同。只有分类记录,后续才能判断是代理连接、目标响应、请求参数还是任务节奏出了问题。
测速脚本怎样保留失败样本?
每次请求都应落一条记录,无论成功、超时还是异常,不能只在成功分支里写日志。
下面是一段精简的Python示例。重点不在请求库,而在于finally中无条件记录耗时:
import time
import requests
records = []
for request_id in range(1000):
started = time.perf_counter()
outcome, status = "unknown", None
try:
response = requests.get(
"https://目标测试地址",
proxies={"http": "代理地址", "https": "代理地址"},
timeout=(2, 3),
)
status = response.status_code
outcome = "success" if 200 <= status < 400 else "http_error"
except requests.ConnectTimeout:
outcome = "connect_timeout"
except requests.ReadTimeout:
outcome = "read_timeout"
except requests.RequestException:
outcome = "request_error"
finally:
records.append({
"request_id": request_id,
"outcome": outcome,
"status": status,
"elapsed_ms": (time.perf_counter() - started) * 1000,
})实际任务还应增加代理出口标识、目标地址、并发数、重试序号、错误码和时间戳。这样才能区分“少数出口持续异常”和“所有出口在高并发下共同变慢”。
超时请求应该算多少毫秒?
超时不能记为0,也不适合直接混进普通成功延迟;应单独报告,并增加一个带惩罚的任务指标。
假设读取超时设置为3秒,请求达到阈值后失败,至少已经消耗3秒左右。直接删除会低估成本,记为0更是方向相反。比较实用的处理方式有两种:
- 成功延迟与超时率分开报告:例如“成功请求P95为480毫秒,读取超时率为6.2%”。
- 增加截断耗时指标:成功请求使用实际耗时,超时请求按超时阈值计入,用于比较固定时间预算下的资源消耗。
第二种指标不是严格意义上的HTTP响应延迟,更适合命名为“惩罚耗时”或“预算占用时间”,避免和成功延迟混淆。
开启重试后,应该统计第一次请求还是最终结果?
两者都要保留,但任务决策应重点看从首次发起到最终完成的端到端时间。
一次请求300毫秒成功,与第一次超时3秒、第二次600毫秒成功,在成功结果表里都可能只留下一个正常响应。对调用方来说,后者实际花费接近3.6秒,还多消耗了一次请求机会。
重试场景建议增加三个指标:
- 首次尝试成功率;
- 重试后最终成功率;
- 从第一次尝试到成功或耗尽预算的完成时间。
还可以统计每100次有效结果需要发出多少次请求。这个数字会把重试放大的带宽、并发和计费成本直接暴露出来,比单独比较平均延迟更接近生产环境。
怎样设计一组不容易“测快”的对照实验?
固定业务条件,只改变代理入口,并同时观察成功率、延迟和有效吞吐。
可以从以下起始条件开始:
| 测试项 | 起始设置 | 需要观察的结果 |
|---|---|---|
| 请求量 | 每组至少1000次 | 样本是否足以暴露长尾 |
| 并发梯度 | 1、5、10、20 | 成功率从哪一级开始下降 |
| 连接/读取超时 | 2秒/3秒 | 失败集中在哪个阶段 |
| 重试次数 | 0次和1次各跑一组 | 重试改善多少、又增加多少耗时 |
| 测试时段 | 至少覆盖两个时段 | 结果是否受单一时段影响 |
这些数值只是压测起点,不是所有任务的固定配置。若真实业务单次允许等待10秒,测试预算也应随之调整;若任务要求低延迟返回,则应缩短超时,并重点观察首次成功率和有效吞吐。
测速结束后,不要急着按平均延迟排序。先淘汰成功率和错误分布明显不符合任务要求的组,再在合格组内比较P95、P99、端到端完成时间与成本,结论会稳得多。
FAQ
Q:平均延迟还有参考价值吗?
有,但必须明确它是成功请求的平均延迟,并与样本量、成功率和超时率一起展示。延迟分布偏斜时,平均值容易被少量长尾拉动,通常还要同时查看P50和P95。只有一个“平均延迟200毫秒”,几乎无法判断代理在真实任务中的表现。
Q:HTTP 403、429要不要算代理失败?
不应不加区分地算作代理连接失败。403、429可能与请求频率、目标策略、授权状态或请求内容有关。测速日志应将它们保留为独立结果类型,再结合直连对照、频率调整和同条件请求判断原因,不能为了提高成功率直接删除。
Q:测试地址应该使用测速网站还是真实目标?
两者可以分层使用。稳定的测试地址适合检查代理连接和基础网络延迟,真实业务目标用于评估最终任务表现。只测测速网站,无法覆盖目标服务的握手、响应体大小和业务限制;只测一个真实目标,又容易把目标自身波动误判为代理问题。
Q:并发越高,测试结果越接近生产环境吗?
不一定。并发超过套餐、客户端连接池或目标服务承载边界后,测试测到的可能只是过载结果。更合理的做法是从低并发开始逐级增加,观察成功率和P95在哪个梯度发生明显变化,再与真实任务的请求节奏对照。
Q:最终应该用哪个指标选择代理?
先用全量成功率和错误分布判断能不能用,再用成功请求P95、重试后完成时间和有效吞吐判断是否好用。若任务对时延敏感,关注首次成功率与长尾;若任务允许重试,更应计算每个有效结果的总耗时和总请求次数,而不是只看成功样本有多快。