hadoop MapReduce 三種連線
為了實現內連線和外連線,MapReduce中有三種連線策略,如下所示。這三種連線策略有的在map階段,有的在reduce階段。它們都針對MapReduce的排序-合併(sort-merge)的架構進行了優化。
- 重分割槽連線(Repartition join)——reduce端連線。使用場景:連線兩個或多個大型資料集。
- 複製連線(Replication join)——map端連線。使用場景:待連線的資料集中有一個資料集小到可以完全放在快取中。
- 半連線(Semi-join)——另一個map端連線。使用場景:待連線的資料集中有一個資料集非常大,但同時這個資料集可以被過濾成小到可以放在記憶體中。
資料模型:
tb_dim_city.dat
0|其他|9999|9999|0
1|長春|1|901|1
2|吉林|2|902|1
3|四平|3|903|1
4|松原|4|904|1
5|通化|5|905|1
6|遼源|6|906|1
7|白城|7|907|1
8|白山|8|908|1
tb_user_profiles.dat
1|2G|123|1
2|3G|333|2
3|3G|555|1
4|2G|777|3
5|3G|666|4
reduce端連線:
package com.mr.reduceSideJoin;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable;
public class CombineValues implements WritableComparable<CombineValues> {
private Text joinKey;// 連結關鍵字
private Text flag;// 檔案來源標誌
private Text secondPart;// 除了連結鍵外的其他部分
public void setJoinKey(Text joinKey) {
this.joinKey = joinKey;
}
public void setFlag(Text flag) {
this.flag = flag;
}
public void setSecondPart(Text secondPart) {
this.secondPart = secondPart;
}
public Text getFlag() {
return flag;
}
public Text getSecondPart() {
return secondPart;
}
public Text getJoinKey() {
return joinKey;
}
public CombineValues() {
this.joinKey = new Text();
this.flag = new Text();
this.secondPart = new Text();
}
@Override
public void write(DataOutput out) throws IOException {
this.joinKey.write(out);
this.flag.write(out);
this.secondPart.write(out);
}
@Override
public void readFields(DataInput in) throws IOException {
this.joinKey.readFields(in);
this.flag.readFields(in);
this.secondPart.readFields(in);
}
@Override
public int compareTo(CombineValues o) {
return this.joinKey.compareTo(o.getJoinKey());
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "[flag=" + this.flag.toString() + ",joinKey="
+ this.joinKey.toString() + ",secondPart="
+ this.secondPart.toString() + "]";
}
}
package com.mr.reduceSideJoin;
import java.io.IOException;
import java.util.ArrayList;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
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.FileSplit;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ReduceSideJoin_LeftOuterJoin extends Configured implements Tool {
private static final Logger logger = LoggerFactory
.getLogger(ReduceSideJoin_LeftOuterJoin.class);
public static class LeftOutJoinMapper extends
Mapper<Object, Text, Text, CombineValues> {
private CombineValues combineValues = new CombineValues();
private Text flag = new Text();
private Text joinKey = new Text();
private Text secondPart = new Text();
@Override
protected void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
// 獲得檔案輸入路徑
String pathName = ((FileSplit) context.getInputSplit()).getPath()
.toString();
// 資料來自tb_dim_city.dat檔案,標誌即為"0"
if (pathName.endsWith("tb_dim_city.dat")) {
String[] valueItems = value.toString().split("\\|");
// 過濾格式錯誤的記錄
if (valueItems.length != 5) {
return;
}
flag.set("0");
joinKey.set(valueItems[0]);
secondPart.set(valueItems[1] + "\t" + valueItems[2] + "\t"
+ valueItems[3] + "\t" + valueItems[4]);
combineValues.setFlag(flag);
combineValues.setJoinKey(joinKey);
combineValues.setSecondPart(secondPart);
context.write(combineValues.getJoinKey(), combineValues);
}// 資料來自於tb_user_profiles.dat,標誌即為"1"
else if (pathName.endsWith("tb_user_profiles.dat")) {
String[] valueItems = value.toString().split("\\|");
// 過濾格式錯誤的記錄
if (valueItems.length != 4) {
return;
}
flag.set("1");
joinKey.set(valueItems[3]);
secondPart.set(valueItems[0] + "\t" + valueItems[1] + "\t"
+ valueItems[2]);
combineValues.setFlag(flag);
combineValues.setJoinKey(joinKey);
combineValues.setSecondPart(secondPart);
context.write(combineValues.getJoinKey(), combineValues);
}
}
}
public static class LeftOutJoinReducer extends
Reducer<Text, CombineValues, Text, Text> {
// 儲存一個分組中的左表資訊
private ArrayList<Text> leftTable = new ArrayList<Text>();
// 儲存一個分組中的右表資訊
private ArrayList<Text> rightTable = new ArrayList<Text>();
private Text secondPar = null;
private Text output = new Text();
/**
* 一個分組呼叫一次reduce函式;相同key的資料進了同一個reduce,這樣就實現了join。
*/
@Override
protected void reduce(Text key, Iterable<CombineValues> value,
Context context) throws IOException, InterruptedException {
leftTable.clear();
rightTable.clear();
/**
* 將分組中的元素按照檔案分別進行存放 這種方法要注意的問題: 如果一個分組內的元素太多的話,可能會導致在reduce階段出現OOM,
* 在處理分散式問題之前最好先了解資料的分佈情況,根據不同的分佈採取最
* 適當的處理方法,這樣可以有效的防止導致OOM和資料過度傾斜問題。
*/
for (CombineValues cv : value) {
secondPar = new Text(cv.getSecondPart().toString());
// 左表tb_dim_city
if ("0".equals(cv.getFlag().toString().trim())) {
leftTable.add(secondPar);
}
// 右表tb_user_profiles
else if ("1".equals(cv.getFlag().toString().trim())) {
rightTable.add(secondPar);
}
}
logger.info("tb_dim_city:" + leftTable.toString());
logger.info("tb_user_profiles:" + rightTable.toString());
// 這裡體現了左連線
for (Text leftPart : leftTable) {
for (Text rightPart : rightTable) {
output.set(leftPart + "\t" + rightPart);
// leftTable中有資料 rightTable中沒有資料 就無法進到這一步
// rightTable中有資料 leftTable中沒有資料 外面的迴圈就進不去
context.write(key, output);
}
}
}
}
@Override
public int run(String[] args) throws Exception {
Configuration conf = getConf(); // 獲得配置檔案物件
Job job = new Job(conf, "LeftOutJoinMR");
job.setJarByClass(ReduceSideJoin_LeftOuterJoin.class);
FileInputFormat.addInputPath(job, new Path(args[0])); // 設定map輸入檔案路徑
FileOutputFormat.setOutputPath(job, new Path(args[1])); // 設定reduce輸出檔案路徑
job.setMapperClass(LeftOutJoinMapper.class);
job.setReducerClass(LeftOutJoinReducer.class);
job.setInputFormatClass(TextInputFormat.class); // 設定檔案輸入格式
job.setOutputFormatClass(TextOutputFormat.class);// 使用預設的output格式
// 設定map的輸出key和value型別
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(CombineValues.class);
// 設定reduce的輸出key和value型別
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
job.waitForCompletion(true);
return job.isSuccessful() ? 0 : 1;
}
public static void main(String[] args) throws IOException,
ClassNotFoundException, InterruptedException {
try {
Tool rdf = new ReduceSideJoin_LeftOuterJoin();
int returnCode = ToolRunner.run(rdf, args);
System.exit(returnCode);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
Map端的連線
package com.mr.mapSideJoin;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.filecache.DistributedCache;
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.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MapSideJoinMain extends Configured implements Tool {
private static final Logger logger = LoggerFactory
.getLogger(MapSideJoinMain.class);
public static class LeftOutJoinMapper extends
Mapper<Object, Text, Text, Text> {
private HashMap<String, String> city_info = new HashMap<String, String>();
private Text outPutKey = new Text();
private Text outPutValue = new Text();
private String mapInputStr = null;
private String mapInputSpit[] = null;
private String city_secondPart = null;
/**
* 此方法在每個task開始之前執行,這裡主要用作從DistributedCache
* 中取到tb_dim_city檔案,並將裡邊記錄取出放到記憶體中。
*/
@Override
protected void setup(Context context) throws IOException,
InterruptedException {
BufferedReader br = null;
// 獲得當前作業的DistributedCache相關檔案
Path[] distributePaths = DistributedCache
.getLocalCacheFiles(context.getConfiguration());
String cityInfo = null;
for (Path p : distributePaths) {
if (p.toString().endsWith("tb_dim_city.dat")) {
// 讀快取檔案,並放到mem中
br = new BufferedReader(new FileReader(p.toString()));
while (null != (cityInfo = br.readLine())) {
String[] cityPart = cityInfo.split("\\|", 5);
if (cityPart.length == 5) {
city_info.put(cityPart[0], cityPart[1] + "\t"
+ cityPart[2] + "\t" + cityPart[3] + "\t"
+ cityPart[4]);
}
}
}
}
}
/**
* Map端的實現相當簡單,直接判斷tb_user_profiles.dat中的
* cityID是否存在我的map中就ok了,這樣就可以實現Map Join了
*/
@Override
protected void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
// 排掉空行
if (value == null || value.toString().equals("")) {
return;
}
mapInputStr = value.toString();
mapInputSpit = mapInputStr.split("\\|", 4);
// 過濾非法記錄
if (mapInputSpit.length != 4) {
return;
}
// 判斷連結欄位是否在map中存在
city_secondPart = city_info.get(mapInputSpit[3]);
if (city_secondPart != null) {
this.outPutKey.set(mapInputSpit[3]);
this.outPutValue.set(city_secondPart + "\t" + mapInputSpit[0]
+ "\t" + mapInputSpit[1] + "\t" + mapInputSpit[2]);
context.write(outPutKey, outPutValue);
}
}
}
@Override
public int run(String[] args) throws Exception {
Configuration conf = getConf(); // 獲得配置檔案物件
DistributedCache.addCacheFile(new Path(args[0]).toUri(), conf);// 為該job新增快取檔案
Job job = new Job(conf, "MapJoinMR");
job.setNumReduceTasks(0);
FileInputFormat.addInputPath(job, new Path(args[0])); // 設定map輸入檔案路徑
FileOutputFormat.setOutputPath(job, new Path(args[1])); // 設定reduce輸出檔案路徑
job.setJarByClass(MapSideJoinMain.class);
job.setMapperClass(LeftOutJoinMapper.class);
job.setInputFormatClass(TextInputFormat.class); // 設定檔案輸入格式
job.setOutputFormatClass(TextOutputFormat.class);// 使用預設的output格式
// 設定map的輸出key和value型別
job.setMapOutputKeyClass(Text.class);
// 設定reduce的輸出key和value型別
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
job.waitForCompletion(true);
return job.isSuccessful() ? 0 : 1;
}
public static void main(String[] args) throws IOException,
ClassNotFoundException, InterruptedException {
try {
int returnCode = ToolRunner.run(new MapSideJoinMain(), args);
System.exit(returnCode);
} catch (Exception e) {
// TODO Auto-generated catch block
logger.error(e.getMessage());
}
}
}
Semi連線
package com.mr.SemiJoin;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.filecache.DistributedCache;
import org.apache.hadoop.fs.Path;
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.FileSplit;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mr.reduceSideJoin.CombineValues;
public class SemiJoin extends Configured implements Tool {
private static final Logger logger = LoggerFactory
.getLogger(SemiJoin.class);
public static class SemiJoinMapper extends
Mapper<Object, Text, Text, CombineValues> {
private CombineValues combineValues = new CombineValues();
private HashSet<String> joinKeySet = new HashSet<String>();
private Text flag = new Text();
private Text joinKey = new Text();
private Text secondPart = new Text();
/**
* 將參加join的key從DistributedCache取出放到記憶體中,以便在map端將要參加join的key過濾出來。b
*/
@Override
protected void setup(Context context) throws IOException,
InterruptedException {
BufferedReader br = null;
// 獲得當前作業的DistributedCache相關檔案
Path[] distributePaths = DistributedCache
.getLocalCacheFiles(context.getConfiguration());
String joinKeyStr = null;
for (Path p : distributePaths) {
if (p.toString().endsWith("joinKey.dat")) {
// 讀快取檔案,並放到mem中
br = new BufferedReader(new FileReader(p.toString()));
while (null != (joinKeyStr = br.readLine())) {
joinKeySet.add(joinKeyStr);
}
}
}
}
@Override
protected void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
// 獲得檔案輸入路徑
String pathName = ((FileSplit) context.getInputSplit()).getPath()
.toString();
// 資料來自tb_dim_city.dat檔案,標誌即為"0"
if (pathName.endsWith("tb_dim_city.dat")) {
String[] valueItems = value.toString().split("\\|");
// 過濾格式錯誤的記錄
if (valueItems.length != 5) {
return;
}
// 過濾掉不需要參加join的記錄
if (joinKeySet.contains(valueItems[0])) {
flag.set("0");
joinKey.set(valueItems[0]);
secondPart.set(valueItems[1] + "\t" + valueItems[2] + "\t"
+ valueItems[3] + "\t" + valueItems[4]);
combineValues.setFlag(flag);
combineValues.setJoinKey(joinKey);
combineValues.setSecondPart(secondPart);
context.write(combineValues.getJoinKey(), combineValues);
} else {
return;
}
}// 資料來自於tb_user_profiles.dat,標誌即為"1"
else if (pathName.endsWith("tb_user_profiles.dat")) {
String[] valueItems = value.toString().split("\\|");
// 過濾格式錯誤的記錄
if (valueItems.length != 4) {
return;
}
// 過濾掉不需要參加join的記錄
if (joinKeySet.contains(valueItems[3])) {
flag.set("1");
joinKey.set(valueItems[3]);
secondPart.set(valueItems[0] + "\t" + valueItems[1] + "\t"
+ valueItems[2]);
combineValues.setFlag(flag);
combineValues.setJoinKey(joinKey);
combineValues.setSecondPart(secondPart);
context.write(combineValues.getJoinKey(), combineValues);
} else {
return;
}
}
}
}
public static class SemiJoinReducer extends
Reducer<Text, CombineValues, Text, Text> {
// 儲存一個分組中的左表資訊
private ArrayList<Text> leftTable = new ArrayList<Text>();
// 儲存一個分組中的右表資訊
private ArrayList<Text> rightTable = new ArrayList<Text>();
private Text secondPar = null;
private Text output = new Text();
/**
* 一個分組呼叫一次reduce函式
*/
@Override
protected void reduce(Text key, Iterable<CombineValues> value,
Context context) throws IOException, InterruptedException {
leftTable.clear();
rightTable.clear();
/**
* 將分組中的元素按照檔案分別進行存放 這種方法要注意的問題: 如果一個分組內的元素太多的話,可能會導致在reduce階段出現OOM,
* 在處理分散式問題之前最好先了解資料的分佈情況,根據不同的分佈採取最
* 適當的處理方法,這樣可以有效的防止導致OOM和資料過度傾斜問題。
*/
for (CombineValues cv : value) {
secondPar = new Text(cv.getSecondPart().toString());
// 左表tb_dim_city
if ("0".equals(cv.getFlag().toString().trim())) {
leftTable.add(secondPar);
}
// 右表tb_user_profiles
else if ("1".equals(cv.getFlag().toString().trim())) {
rightTable.add(secondPar);
}
}
logger.info("tb_dim_city:" + leftTable.toString());
logger.info("tb_user_profiles:" + rightTable.toString());
for (Text leftPart : leftTable) {
for (Text rightPart : rightTable) {
output.set(leftPart + "\t" + rightPart);
context.write(key, output);
}
}
}
}
@Override
public int run(String[] args) throws Exception {
Configuration conf = getConf(); // 獲得配置檔案物件
DistributedCache.addCacheFile(new Path(args[2]).toUri(), conf);
Job job = new Job(conf, "LeftOutJoinMR");
job.setJarByClass(SemiJoin.class);
FileInputFormat.addInputPath(job, new Path(args[0])); // 設定map輸入檔案路徑
FileOutputFormat.setOutputPath(job, new Path(args[1])); // 設定reduce輸出檔案路徑
job.setMapperClass(SemiJoinMapper.class);
job.setReducerClass(SemiJoinReducer.class);
job.setInputFormatClass(TextInputFormat.class); // 設定檔案輸入格式
job.setOutputFormatClass(TextOutputFormat.class);// 使用預設的output格式
// 設定map的輸出key和value型別
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(CombineValues.class);
// 設定reduce的輸出key和value型別
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
job.waitForCompletion(true);
return job.isSuccessful() ? 0 : 1;
}
public static void main(String[] args) throws IOException,
ClassNotFoundException, InterruptedException {
try {
int returnCode = ToolRunner.run(new SemiJoin(), args);
System.exit(returnCode);
} catch (Exception e) {
logger.error(e.getMessage());
}
}
}
只有reduce連線例項能在eclipse中執行,另外兩個必須打成JAR包放到hadoop上去執行。
參考:
參考:
http://zengzhaozheng.blog.51cto.com/8219051/1392961
相關文章
- MapReduce 示例:減少 Hadoop MapReduce 中的側連線Hadoop
- Hadoop面試題總結(三)——MapReduceHadoop面試題
- JDBC 連線oracle三種方法JDBCOracle
- 多個mapreduce連線例項
- Hadoop(三)通過C#/python實現Hadoop MapReduceHadoopC#Python
- Oracle的三種表連線方式Oracle
- MapReduce&&HadoopHadoop
- JDBC連線三種資料庫例子JDBC資料庫
- vmware中三種網路連線方式
- Hadoop學習——MapReduceHadoop
- hadoop_MapReduce yarnHadoopYarn
- Hadoop(十四)MapReduce概述Hadoop
- Hadoop 三劍客之 —— 分散式計算框架 MapReduceHadoop分散式框架
- Linux網路連線的三種方式Linux
- Hadoop面試題之MapReduceHadoop面試題
- [hadoop]mapreduce原理簡述Hadoop
- hadoop的mapreduce常見演算法案例有幾種Hadoop演算法
- 【SQL 效能優化】表的三種連線方式SQL優化
- 三種表連線方式 最佳化總結
- SQL的四種連線:內連線 左外連線 右外連線 全連線SQL
- Hadoop 專欄 - MapReduce 入門Hadoop
- 淺談SQL Server中的三種物理連線操作SQLServer
- 【SQL 效能最佳化】表的三種連線方式SQL
- Hadoop(五)C#連線HiveHadoopC#Hive
- 兩表連線三:合併連線
- 表的三種連線方式官方解釋及個人理解
- 使用者連線到oracle的三種驗證方式Oracle
- merge into三種表連線方式的效能比較(一)
- hadoop的mapreduce串聯執行Hadoop
- Hadoop學習之YARN及MapReduceHadoopYarn
- 使用hadoop mapreduce分析mongodb資料HadoopMongoDB
- Hadoop 新 MapReduce 框架 Yarn 詳解Hadoop框架Yarn
- 深入理解SQL的四種連線-左外連線、右外連線、內連線、全連線SQL
- 各種連線資料庫的連線字串資料庫字串
- SQL 三表連線SQL
- Oracle的表連線方法(三)雜湊連線Oracle
- oracle sql內連線_左(右)連線_全外連線_幾種寫法OracleSQL
- VMware虛擬機器下網路連線的三種模式虛擬機模式