盲目的重试循环,常是集成第三方 API 时把短暂故障变得更糟的原因:不断撞向已失效的密钥,把限额耗在注定失败的调用上,更糟时还会对同一笔购买重复扣费。正确做法不是先设置等待间隔,而是先对故障原因分类——不是每种错误都值得再来一次,也不是每次重发都安全。
API 错误的三种类型
临时性错误与请求内容本身无关:网络中断、连接超时、服务商侧短暂过载。同样的请求几秒后再发很可能就能成功,因为问题出在通道或时机,而非调用本身。
永久性错误正相反:密钥无效、参数格式错误、权限不足。这类调用无论第一次还是第一百次都会以同样方式失败,因为原因出在请求本身或账号配置上。不修正原因就重试,除了多一条日志之外什么也得不到。
资金类错误形式上很像临时性错误——余额不足、目标国家没有可用号码——但需单独处理。稍后再试确实可能在余额充值或库存刷新后成功,但盲目重复调用、尤其是重复发起扣款本身并不安全,原因见下文。
哪些该重试,哪些不该
规则很简单:只重试能靠时间自行恢复的情况。网络中断、超时、5xx 响应或过载信号,都是自动退避的候选对象。密钥无效、参数错误、权限被拒,在原因手动修复前再来一次毫无意义——应先修正配置再重发一次,而非循环等待。余额或库存不足应作为独立状态呈现给调用方,是否重发交由业务逻辑或用户决定,而非计时器。库存不足更容易出现在一次性购买还是按周期租用号码这一模式对比,见OTP 激活与号码租赁对比一文。
指数退避与盲目重试的代价
指数退避是指每次尝试前的等待时间逐次拉长——大致一秒、两秒、四秒、八秒,直至上限,通常三到五次后循环停止,错误作为最终结果向上抛出。等待时间中还应加入小幅随机抖动,避免大量客户端在同一次故障中,恰好于恢复那一刻同时发起同步冲击。
若一开始没有分类,退避本身救不了场:用在永久性原因上,只是把无意义调用拉长到更久时间跨度,每次调用依旧占用账号共享限额中的一个名额。当循环在无效密钥上空转时,同一账号下的正常调用——真实激活、状态查询——会撞上同一限额而失败,并非因原始原因失败。在带并发工作进程的自动化场景中,一个卡在此循环里的进程足以拖垮所有人的限额,见通过 API 实现注册自动化一文。
用于事故排查的日志
事故复盘依靠日志而非记忆,因此要记录得足够详细,才能在不重现问题的情况下回答「这是服务商的问题还是我们自己的问题」。基本字段包括:调用时间、接口路径、请求或激活 ID、服务商返回的错误码与文本、当前尝试次数,以及去除密钥后的请求参数——密钥和令牌绝不能进日志。
除服务商响应外,还应记录处理程序当时把该原因归为哪一类——临时性、永久性还是资金类。若分类器判断错误,把永久性原因当作临时性反复重发,只有这条记录能看出来,仅凭响应码看不出来。
资金操作的幂等性:如何避免重复扣费
超时并不能说明操作到底有没有在服务端执行完成:扣款可能已完成,只是响应尚未送达客户端前连接就断了。在没有额外保护的情况下重发这个请求,对 API 而言看起来就是一笔全新操作——结果就是同一笔购买被扣两次。
幂等键正是为此而生:客户端为每次操作只生成一次唯一标识,并在包括超时后重试在内的每次尝试中携带它。服务端记住该键对应已返回的结果,收到相同值的重复请求时直接返回该结果,而非再执行一次。这是重发资金类调用唯一安全的方式——没有这个键,无论还剩多少次尝试机会,都始终存在重复扣费的风险。
常见问题
429 错误是否应该像超时一样重试?
应该,但等待时间要更长:429 表示的不是故障,而是限额已耗尽,立即重发只会让所有人等得更久。若服务商在响应头里给出具体数值就直接使用,否则首次等待就该设为几秒,而非一秒。
超时后可以自动重发扣款请求吗?
不能直接重发原始请求,只能重发携带幂等键的操作。超时无法说明操作在连接断开前是否已执行完成;用同一个键再次发送是安全的,因为服务商会直接返回已执行结果,而非再扣一次款。没有这个键,这样的重发就有重复扣费的风险。
临时性错误应该设置多少次尝试比较合理?
通常三到五次,配合指数退避,总等待上限控制在 30 到 60 秒左右。更多次数很少能改变结果:若服务商在此窗口内仍未恢复,问题就是系统性的,值得上报,而非继续循环等待。
按类别划分的错误码、幂等键请求头以及各接口的重试参数,详见API 文档。同样依赖重试与幂等性逻辑的 webhook 接收流程,见webhook:无需轮询接收验证码一文,基础集成场景见虚拟号码 API 集成指南。