[英]How to divide a big dataset into multiple small files in Hadoop in an efficient way
我有一個包含每個1M記錄的文件的大數據集,我想將它分成一些文件,每個文件在Hadoop中有1000條記錄。 我正在研究實現這一目標的不同方案。 一種方法是將分割大小設置得很小,這樣每個映射器只需要幾條記錄(~1000條記錄)然后輸出它們。 這需要運行許多效率不高的映射器。 另一種解決方案是考慮一個減速器並將所有記錄發送給它並在那里進行拆分。 這對mapreduce也是違反直覺的,因為所有工作都只由一個節點完成。 將此數據集拆分為小文件的有效替代方法是什么?
您可以使用NLineInputFormat指定應該為映射器提供多少記錄作為輸入。
將屬性“mapreduce.input.lineinputformat.linespermap”設置為1000的倍數,以便生成合理數量的映射器。在映射器中,使用多個輸出將每個1000條記錄寫入使用計數器增量邏輯的單獨文件。
使用多個輸出將數據拆分為1000條記錄的示例代碼(對於文本文件)
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.MultipleOutputs;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
public class DataSplitter {
public static class Map extends Mapper<LongWritable, Text, NullWritable, Text> {
private Text outputValue = new Text();
@SuppressWarnings("rawtypes")
private MultipleOutputs multipleOutputs;
private int fileCounter = 1;
private List<String> recordList = new ArrayList<String>();
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
protected void setup(Mapper<LongWritable, Text, NullWritable, Text>.Context context) throws IOException, InterruptedException {
multipleOutputs = new MultipleOutputs(context);
}
@SuppressWarnings("unchecked")
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
recordList.add(line);
if (recordList.size() == 1000) {
for (int i = 0; i < recordList.size(); i++) {
outputValue.set(recordList.get(i));
multipleOutputs.write("mos", NullWritable.get(), outputValue, "output-" + fileCounter);
}
fileCounter++;
recordList.clear();
}
}
@Override
protected void cleanup(Mapper<LongWritable, Text, NullWritable, Text>.Context context) throws IOException, InterruptedException {
multipleOutputs.close();
if (!recordList.isEmpty()) {
for (int i = 0; i < recordList.size(); i++) {
outputValue.set(recordList.get(i));
context.write(NullWritable.get(), outputValue);
}
recordList.clear();
}
}
}
public static class Reduce extends Reducer<LongWritable, Text, NullWritable, Text> {
private Text outputValue = new Text();
@SuppressWarnings("rawtypes")
private MultipleOutputs multipleOutputs;
private int fileCounter = 1;
private List<String> recordList = new ArrayList<String>();
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
protected void setup(Reducer<LongWritable, Text, NullWritable, Text>.Context context) throws IOException, InterruptedException {
// TODO Auto-generated method stub
multipleOutputs = new MultipleOutputs(context);
}
@SuppressWarnings("unchecked")
public void reduce(NullWritable key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
for (Text value : values) {
String line = value.toString();
recordList.add(line);
if (recordList.size() == 1000) {
for (int i = 0; i < recordList.size(); i++) {
outputValue.set(recordList.get(i));
multipleOutputs.write("mos", NullWritable.get(), outputValue, "output-" + fileCounter);
}
fileCounter++;
recordList.clear();
}
if (!recordList.isEmpty()) {
for (int i = 0; i < recordList.size(); i++) {
outputValue.set(recordList.get(i));
context.write(NullWritable.get(), outputValue);
}
}
}
}
@Override
protected void cleanup(Reducer<LongWritable, Text, NullWritable, Text>.Context context) throws IOException, InterruptedException {
// TODO Auto-generated method stub
super.cleanup(context);
multipleOutputs.close();
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
@SuppressWarnings("deprecation")
Job job = new Job(conf, "DataSplitter");
job.setJarByClass(DataSplitter.class);
job.setOutputKeyClass(NullWritable.class);
job.setOutputValueClass(Text.class);
job.setMapOutputKeyClass(NullWritable.class);
job.setMapOutputValueClass(Text.class);
job.setMapperClass(Map.class);
job.setReducerClass(Reduce.class);
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
FileSystem.get(conf).delete(new Path(args[1]), true);
MultipleOutputs.addNamedOutput(job, "mos", TextOutputFormat.class, NullWritable.class, Text.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
System.exit(job.waitForCompletion(true) == true ? 0 : 1);
}
}
如果您不特別關心哪條記錄在哪里,請事先計算您想要的文件數,並將其放入配置中。 然后你可以在mapper中有一個隨機數生成器,它生成一個介於0和(numFiles -1)之間的隨機數。 將num % numReducers
作為映射器輸出的鍵,numReducers是您想要的reducer數。
對於該值,使用MapWritable<IntWritable,RecordClass>
,將RecordClass
替換為方便存儲記錄本身的任何內容。 對於IntWritable
將原始隨機數表示應該進入哪個文件。 將記錄的其余部分放入RecordClass
插槽。
在reducer中,從映射中提取隨機數,並根據該數字將記錄寫入文件(如果number為1則寫入文件FileName1,如果number為2則寫入FileName2等)。
使用spark將大文件拆分為多個小文件。
下面的示例將輸入文件拆分為2個文件:
scala> sc.textFile("/xyz-path/input-file",2).saveAsTextFile("/xyz-path/output-file")
textFile中的第二個參數是minPartitions,它使用默認分區程序。 您還可以使用客戶分區程序來實現更好的分區策略。 在此處閱讀有關Custom partition的更多信息
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.