Cloudflare R2 是 Cloudflare 推出的 S3 兼容对象存储服务:它提供与 AWS S3 几乎一致的 API,可以直接用 AWS SDK 操作,但与 S3 最大的区别在于——R2 不收取任何出口流量费(egress fee),数据从 R2 分发到全球用户完全免费。这直接命中了 S3 最被诟病的成本黑洞:当你的文件被大量下载时,S3 的出口费往往是存储费的数倍甚至数十倍。
R2 典型适用场景:
- 静态资源图床与文件托管
- SaaS 产品的用户上传文件存储
- 备份与归档
- 配合 Cloudflare CDN 的全球内容分发
- 从 S3 迁移以降低带宽成本
对 Cloudflare 平台整体还不熟悉的读者,可以先阅读 Cloudflare 详解:从 CDN 到全球边缘计算平台。
为什么 R2 便宜:成本模型对比
对象存储的账单由三部分构成:存储费、请求费、出口流量费。三者的定价策略决定了总成本。
| 项目 | AWS S3(us-east-1) | Cloudflare R2 |
|---|---|---|
| 存储费 | $0.023 / GB / 月 | $0.015 / GB / 月 |
| Class A 请求(写/列) | $5.00 / 百万次 | $4.50 / 百万次 |
| Class B 请求(读) | $0.40 / 百万次 | $0.36 / 百万次 |
| 出口流量费 | 约 $0.09 / GB(前 10TB) | $0 |
| 免费额度 | 5GB 存储(仅首年) | 10GB 存储 + 每月 100 万次 Class A + 1000 万次 Class B(长期有效) |
做一个典型场景测算:一个图床类 SaaS,存储 500GB 图片,每月被下载 5TB,读请求 2000 万次。
- S3 账单:存储 500 × $0.023 ≈ $11.5 + 请求约 $8 + 出口 5000GB × $0.09 ≈ $450,合计约 $470/月,其中 95% 是出口费。
- R2 账单:存储 500 × $0.015 = $7.5 + 请求约 $7.2 + 出口 $0,合计约 $15/月。
流量越大,差距越夸张。这正是"零出口费"三个字的含金量:它把对象存储从"按流量计费"变成了"按容量计费",成本曲线变得可预测。
快速上手:开通 R2 并获取 API Token
- 登录 Cloudflare Dashboard,左侧菜单选择 R2 Object Storage,首次使用需开通(免费额度内不扣费,但需要绑定支付方式)。
- 点击 Create bucket,输入 bucket 名称(全局唯一,小写字母与连字符),可选择 location hint(如
apac亚太、eeur东欧),创建完成。 - 获取 API 凭证:进入 R2 概览页,点击右侧 Manage R2 API Tokens → Create API Token,选择权限范围(建议按需只授予目标 bucket 的读写权限),创建后会得到三样东西:
- Access Key ID
- Secret Access Key(只显示一次,务必保存)
- 账号 ID(Account ID),用于拼接 endpoint
R2 的 S3 兼容 endpoint 格式固定为:
https://<account_id>.r2.cloudflarestorage.com
有了 endpoint + Access Key + Secret,任何 S3 工具链都能直接操作 R2。
S3 兼容实战:用 AWS SDK 连接 R2
JavaScript(AWS SDK v3)
import { S3Client, PutObjectCommand, GetObjectCommand,
ListObjectsV2Command, DeleteObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const r2 = new S3Client({
region: 'auto', // R2 必须填 auto
endpoint: 'https://<account_id>.r2.cloudflarestorage.com',
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
},
});
// 上传
await r2.send(new PutObjectCommand({
Bucket: 'my-bucket',
Key: 'images/avatar.png',
Body: fileBuffer,
ContentType: 'image/png',
}));
// 列举
const list = await r2.send(new ListObjectsV2Command({ Bucket: 'my-bucket', Prefix: 'images/' }));
// 删除
await r2.send(new DeleteObjectCommand({ Bucket: 'my-bucket', Key: 'images/avatar.png' }));
// 生成预签名 URL(有效期 1 小时,常用于让前端直传)
const uploadUrl = await getSignedUrl(
r2,
new PutObjectCommand({ Bucket: 'my-bucket', Key: 'uploads/' + crypto.randomUUID() }),
{ expiresIn: 3600 }
);
预签名 URL 是生产环境的标配做法:服务器只负责签发短时效 URL,文件流量不经过你的应用服务器,前端直接 PUT 到 R2,既省带宽又安全。
Python(boto3)
import boto3
s3 = boto3.client(
's3',
endpoint_url='https://<account_id>.r2.cloudflarestorage.com',
aws_access_key_id='<access_key_id>',
aws_secret_access_key='<secret_access_key>',
region_name='auto',
)
s3.upload_file('local.jpg', 'my-bucket', 'images/local.jpg')
obj = s3.get_object(Bucket='my-bucket', Key='images/local.jpg')
data = obj['Body'].read()
url = s3.generate_presigned_url(
'get_object',
Params={'Bucket': 'my-bucket', 'Key': 'images/local.jpg'},
ExpiresIn=3600,
)
要点只有一个:region 必须设为 auto,endpoint 指向你的账号专属地址,其余代码与操作 S3 完全相同。
Workers 绑定实战:免密钥访问 R2
如果你的应用跑在 Cloudflare Workers 上,不需要任何 Access Key——通过 binding 绑定后,Worker 可以直接以原生 API 操作 bucket,延迟极低。(Workers 本身的入门指南见 Cloudflare Workers 入门实战)
wrangler.toml 配置:
name = "file-service"
main = "src/index.js"
compatibility_date = "2026-07-01"
[[r2_buckets]]
binding = "MY_BUCKET" # 代码中通过 env.MY_BUCKET 访问
bucket_name = "my-bucket"
Worker 代码:
export default {
async fetch(request, env) {
const url = new URL(request.url);
const key = url.pathname.slice(1);
if (request.method === 'PUT') {
// request.body 是 ReadableStream,直接流式写入 R2,不占内存
await env.MY_BUCKET.put(key, request.body, {
httpMetadata: { contentType: request.headers.get('content-type') },
customMetadata: { uploader: 'user-123' },
});
return new Response('Uploaded', { status: 201 });
}
if (request.method === 'GET') {
const object = await env.MY_BUCKET.get(key);
if (object === null) return new Response('Not Found', { status: 404 });
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set('etag', object.httpEtag);
// object.body 是流,大文件不经过 Worker 内存直接转发给客户端
return new Response(object.body, { headers });
}
if (request.method === 'DELETE') {
await env.MY_BUCKET.delete(key);
return new Response(null, { status: 204 });
}
},
};
R2Object 上的常用属性:body(ReadableStream)、httpEtag、size、uploaded、customMetadata、httpMetadata。get 还支持 range 参数做分段读取,配合流式响应可以轻松实现视频拖拽播放。list() 方法支持分页与前缀过滤,适合做文件管理后台。
这套"Workers + R2"组合就是 JAMstack 架构深度解析 中"边缘函数 + 边缘存储"理念的落地形态:没有源站、没有服务器,文件在 Cloudflare 网络内闭环。
典型应用场景
静态资源图床
创建 bucket 后,在 bucket 设置中开启 Public Access:可以直接启用 Cloudflare 分配的 r2.dev 子域名,更推荐绑定自定义域名(如 cdn.example.com,要求域名 DNS 托管在 Cloudflare)。绑定后文件即可通过 https://cdn.example.com/images/a.png 公开访问,且自动享有 CDN 缓存与 HTTPS。前端上传走预签名 URL 或 Workers 中转,读取走公开域名,读写分离。
备份与归档
数据库每日导出后推送到 R2,10GB 免费额度足够大多数个人项目。R2 支持生命周期规则(Lifecycle Rules),可自动删除超过 N 天的备份文件。
用户上传文件
SaaS 产品的头像、附件、导出文件等,用 Workers 做鉴权网关:验证登录态后从 R2 取文件返回,私有内容也能享受零出口费。
与 CDN 配合分发
R2 绑定自定义域名后天然跑在 Cloudflare CDN 上,可在 Cache Rules 中调整缓存策略,热门文件直接命中边缘缓存,回源 R2 的请求(Class B)费用也极低。与 Cloudflare Pages 完全指南 中托管的前端站点配合,就是一套完整的零出口费静态架构。
从 S3 迁移到 R2
rclone 全量迁移
rclone 原生支持 R2,配置一个 remote 即可:
rclone config
# 新建 remote:类型选 s3,provider 选 Cloudflare
# endpoint 填 https://<account_id>.r2.cloudflarestorage.com
# 迁移命令(先 dry-run 验证)
rclone sync s3src:source-bucket r2dst:my-bucket --progress --dry-run
rclone sync s3src:source-bucket r2dst:my-bucket --progress --transfers 16
sync 会按 key 和 checksum 增量同步,中断后重跑即可续传。
Sippy 增量迁移
对于不能停机的生产系统,Cloudflare 提供 Sippy:在 bucket 设置中配置源 S3 的凭证后,R2 会在每次对象被请求时自动从 S3 拉取并存入 R2(copy-on-read),未迁移的冷数据继续回源。流量逐步切换,最终实现无感迁移,迁完后关闭 Sippy 即可。
注意事项与 S3 差异
- API 兼容性:R2 实现了 S3 的核心 API(PUT/GET/DELETE/列举/分片上传/预签名),但不支持 bucket ACL、对象级 ACL 等边缘特性,依赖这些功能的程序需要改造。
- 一致性模型:R2 是强一致的——写入成功后立即可读,不需要像早期 S3 那样处理最终一致性问题。
- 区域模型:R2 没有传统意义的 region,数据按 location hint(
apac、eeur、wnam等)就近存储,由 Cloudflare 网络负责全球分发。创建后 location 不可修改,只能在创建时通过wrangler.toml的location字段或 Dashboard 指定。 - r2.dev 限速:公开的
r2.dev子域名有速率限制,生产环境务必绑定自定义域名。 - 幂等与重试:SDK 默认重试策略即可,大文件务必使用分片上传(multipart),R2 完全兼容。
常见问题(FAQ)
Cloudflare R2 真的不收流量费吗?
真的。无论数据从 R2 流向互联网、流向 Workers 还是通过自定义域名经 CDN 分发,出口流量一律 $0。账单只包含存储费($0.015/GB/月)和请求费(Class A/B)。这也是 R2 与 S3、GCS、Azure Blob 最本质的商业模式差异。
R2 和 S3 的性能差距大吗?
写入和单次读取的绝对延迟 S3 略占优(同区域场景),但 R2 绑定自定义域名后读取走 Cloudflare 全球 CDN 缓存,终端用户的实际访问延迟通常比直连 S3 更低。对图床、静态资源、下载分发这类读多写少的场景,R2 体验往往更好;超高频小对象随机写场景建议实测后再定。
如何公开访问 R2 bucket 里的文件?
两种方式:一是在 bucket 设置中开启 r2.dev 公共开发 URL(有限速,仅适合测试);二是绑定自定义域名,要求该域名 DNS 托管在 Cloudflare,绑定后自动签发 HTTPS 证书并接入 CDN,这是生产推荐做法。私有文件则通过 Workers 鉴权读取或预签名 URL 访问。
R2 的免费额度是多少?
长期免费额度为:10GB 存储、每月 100 万次 Class A 操作(写、列)、每月 1000 万次 Class B 操作(读),出口流量永久免费。个人图床、博客附件、小型备份基本零成本。超出部分按量计费,单价也低于 S3。
R2 能直接当图床用吗?
完全可以,而且是目前性价比最高的图床方案之一:上传走预签名 URL 或 Workers 中转,访问走绑定的自定义域名,配合 CDN 缓存后绝大多数请求不产生 R2 操作费。配合自定义域名与缓存规则,10GB 免费额度能支撑一个中型内容站点的全部图片流量。
相关阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。