1、原始數據
人員ID 人員名稱 地址ID
1 張三 1
2 李四 2
3 王五 1
4 趙六 3
5 馬七 3
另外一組爲地址信息:
地址ID 地址名稱
1 北京
2 上海
3 廣州
2、處理說明
該處理接着上一講,我們對這個實現進行了總結,最主要的問題就是實現的可擴展性,由於在reduce端我們通過一個List數據結構保存了所有的某個外鍵的對應的所有人員信息,
而List的最大值爲Integer.MAX_VALUE,所以 在數據量巨大的時候,會造成List越界的錯誤.所以對這個實現的優化顯得很有必要.
3、優化說明
結合第一種實現方式,我們看到第一種方式最需要改進的地方就是如果對於某個地址ID的迭代器values,如果values的第一個元素是地址信息的話,
那麼,我們就不需要緩存所有的人員信息了.如果第一個元素是地址信息,我們讀出地址信息後,後來就全部是人員信息,那麼就可以將人員的地址置爲相應的地址.
現在我們回頭看看mapreduce的partition和shuffle的過程,partitioner的主要功能是根據reduce的數量將map輸出 的結果進行分塊,將數據送入到相應的reducer,
所有的partitioner都必須實現Partitioner接口並實現getPartition 方法,該方法的返回值爲int類型,並且取值範圍在0-numOfReducer-1,
從而能夠將map的輸出輸入到相應的reducer中,對於某個 mapreduce過程,Hadoop框架定義了默認的partitioner爲HashPartition,
該Partitioner使用key的 hashCode來決定將該key輸送到哪個reducer;shuffle將每個partitioner輸出的結果根據key進行group以及排序,
將具有相同key的value構成一個valeus的迭代器,並根據key進行排序分別調用開發者定義的reduce方法進行歸併.
從shuffle的過 程我們可以看出key之間需要進行比較,通過比較才能知道某兩個key是否相等或者進行排序,
因此mapduce的所有的key必須實現 comparable接口的compareto()方法從而實現兩個key對象之間的比較.
回到我們的問題,我們想要的是將地址信息在排序的過程中排到最前面,前面我們只通過locId進行比較的方法就不夠用了,
因爲其無法標識出是地址表中的數據 還是人員表中的數據.因此,我們需要實現自己定義的Key數據結構,完成在想共同locId的情況下地址表更小的需求.
由於map的中間結果需要寫到磁盤 上,因此必須實現writable接口.具體實現如下:
4、構造用於排序的key
package cn.edu.bjut.jointwo;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.WritableComparable;
public class UserKey implements WritableComparable<UserKey> {
private int keyId;
private boolean isPrimary;
public void write(DataOutput out) throws IOException {
out.writeInt(keyId);
out.writeBoolean(isPrimary);
}
public void readFields(DataInput in) throws IOException {
this.keyId = in.readInt();
this.isPrimary = in.readBoolean();
}
public int compareTo(UserKey o) {
if(this.keyId == o.getKeyId()) {
if(this.isPrimary == o.isPrimary()) {
return 0;
} else {
return this.isPrimary ? 1 : -1;
}
} else {
return this.keyId > o.getKeyId() ? 1 : -1;
}
}
@Override
public int hashCode() { //partition 使用key的hashCode方法決定該記錄發往那個一reduce numOfReduce-1
return this.getKeyId();
}
public int getKeyId() {
return keyId;
}
public void setKeyId(int keyId) {
this.keyId = keyId;
}
public boolean isPrimary() {
return isPrimary;
}
public void setPrimary(boolean isPrimary) {
this.isPrimary = isPrimary;
}
}
5、構造用於group的比較器
有 了這個數據結構,我們又發現了一個新的問題——就是shuffle的group過程,shuffle的group過程默認使用的是key的 compareTo()方法.
剛纔我們添加的自定義Key沒有辦法將具有相同的locId的地址和人員放到同一個group中(因爲從compareTo 方法中可以看出他們是不相等的).
不過hadoop框架提供了OutputValueGoupingComparator可以讓使用者自定義key的 group信息.
我們需要的就是自己定義個groupingComparator就可以啦!看看這個比較器吧!
package cn.edu.bjut.jointwo;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
public class MyComparator extends WritableComparator {
protected MyComparator() {
super(UserKey.class, true);
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
UserKey a1 = (UserKey) a;
UserKey a2 = (UserKey) b;
if(a1.getKeyId() == a2.getKeyId()) {
return 0;
} else {
return a1.getKeyId() > a2.getKeyId() ? 1 : -1;
}
}
}
6Map程序:
package cn.edu.bjut.jointwo;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class JoinMapper extends Mapper<LongWritable, Text, UserKey, Member> {
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String line = value.toString();
String[] arr = line.split("\t");
if(arr.length >= 3) {
Member m = new Member();
m.setUserNo(arr[0]);
m.setUserName(arr[1]);
m.setCityNo(arr[2]);
UserKey userKey = new UserKey();
userKey.setKeyId(Integer.parseInt(arr[2]));
userKey.setPrimary(true);
context.write(userKey, m);
} else {
Member m = new Member();
m.setCityNo(arr[0]);
m.setCityName(arr[1]);
UserKey userKey = new UserKey();
userKey.setKeyId(Integer.parseInt(arr[0]));
userKey.setPrimary(false);
context.write(userKey, m);
}
}
}
7.Reduce程序:
package cn.edu.bjut.jointwo;
import java.io.IOException;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class JoinReducer extends Reducer<UserKey, Member, Text, NullWritable> {
@Override
protected void reduce(UserKey key, Iterable<Member> values, Context context)
throws IOException, InterruptedException {
Member m = null;
int num = 0;
for(Member member : values) {
if(0 == num) {
m = new Member(member);
} else {
Member tmp = new Member(member);
tmp.setCityName(m.getCityName());
context.write(new Text(tmp.toString()), NullWritable.get());
}
num++;
}
}
}
8.主程序:
package cn.edu.bjut.jointwo;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class MainJob {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = new Job(conf, "jointwo");
job.setJarByClass(MainJob.class);
job.setMapperClass(JoinMapper.class);
job.setMapOutputKeyClass(UserKey.class);
job.setMapOutputValueClass(Member.class);
job.setReducerClass(JoinReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
job.setGroupingComparatorClass(MyComparator.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
Path outPathDir = new Path(args[1]);
FileSystem fs = FileSystem.get(conf);
if(fs.exists(outPathDir)) {
fs.delete(outPathDir, true);
}
FileOutputFormat.setOutputPath(job, outPathDir);
job.waitForCompletion(true);
}
}