SDK重试与超时概述:为什么需要重试与超时?

说实话,干了这么多年后端,每次带新人第一个要讲的,就是这两个概念。

记得我刚开始写 SDK 那会儿,总是把网络请求想得太完美。直到有一次线上事故,一个第三方支付接口偶尔超时,我那个没加重试的 SDK 直接让整批订单卡住不动了。嗯,凌晨三点被叫起来修复的感觉,真不好受。

从那以后,我深刻明白了一个道理:在分布式系统里,请求失败是常态,不是异常。我们写 SDK 的,必须为这种 常态 设计好兜底策略。

为什么请求会失败?

我们先来盘点一下,一个网络请求到底能遇到多少坑。我把它们分了类,你看看遇到过几个:

失败类型 典型场景 能不能重试?
网络抖动 TCP 丢包、DNS 解析失败 通常可以,短时间后可能恢复
服务端过载 HTTP 503、502,连接被重置 可以,但需要退避
服务端超时 请求处理太久,客户端等不及 需谨慎,可能已经处理了
资源竞争 数据库死锁、Redis 熔断 可以,等锁释放后再试
数据冲突 乐观锁更新版本号不符 可以,需要重新读取最新数据
幂等性违规 重复支付、订单重复创建 不行,必须靠幂等令牌防重
永久性错误 HTTP 400 参数错误、401 鉴权过期 不重试,修复代码或刷新凭据

看到没?上面这些失败里,有 5 类是可以尝试重试的。但你往下看,如果重试策略写不好,反而会把系统搞崩。

没有重试机制的风险链

我举个例子,你在 SDK 里不加重试。用户调用一次失败了,直接抛异常。用户自己想「再点一次」。好嘛,这一点的结果可能就是 雪崩。

我曾经遇到过一个真实案例:某 SDK 调用内部网关超时,没做指数退避。客户端 1 秒内重试 100 次,直接把网关打挂了。那个网关后面还连着数据库,数据库瞬间连接池爆满,整个系统 5 分钟才缓过来。所以,不设重试是找死,乱设重试是作死。

没有重试的后果很明显:一次瞬时故障,就直接转成 失败结果。用户体验差不说,开发排障的成本也高。但更可怕的是一种连锁反应:

  • 客户端频繁重试 -> 服务端负载升高
  • 服务端来不及处理 -> 更多请求排队超时
  • 超时引发更多客户端的重试 -> 负载进一步升高
  • 最终结果:服务雪崩

那超时机制又解决了什么?

重试解决的是「失败了怎么办」,超时解决的是「什么时候放弃」。没有超时,你的线程可能永远挂在一个慢请求上。

想想看,一个 HTTP 连接池默认可能没有超时。如果服务端进程卡死、不返回也不断开,你的调用线程就一直等在那。一次两次还好,QPS 一上来,连接池被占满,新的请求进不来,整个 SDK 就阻塞了。

我个人习惯:在 SDK 设计初期就明确三个超时维度——连接超时(建立 TCP 连接最久等多久)、读取超时(等待服务端返回数据的最长时间)、写入超时(发送请求体的时限)。这三个定好了,再谈重试策略。

举个具体的例子:你写一个微服务间的 RPC 调用 SDK。如果设置了 500ms 的读取超时,那当上游服务慢查询时,最多影响 500ms 的线程资源。结合重试(比如重试 2 次,每次间隔 200ms),一次调用最多耗时 500ms × 3 + 400ms 间隔 = 1900ms。你看,这个时间可控了,不会出现「等 30 秒还没响应」的尴尬。

为什么需要两者配合?

说白了,重试和超时是两兄弟。缺了谁都不行。我个人把它们看作一个 完整决策链路:

  1. 发起请求 -> 带着连接超时和读取超时
  2. 遇到超时或错误 -> 判断错误类型是不是可重试的
  3. 决定重试 -> 按照预设的退避策略等待(比如指数退避 + 随机抖动)
  4. 累计超时检查 -> 如果整体耗时超过了总的最大等待时长,放弃
  5. 重试耗尽 -> 最终报告失败,抛出异常或返回错误码

这个决策链路,我稍后会作为 SDK 核心流程的核心骨架 来展开讲。现在先记住一点:没有超时的重试是盲目的,没有重试的超时是脆弱的。

核心逻辑关系图

重试与超时机制决策流程 重试与超时决策流程 发起 API 请求 判断是否可重试的错误? 不可重试 直接抛出异常 可重试 总体耗时是否超限? 超限 放弃,报告失败 未超限 执行退避策略,重试 继续循环 重试次数 +1 核心原则 退避策略 + 超时兜底 防止雪崩效应

构建策略前先想清楚的问题

好,既然我们知道了「为什么需要」,那我建议你在写代码前,先问自己三个问题:

  • 这次调用幂等吗? 不幂等的接口不要自动重试。你可以提供一个重试策略接口,让调用方自己决定是否重试。
  • 我能忍受的最大延迟是多少? 这决定了你的总超时时间和重试次数。用户界面请求最多 2-3 秒,后台批处理可以久一些。
  • 错误是不是永久性的? 比如 401 鉴权失败,重试 100 次也没用。这种情况直接失败,让上层重新获取 。

一句话总结: 重试解决瞬态失败的恢复问题,超时解决资源释放和系统保护的边界问题。两者配合,才能构建一个健壮的 SDK 调用层。后面我会详细讲具体的策略算法、代码实现和实战中的避坑经验。