分布式存储
分布式存储
存储的基础概念
集群存储:是将多台存储设备中的存储空间聚合成一个能够给应用服务器提供统一访问接口和管理界面的存储池,应用可以通过该访问接口透明地访问和利用所有存储设备上的磁盘,可以充分发挥存储设备的性能和磁盘利用率。数据将会按照一定的规则从多台存储设备上存储和读取,以获得更高的并发访问性能。
存储的类型
-
存储格式:
-
块存储
- 优点:
- 可用通过 Raid、LVM 等简单技术实现高可用;
- 可独立使用;
- 简单方便;
- 缺点:
- 不利于在多台网络设备之间进行共享;
- 不利于不同操作系统主机间的数据共享;
- 优点:
-
文件存储
- 优点:
- 构建成本资金较低;
- 可在不同主机之间共享存储;
- 缺点:
- 读写效率低,传输效率慢;
- 文件存储数据会被分为两部分:超级块,数据块;
- 优点:
-
对象存储
- 优点:
- 读写效率高;
- 可在不同主机之间共享存储;
- 容器无限大:可以到 EB 级别;
- 缺点:
- 造价昂贵;
- 技术实现难度较高;
- 对象存储则可以将数据的元数据信息单独存放到一台机器,然后将文件的真实数据内容存放到不同的机器;
- 优点:
-
- 网络拓扑存储:
- DAS(Direct-Attached Storage 直接附加存储):等同于存储设备通过数据线、光缆、SATA 直连主机;
- 优点:
- 技术简单;
- 传输效率高;
- 缺点:
- 存储设备与主机相互绑定,不利于后期扩展与共享(服务器本身容易成为系统瓶颈);
- 数据备份操作复杂;
- 优点:
- NAS(Network Attached Storage 网络附加存储):通过网络在存储主机与使用主机之间传输数据 ;
- 例如:NFS、Samba等软件实现文件共享;
- 优点:
- 技术相对简单(不需要格式化成文件系统,就可以直接使用);
- 不要求存储设备直连本机(与 DAS 相比),只需在局域网下即可(FTP 是可以在公网中使用);
- 缺点:
- 存储速率较慢,容易受到网络上其他流量的影响,当网络上有其他大数据流量时会严重影响系统性能。因为搭建NAS是为了给多个人使用,这样就造成存储空间和网络宽带传输效率均会被平分;
- 存储数据通过普通数据网络传输,容易产生数据泄密等安全问题。 存储只能以文件方式访问,而不能像普通文件系统一样直接访问物理数据块,因此会在某些情况下严重影响系统效率,比如大型数据库就不能使用NAS;
- 不容易进行扩展;
- SAN (Storage Area Network 存储区域网络):将生产网络与存储网络进行隔离,有效增加各部效率,减轻网络设备压力,适合大并发业务;
- iSCSI 客户机会发现标配两块网卡,主要是为了便于后期的利用,例如:聚合;
- 优点:
- 存储安全性较高 存储速率较高;
- 缺点:
- 造价昂贵;不论是SAN阵列柜还是SAN必须的光纤通道交换机价格都是十分昂贵的,就连服务器上使用的光通道卡的价格也是不容易被小型商业企业所接受的; 技术难度相对较高(相对于NAS,DAS),需要使用专门的存储技术; 需要单独建立光纤网络,异地扩展比较困难;
- DAS,NAS,SAN特性对比
- DAS(Direct-Attached Storage 直接附加存储):等同于存储设备通过数据线、光缆、SATA 直连主机;

Ceph 简介
Ceph是开源,分布式存储统一系统;
三大存储结构:块存储、文件存储、对象存储;
Ceph是一种开源的软件定义存储系统。它也是一款以对象存储技术(独立存储技术)为核心,并在此基础之上实现块存储、文件存储的分布式存储系统。
- 优点:
- 具有良好的扩展性、兼容性和可靠性;
- 缺点:
- 配置较为复杂、学习维护成本较高;
工作原理:
用 “分布式文件柜” 类比最容易理解:Ceph 就是把多台服务器的硬盘 “拼” 成一个超大的 “共享文件柜”,不管存多少数据、哪台服务器坏了,都能安全存取 —— 核心是 “分散存、随便坏、找得到”。
核心逻辑:3 步实现 “分布式存储”
1. 数据 “切碎” 分散存(解决 “存不下”)
2. 每块数据 “多存几份”(解决 “怕损坏”)
3. 用 “导航系统” 找数据(解决 “找得到”)
- Ceph 把每一个待管理的数据流(例如一个文件)切分成一到多个固定大小的对象数据,并以其为原子单元完成数据存取;
- Ceph通过内部的 crush 机制,实时方式计算出一个文件应该存储到哪个存储对象里面,从而实现快速查找对象的一种方式;
- 底层通过RADOS(可靠的自动分发的目的存储)来进行存储;
- 通过OSD绑定每一块物理硬盘,OSD - 对象存储守护进程 (OSD) 存储对象。
Ceph三层结构

RADOS负责底层,数据磁盘的管理:
- OSDs是在存储服务器上运行的进程。OSD 负责 管理单个存储单元,通常是单个磁盘。
- MON是Ceph监控组件(Monitor,简称 MON)负责维护集群的状态和元数据信息。
LIBRADOS是Ceph的中间层,通过自编程方式实现数据的存储能力,提供数据的存储方式:
- RBD:模拟成一个个独立的块;
- CephFS:文件存储,通过一个标准的文件系统接口来进行数据存储;
- RGW(RADOSGW):对象存储网关接口,体统对象存储功能;
对外接口层,通过RBD、CephFS、RGW的API实现;
ceph 组件

ceph环境搭建
-
cephadm
- docker+python3+LVM2+时间同步
-
192.168.25.51 admin
-
192.168.25.52
-
192.168.25.53
-
每个虚拟机加几个硬盘,大于5G 三块 120g
#安装依赖软件(所有节点操作)
apt install -y python3 lvm2 ntpdate
#设置时区(所有节点操作)
timedatectl set-timezone Asia/Shanghai
# 同步到公共 NTP 服务器(如阿里云时间服务器)
sudo ntpdate ntp.aliyun.com
# (推荐)配置定时同步(避免再次偏移)
echo "*/10 * * * * root ntpdate ntp.aliyun.com >/dev/null 2>&1" | sudo tee -a /etc/crontab
# 重启定时任务服务
sudo systemctl restart cron
# 验证时间是否一致(在所有 MON 节点执行,检查输出是否接近)
date
#下载cephadm软件(admin 51)
wget http://192.168.57.200/Software/cephadm
#官网下载cephadm
#curl --silent --remote-name --location https://download.ceph.com/rpm-squid/el9/noarch/cephadm
#添加权限(admin 51)
chmod +x cephadm
#移动到/uxr/local/bin目录(admin 51)
mv cephadm /usr/local/bin
#下载镜像(admin 51)
wget http://192.168.57.200/Software/cephv18.tar
#上传镜像到本地镜像站(admin 51)
docker load -i cephv18.tar
#创建新集群(admin 51)
cephadm bootstrap --mon-ip 192.168.25.51 --cluster-network 192.168.25.0/24 --allow-fqdn-hostname
# 注意观察输出信息,记录dashboard账号信息
#在网页内修改密码
#安装ceph命令(admin 51)
apt install ceph-common -y
#查看现有的集群主机列表
ceph orch host ls
#把ceph的秘钥放到其他服务器上
ssh-copy-id -f -i /etc/ceph/ceph.pub root@lwy52
ssh-copy-id -f -i /etc/ceph/ceph.pub root@lwy53
#将秘钥节点加入到集群
ceph orch host add lwy52 192.168.25.52
ceph orch host add lwy53 192.168.25.53
#温馨提示:
#将集群加入成功后,会自动创建"/var/lib/ceph/<Ceph_Cluster_ID>"相关数据目录。
#其他机会自动下载需要的东西
#查看集群信息
ceph orch host ls
#移除主机【选做,如果你将来真有这个需求在操作】
ceph orch host rm lwy52
部署OSD(存储节点,使用所有磁盘)
#自动发现并部署OSD(推荐)**
#cephadm可自动识别节点上的空磁盘(未分区、未挂载),直接部署OSD:
# 允许ceph使用所有节点的/dev/sdb和/dev/sdc(确保磁盘未被使用)
ceph orch daemon add osd lwy51:/dev/sdb
ceph orch daemon add osd lwy51:/dev/sdc
ceph orch daemon add osd lwy52:/dev/sdb
ceph orch daemon add osd lwy52:/dev/sdc
ceph orch daemon add osd lwy53:/dev/sdb
ceph orch daemon add osd lwy53:/dev/sdc
# 查看OSD部署状态
ceph orch ps --daemon_type=osd
#验证OSD状态
ceph osd tree # 应显示6个OSD(3节点×2块盘),状态为`up`
#验证集群健康状态
# 查看集群整体状态(HEALTH_OK为正常)
ceph -s
块存储(RBD)操作
##创建RBD存储池与镜像
# 1. 创建RBD存储池(pg数:6 OSD × 10 = 60 → 取64)
ceph osd pool create rbd-pool 64 64
# 初始化RBD功能
rbd pool init rbd-pool
# 2. 创建RBD镜像(10GB)
rbd create rbd-pool/test-image --size 10G
# 3. 查看镜像信息
rbd info rbd-pool/test-image
# 列出池内所有镜像
rbd ls rbd-pool
##映射RBD镜像到本地并使用
# 1. 映射镜像到本地设备(需root权限)
rbd map rbd-pool/test-image
# 2. 查看映射结果(应显示/dev/rbd0)
rbd showmapped
# 3. 格式化并挂载
mkfs.ext4 /dev/rbd0
mkdir /mnt/rbd-test
mount /dev/rbd0 /mnt/rbd-test
# 4. 测试读写
echo "RBD block test" > /mnt/rbd-test/test.txt
# 验证内容
cat /mnt/rbd-test/test.txt
# 查看挂载空间
##镜像快照与克隆(高级功能)
# 1. 创建快照
rbd snap create rbd-pool/test-image@v1
# 2. 查看快照
rbd snap ls rbd-pool/test-image
# 3. 保护快照(防止删除,用于克隆)
rbd snap protect rbd-pool/test-image@v1
# 4. 从快照克隆新镜像
rbd clone rbd-pool/test-image@v1 rbd-pool/clone-image
# 5. 查看克隆镜像
rbd ls rbd-pool
##清理操作(可选)
#卸载磁盘
umount /mnt/rbd-test
rbd unmap /dev/rbdrbd unmap /dev/rbd0
# 如需删除镜像(需先删除快照或解除保护)
rbd snap unprotect rbd-pool/test-image@v1
rbd snap rm rbd-pool/test-image@v1
rbd rm rbd-pool/test-image
rbd rm rbd-pool/clone-image
文件存储(CephFS)操作
##创建CephFS存储池与文件系统
# 1. 创建元数据池和数据池
# 元数据池(pg数较小)
ceph osd pool create cephfs-meta 32 32
# 数据池
ceph osd pool create cephfs-data 64 64
# 2. 创建CephFS文件系统(关联两个池)
ceph fs new myfs cephfs-meta cephfs-data
# 3. 查看文件系统状态
# 显示myfs
ceph fs ls
# 详细状态
ceph fs status myfs
##部署MDS(元数据服务器,CephFS必需)
#部署2个MDS(高可用,分布在两个节点)
ceph orch apply mds myfs --placement="lwy52,lwy53"
#查看MDS状态(确保1个active,1个standby)
ceph orch ps --daemon_type=mds
# 确认MDS状态
ceph fs status myfs
##挂载CephFS到本地
#通过内核驱动挂载
# 1. 创建挂载点
mkdir /mnt/cephfs
# 2. 获取Ceph管理员密钥(用于挂载认证)
ceph auth get client.admin -o /etc/ceph/admin.keyring
# 3. 挂载(指定monitor地址和密钥)
mount -t ceph 192.168.25.51:6789,192.168.25.52:6789,192.168.25.53:6789:/ /mnt/cephfs \
-o name=admin,secret=AQC4g/ho+SAuKhAAjL4ir7th3L3cneMaXdjJTw==
#secret=输入/etc/ceph/admin.keyring里key的值
# 4. 验证挂载
df -h /mnt/cephfs
##测试CephFS读写
# 1. 创建测试文件和目录
mkdir /mnt/cephfs/testdir
echo "CephFS file test" > /mnt/cephfs/testdir/file.txt
# 2. 在其他节点挂载并访问(验证共享性)
# 在Server-13执行:
mkdir /mnt/cephfs
mount -t ceph 192.168.25.51:6789:/ /mnt/cephfs -o name=admin,secret=AQC4g/ho+SAuKhAAjL4ir7th3L3cneMaXdjJTw==
# 应能看到内容
cat /mnt/cephfs/testdir/file.txt
##清理操作
# 卸载挂载点
umount /mnt/cephfs # 内核驱动挂载
# 或
fusermount -u /mnt/cephfs # FUSE挂载
# 如需删除文件系统(谨慎操作)
ceph fs rm myfs --yes-i-really-mean-it
ceph osd pool rm cephfs-data cephfs-data --yes-i-really-really-mean-it
ceph osd pool rm cephfs-meta cephfs-meta --yes-i-really-really-mean-it
对象存储(RGW)操作
## 部署RGW服务
# 1. 部署RGW实例(2个节点,高可用)
ceph orch apply rgw myrgw --placement="lwy51,lwy53"
#等待 1-3 分钟(Cephadm 需要拉取镜像、创建容器)
# 2. 查看RGW状态(确认running)
ceph orch ps --daemon_type=rgw
##配置RGW访问(S3兼容接口)
# 1. 创建RGW用户(用于S3认证)
radosgw-admin user create --uid="s3user" --display-name="S3 Test User"
# 2. 记录输出中的access_key和secret_key(后续访问需要)
# 示例输出:
# "access_key": ""
# "secret_key": ""
#配置RGW访问地址
# 查看RGW监听地址(在lwy51执行)
netstat -tulpn | grep 7480 # 应显示0.0.0.0:7480
# 安装模块
pip install boto3
#以下操作还需要搭配python脚本使用
#### 1. 查询RGW文件(list)
#- 列出桶中所有文件:
python3 rgw_tool.py list \
--endpoint "http://192.168.1.100:7480" \
--access-key "你的access-key" \
--secret-key "你的secret-key" \
--bucket "my-rgw-bucket"
#- 列出桶中`data/`前缀(模拟目录)的文件:
python3 rgw_tool.py list \
--endpoint "http://192.168.1.100:7480" \
--access-key "你的access-key" \
--secret-key "你的secret-key" \
--bucket "my-rgw-bucket" \
--prefix "data/" # 只显示data/开头的文件
#### 2. 上传本地文件到RGW(upload)
#- 上传本地文件,使用默认文件名(RGW中文件名与本地相同):
python3 rgw_tool.py upload \
--endpoint "http://192.168.1.100:7480" \
--access-key "你的access-key" \
--secret-key "你的secret-key" \
--bucket "my-rgw-bucket" \
--local-file "./local-data.csv" # 本地文件路径
#- 上传并自定义RGW中的文件名:
python3 rgw_tool.py upload \
--endpoint "http://192.168.1.100:7480" \
--access-key "你的access-key" \
--secret-key "你的secret-key" \
--bucket "my-rgw-bucket" \
--local-file "./local-data.csv" \
--rgw-name "backup/2024-data.csv" # RGW中保存的路径和文件名
#### 3. 从RGW下载文件(download)
#- 下载文件到当前目录(默认文件名):
python3 rgw_tool.py download \
--endpoint "http://192.168.1.100:7480" \
--access-key "你的access-key" \
--secret-key "你的secret-key" \
--bucket "my-rgw-bucket" \
--rgw-file "backup/2024-data.csv" # RGW中的文件路径
#- 下载文件到指定目录:
python3 rgw_tool.py download \
--endpoint "http://192.168.1.100:7480" \
--access-key "你的access-key" \
--secret-key "你的secret-key" \
--bucket "my-rgw-bucket" \
--rgw-file "backup/2024-data.csv" \
--save-to "/home/user/downloads/" # 保存到指定目录
#- 下载并自定义本地文件名:
python3 rgw_tool.py download \
--endpoint "http://192.168.1.100:7480" \
--access-key "你的access-key" \
--secret-key "你的secret-key" \
--bucket "my-rgw-bucket" \
--rgw-file "backup/2024-data.csv" \
--save-to "/home/user/downloads/今年数据.csv" # 自定义文件名
------------------------------------------------------------------------------------------------------
## python脚本
#保存为rgw_tool.py
import os
import boto3
import argparse
from datetime import datetime
from botocore.exceptions import (
NoCredentialsError, ClientError, EndpointConnectionError
)
def init_rgw_client(rgw_endpoint, access_key, secret_key, region="us-east-1"):
"""初始化RGW客户端(通用函数)"""
try:
return boto3.client(
's3',
region_name=region,
endpoint_url=rgw_endpoint,
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
verify=False # 测试环境禁用SSL验证,生产环境删除此行(启用SSL)
)
except Exception as e:
print(f"❌ 客户端初始化失败:{str(e)}")
return None
def list_objects(client, bucket_name, prefix=None, max_items=1000):
"""查询RGW桶中的文件"""
try:
params = {'Bucket': bucket_name, 'MaxKeys': max_items}
if prefix:
params['Prefix'] = prefix # 按前缀筛选(如"dir/"模拟目录)
response = client.list_objects_v2(**params)
if 'Contents' not in response:
print(f"📌 桶 [{bucket_name}] 中无符合条件的文件(前缀:{prefix or '无'})")
return
# 格式化输出
print(f"\n📂 桶 [{bucket_name}] 中的文件(共 {response['KeyCount']} 个):")
print(f"{'文件名':<50} {'大小(KB)':<10} {'最后修改时间'}")
print("-" * 100)
for obj in response['Contents']:
key = obj['Key']
size_kb = round(obj['Size'] / 1024, 2) if obj['Size'] > 0 else 0
last_modified = obj['LastModified'].strftime("%Y-%m-%d %H:%M:%S")
print(f"{key:<50} {size_kb:<10} {last_modified}")
except ClientError as e:
error_code = e.response['Error']['Code']
if error_code == 'NoSuchBucket':
print(f"❌ 错误:桶 [{bucket_name}] 不存在")
elif error_code == 'AccessDenied':
print(f"❌ 错误:无权限访问桶 [{bucket_name}]")
else:
print(f"❌ 查询失败:{str(e)}")
except Exception as e:
print(f"❌ 查询时发生未知错误:{str(e)}")
def upload_file(client, local_path, bucket_name, object_name=None):
"""上传本地文件到RGW"""
# 处理目标文件名(默认用本地文件名)
if not object_name:
object_name = os.path.basename(local_path)
try:
# 检查本地文件是否存在
if not os.path.isfile(local_path):
print(f"❌ 错误:本地文件不存在 - {local_path}")
return False
# 执行上传
client.upload_file(local_path, bucket_name, object_name)
print(f"✅ 上传成功:\n本地文件: {local_path}\nRGW路径: {bucket_name}/{object_name}")
return True
except ClientError as e:
error_code = e.response['Error']['Code']
if error_code == 'NoSuchBucket':
print(f"❌ 错误:桶 [{bucket_name}] 不存在")
elif error_code == 'AccessDenied':
print(f"❌ 错误:无权限上传到桶 [{bucket_name}]")
else:
print(f"❌ 上传失败:{str(e)}")
except Exception as e:
print(f"❌ 上传时发生未知错误:{str(e)}")
return False
def download_file(client, bucket_name, object_name, local_path=None):
"""从RGW下载文件到本地"""
# 处理本地保存路径
if not local_path:
# 默认保存到当前目录,使用原文件名
local_filename = os.path.basename(object_name)
local_path = os.path.join(os.getcwd(), local_filename)
else:
# 若本地路径是目录,则拼接文件名
if os.path.isdir(local_path):
local_filename = os.path.basename(object_name)
local_path = os.path.join(local_path, local_filename)
try:
# 执行下载
client.download_file(bucket_name, object_name, local_path)
print(f"✅ 下载成功:\nRGW文件: {bucket_name}/{object_name}\n本地路径: {local_path}")
return True
except ClientError as e:
error_code = e.response['Error']['Code']
if error_code == 'NoSuchBucket':
print(f"❌ 错误:桶 [{bucket_name}] 不存在")
elif error_code == 'NoSuchKey':
print(f"❌ 错误:RGW中无此文件 - {object_name}")
elif error_code == 'AccessDenied':
print(f"❌ 错误:无权限下载文件 [{object_name}]")
else:
print(f"❌ 下载失败:{str(e)}")
except FileNotFoundError:
print(f"❌ 错误:本地目录不存在 - {os.path.dirname(local_path)}")
except Exception as e:
print(f"❌ 下载时发生未知错误:{str(e)}")
return False
if __name__ == "__main__":
# 命令行参数解析
parser = argparse.ArgumentParser(description="Ceph RGW工具:查询、上传、下载文件")
parser.add_argument(
'action',
choices=['list', 'upload', 'download'],
help="操作类型:list(查询)、upload(上传)、download(下载)"
)
parser.add_argument(
'--endpoint',
required=True,
help="RGW访问端点(如:http://192.168.1.100:7480)"
)
parser.add_argument(
'--access-key',
required=True,
help="RGW用户的Access Key"
)
parser.add_argument(
'--secret-key',
required=True,
help="RGW用户的Secret Key"
)
parser.add_argument(
'--bucket',
required=True,
help="RGW目标桶名"
)
# 查询(list)专属参数
parser.add_argument(
'--prefix',
help="查询前缀(如:'data/',用于筛选特定目录下的文件)"
)
parser.add_argument(
'--max-items',
type=int,
default=1000,
help="查询最大文件数量(默认1000)"
)
# 上传(upload)专属参数
parser.add_argument(
'--local-file',
help="本地文件路径(上传时必填,如:./test.txt)"
)
parser.add_argument(
'--rgw-name',
help="上传到RGW后的文件名(可选,默认使用本地文件名)"
)
# 下载(download)专属参数
parser.add_argument(
'--rgw-file',
help="RGW中的文件路径(下载时必填,如:data/report.pdf)"
)
parser.add_argument(
'--save-to',
help="本地保存路径(可选,默认当前目录)"
)
parser.add_argument(
'--region',
default="us-east-1",
help="区域名(默认:us-east-1,RGW通常不校验)"
)
args = parser.parse_args()
# 初始化RGW客户端
rgw_client = init_rgw_client(
rgw_endpoint=args.endpoint,
access_key=args.access_key,
secret_key=args.secret_key,
region=args.region
)
if not rgw_client:
exit(1)
# 根据操作类型执行对应功能
if args.action == 'list':
list_objects(
client=rgw_client,
bucket_name=args.bucket,
prefix=args.prefix,
max_items=args.max_items
)
elif args.action == 'upload':
# 校验上传必填参数
if not args.local_file:
print("❌ 错误:上传操作必须指定 --local-file(本地文件路径)")
exit(1)
upload_file(
client=rgw_client,
local_path=args.local_file,
bucket_name=args.bucket,
object_name=args.rgw_name
)
elif args.action == 'download':
# 校验下载必填参数
if not args.rgw_file:
print("❌ 错误:下载操作必须指定 --rgw-file(RGW中的文件路径)")
exit(1)
download_file(
client=rgw_client,
bucket_name=args.bucket,
object_name=args.rgw_file,
local_path=args.save_to
)
浙公网安备 33010602011771号