2.1 输入分片机制


文档摘要

2.1 输入分片机制 本节摘要:输入分片是 MapReduce 第一幕的核心概念——它是逻辑计算单位,与 HDFS 的物理存储单位"块"相互独立又彼此对齐。分片数决定 Map 任务数,分片大小由目标尺寸、块大小与剩余长度三者推导。本节给出完整推导、小文件问题的账本与治理手段。 先算一道题 一个 500MB 的文本文件存进 HDFS(块大小 128MB),会被切成 4 个块(128+128+128+116)。现在提交一个 MapReduce 作业处理它,Map 任务有几个? 直觉答案是 4,实际也是 4——但这只是巧合于"分片默认等于块大小"的配置。若把分片目标尺寸调成 200MB,答案变成 3;调成 50MB,答案变成 10。

2.1 输入分片机制

本节摘要:输入分片是 MapReduce 第一幕的核心概念——它是逻辑计算单位,与 HDFS 的物理存储单位"块"相互独立又彼此对齐。分片数决定 Map 任务数,分片大小由目标尺寸、块大小与剩余长度三者推导。本节给出完整推导、小文件问题的账本与治理手段。

先算一道题

一个 500MB 的文本文件存进 HDFS(块大小 128MB),会被切成 4 个块(128+128+128+116)。现在提交一个 MapReduce 作业处理它,Map 任务有几个?

直觉答案是 4,实际也是 4——但这只是巧合于"分片默认等于块大小"的配置。若把分片目标尺寸调成 200MB,答案变成 3;调成 50MB,答案变成 10。Map 任务数不是由文件或块直接决定的,而是由分片规划决定的,而分片规划是一段纯逻辑代码,允许你干预。

这道题算完,第一幕的机制就露出了骨架。

块与分片:一对容易混淆的概念

两个单位名字都带"切",但服务对象完全不同:

维度 块 Block 分片 Split
归属 HDFS 存储层 MapReduce 计算层
性质 物理切块,实际占有磁盘 逻辑区间,仅记录起始偏移与长度
大小 固定,由集群配置(常见 128MB) 可调,由作业配置决定
是否真的切文件 否,只是元数据
数量关系 与分片无强制绑定,默认对齐 决定 Map 任务数

分片在代码里的样子就是一个记录"文件名 + 起始字节 + 长度"的三元组。框架拿到分片列表后,为每个分片启动一个 Map 任务,任务的 RecordReader 从起始字节读到长度用尽为止。这就是"分片数等于 Map 任务数"的全部机制。

分片大小的推导公式

Hadoop 对每个文件按以下规则规划分片(以 FileInputFormat 为准):

goalSize = 文件总长 / 期望分片数(由 mapreduce.input.fileinputformat.split.minsize 等配置间接影响) minSize = 最小分片尺寸(默认 1,调大可强制分片更大) blockSize = 文件所在块大小(默认 128MB) splitSize = max minSize 与 min goalSize 与 blockSize

然后从文件头开始,每次切下 splitSize 字节,最后不足 splitSize 的 1.1 倍的剩余部分并入最后一个分片(这个"尾巴合并"规则避免了产生一个只处理几字节的迷你任务)。

拿开头的例子验算:文件 500MB,minSize=1,blockSize=128MB。

  • 默认情况 goalSize 未指定时以 blockSize 主导,splitSize=128MB:切 128、128、128,剩 116MB。116 小于 128 的 1.1 倍(140.8),并入最后一片?不——116 本身已接近 128,规则是依次切分,每片 splitSize,最后剩余 116 不足 splitSize 且小于 1.1 倍则不再单切,实际产生 4 片(第 4 片为 116MB)。Map 任务 4 个。
  • 设 splitSize=200MB:切 200、200,剩 100 小于 220,并入第二片,得 2 片?按精确实现是 200 与 300 两片,Map 任务 2 个(这里的简化叙述不影响结论:分片数约等于文件大小除以 splitSize,向上取整)。
  • 设 splitSize=50MB:500/50 整除,10 片,Map 任务 10 个。

图 2.1-1 500MB 文件在不同分片尺寸下的任务规划

图 2.1-1 500MB 文件在不同分片尺寸下的任务规划

分片大小的工程账本

每个 Map 任务有固定开销:调度、启动 JVM、初始化输入格式,加起来秒级。设固定开销 10 秒、每 MB 纯计算 0.1 秒,比较三种规划处理 500MB 的总时延(假设机器足够多,任务全部并行,取最慢任务近似):

splitSize 任务数 单任务计算 单任务开销 最慢任务耗时
200MB 2 30 秒 10 秒 40 秒
128MB 4 约 17.8 秒 10 秒 27.8 秒
50MB 10 5 秒 10 秒 15 秒
5MB 100 0.5 秒 10 秒 10.5 秒但调度排队时间飙升

下探到 5MB 时,账面最慢任务 10.5 秒,但 100 个任务的排队与调度本身会侵占集群,集群小的时候反而更慢;再小,任务开销占比超过一半,纯浪费。实践中把 Map 任务平均时长控制在分钟级、任务总数与可用槽位同量级,是常见经验。

什么时候调大:每个记录处理极轻(如纯过滤),任务数过多时合并分片减少固定开销。什么时候调小:单个记录处理很重(复杂解析、外部查询),需要更高并行度摊薄长尾。

分片的边界问题:记录跨块怎么办

块边界不会对齐行边界。一行日志很可能前半段在块 3、后半段在块 4。分片规划对此的答案很优雅:分片的结束边界允许越过。每个分片读到最后会多读下一个字节,若发现停在一行中间,就继续读完这一行;相应地,下一个分片会先跳过开头那段不完整的行,从完整行起点开始读。每个逻辑记录恰好被处理一次。

这层机制藏在 LineRecordReader 里,用户无感,但面试常考"行跨块会不会被处理两次或漏掉"——答案是不会,靠的正是读取端的越界与跳过配合。

小文件问题

分片机制的前提是"文件够大"。一堆小文件是它的天敌:

  • 每个小文件至少一个分片、一个 Map 任务。1 万个 100KB 文件 = 1 万个任务,绝大多数时间花在任务开销上;
  • HDFS 侧,每个文件、块都要 NameNode 里有元数据记录,小文件海量化会撑大命名空间的内存。

治理思路有三条:

  1. 入口归档:数据落 HDFS 前先合并成大文件(顺序追加成 128MB 以上的容器文件),最能治本;
  2. CombineFileInputFormat:把多个小文件打包进一个分片,一个 Map 任务处理多个小文件,同时兼顾本地性(同节点的小文件优先打包在一起);
  3. 序列文件转换:把许多小文件转成一个 SequenceFile 或其他容器格式,文件名作键、内容作值,顺带解决"map 拿不到文件名"的问题。
// 使用 CombineFileInputFormat 治理小文件的核心配置 job.setInputFormatClass(CombineFileInputFormat.class); // 设定一个分片最多打包的数据量 如 128MB CombineFileInputFormat.setMaxSplitSize(job, 134217728); // 设定一个分片最多包含的路径数 防止元数据扫描过慢 CombineFileInputFormat.setMinSplitSizeNode(job, 10485760);

本节要点回顾

  • 分片是逻辑单位:只记录偏移与长度的元数据,块才是物理切块;分片数决定 Map 任务数。
  • 推导公式:splitSize 取最小分片、目标尺寸与块大小三者约束下的结果,任务数约为文件大小除以 splitSize 向上取整。
  • 默认对齐块大小:为了让一个任务的数据尽量落在一个块、一个节点上,为数据本地性铺路。
  • 行跨块不丢不重:读取端越界读完最后一行,下个分片跳过残行。
  • 小文件是反模式:任务开销与元数据双重放大,治理靠入口归档、CombineFileInputFormat、容器格式。

分片决定了"谁在哪块数据上开工",下一节看框架如何让任务尽量在数据所在地开机。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U