大数据系列(二) HDFS:把文件切碎了存到一堆机器上
HDFS把文件切碎了存到一堆机器上大数据系列第 2 篇海量数据存在哪来看看大数据世界的硬盘——HDFS。先问个问题100TB 的文件你怎么存假设你们公司有个需求把过去 10 年的用户行为日志都保存下来用于后续分析。这些日志加起来有100TB。你第一反应可能是买个大硬盘市面上最大的单块硬盘大概 20TB 左右100TB 需要 5 块。但这只是开始硬盘会坏坏了数据就没了得做备份吧5 块硬盘挂在一台机器上这台机器挂了怎么办100TB 只是现在的量明年可能 200TB 了后年 500TB你准备一直买硬盘加机器这么多数据分析的时候怎么读一台机器的网卡带宽撑得住吗说白了单机存储方案在 PB 级数据面前就是个弟弟。这时候HDFS 登场了。HDFS 是什么HDFSHadoop Distributed File SystemHadoop 分布式文件系统说白了就是把一个大文件切成很多小块分散存到多台机器上同时做好备份坏了几台机器数据也不会丢。它的设计哲学来自 Google 2003 年发表的一篇论文GFSGoogle File System。Google 那帮人说咱们别买昂贵的专用存储设备了就用一堆便宜的普通机器几百美元一台的那种通过软件层面的设计来保证可靠性。量大管饱便宜好用。HDFS 有几个核心假设理解这些假设很重要硬件故障是常态机器随时可能挂硬盘随时可能坏设计时就要把容错放在第一位文件很大适合存 GB 甚至 TB 级别的大文件不适合存大量小文件一次写入多次读取文件写入后基本不修改主要用来读和分析高吞吐量优先追求单位时间内读写的数据总量不追求单次读写的延迟HDFS 的架构一个老板带一群工人HDFS 的架构很简单就三种角色┌─────────────────────────────────────────────────────────────────┐ │ HDFS 架构人话版 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ │ │ │ NameNode │ ← 大管家 │ │ │ 名称节点 │ │ │ │ │ • 记住所有文件存在哪 │ │ │ │ • 记住每个文件被切成了几块 │ │ │ │ • 记住每块数据在哪些机器上 │ │ │ │ • 不存实际数据只存目录信息 │ │ └────────┬────────┘ │ │ │ │ │ │ dn1你身上的 block_001 还在吗 │ │ │ 在的老大 │ │ │ │ │ ┌────────┴───────────────────────────────────────────────┐ │ │ │ DataNode 集群 │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ │ │DataNode │ │DataNode │ │DataNode │ │DataNode │ │ │ │ │ │ dn1 │ │ dn2 │ │ dn3 │ │ dn4 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │block_001│ │block_001│ │block_001│ │block_002│ │ │ │ │ │block_002│ │block_003│ │block_002│ │block_003│ │ │ │ │ │block_003│ │ │ │block_004│ │block_004│ │ │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ │ │ │ │ 每个 DataNode 就是一台普通机器存着实际的数据块 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ 还有个 Secondary NameNode辅助管家后面再说它是干嘛的 │ │ │ └─────────────────────────────────────────────────────────────────┘NameNode大管家NameNode 是 HDFS 的老大所有元数据Metadata都存在它脑子里文件系统目录树有哪些文件夹、哪些文件文件到数据块的映射每个文件被切成了哪几块数据块的位置信息每块数据存在哪些 DataNode 上注意NameNode 不存实际数据只存地图。实际数据都在 DataNode 上。NameNode 把所有元数据放在内存里所以访问速度飞快。但这也意味着——NameNode 的内存就是整个集群的瓶颈。据说每个文件或目录大概占用 150 字节内存如果你存 10 亿个文件NameNode 需要 150GB 内存。这数字听着就刺激。DataNode干活的工人DataNode 就是集群里的一台台普通机器负责存实际的数据块定期向 NameNode 汇报“我还活着我身上有这些块”按照 NameNode 的指令复制数据块到其他机器比如某台机器挂了需要补副本Secondary NameNode辅助管家不是备胎很多人一听Secondary就以为它是 NameNode 的备份故障时能顶上去。其实不是。Secondary NameNode 的工作是定期帮 NameNode “整理账本”。NameNode 的元数据有两种存储形式FsImage文件系统的完整快照就像一本完整的账EditLog最近的操作记录就像每天的流水账Secondary NameNode 定期把 FsImage 和 EditLog 合并生成新的 FsImage减轻 NameNode 启动时的负担。它不能替代 NameNodeNameNode 挂了它顶不上。那 NameNode 挂了怎么办后面聊高可用的时候再说。数据块与副本怎么保证不丢数据文件怎么切HDFS 把大文件切成固定大小的数据块Block默认 128MBHadoop 1.x 是 64MB。┌─────────────────────────────────────────────────────────────────┐ │ HDFS 文件切分示意 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 你的文件mydata.log500MB │ │ │ │ HDFS 把它切成 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Block 1 │ │ Block 2 │ │ Block 3 │ │ Block 4 │ │ │ │ 128MB │ │ 128MB │ │ 128MB │ │ 116MB │ │ │ │ │ │ │ │ │ │最后一块 │ │ │ │ │ │ │ │ │ │ 可能不满│ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ 切分的好处 │ │ • 大文件能分散存到多台机器 │ │ • 读取时可以并行从多台机器读速度快 │ │ • 方便做负载均衡哪台机器空闲就往哪存 │ │ │ └─────────────────────────────────────────────────────────────────┘副本机制默认存 3 份HDFS 默认给每个数据块存 3 个副本。这 3 个副本不是随便放的有个**机架感知Rack-Aware**策略┌─────────────────────────────────────────────────────────────────┐ │ HDFS 副本放置策略3 副本 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 机架 1Rack 1 机架 2Rack 2 │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ DataNode 1 │ │ DataNode 3 │ │ │ │ │ │ │ │ │ │ ★ Block 1 │◄────►│ ★ Block 1 │ │ │ │ 第 1 个副本 │ │ 第 3 个副本 │ │ │ │ │ │ │ │ │ │ ★ Block 2 │ │ ★ Block 2 │ │ │ └─────────────────┘ └─────────────────┘ │ │ │ │ │ │ 同机架传输带宽高、速度快 │ │ ▼ │ │ ┌─────────────────┐ │ │ │ DataNode 2 │ │ │ │ │ │ │ │ ★ Block 1 │ │ │ │ 第 2 个副本 │ │ │ │ │ │ │ │ ★ Block 3 │ │ │ └─────────────────┘ │ │ │ │ 放置规则 │ │ 1. 第 1 个副本放在客户端所在的机器如果客户端在集群外 │ │ 就随机选一台 │ │ 2. 第 2 个副本放在另一个机架的机器上 │ │ 3. 第 3 个副本放在和第 1 个副本同一个机架、但不同机器上 │ │ │ │ 为什么这么放 │ │ • 同机架两台机器有副本读取时优先读近的速度快 │ │ • 跨机架也有副本万一整个机架断电/断网数据不会全丢 │ │ │ └─────────────────────────────────────────────────────────────────┘这个设计挺巧妙的两个副本在同机架读取快一个副本在异机架容错强。既考虑了性能又考虑了可靠性。数据完整性校验HDFS 还会给每个数据块算一个校验和Checksum。DataNode 定期扫描自己存的数据块重新算一遍校验和跟原来的比对。如果发现不一致说明数据损坏了就报告给 NameNodeNameNode 会从其他副本复制一份好的过来替换掉坏的。所以除非同一时刻 3 个副本所在的机器全挂了否则数据不会丢。这种概率比你中彩票还低。读写流程数据怎么存进去、怎么读出来写入流程┌─────────────────────────────────────────────────────────────────┐ │ HDFS 写入流程简化版 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 你客户端 NameNode DataNode 1 DataNode 2 │ │ │ │ │ │ │ │ │ 我要写文件 │ │ │ │ │ │─────────────►│ │ │ │ │ │ │ │ │ │ │ │◄─────────────│ 去 dn1、dn2、dn3 上写 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 建立流水线 ──►│───────────────►│─────────────►│ │ │ │ 你→dn1→dn2→dn3 │ │ │ │ │ │ │ │ │ 发数据包 ────►│───────────────►│─────────────►│ │ │ │ 64KB/包 │ │ │ │ │ │ │ │ │ │ │ │◄─────────────│◄───────────────│◄─────────────│ 写好了 │ │ │ │ │ │ │ │ │ 关闭文件 │ │ │ │ │ │─────────────►│ │ │ │ │ │◄─────────────│ OK元数据已更新 │ │ │ │ 关键点 │ │ • 数据通过流水线传输你发给 dn1dn1 转发给 dn2dn2 转发给 dn3 │ │ • 每个数据包默认 64KB都要等最后一个节点确认后才发下一个 │ │ • 这样设计是为了保证所有副本都写成功后才算完成 │ │ │ └─────────────────────────────────────────────────────────────────┘写入过程有几个值得注意的点流水线传输客户端不是分别给 3 个 DataNode 各发一份数据而是发给第一个第一个转发给第二个第二个转发给第三个。这样客户端只需要维护一个网络连接。确认机制每个数据包要逐级返回确认客户端收到确认后才发下一个包。这保证了数据确实写到了所有副本上。内存缓冲数据先写入 DataNode 的内存缓冲区缓冲区满了或者文件关闭时才刷到磁盘。所以 HDFS 写入性能还不错但断电时可能丢一点缓冲区的数据。读取流程读取就简单多了你问 NameNode“我要读这个文件数据在哪些机器上”NameNode 告诉你“block_001 在 dn1、dn2、dn3 上block_002 在 dn2、dn3、dn4 上……”你挑最近的 DataNode通常是同机架的去读数据读完一个 block自动去读下一个 block直到文件读完NameNode 会按网络距离排序返回副本位置优先让你读最近的。比如你跟 dn1 在同一个机架那就先读 dn1dn1 挂了再读 dn2以此类推。NameNode 高可用大管家不能挂前面说了Secondary NameNode 不是 NameNode 的备份。那 NameNode 挂了怎么办在 Hadoop 2.x 之前这确实是个大问题——NameNode 一挂整个 HDFS 集群就停摆了恢复可能要几十分钟。Hadoop 2.x 引入了**高可用HA**方案┌─────────────────────────────────────────────────────────────────┐ │ HDFS NameNode 高可用HA │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ Active │◄─────────►│ Standby │ │ │ │ NameNode │ 状态同步 │ NameNode │ │ │ │ │ │ │ │ │ │ • 处理所有 │ │ • 实时同步 │ │ │ │ 客户端请求 │ │ 元数据 │ │ │ │ • 写 EditLog │ │ • 热备待命 │ │ │ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ 写 EditLog │ 读 EditLog │ │ │ │ │ │ └──────────┬──────────────┘ │ │ │ │ │ ┌─────────┴─────────┐ │ │ │ JournalNode 集群 │ │ │ │ ┌─────┐┌─────┐┌─────┐ │ │ │ │ JN1 ││ JN2 ││ JN3 │ 通常 3 个或 5 个 │ │ │ └─────┘└─────┘└─────┘ │ │ │ │ │ │ • Active NN 把操作日志写到 JournalNode │ │ │ • Standby NN 从 JournalNode 读日志保持同步 │ │ │ • 多数派写入成功才算成功类似 Paxos │ │ └───────────────────────┘ │ │ │ │ ZooKeeper负责监控 NameNode 健康状态故障时自动切换 │ │ │ │ 故障切换流程 │ │ 1. ZooKeeper 发现 Active NameNode 挂了 │ │ 2. Standby NameNode 自动升级为 Active │ │ 3. 整个过程秒级完成客户端几乎无感知 │ │ │ └─────────────────────────────────────────────────────────────────┘核心思路是搞两个 NameNode一个 Active干活一个 Standby待命。Active 的所有操作都写到 JournalNode 集群里Standby 实时从 JournalNode 读日志保持元数据同步。Active 挂了Standby 秒级切换上位。JournalNode 通常部署奇数个3 或 5采用多数派写入机制——Active 把日志写到超过一半的 JournalNode 上才算成功。这样即使个别 JournalNode 挂了系统还能正常工作。HDFS 的坑小文件问题聊到这里不得不提 HDFS 的一个大坑——小文件问题。HDFS 的设计假设是文件很大每个文件无论多小NameNode 都要在内存里记录它的元数据文件名、权限、块列表等。如果你存了几千万个几 KB 的小文件NameNode 内存会被撑爆读取时要跟 NameNode 频繁交互效率极低MapReduce/Spark 处理时每个小文件对应一个 TaskTask 启动开销比处理数据还大所以HDFS 不适合存大量小文件。如果你的场景确实有很多小文件常见的解决方案有合并小文件写数据时定期把小文件合并成大文件比如用 SequenceFile、Avro、Parquet 格式HAR 文件Hadoop Archive把多个小文件打包成一个 HAR 文件对象存储小文件存到 HBase 或 S3 等对象存储里HDFS 适合什么、不适合什么适合的场景不适合的场景存大文件100MB 以上存大量小文件几 KB 几 MB批量数据读取MapReduce/Spark 输入低延迟随机读写像数据库那样数据归档和备份频繁修改文件内容日志存储和分析事务型 OLTP 应用一次写入多次读取需要 POSIX 文件系统语义小结今天咱们聊了 HDFS 的核心机制架构NameNode 管元数据DataNode 存实际数据Secondary NameNode 帮忙整理账本数据块大文件切成 128MB 的块分散存储副本机制默认 3 副本机架感知放置兼顾性能和容错读写流程流水线传输、逐级确认、就近读取高可用Active Standby NameNode JournalNode秒级故障切换小文件问题HDFS 的软肋大量小文件会撑爆 NameNode 内存HDFS 的定位很清晰它是一个高容错、高吞吐量的分布式文件系统专门为批量数据处理场景设计。它不是数据库不能随机读写它不是低延迟存储不适合交互式查询。但在海量数据、批量读取、一次写入多次读取的场景下HDFS 是当之无愧的基石。你用过 HDFS 吗有没有遇到过小文件问题或者 NameNode 内存不够的情况欢迎聊聊