HADOOP数据分片及MapTask并行度

MapReduce进行数据处理时,首先,需要从hdfs读取数据借助getSplits()方法进行分片;然后,创建和分片数量一致的Maptask,并为每个MapTask分配一个数据分片;最后,再借助RecordReader类读取分配的数据分片,以key,value的形式传递给map函数使用。分片数量决定了MapTask数量,进一步决定了Map任务的并行度。InputFormat的getSplits()方法和RecordReader是数据分片的关键函数。

InputSplit对象

FileSplit用于描述分片的结果信息,是getSplits方法的处理结果,其部分类属性如下。

其中file为分片对应的文件的hdfs路径或本地文件系统file路径,

start表示从file的哪一个字节开始属于此分片,

length表示file中从start取多少字节数据。

private Path file;
private long start;
private long length;

getSplits()方法

getSplits方法对输入的文件进行分片,并将分片的结果信息封装到InputSplit对象中。主要根据文件的数量,文件的大小进行文件的分片。以分片大小设置为100M为例,对于文件大小不超过100M的,直接作为一个单独数据分片。对于超过100M超过的文件,将其切分为多个数据分片,每个分片小于等于100M。

#此处以TextInputFormat的代码为例
#getSplits方法的简略代码如下,其中bytesRemaining表示文件还剩多少字节,如果剩下的大小还超过分片大小,就截取splitSize长度的文件创建新分片,直到剩下的字节小于等于分片大小。
while (((double) bytesRemaining)/splitSize > =1) {
          splits.add(new FileSplit(file=path, start=length-bytesRemaining, length=splitSize);     
          bytesRemaining -= splitSize;        //剩余的大小
        }

下边对分片过程进行举例说明,使用getSplits方法获得InputSplit对象结果。

例如,对于两个文件:1.txt,2.txt,分片大小为100M。1.txt大小为180M,2.txt大小为280M。其分片结果为,

InputSplit(file=“1.txt”,start=0,length=100x1024x1024) InputSplit(file=“1.txt”,start=80x1024x1024,length=80x1024x1024)

InputSplit(file=“2.txt”,start=0,length=100x1024x1024)

InputSplit(file=“2.txt”,start=100x1024x102,length=100x1024x1024)

InputSplit(file=“2.txt”,start=200x1024x102,length=80x1024x1024)

这样共产生了5个数据分片,举例说明第一个分片,它是来自1.txt,开始索引为0,长度为100x1024x1024字节,即100M。实际上不同文件的分片结果互不影响,只用关注单个文件的分片过程即可。

MapTask

MapTask是map任务的执行者,有多少MapTask就有多少个并行执行的任务,即并行度。每个MapTask对应一个FileSplit,两者关系为一对一,MapTask通过调用RecordReader的nextKey,NextValue逐个读取FileSplit中的数据。

#contenxt.getCurrentKey,context.getCurrentValue本质就是调用的RecordReader的getCurrentKey,getCurrentValue,通过循环调用nextKeyValue()进行取值,直到nextKeyValue()返回false,到达数据分片的结尾。
while (context.nextKeyValue()) {
        map(context.getCurrentKey(), context.getCurrentValue(), context);
      }

RecordReader

RecordReader用于控制如何读取MapTask的InputSplit对象数据,并传递给Map函数。关键函数为NextKeyValue(),getCurrentKey(),getCurrentValue()。NextKeyValue()返回true或false,表示是否读取到结尾,getCurrentKey(),getCurrentValue()用于获取key、value的值。

#以TextInputFormat使用的LineRecordReader的简略代码为例,其NextKeyValue()如下,每次调用NextKeyValue()它会读取InputSplit的一行数据
key=new LongWritable()
value=new Text()
newSize = in.readLine(value, maxLineLength, maxBytesToConsume(pos));  //读取的结果引用传递给value
pos=pos+newSize  //pos表示读取到那里了,即在数据分片中的偏移量
k.set(pos)

#之后MapTask会调用getCurrentKey(),getCurrentValue()获取刚刚NextKeyValue()生成的key和value。并传递给map函数使用
 @Override
  public LongWritable getCurrentKey() {
    return key;
  }

  @Override
  public Text getCurrentValue() {
    return value;
  }

其他分片方式

CombineTextInputFormat

CombineTextInputFormat使用TextRecordReaderWrapper作为RecordReader,将CombineFileSplit作为TextRecordReaderWrapper的输入,且CombineTextInputFormat实现了自己的getSplits,他会将多个小文件合并为一个分片,直到达到分片大小,可以有效解决小文件过多,MapTask过多降低效率的问题。

自定义RecordReader

为了实现小文件合并,并且每次调用map函数,读取一个小文件的全部内容,编写一个类继承了CombineTextInputFormat,并重写了createRecordReader方法,编写一个类继承了RecordReader类,重写了三个关键方法。

public boolean nextKeyValue() throws IOException, InterruptedException {
//        split包括逻辑文件路径,文件内偏移。根据FileSplit的getPaths属性取出路径,然后一次全部读取到value中,并设置key和value。
        if(fileIndex<split.getPaths().length)
        {
            Path path=split.getPath(fileIndex);
            System.out.println("CombineWholeRecordReader read whole json file in:"+path+" startOffset is "+split.getOffset(fileIndex));
            FileSystem fileSystem=path.getFileSystem(configuration);
            InputStream inputStream=fileSystem.open(path);
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            StringBuffer json=new StringBuffer();
            String tem="";
            while(true){
                tem = bufferedReader.readLine();
                if (tem != null) {
                    json.append(tem);
                } else break;
            }
            k.set(fileIndex);
            v.set(json.toString());
            isProgress=false;
            fileIndex++;
            return true;
        }
        return false;
    }

分片优化

HADOOP分块

HDFS的文件在物理上是分块存储的(Block),块的大小可以通过配置参数dfs.blocksize来规定,Hadoop3中默认块大小为128M。当存储一个大于128M的文件时,hdfs会将其划分为Block1,Block2… …,每一块最大为128M,而文件与划分后地Block的对应关系由NameNode进行存储和维护。DataNode负责存储实际地数据块Block,执行数据块地读写操作。

在分片完成后,MapTask数据需要获取自身所需的分片数据,一般情况下对应的Block都在本地,无需进行网络沟通。但是,如果将分片尺寸设置的与Blocksize 不同,便会将Block切分开,发送到不同结点上的MapTask,所以会造成网络开销,故一般将两者设置的一样大。

处理核心数

处理核心数=结点机器数*每台机器的核心数

可参考此参数和输入数据的规模设计分片规则,让机器的到最大利用,并且挑选合适的并行度用于削减执行时间,注意执行时间与数据传输时间、初始化时间的占比。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值