Cloudflare Worker中tee()导致R2上传受客户端下载速度限制的问题
问题分析与解决方案
现象确认
你遇到的问题确实是Cloudflare Worker中tee()的实现特性导致的:通过tee()拆分的两个流会共享同一个底层数据源,必须等待较慢的消费端读完当前数据块,才会继续读取下一块数据。因此当客户端下载速度慢时,R2上传的流会被同步卡住,最终触发ctx.waitUntil的30秒超时限制,导致大文件上传失败。
可行解决方案
1. 独立拉取数据源(规避流共享)
放弃tee(),改为发起两次独立的fetch请求,分别用于R2上传和客户端响应。这样两个流完全独立,R2上传不受客户端网速限制:
// 后台执行R2上传,不阻塞客户端响应 ctx.waitUntil((async () => { const r2Resp = await fetch(request); await env.BUCKET.put(objectName, r2Resp.body, { httpMetadata: r2Resp.headers }); })()); // 返回给客户端的响应 const clientResp = await fetch(request); return new Response(clientResp.body, clientResp);
注意:该方案会增加源站的请求量(同一文件拉取两次),需评估源站的带宽、请求次数成本。
2. R2分段上传(最优大文件方案)
将大文件拆分为固定大小的块(比如5MB/块),通过分段上传并行处理,既保证客户端流式下载,又避免单一流的背压问题,且仅拉取一次源站数据:
const originResp = await fetch(request); const contentType = originResp.headers.get('Content-Type') || 'application/octet-stream'; // 初始化R2分段上传 const uploadId = await env.BUCKET.createMultipartUpload(objectName, { httpMetadata: { contentType } }); const chunkSize = 5 * 1024 * 1024; // 5MB每块 const reader = originResp.body.getReader(); let chunks = []; let chunkIndex = 0; let currentChunkLength = 0; ctx.waitUntil((async () => { try { while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); currentChunkLength += value.length; // 达到块大小则上传当前段 if (currentChunkLength >= chunkSize) { const chunkBlob = new Blob(chunks); await env.BUCKET.uploadPart(objectName, uploadId, chunkIndex + 1, chunkBlob); chunks = []; currentChunkLength = 0; chunkIndex++; } } // 上传剩余的最后一块 if (chunks.length > 0) { const finalChunk = new Blob(chunks); await env.BUCKET.uploadPart(objectName, uploadId, chunkIndex + 1, finalChunk); } // 完成分段上传 await env.BUCKET.completeMultipartUpload(objectName, uploadId, { parts: Array.from({ length: chunkIndex + 1 }, (_, i) => ({ partNumber: i + 1 })) }); } catch (err) { // 上传失败时取消分段任务 await env.BUCKET.abortMultipartUpload(objectName, uploadId); throw err; } })()); // 同时向客户端返回流式响应 return new Response(originResp.body, originResp);
3. 调整waitUntil超时(临时缓解)
Cloudflare Worker允许在控制台将ctx.waitUntil的最长超时调整至15分钟,但这只是治标不治本——客户端慢的情况下,R2上传速度仍会被限制,仅适合小文件或临时应急场景。
内容的提问来源于stack exchange,提问作者Tamás Pollák
相关产品推荐
相关产品推荐

