1. 从一个看似简单的问题说起
“HDFS 的 block 默认存储大小是多少?”——这大概是每个刚接触大数据存储的人都会被问到、也都会去问别人的一个问题。标准答案张口就来:Hadoop 2.x 及以后是 128MB,Hadoop 1.x 是 64MB。但如果你只记住这个数字,面试官稍微拐个弯问一句“为什么是 128MB 而不是 256MB”,或者“这个值能不能随便改”,很多人就卡壳了。
我自己在这个参数上踩过的坑不算少:早期做日志采集的时候,天真地把 block size 调到 256MB 想“减少 NameNode 压力”,结果小文件场景下 MapReduce 任务数骤减、单任务跑得又慢又容易 OOM;后来做冷数据归档,又反过来把 block 调小,结果 NameNode 元数据暴涨,内存直接告急。这些经历让我意识到,block size 这个参数绝不是“背个数字”那么简单,它背后牵扯的是 NameNode 内存模型、寻址开销、MapReduce 切片逻辑、网络传输效率这一整套东西。
这篇内容我打算把 HDFS block 默认存储大小这件事彻底讲透:它是什么、默认值从哪来、为什么是这个数、改了会连锁影响什么、不同场景下该怎么选、以及实操中怎么查看和修改。适合正在搭 Hadoop 集群的运维、写 MapReduce/Spark 作业的开发者,也适合准备大数据相关面试、想把底层原理理顺的朋友。哪怕你只是好奇“128MB 这个数字是怎么拍出来的”,看完也能有个清晰的答案。
2. HDFS block 到底是什么,为什么需要它
2.1 从“文件”到“块”的抽象
在单机文件系统里,你存一个 1GB 的文件,操作系统会把它切成若干个小扇区、簇来管理,但对用户来说它就是一个完整的文件。HDFS 借鉴了这个思路,但做得更彻底:它把一个文件物理上切成固定大小的“块”(block),每个块独立存储、独立复制、独立寻址。
这里有个关键点容易被忽略:HDFS 的 block 是逻辑概念,不是物理磁盘上的连续扇区。一个 block 在底层可能分散在不同机器的不同磁盘上,NameNode 只记录“这个 block 属于哪个文件、存在哪些 DataNode 上”,DataNode 则负责实际的数据块文件。这种“元数据与数据分离”的设计,是 HDFS 能横向扩展到上千节点的根基。
为什么非要切块?三个理由,缺一不可:
- 突破单机容量限制:一个文件可以远大于任何单块磁盘,因为它是被拆开存的。
- 简化存储管理:块大小固定,NameNode 只需要管理“块到节点的映射”,不用关心文件内部结构,元数据量大幅下降。
- 便于并行与容错:每个块可以独立复制(默认 3 副本)、独立校验、独立迁移,某个块损坏不影响整个文件。
2.2 block size 与 NameNode 内存的隐秘关系
很多人调 block size 时只盯着“读写效率”,却忘了它和 NameNode 内存是强绑定的。NameNode 在内存里为每个 block 维护一条元数据记录,业内经验值大约是150 字节/block(不同版本略有差异,包含 block ID、副本位置、校验信息等)。
这意味着什么?假设你有 1PB 数据:
- block size = 128MB,block 数量 ≈ 1PB / 128MB ≈ 838万个,NameNode 内存需求 ≈ 838万 × 150B ≈ 1.2GB。
- block size = 64MB,block 数量翻倍到 1677万个,内存需求 ≈ 2.4GB。
- block size = 256MB,block 数量减半到 419万个,内存需求 ≈ 600MB。
看到没?block size 每翻一倍,NameNode 的元数据压力就减半。这就是为什么大集群倾向于用更大的 block——不是为了读写快,而是为了省 NameNode 内存。但反过来,block 太大又会导致小文件浪费、任务并行度下降,这就是后面要讲的权衡。
2.3 默认值的历史演变
| Hadoop 版本 | 默认 block size | 背景 |
|---|---|---|
| 0.x / 1.x | 64MB | 受限于当时磁盘带宽与内存成本 |
| 2.x 及以后 | 128MB | 磁盘顺序读写能力提升,元数据压力需要缓解 |
| 部分发行版(如某些云厂商) | 256MB | 面向超大集群与冷数据场景调优 |
这个演变逻辑很清晰:磁盘越来越快、单盘容量越来越大,而 NameNode 内存相对昂贵,所以默认值往上走是必然。128MB 是 2010 年前后硬件条件下的一个平衡点,放到今天其实已经偏保守了,很多生产集群会主动调到 256MB。
3. 为什么默认是 128MB,而不是 64MB 或 256MB
3.1 寻址开销与传输时间的平衡
HDFS 读一个 block 的过程,不是“打开文件就读”,而是要先向 NameNode 请求 block 位置,再和 DataNode 建立连接、传输数据。这个“握手 + 寻址”的固定开销,业内估算在几十毫秒量级。
如果 block 太小,比如 1MB,那么读 1GB 文件要经历 1024 次寻址,光握手就耗掉几十秒,磁盘带宽根本跑不满。反过来,block 太大,寻址次数少了,但单个 block 传输时间变长,任务粒度变粗,并行度下降,而且一旦某个 block 传输失败,重传代价也大。
128MB 的妙处在于:在当时的千兆网络和机械盘条件下,传输一个 128MB block 的时间(约 1 秒级)远大于寻址开销(几十毫秒),寻址开销被摊薄到可以忽略。这就是经典的“让传输时间主导、让寻址开销边缘化”的思路。
3.2 与 MapReduce 切片的联动
这是最容易被忽视的一环。MapReduce 的 InputFormat 在切分任务时,默认是按 block 边界切的(准确说是按 block 所在节点做数据本地化)。一个 block 通常对应一个 map 任务。
- block = 64MB,1GB 文件 → 16 个 map 任务。
- block = 128MB,1GB 文件 → 8 个 map 任务。
- block = 256MB,1GB 文件 → 4 个 map 任务。
map 任务数不是越多越好,也不是越少越好。任务太多,调度开销、JVM 启动开销累积起来很可观;任务太少,并行度不够,集群资源闲置。128MB 在“任务粒度”和“并行度”之间给了一个相对舒服的默认值。当然,Spark 时代这个逻辑有所变化,因为 Spark 的 partition 切分更灵活,但底层读 HDFS 时依然受 block 影响。
3.3 一个具体的计算示例
假设集群有 10 个 DataNode,每个节点 4 块 2TB 机械盘,千兆网络(理论 125MB/s,实际约 100MB/s)。
- 读一个 128MB block,理想传输时间 ≈ 128 / 100 ≈ 1.28 秒。
- 寻址 + 握手开销假设 50ms,占比 ≈ 50 / 1280 ≈ 3.9%,可以接受。
- 如果 block = 32MB,传输时间 ≈ 0.32 秒,寻址占比升到 ≈ 13.5%,明显偏高。
- 如果 block = 512MB,传输时间 ≈ 5.12 秒,寻址占比降到 1% 以下,但单任务变重,失败重传成本高,且小文件场景浪费严重。
所以 128MB 不是数学上的最优解,而是工程上的舒适区:寻址开销占比低、任务粒度适中、元数据压力可控。
4. 修改 block size 会连锁影响什么
4.1 对 NameNode 的影响
前面算过,block size 翻倍,block 数量减半,NameNode 内存压力减半。这是调大 block 最直接的收益。但要注意:已经写入的数据不会因为改了配置就重新切块。block size 是“写入时生效”的参数,改配置只影响新写入的文件,存量文件的 block 还是老大小。所以指望改个参数就立刻缓解 NameNode 内存,是不现实的,得配合数据重写或迁移。
4.2 对小文件问题的影响
HDFS 最怕小文件。一个 1KB 的文件,如果 block size 是 128MB,它依然占用一个 block 的元数据(约 150 字节),但实际数据只占 1KB。1000 万个 1KB 文件,NameNode 要维护 1000 万条元数据,内存直接爆炸,而实际数据量才 10GB。
调大 block size 对小文件问题没有任何帮助,反而可能让“一个 block 装很多小文件”的错觉更严重(实际上 HDFS 不会把多个小文件塞进一个 block,每个文件至少占一个 block)。解决小文件要靠归档(HAR)、合并(CombineFileInputFormat)或上层治理,不是调 block size。
4.3 对任务并行度的影响
block 调大,map 任务数减少,单任务处理数据量增加。这在数据量大、单任务逻辑轻的场景下是好事(减少调度开销);但在单任务逻辑重、容易 OOM 的场景下就是灾难。我见过一个案例:某作业把 block 从 128MB 调到 256MB 后,单个 map 处理的数据翻倍,结果 executor 内存不够频繁 GC,整体耗时反而上升了 30%。所以调 block size 一定要结合任务的实际内存模型来评估。
4.4 对网络与磁盘 IO 的影响
大 block 意味着单次顺序读写的数据量更大,对磁盘顺序吞吐更友好,对网络带宽的利用也更充分(减少了连接建立次数)。但代价是故障恢复粒度变粗:一个 256MB 的 block 传输失败,要重传 256MB;而 128MB 只需重传 128MB。在稳定性一般的集群里,大 block 的重传成本不容忽视。
5. 实操:查看与修改 block size 的完整流程
5.1 查看当前默认值
最直接的方式是看配置文件hdfs-site.xml:
<property> <name>dfs.blocksize</name> <value>134217728</value> </property>注意单位是字节,134217728 = 128 × 1024 × 1024。如果你看到的是 67108864,那就是 64MB。
也可以用命令行查看某个已存在文件的 block 情况:
hdfs fsck /path/to/file -files -blocks输出里会列出每个 block 的 ID、大小和所在 DataNode。如果文件是 128MB 整,你会看到一个满 block;如果文件是 200MB,你会看到 128MB + 72MB 两个 block。
5.2 修改默认值
修改hdfs-site.xml中的dfs.blocksize,然后重启 NameNode 和 DataNode(部分版本支持滚动重启)。注意这是集群级默认值,改完只对新写入文件生效。
<property> <name>dfs.blocksize</name> <value>268435456</value> <!-- 256MB --> </property>5.3 为单个文件指定 block size
不想改全局配置?可以在写入时按文件指定。命令行方式:
hdfs dfs -Ddfs.blocksize=268435456 -put localfile /hdfs/pathJava API 方式:
Configuration conf = new Configuration(); conf.setLong("dfs.blocksize", 256 * 1024 * 1024L); FileSystem fs = FileSystem.get(conf); FSDataOutputStream out = fs.create(new Path("/hdfs/path/file"));这种方式适合“大部分文件用默认值,个别大文件单独调大”的场景,灵活性最高。
5.4 修改后的验证
写完文件后,用hdfs fsck确认 block 大小是否符合预期:
hdfs fsck /hdfs/path/file -files -blocks如果看到 block 大小是 268435456,说明生效了。如果还是 134217728,检查是不是配置没重启、或者写入时被其他配置覆盖了。
提示:
dfs.blocksize是服务端配置,客户端提交作业时如果没显式指定,会以服务端为准。但某些计算框架(如 Spark)有自己的切分逻辑,实际读取时未必严格按 block 对齐,这点后面会讲。
6. 不同场景下 block size 怎么选
6.1 场景对照表
| 场景 | 推荐 block size | 理由 |
|---|---|---|
| 通用批处理 | 128MB ~ 256MB | 平衡元数据与并行度 |
| 海量小文件 | 不调 block,先治理小文件 | 调 block 无效 |
| 超大文件顺序读 | 256MB ~ 512MB | 减少寻址,提升吞吐 |
| 冷数据归档 | 256MB 或更大 | 降低 NameNode 内存压力 |
| 频繁随机读 | 128MB 或更小 | 减少单次读取浪费 |
| 内存敏感型任务 | 128MB 或更小 | 降低单任务内存压力 |
6.2 一个真实的调优案例
某日志分析集群,日均新增 2TB 日志,文件普遍在 1GB~5GB。最初用默认 128MB,NameNode 内存吃紧(约 8000 万 block)。后来把 block 调到 256MB,新数据 block 数减半,NameNode 内存增长明显放缓,同时因为文件本身较大,map 任务数减少带来的调度收益也很明显,整体作业耗时下降了约 15%。但同一集群里有个做实时查询的组件,读的是随机偏移,block 调大后单次读取要拉更多数据,延迟反而上升,最后给这个组件单独指定了 128MB。
这个案例说明:block size 没有全局最优,只有场景最优。同一个集群里不同业务用不同 block size 是完全合理的。
7. 常见问题与排查技巧
7.1 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 |
|---|---|---|
| 改了配置不生效 | 未重启 / 只对新文件生效 | 检查服务状态,确认写入时间 |
| 文件 block 数异常多 | 小文件过多 | 用 fsck 统计,考虑归档 |
| NameNode 内存暴涨 | block 数过多 | 统计 block 总数,评估调大 block |
| 任务 OOM | block 调大后单任务数据量增加 | 评估 executor 内存或调回 block |
| 读取延迟高 | block 过大导致单次读取浪费 | 检查访问模式,考虑调小 |
7.2 独家避坑经验
坑一:以为改了配置存量数据会重切。这是最常见的误解。block size 是写入时决定的,存量文件的 block 布局不会变。要改变存量数据,只能重写(比如用 DistCp 重新拷贝一遍)。
坑二:盲目调大 block 导致任务倾斜。如果数据本身分布不均,大 block 会让“大文件集中在一个节点”的问题更突出,某些 map 任务处理的数据量远超其他任务,拖慢整体。调 block 前先看数据分布。
坑三:忽略计算框架的切分逻辑。Spark 读 HDFS 时,partition 数不完全等于 block 数,它还会考虑spark.sql.files.maxPartitionBytes等参数。只调 HDFS block 不一定能达到预期的并行度,要两边一起看。
坑四:在 NameNode 内存已经告急时直接调大 block。调大 block 只对新数据生效,短期内 NameNode 内存不会下降,反而可能因为新旧 block 混杂增加管理复杂度。正确做法是先治理存量(删冷数据、归档小文件),再调新数据的 block。
7.3 一个实用的监控指标
想知道 block size 设置是否合理,盯住两个指标:
- 平均 block 大小:如果远小于配置值,说明小文件多,调 block 没用。
- 单 DataNode block 数:如果持续增长且 NameNode 内存吃紧,考虑调大 block。
这两个指标在 Hadoop 的 JMX 或 NameNode Web UI 里都能看到,养成定期看的习惯,比出事再救火强得多。
8. 关于 block size 的几个延伸思考
8.1 云存储时代的 block 概念变化
对象存储(如各类云对象存储)里没有“block”这个概念,取而代之的是“对象”和“分片上传”。但很多大数据组件在对接对象存储时,会模拟出类似 block 的切分逻辑(比如按 128MB 分片)。理解 HDFS block 的原理,对理解这些模拟机制依然有帮助——本质都是“大文件切分 + 元数据管理 + 并行读写”这套思路。
8.2 纠删码对 block size 的影响
HDFS 3.x 引入了纠删码(Erasure Coding),它把数据切成更小的单元(cell)再做编码。在纠删码模式下,block size 的含义和副本模式略有不同,通常建议配合更小的 cell size 使用。如果你的集群启用了纠删码,调 block size 前一定要先搞清楚 EC 的条带化逻辑,否则容易踩坑。
8.3 面试里怎么答这个问题
如果被问到“HDFS block 默认多大”,别只答数字。可以这样组织:默认 128MB(Hadoop 2.x+),这个值是在寻址开销、NameNode 内存、任务并行度之间权衡的结果;它只对新写入文件生效;调大能缓解 NameNode 内存压力但会降低并行度;小文件场景调它没用。这样答,基本能覆盖面试官想考察的所有点。
我个人在实际操作中的体会是,block size 这个参数最忌讳“拍脑袋调”。每次调整前,先问自己三个问题:当前 NameNode 内存压力大不大?任务并行度够不够?数据是小文件为主还是大文件为主?把这三个问题想清楚,答案基本就出来了。最后再分享一个小技巧:如果拿不准,就先用默认 128MB 跑一段时间,通过 NameNode Web UI 观察 block 总数和平均大小,用数据说话,比任何经验公式都靠谱。