hadoop mapreduce几种处理方式

mapreduce在应用过程中可以通过灵活定制各部分代码用以解决复杂逻辑,本文简单介绍各部分定制代码的流程,代码由于过多就不进行书写。

1、多map多reduce

在某些业务场景中,如找出公共好友的过程中,一组map-reduce任务没办法一次处理得到结果,因此应用两次map-reduce进行处理。找出共同好友

A:B,C,D,F,E,O
B:A,C,E,K
C:F,A,D,I
......

map1:生成按照逗号拆分成多组好友对应关系

B:A
C:A
....
A:B
C:B
....
F:C
A:C
...

reduce1:组合成如下形式,注意冒号后面的字符要进行排序,这是为了后面处理不会出现A-C和C-A成为两组的情况

A:B,C,D,E
B:A,C,E
C:A,B,O
...

此时还不是最终结果,但可以大致看出C-E共同好友有A和B等,因此再进一步处理

map2:将reduce1结果传入到map中,组成如下形式

B-C:A
C-D:A
...
A-C:B
A-E:B
...

reduce2:最终找到共同好友

A-C:B,D,E
B-D:A,E,F
.....

应用两套map-reduce得到最终结果

配置上只需要

public static void main(String[] args) throws Exception {
		Configuration conf = new Configuration();
		
		Job job1 = Job.getInstance(conf);
		
		job1.setJarByClass(CommonFriend.class);
		
		job1.setMapperClass(FriendMapper.class);
		job1.setReducerClass(FriendReducer.class);
		
		job1.setOutputKeyClass(Text.class);
		job1.setOutputValueClass(Text.class);
		
		FileInputFormat.setInputPaths(job1, new Path("/mapreduce/commonfriend"));
		FileOutputFormat.setOutputPath(job1, new Path("/mapreduce/commonpass"));
		
		boolean flag = job1.waitForCompletion(true);
		
		if(flag) {

			Job job2 = Job.getInstance(conf);
			
			job2.setJarByClass(CommonFriend.class);
			
			job2.setMapperClass(PeopleMapper.class);
			job2.setReducerClass(PeopleReducer.class);
			
			job2.setOutputKeyClass(Text.class);
			job2.setOutputValueClass(Text.class);

			FileInputFormat.setInputPaths(job2, new Path("/mapreduce/commonpass/part-r-00000"));
			FileOutputFormat.setOutputPath(job2, new Path("/mapreduce/commonout"));
			
			boolean flag1 = job2.waitForCompletion(true);
			System.exit(flag1?0:1);
		}
		
		
	}

2、排序分组

有时业务数据需要对一组拥有多元素的数据进行分组排序处理,或者按照每组取到其中最大值

例如

Order_0000001	Pdt_01	222.8
Order_0000001	Pdt_05	25.8
Order_0000002	Pdt_03	522.8
Order_0000002	Pdt_04	122.4
Order_0000002	Pdt_05	722.4
Order_0000003	Pdt_01	222.8

按照id及最后的money进行排序,那么此时就需要进行分组处理

实现方法是:

1)将每条数据看做是一个bean

2)在map端先按照orderid进行排序,按照money再进行排序

3)根据orderid不同分到不同分区

4)在reduce端根据orderid相同的为一组

代码实现主要进行两部分处理

第一部分:map端分组,首先要将每条数据定义为一个bean,每个bean实现WritableComparable类,并重写其中compareTo方法

	public int compareTo(Oderbean o) {
		int cmp = this.orderId.compareTo(o.getOrderId());
		if(cmp == 0) {
			cmp = -this.money.compareTo(o.getMoney());
		}
		return 0;
	}

该部分作用就是在进行partition过程是按照这种规则放到一个分区中

如果数据很乱,例如

Order_0000001	Pdt_01	222.8
Order_0000002	Pdt_03	522.8
Order_0000001	Pdt_05	25.8
Order_0000003	Pdt_01	222.8

partition后顺序就有了变化

Order_0000001	Pdt_05	25.8
Order_0000001	Pdt_01	222.8
Order_0000002	Pdt_03	522.8
Order_0000003	Pdt_01	222.8

当然分区多的时候数据就拆分到多个文件中了

第二部分:reduce端分组,在进行map端排序分区之后,reduce还需要对数据进行分组取top

此时需要实现如下部分

static class TestGroup extends WritableComparator {
		
		protected TestGroup () {
			super(Testbean.class,true);
		}
		
		@SuppressWarnings("rawtypes")
		@Override
		public int compare(WritableComparable a, WritableComparable b) {
			Testbean o1 = (Testbean) a;
			Testbean o2 = (Testbean) b;
			
			return o1.getOrderId().compareTo(o2.getOrderId());
		}
	}

该部分就是确定哪些id是一组,因此便于reduce确定(k,[v, v, v,])哪些k和哪些v放在一组

最后就是取topn就容易多了

3、输入小文件合并自定制输入

有时在输入目录存在很多小文件,那么会形成很多map分片,影响处理速度,当然该种方法已经有实现,我们可以应用该方法进行我们需要的输入端数据处理,如对文件按照文件名分组多文件目标输出等,当然这部分还要结合输出定制,后面写

用于对输入定制,也非常简单

大致分为两部分:

第一部分:

定义一个自定义类继承RecordReader类,该类就是将文件内容拆分成k,v对传递给map进行处理

根据这种机制,我们可以先map一步把文件内容进行处理,处理方法在nextKeyValue中实现

下面做的就是把小文件以byte形式每个文件成为一个以(文件名,文件内容byte形式)传递给map处理

public class TestReader extends RecordReader{

	private FileSplit file;
	private boolean process = false;
	private Configuration conf;
	private BytesWritable value = new BytesWritable();
	
	
	@Override
	public void close() throws IOException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public NullWritable getCurrentKey() throws IOException, InterruptedException {
		// TODO Auto-generated method stub
		return NullWritable.get();
	}

	@Override
	public BytesWritable getCurrentValue() throws IOException, InterruptedException {
		// TODO Auto-generated method stub
		return value;
	}

	@Override
	public float getProgress() throws IOException, InterruptedException {
		// TODO Auto-generated method stub
		return process?1.0f:0.0f;
	}

	@Override
	public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
		this.file = (FileSplit) split;
		this.conf = context.getConfiguration();		
	}

	@Override
	public boolean nextKeyValue() throws IOException, InterruptedException {
		if(!process) {
			byte[] contents = new byte[(int) file.getLength()];
			Path path = file.getPath();
			FileSystem fs = FileSystem.get(conf);
			FSDataInputStream in = null;
			try {
				in = fs.open(path);
				IOUtils.read(in, contents, 0, contents.length);
				value.set(contents,0,contents.length);
			}catch(Exception e) {
				IOUtils.closeQuietly(in);;
			}
			process = false;
			return true;
		}
		return false;
	}

}

第二部分:我们做的RecordReader的目的是要实现真正读类中的功能,下面要做的是自定义类实现文件读取类FileInputFormat

这部分书写方法大致相同,isSplitable确定是不是分片,小文件不用分片,createRecordReader用什么方法组成k,v传给map

public class TestInput extends FileInputFormat{

	@Override
	protected boolean isSplitable(JobContext context, Path filename) {
		// TODO Auto-generated method stub
		return false;
	}
	
	@Override
	public RecordReader createRecordReader(InputSplit split, TaskAttemptContext context)
			throws IOException, InterruptedException {
		// TODO Auto-generated method stub
		TestReader testReader = new TestReader();
		TestReader.initialize(split, context);
		return testReader;
	}

}

4、多路径输出自定制输出

有时候我们想根据数据内容将不同内容输出到不同的结果中,不想生成文件名为part-r-xxxxx,此时我们可以通过定制输出

定制输出和输出思路大致相同,进行两部分定制

第一部分:自定制RecordWriter

该部分比较简单,粘贴代码

public class OutputWrite extends RecordWriter {

		FSDataOutputStream fileOutOne = null;
		FSDataOutputStream fileOutTwo = null;
		
		public OutputWrite(FSDataOutputStream fileOutOne, FSDataOutputStream fileOutTwo) {
			this.fileOutOne = fileOutOne;
			this.fileOutTwo  = fileOutTwo;
		}
		
		@Override
		public void close(TaskAttemptContext context) throws IOException, InterruptedException {
			if(fileOutOne != null) {
				fileOutOne.close();
			}
			
			if(fileOutTwo != null) {
				fileOutTwo.close();
			}
			
		}

		@Override
		public void write(Text key, NullWritable value) throws IOException, InterruptedException {
			String line = key.toString();
			if(line.contains("hehe")) {
				fileOutOne.write(line.getBytes());
			} else {
				fileOutTwo.write(line.getBytes());
			}
		}
}

第二部分:自定制FileOutputFormat

public class OutputPersonnal extends FileOutputFormat {
	
		@Override
		public RecordWriter getRecordWriter(TaskAttemptContext context)
				throws IOException, InterruptedException {
			
			FileSystem fs = FileSystem.get(context.getConfiguration());
			
			Path path1 = new Path("f:/outputFormat1");
			Path path2 = new Path("f:/outputFormat2");
			
			FSDataOutputStream create1 = fs.create(path1);
			FSDataOutputStream create2 = fs.create(path2);
			
			return new OutputWrite(create1, create2);
		}
		
	}

5、mapjoin

有些时候我们需要对两个文件内容进行Join处理,当然进行hive处理的时候有mapjoin处理,这里我们实现mapjoin,应用该方法在map端通过多节点处理,可以减轻传入reduce数据,并省去reduce端数据处理

具体时间方式很简单

第一步:将进行join的小文件预先读取存入内存

第二步:将map读取的每一条与小文件内容进行join处理

实现代码,通过setup将内容预加载存到内存中,再进行处理

public class MapJoin {

	static class MapJoinMapper extends Mapper {
		
		Map product = new HashMap();
		Text k = new Text();
		
		@Override
		protected void setup(Context context)
				throws IOException, InterruptedException {
			  BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("little.txt")));
			  String line = null;
			  while(StringUtils.isNotEmpty(line = br.readLine())) {
				  String[] fields = line.split(",");
				  product.put(fields[0], fields[1]);
			  }
			  br.close();
		}
				
		@Override
		protected void map(LongWritable key, Text value, Context context)
				throws IOException, InterruptedException {
			String lines = value.toString();
			System.out.println(lines);
			String[] fieldss = lines.split("\t");
			String pname = product.get(fieldss[1]);
			k.set(lines + "\t" +pname );
			context.write(k, NullWritable.get());
		}
	}
}

 

你可能感兴趣的:(hadoop)