接口流式输出断了怎么排查

接入与开发

帮你按顺序定位流式接口半路断掉的原因,覆盖连接、超时、缓冲、网关与客户端几个最常见的卡点,给出可照着做的排查步骤。

流式接口最烦人的地方是它不像普通请求那样干脆失败,而是先吐了几个字再没下文,日志里还看不出错。这篇不讲协议原理,只给一套从客户端到服务端的排查顺序,以及每一层该看什么、该改什么;每步都能独立验证,不用把整套链路推倒重来。

流式接口最烦人的地方是它不像普通请求那样干脆失败,而是先吐了几个字再没下文,日志里还看不出错。上一篇把「流式断了怎么办」讲透了止损和迁移,这篇只做一件事:教你从客户端到服务端按顺序定位断点,看看是哪一层把它掐了。

排查的关键不是把整条链路推倒重来,而是每一步都能独立验证。下面给出一套排查顺序,配上每层该看什么、怎么改、什么情况下这套判断不适用。你照着走一遍,大多数断流问题能落到具体某一跳。

先把「断了」拆成三种不同的病

「断了」是个笼统说法,实际至少分三种表现,排查方向完全不同。先别急着改代码,先把现象归类,归类错了后面白忙。

表现典型原因方向先查什么
首字一直不来连接建立失败 / 首字超时网络连通性、鉴权、上游是否开始生成
吐一半停住空闲超时 / 缓冲 / 上游提前结束两次数据间隔、代理缓冲开关
收不到结束标记结束标记未透传 / 连接被回收上游返回内容、中间层转发逻辑
  • 记录断开是否稳定复现:每次都断在同一位置,多半和缓冲或大小上限有关;断点随机,往网络和超时查。
  • 记录断开是否和内容长度相关:短内容正常、长内容必断,优先怀疑总超时和网关缓冲。
  • 同一份请求换非流式调用一次做对照,如果非流式正常,问题就被限定在流式链路上,而不是模型本身。

客户端这一层先排干净

很多「断流」其实是客户端自己没读对。最常见的是用了默认会缓冲完整响应体的 HTTP 客户端,它压根没在流式读,只是等服务端全部返回,看起来就像半路卡住。

  1. 确认是逐块读取检查代码里是否显式按 chunk 或按行消费响应体,而不是 await 整个响应再解析。验证方式:在读取循环里打时间戳,看是不是边收边打。
  2. 检查客户端超时设置读取超时和空闲超时如果短于服务端生成一段内容的时间,就会半路掐断。临时把这两个值放大测一次,能断就说明是客户端掐的。
  3. 检查解析逻辑逐行读取要正确处理空行和心跳注释行,解析写错也会表现为「断了」。拿一段固定样本离线跑一遍解析函数,能快速排除。

这一层排干净的标准是:本地能稳定收到一段完整流。如果这步都过不了,别往下查,先修客户端。

超时不是一个值,而是一条链

从客户端到网关再到上游,每一层都有自己的超时。被哪一层掐,取决于哪一层的值最小。把所有超时当成一个值调,是排查里最常见的误区。

连接超时
建立 TCP 连接的时间上限,连不上一般直接报错而不是断流。
首字超时
从发出请求到收到第一个数据块的时间上限。
空闲超时
两次数据之间的间隔上限,不是总时长,最容易被忽略。
总超时
整段响应从头到尾的时间上限。

排查动作:在每一层把实际耗时打出来,对照各层配置,看是谁先到点。空闲超时尤其要看,长内容生成时两次数据之间可能隔得比你想的久。具体各层的合理档位,建议到站内对照当时的口径,别照抄别人的数值。

  • 什么情况下不适用:如果断点固定在内容开头附近,多半不是空闲超时,往首字超时和鉴权查。
  • 如果每次都在固定字数附近断,且客户端超时放很大也断,往网关缓冲查,不要死磕超时。

中间层的缓冲与压缩最会装死

反向代理和网关默认可能开启响应缓冲,它会把上游的数据攒着,攒够一批再发。对流式接口来说,这就是「吐一半停住」的经典原因,而且日志里通常什么都不报。

中间层动作对流的典型影响处理方向
响应缓冲数据被攒着不发,看着像卡住针对流式路径单独关闭缓冲
压缩分块边界被打乱,解析失败流式路径关掉压缩
字符集转换中途插入额外处理,拖慢节奏确认不做额外转码
请求体改写参数被改,上游行为异常检查改写规则是否误伤

自建中转的场景里,用逐层剥离的方式定位:先本地直连上游,跳过所有中间层;再一层层加回来,看断点在第几跳出现。这比盯着配置猜要快得多。判断标准是:每加一层,观察断开是否复现。

服务端与上游模型的这一侧

排完客户端和中间层,再往服务端和上游看。很多中断其实是上游提前结束,而中间层没把结束标记透传,客户端就一直等。

  • 确认上游返回的结束标记有没有正常透传,对比上游原始响应和你收到的最后一块。
  • 长内容生成时上游可能触发自身的超时或内容策略,表现为中途停住而不是报错,需要看上游侧的状态码和结束原因。
  • 并发高时连接被复用或提前回收也会断流,结合同一时刻的并发量一起看,别单独看单个请求的日志。

这一层的判断标准是:能不能拿到上游的原始结束原因。拿不到就先补日志,拿到了再决定是改上游参数还是改中间层转发。

让下一次断掉能直接复盘

排查断流最花时间的是复现。与其猜,不如提前把日志打成可复盘的样子,下次断了直接看数据。

  1. 给每个请求打唯一标识从客户端一直传到上游,确保整条链路能按同一个标识串起来查。
  2. 记录分段耗时与分块数把各段耗时、收到的分块数量、结束原因一并落盘,能一眼看出是慢还是被截。
  3. 留一小段原始分块样本断开时的原始内容留个样本,能看出是截断、乱码还是正常收尾,省去反复复现。

这套日志的落地标准是:拿到一个出问题的请求标识,能在几分钟内还原出它断在哪一层、断前发了多少数据。做不到就说明日志还缺字段。

断流的兜底与降级怎么写

定位清楚之后,还要有兜底,否则每次断流都会变成一次用户可见的失败。兜底的目标是让部分内容也能用,而不是追求永不断流。

  1. 部分内容即视为可用给流式调用加一层判断,收到部分内容就当作可用结果,避免整段丢弃。验证方式:模拟中途断开,看前端是否还能显示已收到的部分。
  2. 连续断开再切非流式连续断开的次数达到阈值时,自动切换到非流式重试一次,再决定是否向上暴露错误。阈值别设太小,偶发一次不该触发降级。
  3. 给界面上做收尾对已经吐给用户的内容做收尾处理,不要让界面永远停在转圈状态。验证方式:断开后看按钮是否恢复可点。
排查断流的核心不是把所有超时调大,而是知道被哪一层掐了。
下一步想先确认你用的中转站本身稳不稳,可以对着站内的可信度检测和测速结果看一眼,再决定是改自己的代码还是换接入点。

常见问题

为什么本地跑得好好的,一上线就断?

最常见的原因是上线路径上多了一层反向代理或负载均衡,它们默认会缓冲响应。先确认流式请求是否真的逐块到达客户端,再看代理的缓冲相关配置;本地没有这层,所以差别往往就出在这里。

怎么判断是网络问题还是服务端问题?

看断开的时间点分布。固定间隔断、且与内容长度无关,多半是某一层的超时;内容越长越容易断、或者断点位置随机,更偏向网络抖动或缓冲。最直接的办法是同一份请求换非流式跑一次做对照。

客户端要不要自己设读取超时?

要设,但不要设得太紧。建议给两次数据之间的空闲留出足够余量,而不是按整段响应时长来卡。具体留多少取决于你的上游出字速度,可以先量出延迟基线再定,站内接口测速页可以帮你先拿到这个基线。

流式断了重试,会不会重复计费?

通常已经生成并返回的部分仍然会被计入。重试前先判断是否值得——如果只差收尾,可以让客户端自己补结束状态;如果整段都不可用,再考虑换非流式重试一次。用量口径以站内用量与计费页面当时的口径为准。

换了一家中转站就不断了,是不是原来的有问题?

不一定,也可能只是两边超时与缓冲策略不同。判断前先固定客户端参数,只换接入地址做对比;如果新地址稳定而旧地址在同样参数下必断,再去核对服务状态与官方口径,别只凭一次体验下结论。

本文依据各平台公开文档与官方公告整理,只做中文归纳;具体做法、参数与限制以官方文档为准。