当map task 开始运,q生中间数据时Q其产生的中间结果ƈ非直接就单的写入盘。这中间的过E比较复杂,q且利用C内存buffer 来进行已l生的部分l果的缓存,q在内存buffer 中进行一些预排序来优化整个map 的性能。如上图所C,每一个map 都会对应存在一个内存buffer QMapOutputBuffer Q即上图的buffer in memory Q,map 会将已经产生的部分结果先写入到该buffer 中,q个buffer 默认?00MB 大小Q但是这个大是可以Ҏjob 提交时的参数讑֮来调整的Q该参数即ؓQ?/span> io.sort.mb 。当map 的生数据非常大Ӟq且把io.sort.mb 调大Q那么map 在整个计过E中spill 的次数就势必会降低,map task 对磁盘的操作׃变少Q如果map tasks 的瓶颈在盘上,q样调整׃大大提高map 的计性能。map 做sort 和spill 的内存结构如下如所C:
map 在运行过E中Q不停的向该buffer 中写入已有的计算l果Q但是该buffer q不一定能全部的map 输出~存下来Q当map 输出出一定阈|比如100M Q,那么map 必d该buffer 中的数据写入到磁盘中去,q个q程在mapreduce 中叫做spill 。map q不是要{到该buffer 全部写满时才q行spill Q因为如果全部写满了再去写spill Q势必会造成map 的计部分等待buffer 释放I间的情c所以,map 其实是当buffer 被写满到一定程度(比如80% Q时Q就开始进行spill 。这个阈g是由一个job 的配|参数来控制Q即 io.sort.spill.percent Q默认ؓ0.80 ?0% 。这个参数同样也是媄响spill 频繁E度Q进而媄响map task q行周期对磁盘的d频率的。但非特D情况下Q通常不需要h为的调整。调整io.sort.mb 对用h说更加方ѝ?/span>
当map task 的计部分全部完成后Q如果map 有输出,׃生成一个或者多个spill 文gQ这些文件就是map 的输出结果。map 在正帔RZ前,需要将q些spill 合ƈQmerge Q成一个,所以map 在结束之前还有一个merge 的过E。merge 的过E中Q有一个参数可以调整这个过E的行ؓQ该参数为: io.sort.factor 。该参数默认?0 。它表示当merge spill 文gӞ最多能有多ƈ行的stream 向merge 文g中写入。比如如果map 产生的数据非常的大,产生的spill 文g大于10 Q而io.sort.factor 使用的是默认?0 Q那么当map 计算完成做merge Ӟ没有办法一ơ将所有的spill 文gmerge 成一个,而是会分多次Q每ơ最?0 个stream 。这也就是说Q当map 的中间结果非常大Q调大io.sort.factor Q有利于减少merge ơ数Q进而减map 对磁盘的d频率Q有可能辑ֈ优化作业的目的?/span>
当job 指定了combiner 的时候,我们都知道map 介绍后会在map 端根据combiner 定义的函数将map l果q行合ƈ。运行combiner 函数的时机有可能会是merge 完成之前Q或者之后,q个时机可以׃个参数控Ӟ?/span> min.num.spill.for.combine Qdefault 3 Q,当job 中设定了combiner Qƈ且spill 数最有3 个的时候,那么combiner 函数׃在merge 产生l果文g之前q行。通过q样的方式,可以在spill 非常多需要merge Qƈ且很多数据需要做conbine 的时候,减少写入到磁盘文件的数据数量Q同hZ减少对磁盘的d频率Q有可能辑ֈ优化作业的目的?/span>
减少中间l果dq出盘的方法不止这些,q有是压羃。也是说map 的中_无论是spill 的时候,q是最后merge 产生的结果文Ӟ都是可以压羃的。压~的好处在于Q通过压羃减少写入d盘的数据量。对中间l果非常大,盘速度成ؓmap 执行瓉的job Q尤其有用。控制map 中间l果是否使用压羃的参CؓQ?/span> mapred.compress.map.output (true/false) 。将q个参数讄为true Ӟ那么map 在写中间l果Ӟ׃数据压~后再写入磁盘,ȝ果时也会采用先解压后d数据。这样做的后果就是:写入盘的中间结果数据量会变,但是cpu 会消耗一些用来压~和解压。所以这U方式通常适合job 中间l果非常大,瓉不在cpu Q而是在磁盘的d的情c说的直白一些就是用cpu 换IO 。根据观察,通常大部分的作业cpu 都不是瓶颈,除非q算逻辑异常复杂。所以对中间l果采用压羃通常来说是有收益的。以下是一个wordcount 中间l果采用压羃和不采用压羃产生的map 中间l果本地盘d的数据量ҎQ?/span>
map 中间l果不压~:
map 中间l果压羃Q?/span>
可以看出Q同Ljob Q同L数据Q在采用压羃的情况下Qmap 中间l果能羃将q?0 倍,如果map 的瓶颈在盘Q那么job 的性能提升会非常可观?/span>
当采用map 中间l果压羃的情况下Q用戯可以选择压羃ӞK�用哪U压~格式进行压~,现在Hadoop 支持的压~格式有Q?/span> GzipCodec Q?/span> LzoCodec Q?/span> BZip2Codec Q?/span> LzmaCodec {压~格式。通常来说Q想要达到比较^衡的 cpu 和磁盘压~比Q?/span> LzoCodec 比较适合。但也要取决?/span> job 的具体情c用戯惌自行选择中间l果的压~算法,可以讄配置参数Q?/span> mapred.map.output.compression.codec =org.apache.hadoop.io.compress.DefaultCodec 或者其他用戯行选择的压~方式?/span>
选项 | cd | 默认?/span> | 描述 |
io.sort.mb | int | 100 | ~存 map 中间l果?/span> buffer 大小 (in MB) |
io.sort.record.percent | float | 0.05 | io.sort.mb 中用来保?/span> map output 记录边界的百分比Q其他缓存用来保存数?/span> |
io.sort.spill.percent | float | 0.80 | map 开始做 spill 操作的阈?/span> |
io.sort.factor | int | 10 | ?/span> merge 操作时同时操作的 stream C限?/span> |
min.num.spill.for.combine | int | 3 | combiner 函数q行的最?/span> spill ?/span> |
mapred.compress.map.output | boolean | false | map 中间l果是否采用压羃 |
mapred.map.output.compression.codec | class name | org.apache.Hadoop.io. compress.DefaultCodec | map 中间l果的压~格?/span> |
reduce 的运行是分成三个阶段的。分别ؓ copy->sort->reduce 。由?/span> job 的每一?/span> map 都会Ҏ reduce(n) 数将数据分成 map 输出l果分成 n ?/span> partition Q所?/span> map 的中间结果中是有可能包含每一?/span> reduce 需要处理的部分数据的。所以,Z优化 reduce 的执行时_ hadoop 中是{?/span> job 的第一?/span> map l束后,所有的 reduce 开始尝试从完成?/span> map 中下载该 reduce 对应?/span> partition 部分数据。这个过E就是通常所说的 shuffle Q也是 copy q程?/span>
Reduce task 在做 shuffle Ӟ实际上就是从不同的已l完成的 map 上去下蝲属于自己q个 reduce 的部分数据,׃ map 通常有许多个Q所以对一?/span> reduce 来说Q下载也可以是ƈ行的从多?/span> map 下蝲Q这个ƈ行度是可以调整的Q调整参CؓQ?/span> mapred.reduce.parallel.copies Q?/span> default 5 Q。默认情况下Q每个只会有 5 个ƈ行的下蝲U程在从 map 下数据,如果一个时间段?/span> job 完成?/span> map ?/span> 100 个或者更多,那么 reduce 也最多只能同时下?/span> 5 ?/span> map 的数据,所以这个参数比较适合 map 很多q且完成的比较快?/span> job 的情况下调大Q有利于 reduce 更快的获取属于自己部分的数据?/span>
reduce 的每一个下载线E在下蝲某个 map 数据的时候,有可能因为那?/span> map 中间l果所在机器发生错误,或者中间结果的文g丢失Q或者网l瞬断等{情况,q样 reduce 的下载就有可能失败,所?/span> reduce 的下载线Eƈ不会无休止的{待下去Q当一定时间后下蝲仍然p|Q那么下载线E就会放弃这ơ下载,q在随后试从另外的地方下蝲Q因D|?/span> map 可能重跑Q。所?/span> reduce 下蝲U程的这个最大的下蝲旉D|可以调整的,调整参数为: mapred.reduce.copy.backoff Q?/span> default 300 U)。如果集环境的|络本n是瓶颈,那么用户可以通过调大q个参数来避?/span> reduce 下蝲U程被误判ؓp|的情c不q在|络环境比较好的情况下,没有必要调整。通常来说专业的集网l不应该有太大问题,所以这个参数需要调整的情况不多?/span>
Reduce ?/span> map l果下蝲到本地时Q同样也是需要进?/span> merge 的,所?/span> io.sort.factor 的配|选项同样会媄?/span> reduce q行 merge 时的行ؓQ该参数的详l介l上文已l提刎ͼ当发?/span> reduce ?/span> shuffle 阶段 iowait 非常的高的时候,有可能通过调大q个参数来加大一?/span> merge 时的q发吞吐Q优?/span> reduce 效率?/span>
Reduce ?/span> shuffle 阶段对下载来?/span> map 数据Qƈ不是立刻写入磁盘的Q而是会先~存在内存中Q然后当使用内存辑ֈ一定量的时候才刷入盘。这个内存大的控制׃?/span> map 一样可以通过 io.sort.mb 来设定了Q而是通过另外一个参数来讄Q?/span> mapred.job.shuffle.input.buffer.percent Q?/span> default 0.7 Q,q个参数其实是一个百分比Q意思是_ shuffile ?/span> reduce 内存中的数据最多用内存量为: 0.7 × maxHeap of reduce task 。也是_如果?/span> reduce task 的最?/span> heap 使用量(通常通过 mapred.child.java.opts 来设|,比如讄?/span> -Xmx1024m Q的一定比例用来缓存数据。默认情况下Q?/span> reduce 会用其 heapsize ?/span> 70% 来在内存中缓存数据。如?/span> reduce ?/span> heap ׃业务原因调整的比较大Q相应的~存大小也会变大Q这也是Z?/span> reduce 用来做缓存的参数是一个百分比Q而不是一个固定的g?/span>
假设 mapred.job.shuffle.input.buffer.percent ?/span> 0.7 Q?/span> reduce task ?/span> max heapsize ?/span> 1G Q那么用来做下蝲数据~存的内存就为大?/span> 700MB 左右Q这 700M 的内存,?/span> map 端一P也不是要{到全部写满才会往盘LQ而是当这 700M 中被使用C一定的限度Q通常是一个百分比Q,׃开始往盘列这个限度阈g是可以通过 job 参数来设定的Q设定参CؓQ?/span> mapred.job.shuffle.merge.percent Q?/span> default 0.66 Q。如果下载速度很快Q很Ҏ把内存~存撑大Q那么调整一下这个参数有可能会对 reduce 的性能有所帮助?/span>
?/span> reduce 所有的 map 上对应自?/span> partition 的数据下载完成后Q就会开始真正的 reduce 计算阶段Q中间有?/span> sort 阶段通常旉非常短,几秒钟就完成了,因ؓ整个下蝲阶段已l是边下载边 sort Q然后边 merge 的)。当 reduce task 真正q入 reduce 函数的计阶D늚时候,有一个参C是可以调?/span> reduce 的计行为。也是Q?/span> mapred.job.reduce.input.buffer.percent Q?/span> default 0.0 Q。由?/span> reduce 计算时肯定也是需要消耗内存的Q而在d reduce 需要的数据Ӟ同样是需要内存作?/span> buffer Q这个参数是控制Q需要多的内存癑ֈ比来作ؓ reduce dl?/span> sort 好的数据?/span> buffer 癑ֈ比。默认情况下?/span> 0 Q也是_默认情况下, reduce 是全部从盘开始读处理数据。如果这个参数大?/span> 0 Q那么就会有一定量的数据被~存在内存ƈ输送给 reduce Q当 reduce 计算逻辑消耗内存很时Q可以分一部分内存用来~存数据Q反?/span> reduce 的内存闲着也是闲着?/span>
选项 | cd | 默认?/span> | 描述 | |||
mapred.reduce.parallel.copies | int | 5 | 每个 reduce q行下蝲 map l果的最大线E数 | |||
mapred.reduce.copy.backoff | int | 300 | reduce 下蝲U程最大等待时_ in sec io.sort.factor | int | 10 | 同上 |
mapred.job.shuffle.input.buffer.percent | float | 0.7 | 用来~存 shuffle 数据?/span> reduce task heap 癑ֈ?/span> | |||
mapred.job.shuffle.merge.percent | float | 0.66 | ~存的内存中多少癑ֈ比后开始做 merge 操作 | |||
mapred.job.reduce.input.buffer.percent | float | 0.0 | sort 完成?/span> reduce 计算阶段用来~存数据的百分比 |
有一Ҏ据用hadoop mapreduce job处理Ӟ业务特点要求一个文件对应一个map来处理,如果两个或多个map处理了同一个文Ӟ可能会有问题。开始想通过讄 dfs.blocksize 或?mapreduce.input.fileinputformat.split.minsize/maxsize 参数来控制map的个敎ͼ后来惛_其实不用q么复杂Q在自定义的InputFormat里面直接让文件不要进行split可以了?/p>
public class CustemDocInputFormat extends TextInputFormat { |
@Override |
public RecordReader<LongWritable, Text> createRecordReader(InputSplit split, TaskAttemptContext context) { |
DocRecordReader reader = null ; |
try { |
reader = new DocRecordReader(); // 自定义的reader |
} catch (IOException e) { |
e.printStackTrace(); |
} |
return reader; |
} |
@Override |
protected boolean isSplitable(JobContext context, Path file) { |
return false ; |
} |
} |
q样Q输入文件有多少个,job׃启动多少个map了?/p>
hadoop中提供了 MultiOutputFormat 能将l果数据输出C同的目录Q也提供?FileInputFormat 来一ơ读取多个目录的数据Q但是默认一个job只能使用 job.setInputFormatClass 讄使用一个inputfomat处理一U格式的数据。如果需要实?在一个job中同时读取来自不同目录的不同格式文g 的功能,需要自己实C?MultiInputFormat 来读取不同格式的文g?原来已经提供?a title="MultipleInputs" target="_blank">MultipleInputs)?/p>
例如Q有一个mapreduce job需要同时读取两U格式的数据Q一U格式是普通的文本文gQ用 LineRecordReader 一行一行读取;另外一U文件是伪XML文gQ用自定义的AJoinRecordReaderd?/p>
自己实现了一个简单的 MultiInputFormat 如下Q?/p>
import org.apache.hadoop.io.LongWritable; |
import org.apache.hadoop.io.Text; |
import org.apache.hadoop.mapreduce.InputSplit; |
import org.apache.hadoop.mapreduce.RecordReader; |
import org.apache.hadoop.mapreduce.TaskAttemptContext; |
import org.apache.hadoop.mapreduce.lib.input.FileSplit; |
import org.apache.hadoop.mapreduce.lib.input.LineRecordReader; |
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat; |
public class MultiInputFormat extends TextInputFormat { |
@Override |
public RecordReader<LongWritable, Text> createRecordReader(InputSplit split, TaskAttemptContext context) { |
RecordReader reader = null ; |
try { |
String inputfile = ((FileSplit) split).getPath().toString(); |
String xmlpath = context.getConfiguration().get( "xml_prefix" ); |
String textpath = context.getConfiguration().get( "text_prefix" ); |
if (- 1 != inputfile.indexOf(xmlpath)) { |
reader = new AJoinRecordReader(); |
} else if (- 1 != inputfile.indexOf(textpath)) { |
reader = new LineRecordReader(); |
} else { |
reader = new LineRecordReader(); |
} |
} catch (IOException e) { |
// do something ... |
} |
return reader; |
} |
} |
其实原理很简单,是?createRecordReader 的时候,通过 ((FileSplit) split).getPath().toString() 获取到当前要处理的文件名Q然后根据特征匹配,选取对应?RecordReader 卛_。xml_prefix和text_prefix可以在程序启动时通过 -D 传给Configuration?/p>
比如某次执行打印的值如下:
inputfile=hdfs://test042092.sqa.cm4:9000/ test /input_xml/common-part-00068 |
xmlpath_prefix=hdfs://test042092.sqa.cm4:9000/ test /input_xml |
textpath_prefix=hdfs://test042092.sqa.cm4:9000/ test /input_txt |
q里只是通过单的文g路径和标C符匚w来做Q也可以采用更复杂的ҎQ比如文件名、文件后~{?/p>
接着在mapcMQ也同样可以Ҏ不同的文件名特征q行不同的处理:
@Override |
public void map(LongWritable offset, Text inValue, Context context) |
throws IOException { |
String inputfile = ((FileSplit) context.getInputSplit()).getPath() |
.toString(); |
if (- 1 != inputfile.indexOf(textpath)) { |
...... |
} else if (- 1 != inputfile.indexOf(xmlpath)) { |
...... |
} else { |
...... |
} |
} |
q种方式太土了,原来hadoop里面已经提供?MultipleInputs 来实现对一个目录指定一?a title="查看inputformat中的全部文章" target="_blank">inputformat和对应的map处理cR?/p>
MultipleInputs.addInputPath(conf, new Path( "/foo" ), TextInputFormat. class , |
MapClass. class ); |
MultipleInputs.addInputPath(conf, new Path( "/bar" ), |
KeyValueTextInputFormat. class , MapClass2. class ); |
某日Q接手了同事写的?a title="查看hadoop中的全部文章" target="_blank">Hadoop集群拯数据到另外一个集的E序Q该E序是运行在Hadoop集群上的job。这个job只有map阶段Q读取hdfs目录下数据的数据Q然后写入到另外一个集?/p>
昄Q这个程序没有考虑大数据量的情况,如果输入目录下文件很多或数据量很大,׃Dmap数很多。而实际上我们需要拷贝的一个数据源有q?6TQjob启动h?w多个mapQ一下子整个queue的资源就占满了。虽焉过调整一些参数可以控制map?也就是ƈ发数)Q但是无法准的?制map敎ͼ而且换个数据源又得重新配|参数?/p>
W一个改q的版本是,加了Reduceq程Q以期望通过讄Reduce数量来控制ƈ发数。这栯然能_地控制ƈ发数Q但是增加了shuffle q程Q实际运行中发现输入数据有倾斜Q而partition的key׃业务需要无法更改)Q导致部分机器网l被打满Q从而媄响到了集中的其他应用。即 佉K过 mapred.reduce.parallel.copies 参数来限制shuffle也是L不治本。这个^白增加的shuffleq程实际上浪费了很多|络带宽和IO?/p>
最理想的情况当然是只有map阶段Q而且能够准确的控制ƈ发数了?/p>
于是Q第二个优化版本诞生了。这个job只有map阶段Q采?a title="CombineFileInputFormat" target="_blank">CombineFileInputFormatQ?它可以将多个文件打包成一个InputSplit提供l一个Map处理Q避免因为大量小文g问题Q启动大量map。通过 mapred.max.split.size 参数可以大概地控制ƈ发数。本以ؓq样p解决问题了,l果又发C数据倾斜的问题。这U粗略地分splits的方式,D有的map处理的数据少Q有?map处理的数据多Qƈ不均匀。几个拖后退的map导致job的实际运行时间长了一倍多?/p>
看来只有让每个map处理的数据量一样多Q才能完的解决q个问题了?/p>
W三个版本也诞生了,q次是重写了CombineFileInputFormatQ自己实现getSplitsҎ。由于输入数据ؓSequenceFile格式Q因此需要一个SequenceFileRecordReaderWrappercR?/p>
实现代码如下Q?br /> CustomCombineSequenceFileInputFormat.java
import java.io.IOException; |
import org.apache.hadoop.classification.InterfaceAudience; |
import org.apache.hadoop.classification.InterfaceStability; |
import org.apache.hadoop.mapreduce.InputSplit; |
import org.apache.hadoop.mapreduce.RecordReader; |
import org.apache.hadoop.mapreduce.TaskAttemptContext; |
import org.apache.hadoop.mapreduce.lib.input.CombineFileInputFormat; |
import org.apache.hadoop.mapreduce.lib.input.CombineFileRecordReader; |
import org.apache.hadoop.mapreduce.lib.input.CombineFileRecordReaderWrapper; |
import org.apache.hadoop.mapreduce.lib.input.CombineFileSplit; |
import org.apache.hadoop.mapreduce.lib.input.SequenceFileInputFormat; |
/** |
* Input format that is a <code>CombineFileInputFormat</code>-equivalent for |
* <code>SequenceFileInputFormat</code>. |
* |
* @see CombineFileInputFormat |
*/ |
@InterfaceAudience .Public |
@InterfaceStability .Stable |
public class CustomCombineSequenceFileInputFormat<K, V> extends MultiFileInputFormat<K, V> { |
@SuppressWarnings ({ "rawtypes" , "unchecked" }) |
public RecordReader<K, V> createRecordReader(InputSplit split, TaskAttemptContext context) |
throws IOException { |
return new CombineFileRecordReader((CombineFileSplit) split, context, |
SequenceFileRecordReaderWrapper. class ); |
} |
/** |
* A record reader that may be passed to <code>CombineFileRecordReader</code> so that it can be |
* used in a <code>CombineFileInputFormat</code>-equivalent for |
* <code>SequenceFileInputFormat</code>. |
* |
* @see CombineFileRecordReader |
* @see CombineFileInputFormat |
* @see SequenceFileInputFormat |
*/ |
private static class SequenceFileRecordReaderWrapper<K, V> |
extends CombineFileRecordReaderWrapper<K, V> { |
// this constructor signature is required by CombineFileRecordReader |
public SequenceFileRecordReaderWrapper(CombineFileSplit split, TaskAttemptContext context, |
Integer idx) throws IOException, InterruptedException { |
super ( new SequenceFileInputFormat<K, V>(), split, context, idx); |
} |
} |
} |
MultiFileInputFormat.java
import java.io.IOException; |
import java.util.ArrayList; |
import java.util.List; |
import org.apache.commons.logging.Log; |
import org.apache.commons.logging.LogFactory; |
import org.apache.hadoop.fs.FileStatus; |
import org.apache.hadoop.fs.Path; |
import org.apache.hadoop.mapreduce.InputSplit; |
import org.apache.hadoop.mapreduce.Job; |
import org.apache.hadoop.mapreduce.JobContext; |
import org.apache.hadoop.mapreduce.lib.input.CombineFileInputFormat; |
import org.apache.hadoop.mapreduce.lib.input.CombineFileSplit; |
/** |
* multiple files can be combined in one InputSplit so that InputSplit number can be limited! |
*/ |
public abstract class MultiFileInputFormat<K, V> extends CombineFileInputFormat<K, V> { |
private static final Log LOG = LogFactory.getLog(MultiFileInputFormat. class ); |
public static final String CONFNAME_INPUT_SPLIT_MAX_NUM = "multifileinputformat.max_split_num" ; |
public static final Integer DEFAULT_MAX_SPLIT_NUM = 50 ; |
public static void setMaxInputSplitNum(Job job, Integer maxSplitNum) { |
job.getConfiguration().setInt(CONFNAME_INPUT_SPLIT_MAX_NUM, maxSplitNum); |
} |
@Override |
public List<InputSplit> getSplits(JobContext job) throws IOException { |
// get all the files in input path |
List<FileStatus> stats = listStatus(job); |
List<InputSplit> splits = new ArrayList<InputSplit>(); |
if (stats.size() == 0 ) { |
return splits; |
} |
// 计算split的^均长?/code> |
long totalLen = 0 ; |
for (FileStatus stat : stats) { |
totalLen += stat.getLen(); |
} |
int maxSplitNum = job.getConfiguration().getInt(CONFNAME_INPUT_SPLIT_MAX_NUM, DEFAULT_MAX_SPLIT_NUM); |
int expectSplitNum = maxSplitNum < stats.size() ? maxSplitNum : stats.size(); |
long averageLen = totalLen / expectSplitNum; |
LOG.info( "Prepare InputSplit : averageLen(" + averageLen + ") totalLen(" + totalLen |
+ ") expectSplitNum(" + expectSplitNum + ") " ); |
// 讄inputSplit |
List<Path> pathLst = new ArrayList<Path>(); |
List<Long> offsetLst = new ArrayList<Long>(); |
List<Long> lengthLst = new ArrayList<Long>(); |
long currentLen = 0 ; |
for ( int i = 0 ; i < stats.size(); i++) { |
FileStatus stat = stats.get(i); |
pathLst.add(stat.getPath()); |
offsetLst.add(0L); |
lengthLst.add(stat.getLen()); |
currentLen += stat.getLen(); |
if (splits.size() < expectSplitNum - 1 && currentLen > averageLen) { |
Path[] pathArray = new Path[pathLst.size()]; |
CombineFileSplit thissplit = new CombineFileSplit(pathLst.toArray(pathArray), |
getLongArray(offsetLst), getLongArray(lengthLst), new String[ 0 ]); |
LOG.info( "combineFileSplit(" + splits.size() + ") fileNum(" + pathLst.size() |
+ ") length(" + currentLen + ")" ); |
splits.add(thissplit); |
// |
pathLst.clear(); |
offsetLst.clear(); |
lengthLst.clear(); |
currentLen = 0 ; |
} |
} |
if (pathLst.size() > 0 ) { |
Path[] pathArray = new Path[pathLst.size()]; |
CombineFileSplit thissplit = |
new CombineFileSplit(pathLst.toArray(pathArray), getLongArray(offsetLst), |
getLongArray(lengthLst), new String[ 0 ]); |
LOG.info( "combineFileSplit(" + splits.size() + ") fileNum(" + pathLst.size() |
+ ") length(" + currentLen + ")" ); |
splits.add(thissplit); |
} |
return splits; |
} |
private long [] getLongArray(List<Long> lst) { |
long [] rst = new long [lst.size()]; |
for ( int i = 0 ; i < lst.size(); i++) { |
rst[i] = lst.get(i); |
} |
return rst; |
} |
} |
通过 multifileinputformat.max_split_num 参数可以较为准的控制map数量Q而且会发现每个map处理的数据量很均匀。至此,问题ȝ解决了?/p>