
简介一份系统梳理主流分布式存储技术的PDF文档面向大数据、云计算领域的开发者与架构师旨在帮助读者厘清GFS、HDFS、Minio等系统的设计思路与适用场景。内容先对比文件、块、对象三种存储方式的本质区别再深入具体系统GFS采用主从架构以64MB chunk和多副本机制保障大规模数据读写HDFS作为开源实现强调高容错与自动恢复适合批处理场景Minio则基于REST API与S3兼容接口聚焦轻量级对象存储。此外还梳理了分布式文件系统从网络文件系统、SAN共享文件系统到云文件系统的演化历程并论及OpenStack Swift等代表产品。资源为单个PDF文件压缩包仅1.39MB便于下载阅读。目前已有402人学习适合作为技术综述、系统设计参考或面试复习的紧凑资料。1. 分布式存储技术选型先吵清楚文件、块、对象再动手做存储选型这事我在不同团队见过太多次「会前拍桌子、会后各干各」的场面业务方说数据要共享DBA 说要低延迟块设备算法组说只要对象接口。吵到最后才发现分布式存储技术本身并不是一套方案打天下文件、块、对象三种形态服务的根本不是同一类「用户」。选型的第一步不是比产品参数而是先问清楚这份数据是给人看的还是给软件读写的还是给另一个程序按 ID 取的。这个问题不解决后面所有架构讨论都是空中楼阁。这篇笔记从三种存储形态讲起沿着 GFS、HDFS、MinIO、Ceph 这些经典系统的架构和取舍说清楚每一类系统的边界和踩坑点最后落到部署验证的具体步骤上希望对正在做选型或者刚接触分布式存储的从业者有点实际帮助。2. 块、文件、对象三种形态服务对象不同参数和边界就差很远2.1 块存储它只认字节不认内容块存储最常见的形态就是 Windows 的 C 盘或者 Linux 里的 /dev/sda。它对外提供的是一个可以按字节寻址的块设备系统里存的是什么内容、什么格式块存储本身完全不知道。它的职责只有两个数据写入和读回。因为不解析内容它可以把性能做到极致响应时间能压到很低这也是数据库这类对延迟敏感的系统必须用块存储的原因。在分布式场景里块存储的典型代表是 Ceph RBD 和开源的 Curve。Ceph RBD 在 OpenStack 里对接 cinder 后端、做虚拟机实例的硬盘是特别成熟的用法。虚拟化环境里一台虚拟机就是一个块设备消费者它不在乎上层是 ext4 还是 xfs只在乎读写的延迟和 IOPS。块存储的问题在于共享能力弱一个块设备在同一个时间点通常只能被一个客户端挂载读写要让多个节点同时读写同一个块必须依赖 GFS 这类集群文件系统或者分布式锁复杂度一下子上来了。2.2 文件存储目录层级和共享是两个核心资产文件存储对用户的呈现是 C:\Users\Downloads\text.doc 或者 /data/logs/app.log 这种路径。它比块存储多做了一层管理目录结构、文件名、文件权限都交给了文件系统本身。NFS、CIFS、FTP 这些最常见的共享协议底层都是文件存储。文件存储的共享能力是它最大的护城河——同一个目录可以被几十个客户端挂载权限可以在文件级别控制这对办公协作、代码仓库、日志采集这类场景是刚需。分布式文件系统里HDFS 和 MooseFS 都是文件存储的代表。HDFS 提供的是一个大 namespace业务方通过统一的接口访问分布式集群感觉就像在访问一个普通文件系统。MooseFS 则直接把目录结构缓存在 Master 内存里客户端通过 FUSE 挂载成本地目录用起来和本地文件系统几乎没有差别。文件存储明显的短板是性能和规模受元数据服务制约尤其是海量小文件场景元数据服务器的内存和访问吞吐会成为绕不开的瓶颈。2.3 对象存储UUID 一报数据就到对象存储的呈现方式是一个 UUID。数据和元数据打包成一个对象存放在一个超大池子里访问时只需要报出这个 UUID系统就能定位到它。你不需要知道它存在哪个目录、哪个磁盘它也不支持像文件系统那样的路径遍历。由于设计之初就基于一致性哈希这类技术对象存储天然容易扩展到超大规模非常适合数据量大、增速快的视频、图片、日志备份这类非结构化数据。对象存储的代表是 OpenStack Swift 和 MinIO。Swift 用 Ring 环结构把对象均匀分布在虚拟节点上增加、删除节点时只迁移少量数据MinIO 用 Golang 实现兼容 S3 接口用纠删码和 checksum 保护数据部署极其简单。对象存储的代价是一个文件一旦写入基本就是整体读回不支持文件中间修改也不提供文件系统级别的目录管理。它适合的是「写一次、读很多次、不关心路径」的数据。2.4 一张表和一个选型顺序维度块存储文件存储对象存储服务用户可读写块设备的软件系统文件系统、数据库自然人 / 文件共享应用其它计算机软件典型形态/dev/sda/data/logs/app.logUUID性能特征最低延迟、最高 IOPS中等受元数据影响适合大文件顺序读写共享能力差通常单客户端独占强多客户端挂载强通过 REST API 访问适用场景数据库、虚拟化硬盘文件共享、日志、备份图片视频、海量非结构化数据代表系统Ceph RBD、CurveHDFS、MooseFS、GlusterFSSwift、MinIO、Ozone我一般会按这个顺序做选型先确认数据量级和访问模式如果是数据库或虚拟机直接用块存储如果业务方明确要目录和权限走文件存储如果数据是海量非结构化、以写后读为主对象存储最省事。顺序定下来再进具体产品的技术细节。很多时候团队吵架吵的不是产品好坏而是连数据形态的服务对象都没对齐。3. GFS 与 HDFS 架构拆解中心化架构的成名、瓶颈与演进方向3.1 四十年演进从 NFS 到云文件系统的四次转身分布式存储的发展大致可以分成四个阶段。1980 年代以太网兴起研究重点是网络环境下的文件共享成果是 CMU 的 AFS 和 SUN 的 NFS。1990 年代 SAN 存储区域网络出现存储开始独立于计算机系统发展研究重点转向 SAN 共享文件系统。2000 年代高速网络普及面向对象并行文件系统登场对象存储技术和高效元数据管理成为核心。2010 年代云计算和大数据落地数据爆炸式增长研究重点变成 EB 级大规模存储、数据高可用策略复制、HA、纠删码、消重压缩分层等智能存储技术以及计算存储融合。每个阶段都是在补齐上一个阶段的能力缺口。1980 年代解决的是「能不能共享」1990 年代解决的是「存储能不能独立扩展」2000 年代解决的是「容量和性能瓶颈」2010 年代解决的是「云环境下的弹性、多租户和 QoS」。这套演进逻辑对选型很有参考价值如果一个业务还停留在早期阶段的需求就没必要上太重型的云原生存储方案。3.2 GFS中心化架构的模板与它的三个缺点2003 年 Google 发布 GFS 论文是分布式文件系统领域里程碑式的事件。GFS 由 master、多个 chunkserver 和多个 client 组成。文件被切分成固定大小的 chunk每个 chunk 创建时 master 分配一个 64 位的全局唯一 chunk handle。chunkserver 把 chunk 存储在本地磁盘的 Linux 文件里通过 chunk handle 和 byte range 读写。默认每个 chunk 存三副本用户可以为不同 namespace 下的文件配置不同副本数。GFS 是典型的中心化架构后来的很多分布式系统都参考它。这套架构的优点很实在结构简单、集群水平可扩展、能构建在廉价服务器上、支持 PB 级存储。缺点也一样明显不适合小文件存储master 会成为内存瓶颈写文件只支持 Append 模式没有实现 POSIX 标准接口一致性只有松弛一致性。这里的关键认知是GFS 有这些问题但它依然是 Google 存储的核心基础设施。说明一个系统只要在自己的场景里做到多指标的平衡就足够成功不需要面面俱到。3.3 HDFS 与生态同一个模板的工程化改良HDFS 是 Hadoop 项目对 GFS 思想的开源实现核心架构同样是主从模式NameNode 管元数据DataNode 管数据块默认三副本。它比 GFS 更注重稳定性和易用性能处理 GB、TB 甚至 PB 级别的数据能构建在廉价机器上通过多副本机制提高可靠性。HDFS 被诟病的问题在业界也是有共识的不适合低延迟数据访问毫秒级访问做不到海量小文件会占用 NameNode 大量内存内存总是有限的一个文件只能有一个写者不支持并发写入也不支持随机修改只能 append。这些限制和 GFS 几乎一脉相承都是中心化架构的固有代价。但 HDFS 在 Hadoop 生态里的地位是不可替代的MapReduce、Spark 等计算框架都是基于「数据本地性」这个假设设计的HDFS 的顺序写、批量读模式天然契合批处理。3.4 无中心化与去中心化GlusterFS、Ceph 的另一条路与中心化对应的另一条路是无中心化架构代表是 GlusterFS。GlusterFS 没有独立的元数据服务通过可叠加的 Translator 中间件把多个单机文件系统融合成统一 namespace。它的优势是数据以相同目录结构保存在单机文件系统上不用担心 GlusterFS 本身不可用导致数据丢失且没有明显单点。缺点是元数据操作性能差而文件系统日常操作里元数据操作占比超过 50%另外服务器进出集群会引起一致性哈希重新计算导致部分数据迁移影响业务 IO。Ceph 走的是另一条路它有一个底层的 RADOS 分布式对象存储系统通过 CRUSH 算法计算数据存储位置不依赖传统的单点元数据服务数据分布和性能扩展做得很好。Ceph 最突出的点在于它是统一存储——对象RADOSGW、块RBD、文件CephFS三种接口全部支持。但代价是运维复杂度高、无中心化架构需要提前规划设计而且因为加入日志数据实际会双写性能天然打折扣。我在实际项目里的体感是Ceph 适合愿意投入专职运维团队的规模小团队直接上 Ceph 很容易被日常维护拖垮。3.5 演进方向Colossus、JuiceFS 与新型硬件GFS 之后Google 第二代文件系统 Colossus 对元数据管理做了两个方向的演进一是对元数据存储进行分组存在 N 组元数据服务每组独立负责一部分元数据二是将元数据存储在分布式 KV 系统中。这个演进思路直接影响了后来的很多系统。JuiceFS 是云原生的另一个思路利用公有云已有的对象存储替换 DataNode 和 ChunkServer自身只负责客户端逻辑和元数据管理。它用 Redis 做元数据存储对象存储在底层保存实际数据是一个完全弹性的 Serverless 存储系统。代价是元数据依赖 Redis数据安全依赖云厂商的存储可靠性。PolarFS 则是走新硬件路线基于 RDMA 和 NVMe SSD通过 SPDK 绕过内核降低软件开销延迟压到极低但它依赖专用硬件、不支持跨 AZ适用范围相对窄。这些方向说明一个事实不存在放之四海而皆准的解决方案每类系统都在用「丢掉一部分通用性」换「某个指标的最大化」。4. HDFS 高可用部署从单 NameNode 到双 Active 的落地步骤4.1 为什么建议先用 HDFS 练手而非 Ceph如果你刚开始接触分布式存储我的建议是先搭 HDFS而不是先搭 Ceph。原因有三HDFS 架构简单清晰NameNode 和 DataNode 的角色一目了然排查问题路径短HDFS 有海量资料任何报错几乎都能搜到别人的处理记录HDFS 不需要像 Ceph 那样做复杂的 CRUSH map 规划三台机器就能跑起来。Ceph 适合在理解了分布式存储基本概念之后再去碰否则 RADOS、PG、Monitor 这些概念堆在一起很难定位是哪里出了问题。4.2 部署前版本选择与最小规划HDFS 的高可用依赖 JournalNodeQJM机制。三台机器是最小规模node1 和 node2 跑 NameNodeActive/Standbynode1、node2、node3 各跑一个 JournalNodeDataNode 在三个节点上都跑。我一般会选 Hadoop 3.x 的稳定版本它内置了基于 QJM 的高可用不用再额外配 Zookeeper Failover Controller 之外的第三方组件。在开始之前把三台机器的 /etc/hosts 配好确保主机名能互相解析。很多所谓的高可用切换失败最后排查下来都是主机名写错导致的。另外两个 NameNode 节点之间要配置 SSH 免密JournalNode 负责共享 edits 日志不需要免密但 NameNode 之间切换需要能互访。4.3 配置与启动core-site.xml 与 hdfs-site.xml 的关键参数在 Hadoop 安装目录的 etc/hadoop 下先配置 core-site.xml关键是把默认文件系统指向 nameserviceconfiguration property namefs.defaultFS/name valuehdfs://mycluster/value /property property namehadoop.tmp.dir/name value/data/hadoop/tmp/value /property /configurationfs.defaultFS 是整个客户端访问的入口mycluster 是逻辑名称需要在 hdfs-site.xml 里定义它由哪两个 NameNode 组成。hadoop.tmp.dir 如果默认放在 /tmp 下系统重启会被清空这是新手最容易忽略的点。再配置 hdfs-site.xml这是高可用最核心的文件configuration property namedfs.nameservices/name valuemycluster/value /property property namedfs.ha.namenodes.mycluster/name valuenn1,nn2/value /property property namedfs.namenode.rpc-address.mycluster.nn1/name valuenode1:8020/value /property property namedfs.namenode.rpc-address.mycluster.nn2/name valuenode2:8020/value /property property namedfs.namenode.http-address.mycluster.nn1/name valuenode1:9870/value /property property namedfs.namenode.http-address.mycluster.nn2/name valuenode2:9870/value /property property namedfs.namenode.shared.edits.dir/name valueqjournal://node1:8485;node2:8485;node3:8485/mycluster/value /property property namedfs.journalnode.edits.dir/name value/data/hadoop/journal/value /property property namedfs.ha.automatic-failover.enabled/name valuetrue/value /property property namedfs.client.failover.proxy.provider.mycluster/name valueorg.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider/value /property /configuration这里最关键的是 dfs.namenode.shared.edits.dir它指定了三个 JournalNode 的地址两个 NameNode 通过共享 edits 目录来同步元数据操作日志。automatic-failover.enabled 开启后配合 ZKFCHadoop 自带的 Zookeeper Failover Controller实现自动切换生产环境建议开启。如果不开启故障时只能手动执行hdfs haadmin -failover切换对于核心业务是不能接受的。配置完同步到所有节点后按顺序启动# 在三个节点上分别启动 journalnode hdfs --daemon start journalnode # 在 node1 上格式化 NameNode hdfs namenode -format # 在 node1 上启动 Active NameNode hdfs --daemon start namenode # 在 node2 上同步元数据并启动 Standby NameNode hdfs namenode -bootstrapStandby hdfs --daemon start namenode # 在 node1 上格式化 ZKFC如果开启自动故障转移 hdfs zkfc -formatZK # 所有节点启动 DataNode hdfs --daemon start datanode第一个命令启动 JournalNode三个节点都要执行。格式化 NameNode 只在首次部署时做格式化会清空原有元数据所以执行前务必确认这台机器上没有需要保留的 HDFS 数据。bootstrapStandby 的作用是把 Active NameNode 的元数据同步到 Standby保证两个节点的元数据状态一致。ZKFC 格式化是在 Zookeeper 里创建 HA 相关的节点只需要执行一次。4.4 高可用验证kill 掉 Active NameNode 看 Standby 是否接管部署完成后验证环节不要跳过。我先创建一个测试目录并写一个文件hdfs dfs -mkdir -p /tmp/ha-test echo ha test | hdfs dfs -put - /tmp/ha-test/test.txt hdfs dfs -cat /tmp/ha-test/test.txt写入成功后找到当前 Active NameNode 的进程号直接 kill 掉jps | grep NameNode kill -9 pid # 等待 30 秒左右再检查状态 hdfs haadmin -getAllServiceState正常情况下另一个 NameNode 会由 Standby 转变为 Active原来写进去的文件依然能正常读取。我遇到过的情况是 kill 之后 30 秒内切换完成但如果你没配 ZKFC 或者 Zookeeper 连接有问题会一直停留在 Standby 状态。这个步骤我强烈建议每次部署完都跑一遍因为它验证的是整个高可用链路是否真的通了而不是配置看起来对了。从那以后我每次搭建 HDFS 高可用集群验证步骤一律不跳过宁可在上线前多花十分钟也不在故障时赌运气。5. 对象存储实施避坑MinIO、Ceph 部署常见问题与排查5.1 MinIO纠删码模式下磁盘不足数据直接写不进去现象MinIO 集群跑得好好的突然上传文件报Insufficient storage space但磁盘明显还没满。原因MinIO 默认用纠删码保护数据N 块盘组成的集群写入一份数据实际上要占用至少 N/2 块盘的容量。如果你部署的时候用的是 4 块盘模式实际可用容量并不是 4 块盘的总和而是约等于 2 块盘的总和。很多刚接触 MinIO 的人把它当成普通硬盘池用等数据量接近逻辑上的「一半」时就发现写不进去了。解决部署前先明确纠删码的存储效率。MinIO 官方建议生产环境用纠删码模式但你要按实际可用容量去做监控告警不能只盯着物理磁盘使用率。同时要清楚 MinIO 不支持动态扩容扩容只能新增一套独立集群然后做数据迁移。在设计容量时我一般会预留 40% 以上的余量否则后续扩容非常被动。5.2 HDFS小文件把 NameNode 内存打爆现象集群文件数涨到几百万之后NameNode 频繁 Full GCActive NameNode 假死客户端超时元数据操作延迟从毫秒级涨到秒级。原因NameNode 把整个文件系统的目录树和文件块信息全部放在内存里。每个文件、每个目录、每个 block 都要占用内存小文件越多内存消耗越大。官方数据是每百万个文件块大约需要 1GB 左右内存但实际算上副本、权限等信息往往更高。HDFS 设计目标是「大文件顺序读写」小文件场景本来是它的盲区。解决两条路。一是从源头控制小文件数量用 Har、SequenceFile 把小文件合并成大文件再写入二是如果业务上必须存海量小文件改用 TFS 或者 Ozone 这类专门设计的系统。TFS 是淘宝针对小文件场景开发的把大量小文件合并成 Block用 Index 文件做映射Ozone 用 RocksDB 保存元数据占内存比 HDFS 小得多。选型之前先估算文件平均大小如果平均不到 1MBHDFS 大概率不是最优解。5.3 Ceph时钟漂移和没有预规划 pool 是两大坑现象Ceph 集群运行一段时间后OSD 报clock skew detected部分 PG 状态变成inactive或peered数据读写超时。原因Ceph 的 Monitor 依赖各节点时钟同步来确认 OSD 心跳。节点间时钟偏差超过 mon clock drift 阈值时Monitor 会认为该节点异常。另外很多新手上 Ceph 时没有提前规划 pool 的 PG 数量等数据写入了再调触发了大规模 PG 迁移风暴把集群 IO 全占了。解决所有存储节点统一配置 NTP 服务设好 cron 任务定期同步时间。规划 pool 时按「PG 数 (OSD 数 × 100) / 副本数」这个经验公式去估算并在创建 pool 时一次性定好不要事后修改。Ceph 的运维复杂度是这三类系统里最高的没有专职运维的情况下我建议优先考虑托管服务或者更轻量的方案。5.4 FastDFS不支持断点续传对大文件是噩梦现象用 FastDFS 上传 2GB 以上的大文件网络闪断后必须从头再传传到一半又断操作人员心态直接崩溃。原因FastDFS 是纯 C 实现的轻量级分布式文件系统设计目标是 4KB 到 500MB 的中小文件在线服务。它的上传接口没有实现断点续传机制也不支持文件正确性校验这是它的架构取舍不是 bug。文档里明确写了「建议范围 4KB file_size 500MB」但很多选型的人没注意到这条边界。解决如果业务里大文件比例高要么在上层应用自己做分片上传要么直接换对象存储。FastDFS 适合的是相册、视频网站这类海量中小文件的场景它在 UC 支撑过网盘类业务说明场景匹配时表现很好。但把它当通用文件系统用就会踩到接口不支持 POSIX、只能通过专有 API 访问这类硬边界。5.5 通用扩容与数据均衡的认知差现象给 Ceph 或 HDFS 集群加了新节点后老节点依然繁忙新节点一直空闲数据分布严重不均。原因这是对「平滑扩容」的误解。HDFS 的 rebalance 需要手动触发Ceph 的 rebalance 依赖 CRUSH 算法自动重算但也需要时间。MinIO 分发模式下新节点不会自动分担老数据。很多分布式系统的扩容只是「后续新数据能写到新节点」存量数据的迁移是另一回事。解决扩容前先确认目标系统是否支持自动数据均衡。HDFS 需要执行hdfs balancerCeph 要配置好 rebalance 带宽限制MinIO 干脆不支持动态扩容只能新建集群迁移。扩容操作放在业务低峰期做并且先跑小规模验证不要一次性把几十个节点加进去。我见过一个团队在白天高峰期给 Ceph 加 20 个 OSD结果整晚集群都在做数据重平衡业务 IO 被拖到不可用。6. 用基准测试验证选型fio、mdtest 与 warp 的具体用法6.1 块存储先测延迟fio 是最低成本的体检工具块存储选型时我先用 fio 做一轮基础测试分别覆盖随机读写和顺序读写fio --namerandwrite \ --rwrandwrite \ --bs4k \ --size1G \ --numjobs8 \ --runtime60 \ --iodepth32 \ --direct1 \ --group_reporting \ --ioenginelibaioioenginelibaio 表示用 Linux 原生异步 IOdirect1 绕过 page cache这两个参数是测真实盘性能的前提。测出来的 IOPS 和延迟数据要跟业务要求对比数据库类业务通常要求 4k 随机写延迟在毫秒级如果 fio 测出来的 p99 延迟超过 10ms这个存储方案在数据库场景里基本不可接受。如果只是做备份存储顺序读写吞吐bs1M 的测法比随机 IOPS 更重要不要拿一套参数测所有场景。6.2 文件系统测元数据mdtest 比 dd 可靠得多文件存储的瓶颈往往不在带宽而在元数据操作。dd 只能测顺序读写测不出百万小文件的建目录和删除能力。我一般用 mdtest 测元数据压力mpirun --hostfile hosts -np 16 \ mdtest -d /mnt/hdfs-mdtest \ -n 10000 \ -F \ -C \ -R \ -D \ -v-n 10000 表示每个进程创建 10000 个文件-F 表示创建独立文件-C、-R、-D 分别代表 create、read、delete 三个阶段。这个测试跑完你能很清楚看到文件系统在小文件场景下的每秒操作数。HDFS 在这种测试里表现通常不如本地文件系统因为每个文件创建都要经过 NameNode 元数据写入和 DataNode 数据写入两步。如果 mdtest 的 create 速率远低于业务预期就要考虑 TFS、Ozone 这类优化过小文件场景的系统。6.3 对象存储验证MinIO 用 warpS3 兼容用 s3cmdMinIO 官方提供了 warp 工具专门做 S3 兼容对象存储的压力测试warp mixed --hosts3:9000 \ --access-keyminioadmin \ --secret-keyminioadmin \ --duration60s \ --obj-size16Mobj-size16M 是对象大小mixed 模式会同时混入 PUT、GET、DELETE 操作更贴近真实业务。测试结果里重点看 PUT 吞吐和 GET 吞吐对象存储在视频、图片场景下顺序大对象的读写吞吐基本决定了用户体验。还有一个细节warp 的 access-key 和 secret-key 是 MinIO 默认的初始凭据生产环境一定要改掉否则等于把存储裸奔在公网。选型这件事说到底没有银弹。曾经有一段时间我以为只要把系统性能和稳定性做到极致就能覆盖所有场景后来发现每次「通用方案」在一类新场景面前都会出现明显的短板。从那以后我每次做存储选型都强制走一遍「确认存储形态 → 对比架构取舍 → 部署最小验证 → 基准测试收口」的流程不再凭感觉拍板。这套流程不一定最省时但能保证数据形态、系统架构和测试结论是对齐的希望帮到你。本文还有配套的精品资源点击获取