对象存储 (COS / S3)
#组件 · #对象存储 · #COS · #S3 · #云存储 · #MinIO
海量非结构化数据的归宿——存储模型、访问控制、性能优化与典型应用场景。
一、概念模型
1.1 为什么不用文件系统
| 维度 | POSIX 文件系统 | 对象存储 |
|---|---|---|
| 数据模型 | 层级目录树 | 扁平命名空间 (Bucket → Object) |
| 扩展性 | 受限于单机/集群规模 | 近乎无限(水平扩展) |
| 元数据 | inode (有限) | K-V 数据库 (可海量) |
| 并发 | 锁竞争,目录瓶颈 | 无锁,Key 级别并发 |
| 数据冗余 | RAID / 副本 | 多副本 + 纠删码 (跨 AZ) |
| POSIX 兼容 | 完全兼容 | 受限(强最终一致性) |
| 成本 | 高(高性能硬件) | 低(通用硬件 + 纠删码) |
1.2 核心概念
mermaid
flowchart TB
User["用户 (腾讯云/AWS 账户)"]
subgraph Buckets["存储桶"]
B1["Bucket A"]
B2["Bucket B"]
B3["Bucket C"]
end
User --> B1
User --> B2
User --> B3
subgraph ObjA["Bucket A 对象"]
O1["Object (Key)<br/>+ Data<br/>+ Metadata<br/>+ VersionID"]
end
subgraph ObjB["Bucket B 对象"]
O2["Object (Key)<br/>+ Data<br/>+ Metadata<br/>+ VersionID"]
end
B1 --> ObjA
B2 --> ObjB| 概念 | 说明 | 限制 |
|---|---|---|
| Bucket | 存储桶,全局唯一名称,顶级命名空间 | 单用户最多 200 个 |
| Object | 存储对象,由 Key+Data+Metadata 组成 | 单 Object 最大 5TB (S3) / 48.82TB (COS) |
| Key | 对象的唯一标识,支持 / 分隔模拟目录 | 长度 < 1024B |
| Metadata | 用户自定义元数据 + 系统元数据 | 自定义元数据 < 2KB |
| VersionID | 版本号(开启版本控制后每次上传生成) | — |
二、存储类型
2.1 生命周期管理
数据访问热度:
热 ←───────────────────────────────→ 冷
STANDARD(标准) → STANDARD_IA(低频) → ARCHIVE(归档) → DEEP_ARCHIVE(深度归档)
毫秒级访问 毫秒级访问 分钟级取回(1-5min) 小时级取回(12-48h)
高可用(99.95%) 高可用 数据恢复成本高 最低成本| 类型 | 最小存储时长 | 最小计费大小 | 取回费用 | 适用 |
|---|---|---|---|---|
| STANDARD | — | — | ❌ | 热数据、网站、CDN |
| STANDARD_IA | 30 天 | 64KB | ❌ | 备份、灾难恢复 |
| ARCHIVE | 90 天 | 64KB | ✅ 按量 | 归档、合规 |
| DEEP_ARCHIVE | 180 天 | 64KB | ✅ 按量 | 长期归档 |
2.2 生命周期规则
xml
<!-- COS 生命周期配置 -->
<LifecycleConfiguration>
<Rule>
<ID>log-archival</ID>
<Filter>
<Prefix>logs/</Prefix> <!-- 对 logs/ 前缀生效 -->
</Filter>
<Status>Enabled</Status>
<Transition>
<Days>30</Days> <!-- 30天后转换 -->
<StorageClass>STANDARD_IA</StorageClass>
</Transition>
<Transition>
<Days>90</Days> <!-- 90天后归档 -->
<StorageClass>ARCHIVE</StorageClass>
</Transition>
<Expiration>
<Days>365</Days> <!-- 365天后删除 -->
</Expiration>
</Rule>
</LifecycleConfiguration>三、访问控制
3.1 三层控制体系
┌──────────────────────────────────────────────┐
│ 访问控制 │
│ │
│ 1. Bucket 策略 (Policy, JSON) │
│ · 基于资源的精细化权限 │
│ · 支持跨账号授权 │
│ · 粒度: Bucket / 前缀 / 对象 │
│ │
│ 2. ACL (访问控制列表) │
│ · 预定义组 (所有用户/认证用户) │
│ · 粒度: Bucket / 对象 │
│ │
│ 3. 临时密钥 (STS) │
│ · 临时 AK/SK + SessionToken │
│ · 精细权限 + 时效性 │
│ · 前端直传/移动端/第三方 │
└──────────────────────────────────────────────┘3.2 访问控制对比
| 方式 | 粒度 | 时效 | 适用 |
|---|---|---|---|
| Bucket Policy | Bucket/前缀/对象 | 永久 | 静态网站托管、跨账号分享 |
| ACL | Bucket/对象 | 永久 | 简单权限(即将废弃) |
| STS 临时密钥 | 细粒度, 可指定条件 | 15min-12h | 前端直传、移动端、CI |
3.3 预签名 URL
GET /photo.jpg
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=AKIDxxx/20240101/region/s3/aws4_request
&X-Amz-Date=20240101T000000Z
&X-Amz-Expires=3600 ← 1小时过期
&X-Amz-Signature=xxxx
适用: 临时下载/上传,无需暴露 AK/SK四、上传
4.1 上传方式
| 方式 | 限制 | 适用 |
|---|---|---|
| 简单上传 (PUT Object) | ≤ 5GB | 小文件 |
| 分片上传 (Multipart) | 必选>5GB,单块≥1MB, 最多10000块 | 大文件、网络不稳定 |
| 追加上传 (Append, COS) | 首次≤5GB, 后续≤5GB | 日志追加(S3不支持) |
| 表单上传 (POST Object) | ≤ 5GB | Web 页面直传 |
| 批量上传 (SDK/CLI) | — | 批量迁移 |
4.2 分片上传流程
1. Initiate Multipart Upload
POST /object?uploads
← UploadId
2. Upload Part (可并行, 每个 Part 返回 ETag)
PUT /object?partNumber=1&uploadId=xxx
PUT /object?partNumber=2&uploadId=xxx
PUT /object?partNumber=N&uploadId=xxx
3. Complete Multipart Upload (或 Abort)
POST /object?uploadId=xxx
Body: <CompleteMultipartUpload>
<Part><PartNumber>1</PartNumber><ETag>etag1</ETag></Part>
...
</CompleteMultipartUpload>
中断恢复: ListParts 获取已上传分片, 断点续传
中断清理: AbortMultipartUpload 删除碎片 (或生命周期自动清理)五、数据保护
5.1 冗余方案
| 方案 | 可用性 | 持久性 | 说明 |
|---|---|---|---|
| 单 AZ | 99.5% | 99.999999999% (11个9) | 同数据中心多副本 |
| 多 AZ | 99.95% | 99.9999999999% (12个9) | 跨可用区 |
| 跨域复制 | — | — | 跨 Region 异步复制 |
5.2 版本控制与 WORM
版本控制:
Bucket 开启版本控制
覆盖写入 → 生成新版本 (保留旧版本)
删除 → 标记 DeleteMarker (可恢复)
彻底删除 → 指定 VersionID 删除
WORM (Write Once Read Many, COS):
合规保留: 锁定期间任何人(含 root)不可删除/修改
对象锁定: Retention (保留期限) / Legal Hold (诉讼保留)5.3 加密
| 方式 | 说明 |
|---|---|
| SSE-COS (S3-SSE) | 服务端托管密钥, 自动加解密 |
| SSE-KMS | 使用 KMS 密钥, 审计日志 |
| SSE-C | 客户端提供密钥 |
| CSE (客户端加密) | 客户端本地加密后上传 |
六、访问加速
6.1 CDN 加速
用户 ──→ CDN 边缘节点 (就近访问)
│ 未命中
▼
对象存储源站
配置: 绑定 CDN 域名 + 回源鉴权
Bucket Policy 允许 CDN 回源
CDN 缓存规则 (按路径/后缀)6.2 全球加速(COS)
国内用户 ──→ 国内 CDN ──→ 国内 COS
海外用户 ──→ CloudFront/海外 CDN ──→ 海外 COS(跨域复制)6.3 数据取回加速
归档数据直接访问 → 需要先取回(分钟~小时级)
如果需要快速访问归档数据,预先按需转换存储级别,或使用批量取回。
七、事件通知
对象存储操作 → 触发事件 → 通知目标:
触发事件: PUT (上传), DELETE (删除),
POST (表单), Replication (复制完成)
通知目标: SCF 云函数 → 图片压缩/转码
CMQ/Kafka → 异步解耦
HTTP 回调 → 业务系统典型场景:上传图片 → 事件通知 → 云函数自动生成缩略图。
八、典型应用
静态网站托管
Bucket 开启静态网站托管
Index Document: index.html
Error Document: error.html
绑定自定义域名 + CDN数据湖
数据源 → 对象存储 (原始数据层)
↓ SQL 查询 (SelectObjectContent / Athena)
× → 数据处理 (EMR, Spark)
× → 机器学习 (SageMaker / TI-ONE)日志归档
应用日志 → 本地聚合 → 压缩 → 分片上传到 COS/S3 归档层
配合生命周期: 30天 → STANDARD_IA, 90天 → ARCHIVE, 365天 → 删除九、对比 COS vs S3
| 维度 | COS (腾讯云) | S3 (AWS) |
|---|---|---|
| 一致性 | 强一致性(所有Region) | 强一致性(2020.12 后) |
| 最大 Object | 48.82TB | 5TB |
| 存储类型 | STANDARD/IA/ARCHIVE/DEEP_ARCHIVE/MAZ | 类似 + Intelligent-Tiering/Glacier |
| 追加上传 | ✅ | ❌ |
| 清单 (Inventory) | ✅ | ✅ |
| Select | ✅ (CSV/JSON) | ✅ (CSV/JSON/Parquet) |
| 批量操作 | ✅ | ✅ S3 Batch Operations |
| 跨域复制 | ✅ (异步) | ✅ (CRR, SRR) |
对象存储 vs HDFS vs NAS vs 块存储选型
1. 四种存储模型对比
mermaid
flowchart TD
APP["应用层"]
subgraph "块存储"
B1["LUN / Volume (/dev/sdb)"]
B2["格式化为 ext4/XFS"]
B3["应用直接读写块"]
end
subgraph "文件存储 (NAS)"
N1["NFS / CIFS 挂载"]
N2["POSIX 文件接口"]
N3["多客户端共享"]
end
subgraph "对象存储"
O1["Bucket / Object"]
O2["REST API (PUT/GET/DELETE)"]
O3["无限扩展 + 元数据"]
end
subgraph "HDFS"
H1["NameNode + DataNode"]
H2["文件分块 (128MB/块)"]
H3["本地磁盘 + 三副本"]
end
APP --> B3
APP --> N3
APP --> O3
APP --> H3| 维度 | 块存储 (EBS/CBS) | NAS (NFS/EFS) | 对象存储 (S3/COS) | HDFS |
|---|---|---|---|---|
| 接口 | 块设备 (/dev/sdb) | POSIX 文件系统 | REST API | HDFS API |
| 访问方式 | 挂载给单实例 | 多客户端共享挂载 | HTTP(S) | 客户端 SDK |
| 扩展性 | 单卷最大 32-64TB | PB 级 | 无限 (EB 级) | PB 级 |
| 延迟 | < 1ms (本地 SSD) | 1-5ms (同 AZ) | 10-100ms | 10-50ms |
| 一致性 | 强一致 | 强一致 | 强一致 (2020+, S3) | 写一次、读多次 |
| 修改 | 随机读写 | 随机读写 | 不支持部分修改 (覆盖整个 Object) | 不支持修改 (追加) |
| 元数据 | 无 | 文件属性 | 丰富的自定义元数据 | 有限 |
| 成本 | 高 ($0.08-0.12/GB/月) | 中-高 | 低 ($0.02/GB/月) | 中等 (自建硬件) |
| 适合 | 数据库 | 共享文件 / 容器存储 | 图片/视频/备份/日志 | 大数据批处理 |
2. S3 一致性模型演进
text
2020 年 12 月之前:
S3 是"最终一致性"模型
- PUT 新对象: 写后读一致性 (read-after-write)
- PUT 覆盖已有对象: 最终一致性 (可能读到旧版本)
- DELETE: 最终一致性 (可能仍然能读到)
2020 年 12 月之后:
S3 是"强一致性"模型
- 所有 PUT/DELETE 操作都是强一致的
- 写完立即能读到最新版本
- 不需要等待,不需要重试
为什么能实现强一致性?
- 内部使用 Paxos/Raft 类共识算法
- 元数据服务从最终一致升级为强一致
- 数据复制路径优化
对应用的影响:
- 2020 年前: 覆盖写入后需要等待或重试
- 2020 年后: 不需要考虑一致性延迟3. 对象存储不适合什么?
text
❌ 不适合:
- 数据库存储 (需要 < 1ms 延迟 + 随机写)
- 高频小文件随机读写 (HTTP 开销 > 磁盘开销)
- 需要文件锁/NFS 共享语义的场景
- 需要 POSIX 权限模型
✅ 适合:
- 静态资源 (图片/视频/JS/CSS)
- 备份/归档/日志
- 数据湖 (Parquet/ORC/Avro)
- 大数据计算中间结果
- 容器镜像仓库4. 选型决策树
mermaid
flowchart TD
Q1{"需要 < 5ms 延迟?"} -->|"是"| Q2{"需要多实例共享?"}
Q2 -->|"是"| NAS["NAS (NFS/EFS)"]
Q2 -->|"否"| Block["块存储 (EBS/CBS)"]
Q1 -->|"否"| Q3{"需要修改已写入数据?"}
Q3 -->|"是"| NAS2["NAS 或 块存储"]
Q3 -->|"否"| Q4{"大数据生态 (Spark/Hive)?"}
Q4 -->|"是"| HDFS["HDFS / S3 Compatible"]
Q4 -->|"否"| S3["对象存储 (S3/COS)"]参考
十、纠删码(Erasure Coding)原理
多副本 vs 纠删码
mermaid
flowchart TB
subgraph Replica["三副本方案"]
R1["原始数据 1GB"]
R2["副本 1: 1GB"]
R3["副本 2: 1GB"]
R4["副本 3: 1GB"]
RNote["总存储: 3GB<br/>存储效率: 33%<br/>容忍故障: 2 节点"]
end
subgraph EC["纠删码 (4+2)"]
D1["数据块 1: 256MB"]
D2["数据块 2: 256MB"]
D3["数据块 3: 256MB"]
D4["数据块 4: 256MB"]
P1["校验块 1: 256MB"]
P2["校验块 2: 256MB"]
ECNote["总存储: 1.5GB<br/>存储效率: 67%<br/>容忍故障: 2 节点"]
end| 维度 | 三副本 | 纠删码 (4+2) | 纠删码 (8+4) |
|---|---|---|---|
| 存储效率 | 33% | 67% | 67% |
| 容忍故障数 | 2 | 2 | 4 |
| 读性能 | 高(任一副本) | 中(需读 k 块) | 中 |
| 写性能 | 中(写 3 份) | 低(编码计算) | 低 |
| 修复开销 | 低(复制 1 份) | 高(需读 k 块重算) | 高 |
| 适用 | 热数据、小文件 | 温/冷数据、大文件 | 归档数据 |
纠删码工作原理
Reed-Solomon 编码(最常用):
原始数据: D1, D2, D3, D4 (4 个数据块)
编码矩阵 × 数据向量 = 编码向量
┌ ┐ ┌ ┐ ┌ ┐
│ 1 0 0 0 │ │ D1 │ │ D1 │ ← 数据块(原样保留)
│ 0 1 0 0 │ │ D2 │ │ D2 │
│ 0 0 1 0 │ × │ D3 │ = │ D3 │
│ 0 0 0 1 │ │ D4 │ │ D4 │
│ a b c d │ │ │ │ P1 │ ← 校验块(线性组合)
│ e f g h │ │ │ │ P2 │
└ ┘ └ ┘ └ ┘
恢复:任意丢失 2 块,用剩余 4 块 + 逆矩阵恢复
实际实现:
- Intel ISA-L 库(SIMD 加速,吞吐 10GB/s+)
- MinIO 使用 Go 实现 + SIMD 优化
- AWS S3 内部使用自研纠删码对象存储中的纠删码应用
AWS S3 Standard:
跨 3 个 AZ,每个 AZ 内部使用纠删码
→ 11 个 9 的持久性 (99.999999999%)
腾讯云 COS:
标准存储: 跨 AZ 纠删码
归档存储: 更高冗余度的纠删码(容忍更多故障)
MinIO (自建):
默认 EC:4(4 数据 + 4 校验,50% 效率)
可配置 EC:2 到 EC:8十一、分片上传性能调优
分片大小选择
分片大小直接影响上传性能,需要根据网络带宽和文件大小权衡:
| 文件大小 | 推荐分片大小 | 分片数 | 原因 |
|---|---|---|---|
| 5MB - 100MB | 5MB | 1-20 | 小文件,分片开销不值得 |
| 100MB - 1GB | 16-32MB | 4-64 | 平衡并发和开销 |
| 1GB - 10GB | 64-128MB | 8-160 | 减少分片数,降低 Complete 开销 |
| 10GB+ | 128-256MB | 40-80 | 大分片减少 HTTP 请求数 |
并发上传调优
go
// Go SDK 分片上传最佳实践
type UploadConfig struct {
PartSize int64 // 分片大小
Concurrency int // 并发数
MaxRetries int // 单片重试次数
}
// 根据网络带宽自适应配置
func OptimalConfig(fileSize int64, bandwidthMbps int) UploadConfig {
// 分片大小:确保每片上传时间 2-10 秒(避免超时)
partUploadTime := 5 * time.Second
partSize := int64(bandwidthMbps) * 1024 * 1024 / 8 * int64(partUploadTime.Seconds())
// 限制范围
if partSize < 5*1024*1024 {
partSize = 5 * 1024 * 1024 // 最小 5MB
}
if partSize > 256*1024*1024 {
partSize = 256 * 1024 * 1024 // 最大 256MB
}
// 并发数:带宽 / 单片带宽,但不超过 CPU 核数
concurrency := bandwidthMbps / (int(partSize) * 8 / 1024 / 1024 / 5)
if concurrency < 2 {
concurrency = 2
}
if concurrency > runtime.NumCPU()*2 {
concurrency = runtime.NumCPU() * 2
}
return UploadConfig{
PartSize: partSize,
Concurrency: concurrency,
MaxRetries: 3,
}
}性能对比数据
测试环境:1Gbps 带宽,上传 5GB 文件到 COS
┌──────────────────────────────────────────────────┐
│ 分片大小 │ 并发数 │ 耗时 │ 实际带宽利用率 │
├──────────┼────────┼─────────┼──────────────────┤
│ 5MB │ 4 │ 180s │ 22%(HTTP 开销大)│
│ 16MB │ 4 │ 85s │ 47% │
│ 64MB │ 4 │ 52s │ 77% │
│ 64MB │ 8 │ 45s │ 89% │
│ 128MB │ 8 │ 43s │ 93% ← 最优 │
│ 256MB │ 8 │ 44s │ 91%(单片失败代价大)│
└──────────────────────────────────────────────────┘
结论:
- 分片太小 → HTTP 请求开销占比大
- 分片太大 → 单片失败重传代价大
- 最优点:单片上传时间 3-8 秒断点续传实现
go
// 断点续传核心逻辑
type UploadCheckpoint struct {
UploadID string `json:"upload_id"`
FilePath string `json:"file_path"`
FileSize int64 `json:"file_size"`
FileModTime time.Time `json:"file_mod_time"`
PartSize int64 `json:"part_size"`
Parts []CompletedPart `json:"parts"` // 已完成的分片
}
func ResumeUpload(checkpoint *UploadCheckpoint) error {
// 1. 验证文件未被修改
info, _ := os.Stat(checkpoint.FilePath)
if info.Size() != checkpoint.FileSize || info.ModTime() != checkpoint.FileModTime {
return errors.New("file changed, restart upload")
}
// 2. 查询已上传的分片(ListParts)
uploaded := listParts(checkpoint.UploadID)
// 3. 只上传缺失的分片
totalParts := (checkpoint.FileSize + checkpoint.PartSize - 1) / checkpoint.PartSize
for i := int64(0); i < totalParts; i++ {
if !isPartUploaded(uploaded, i+1) {
uploadPart(checkpoint.UploadID, i+1, checkpoint.PartSize)
}
}
// 4. Complete
return completeMultipartUpload(checkpoint.UploadID)
}
登录后即可发表评论 👇