云计算(三)存储系统与分布式架构
Chen Kai BOSS

在云计算的三大核心服务中,存储系统往往是最容易被忽视但又是最关键的组件。无论是对象存储、块存储还是文件存储,它们都承载着现代应用的数据基石。本文将从分布式存储的理论基础出发,深入探讨各类存储系统的架构设计与实现细节,并通过实战案例展示如何在实际生产环境中部署、优化和管理大规模存储集群。

分布式存储基础

CAP 定理与存储系统

CAP 定理指出,在分布式系统中,一致性( Consistency)、可用性( Availability)和分区容错性( Partition tolerance)三者不可兼得,最多只能同时满足两个。

1
2
3
4
5
6
7
8
9
10
┌─────────────────────────────────────────┐
│ 分布式存储系统 │
├─────────────────────────────────────────┤
│ │
│ 一致性 (C) ←→ 可用性 (A) │
│ ↖ ↙ │
│ 分区容错 (P) │
│ │
│ 必须选择: CP 或 AP │
└─────────────────────────────────────────┘

CP 系统(一致性优先):

  • 典型代表: HDFS 、传统关系型数据库集群
  • 特点:强一致性,但网络分区时可能不可用
  • 适用场景:金融交易、关键业务数据

AP 系统(可用性优先):

  • 典型代表: Amazon S3 、 Cassandra
  • 特点:高可用,但可能出现最终一致性
  • 适用场景:内容分发、日志存储、非关键数据

CA 系统

  • 在分布式环境下无法实现(必须容忍网络分区)

BASE 理论

BASE 是 CAP 定理中 AP 方向的延伸:

  • BA( Basically Available):基本可用,允许部分功能降级
  • S( Soft state):软状态,允许中间状态存在
  • E( Eventually consistent):最终一致性,数据最终会达到一致

对象存储系统通常采用 BASE 理论,通过异步复制实现最终一致性。

数据分布策略

一致性哈希

1
2
3
节点环:  [Node1]---[Node2]---[Node3]---[Node1]
↓ ↓ ↓
Key1 Key2 Key3

一致性哈希解决了传统哈希在节点增减时需要大量数据迁移的问题。当节点加入或离开时,只需要迁移相邻节点的部分数据。

虚拟节点( Virtual Nodes)

1
2
3
4
物理节点: Node1, Node2, Node3
虚拟节点: V1-1, V1-2, V1-3 (属于 Node1)
V2-1, V2-2, V2-3 (属于 Node2)
V3-1, V3-2, V3-3 (属于 Node3)

虚拟节点技术让数据分布更加均匀,避免热点问题。

对象存储详解

S3/OSS 架构设计

对象存储的核心思想是将数据作为对象( Object)存储,每个对象包含:

  • 数据本身( Data)
  • 元数据( Metadata)
  • 唯一标识符( Object Key)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────────┐
│ 对象存储架构 │
├─────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ API Gateway │ │ 元数据服务 │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ └───────┬───────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ 对象存储服务 │ │
│ └───────┬───────┘ │
│ │ │
│ ┌──────────┼──────────┐ │
│ │ │ │ │
│ ┌──▼──┐ ┌───▼───┐ ┌───▼───┐ │
│ │存储节点 1 │ │存储节点 2 │ │存储节点 3 │ │
│ └─────┘ └──────┘ └──────┘ │
│ │
└─────────────────────────────────────────────┘

S3 API 核心操作

PUT Object(上传对象):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import boto3

s3 = boto3.client('s3',
endpoint_url='https://oss.example.com',
aws_access_key_id='your-access-key',
aws_secret_access_key='your-secret-key'
)

# 上传小文件(<5MB)
s3.put_object(
Bucket='my-bucket',
Key='path/to/file.jpg',
Body=open('file.jpg', 'rb'),
ContentType='image/jpeg',
Metadata={'author': 'user123'}
)

# 分片上传大文件( Multipart Upload)
upload_id = s3.create_multipart_upload(
Bucket='my-bucket',
Key='large-file.zip'
)['UploadId']

parts = []
part_number = 1
chunk_size = 5 * 1024 * 1024 # 5MB

with open('large-file.zip', 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break

part = s3.upload_part(
Bucket='my-bucket',
Key='large-file.zip',
PartNumber=part_number,
UploadId=upload_id,
Body=chunk
)
parts.append({'PartNumber': part_number, 'ETag': part['ETag']})
part_number += 1

s3.complete_multipart_upload(
Bucket='my-bucket',
Key='large-file.zip',
UploadId=upload_id,
MultipartUpload={'Parts': parts}
)

GET Object(下载对象):

1
2
3
4
5
6
7
8
9
10
11
12
13
# 下载整个对象
response = s3.get_object(
Bucket='my-bucket',
Key='path/to/file.jpg'
)
data = response['Body'].read()

# 范围下载( Range Request)
response = s3.get_object(
Bucket='my-bucket',
Key='large-file.zip',
Range='bytes=0-1048575' # 下载前 1MB
)

LIST Objects(列出对象):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 简单列出
objects = s3.list_objects_v2(
Bucket='my-bucket',
Prefix='images/',
MaxKeys=1000
)

# 分页列出
paginator = s3.get_paginator('list_objects_v2')
pages = paginator.paginate(
Bucket='my-bucket',
Prefix='images/'
)

for page in pages:
for obj in page.get('Contents', []):
print(f"{obj['Key']}: {obj['Size']} bytes")

对象存储实现细节

命名空间设计

  • 扁平化结构:所有对象存储在同一命名空间,通过 Key 区分
  • 模拟目录:通过 Key 中的 / 模拟目录结构(如 images/2024/photo.jpg
  • 桶( Bucket)隔离:不同桶之间完全隔离,可作为多租户边界

元数据存储

1
2
3
4
5
6
7
8
对象元数据表结构:
┌─────────────┬──────────────┬─────────────┬─────────────┐
│ Object Key │ Size (bytes) │ ContentType │ CreateTime │
├─────────────┼──────────────┼─────────────┼─────────────┤
│ file1.jpg │ 1,234,567 │ image/jpeg │ 2024-01-01 │
│ file2.pdf │ 5,678,901 │ application │ 2024-01-02 │
│ │ │ /pdf │ │
└─────────────┴──────────────┴─────────────┴─────────────┘

元数据通常存储在分布式数据库(如 DynamoDB 、 Cassandra)或专门的元数据服务中。

数据冗余策略

  • 多副本:同一对象存储多个副本(通常 3 副本)
  • 纠删码( Erasure Coding):将数据分成 k 个数据块,生成 m 个校验块,总共 n=k+m 个块。可以容忍任意 m 个块丢失。

纠删码示例( RS(10,4)):

1
2
3
4
5
6
原始数据: [D1, D2, D3, D4, D5, D6, D7, D8, D9, D10]
↓ 编码
存储块: [D1...D10, P1, P2, P3, P4]

可容忍任意 4 个块丢失(数据块或校验块)
存储效率: 10/14 ≈ 71%(相比 3 副本的 33% 更高效)

块存储与文件存储深度对比

块存储( Block Storage)

块存储将数据分割成固定大小的块(通常 4KB-1MB),每个块有唯一地址。操作系统通过块设备接口访问。

特点

  • 低延迟:直接访问块,无需文件系统层
  • 高性能:适合数据库、虚拟机磁盘
  • 无结构:只有块地址,无文件概念

典型应用

  • 数据库数据文件( MySQL 、 PostgreSQL)
  • 虚拟机虚拟磁盘( VMware 、 KVM)
  • 高性能计算( HPC)应用

架构示例

1
2
3
4
5
6
7
8
9
应用层

文件系统( ext4/xfs)

块设备接口(/dev/sda1)

块存储服务( iSCSI/Ceph RBD)

物理存储节点

文件存储( File Storage)

文件存储提供完整的文件系统接口,支持目录树、权限管理、文件属性等。

特点

  • 结构化:完整的目录树和文件系统语义
  • 共享访问:多个客户端可同时访问同一文件系统
  • 协议支持: NFS 、 SMB/CIFS 、 FUSE

典型应用

  • 共享文档存储
  • 代码仓库( Git)
  • 媒体文件存储
  • 容器持久化存储

对比表格

特性 块存储 文件存储 对象存储
访问接口 块设备(/dev/sdX) 文件系统(/mnt/nfs) REST API( HTTP)
延迟 极低(<1ms) 低( 1-10ms) 中等( 10-100ms)
吞吐量 极高(>10GB/s) 高( 1-10GB/s) 高( 1-10GB/s)
一致性模型 强一致性 强一致性 最终一致性
扩展性 中等 极高
适用场景 数据库、虚拟机 共享文件、代码库 静态资源、备份
成本

选择建议

选择块存储当

  • 需要极低延迟(数据库)
  • 需要随机读写( OLTP 系统)
  • 应用需要直接控制块布局

选择文件存储当

  • 需要共享访问(多服务器共享数据)
  • 需要 POSIX 文件系统语义
  • 传统应用迁移(无需修改代码)

选择对象存储当

  • 存储大量静态文件(图片、视频)
  • 需要全球分发( CDN 集成)
  • 成本敏感(冷数据归档)

HDFS 分布式文件系统实践

HDFS 架构

HDFS( Hadoop Distributed File System)是 Hadoop 生态的核心存储组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────────────┐
│ HDFS 架构 │
├─────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ NameNode │ (主节点,管理元数据) │
│ │ (Active) │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────▼───────┐ │
│ │ NameNode │ (备用节点,高可用) │
│ │ (Standby) │ │
│ └──────────────┘ │
│ │
│ │ │
│ ┌────┴────┐ │
│ │ │ │
│ ┌──▼──┐ ┌──▼──┐ ┌──▼──┐ │
│ │ DataNode │ │ DataNode │ │ DataNode │ │
│ │ 1 │ │ 2 │ │ 3 │ │
│ └──────┘ └──────┘ └──────┘ │
│ │
└─────────────────────────────────────────────────┘

核心组件

  • NameNode:管理文件系统命名空间和客户端访问
  • DataNode:存储实际数据块
  • Secondary NameNode:辅助 NameNode,执行检查点操作

HDFS 数据组织

文件分块

  • 默认块大小: 128MB( Hadoop 2.x+)或 64MB( Hadoop 1.x)
  • 大文件被分割成多个块
  • 每个块存储 3 个副本(默认)

数据块分布

1
2
3
4
5
6
文件: /user/data/large-file.txt (500MB)
↓ 分块
块 1 (128MB) → DataNode1, DataNode2, DataNode3
块 2 (128MB) → DataNode2, DataNode3, DataNode4
块 3 (128MB) → DataNode3, DataNode4, DataNode1
块 4 (116MB) → DataNode4, DataNode1, DataNode2

HDFS 配置示例

core-site.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<configuration>
<!-- NameNode 地址 -->
<property>
<name>fs.defaultFS</name>
<value>hdfs://namenode:9000</value>
</property>

<!-- 临时目录 -->
<property>
<name>hadoop.tmp.dir</name>
<value>/opt/hadoop/tmp</value>
</property>
</configuration>

hdfs-site.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<configuration>
<!-- 副本数 -->
<property>
<name>dfs.replication</name>
<value>3</value>
</property>

<!-- 块大小 -->
<property>
<name>dfs.blocksize</name>
<value>134217728</value> <!-- 128MB -->
</property>

<!-- NameNode 数据目录 -->
<property>
<name>dfs.namenode.name.dir</name>
<value>/opt/hadoop/namenode</value>
</property>

<!-- DataNode 数据目录 -->
<property>
<name>dfs.datanode.data.dir</name>
<value>/opt/hadoop/datanode</value>
</property>

<!-- 启用 WebHDFS -->
<property>
<name>dfs.webhdfs.enabled</name>
<value>true</value>
</property>
</configuration>

HDFS 操作命令

文件操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建目录
hdfs dfs -mkdir -p /user/data/input

# 上传文件
hdfs dfs -put local-file.txt /user/data/input/

# 列出文件
hdfs dfs -ls /user/data/input

# 查看文件内容
hdfs dfs -cat /user/data/input/file.txt

# 下载文件
hdfs dfs -get /user/data/input/file.txt ./local-file.txt

# 删除文件
hdfs dfs -rm /user/data/input/file.txt

# 查看文件系统使用情况
hdfs dfs -df -h

管理命令

1
2
3
4
5
6
7
8
9
10
11
# 进入安全模式(维护时)
hdfs dfsadmin -safemode enter

# 退出安全模式
hdfs dfsadmin -safemode leave

# 查看集群状态
hdfs dfsadmin -report

# 平衡数据分布
hdfs balancer -threshold 10

HDFS 高可用配置

启用 NameNode HA( hdfs-site.xml):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<configuration>
<!-- 启用 HA -->
<property>
<name>dfs.nameservices</name>
<value>mycluster</value>
</property>

<!-- NameNode ID -->
<property>
<name>dfs.ha.namenodes.mycluster</name>
<value>nn1,nn2</value>
</property>

<!-- NameNode RPC 地址 -->
<property>
<name>dfs.namenode.rpc-address.mycluster.nn1</name>
<value>namenode1:9000</value>
</property>
<property>
<name>dfs.namenode.rpc-address.mycluster.nn2</name>
<value>namenode2:9000</value>
</property>

<!-- 共享存储(用于 JournalNode) -->
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://jn1:8485;jn2:8485;jn3:8485/mycluster</value>
</property>

<!-- 故障转移代理 -->
<property>
<name>dfs.client.failover.proxy.provider.mycluster</name>
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
</property>
</configuration>

Ceph 存储集群部署与管理

Ceph 架构概述

Ceph 是一个统一的分布式存储系统,提供对象存储、块存储和文件存储三种接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────────┐
│ Ceph 存储集群 │
├─────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Monitor │ │ Monitor │ │ Monitor │ │
│ │ (MDS) │ │ (MDS) │ │ (MDS) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Manager │ │ Manager │ │ Manager │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ OSD (Object Storage Daemon) │ │
│ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ │
│ │ │ OSD1 │ │ OSD2 │ │ OSD3 │ │ OSD4 │ ... │ │
│ │ └────┘ └────┘ └────┘ └────┘ │ │
│ └──────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────┘

核心组件

  • Monitor (MON):维护集群映射和状态
  • Manager (MGR):提供监控和管理接口
  • OSD:实际存储数据的守护进程
  • MDS(可选):用于 CephFS 文件系统

Ceph 部署准备

系统要求

  • 操作系统: CentOS 7+ / Ubuntu 18.04+
  • 内存:每个 OSD 至少 4GB
  • 磁盘:每个 OSD 至少 1TB(推荐 SSD)
  • 网络: 10GbE 或更高

安装 Ceph( Ubuntu):

1
2
3
4
5
6
7
# 添加 Ceph 仓库
wget -q -O- 'https://download.ceph.com/keys/release.asc' | sudo apt-key add -
echo "deb https://download.ceph.com/debian-octopus/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/ceph.list

# 更新并安装
sudo apt update
sudo apt install ceph-deploy ceph-common

Ceph 集群部署

1. 初始化集群

1
2
3
4
5
6
7
8
9
10
11
12
# 创建集群配置目录
mkdir my-cluster
cd my-cluster

# 创建新集群(指定第一个 Monitor)
ceph-deploy new node1

# 安装 Ceph(在所有节点)
ceph-deploy install node1 node2 node3

# 初始化 Monitor
ceph-deploy mon create-initial

2. 部署 OSD

1
2
3
4
5
6
7
8
9
10
11
12
# 准备 OSD(在所有存储节点)
ceph-deploy disk zap node2 /dev/sdb
ceph-deploy disk zap node3 /dev/sdb
ceph-deploy disk zap node4 /dev/sdb

# 创建 OSD
ceph-deploy osd create --data /dev/sdb node2
ceph-deploy osd create --data /dev/sdb node3
ceph-deploy osd create --data /dev/sdb node4

# 部署 Manager
ceph-deploy mgr create node1

3. 部署客户端

1
2
3
4
5
# 复制配置文件到客户端
ceph-deploy admin node-client

# 在客户端设置权限
sudo chmod +r /etc/ceph/ceph.client.admin.keyring

Ceph 配置管理

ceph.conf 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[global]
fsid = 12345678-1234-1234-1234-123456789abc
mon_initial_members = node1, node2, node3
mon_host = 192.168.1.10, 192.168.1.11, 192.168.1.12
auth_cluster_required = cephx
auth_service_required = cephx
auth_client_required = cephx

# 网络配置
public_network = 192.168.1.0/24
cluster_network = 10.0.0.0/24

# OSD 配置
osd_journal_size = 10240
osd_pool_default_size = 3
osd_pool_default_min_size = 2
osd_pool_default_pg_num = 128
osd_pool_default_pgp_num = 128

# 性能调优
filestore_max_sync_interval = 5
journal_max_write_bytes = 10737418240
journal_max_write_entries = 10000

Ceph 存储池管理

创建存储池

1
2
3
4
5
6
7
8
9
# 创建副本池
ceph osd pool create mypool 128 128

# 创建纠删码池
ceph osd pool create ecpool 32 erasure
ceph osd pool set ecpool allow_ec_overwrites true

# 查看池信息
ceph osd pool ls detail

PG( Placement Group)配置

1
2
3
4
5
6
7
8
PG 数量计算公式:
PGs = (OSDs × 100) / 副本数

示例:

- 10 个 OSD, 3 副本
- PGs = (10 × 100) / 3 ≈ 333
- 向上取整到 2 的幂次: 512

设置 PG 数量

1
2
3
# 设置 PG 数量
ceph osd pool set mypool pg_num 512
ceph osd pool set mypool pgp_num 512

Ceph 块存储( RBD)

创建 RBD 镜像

1
2
3
4
5
6
7
8
9
# 创建镜像
rbd create --size 100G mypool/myimage

# 映射到本地
sudo rbd map mypool/myimage

# 格式化并挂载
sudo mkfs.ext4 /dev/rbd0
sudo mount /dev/rbd0 /mnt/rbd

快照管理

1
2
3
4
5
6
7
8
9
10
11
12
# 创建快照
rbd snap create mypool/myimage@snapshot1

# 列出快照
rbd snap ls mypool/myimage

# 回滚到快照
rbd snap rollback mypool/myimage@snapshot1

# 克隆快照
rbd snap protect mypool/myimage@snapshot1
rbd clone mypool/myimage@snapshot1 mypool/myclone

Ceph 对象存储( RADOS Gateway)

部署 RGW

1
2
3
4
5
# 创建 RGW 实例
ceph-deploy rgw create node1

# 或手动创建
ceph auth get-or-create client.rgw.node1 osd 'allow rwx' mon 'allow rw' -o /etc/ceph/ceph.client.rgw.keyring

RGW 配置( ceph.conf):

1
2
3
4
5
[client.rgw.node1]
rgw_frontends = civetweb port=7480
rgw_dns_name = s3.example.com
rgw_zone = default
rgw_zonegroup = default

使用 S3 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装 s3cmd
pip install s3cmd

# 配置
s3cmd --configure

# 创建桶
s3cmd mb s3://my-bucket

# 上传文件
s3cmd put file.txt s3://my-bucket/

# 列出对象
s3cmd ls s3://my-bucket/

Ceph 监控与维护

集群状态检查

1
2
3
4
5
6
7
8
9
10
11
# 查看集群健康状态
ceph health
ceph health detail

# 查看集群使用情况
ceph df
ceph df detail

# 查看 OSD 状态
ceph osd tree
ceph osd df

性能监控

1
2
3
4
5
# 启用 Dashboard(需要 MGR)
ceph mgr module enable dashboard
ceph dashboard create-self-signed-cert

# 访问 https://node1:8443

常见维护操作

1
2
3
4
5
6
7
8
9
10
11
12
13
# OSD 下线(数据迁移)
ceph osd out osd.1

# OSD 上线
ceph osd in osd.1

# 移除 OSD
ceph osd crush remove osd.1
ceph auth del osd.1
ceph osd rm osd.1

# 数据重平衡
ceph osd reweight osd.1 0.8 # 降低权重到 80%

数据一致性与复制策略

一致性模型

强一致性( Strong Consistency)

  • 所有副本同时更新
  • 读取总是返回最新数据
  • 实现方式:同步复制 + 多数派写入

最终一致性( Eventually Consistency)

  • 允许短暂的不一致
  • 最终所有副本会达到一致
  • 实现方式:异步复制

因果一致性( Causal Consistency)

  • 保证因果相关的操作顺序
  • 不相关的操作可以乱序

复制策略

同步复制

1
2
3
4
5
6
写入流程:
Client → Primary → Replica1 (等待确认)

Replica2 (等待确认)

所有副本确认 → Client 收到成功

异步复制

1
2
3
4
5
6
写入流程:
Client → Primary (立即返回成功)

Replica1 (异步)

Replica2 (异步)

多数派写入( Quorum)

1
2
3
4
5
6
7
8
9
3 副本系统:

- 写入需要至少 2 个副本确认( W=2)
- 读取需要至少 2 个副本确认( R=2)
- 可以容忍 1 个副本故障

5 副本系统:

- W=3, R=3,可容忍 2 个副本故障

冲突解决

最后写入获胜( LWW)

  • 使用时间戳比较
  • 简单但可能丢失数据

向量时钟( Vector Clock)

1
2
3
4
5
节点 A: [A:2, B:1, C:0]
节点 B: [A:1, B:2, C:1]
节点 C: [A:0, B:1, C:2]

可以检测并发写入冲突

CRDT(无冲突复制数据类型)

  • 数学上保证合并结果唯一
  • 适用于计数器、集合等数据类型

数据备份与容灾方案

备份策略

3-2-1 规则

  • 3 份数据副本
  • 2 种不同存储介质
  • 1 份异地备份

备份类型

  • 全量备份:备份所有数据
  • 增量备份:只备份变更数据
  • 差异备份:备份自上次全量备份后的变更

对象存储备份

S3 版本控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import boto3

s3 = boto3.client('s3')

# 启用版本控制
s3.put_bucket_versioning(
Bucket='my-bucket',
VersioningConfiguration={'Status': 'Enabled'}
)

# 上传文件(自动创建版本)
s3.put_object(
Bucket='my-bucket',
Key='document.pdf',
Body=open('document.pdf', 'rb')
)

# 列出所有版本
versions = s3.list_object_versions(
Bucket='my-bucket',
Prefix='document.pdf'
)

# 恢复旧版本
s3.copy_object(
Bucket='my-bucket',
Key='document.pdf',
CopySource={
'Bucket': 'my-bucket',
'Key': 'document.pdf',
'VersionId': 'old-version-id'
}
)

生命周期策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"Rules": [
{
"Id": "ArchiveRule",
"Status": "Enabled",
"Prefix": "archive/",
"Transitions": [
{
"Days": 30,
"StorageClass": "GLACIER"
},
{
"Days": 90,
"StorageClass": "DEEP_ARCHIVE"
}
]
},
{
"Id": "DeleteRule",
"Status": "Enabled",
"Prefix": "temp/",
"Expiration": {
"Days": 7
}
}
]
}

跨区域复制

S3 跨区域复制配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建复制配置
replication_config = {
'Role': 'arn:aws:iam::account:role/replication-role',
'Rules': [{
'Id': 'ReplicateAll',
'Status': 'Enabled',
'Priority': 1,
'Filter': {},
'Destination': {
'Bucket': 'arn:aws:s3:::destination-bucket',
'StorageClass': 'STANDARD_IA'
}
}]
}

s3.put_bucket_replication(
Bucket='source-bucket',
ReplicationConfiguration=replication_config
)

容灾方案设计

RTO 与 RPO

  • RTO( Recovery Time Objective):恢复时间目标,系统恢复所需时间
  • RPO( Recovery Point Objective):恢复点目标,可接受的数据丢失量

容灾等级

等级 RTO RPO 实现方式
0 级 无备份
1 级 1 周 1 天 定期备份
2 级 1 天 1 小时 实时备份
3 级 1 小时 5 分钟 热备
4 级 10 分钟 0 双活
5 级 0 0 多活

多活架构

1
2
3
4
5
6
┌─────────────┐         ┌─────────────┐
│ 区域 A │ ←────→ │ 区域 B │
│ (主) │ 双向 │ (主) │
└─────────────┘ 复制 └─────────────┘
↓ ↓
用户流量 用户流量

存储性能优化实战

性能指标

关键指标

  • IOPS( Input/Output Operations Per Second):每秒 IO 操作数
  • 吞吐量( Throughput):每秒传输的数据量( MB/s 或 GB/s)
  • 延迟( Latency):单个 IO 操作的响应时间
  • 队列深度( Queue Depth):同时进行的 IO 请求数

对象存储性能优化

并发上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from concurrent.futures import ThreadPoolExecutor
import boto3

def upload_chunk(args):
bucket, key, chunk_data, part_number, upload_id = args
s3 = boto3.client('s3')
response = s3.upload_part(
Bucket=bucket,
Key=key,
PartNumber=part_number,
UploadId=upload_id,
Body=chunk_data
)
return {'PartNumber': part_number, 'ETag': response['ETag']}

# 并发分片上传
def parallel_multipart_upload(bucket, key, file_path, chunk_size=5*1024*1024):
s3 = boto3.client('s3')

# 创建分片上传
upload_id = s3.create_multipart_upload(Bucket=bucket, Key=key)['UploadId']

# 读取文件并分块
chunks = []
part_number = 1
with open(file_path, 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
chunks.append((bucket, key, chunk, part_number, upload_id))
part_number += 1

# 并发上传(最多 10 个线程)
parts = []
with ThreadPoolExecutor(max_workers=10) as executor:
futures = executor.map(upload_chunk, chunks)
parts = list(futures)

# 完成上传
s3.complete_multipart_upload(
Bucket=bucket,
Key=key,
UploadId=upload_id,
MultipartUpload={'Parts': sorted(parts, key=lambda x: x['PartNumber'])}
)

CDN 加速

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# CloudFront 分发配置
cloudfront = boto3.client('cloudfront')

distribution_config = {
'CallerReference': str(time.time()),
'Comment': 'S3 bucket distribution',
'DefaultCacheBehavior': {
'TargetOriginId': 'S3-my-bucket',
'ViewerProtocolPolicy': 'redirect-to-https',
'AllowedMethods': ['GET', 'HEAD', 'OPTIONS'],
'CachedMethods': ['GET', 'HEAD'],
'ForwardedValues': {
'QueryString': False,
'Cookies': {'Forward': 'none'}
},
'MinTTL': 3600,
'DefaultTTL': 86400,
'MaxTTL': 31536000
},
'Origins': {
'Quantity': 1,
'Items': [{
'Id': 'S3-my-bucket',
'DomainName': 'my-bucket.s3.amazonaws.com',
'S3OriginConfig': {
'OriginAccessIdentity': ''
}
}]
},
'Enabled': True
}

response = cloudfront.create_distribution(
DistributionConfig=distribution_config
)

块存储性能优化

IO 调度器调优( Linux):

1
2
3
4
5
6
7
8
# 查看当前调度器
cat /sys/block/sda/queue/scheduler

# 设置为 deadline 调度器(适合数据库)
echo deadline > /sys/block/sda/queue/scheduler

# 或设置为 noop(虚拟环境)
echo noop > /sys/block/sda/queue/scheduler

预读优化

1
2
3
4
5
# 增加预读大小(适合顺序读取)
blockdev --setra 8192 /dev/sda

# 查看当前值
blockdev --getra /dev/sda

文件系统优化( ext4):

1
2
3
4
5
# 挂载选项优化
mount -o noatime,nodiratime,data=writeback /dev/sda1 /mnt

# 或写入 /etc/fstab
/dev/sda1 /mnt ext4 noatime,nodiratime,data=writeback 0 2

HDFS 性能优化

数据本地性

  • 本地读取:数据在本地 DataNode
  • 机架本地:数据在同一机架
  • 跨机架:数据在不同机架

优化策略:

1
2
3
4
5
6
7
8
9
<!-- hdfs-site.xml -->
<property>
<name>dfs.client.read.shortcircuit</name>
<value>true</value>
</property>
<property>
<name>dfs.domain.socket.path</name>
<value>/var/lib/hadoop-hdfs/dn_socket</value>
</property>

压缩

1
2
3
# 使用 Snappy 压缩(平衡压缩率和速度)
hdfs dfs -Ddfs.compression.codec=org.apache.hadoop.io.compress.SnappyCodec \
-put large-file.txt /user/data/

Ceph 性能优化

CRUSH 规则优化

1
2
3
4
5
6
7
8
# 创建 SSD 池规则
ceph osd crush rule create-replicated ssd-pool default host ssd

# 创建 HDD 池规则
ceph osd crush rule create-replicated hdd-pool default host hdd

# 应用规则到存储池
ceph osd pool set mypool crush_rule ssd-pool

OSD 调优

1
2
3
4
5
6
7
8
9
10
11
12
# ceph.conf
[osd]
# 增加并发数
osd_op_threads = 8
osd_disk_threads = 4

# 内存缓存
osd_cache_size = 2048 # MB

# 日志优化( SSD)
bluestore_block_db_size = 10737418240 # 10GB
bluestore_block_wal_size = 1073741824 # 1GB

网络优化

1
2
3
4
5
6
# 增加网络缓冲区
sysctl -w net.core.rmem_max = 134217728
sysctl -w net.core.wmem_max = 134217728

# 启用 TCP 窗口缩放
sysctl -w net.ipv4.tcp_window_scaling = 1

成本优化策略

存储分层

S3 存储类别

存储类别 成本($/GB/月) 访问延迟 适用场景
Standard 0.023 毫秒级 频繁访问
Standard-IA 0.0125 毫秒级 不频繁访问
Glacier 0.004 分钟级 归档
Deep Archive 0.00099 小时级 长期归档

生命周期策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
lifecycle_config = {
'Rules': [
{
'Id': 'MoveToIA',
'Status': 'Enabled',
'Transitions': [{
'Days': 30,
'StorageClass': 'STANDARD_IA'
}],
'Filter': {'Prefix': 'logs/'}
},
{
'Id': 'ArchiveToGlacier',
'Status': 'Enabled',
'Transitions': [
{
'Days': 90,
'StorageClass': 'GLACIER'
},
{
'Days': 365,
'StorageClass': 'DEEP_ARCHIVE'
}
],
'Filter': {'Prefix': 'archive/'}
}
]
}

s3.put_bucket_lifecycle_configuration(
Bucket='my-bucket',
LifecycleConfiguration=lifecycle_config
)

数据去重

块级去重

1
2
3
4
5
6
7
8
9
10
原始数据:
文件 A: [块 1, 块 2, 块 3, 块 4]
文件 B: [块 1, 块 5, 块 3, 块 6]

去重后:
存储: [块 1, 块 2, 块 3, 块 4, 块 5, 块 6]
引用: 文件 A → [1,2,3,4]
文件 B → [1,5,3,6]

节省空间: 2/8 = 25%

实现示例(简化):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import hashlib

class DeduplicationStorage:
def __init__(self):
self.block_store = {} # hash -> data
self.file_index = {} # filename -> [hashes]

def store_file(self, filename, data, block_size=4096):
blocks = []
for i in range(0, len(data), block_size):
block = data[i:i+block_size]
block_hash = hashlib.sha256(block).hexdigest()

# 只存储新块
if block_hash not in self.block_store:
self.block_store[block_hash] = block

blocks.append(block_hash)

self.file_index[filename] = blocks

def get_file(self, filename):
blocks = self.file_index.get(filename, [])
return b''.join(self.block_store[h] for h in blocks)

压缩策略

压缩算法对比

算法 压缩率 速度 CPU 消耗 适用场景
gzip 中等 中等 中等 通用
bzip2 归档
lz4 极快 实时压缩
zstd 中等 推荐

Hadoop 压缩配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- core-site.xml -->
<property>
<name>io.compression.codecs</name>
<value>
org.apache.hadoop.io.compress.GzipCodec,
org.apache.hadoop.io.compress.BZip2Codec,
org.apache.hadoop.io.compress.Lz4Codec,
org.apache.hadoop.io.compress.SnappyCodec
</value>
</property>

<property>
<name>mapreduce.map.output.compress</name>
<value>true</value>
</property>

<property>
<name>mapreduce.map.output.compress.codec</name>
<value>org.apache.hadoop.io.compress.SnappyCodec</value>
</property>

容量规划

容量计算公式

1
2
3
4
5
6
7
8
9
10
总容量需求 = 原始数据 × (1 + 副本数) × (1 + 增长因子) × (1 + 冗余率)

示例:

- 原始数据: 100TB
- 副本数: 3
- 年增长: 20%
- 冗余率: 10%

总容量 = 100TB × 4 × 1.2 × 1.1 = 528TB

成本估算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def estimate_storage_cost(data_size_tb, replication=3, growth_rate=0.2, 
years=3, cost_per_tb_month=20):
"""
估算存储成本

Args:
data_size_tb: 初始数据量( TB)
replication: 副本数
growth_rate: 年增长率
years: 年限
cost_per_tb_month: 每 TB 每月成本(美元)
"""
total_cost = 0
current_size = data_size_tb

for year in range(years):
# 当前年容量(考虑副本)
capacity = current_size * replication

# 年度成本
year_cost = capacity * cost_per_tb_month * 12
total_cost += year_cost

# 下一年数据增长
current_size *= (1 + growth_rate)

print(f"Year {year+1}: {capacity:.2f}TB, Cost: ${year_cost:,.2f}")

print(f"\nTotal {years}-year cost: ${total_cost:,.2f}")
return total_cost

# 示例
estimate_storage_cost(100, replication=3, growth_rate=0.2, years=3)

实战案例

案例一:大规模日志存储系统

场景:某互联网公司需要存储每天 10TB 的日志数据,保留 90 天。

需求分析

  • 写入:高吞吐(>1GB/s)
  • 读取:按时间范围查询
  • 成本:尽可能低
  • 可靠性: 99.9%

架构设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──────────────┐
│ 日志采集 │ → Kafka
└──────┬───────┘


┌──────────────┐
│ 日志处理 │ → 压缩、分区
└──────┬───────┘


┌──────────────┐ ┌──────────────┐
│ 对象存储 │ ←→ │ 冷存储 │
│ (热数据) │ │ (归档数据) │
│ 30 天 │ │ 60 天 │
└──────────────┘ └──────────────┘

实现方案

  1. 数据分区策略

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    from datetime import datetime
    import boto3

    def get_log_path(timestamp, log_type):
    """按日期和类型分区"""
    dt = datetime.fromtimestamp(timestamp)
    return f"logs/{log_type}/year={dt.year}/month={dt.month:02d}/day={dt.day:02d}/hour={dt.hour:02d}/"

    def upload_logs(logs, bucket, log_type):
    s3 = boto3.client('s3')

    # 按小时分组
    hourly_logs = {}
    for log in logs:
    hour_key = get_log_path(log['timestamp'], log_type)
    if hour_key not in hourly_logs:
    hourly_logs[hour_key] = []
    hourly_logs[hour_key].append(log)

    # 并发上传
    for hour_key, hour_logs in hourly_logs.items():
    # 压缩
    import gzip
    import json
    data = gzip.compress(
    '\n'.join(json.dumps(log) for log in hour_logs).encode()
    )

    # 上传到 S3
    key = f"{hour_key}logs-{int(hour_logs[0]['timestamp'])}.json.gz"
    s3.put_object(
    Bucket=bucket,
    Key=key,
    Body=data,
    StorageClass='STANDARD_IA', # 不频繁访问
    ContentEncoding='gzip'
    )

  2. 生命周期管理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    lifecycle_config = {
    'Rules': [
    {
    'Id': 'MoveToGlacier',
    'Status': 'Enabled',
    'Transitions': [{
    'Days': 30,
    'StorageClass': 'GLACIER'
    }],
    'Filter': {'Prefix': 'logs/'}
    },
    {
    'Id': 'DeleteOldLogs',
    'Status': 'Enabled',
    'Expiration': {'Days': 90},
    'Filter': {'Prefix': 'logs/'}
    }
    ]
    }

  3. 查询接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    def query_logs(bucket, log_type, start_time, end_time):
    """查询指定时间范围的日志"""
    s3 = boto3.client('s3')

    # 生成需要查询的前缀
    prefixes = generate_prefixes(log_type, start_time, end_time)

    logs = []
    for prefix in prefixes:
    # 列出对象
    objects = s3.list_objects_v2(
    Bucket=bucket,
    Prefix=prefix
    )

    for obj in objects.get('Contents', []):
    # 下载并解压
    response = s3.get_object(Bucket=bucket, Key=obj['Key'])
    data = gzip.decompress(response['Body'].read())

    # 解析日志
    for line in data.decode().split('\n'):
    if line:
    log = json.loads(line)
    if start_time <= log['timestamp'] <= end_time:
    logs.append(log)

    return logs

成本分析

  • 热数据( 30 天): 300TB × $0.0125/GB/月 = $3,750/月
  • 冷数据( 60 天): 600TB × $0.004/GB/月 = $2,400/月
  • 总成本:$6,150/月
  • 相比全量 Standard 存储节省:约 70%

案例二:数据库备份系统

场景: MySQL 数据库集群,总数据量 50TB,需要每日全量备份 + 每小时增量备份。

需求

  • RPO: 1 小时
  • RTO: 4 小时
  • 保留策略: 7 天每日备份 + 30 天每周备份

架构设计

1
2
3
4
5
6
7
8
9
MySQL 集群

XtraBackup (备份工具)

压缩 + 加密

对象存储 (多区域)
├── 区域 A (主)
└── 区域 B (备)

实现方案

  1. 备份脚本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    #!/bin/bash
    # backup-mysql.sh

    BACKUP_TYPE=${1:-incremental} # full or incremental
    BUCKET="mysql-backups"
    REGION="us-east-1"
    DATE=$(date +%Y%m%d)
    TIME=$(date +%H%M%S)
    BACKUP_DIR="/backup/mysql"

    # 全量备份
    if [ "$BACKUP_TYPE" == "full" ]; then
    BACKUP_PATH="$BACKUP_DIR/full-$DATE-$TIME"
    xtrabackup --backup --target-dir=$BACKUP_PATH

    # 压缩
    tar czf $BACKUP_PATH.tar.gz -C $BACKUP_DIR full-$DATE-$TIME

    # 加密
    gpg --encrypt --recipient backup-key $BACKUP_PATH.tar.gz

    # 上传到 S3
    aws s3 cp $BACKUP_PATH.tar.gz.gpg \
    s3://$BUCKET/full/$DATE/ \
    --storage-class STANDARD_IA

    # 增量备份
    else
    # 找到最新的全量备份
    LATEST_FULL=$(aws s3 ls s3://$BUCKET/full/ | sort | tail -1 | awk '{print $2}')

    BACKUP_PATH="$BACKUP_DIR/incr-$DATE-$TIME"
    xtrabackup --backup \
    --target-dir=$BACKUP_PATH \
    --incremental-basedir=/backup/mysql/$LATEST_FULL

    tar czf $BACKUP_PATH.tar.gz -C $BACKUP_DIR incr-$DATE-$TIME
    gpg --encrypt --recipient backup-key $BACKUP_PATH.tar.gz

    aws s3 cp $BACKUP_PATH.tar.gz.gpg \
    s3://$BUCKET/incremental/$DATE/ \
    --storage-class STANDARD_IA
    fi

    # 清理本地备份(保留最近 3 天)
    find $BACKUP_DIR -type f -mtime +3 -delete

  2. 恢复脚本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #!/bin/bash
    # restore-mysql.sh

    BACKUP_DATE=$1
    BUCKET="mysql-backups"
    RESTORE_DIR="/restore/mysql"

    # 下载全量备份
    aws s3 cp s3://$BUCKET/full/$BACKUP_DATE/ $RESTORE_DIR/full/ --recursive

    # 下载增量备份
    aws s3 cp s3://$BUCKET/incremental/$BACKUP_DATE/ $RESTORE_DIR/incremental/ --recursive

    # 解密全量备份
    gpg --decrypt $RESTORE_DIR/full/*.gpg | tar xz -C $RESTORE_DIR

    # 准备基础备份
    xtrabackup --prepare --apply-log-only --target-dir=$RESTORE_DIR/full

    # 应用增量备份(按时间顺序)
    for incr_backup in $(ls -t $RESTORE_DIR/incremental/*.gpg); do
    gpg --decrypt $ incr_backup | tar xz -C $RESTORE_DIR/incremental
    xtrabackup --prepare --apply-log-only \
    --target-dir=$RESTORE_DIR/full \
    --incremental-dir=$RESTORE_DIR/incremental/$(basename $ incr_backup .tar.gz.gpg)
    done

    # 最终准备
    xtrabackup --prepare --target-dir=$RESTORE_DIR/full

    # 恢复数据
    systemctl stop mysql
    rm -rf /var/lib/mysql/*
    xtrabackup --copy-back --target-dir=$RESTORE_DIR/full
    chown -R mysql:mysql /var/lib/mysql
    systemctl start mysql

  3. 跨区域复制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import boto3

    s3 = boto3.client('s3')

    # 配置跨区域复制
    replication_config = {
    'Role': 'arn:aws:iam::account:role/replication-role',
    'Rules': [{
    'Id': 'ReplicateBackups',
    'Status': 'Enabled',
    'Filter': {'Prefix': ''},
    'Destination': {
    'Bucket': 'arn:aws:s3:::mysql-backups-dr',
    'StorageClass': 'STANDARD_IA'
    }
    }]
    }

    s3.put_bucket_replication(
    Bucket='mysql-backups',
    ReplicationConfiguration=replication_config
    )

  4. 生命周期策略

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    lifecycle_config = {
    'Rules': [
    {
    'Id': 'DeleteDailyAfter7Days',
    'Status': 'Enabled',
    'Expiration': {'Days': 7},
    'Filter': {'Prefix': 'full/'}
    },
    {
    'Id': 'ArchiveWeekly',
    'Status': 'Enabled',
    'Transitions': [{
    'Days': 7,
    'StorageClass': 'GLACIER'
    }],
    'Expiration': {'Days': 30},
    'Filter': {'Prefix': 'full/'},
    'TagFilters': [{
    'Key': 'backup-type',
    'Value': 'weekly'
    }]
    },
    {
    'Id': 'DeleteIncrementalAfter24Hours',
    'Status': 'Enabled',
    'Expiration': {'Days': 1},
    'Filter': {'Prefix': 'incremental/'}
    }
    ]
    }

性能指标

  • 备份速度: 500MB/s(压缩后)
  • 恢复时间:全量恢复 2 小时,增量恢复 30 分钟
  • 存储成本:约 $500/月(含跨区域复制)

案例三:多媒体内容分发系统

场景:视频平台,存储 100PB 视频文件,全球用户访问。

需求

  • 高可用: 99.99%
  • 全球加速: CDN 集成
  • 成本优化:热冷数据分离
  • 转码支持:多种分辨率

架构设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──────────────┐
│ 视频上传 │ → 对象存储(原始)
└──────┬───────┘


┌──────────────┐
│ 转码服务 │ → 多分辨率版本
└──────┬───────┘


┌──────────────┐ ┌──────────────┐
│ 对象存储 │ ←→ │ CDN │
│ (源站) │ │ (边缘缓存) │
└──────────────┘ └──────────────┘

实现方案

  1. 上传优化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    import boto3
    from concurrent.futures import ThreadPoolExecutor
    import hashlib

    class VideoUploader:
    def __init__(self, bucket, region):
    self.s3 = boto3.client('s3', region_name=region)
    self.bucket = bucket
    self.chunk_size = 10 * 1024 * 1024 # 10MB

    def upload_video(self, video_path, video_id):
    """分片并发上传视频"""
    file_size = os.path.getsize(video_path)

    # 创建分片上传
    upload_id = self.s3.create_multipart_upload(
    Bucket=self.bucket,
    Key=f'originals/{video_id}.mp4',
    Metadata={
    'video-id': video_id,
    'original-size': str(file_size)
    },
    StorageClass='STANDARD'
    )['UploadId']

    # 计算分片
    parts = []
    part_number = 1

    with open(video_path, 'rb') as f:
    while True:
    chunk = f.read(self.chunk_size)
    if not chunk:
    break

    # 并发上传分片
    part = self.s3.upload_part(
    Bucket=self.bucket,
    Key=f'originals/{video_id}.mp4',
    PartNumber=part_number,
    UploadId=upload_id,
    Body=chunk
    )
    parts.append({
    'PartNumber': part_number,
    'ETag': part['ETag']
    })
    part_number += 1

    # 完成上传
    self.s3.complete_multipart_upload(
    Bucket=self.bucket,
    Key=f'originals/{video_id}.mp4',
    UploadId=upload_id,
    MultipartUpload={'Parts': parts}
    )

    # 触发转码任务
    self.trigger_transcoding(video_id)

    def trigger_transcoding(self, video_id):
    """触发转码任务( Lambda/ECS)"""
    import boto3
    lambda_client = boto3.client('lambda')
    lambda_client.invoke(
    FunctionName='video-transcoder',
    InvocationType='Event',
    Payload=json.dumps({
    'video_id': video_id,
    'source_key': f'originals/{video_id}.mp4',
    'resolutions': ['1080p', '720p', '480p', '360p']
    })
    )

  2. 转码后存储

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    def store_transcoded_video(video_id, resolution, transcoded_file):
    """存储转码后的视频"""
    s3 = boto3.client('s3')

    key = f'videos/{video_id}/{resolution}.mp4'

    # 上传转码文件
    s3.put_object(
    Bucket='video-bucket',
    Key=key,
    Body=transcoded_file,
    StorageClass='STANDARD', # 热数据
    CacheControl='max-age=31536000', # CDN 缓存 1 年
    ContentType='video/mp4',
    Metadata={
    'video-id': video_id,
    'resolution': resolution
    }
    )

    # 生成播放列表( HLS)
    generate_hls_playlist(video_id, resolution)

  3. CDN 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    def configure_cdn_for_video(bucket, distribution_id):
    """配置 CloudFront 用于视频分发"""
    cloudfront = boto3.client('cloudfront')

    # 更新分发配置
    config = cloudfront.get_distribution_config(Id=distribution_id)
    etag = config['ETag']
    distribution_config = config['DistributionConfig']

    # 添加缓存行为
    distribution_config['CacheBehaviors']['Items'].append({
    'PathPattern': 'videos/*',
    'TargetOriginId': 'S3-video-bucket',
    'ViewerProtocolPolicy': 'redirect-to-https',
    'AllowedMethods': {
    'Quantity': 2,
    'Items': ['GET', 'HEAD'],
    'CachedMethods': {
    'Quantity': 2,
    'Items': ['GET', 'HEAD']
    }
    },
    'Compress': True,
    'MinTTL': 86400,
    'DefaultTTL': 31536000,
    'MaxTTL': 31536000,
    'ForwardedValues': {
    'QueryString': False,
    'Cookies': {'Forward': 'none'}
    }
    })

    cloudfront.update_distribution(
    Id=distribution_id,
    IfMatch=etag,
    DistributionConfig=distribution_config
    )

  4. 热冷数据分离

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    def manage_video_lifecycle(video_id, access_stats):
    """根据访问统计管理视频生命周期"""
    s3 = boto3.client('s3')

    # 计算访问频率
    views_last_30_days = access_stats.get('views_30d', 0)

    if views_last_30_days < 100:
    # 冷数据,移动到 Glacier
    for resolution in ['1080p', '720p', '480p', '360p']:
    key = f'videos/{video_id}/{resolution}.mp4'

    # 复制到 Glacier
    s3.copy_object(
    Bucket='video-bucket',
    CopySource={'Bucket': 'video-bucket', 'Key': key},
    Key=f'archive/{video_id}/{resolution}.mp4',
    StorageClass='GLACIER',
    MetadataDirective='COPY'
    )

    # 删除原文件(或保留元数据)
    s3.delete_object(Bucket='video-bucket', Key=key)

    elif views_last_30_days < 1000:
    # 温数据,移动到 Standard-IA
    for resolution in ['1080p', '720p']:
    key = f'videos/{video_id}/{resolution}.mp4'
    s3.copy_object(
    Bucket='video-bucket',
    CopySource={'Bucket': 'video-bucket', 'Key': key},
    Key=key,
    StorageClass='STANDARD_IA',
    MetadataDirective='COPY'
    )

  5. 访问统计

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    def track_video_access(video_id, resolution, user_ip):
    """记录视频访问(用于生命周期管理)"""
    import boto3
    from datetime import datetime

    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('video-access-stats')

    date = datetime.now().strftime('%Y-%m-%d')

    table.update_item(
    Key={
    'video_id': video_id,
    'date': date
    },
    UpdateExpression='ADD views :inc, #res.#r :inc',
    ExpressionAttributeNames={
    '#res': 'resolutions'
    },
    ExpressionAttributeValues={
    ':inc': 1,
    '#r': resolution
    }
    )

性能指标

  • 上传速度:>100MB/s( 10 并发分片)
  • CDN 命中率:>95%
  • 存储成本:相比全量 Standard 节省 60%(热冷分离)
  • 全球访问延迟:<100ms( CDN 边缘节点)

❓ Q&A: 云存储常见问题

Q1: 对象存储、块存储和文件存储如何选择?

A: 选择主要取决于应用场景:

  • 对象存储:适合存储大量静态文件(图片、视频、文档),需要 REST API 访问,成本敏感的场景。典型应用:网站静态资源、备份归档、大数据分析。

  • 块存储:适合需要低延迟、高 IOPS 的场景,如数据库、虚拟机磁盘。提供块设备接口,需要文件系统层。

  • 文件存储:适合需要共享文件系统、 POSIX 语义的场景,如代码仓库、共享文档、容器持久化存储。

混合方案:很多企业采用混合架构,如数据库用块存储,静态资源用对象存储,共享文件用文件存储。

Q2: 如何保证对象存储的数据一致性?

A: 对象存储通常采用最终一致性模型,通过以下机制保证:

  1. 版本控制:启用对象版本控制,可以追踪所有变更历史。

  2. 多副本机制:数据写入多个副本(通常 3 副本),使用多数派写入( Quorum)保证强一致性。

  3. 校验和:每个对象都有 ETag( MD5/SHA256),下载时验证完整性。

  4. 跨区域复制:异步复制到多个区域,保证地理冗余。

  5. 读写一致性

    • 强一致性读取:读取最新写入的数据(可能延迟)
    • 最终一致性读取:可能读到旧数据(性能更好)
1
2
3
4
5
6
# 强一致性读取示例
response = s3.get_object(
Bucket='my-bucket',
Key='file.txt',
ConsistencyControl='STRONG' # 如果支持
)

Q3: HDFS 和对象存储(如 S3)有什么区别?

A: 主要区别:

特性 HDFS 对象存储( S3)
访问方式 文件系统接口( hdfs://) REST API( HTTP)
一致性 强一致性 最终一致性
延迟 低(本地访问) 中等(网络访问)
扩展性 有限( NameNode 瓶颈) 极高(无单点)
适用场景 Hadoop 生态、大数据分析 通用存储、 Web 应用
成本 自建成本高 按需付费

选择建议

  • 如果使用 Hadoop/Spark 生态,选择 HDFS
  • 如果是通用存储需求,选择对象存储
  • 很多企业使用 S3 作为 HDFS 的后端存储( S3A)

Q4: 如何优化对象存储的上传下载性能?

A: 性能优化策略:

  1. 并发上传

    • 使用分片上传( Multipart Upload)
    • 并发上传多个分片(建议 5-10 个并发)
    • 分片大小: 5-10MB(小文件)或 100MB(大文件)
  2. 压缩

    • 上传前压缩( gzip 、 zstd)
    • 设置 Content-Encoding 头
  3. CDN 加速

    • 静态资源通过 CDN 分发
    • 设置合适的缓存策略
  4. 区域选择

    • 选择离用户最近的区域
    • 使用跨区域复制实现全球访问
  5. 连接池

    • 复用 HTTP 连接
    • 使用连接池管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 性能优化示例
from concurrent.futures import ThreadPoolExecutor
import boto3

def optimized_upload(bucket, key, file_path):
s3 = boto3.client('s3')

# 1. 分片大小优化
file_size = os.path.getsize(file_path)
if file_size > 100 * 1024 * 1024: # >100MB
chunk_size = 10 * 1024 * 1024 # 10MB chunks
max_workers = 10
else:
chunk_size = 5 * 1024 * 1024 # 5MB chunks
max_workers = 5

# 2. 并发上传
upload_id = s3.create_multipart_upload(Bucket=bucket, Key=key)['UploadId']

with ThreadPoolExecutor(max_workers=max_workers) as executor:
# ... 并发上传逻辑
pass

Q5: Ceph 和传统 SAN/NAS 存储有什么区别?

A: 主要区别:

传统 SAN/NAS

  • 集中式架构,有单点故障风险
  • 扩展性有限,需要专业硬件
  • 成本高(硬件 + 软件许可)
  • 适合传统企业应用

Ceph

  • 分布式架构,无单点故障
  • 软件定义,使用通用硬件
  • 成本低,开源免费
  • 提供对象、块、文件三种接口
  • 适合云原生、大规模场景

迁移建议

  • 新项目优先考虑 Ceph
  • 传统应用可以逐步迁移
  • 关键业务需要充分测试

Q6: 如何设计存储系统的容灾方案?

A: 容灾设计要点:

  1. 3-2-1 规则

    • 3 份数据副本
    • 2 种不同存储介质
    • 1 份异地备份
  2. RTO/RPO 定义

    • 根据业务需求定义恢复时间目标( RTO)和恢复点目标( RPO)
    • 关键业务: RTO<1 小时, RPO<5 分钟
    • 一般业务: RTO<24 小时, RPO<1 小时
  3. 多区域部署

    • 主区域:生产环境
    • 备区域:灾难恢复
    • 跨区域异步复制
  4. 定期演练

    • 定期进行灾难恢复演练
    • 验证备份完整性
    • 测试恢复流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 容灾配置示例
disaster_recovery_config = {
'primary_region': 'us-east-1',
'backup_region': 'us-west-2',
'replication': {
'enabled': True,
'mode': 'async', # 异步复制
'rpo': 3600 # 1 小时
},
'backup': {
'schedule': 'daily',
'retention': 30, # 保留 30 天
'encryption': True
},
'monitoring': {
'health_check_interval': 300, # 5 分钟
'alert_on_failure': True
}
}

Q7: 存储成本如何优化?

A: 成本优化策略:

  1. 存储分层

    • 热数据: Standard 存储
    • 温数据: Standard-IA(不频繁访问)
    • 冷数据: Glacier(归档)
  2. 生命周期管理

    • 自动迁移到低成本存储类别
    • 自动删除过期数据
  3. 数据去重

    • 块级去重
    • 文件级去重
  4. 压缩

    • 上传前压缩
    • 选择合适压缩算法(平衡压缩率和速度)
  5. 容量规划

    • 避免过度预留
    • 按需扩展
    • 定期清理无用数据

成本对比示例

1
2
3
4
5
6
7
8
9
10
11
100TB 数据,保留 1 年:

方案 1(全量 Standard):
100TB × $0.023/GB/月 × 12 = $27,600/年

方案 2(分层存储):

- 热数据( 10TB, 30% 时间): 10TB × $0.023 × 12 = $2,760
- 温数据( 30TB, 60% 时间): 30TB × $0.0125 × 12 = $4,500
- 冷数据( 60TB, 10% 时间): 60TB × $0.004 × 12 = $2,880
总计:$10,140/年(节省 63%)

Q8: 如何处理存储系统的数据迁移?

A: 数据迁移策略:

  1. 迁移前准备

    • 评估数据量、网络带宽
    • 制定迁移计划和时间窗口
    • 准备回滚方案
  2. 迁移方式

    • 在线迁移:不停机,逐步迁移
    • 离线迁移:停机窗口,批量迁移
    • 混合迁移:先迁移历史数据,再同步增量
  3. 工具选择

    • AWS:aws s3 sync、 DataSync
    • 阿里云: ossutil 、 ossimport
    • 通用: rclone 、 rsync
  4. 验证

    • 校验和对比
    • 抽样验证
    • 完整性检查
1
2
3
4
5
6
7
8
9
10
11
12
# 使用 rclone 迁移示例
# 1. 配置源和目标
rclone config

# 2. 测试迁移( dry-run)
rclone copy source:path dest:path --dry-run

# 3. 执行迁移(带进度)
rclone copy source:path dest:path --progress --transfers 10

# 4. 验证(校验和)
rclone check source:path dest:path

Q9: 对象存储的安全性如何保证?

A: 安全措施:

  1. 访问控制

    • IAM 策略(细粒度权限)
    • 桶策略( Bucket Policy)
    • ACL(访问控制列表)
    • 预签名 URL(临时访问)
  2. 加密

    • 传输加密: HTTPS/TLS
    • 存储加密:服务端加密( SSE-S3 、 SSE-KMS)
    • 客户端加密:上传前加密
  3. 审计

    • 启用访问日志( Access Logging)
    • CloudTrail 审计( AWS)
    • 监控异常访问
  4. 合规

    • 数据分类和标记
    • 保留策略
    • 合规性检查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 安全配置示例
# 1. 加密存储
s3.put_object(
Bucket='my-bucket',
Key='sensitive-data.txt',
Body=data,
ServerSideEncryption='AES256' # 或 'aws:kms'
)

# 2. 预签名 URL(临时访问)
url = s3.generate_presigned_url(
'get_object',
Params={'Bucket': 'my-bucket', 'Key': 'file.txt'},
ExpiresIn=3600 # 1 小时有效
)

# 3. 桶策略(禁止公开访问)
bucket_policy = {
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyPublicAccess",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*",
"Condition": {
"StringNotEquals": {
"aws:username": ["allowed-user"]
}
}
}]
}

Q10: 如何监控存储系统的性能和健康状态?

A: 监控指标和工具:

关键指标: 1. 容量指标

  • 总容量、已用容量、可用容量
  • 增长率、预测容量
  1. 性能指标

    • IOPS 、吞吐量、延迟
    • 请求成功率、错误率
  2. 可用性指标

    • 服务可用性( SLA)
    • 节点健康状态
    • 副本完整性
  3. 成本指标

    • 存储成本、请求成本
    • 数据传输成本

监控工具

  • 云服务商: CloudWatch( AWS)、云监控(阿里云)
  • 开源: Prometheus + Grafana
  • 专业工具: Datadog 、 New Relic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 监控示例(使用 boto3)
import boto3
from datetime import datetime, timedelta

cloudwatch = boto3.client('cloudwatch')

# 获取存储使用量
response = cloudwatch.get_metric_statistics(
Namespace='AWS/S3',
MetricName='BucketSizeBytes',
Dimensions=[
{'Name': 'BucketName', 'Value': 'my-bucket'},
{'Name': 'StorageType', 'Value': 'StandardStorage'}
],
StartTime=datetime.now() - timedelta(days=7),
EndTime=datetime.now(),
Period=3600,
Statistics=['Average']
)

# 获取请求数
response = cloudwatch.get_metric_statistics(
Namespace='AWS/S3',
MetricName='NumberOfObjects',
Dimensions=[
{'Name': 'BucketName', 'Value': 'my-bucket'}
],
StartTime=datetime.now() - timedelta(days=1),
EndTime=datetime.now(),
Period=300,
Statistics=['Average']
)

# 告警配置
cloudwatch.put_metric_alarm(
AlarmName='s3-bucket-size-alarm',
MetricName='BucketSizeBytes',
Namespace='AWS/S3',
Statistic='Average',
Period=3600,
EvaluationPeriods=1,
Threshold=1000000000000, # 1TB
ComparisonOperator='GreaterThanThreshold',
AlarmActions=['arn:aws:sns:region:account:topic']
)

最佳实践

  • 设置容量告警( 80%、 90%、 95%)
  • 监控异常访问模式
  • 定期检查备份完整性
  • 跟踪成本趋势
  • 建立监控仪表板( Dashboard)

总结

云存储系统是现代云计算基础设施的核心组件,选择合适的存储类型、设计合理的架构、实施有效的优化策略,对于构建稳定、高效、经济的存储解决方案至关重要。无论是对象存储的 RESTful 接口、块存储的低延迟特性,还是文件存储的 POSIX 语义,每种存储类型都有其适用场景。通过理解 CAP/BASE 理论、掌握复制策略、实施容灾方案,可以构建出既满足业务需求又控制成本的存储系统。

在实际应用中,往往需要结合多种存储类型,形成混合存储架构。同时,随着数据量的增长和业务需求的变化,存储系统也需要持续优化和演进。希望本文的内容能够帮助读者更好地理解和应用云存储技术,在实际项目中做出明智的技术选择。

  • 本文标题:云计算(三)存储系统与分布式架构
  • 本文作者:Chen Kai
  • 创建时间:2023-01-25 10:45:00
  • 本文链接:https://www.chenk.top/cloud-computing-storage-systems/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论