学习 / 实践

Java 请求重试:三次超时,不等于一个总期限

AI 应用 / 构建 · 工程案例

七种真实本地 HTTP 情况,比较状态码、Retry-After、尝试上限和总等待预算。

进阶 · 9 分钟 · 更新于 2026-10-07 · 步骤 12 / 17

浏览统计暂不可用

本文目录 6
跳到正文 ↓

固定版本代码 · 原始证据 · 实验档案

给每次请求设五秒超时,再重试三次,并不能得到“最多五秒”。连接、读取和等待都会消耗时间。更危险的是,客户端超时并不能证明服务端没有执行;把所有异常重试可能重复产生答案、费用或副作用。

本篇实现 RetryBudget,用本地 HTTP 服务验证决策。它只对调用方明确认定可安全重试的 GET 操作开放重试,不把这套规则自动套到模型生成 POST。七个条件都有真实连接,但响应文字是固定 fixture,不是模型输出。

两道门:能否重试,以及来不来得及

收到结果 → 成功:返回
         → 不可安全重试 / 401:停止
         → 429或503:检查次数 → 计算等待 → 检查剩余预算
                                              ↓
                                     够等才发下一次

次数上限保护调用数量,总期限控制用户等待;两者都要有。使用 System.nanoTime() 计算经过时间,避免把系统日历时钟调节混进预算。每次连接和读取前重新计算剩余量,已经耗尽就停止。

但这个 Java8 实现是协作式期限,不是硬实时取消器:DNS、操作系统调度、一次阻塞调用的阶段切换可能超出理想时刻。慢响应案例的40ms预算实际记录为45ms,不能把它包装成精确40ms保证。

Retry-After 不能被随意压短

本地服务返回503和 Retry-After: 1,而本次总预算只有500ms。正确结果是停止,并报告 deadline-before-retry;不是只等剩下的几十毫秒再请求。

if (wait >= remaining(deadline)) {
    result.outcome = "deadline-before-retry";
    break;
}
Thread.sleep(wait);

示例只解析秒数形式;HTTP 日期形式会明确报告 unsupported-retry-after,而非猜测一个值。正式客户端应按协议补充日期解析、退避抖动、连接池策略与可观察日志。这里20ms线性等待只是本地可检验参数,不是公网服务的推荐退避。

实测决策表

本地情况 尝试次数 结果
立即200 1 ok
先503再200 2 ok
401 1 not-retryable
Retry-After超过剩余预算 1 deadline-before-retry
持续503 3 attempt-limit
响应头延迟200ms,预算40ms 1 deadline-or-interrupt
调用方未允许安全重试 1 not-retryable

尝试数是客户端开始尝试的次数,不等于服务端已处理或已计费数量。原始结果保留了耗时,但没有统计样本支持延迟百分位。

接入聊天前必须补的一层

对模型调用,先判断失败发生在发出前还是发出后,确认平台是否有真正的幂等语义。仅自己加一个 request ID 并不能让远端去重。重试拿到回答之后仍要走输出验收与整轮提交;临时预览不能提前写成最终历史。

本例关闭自动重定向,限制成功响应体4096字节;失败响应不解析为模型消息。IO异常不会被泛化为自动重试。中断在等待阶段向调用者传播,已观察到线程中断的读取预算检查则停止,不清除中断标志。

小练习与排错

把总预算改成10ms,但保留首次503后的20ms等待。预期不会开始第二次尝试。再把 maxAttempts 改成1,预期即使服务器下一次可能成功也不会重试。二者说明预算优先于“多试一次可能成功”的猜想。

若看到超过总预算的耗时,先区分程序决策检查点与底层阻塞阶段;本轮没有TLS、真实网络故障或连接池压力测试。继续阅读崩溃后恢复完整问答,把请求失败和历史持久化分开。

从干净目录复现

需要 Java 8、Python 3、Gson 2.10.1。请将两个路径替换为自己的 JDK 和 jar;既有工程可复用 Maven 缓存。

git clone https://github.com/VirtualSelect/hohoo-ai-lab.git hohoo-ai-lab-study
cd hohoo-ai-lab-study
git checkout 02a2660e22c969e70e3a6f095003614a7dfa448e
python demos/11-bounded-assistant/run.py --java-home /path/to/jdk8 --gson /path/to/gson-2.10.1.jar --out outputs/my-run
python demos/11-bounded-assistant/audit.py outputs/my-run

输出目录必须不存在。本机验证环境为 Windows,Linux/macOS尚未复测。不需要模型密钥或付费服务。manifest中的代码冻结提交早于上方含证据的归档提交,源码哈希对应实际执行文件。

写给未来的自己合上文章前,留下一点自己的理解。

你的私人学习便签,只存在当前浏览器,不会上传或公开。清理浏览器数据会丢失,请导出留存。三种语言共用这篇文章的便签。

下一篇已发布内容:Java 对话历史落盘:程序崩溃后,半轮记录怎么办? →

当前学习位置

构建 / AI 应用开发

  1. ○ Java 会话历史与请求窗口:裁掉的旧事实还能回来吗?
  2. ○ Java 请求重试:三次超时,不等于一个总期限
  3. ○ Java 对话历史落盘:程序崩溃后,半轮记录怎么办?

继续探索

继续探索

  1. 实验 / 031

    Java 请求重试:三次超时,不等于一个总期限

    七种真实本地 HTTP 情况,比较状态码、Retry-After、尝试上限和总等待预算。

    已完成
  2. 项目 / 002

    Java LLM 实践集

    Java 会话、请求重试与持久化边界,以及注意力、缓存成本和量化误差的可复现实验。

    构建中
  3. 工程案例

    Java LLM 实践(十):点了取消,读取线程为什么还在运行?

    30次本地HTTP连接,区分会话取消、阻塞读取退出与远端工作,比较Future中断和主动关闭连接。

    11 分钟已发布

接下来,走哪条路?

依据文章关联与同主题已发布内容整理,不使用随机推荐。

← 全部文章