当前位置:  首页>> 技术小册>> Hadoop入门教程

通过InputFormat决定读取的数据的类型,然后拆分成一个个InputSplit,每个InputSplit对应一个Map处理,RecordReader读取InputSplit的内容给Map

InputFormat

决定读取数据的格式,可以是文件或数据库等

功能

验证作业输入的正确性,如格式等
将输入文件切割成逻辑分片(InputSplit),一个InputSplit将会被分配给一个独立的Map任务
提供RecordReader实现,读取InputSplit中的”K-V对”供Mapper使用

方法

List getSplits(): 获取由输入文件计算出输入分片(InputSplit),解决数据或文件分割成片问题

  1. RecordReader <k,v>createRecordReader():</k,v> 创建#x5EFA;RecordReader,从InputSplit中读取数据,解决读取分片中数据问题

类结构

TextInputFormat: 输入文件中的每一行就是一个记录,Key是这一行的byte offset,而value是这一行的内容

KeyValueTextInputFormat: 输入文件中每一行就是一个记录,第一个分隔符字符切分每行。在分隔符字符之前的内容为Key,在之后的为Value。分隔符变量通过key.value.separator.in.input.line变量设置,默认为(\t)字符。

NLineInputFormat: 与TextInputFormat一样,但每个数据块必须保证有且只有N行,mapred.line.input.format.linespermap属性,默认为1

SequenceFileInputFormat: 一个用来读取字符流数据的InputFormat,为用户自定义的。字符流数据是Hadoop自定义的压缩的二进制数据格式。它用来优化从一个MapReduce任务的输出到另一个MapReduce任务的输入之间的数据传输过程。

InputSplit
代表一个个逻辑分片,并没有真正存储数据,只是提供了一个如何将数据分片的方法

Split内有Location信息,利于数据局部化

一个InputSplit给一个单独的Map处理

  1. public abstract class InputSplit {
  2. /**
  3. * 获取Split的大小,支持根据size对InputSplit排序.
  4. */
  5. public abstract long getLength() throws IOException, InterruptedException;
  6. /**
  7. * 获取存储该分片的数据所在的节点位置.
  8. */
  9. public abstract String[] getLocations() throws IOException, InterruptedException;
  10. }

RecordReader

将InputSplit拆分成一个个对给Map处理,也是实际的文件读取分隔对象

问题

大量小文件如何处理
CombineFileInputFormat可以将若干个Split打包成一个,目的是避免过多的Map任务(因为Split的数目决定了Map的数目,大量的Mapper Task创建销毁开销将是巨大的)

怎么计算split的
通常一个split就是一个block(FileInputFormat仅仅拆分比block大的文件),这样做的好处是使得Map可以在存储有当前数据的节点上运行本地的任务,而不需要通过网络进行跨节点的任务调度

通过mapred.min.split.size, mapred.max.split.size, block.size来控制拆分的大小

如果mapred.min.split.size大于block size,则会将两个block合成到一个split,这样有部分block数据需要通过网络读取

如果mapred.max.split.size小于block size,则会将一个block拆成多个split,增加了Map任务数(Map对split进行计算ק#x5E76;且上报结果,关闭当前计算打开新的split均需要耗费资源)

先获取文件在HDFS上的路径和Block信息,然后根据splitSize对文件进行切分( splitSize = computeSplitSize(blockSize, minSize, maxSize) ),默认splitSize 就等于blockSize的默认值(64m)

  1. public List<InputSplit> getSplits(JobContext job) throws IOException {
  2. // 首先计算分片的最大和最小值。这两个值将会用来计算分片的大小
  3. long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
  4. long maxSize = getMaxSplitSize(job);
  5. // generate splits
  6. List<InputSplit> splits = new ArrayList<InputSplit>();
  7. List<FileStatus> files = listStatus(job);
  8. for (FileStatus file: files) {
  9. Path path = file.getPath();
  10. long length = file.getLen();
  11. if (length != 0) {
  12. FileSystem fs = path.getFileSystem(job.getConfiguration());
  13. // 获取该文件所有的block信息列表[hostname, offset, length]
  14. BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0, length);
  15. // 判断文件是否可分割,通常是可分割的,但如果文件是压缩的,将不可分割
  16. if (isSplitable(job, path)) {
  17. long blockSize = file.getBlockSize();
  18. // 计算分片大小
  19. // 即 Math.max(minSize, Math.min(maxSize, blockSize));
  20. long splitSize = computeSplitSize(blockSize, minSize, maxSize);
  21. long bytesRemaining = length;
  22. // 循环分片。
  23. // 当剩余数据与分片大小比值大于Split_Slop时,继续分片, 小于等于时,停止分片
  24. while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
  25. int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
  26. splits.add(makeSplit(path, length-bytesRemaining, splitSize, blkLocations[blkIndex].getHosts()));
  27. bytesRemaining -= splitSize;
  28. }
  29. // 处理余下的数据
  30. if (bytesRemaining != 0) {
  31. splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining, blkLocations[blkLocations.length-1].getHosts()));
  32. }
  33. } else {
  34. // 不可split,整块返回
  35. splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts()));
  36. }
  37. } else {
  38. // 对于长度为0的文件,创建空Hosts列表,返回
  39. splits.add(makeSplit(path, 0, length, new String[0]));
  40. }
  41. }
  42. // 设置输入文件数量
  43. job.getConfiguration().setLong(NUM_INPUT_FILES, files.size());
  44. LOG.debug("Total # of splits: " + splits.size());
  45. return splits;
  46. }

分片间的数据如何处理

split是根据文件大小分割的,而一般处理是根据分隔符进行分割的,这样势必存在一条记录横跨两个split

解决办法是只要不是第一个split,都会远程读取一条记录。不是第一个split的都忽略到第一条记录

  1. public class LineRecordReader extends RecordReader<LongWritable, Text> {
  2. private CompressionCodecFactory compressionCodecs = null;
  3. private long start;
  4. private long pos;
  5. private long end;
  6. private LineReader in;
  7. private int maxLineLength;
  8. private LongWritable key = null;
  9. private Text value = null;
  10. // initialize函数即对LineRecordReader的一个初始化
  11. // 主要是计算分片的始末位置,打开输入流以供读取K-V对,处理分片经过压缩的情况等
  12. public void initialize(InputSplit genericSplit, TaskAttemptContext context) throws IOException {
  13. FileSplit split = (FileSplit) genericSplit;
  14. Configuration job = context.getConfiguration();
  15. this.maxLineLength = job.getInt("mapred.linerecordreader.maxlength", Integer.MAX_VALUE);
  16. start = split.getStart();
  17. end = start + split.getLength();
  18. final Path file = split.getPath();
  19. compressionCodecs = new CompressionCodecFactory(job);
  20. final CompressionCodec codec = compressionCodecs.getCodec(file);
  21. // 打开文件,并定位到分片读取的起始位置
  22. FileSystem fs = file.getFileSystem(job);
  23. FSDataInputStream fileIn = fs.open(split.getPath());
  24. boolean skipFirstLine = false;
  25. if (codec != null) {
  26. // 文件是压缩文件的话,直接打开文件
  27. in = new LineReader(codec.createInputStream(fileIn), job);
  28. end = Long.MAX_VALUE;
  29. } else {
  30. // 只要不是第一个split,则忽略本split的第一行数据
  31. if (start != 0) {
  32. skipFirstLine = true;
  33. --start;
  34. // 定位到偏移位置,下&#x#x6B21;读取就会从偏移位置开始
  35. fileIn.seek(start);
  36. }
  37. in = new LineReader(fileIn, job);
  38. }
  39. if (skipFirstLine) {
  40. // 忽略第一行数据,重新定位start
  41. start += in.readLine(new Text(), 0, (int) Math.min((long) Integer.MAX_VALUE, end - start));
  42. }
  43. this.pos = start;
  44. }
  45. public boolean nextKeyValue() throws IOException {
  46. if (key == null) {
  47. key = new LongWritable();
  48. }
  49. key.set(pos);// key即为偏移量
  50. if (value == null) {
  51. value = new Text();
  52. }
  53. int newSize = 0;
  54. while (pos < end) {
  55. newSize = in.readLine(value, maxLineLength, Math.max((int) Math.min(Integer.MAX_VALUE, end - pos), maxLineLength));
  56. // 读取的数据长度为0,则说明已读完
  57. if (newSize == 0) {
  58. break;
  59. }
  60. pos += newSize;
  61. // 读取的数据长度小于最大行长度,也说明已读取完毕
  62. if (newSize < maxLineLength) {
  63. break;
  64. }
  65. // 执行到此处,说明该行数据没读完,继续读入
  66. }
  67. if (newSize == 0) {
  68. key = null;
  69. value = null;
  70. return false;
  71. } else {
  72. return true;
  73. }
  74. }
  75. }

该分类下的相关小册推荐:

暂无相关推荐.