HDFS — 把 GFS 用 Java 重写一遍并撑到 25 PB
是什么
HDFS(Hadoop Distributed File System)是 GFS 的开源 Java 重写,2010 年这篇论文是 Yahoo 把它跑到 25 PB / 3500 节点 / 1 亿 block 之后写的工业说明书。日常类比:GFS 是 Google 内部菜谱(写在 SOSP 2003 那张餐巾纸上),HDFS 是按那张餐巾纸开起来的连锁店——同样的菜,但要给上千家加盟商写实操手册,所有 GFS 论文一笔带过的”火候自己掌握”在这里都得写清楚。
架构几乎照抄 GFS:一个 NameNode(对应 GFS master)把整个文件树和”哪个 block 在哪台机器”全放内存里;一堆 DataNode(对应 chunkserver)实际存 block;client 读写时先问 NameNode 拿位置,然后直连 DataNode 传数据。block 默认 64 MB、3 副本、机架感知放置(1 本机架 + 2 远机架)。
GFS 论文里没细说的几件事——NameNode 怎么 checkpoint、DataNode 怎么 block report、replica 失败怎么 re-replicate、cluster 怎么 rebalance——HDFS 论文每一项都写出了生产数字。
为什么重要
不理解 HDFS,下面这些事都没法解释:
- 为什么 2010 年代所有大数据栈(Hive / Spark / HBase / Presto)默认底层是 HDFS——它是第一个能开源、能 Java、能跑生产的 GFS
- 为什么 NameNode SPOF(单点故障)成了所有 HDFS 用户的噩梦——这篇论文公开承认是 known issue,催生了 Hadoop 2.x HA NameNode
- 为什么对象存储(S3 / OSS / Tectonic)要走 multi-master + 元数据外存——HDFS 撞到的内存上限是反面教材
- 为什么 HBase / Kafka / Spark 这些系统都”建议但不要求”跑在 HDFS 上——HDFS append-only + 大 block 的语义限制了上层
核心要点
HDFS 把 GFS 思想做成开源工业品,主要靠 三个工程权衡:
-
NameNode 内存装下整张元数据表:把命名空间 + block→DataNode 映射全塞内存,简单到极致——一次定位只要一次 RPC,不查磁盘。代价:内存就是集群上限。每文件 ~150 ByteDance、每 block ~150 ByteDance,60 GB 内存大约撑 1 亿 block。这是 GFS 没明说但 HDFS 把数字摆在桌面的地方。
-
机架感知 3 副本放置:第 1 副本写本机架,第 2、3 副本写另一个机架的两台机器。类比:一个文件抄三份,一份放办公桌、两份放另一栋楼——办公楼塌了文件还在,但写入只跨一次楼(省网络)。这套”1+2”是 HDFS 论文写出来给所有人抄的,后来几乎成了分布式存储默认。
-
EditLog + FsImage 双账本:NameNode 内存数据不能丢,所以每次改命名空间先写 EditLog(append-only 日志,类似数据库 WAL),定期把内存快照成 FsImage。重启时 FsImage + 重放 EditLog 就还原。这套和数据库 redo log 同源。
第 2、3 步留下了两个隐藏成本——副本越多写越贵,重启时间随 EditLog 长度线性涨。论文老老实实把这些数字摆在桌面,让用户能照着算自己集群的天花板。
合起来:简单的中心化 + 暴力的内存索引 + 朴素的日志持久化——能撑 25 PB,证明 GFS 思想在 Java + 普通 Linux 集群上是真能跑的。
实践案例
案例 1:HDFS 怎么读一个 1 GB 文件
block 默认 64 MB,1 GB 文件 = 16 个 block,每个 block 3 副本散在不同 DataNode:
# 对应 HDFS DFSClient.javadef read(path): # 第 1 步:问 NameNode 要全部 block 的位置(一次 RPC 拿全) blocks = namenode.getBlockLocations(path) # blocks = [(blk_id_1, [dn1, dn5, dn12]), (blk_id_2, [dn2, dn6, dn14]), ...] for blk_id, replicas in blocks: # 第 2 步:直连最近的 DataNode(机架感知排序) dn = pick_closest(replicas) yield dn.readBlock(blk_id) # NameNode 不参与关键点:16 个 block 只问 NameNode 一次,之后全是 DataNode 直连——这就是 single NameNode 能扛住几千 client 并发的原因。
案例 2:写入时的 pipeline 复制
写一个新文件,client 不会自己往 3 个 DataNode 各写一份,而是 DataNode 之间串成一条流水线:
client → DN1 → DN2 → DN3 ack ← ack ← ack(反向)DN1 收到一个 packet(默认 64 KB)就立刻转给 DN2,自己同时落盘。这样 client 上行带宽只用一次,3 副本的成本被分摊到 DataNode 之间。GFS 论文里有这套,但 HDFS 公开了 packet 大小、ack 队列、容错切换的实现细节,让所有抄作业的人能照着写。
案例 3:NameNode 重启的痛
生产事故现场:1 亿 block 的集群,NameNode 进程挂了:
启动流程:1. 加载 FsImage(10 GB 文件读盘) ~5 分钟2. 重放 EditLog(几百万条变更) ~10 分钟3. 等待所有 DataNode 上报 block report ~15 分钟4. 退出 safe mode,开始服务请求 合计 30 分钟这 30 分钟整个集群只能读、不能写、所有 MapReduce 卡住——这就是 SPOF 的真实代价,也是 Hadoop 2.x 必须做 HA NameNode(active + standby + ZK 选主)的根本原因。
更隐蔽的痛是 block report 风暴:3500 个 DataNode 同时上报,每个 DataNode 报几万个 block,网络瞬时打满。HDFS 后来加了 staggered report(错峰上报)才缓解。论文里这种”重启故事”是 GFS 论文完全没写的工业现实。
踩过的坑
- 小文件灾难:每个文件占 NameNode 内存 ~150 ByteDance,1 亿个 1 KB 小文件 = 15 GB 内存白吃 + 1 GB 真实数据。HDFS 不是通用 FS,对小文件密集场景必须用 HAR / SequenceFile 打包,或者干脆换成 Haystack / 对象存储。
- NameNode SPOF:2010 论文版本只有冷备 Secondary NameNode(其实是 checkpoint helper,不接管),主挂了集群停服。生产环境必须升 Hadoop 2.x HA NameNode + JournalNode + ZK 选主。
- default block size 64 MB:现代集群普遍调到 128 MB / 256 MB,因为磁盘 / 网络已经快了 10 倍,64 MB 反而 metadata 压力大。盲抄论文默认值会埋性能坑。
- 3 副本 vs erasure coding:3 副本占 200% 额外空间,在 PB 级集群是真金白银。Hadoop 3.x 后 RS-6-3 erasure coding 能压到 50% 开销,老集群升级时这个不切就一直在烧钱。
适用 vs 不适用场景
适用:
- 大文件批处理(GB 起步,MapReduce / Spark / Hive 离线数仓)
- append-mostly 工作负载(日志归档 / 索引中间结果)
- 单租户 + 内部集群,能容忍 NameNode 重启时的几十分钟停服(或上 HA)
- block 数量 < 几亿(受 NameNode 内存限制)
不适用:
- 小文件多(用 Haystack / HBase / 对象存储)
- 强一致 OLTP / random write(HDFS append-only,根本不支持)
- 多租户云存储(用 S3 / OSS / Tectonic 类)
- 需要 POSIX 严格语义的应用(HDFS 砍掉了 random write、强一致 fsync 语义)
历史小故事(可跳过)
- 2003:Google 发 GFS 论文,公开 single-master + chunkserver 架构,但代码闭源。
- 2004-2005:Doug Cutting 在 Nutch(开源搜索引擎)项目里手写了一个山寨 GFS,叫 NDFS。
- 2006:Yahoo 把 Doug Cutting 招过去,专门做 Hadoop(NDFS 改名 HDFS + MapReduce),目标是替代 Yahoo 自家闭源的 Dreadnaught 集群。
- 2010:Konstantin Shvachko(HDFS 架构师)在 MSST 公开 Yahoo 25 PB / 3500 节点的生产数据,论文承认 NameNode SPOF。
- 2012-2014:Hadoop 2.x 上 YARN(资源调度从 MR 解耦)+ HA NameNode(双 NameNode + ZK 选主),SPOF 终结。
- 至今:Cloudera CDP / 阿里云 EMR / ByteDance内部都还在跑 HDFS,是离线数仓事实标准底座。
学到什么
- 论文不是终点是起点——GFS 论文 7 年后,HDFS 用工业数字告诉你哪些细节论文没写但生死攸关(NameNode 启动时间 / block report 风暴 / 机架感知放置)。
- 简单 = 上限——NameNode 内存索引简单到极致,但内存就是集群天花板,超过就得改架构(Hadoop Federation / Tectonic)。
- SPOF 是必须公开承认的债——HDFS 论文坦白 NameNode SPOF 是 known issue,催生了后续 HA 方案。掩盖问题只会让用户在生产里踩。
- 开源 + 文档 = 生态——GFS 闭源 9 年没人模仿,HDFS 开源 + 论文 + Yahoo 生产数据 4 年内变成大数据事实标准。
延伸阅读
- 论文 PDF:HDFS MSST 2010(10 页,必读)
- 源码:apache/hadoop(HDFS 在
hadoop-hdfs-project/) - 视频:The Apache Hadoop Filesystem(Doug Cutting 早年讲)
- 工程对比:HDFS vs Tectonic vs Colossus 设计对比(Murat 教授系列博客)
- gfs —— HDFS 的设计来源,先读它再读这篇
- mapreduce —— HDFS 头号用户,论文里 90% benchmark 都在跑 MR 作业
- bigtable-2006 —— 同期 Google 在 GFS 上加结构化层,HBase 是它的 HDFS 版
关联
- gfs —— GFS 是 HDFS 的设计模板,NameNode = master,DataNode = chunkserver
- mapreduce —— HDFS 为 MR 而生,block size 和 split size 直接对应
- bigtable-2006 —— Bigtable 跑在 GFS 上,HBase 跑在 HDFS 上,复刻同一套分层
- chubby —— GFS 的锁服务,HDFS 后来选 ZooKeeper(ZAB)做等价物
- zab-2011 —— ZooKeeper 共识协议,Hadoop 2.x HA NameNode 选主依赖它
- paxos-1998 —— ZAB / Chubby 共同的理论基础
- haystack-2010 —— Facebook 同年的小文件存储,正面回答 HDFS 的小文件灾难
- azure-storage-2011 —— 微软的多租户云存储,反向证明 HDFS 单租户假设的边界
反向链接
- azure-storage-2011 —— Windows Azure Storage 2011 — 云对象存储第一次在工业界做到强一致
- bigtable-2006 —— Bigtable 2006 — Google 把行级随机读写做到 PB 级的存储系统
- chubby —— Chubby — 给凡人用的分布式锁服务
- f4-2014 —— f4 — Facebook 把 90 天前的旧图片搬到一个省 40% 存储的仓库
- gfs —— GFS — 编译器决定不做哪些事
- haystack-2010 —— Haystack — Facebook 十亿张照片怎么存
- mapreduce —— MapReduce — 用户只写两个函数,框架替你扛千节点
- paxos-1998 —— Paxos 1998 — 古希腊议会寓言里藏的共识协议
- soft-updates-1999 —— Soft Updates — 不写 journal 也能保证文件系统元数据一致
- zab-2011 —— Zab — ZooKeeper 怎么把客户端写入按顺序复制到所有副本
- zfs-2003 —— ZFS — 把磁盘当成水池,每滴水都贴标签