💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快!
💝💝💝如有需要请大家订阅我的专栏【大数据系列】哟!我会定期更新相关系列的文章
💝💝💝关注!关注!!请关注!!!请大家关注下博主,您的支持是我不断创作的最大动力!!!
文章目录
-
- 引言
- 一、Block是什么?HDFS的核心存储单元
-
- [1.1 基本概念](#1.1 基本概念)
- [1.2 大块设计的三个核心优势](#1.2 大块设计的三个核心优势)
- 二、Block的物理存储:数据到底怎么落盘?
-
- [2.1 存储结构](#2.1 存储结构)
- [2.2 小于块大小的文件](#2.2 小于块大小的文件)
- 三、副本放置策略:块是如何分布的?
-
- [3.1 默认的3副本策略](#3.1 默认的3副本策略)
- [3.2 副本策略的源码实现](#3.2 副本策略的源码实现)
- [3.3 动态调整副本数](#3.3 动态调整副本数)
- [3.4 多副本的常见疑问](#3.4 多副本的常见疑问)
- 四、块写入流程:数据如何变成Block?
-
- [4.1 完整写入流程](#4.1 完整写入流程)
- [4.2 代码示例:通过Hadoop API上传文件](#4.2 代码示例:通过Hadoop API上传文件)
- 五、数据完整性:如何保证Block不出错?
-
- [5.1 校验和(Checksum)机制](#5.1 校验和(Checksum)机制)
- [5.2 DataBlockScanner:后台静默检测](#5.2 DataBlockScanner:后台静默检测)
- [5.3 自动修复机制](#5.3 自动修复机制)
- 六、小文件问题:HDFS的阿克琉斯之踵
-
- [6.1 问题根源](#6.1 问题根源)
- [6.2 解决方案对比](#6.2 解决方案对比)
- [6.3 最佳实践建议](#6.3 最佳实践建议)
- 七、Block大小如何选择?
-
- [7.1 默认值演进](#7.1 默认值演进)
- [7.2 块大小选择原则](#7.2 块大小选择原则)
- [7.3 块大小与Map任务的关系](#7.3 块大小与Map任务的关系)
- [八、Block机制的演进:HDFS 3.x的纠删码(EC)](#八、Block机制的演进:HDFS 3.x的纠删码(EC))
-
- [8.1 为什么需要纠删码?](#8.1 为什么需要纠删码?)
- [8.2 EC vs 3副本核心对比](#8.2 EC vs 3副本核心对比)
- [8.3 EC的适用场景与配置](#8.3 EC的适用场景与配置)
- 九、生产环境常见问题排查
-
- [9.1 查找丢失的Block](#9.1 查找丢失的Block)
- [9.2 手动触发Block复制](#9.2 手动触发Block复制)
- [9.3 常见错误及解决方案](#9.3 常见错误及解决方案)
- [9.4 监控关键指标](#9.4 监控关键指标)
- [9.5 关键配置参数参考](#9.5 关键配置参数参考)
- 十、总结:Block机制的核心要点
引言
如果说HDFS是一栋宏伟的大厦,那么Block就是构成这座大厦的每一块砖。理解Block机制,是真正掌握HDFS的第一步,也是构建高性能大数据应用的基础。
本文将带你从零开始,彻底搞懂HDFS的Block机制------它是什么、为什么这么设计、如何工作、以及在实际生产中有哪些坑和最佳实践。
一、Block是什么?HDFS的核心存储单元
1.1 基本概念
在传统文件系统中,文件是一个连续的字节序列。而在HDFS中,文件被划分为固定大小的块(Block),作为独立的存储单元分布在集群的不同节点上。
HDFS默认块大小为128MB(Hadoop 2.x及以后版本;Hadoop 1.x中为64MB),这个大小远大于普通文件系统的块大小(通常为4KB或8KB)。
为什么这么大?这是HDFS为了处理海量数据而做的专门设计。
1.2 大块设计的三个核心优势
| 优势 | 具体说明 |
|---|---|
| 减少元数据开销 | NameNode内存中存储的元数据与文件块数量成正比。存储1GB文件,若块大小为128MB,仅需8个块记录;若块大小为4KB,则需要26万个块记录 |
| 降低寻址成本 | 磁盘寻道时间约10ms。假设传输速率为100MB/s,块越大,寻址时间占总时间的比例越低 |
| 简化存储管理 | 块作为独立单元,便于复制、迁移和负载均衡,也便于容错------单个块损坏不影响其他块 |
📌 通俗理解:1000本书搬到新图书馆,一种方式是每本书单独搬运(小文件),另一种是把书捆成128MB一捆再搬运(大块)。捆数少了,登记找地方的工作量(元数据)就小了,搬运效率也高了。
二、Block的物理存储:数据到底怎么落盘?
2.1 存储结构
每个Block在DataNode上对应两个物理文件:
bash
# DataNode存储目录结构示例
/data/hadoop/data/
├── current/
│ ├── BP-123456789-192.168.1.10-1640000000000/ # Block Pool ID
│ │ ├── finalized/ # 已完成的数据块
│ │ │ ├── subdir0/
│ │ │ │ ├── blk_1073741825 # Block数据文件
│ │ │ │ └── blk_1073741825_1001.meta # 校验和元数据文件
│ │ │ └── subdir1/
│ │ └── rbw/ # 正在写入的块
- 数据文件(blk_XXXXX) :存储实际的Block数据
- 元数据文件(blk_XXXXX_YYYY.meta) :存储校验和等信息
2.2 小于块大小的文件
很多人误以为存储1MB的文件会占用整个128MB的块空间。这是一个常见的误解!
实际上,1MB的文件存储在HDFS中时,只占用1MB的磁盘空间,而不是128MB。它只是被当作一个不完整的块来处理。这个问题我将在"小文件问题"部分详细讨论。
三、副本放置策略:块是如何分布的?
3.1 默认的3副本策略
HDFS默认维护3个副本,存储策略的核心原则是:本地优先、跨机架容错、同机架均衡。
机架B
机架A
第一副本
第二副本
第三副本
DataNode A1
DataNode A2
DataNode B1
DataNode B2
客户端
具体放置规则:
| 副本序号 | 放置位置 | 目的 |
|---|---|---|
| 第一副本 | 客户端所在节点(客户端在集群外则随机选择) | 减少网络传输,实现数据本地性 |
| 第二副本 | 与第一副本不同机架的随机节点 | 机架级容错,防止单机架故障 |
| 第三副本 | 与第二副本同机架的另一节点 | 保证读取性能,降低跨机架流量 |
这种策略的优势在于:
- 可靠性:最多可容忍一个机架完全故障,数据仍可通过另一机架的副本恢复
- 读取性能:约2/3的读取可在本地机架完成,减少跨机架带宽消耗
- 写入效率:两个副本在同一机架,减少了写操作的数据传输距离
3.2 副本策略的源码实现
副本策略由 BlockPlacementPolicyDefault 类实现,其核心方法是 chooseTargetInOrder():
java
/**
* 判断一个DataNode是否适合作为副本目标
* @param node 目标DataNode
* @param maxTargetPerRack 每个机架允许的最大副本数
* @param considerLoad 是否考虑节点负载
* @param results 当前已选中的副本列表
* @param avoidStaleNodes 是否避免选择僵死节点
* @return true表示节点合格,false表示被拒绝
*/
protected boolean isGoodDatanode(DatanodeDescriptor node, int maxTargetPerRack,
boolean considerLoad, List<DatanodeDescriptor> results,
boolean avoidStaleNodes) {
// 检查节点是否存活、是否在排除列表中、负载是否过高、是否属于已选机架等
}
3.3 动态调整副本数
bash
# 查看文件副本数
hdfs dfs -ls /path/to/file
# 动态修改文件副本数(将副本数改为4)
hdfs dfs -setrep -w 4 /path/to/file
# 设置默认副本数(hdfs-site.xml)
<property>
<name>dfs.replication</name>
<value>3</value>
</property>
3.4 多副本的常见疑问
Q:1GB文件存储3副本,真的会占用3GB磁盘空间吗?
A:是的。HDFS会为这个文件生成3个完整的副本,分别存储在不同的DataNode上,每个副本都包含文件的完整数据,再加上少量块管理信息,总体略高于3GB。
四、块写入流程:数据如何变成Block?
当一个客户端向HDFS写入文件时,Block的创建和分布过程可以概括为下图:
DataNode C DataNode B DataNode A NameNode 客户端 DataNode C DataNode B DataNode A NameNode 客户端 loop [传输Packet (64KB)] 1. 创建文件请求 2. 返回允许写入 3. 请求第一个Block的存储节点 4. 返回节点列表 [A, B, C] 5. 建立Pipeline连接 建立连接 建立连接 ACK ACK 6. Pipeline就绪 7. 发送Packet 转发Packet 转发Packet ACK ACK ACK 8. 通知Block写入完成
4.1 完整写入流程
第一阶段:客户端初始化
客户端通过DistributedFileSystem对象向NameNode发起上传请求。NameNode检查目标文件是否存在、父目录合法性及用户权限。
第二阶段:数据分块与管道建立
客户端按默认128MB块大小划分文件。NameNode基于机架感知策略返回DataNode地址列表。客户端与第一个DataNode建立连接,各DataNode逐级调用形成Pipeline。
第三阶段:数据写入
数据以64KB的Packet为单位进行传输。DataStreamer线程异步从队列获取Packet并发送至Pipeline。每个Packet传输完成后,DataNode通过反向管道返回ACK确认。若传输失败,触发重传机制。
第四阶段:收尾
一个Block传输完成后,客户端通知NameNode记录元数据。重复上述步骤处理后续Block,最后关闭输出流。
4.2 代码示例:通过Hadoop API上传文件
java
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
public class HDFSBlockWriteDemo {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
// 可选:设置块大小(单位:字节)
conf.set("dfs.blocksize", "268435456"); // 256MB
FileSystem fs = FileSystem.get(conf);
FSDataOutputStream out = fs.create(new Path("/hdfs/testfile.dat"));
// 读取本地文件并写入HDFS
try (InputStream in = new BufferedInputStream(
new FileInputStream("/local/data/largefile.dat"))) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
// 数据自动按块大小切分,无需手动处理
}
}
out.close();
fs.close();
System.out.println("File uploaded successfully");
}
}
五、数据完整性:如何保证Block不出错?
5.1 校验和(Checksum)机制
HDFS在写入和读取数据时,都会进行校验和验证,确保数据没有被损坏。
写入阶段 :客户端将数据切分为数据包并生成校验和,沿写入管线发送到DataNode。默认每512字节数据计算一个CRC-32值(4字节),可通过io.bytes.per.checksum参数调整。管线中通常由最后一个DataNode对整块数据做校验,若不匹配抛出ChecksumException。
读取阶段:客户端读取每个数据块时,会重新计算校验和并与DataNode持久化的校验值比对;若不一致,客户端立即切换到其他副本读取,并向NameNode上报该块为"损坏"。
5.2 DataBlockScanner:后台静默检测
每个DataNode运行DataBlockScanner后台线程,定期扫描本地块以发现"静默损坏"(如介质位衰减)。默认扫描周期约为3周(21天),可通过参数dfs.datanode.scan.period.hours调整。发现异常后同样触发上报与修复流程。
xml
<!-- 调整块扫描周期为7天 -->
<property>
<name>dfs.datanode.scan.period.hours</name>
<value>168</value>
</property>
5.3 自动修复机制
当NameNode检测到某个Block的副本数量低于配置值(默认3),系统会自动启动修复流程:
- 状态监控:NameNode每隔3秒接收DataNode的心跳与块报告,识别欠副本Block
- 任务调度:ReplicationMonitor线程根据优先级生成修复任务,优先复制重要数据块,同时考虑网络拓扑和节点负载
- 数据复制:NameNode向源DataNode发送复制指令,目标DataNode主动拉取数据
- 结果反馈:目标节点完成写入后向NameNode确认,NameNode更新元数据
bash
# 手动触发块修复(查看欠副本块数量)
hdfs fsck / -files -blocks -locations | grep "Under replicated"
# 设置最小副本数
<property>
<name>dfs.replication.min</name>
<value>1</value> <!-- 至少保留1份副本,数据不会丢失 -->
</property>
六、小文件问题:HDFS的阿克琉斯之踵
6.1 问题根源
HDFS是为大文件设计的,小文件(通常指远小于默认块大小128MB的文件)会带来严重问题:
NameNode内存压力:每个文件、目录和块都会在NameNode内存中形成约150字节的对象。当存在1000万个平均大小为1MB的小文件时,仅元数据就会消耗约1.5GB内存,而实际存储的数据量仅为10TB。
RPC请求风暴:客户端需要频繁与NameNode交互获取文件位置信息,Cloudera实测显示小文件场景下NameNode的RPC吞吐量会下降40-60%。
DataNode存储效率低下:小文件虽然不占用完整的Block空间,但每个小文件仍是独立存储单元,导致磁盘寻道次数激增。处理包含百万小文件的MapReduce作业时,磁盘I/O耗时占比可达总运行时间的75%以上。
计算框架性能恶化:每个小文件通常对应独立的Map任务,启动10万个处理1MB文件的Map任务,其调度开销可能超过实际计算时间的300%。
6.2 解决方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| HAR归档 | NameNode元数据压力降低80% | 读取需两次IO,不支持追加写 | 历史数据归档,只读场景 |
| SequenceFile | 支持压缩,可追加 | 无内置目录结构 | MapReduce中间数据 |
| CombineFileInputFormat | 无需预处理,任务层面合并 | 仅对MapReduce有效 | 计算任务优化 |
| 应用层合并 | 灵活可控,性能最优 | 需修改代码 | 有代码控制权的场景 |
HAR归档示例:
bash
# 创建HAR归档文件
hadoop archive -archiveName logs.har -p /input/logs /output
# 查看HAR文件内容
hdfs dfs -ls har:///output/logs.har
# 提取HAR中的文件
hdfs dfs -cp har:///output/logs.har/* /extracted/
6.3 最佳实践建议
- 预处理 :在向HDFS写入数据前进行合并,如使用Flume的批次写入配置
batchSize - 调整作业参数:控制MapReduce的reducer数量,避免产生过多小文件
- 使用Hive的CombineHiveInputFormat:合并多个小文件为一个Map任务处理
七、Block大小如何选择?
7.1 默认值演进
| Hadoop版本 | 默认块大小 |
|---|---|
| Hadoop 1.x | 64 MB |
| Hadoop 2.x及以后 | 128 MB |
| 可配置范围 | 1 MB ~ 512 MB+ |
7.2 块大小选择原则
| 场景 | 推荐块大小 | 理由 |
|---|---|---|
| 普通大数据(GB~TB级) | 128 MB(默认) | 平衡元数据开销和Map任务并行度 |
| 超大文件(PB级) | 256 MB ~ 512 MB | 进一步降低NameNode元数据压力 |
| 大量中小文件 | 保持默认 | 小文件本身才是问题根源,而非块大小 |
| MapReduce作业 | 与输入分片大小匹配 | 理想情况下,一个Map处理一个Block |
xml
<!-- hdfs-site.xml:修改块大小 -->
<property>
<name>dfs.blocksize</name>
<value>268435456</value> <!-- 256MB -->
</property>
7.3 块大小与Map任务的关系
MapReduce中,输入分片(InputSplit)通常对应一个Block。块越小,Map任务数越多,任务调度开销越大;块越大,Map任务数越少,可能降低并行度。
最佳实践:块大小不应小于HDFS默认值,也不应超过MapReduce作业中单个Map任务处理能力的上限。
八、Block机制的演进:HDFS 3.x的纠删码(EC)
8.1 为什么需要纠删码?
传统3副本机制虽然简单可靠,但存储开销高达200%(1份数据存3份)。对于动辄PB级的冷数据存储,这个成本难以承受。
HDFS 3.0引入了纠删码(Erasure Coding, EC),通过编码方式实现冗余,存储开销降低至50%左右。
8.2 EC vs 3副本核心对比
以最常见的RS-6-3编码为例(6个数据块 + 3个校验块):
| 对比维度 | 3副本 | 纠删码(RS-6-3) |
|---|---|---|
| 存储开销 | 300%(3倍) | 150%(1.5倍) |
| 空间利用率 | 33.3% | 66.7% |
| 容错能力 | 最多容忍2个节点故障 | 最多容忍任意3个块损坏 |
| 读性能 | 优秀,就近读取 | 正常读取相当,损坏时需解码 |
| 写性能 | 简单高效 | 需额外编码计算 |
| 适用场景 | 热数据、频繁读写 | 冷数据、归档数据、日志 |
关键结论:同等数据量下,纠删码比3副本节省50%的存储空间。随着集群规模扩大,两者的成本差距会愈发明显。
8.3 EC的适用场景与配置
适用场景 :冷数据、日志归档、遥感数据、数字孪生仿真结果等访问频率低但体量庞大的数据。不适用于频繁写入的热数据目录。
bash
# 查看支持的EC策略
hdfs ec -listPolicies
# 对目录启用EC策略(例如RS-6-3-1024k)
hdfs ec -setPolicy -path /data/archive -policy RS-6-3-1024k
# 查看目录当前的EC策略
hdfs ec -getPolicy -path /data/archive
九、生产环境常见问题排查
9.1 查找丢失的Block
bash
# 检查整个文件系统的健康状态
hdfs fsck /
# 检查特定目录并输出详细块信息
hdfs fsck /user/data -files -blocks -locations
# 列出所有丢失的Block
hdfs fsck / -list-corruptfileblocks
9.2 手动触发Block复制
bash
# 设置文件副本数,触发块复制
hdfs dfs -setrep -w 3 /path/to/file
# 启动均衡器,重新分布数据块
hdfs balancer -threshold 10
9.3 常见错误及解决方案
| 错误信息 | 常见原因 | 解决方案 |
|---|---|---|
BlockMissingException |
DataNode故障或Block副本全部丢失 | 检查是否有其他副本,若无则数据无法恢复 |
ChecksumException |
数据损坏或磁盘介质问题 | 客户端自动从其他副本读取,需关注硬盘健康 |
Under replicated blocks |
副本数不足 | 系统会自动修复,或手动-setrep强制复制 |
NotReplicatedYetException |
写入过程中副本未完成 | 等待写入完成或检查DataNode是否正常 |
9.4 监控关键指标
bash
# 查看集群总体状态(包括块数量、缺失块数)
hdfs dfsadmin -report
# 查看NameNode Web UI(默认端口50070或9870)
# 重点关注"Under-replicated blocks"和"Corrupt blocks"
9.5 关键配置参数参考
| 参数 | 默认值 | 说明 |
|---|---|---|
dfs.blocksize |
134217728(128MB) | 块大小,影响元数据总量 |
dfs.replication |
3 | 默认副本数 |
dfs.replication.min |
1 | 写入成功所需的最小副本数 |
dfs.namenode.replication.interval |
3 | NameNode处理欠副本块的间隔(秒) |
dfs.datanode.scan.period.hours |
504(21天) | DataNode块扫描周期(小时) |
十、总结:Block机制的核心要点
Block机制是HDFS的基石,理解它就能理解整个HDFS的设计哲学。
Block机制的核心价值:
| 设计决策 | 解决的问题 |
|---|---|
| 大块(128MB) | 减少元数据开销、降低寻址成本 |
| 多副本(默认3) | 容错能力 + 读取性能 |
| 机架感知放置 | 机架级容错 + 带宽优化 |
| 校验和 + DataBlockScanner | 保证数据完整性 |
| 自动修复 | 维持副本因子,无需人工干预 |
Block机制的使用建议:
- 大文件场景是HDFS的优势,尽量避免大量小文件
- 默认128MB块大小和3副本配置适合大多数场景
- 定期监控欠副本和损坏块的数量
- 冷数据归档可考虑使用EC纠删码替代3副本
- 合理设置块扫描周期,在发现问题和系统开销之间取得平衡
Block机制的设计处处体现着HDFS的核心理念:通过牺牲一点空间,换取海量数据的高可靠、高性能和低成本。这正是Hadoop能够在云计算和大数据浪潮中屹立不倒的根本原因。
💬 互动话题
你在生产环境中遇到过Block丢失或小文件困扰吗?是如何解决的?欢迎在评论区分享你的经验~
❤️❤️❤️觉得有用的话点个赞 👍🏻 呗。
❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄
💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍
🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙