在某些业务场景中,如找出公共好友的过程中,一组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);
}
}
有时业务数据需要对一组拥有多元素的数据进行分组排序处理,或者按照每组取到其中最大值
例如
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就容易多了
有时在输入目录存在很多小文件,那么会形成很多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;
}
}
有时候我们想根据数据内容将不同内容输出到不同的结果中,不想生成文件名为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);
}
}
有些时候我们需要对两个文件内容进行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());
}
}
}