前置阅读:建议先阅读 Cloudflare R2 对象存储实战。
关键概念:R2 的核心差异化优势是零出口费(Zero Egress),这意味着将数据从 R2 分发到全球的成本远低于 AWS S3。对于高流量分发场景,成本节省可达 80%+。
² 跨区域复制与一致性
R2 的多区域复制不是原生功能,需通过 Workers + Rules 实现:
// workers/r2-replication.ts export interface Env { R2_PRIMARY: R2Bucket; // 主区域 bucket R2_BACKUP: R2Bucket; // 备份区域 bucket REPLICATION_QUEUE: Queue; } export default { // 监听 PUT 事件(通过 R2 Event Notifications 或 Object Lifecycle Rules) async fetch(request: Request, env: Env) { const url = new URL(request.url); if (url.pathname === "/replicate") { const { bucket, key } = await request.json(); // 跨区域复制 const source = await env.R2_PRIMARY.get(key); if (!source) return new Response("Not found", { status: 404 }); await env.R2_BACKUP.put(key, source.body, { httpMetadata: source.httpMetadata, customMetadata: { ...source.customMetadata, replicated_at: new Date().toISOString(), source_bucket: bucket, }, }); return Response.json({ replicated: true, key }); } return new Response("OK"); }, // Queue Consumer:异步批量复制 async queue(batch: MessageBatch<any>, env: Env) { for (const message of batch.messages) { const { key } = message.body; const source = await env.R2_PRIMARY.get(key); if (source) { await env.R2_BACKUP.put(key, source.body, { httpMetadata: source.httpMetadata, }); } message.ack(); } }, };复制一致性模型:
策略 延迟 一致性 适用场景 同步复制 +200-500ms 强一致 金融交易记录 异步 Queue ~1-5s 最终一致 常规文件、日志 定时批处理 ~1h 最终一致 冷备份、归档 ³ 对象生命周期管理
R2 原生支持生命周期规则(S3 兼容 API):
# 通过 wrangler 配置生命周期(当前需通过 S3 API 或 Dashboard) # 规则示例:30天后转低频,90天后删除// workers/lifecycle-manager.ts // 自定义生命周期:基于 Workers 定时触发(Cron Triggers) export default { async scheduled(event: ScheduledEvent, env: Env) { // 每天执行一次清理 const { objects } = await env.R2_BUCKET.list({ prefix: "uploads/temp/", }); const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000; // 7 天前 const toDelete = []; for (const obj of objects) { const uploaded = new Date(obj.uploaded).getTime(); if (uploaded < cutoff) { toDelete.push(obj.key); } } // 批量删除(R2 支持一次最多 1000 个) if (toDelete.length > 0) { // 由于 R2 目前不原生支持批量删除 API,循环删除: await Promise.all( toDelete.map(key => env.R2_BUCKET.delete(key)) ); console.log(`Deleted ${toDelete.length} expired objects`); } }, };# wrangler.toml [triggers] crons = ["0 2 * * *"] # 每天凌晨 2 点⁴ Workers 流式上传与大文件处理
// workers/stream-upload.ts export default { async fetch(request: Request, env: Env) { if (request.method !== "POST") { return new Response("Method not allowed", { status: 405 }); } const url = new URL(request.url); const key = url.searchParams.get("key"); if (!key) return new Response("Missing key", { status: 400 }); // 直接流式转发(不加载到内存) // 适合大文件:视频、备份、日志 await env.R2_BUCKET.put(key, request.body, { httpMetadata: { contentType: request.headers.get("content-type") || "application/octet-stream", }, }); return Response.json({ uploaded: true, key }); }, };分片上传(Multipart Upload):
// workers/multipart-upload.ts // R2 支持 S3 兼容的分片上传 import { S3Client, CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; const s3 = new S3Client({ region: "auto", endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`, credentials: { accessKeyId: env.R2_ACCESS_KEY_ID, secretAccessKey: env.R2_SECRET_ACCESS_KEY, }, }); // 步骤 1: 初始化分片上传 export async function initiateMultipart(key: string) { const command = new CreateMultipartUploadCommand({ Bucket: "my-bucket", Key: key, }); const response = await s3.send(command); return response.UploadId; } // 步骤 2: 为每个分片生成预签名 URL(客户端直传) export async function getPresignedPartUrls( key: string, uploadId: string, totalParts: number ) { const urls = []; for (let i = 1; i <= totalParts; i++) { const command = new UploadPartCommand({ Bucket: "my-bucket", Key: key, UploadId: uploadId, PartNumber: i, }); const url = await getSignedUrl(s3, command, { expiresIn: 3600 }); urls.push({ partNumber: i, url }); } return urls; } // 步骤 3: 完成上传 export async function completeMultipart( key: string, uploadId: string, parts: { PartNumber: number; ETag: string }[] ) { const command = new CompleteMultipartUploadCommand({ Bucket: "my-bucket", Key: key, UploadId: uploadId, MultipartUpload: { Parts: parts }, }); return s3.send(command); }⁵ 图片/视频动态优化管道
使用 Cloudflare Image Resizing + Workers 构建动态图片服务:
// workers/image-optimization.ts export default { async fetch(request: Request, env: Env) { const url = new URL(request.url); const key = url.pathname.slice(1); // 去掉前导 / const width = parseInt(url.searchParams.get("w") || "0"); const format = url.searchParams.get("f") || "auto"; const quality = parseInt(url.searchParams.get("q") || "85"); // 检查缓存(KV 中存储已处理的变体) const cacheKey = `img:${key}:w=${width}:f=${format}:q=${quality}`; const cached = await env.CACHE.get(cacheKey, { type: "stream" }); if (cached) { return new Response(cached, { headers: { "Content-Type": `image/${format === "auto" ? "webp" : format}` }, }); } // 获取原始图片 const original = await env.R2_BUCKET.get(key); if (!original) return new Response("Not found", { status: 404 }); // 使用 Cloudflare Image Resizing(需开通 Image Resizing 服务) // 或者使用 Workers 集成 sharp/wasm(实验性) const imageUrl = new URL( `https://imagedelivery.net/${env.IMAGE_ACCOUNT_HASH}/${key}/public` ); if (width) imageUrl.searchParams.set("width", width.toString()); if (format !== "auto") imageUrl.searchParams.set("format", format); imageUrl.searchParams.set("quality", quality.toString()); const optimized = await fetch(imageUrl); // 缓存处理结果(24小时) const body = await optimized.arrayBuffer(); await env.CACHE.put(cacheKey, body, { expirationTtl: 86400 }); return new Response(body, { headers: { "Content-Type": optimized.headers.get("Content-Type") || "image/webp", "Cache-Control": "public, max-age=86400", }, }); }, };图片处理参数速查:
参数 说明 示例 w/width最大宽度 ?w=800h/height最大高度 ?h=600f/format输出格式 ?f=webp/?f=avifq/quality质量(1-100) ?q=85fit适应模式 ?fit=crop/?fit=contain⁶ 成本模型对比
月度假设:存储 5TB,月出口流量 20TB,1000 万次请求
服务商 存储费 出口费 请求费 月总计 vs R2 Cloudflare R2 $0 $0 ~$4 ~$4 基准 AWS S3 Standard $115 $1,800 $50 $1,965 490x AWS S3 + CloudFront $115 $1,200 $85 $1,400 350x Google Cloud Storage $104 $1,600 $40 $1,744 435x MinIO (自托管) $200 (VPS) 含在带宽 - $200 50x 注意:R2 的 “免费” 有使用上限。Pro 以上套餐含更大额度,超额后请求费 $0.36/百万。
⁷ 多云容灾架构
┌─────────────┐ 写 ┌─────────────┐ │ 应用服务器 │──────────→│ R2 (主) │ │ (Workers) │ │ 零出口费分发 │ └─────────────┘ └──────┬──────┘ │ │ │ 异步复制 (Queue) │ 读取 ▼ ▼ ┌─────────────┐ ┌─────────────┐ │ S3 (备份) │ │ 全球 CDN │ │ 合规/冷备 │ │ <50ms 延迟 │ └─────────────┘ └─────────────┘容灾切换逻辑:
class MultiCloudStorage { constructor( private primary: R2Bucket, private backup: S3Client, private healthCheck: () => Promise<boolean> ) {} async get(key: string) { try { if (await this.healthCheck()) { return await this.primary.get(key); } } catch (e) { console.warn("Primary failed, falling back to S3"); } // 灾备读取 return this.backup.send(new GetObjectCommand({ Bucket: "backup", Key: key })); } }⁸ S3 兼容 API 深度解析
R2 实现了绝大多数 S3 REST API,使得迁移极为平滑:
import { S3Client, ListBucketsCommand, ListObjectsV2Command, GetObjectCommand, PutObjectCommand, DeleteObjectCommand, CopyObjectCommand } from "@aws-sdk/client-s3"; const r2 = new S3Client({ region: "auto", endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`, credentials: { accessKeyId: env.R2_ACCESS_KEY_ID, secretAccessKey: env.R2_SECRET_ACCESS_KEY, }, }); // 列出所有 buckets async function listBuckets() { const result = await r2.send(new ListBucketsCommand({})); return result.Buckets; } // 分页列出对象(比 list() 更高效) async function listObjectsPaginated(bucket: string, prefix: string) { let continuationToken: string | undefined; const objects = []; do { const result = await r2.send(new ListObjectsV2Command({ Bucket: bucket, Prefix: prefix, ContinuationToken: continuationToken, MaxKeys: 1000, // 每页最多 1000 个 })); objects.push(...(result.Contents || [])); continuationToken = result.NextContinuationToken; } while (continuationToken); return objects; } // 服务端复制(不下载再上传) async function copyObject( sourceBucket: string, sourceKey: string, destBucket: string, destKey: string ) { await r2.send(new CopyObjectCommand({ Bucket: destBucket, Key: destKey, CopySource: `/${sourceBucket}/${encodeURIComponent(sourceKey)}`, })); }S3 API 兼容性速查:
S3 操作 R2 支持 注意事项 PutObject✅ 完全 单文件最大 5TB GetObject✅ 完全 支持 Range 请求 DeleteObject✅ 完全 — ListObjectsV2✅ 完全 MaxKeys ≤ 1000 Multipart Upload✅ 完全 每部分 5MB - 5GB CopyObject✅ 完全 跨区域复制额外计费 DeleteObjects⚠️ 部分 需循环单删替代 Object Lock❌ 不支持 合规存储需 S3 S3 Object Lambda❌ 不支持 Workers 可实现类似功能 ⁹ Workers 与 R2 深度集成模式
模式一:上传预签名 URL(直传)
避免大文件经过 Workers,减少 CPU 时间消耗:
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import { PutObjectCommand } from "@aws-sdk/client-s3"; export default { async fetch(request: Request, env: Env) { const key = `uploads/${crypto.randomUUID()}.zip`; const command = new PutObjectCommand({ Bucket: env.BUCKET_NAME, Key: key, ContentType: "application/zip", }); const presignedUrl = await getSignedUrl(s3, command, { expiresIn: 300, // 5 分钟有效期 }); return Response.json({ uploadUrl: presignedUrl, key }); }, };客户端直接使用预签名 URL 上传,无需经过 Workers:
const { uploadUrl, key } = await fetch("/get-upload-url").then(r => r.json()); await fetch(uploadUrl, { method: "PUT", body: file, headers: { "Content-Type": "application/zip" }, });模式二:边缘缓存 + R2 回源
export default { async fetch(request: Request, env: Env) { const url = new URL(request.url); const cacheKey = new Request(url.toString(), request); // 1. 检查边缘缓存(Cloudflare Cache API) const cache = caches.default; let response = await cache.match(cacheKey); if (response) return response; // 2. 回源到 R2 const object = await env.R2_BUCKET.get(url.pathname.slice(1)); if (!object) return new Response("Not Found", { status: 404 }); // 3. 构建响应并缓存 const headers = new Headers(); object.writeHttpMetadata(headers); headers.set("etag", object.httpEtag); headers.set("cache-control", "public, max-age=86400"); response = new Response(object.body, { headers }); ctx.waitUntil(cache.put(cacheKey, response.clone())); return response; }, };¹⁰ 冷数据归档与存储分层
R2 目前不提供 S3 Glacier 式的自动归档,但可通过 Workers Cron 实现:
// workers/tiered-storage.ts interface StoragePolicy { prefix: string; // 匹配前缀 hotDays: number; // 热数据保留天数 warmDays: number; // 温数据保留天数 coldAction: "delete" | "compress" | "move-bucket"; } const policies: StoragePolicy[] = [ { prefix: "logs/", hotDays: 7, warmDays: 30, coldAction: "compress" }, { prefix: "temp/", hotDays: 1, warmDays: 1, coldAction: "delete" }, { prefix: "archives/", hotDays: 365, warmDays: 730, coldAction: "move-bucket" }, ]; export default { async scheduled(event: ScheduledEvent, env: Env) { for (const policy of policies) { const now = Date.now(); const hotCutoff = now - policy.hotDays * 86400000; const warmCutoff = now - policy.warmDays * 86400000; const { objects } = await env.R2_BUCKET.list({ prefix: policy.prefix }); for (const obj of objects) { const uploaded = new Date(obj.uploaded).getTime(); if (policy.coldAction === "delete" && uploaded < warmCutoff) { await env.R2_BUCKET.delete(obj.key); } // compress/move-bucket 类似... } } }, };分层存储对比:
层级 存储位置 访问延迟 成本 适用数据 热(Hot) R2 + 边缘缓存 < 50ms 中等 活跃用户头像、热点资源 温(Warm) R2 原始存储 ~200ms 低 近期日志、备份文件 冷(Cold) 压缩归档 需解压 极低 历史日志、过期资产 ¹¹ 生产监控指标
使用 Workers Analytics 和 R2 内置指标监控存储健康:
// workers/metrics-collector.ts export default { async scheduled(event: ScheduledEvent, env: Env) { // 收集 R2 存储指标 const metrics = { totalObjects: 0, totalBytes: 0, byPrefix: {} as Record<string, { count: number; bytes: number }>, }; let truncated = true; let cursor: string | undefined; while (truncated) { const result = await env.R2_BUCKET.list({ cursor, limit: 1000 }); for (const obj of result.objects) { metrics.totalObjects++; metrics.totalBytes += obj.size; const prefix = obj.key.split("/")[0] || "root"; if (!metrics.byPrefix[prefix]) { metrics.byPrefix[prefix] = { count: 0, bytes: 0 }; } metrics.byPrefix[prefix].count++; metrics.byPrefix[prefix].bytes += obj.size; } truncated = result.truncated; cursor = result.truncated ? result.cursor : undefined; } // 推送到外部监控系统 await fetch(env.METRICS_ENDPOINT, { method: "POST", headers: { "Authorization": `Bearer ${env.METRICS_TOKEN}` }, body: JSON.stringify(metrics), }); }, };关键告警阈值:
- 单 Bucket 对象数 > 1M(ListObjectsV2 分页性能下降)
- 单对象大小 > 5GB(需使用 Multipart Upload)
- R2 操作错误率 > 0.1%
建议每周审查一次存储指标,及时发现异常增长趋势,优化成本结构并提前扩容。
延伸阅读:
- Cloudflare R2 对象存储实战 — 基础接入、S3 SDK、成本测算
- Cloudflare Workers AI 高级实战 — Workers 流式响应优化
- Vercel 与 Cloudflare Pages 对比 — 静态托管方案选型
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。