Cloud Run部署后ReadableStream取消入队延迟5分钟原因咨询
问题:Google Cloud Run上SvelteKit流式API取消请求延迟5分钟触发
cancel() 我基于adapter-node搭建了SvelteKit API端点,用ReadableStream流式传输行情数据。退出客户端路由时,流式传输应该停止。在SvelteKit开发环境(npm run dev,即vite dev)或Windows桌面容器(node build)中,以下代码运行正常:
onDestroy(async () => { await reader.cancel(); // 停止流式传输 controller.abort(); // 中止fetch请求 });
但将Node容器构建部署到Google Cloud Run后,流式传输功能正常,但退出客户端路由时,API端点会持续流数据5分钟,之后才延迟触发ReadableStream的cancel()。
API代码(+server.js)
import { YahooFinanceTicker } from "yahoo-finance-ticker"; /** @type {import('./$types').RequestHandler} */ export async function POST({ request }) { const { logging, symbols } = await request.json(); const controller = new AbortController(); const ticker = new YahooFinanceTicker(); ticker.setLogging(logging); if (logging) console.log("api ticker", symbols); const stream = new ReadableStream({ start(controller) { (async () => { const tickerListener = await ticker.subscribe(symbols); tickerListener.on("ticker", (quote) => { if (logging) console.log("api", JSON.stringify(quote, ["id", "price", "changePercent"])); controller.enqueue(JSON.stringify(quote, ["id", "price", "changePercent"])); }); })().catch(err => console.error(`api listen exeption: ${err}`)); }, cancel() { // 5分钟后才触发!!! console.log("api", "cancel: unsubscribe ticker and abort"); ticker.unsubscribe(); controller.abort(); }, }); return new Response(stream, { headers: { 'content-type': 'text/event-stream', } }); }
路由代码(+page.svelte)
const controller = new AbortController(); let reader = null; const signal = controller.signal; async function streaming(params) { try { const response = await fetch("/api/yahoo-finance-ticker", { method: "POST", body: JSON.stringify(params), headers: { "content-type": "application/json", }, signal: signal, }); const stream = response.body.pipeThrough(new TextDecoderStream("utf-8")); reader = stream.getReader(); while (true) { const { value, done } = await reader.read(); if (logging) console.log("resp", done, value); if (done) break; // ... 处理行情数据的逻辑 } } catch (err) { if (!["AbortError"].includes(err.name)) throw err; } } // ...
原因分析
核心:Google Cloud Run的闲置连接超时机制
Google Cloud Run的负载均衡器默认会对闲置超过5分钟的HTTP连接强制断开,这就是你看到5分钟延迟的直接原因。本地/Windows容器环境没有这种平台级强制闲置超时,客户端取消请求后TCP连接会立即断开,服务器能快速感知并触发cancel()。
具体流程:
- 客户端调用
reader.cancel()和controller.abort()后,浏览器发送TCP FIN包终止连接,但Cloud Run的负载均衡器不会立即将断开信号转发给容器实例。 - 服务器端的
ReadableStream仍在持续enqueue数据,但数据无法送达客户端,连接进入"闲置"状态。 - 直到负载均衡器检测到连接闲置满5分钟,才会主动断开与容器的连接,此时服务器的
ReadableStream才会触发cancel()回调。
代码层面的放大因素
你的API代码还存在两个细节问题,加剧了延迟:
- 未监听请求的中止信号:服务器端
Request对象自带signal属性,你没有利用它主动监听客户端的中止请求,完全依赖ReadableStream的cancel()回调,而该回调仅在连接被断开时触发。 - 混淆了两个
controller:ReadableStream的cancel()回调里的controller是流控制器,此时调用abort()已无意义,且你混淆了流控制器和自己创建的AbortController。
解决办法
1. 监听请求中止信号,主动停止数据推送
修改+server.js,监听请求的signal,一旦客户端中止请求,立即取消订阅并终止流:
export async function POST({ request }) { const { logging, symbols } = await request.json(); const ticker = new YahooFinanceTicker(); ticker.setLogging(logging); if (logging) console.log("api ticker", symbols); // 监听客户端的请求中止信号 request.signal.addEventListener('abort', () => { console.log("api", "request aborted: unsubscribe ticker"); ticker.unsubscribe(); }); const stream = new ReadableStream({ start(controller) { (async () => { const tickerListener = await ticker.subscribe(symbols); tickerListener.on("ticker", (quote) => { // 提前检查请求是否已中止,避免无效入队 if (request.signal.aborted) { ticker.unsubscribe(); controller.close(); return; } if (logging) console.log("api", JSON.stringify(quote, ["id", "price", "changePercent"])); controller.enqueue(JSON.stringify(quote, ["id", "price", "changePercent"])); }); })().catch(err => console.error(`api listen exception: ${err}`)); }, cancel() { console.log("api", "stream canceled"); ticker.unsubscribe(); }, }); return new Response(stream, { headers: { 'content-type': 'text/event-stream', // 可选:设置更短的Keep-Alive超时,帮助Cloud Run更快检测闲置连接 'Connection': 'keep-alive', 'Keep-Alive': 'timeout=30' } }); }
2. 优化客户端取消逻辑
调整客户端取消顺序,先中止请求再取消阅读器:
onDestroy(async () => { controller.abort(); // 先中止fetch请求 if (reader) { await reader.cancel(); // 再取消阅读器 } });
3. 可选:调整Cloud Run连接超时
你可以在Cloud Run服务配置中设置更短的闲置超时(最小1分钟),让负载均衡器更快断开闲置连接。但更推荐通过代码监听中止信号的方式主动处理,比依赖平台超时更可靠。
内容的提问来源于stack exchange,提问作者voscausa
相关产品推荐
相关产品推荐

