Toonflow 流式输出 SSE 断连后怎么恢复?事件 id、续传游标和心跳分别要怎么设置?

Toonflow 的流式输出要能断连恢复,需要三层配合:服务端为每条事件写入可持久化且单调递增的 id,客户端重连时通过 Last-Event-ID 或 query 参数回传游标,服务端从事件表补发缺失片段,并在超出保留窗口时回退成快照。只开启浏览器自动重连,只能恢复连接,丢失的片段不会自己回来。

Toonflow 流式输出 SSE 断连恢复不是某一个开关,而是「事件编号可持久化、客户端重连带游标、服务端按游标补发」三件事同时成立才生效。只在浏览器端打开自动重连,只能把 HTTP 连接重新建立起来,断连期间丢掉的片段不会自己回来;如果生成任务本身跑在服务端队列里,连接断开也不应该触发重新生成。 SSE 断连要拆成「连接断」和「内容断」两层来处理,恢复设置也必须分开做。连接层由 event-stream 响应里的 retry 字段和前端退避策略控制,决定多久重试一次;内容层由事件的 id 字段和业务游标决定,决定重连后从哪里接着推送。两层混在一起调整,最容易出现连接重连成功、但内容重复或跳段的结果。 服务端每条推送都必须带可持久化的单调 id,这是断点续传的前提条件。事件格式里的 id 行写全局递增序号,data 中同时带 task_id、seq 和分片内容;id 不能用单机内存自增,多实例部署时必须由 Redis INCR 或数据库序列统一发号,否则客户端连到另一台实例回放时会乱序。 原生 EventSource 重连时会自动在请求头带上 Last-Event-ID,服务端读取这个头就能定位断点位置。但 EventSource 不支持自定义请求头,鉴权只能走同域 Cookie 或短期有效的 query token;跨域场景还需要服务端在 Access-Control-Allow-Headers 里显式列出 Last-Event-ID,否则浏览器拿不到补发位置。 用 fetch 加 ReadableStream 自己解析 text/event-stream 时,游标要显式回传,不能依赖浏览器自动行为。把最后一个 id 同时存进内存和 sessionStorage,重连请求里带上 task_id 与 last_event_id,按 1 秒、2 秒、4 秒、8 秒做指数退避并封顶 30 秒,同时加随机抖动,避免大量客户端在同一秒回连压垮服务端。 服务端要有事件表或 Redis Stream 才能补发,只靠内存缓存会在扩容或重启后直接失效。事件按 task_id 分片写入,保留窗口按业务时长定,例如最近 5 分钟;重连时如果 last_event_id 仍在窗口内就逐条回放,超出窗口则返回一份当前完整文本快照加后续增量,避免全量重放带来的重复流量。 断连恢复一定会产生重叠,客户端必须按 seq 去重,渲染时使用累积缓冲而不是清屏重绘。服务端要保证同一 seq 的 data 内容稳定可复现,前端在追加前判断该 seq 是否已经存在,存在就跳过;这样即使补发时多推了几条,界面上也不会出现重复段落或文字来回跳跃。 心跳是让断连能被及时发现的关键设置,建议每 15 到 25 秒发一次注释行或独立的 heartbeat 事件。中间代理和负载均衡普遍对空闲连接设 60 秒超时,长时间没有内容输出时连接会被静默掐断,而客户端往往要等到下一次写入失败才感知;周期心跳能让半开连接尽快暴露并触发重连判断。 反向代理和 CDN 常常是 SSE 断连的真正原因,需要在网关侧关闭缓冲并放长读超时。Nginx 配置 proxy_buffering off、proxy_read_timeout 300s,响应头加 X-Accel-Buffering: no 和 Cache-Control: no-cache;部分 CDN 会对 chunked 响应做缓冲,需要单独确认其 SSE 支持情况,不要默认开启就能用。 生成任务要与 SSE 连接解耦,连接只是输出通道,不是任务本身。任务提交后进服务端队列,结果边生成边写入事件表;客户端重连时先拿一份 status 快照,确认任务是运行中、已完成还是失败,已完成的直接读快照即可,失败的任务重连没有意义,应走重新生成入口。 几种常见配置失效的原因值得单独排查:只调 retry 不写 id,重连后只能从头推;只加心跳没有事件表,重连后依然丢片段;把断连直接当成失败重跑的触发条件,会造成重复生成;多实例用本地内存存事件,负载均衡换一台就补不出来。配套要看三个指标:单位时间重连次数、补发事件条数、快照回退比例,补发比例突然升高通常说明网关超时被调短或心跳被关掉。 验收时按顺序自测:先用 curl -N 看原始事件流是否带 id 与 retry,再人工断网 10 秒观察请求头里的 Last-Event-ID,然后对比重连后前端文本是否连续且无重复,最后核对网关日志中的超时记录与事件表的游标位置,三处对得上才算真正配好。这套判断只适用于能拿到分片事件的服务端实现,如果上游是一次性返回整段结果,只能在服务端缓存已产出文本做快照式恢复,无法从中间片段续推。