可通过捕获 SocketTimeoutException 并结合指数退避动态延长连接/读取超时及重试延迟,实现健壮容错;需区分超时类型、仅对幂等请求重试,并在 OkHttp 等客户端中手动控制超时与重试逻辑。

可以通过捕获 SocketTimeoutException,结合指数退避(Exponential Backoff)或线性阶梯式延迟,在重试逻辑中动态延长每次请求的超时时间,从而实现更健壮的网络容错。
理解 SocketTimeoutException 的触发时机
SocketTimeoutException 通常发生在连接建立(connect timeout)或数据读取(read timeout)阶段超时时。它本身不表示网络不可达,而是“等不到响应”,适合重试。但需注意:
- 它是 IOException 的子类,不是所有超时异常都属于它(如 HTTP 408、504 是服务端返回,需单独处理);
- 仅对幂等请求(如 GET、HEAD)安全重试,POST/PUT 等非幂等操作需配合去重 Token 或服务端幂等支持。
设计阶梯式超时退避的核心逻辑
关键是在每次重试前,同时增大:① 连接超时时间,② 读取超时时间,③ 重试间隔延迟。三者可统一按阶梯增长,例如:
- 第 1 次请求:connect=1s,read=2s,重试前 sleep=100ms
- 第 2 次请求:connect=2s,read=4s,重试前 sleep=300ms
- 第 3 次请求:connect=4s,read=8s,重试前 sleep=900ms
- 第 4 次请求:connect=8s,read=16s,重试前 sleep=2700ms(即 3s)
推荐使用「乘数因子」(如 ×1.5 或 ×2)+「上限截断」(如最大 connect=30s),避免无限增长。
在 OkHttp 或 HttpURLConnection 中落地示例
以 OkHttp 为例,可在拦截器中实现:
- 用
Request.tag()存储当前重试次数和基础超时值 - 在拦截器中检查是否为重试请求;若是,调用
request.newBuilder().tag(...)构建新请求,并设置client.newBuilder().connectTimeout(...).readTimeout(...)动态超时 - 捕获
SocketTimeoutException后,计算下一次的超时与休眠时间,用Thread.sleep()或调度器延时后发起重试
注意:OkHttp 默认不重试 SocketTimeoutException(因认为它可能反映临时拥塞),需手动关闭内置重试(RetryOnConnectionFailure(false))并自行控制。
避免常见陷阱
不要只增加重试间隔却不提升超时值——若第 3 次仍用 1s 超时,大概率再次抛出 SocketTimeoutException,形成无效循环。
不要无限制重试——建议最多 3~5 次,配合总耗时上限(如 30 秒内强制失败)。
不要忽略线程中断——sleep 期间应检查 Thread.interrupted(),及时响应取消。










