mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2147 字
6 分钟
对象存储
2025-09-27

POSIX 文件系统的目录树、权限检查、文件锁——这些语义在单机上好用,到了超大规模反而成了枷锁。一个目录下放百万文件,ls 就卡死;跨节点维护一致性元数据的开销,随规模指数级增长。S3 的设计者做了一个大胆的决定:砍掉 POSIX 语义,只保留 Put/Get/Delete 三个操作。没有目录,没有权限树,没有文件锁——换来的是几乎无限的水平扩展能力。

对象存储用 Key-Object 模型替代目录树,用纠删码替代 RAID,用最终一致性替代强一致——这是为海量非结构化数据量身定制的存储范式。

一、对象存储概述#

1.1 从文件系统到对象存储#

graph TB subgraph 文件系统["文件系统"] DIR1["/data/"] --> DIR2["images/"] DIR2 --> FILE1["photo1.jpg"] DIR2 --> FILE2["photo2.jpg"] DIR1 --> DIR3["logs/"] DIR3 --> FILE3["app.log"] end subgraph 对象存储["对象存储"] BUCKET["Bucket: my-data"] OBJ1["images/photo1.jpg → 二进制数据 + 元数据"] OBJ2["images/photo2.jpg → 二进制数据 + 元数据"] OBJ3["logs/app.log → 二进制数据 + 元数据"] end style DIR1 fill:#ffe0b2,stroke:#e65100 style BUCKET fill:#c8e6c9,stroke:#2e7d32
维度文件系统对象存储
数据组织目录树扁平命名空间
访问方式POSIX APIHTTP REST API
元数据固定属性(inode)自定义元数据
一致性强一致通常最终一致
修改支持原地修改只支持整体替换
规模百万级文件万亿级对象
协议NFS/SMB/CIFSS3/Swift

1.2 对象存储的核心概念#

概念说明类比
Object数据 + 元数据 + 唯一标识文件
Bucket对象的命名空间目录/卷
Key对象的唯一标识(Bucket 内)文件路径
Metadata对象的自定义元数据扩展属性
ETag对象的内容哈希校验和
Version对象的版本号

二、S3 协议#

2.1 S3 API 核心操作#

# S3 核心操作
# 创建桶
aws s3 mb s3://my-bucket
# 上传对象
aws s3 cp photo.jpg s3://my-bucket/images/
# 下载对象
aws s3 cp s3://my-bucket/images/photo.jpg ./
# 列出对象
aws s3 ls s3://my-bucket/images/
# 删除对象
aws s3 rm s3://my-bucket/images/photo.jpg
# 分片上传(大文件)
aws s3 cp large-file.tar.gz s3://my-bucket/ --storage-class STANDARD_IA
# 生命周期管理
aws s3api put-bucket-lifecycle-configuration \
--bucket my-bucket \
--lifecycle-configuration file://lifecycle.json

2.2 S3 一致性模型#

操作S3 之前S3 现在(2020+)
写新对象原子(强一致)强一致
覆盖写最终一致强一致
删除最终一致强一致
列表最终一致强一致
Note

2020 年后,AWS S3 已对所有操作提供强一致性。但自建的 MinIO 和其他兼容 S3 的存储可能仍使用最终一致性模型。使用前需确认一致性保证。

2.3 S3 存储类别#

类别可用性访问频率存储成本访问成本最低存储时间
STANDARD99.99%频繁
STANDARD_IA99.9%不频繁较高30 天
ONEZONE_IA99%不频繁更低较高30 天
GLACIER99.9%归档极低90 天
DEEP_ARCHIVE99.9%长期归档最低最高180 天

三、MinIO 架构#

3.1 MinIO 整体架构#

graph TB subgraph MinIO["MinIO 架构"] CLIENT_M["S3 客户端"] --> LB["负载均衡"] LB --> NODE1["MinIO 节点 1<br/>Server 1"] LB --> NODE2["MinIO 节点 2<br/>Server 2"] LB --> NODE3["MinIO 节点 3<br/>Server 3"] LB --> NODE4["MinIO 节点 4<br/>Server 4"] NODE1 --> DISK1["磁盘 1-4"] NODE2 --> DISK2["磁盘 1-4"] NODE3 --> DISK3["磁盘 1-4"] NODE4 --> DISK4["磁盘 1-4"] end style CLIENT_M fill:#e3f2fd,stroke:#1565c0 style NODE1 fill:#c8e6c9,stroke:#2e7d32 style NODE2 fill:#c8e6c9,stroke:#2e7d32

3.2 MinIO 纠删码#

MinIO 使用纠删码保护数据,默认配置为 N/2 数据块 + N/2 校验块:

# MinIO 纠删码配置
# 4 节点 × 4 磁盘 = 16 磁盘
# 默认: 8 数据块 + 8 校验块
# 可容忍 8 块磁盘故障
# 空间利用率: 50%
# 自定义配置
# 16 磁盘: 12 数据 + 4 校验
# 可容忍 4 块磁盘故障
# 空间利用率: 75%
minio_ec_configs = {
"默认 (8+8)": {
"data_blocks": 8,
"parity_blocks": 8,
"fault_tolerance": 8,
"utilization": "50%",
},
"高利用率 (12+4)": {
"data_blocks": 12,
"parity_blocks": 4,
"fault_tolerance": 4,
"utilization": "75%",
},
"高容错 (4+12)": {
"data_blocks": 4,
"parity_blocks": 12,
"fault_tolerance": 12,
"utilization": "25%",
},
}

3.3 MinIO 读写流程#

sequenceDiagram participant Client participant MinIO as MinIO Gateway participant D1 as Disk 1 (Data) participant D2 as Disk 2 (Data) participant P1 as Disk 3 (Parity) participant P2 as Disk 4 (Parity) Client->>MinIO: PUT object MinIO->>MinIO: 计算 EC 编码 MinIO->>D1: 写入数据块 1 MinIO->>D2: 写入数据块 2 MinIO->>P1: 写入校验块 1 MinIO->>P2: 写入校验块 2 D1-->>MinIO: ACK D2-->>MinIO: ACK P1-->>MinIO: ACK P2-->>MinIO: ACK MinIO-->>Client: 200 OK (ETag)

四、多地域复制#

4.1 S3 跨地域复制#

# 配置 S3 跨地域复制
# 1. 开启版本控制
aws s3api put-bucket-versioning \
--bucket my-bucket \
--versioning-configuration Status=Enabled
# 2. 创建复制配置
aws s3api put-bucket-replication \
--bucket my-bucket \
--replication-configuration file://replication.json
# replication.json
# {
# "Role": "arn:aws:iam::123456:role/s3-replication",
# "Rules": [{
# "Status": "Enabled",
# "Destination": {
# "Bucket": "arn:aws:s3:::my-bucket-replica",
# "StorageClass": "STANDARD"
# }
# }]
# }

4.2 MinIO 站点复制#

# MinIO 站点复制配置
# 主站点
mc admin bucket remote add \
myminio/mybucket \
http://minio-replica:9000/mybucket \
--name replica-site
# 启用版本控制
mc version enable myminio/mybucket
# 验证复制状态
mc admin bucket remote ls myminio/mybucket

五、生命周期管理#

5.1 生命周期规则#

{
"Rules": [
{
"ID": "Transition to IA after 30 days",
"Status": "Enabled",
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
}
]
},
{
"ID": "Transition to Glacier after 90 days",
"Status": "Enabled",
"Transitions": [
{
"Days": 90,
"StorageClass": "GLACIER"
}
]
},
{
"ID": "Expire after 365 days",
"Status": "Enabled",
"Expiration": {
"Days": 365
}
},
{
"ID": "Cleanup incomplete uploads",
"Status": "Enabled",
"AbortIncompleteMultipartUpload": {
"DaysAfterInitiation": 7
}
}
]
}

5.2 生命周期管理策略#

数据年龄存储类别成本访问延迟
0–30 天STANDARD毫秒级
30–90 天STANDARD_IA毫秒级
90–365 天GLACIER极低分钟–小时级
> 365 天删除

六、对象存储 vs 文件系统#

6.1 适用场景对比#

场景文件系统对象存储
应用数据目录
用户上传文件
日志归档
数据湖
机器学习数据
数据库存储
配置文件
静态网站

6.2 数据库与对象存储#

Important

数据库不能直接使用对象存储作为主存储,因为对象存储不支持原地修改和随机读写。但数据库的备份、归档、WAL 归档可以存储在对象存储上。云原生数据库(如 Aurora)使用对象存储作为底层存储层,但通过计算节点提供块接口。

七、实战:MinIO 部署#

7.1 MinIO 分布式部署#

# MinIO 分布式部署(4 节点)
# 每个节点 4 块磁盘
export MINIO_ROOT_USER=minioadmin
export MINIO_ROOT_PASSWORD=minioadmin
minio server \
http://node{1...4}/mnt/data{1...4} \
--console-address ":9001"
# 使用 Docker Compose
cat > docker-compose.yml << 'EOF'
version: "3.8"
services:
minio1:
image: minio/minio:latest
command: server http://minio{1...4}/data{1...4} --console-address ":9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
ports:
- "9000:9000"
- "9001:9001"
volumes:
- data1-1:/data1
- data1-2:/data2
- data1-3:/data3
- data1-4:/data4
# ... 其他节点类似
EOF

7.2 MinIO 性能测试#

# 使用 warp 进行性能测试
# 写入测试
warp put --host=minio:9000 --access-key=minioadmin \
--secret-key=minioadmin --bucket=bench --duration=60s
# 读取测试
warp get --host=minio:9000 --access-key=minioadmin \
--secret-key=minioadmin --bucket=bench --duration=60s
# 混合测试
warp mixed --host=minio:9000 --access-key=minioadmin \
--secret-key=minioadmin --bucket=bench --duration=60s \
--get=50 --put=20 --delete=30

八、S3 分片上传详解#

8.1 分片上传流程#

大文件上传时,S3 使用分片上传(Multipart Upload)提高可靠性和并发度:

sequenceDiagram participant Client participant S3 as S3/MinIO Client->>S3: 1. Initiate Multipart Upload S3-->>Client: Upload ID par 并发上传分片 Client->>S3: 2a. Upload Part 1 (Upload ID + Part Number + Data) Client->>S3: 2b. Upload Part 2 Client->>S3: 2c. Upload Part 3 end S3-->>Client: 每个分片返回 ETag alt 正常完成 Client->>S3: 3a. Complete Multipart Upload<br/>(Upload ID + Part List + ETags) S3-->>Client: 200 OK (合并后的对象) else 取消上传 Client->>S3: 3b. Abort Multipart Upload S3-->>Client: 已删除所有分片 end
# S3 分片上传示例
import boto3
s3 = boto3.client('s3')
bucket = 'my-bucket'
key = 'large-file.tar.gz'
file_path = '/data/large-file.tar.gz'
part_size = 64 * 1024 * 1024 # 64MB
# 1. 初始化分片上传
response = s3.create_multipart_upload(Bucket=bucket, Key=key)
upload_id = response['UploadId']
# 2. 上传分片
parts = []
with open(file_path, 'rb') as f:
part_number = 1
while True:
data = f.read(part_size)
if not data:
break
resp = s3.upload_part(
Bucket=bucket, Key=key, UploadId=upload_id,
PartNumber=part_number, Body=data
)
parts.append({'PartNumber': part_number, 'ETag': resp['ETag']})
part_number += 1
# 3. 完成分片上传
s3.complete_multipart_upload(
Bucket=bucket, Key=key, UploadId=upload_id,
MultipartUpload={'Parts': parts}
)

8.2 分片大小选择#

文件大小推荐分片大小分片数说明
< 64MB不使用分片1直接 PUT 更简单
64MB–1GB16–64MB4–16平衡并发与开销
1GB–10GB64–128MB8–80并发上传,失败重传代价低
> 10GB128–256MB40+S3 最大分片数 10000,需合理规划

分片大小的选择需要在并发度和重传开销之间权衡:分片太小,请求次数多、元数据开销大;分片太大,单个分片上传失败后重传代价高。实践中 64MB–128MB 是多数场景的最优选择。

九、S3 权限与安全#

9.1 IAM 策略与桶策略#

S3 的权限控制分为三个层级:IAM 策略(用户级别)、桶策略(桶级别)、ACL(对象级别,已不推荐):

// IAM 策略:允许特定用户访问指定桶
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
},
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-bucket"
}
]
}
// 桶策略:允许跨账号只读访问
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::999999999999:root"},
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::my-bucket/public/*"
}
]
}

9.2 预签名 URL 与服务端加密#

安全机制说明适用场景
IAM 策略用户/角色的权限策略内部服务访问
桶策略桶级别的访问控制跨账号授权、公开访问
预签名 URL带过期时间的临时访问链接临时授权第三方上传/下载
SSE-S3S3 托管密钥加密默认加密,零运维
SSE-KMSKMS 托管密钥加密需要密钥审计和轮换
SSE-C客户端提供密钥自管理密钥
Warning

预签名 URL 的过期时间一旦设定无法撤回。如果 URL 泄露,在过期前任何人都可以访问该对象。对于敏感数据,建议使用短过期时间(如 5 分钟),并通过 x-amz-content-sha256 限制请求体。

十、对象存储性能优化#

10.1 前缀设计与热分区避免#

S3 将对象按 Key 前缀分区。如果所有对象的 Key 以相同前缀开头(如 logs/2024/),请求会集中到少数分区,形成热分区。解决方案是在前缀中加入散列前缀:

# 热分区避免:前缀散列
import hashlib
def generate_key(original_path):
"""生成散列前缀的 Key,避免热分区"""
hash_prefix = hashlib.md5(original_path.encode()).hexdigest()[:4]
return f"{hash_prefix}/{original_path}"
# 原始: logs/2024/app.log → 所有请求打到同一分区
# 优化: a1b2/logs/2024/app.log → 请求分散到 65536 个分区
优化策略说明效果
散列前缀Key 前加 hash 前缀分散到多个分区
分片并发大文件使用分片上传并发提升上传吞吐
连接池复用 HTTP 连接,减少 TLS 握手降低延迟
多线程下载Range GET 并发下载提升下载吞吐
S3 Transfer Acceleration利用 CloudFront 边缘节点跨地域上传加速
Tip

MinIO 和自建对象存储通常不存在 S3 的热分区问题,因为数据分布由本地纠删码决定而非前缀分区。但遵循散列前缀的设计仍然有助于数据均匀分布到各磁盘。

十一、对象存储与数据湖#

11.1 S3 + 数据湖架构#

对象存储是现代数据湖的底层存储。Hive/Glue Catalog 管理元数据,数据文件以 Parquet/ORC 格式存储在 S3 上:

graph TB subgraph 数据湖["数据湖架构"] ENGINE["计算引擎<br/>Spark/Trino/Flink"] --> CATALOG["元数据目录<br/>Hive Metastore/Glue"] CATALOG --> METADATA["元数据<br/>表定义/分区/Schema"] ENGINE --> S3_STORE["S3/MinIO<br/>数据文件"] S3_STORE --> PARQUET["Parquet/ORC 文件"] end style ENGINE fill:#e3f2fd,stroke:#1565c0 style CATALOG fill:#fff9c4,stroke:#f9a825 style S3_STORE fill:#c8e6c9,stroke:#2e7d32
数据湖格式ACID 支持更新方式S3 兼容性适用场景
Delta LakeCopy-on-Write好(需避免 S3 一致性问题)Spark 生态
Apache IcebergMoR/CoW好(原生 S3 支持)多引擎互操作
Apache HudiMoR/CoW增量摄入/流式更新

Ch13 列式存储中讨论了 Parquet/ORC 的文件格式,这里的数据湖格式在 Parquet/ORC 之上增加了 ACID 事务、Schema 演进和时间旅行能力,使对象存储上的数据分析具备了接近数据库的数据管理能力。

十二、MinIO K8s 部署#

12.1 MinIO Operator#

在 Kubernetes 环境中,MinIO 提供了 Operator 和 Tenant CRD 来管理分布式部署:

# MinIO Tenant CRD
apiVersion: minio.min.io/v2
kind: Tenant
metadata:
name: my-minio
namespace: minio-system
spec:
image: minio/minio:latest
imagePullPolicy: IfNotPresent
pools:
- name: pool-0
servers: 4
volumesPerServer: 4
volumeClaimTemplate:
metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Ti
mountPath: /data
requestAutoCert: true
credsSecret:
name: minio-creds-secret
console:
enabled: true
replicas: 2

Operator 会自动创建 4 个 StatefulSet(每个 pool 一个),每个 Pod 挂载 4 个 PVC,组成 4×4=16 磁盘的纠删码集合。扩容只需在 pools 中添加新的 pool,MinIO 会自动执行数据重平衡。

十三、总结#

主题核心要点关键词
对象存储 vs 文件系统扁平命名空间、HTTP API、不支持原地修改Key-Object, Bucket
S3 协议事实标准,2020+ 强一致性REST API, ETag
MinIO纠删码保护、S3 兼容、分布式部署EC, N/2+N/2
分片上传Initiate → Upload Part → Complete/AbortMultipart, Part Size
权限与安全IAM + 桶策略 + 预签名 URL + SSEPresigned URL, SSE-KMS
性能优化散列前缀、分片并发、连接池热分区, Hash Prefix
数据湖S3 + Catalog + Delta/Iceberg/HudiACID on S3
MinIO K8sOperator + Tenant CRD 自动化部署StatefulSet, Pool
多地域复制跨地域数据冗余,异步复制站点复制
生命周期自动分层存储,过期删除Transition, Expiration

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

对象存储
https://blog.souloss.com/posts/storage/storage-object-storage/
作者
Souloss
发布于
2025-09-27
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时