You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 13:35:22