用 Java 接入大模型(十一):模型超时或挂掉时,业务怎么降级

简介: 本文是「用Java接入大模型」系列第十一篇,聚焦**系统最后一道防线——降级设计**。面对上游故障、限流、网络分区等必然发生的不可用场景,详解超时分级对应策略、熔断机制(含代码实现)、四层降级路径(缓存→换模型→换能力→明确失败)及缓存、重试的降级化运用,强调“出错时给用户什么”比“如何不出错”更重要。

上一篇把并发控制住了。但控制得住不等于不会出事——上游会故障、配额会用完、网络会分区。这一篇讲的是最后一道防线:当"调用成功"这件事做不到时,业务该怎么办。 这也是阶段二的收尾。


一、先接受一件事:模型一定会有不可用的时候

前几篇都在讲"怎么让调用更可靠":分层超时、重试带抖动、限流、连接复用。这些都是提高成功率的手段。但有一类事实必须单独面对:

上游故障不是小概率事件,而是必然事件。

可能的来源有四个,性质完全不同:

  • 上游整体故障:服务端 5xx、接口大面积超时。你做什么都没用,只能等。
  • 限流:429。你的用量超过了配额,或者上游整体过载。
  • 网络分区:连接被重置、DNS 失败、专线抖动。和上游本身状态无关。
  • 你自己的配额耗尽:账户欠费、额度用完、并发超限。上游是好的,但对你关门了。

这四种情况的共同点是:它们的发生不取决于你的代码质量。 所以一个有真实流量的系统,如果没有准备降级方案,那就等于把"接口一定会在某个时刻整体不可用"当成了设计前提——而这个前提是错的。

降级要解决的核心问题不是"怎么不出错",而是"出错时给用户什么"。这听起来像妥协,但它其实是产品决策:给一个不完整但正确的结果,也好过给一个错误的结果,更好过什么都不给。


二、超时分级和降级动作要一一对应

第四篇讲过超时要分四层设。这里补上另一半:每一层超时对应什么降级动作。

因为不同层超时的原因不同,能做的补救也不同。

连接超时(5 秒级):网络或 DNS 问题。可能是瞬时抖动,也可能真的断了。

  • 动作:重试 1 次,不要多。如果连接都建不起来,重试十次也是白等。
  • 不要在这时候换模型——因为可能所有模型都连不上,换了只是换个等待对象。

首字超时(15 秒级):连接建起来了,但上游迟迟不开始返回。说明上游在排队或过载。

  • 动作:可以重试,也可以切换到备用模型。这里换模型是有意义的,因为备用模型可能是不同的服务集群,负载情况不一样。
  • 这是四层里最适合"换模型"的一层。

块间隔超时(20 秒级):流已经开始,但卡住了。

  • 动作:只能按中断处理。内容已经推出去一部分,重试会让用户看到重复或矛盾的内容(第四篇讲过)。
  • 这条路径没有"优雅降级"的选项,只能承认失败,并把"这是中断"这个事实准确地传给前端。

总时长超时(兜底):只用来防资源泄漏。

  • 动作:按失败处理,但要明确告知用户"任务可能已经完成,请稍后刷新"。因为这种超时下,上游很可能已经算完了,只是你没收到。直接说"失败了"会让用户重复提交,可能产生重复扣费。

这里有个容易搞混的点值得单独说:用户主动断开也要走取消,而不是走降级。 用户点了停止或关了页面,上游的流还在跑、token 还在烧——这是成本问题,不是可用性问题。第四篇提过要留取消的钩子,在这里它是必须的。


三、熔断:别继续打一个已经倒下的上游

重试和降级都是针对单个请求的。但如果上游整体挂了,每个请求都去试一遍、都等一次超时,会带来两个恶果:

  • 你的线程和连接全被这些注定失败的请求占住,连正常请求也处理不了;
  • 上游本来在恢复,被你持续的高频请求又压垮一次。

所以需要熔断:连续失败到一定比例或次数,就直接跳过调用,走降级路径,不再打上游。

熔断器有三个状态,理解它们就够用了:

  • 关闭(正常):请求正常放行,同时统计失败率;
  • 打开(熔断):达到阈值后,短时间内所有请求直接走降级,一次都不打上游;
  • 半开(探测):熔断一段时间后,放少量请求过去试探。如果成功,回到关闭;如果还失败,回到打开并延长熔断时间。

半开这一步很关键。没有它,熔断器就只是一个定时的开关——时间到了不管上游好没好都全量放行,容易造成二次冲击。

一个极简的实现(生产环境建议直接用 Resilience4j 或 Sentinel,这里是为了看清机制):

public final class SimpleCircuitBreaker {
   

    private enum State {
    CLOSED, OPEN, HALF_OPEN }

    private final int failureThreshold;      // 连续失败多少次触发熔断
    private final long openDurationMs;       // 熔断保持多久
    private final int halfOpenPermits;       // 半开状态放行几个探针

    private State state = State.CLOSED;
    private int consecutiveFailures = 0;
    private int halfOpenInFlight = 0;
    private long openedAt = 0L;

    /** 判断当前是否允许放行 */
    public synchronized boolean tryAcquire() {
   
        long now = System.currentTimeMillis();
        switch (state) {
   
            case CLOSED:
                return true;
            case OPEN:
                if (now - openedAt >= openDurationMs) {
   
                    state = State.HALF_OPEN;
                    halfOpenInFlight = 1;
                    return true;                 // 第一个探针
                }
                return false;                    // 熔断中,直接走降级
            case HALF_OPEN:
                if (halfOpenInFlight < halfOpenPermits) {
   
                    halfOpenInFlight++;
                    return true;
                }
                return false;                    // 探针够了,其余走降级
            default:
                return false;
        }
    }

    public synchronized void onSuccess() {
   
        consecutiveFailures = 0;
        halfOpenInFlight = 0;
        state = State.CLOSED;                    // 有一例成功就恢复
    }

    public synchronized void onFailure() {
   
        switch (state) {
   
            case HALF_OPEN:
                state = State.OPEN;              // 探针失败,重新熔断
                openedAt = System.currentTimeMillis();
                halfOpenInFlight = 0;
                break;
            case CLOSED:
                if (++consecutiveFailures >= failureThreshold) {
   
                    state = State.OPEN;
                    openedAt = System.currentTimeMillis();
                }
                break;
            default:
                break;
        }
    }
}

哪些失败应该计入熔断统计,这是最容易做错的一点。

  • 要计入:连接超时、首字超时、5xx、429。这些反映上游的真实健康度。
  • 不要计入:400 参数错误、401/403 鉴权失败、内容被安全策略拦截。这些是你的请求有问题,不是上游有问题。把它们计入,会出现"因为代码里一个参数写错,导致熔断器打开,全站 AI 功能被降级"这种荒唐事故。

这个区分必须做对,否则熔断器会变成一个故障放大器。


四、降级的四个层次

前面讲的是"什么时候降级",这一节讲"降成什么样"。按代价从低到高,有四层可选。

第一层:换模型。

主用旗舰模型不可用时,切到备用模型。通常是"旗舰 → 轻量",副作用是质量下降和风格变化。

几个要点:

  • 备用模型要提前接通并测过。 上线时才第一次调用备用模型,等于把故障现场当测试环境。
  • 输出格式要兼容。 如果主模型走的是结构化输出,备用模型也必须支持同样的约束,否则降级后下游解析全崩——这比不降级更糟。
  • 降级要能被感知。 用户应该知道"当前用的是快速模式",而不是默默收到质量更差的结果然后开始怀疑系统。

第二层:换能力。

模型这条路走不通,就退到不依赖模型的能力。比如:

  • 规则/模板:把常见的几类问题用规则回答(FAQ 命中就用预设答案);
  • 历史结果:相同或近似的问题直接返回上次的结果;
  • 简化任务:把"生成一段分析"降级为"返回一段统计数据",让代码直接算。

这一层通常是体验下降但不至于不可用,是性价比最高的一层。

第三层:换交互。

把同步等待改成异步处理。用户提交后不等待,完成后通知。

// 降级路径:不阻塞用户,转为异步
if (!circuitBreaker.tryAcquire()) {
   
    taskQueue.submit(new LlmTask(requestId, prompt));
    return Response.accepted(requestId, "任务已受理,完成后将通知你");
}

这一层适合那些用户不一定立刻需要结果的场景,比如生成报告、批量处理。但如果是实时对话,改异步就等于功能不可用,要谨慎。

第四层:明确失败。

前面都不行,就老老实实告诉用户"现在用不了,请稍后再试"。

这听起来像放弃,但它是必须保留的一层。因为相比"给一个编造的答案",诚实地说"暂时不可用"是更好的选择。尤其在那些输出会被当作事实依据的场景(法律、医疗、财务),给出错误内容的代价远大于不给。

顺着这一层往下还有一个更细的决策:失败时要不要重试按钮? 这里要区分两种失败:

  • 可重试的(超时、限流)→ 提示"请稍后重试",并说明大概多久;
  • 不可重试的(内容被拦、参数错误)→ 不要让用户反复重试,因为结果不会变。要提示用户换个说法或改输入。

五、缓存是最容易被忽略的降级手段

降级方案里最常见的漏洞是"没有东西可退"。而缓存恰好能补上这一层。

缓存有两种用法,在降级场景里都有效:

精确缓存:同一个问题、同样的上下文,直接返回上次的答案。

语义缓存:把问题向量化,相似度超过阈值就复用答案。这个在问答类场景里命中率相当可观,因为用户问法千变万化,但问题本身重复度很高。

从降级角度看,缓存的价值在于:它是一个完全不依赖上游的能力。 上游全挂的时候,缓存还能服务相当一部分请求。所以值得把"缓存命中"当成降级路径里优先级最高的一层——它比换模型好(质量不降),比换能力好(结果更贴合)。

但必须处理过期问题,这是缓存作为降级兜底时最容易出事的地方:

  • 如果缓存的是会变的信息(库存、价格、状态),降级时返回旧值可能造成实际损失。这种内容就不该进降级兜底。
  • 如果缓存的是不会变或变化很慢的信息(产品说明、政策条款、常见问题),拿旧值救急是完全可以接受的。

所以缓存要能区分这两类。一个务实的做法是给缓存项加一个"可降级"标记,只有标了这个的内容才允许在降级路径里使用。

还有一点:缓存命中时不要忘了记录。 因为降级期间命中率会突然升高,如果不记,你会看到上游调用量下降、以为流量掉了,实际是大量请求走了缓存。


六、重试的代价,在降级场景里要重新算一遍

第四篇算过一次重试成本:重试 2 次最坏是请求数的 3 倍。在降级场景里这个账更值得细算,因为故障期间的重试几乎没有产出。

具体来说:

  • 如果上游整体故障,你重试三次换来的大概率是三次失败。消耗的额度却是实打实的。
  • 重试还会延缓熔断触发。每次重试都算一次尝试,熔断器要花更久才统计到足够的连续失败,这期间所有请求都在白等。
  • 熔断打开期间,不要重试。 这一点要落实在代码里:熔断的判断应该在最外层,tryAcquire() 返回 false 就直接走降级,不要进入重试逻辑。

一个合理的组合顺序是:

熔断检查 → 限流检查 → 调用(含重试) → 降级

顺序不能乱。如果先重试再判断熔断,那么熔断就形同虚设——请求已经打了三次上游才被告知"不该打"。


七、小结与决策清单

把这一篇收成一份可以对照执行的清单。

故障类型对应的降级动作:

  • 连接超时 → 重试 1 次;不换模型
  • 首字超时 → 重试或切换到备用模型
  • 块间隔超时 → 按中断处理,明确告知用户内容不完整;不重试
  • 总时长超时 → 按失败处理,并提示"可能已完成,请稍后刷新"
  • 429 限流 → 退避后重试;若持续则走降级
  • 5xx 上游故障 → 触发熔断,走降级
  • 401/403 鉴权问题 → 不降级、不重试,立刻告警,这是配置问题
  • 400 参数错误 → 不降级,直接暴露,这是代码问题

降级层次的优先级:

  • 缓存命中(质量不降,最优)
  • 换模型(质量下降但可用)
  • 换能力(规则/模板/历史结果)
  • 换交互(同步改异步)
  • 明确失败(诚实告知,保留重试入口)

必须做对的三件事:

  • 熔断只统计上游原因的失败,别把参数错误算进去,否则一个 bug 能触发全站降级
  • 备用模型要提前接通并测过,包括输出格式的兼容性
  • 熔断打开期间不重试,顺序是"熔断 → 限流 → 调用 → 降级"

最后一句判断标准:如果上游现在全挂,你的接口会返回什么? 能立刻答出这个问题的系统,才算有了降级方案。


下一篇

阶段二到这里收尾:Prompt 会写了、模板能改了、token 算得清、并发控得住、坏掉有退路。

接下来进入阶段三,也就是整个系列里篇幅最大的一块——RAG。开始之前先要回答一个很多人跳过的问题:RAG 到底该不该做。它解决的问题很明确,但被滥用的程度也很高,很多项目上了向量库之后发现"还不如把文档直接贴进提示词"。下一篇就给一套判断标准,以及什么时候该用别的手段。


本文是「用 Java 接入大模型」系列第十一篇,阶段二收尾。前几篇分别讲了 Prompt 工程、提示词模板化、token 计数、并发与限流,建议按顺序看。文中熔断阈值与超时取值为实践起点,实际值请按你的上游 SLA 与压测结果标定;生产环境建议直接使用成熟的熔断组件而非自行实现。

目录
相关文章
|
15天前
|
JSON Java 数据格式
用 Java 接入大模型(七):Prompt 工程值不值得投入
本文深入剖析Prompt工程的真实价值:它仅在模型“没听懂”(而非“不知道”)时才有效。厘清适用边界——任务描述模糊、格式约束、术语对齐、示例引导等场景收益显著;而知识缺失、精确计算、实时数据等则应交由RAG、代码或微调。强调用测试集评估整体通过率,警惕边际收益递减。提示词解决“表达”,不解决“知识”与“能力”。
93 1
|
18天前
|
JSON Java 数据库
用 Java 接入大模型(六):让模型稳定返回 JSON
本文详解大模型结构化输出的工程实践:从Prompt约束、JSON Mode到Schema校验三层可靠性提升方案,强调语法合法≠语义正确,必须配合解析兜底与业务校验,让模型输出真正可被程序可靠消费。
111 1
|
19天前
|
运维 物联网 API
从架构视角拆解 LoRA:低秩适配的取舍边界
LoRA 用极少的可训练参数换来显存下降与部署灵活性,但它并非万能。本文从参数架构、训练架构、部署架构、成本架构四个层面,分析它的设计取舍与适用边界,帮助做技术选型判断。
136 1
|
云栖大会 开发者
收到阿里云【乘风者计划】博主证书和奖励
收到阿里云【乘风者计划】博主证书和奖励 2023年2月对我来说是一个很好的开端,因为我在1号就收到了阿里云寄给我的【乘风者计划】博主证书和奖励。好兆头啊! 我收到的是我获得的【技术博主】【星级博主】【专家博主】三个的奖品和证书,一快给我寄过来哒!
3495 2
收到阿里云【乘风者计划】博主证书和奖励
|
29天前
|
存储 NoSQL 安全
用 Java 接入大模型(五):多轮对话的上下文怎么管
本文深入剖析多轮对话的核心误区:大模型本身无记忆,所谓“遗忘”实为上下文管理失当。从messages结构化封装、token精准截断策略,到Redis会话存储与三步排错法,系统讲解如何可靠构建长程对话能力。
201 7
|
1月前
|
机器学习/深度学习 JSON 前端开发
用 Java 接入大模型(一):把流式输出跑通
本文是「用 Java 接入大模型」系列首篇,聚焦最易卡壳的流式输出:详解 SSE 协议原理、手写 60 行无依赖流式客户端(兼容通义千问/OpenAI),并揭示新手必踩的三大坑——不按行解析、漏传 `stream:true`、忽略空 content。代码即学即用,拒绝玄学,只讲真实可跑的 Java 实现。
221 1
|
8天前
|
人工智能 缓存 测试技术
阿里云Qwen3.8-Omni-Flash大模型介绍:模型能力、功能、适用场景、收费标准与便宜购买技巧
能听会看、还能说话?qwen3-omni-flash 是阿里云百炼的多模态选手:采用 Thinker-Talker MoE 架构,支持文本、图像、音频、视频输入及文本、语音输出,覆盖 119 种语言文本、20 种语音交互,最大上下文 65,536 Tokens(输入 49,152/输出 16,384)。支持 Function Calling,结构化输出、联网搜索、缓存与批量推理均不支持,输入限文本加单一模态,音视频≤150 秒。价格上,文本输入 1.8 元/百万 Tokens,纯文本输出 6.9 元,语音回复 62.6 元,以控制台为准。
|
10天前
|
存储 人工智能 安全
阿里云 2026 DeepSeek Harness 技术详解:开源 Agent 运行框架,Cordis 插件内核与本地部署实操指南
在AI智能体快速落地的阶段,很多开发者会陷入一个困境:市面上现有的Agent工具大多是封装完整的成品应用,模型调用逻辑、工具执行循环、存储会话机制全部写死在底层,开发者只能调整表层参数,一旦业务需要自定义任务调度规则、替换沙箱执行环境、改造模型接入适配器,修改成本极高,很难适配项目的个性化需求。DeepSeek Harness,简称dsh,是开源的生产级AI智能体执行脚手架,遵循“Agent = 模型 + Harness”的工程范式,大模型负责推理思考,而Harness作为控制中枢,承担环境交互、工具契约管理、安全沙箱、持久化记忆、任务循环调度的全部工作。
425 1
|
13天前
|
缓存 JSON 安全
最新版通义千问Qwen3.8-Max 开发者实操手册:多模态能力解析、长周期 Agent 任务部署与代码调用教程
在复杂专业任务、长周期智能体、百万字文档精读、多模态深度推理等场景,普通大模型常常出现逻辑断层、长距离信息遗忘、复杂推理精度不足的问题。通义千问Qwen3.8-Max作为Qwen系列旗舰基座大模型,专为高复杂度专业任务打造,总参数量达到2.4万亿,推理时动态激活仅950亿参数,兼顾顶尖推理能力与可控的推理开销。模型原生支持百万Token超长上下文窗口,原生集成多模态理解、函数调用、联网检索、代码沙盒执行、上下文缓存等全套能力,在文本推理、视觉理解、复杂代码工程、金融量化、法律文书分析等专业基准测试中取得优异成绩。开发者可以通过百炼平台API快速调用,也可依托开源权重做私有化部署,适配企业级智能
186 1
|
16天前
|
缓存 测试技术 API
通义千问Qwen3.8-Flash架构解析:GDN+QSA混合注意力原理与企业应用教程
综合来看,新版通义千问Qwen3.8‑Flash的核心优势在于百万上下文高速预填充、多模态理解、轻量化推理成本,MoE稀疏混合专家架构兼顾模型能力、推理速度与调用开销,适配批量文档处理、代码工程、多模态智能体等高并发业务场景。在实际开发使用时,需要结合业务场景选择是否开启思考模式,合理设计工具描述,做好上下文管理与异常处理,充分发挥模型高速长文本与跨模态推理能力,同时做好Token用量管控,平衡应用效果与调用成本。作为面向高并发业务的高速多模态基座模型,它可以大幅降低大规模智能体应用的开发成本,支撑企业级批量文档分析、自动化智能体项目落地。
151 3

热门文章

最新文章