先保结果。限流一旦触发,继续重试通常换不来更多有效数据,反而会挤掉已经拿到的响应。正确顺序是:立即停止当前请求批次,把已获取的原始响应和任务游标落盘,再根据限流返回的信号决定等待多久、从哪个位置续跑。下面的判断都围绕这个顺序展开。
假设你写了一个脚本,按资产清单分批调用某款网站漏洞扫描工具的接口,每批提交若干目标,轮询取回结果。运行到中途,接口开始返回限流类错误。此时脚本内存里有三部分东西:已经成功取回的检测结果、已经提交但还没取回结果的批次标识、还没提交的目标列表。限流发生后,如果脚本直接崩溃退出,第一部分丢失,后两部分也无法确认状态。这就是需要提前设计保护机制的原因。
第一种做法是在捕获限流错误后原地等待并重试,直到成功。第二种做法是立即把当前状态写入本地文件,然后按退避策略暂停,稍后从断点续跑。两种做法都能成立,但适用条件不同。
判断依据不是错误信息的措辞,而是限流是否在多次重试后重复出现。如果连续几次重试都在同一时间尺度内被拒,就应转向第二种做法。代价是任务完成时间被拉长,收益是已获取的结果不会因为进程退出而作废。
只保存检测结果是不够的。续跑需要能回答三个问题:哪些目标已经完成、哪些已提交但结果未知、哪些还没开始。因此落盘内容至少应包括:
写入方式建议用临时文件加改名的方式,避免写到一半进程被终止留下半截文件。这一步的实际动作是:捕获限流错误后先执行一次完整落盘,落盘成功再决定是否退出。落盘失败时应保留进程,而不是直接放弃内存中的数据。
等待时长应优先参考服务端返回的重试提示;如果没有明确提示,就用逐步拉长的间隔试探,而不是固定间隔高频重试。试探时只发一个最小请求,收到正常响应后再恢复批量提交。这个动作的结果直接决定下一步:如果最小请求仍被拒,说明限流窗口未过,继续等待;如果通过,则从“已提交但结果未知”的批次开始核对,而不是从已完成目标之后直接续跑。
核对未知批次时,先查询这些批次的状态,再决定是取回结果还是重新提交。这一步能避免两类错误:把已完成的任务当新任务重发,以及把未完成的任务当已完成跳过。
请求量下降、脚本不再报错、或者某次探测返回正常,都不足以单独证明限流窗口已过。这些现象也可能来自:目标清单恰好变短、错误被脚本上层逻辑吞掉、或者探测请求本身不计入受限配额。要区分这些原因,需要保留每次请求的时间戳和响应状态,观察失败是否集中在某个时间区间,以及恢复后是否再次在相近间隔处触发。只有连续一段时间的正常响应,才支持“限流已解除”的判断。
把这套机制固定进脚本,比事后从日志里拼凑状态更省事。核心不是重试多少次,而是限流发生的那一刻,内存里的结果有没有变成磁盘上可以续跑的状态。