day44-hadoop-mapreduce
hadoop-mapreduce
Reduce Join
reduce端合并的数据倾斜问题
处理的数据集大小不均,一部分大,一部分小
交给某个reduce的数据太大,导致无法解决
Map Join
Map Join适用于一张表十分小、一张表很大的场景
如果两张表都很大,就不适合使用mapjoin
思考:在Reduce端处理过多的表,非常容易产生数据倾斜。怎么办?
在Map端缓存多张表,提前处理业务逻辑,这样增加Map端业务,减少Reduce端数据的压力,尽可能的减少数据倾斜
1. 在Mapper的setup阶段,将文件读取到缓存集合中
2. 在驱动函数中加载缓存
缓存普通文件到Task运行节点
job.addCacheFile(new URI(""))
Map Join 案例
package lc.mapreduce.mapper.mapjoin;
import org.apache.hadoop.conf.Configuration;
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;
import java.io.IOException;
import java.net.URI;
public class MapJoinDriver {
// 通过IO的方式直接读取小表的数据
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
//
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
job.setJarByClass(MapJoinDriver.class);
// 关联mapper
job.setMapperClass(MapJoinMapper.class);
//
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
// 设置最终写的kv
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
// 设置输入输出的文件
FileInputFormat.setInputPaths(job, new Path("D:\\a测试文件\\input\\bigtable"));
FileOutputFormat.setOutputPath(job, new Path("D:\\a测试文件\\output\\mapjoin4"));
// 设置reduce的个数为0个
job.setNumReduceTasks(0);
// 设置小表的缓存数据
job.addCacheFile(URI.create("file:///D:/a测试文件/input/smalltable/pd.txt"));
// 提交job
job.waitForCompletion(true);
}
}
package lc.mapreduce.mapper.mapjoin;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.BufferedReader;
import java.io.Console;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
public class MapJoinMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
private Map<String,String> pdMap=new HashMap<>();
private Text outk=new Text();
/**
* 只会执行一次
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void setup(Mapper<LongWritable, Text, Text, NullWritable>.Context context) throws IOException, InterruptedException {
// 将小表的数据缓存到内存中
// 获取待缓存数据
URI[] cacheFiles = context.getCacheFiles();
URI uriFile=cacheFiles[0];// pd.txt
// 获取文件系统对象
FileSystem fs=FileSystem.get(context.getConfiguration());
FSDataInputStream open = fs.open(new Path(uriFile));
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(open,"utf-8"));
String line;
while ((line = bufferedReader.readLine()) != null) {
// 处理每行读取到的数据
// 01 小米
String[] split = line.split("\t");
pdMap.put(split[0],split[1]);
}
pdMap.forEach((x,y)->{
System.out.println(x+":"+y);
});
// 关闭流
IOUtils.closeStream(bufferedReader);
}
@Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, NullWritable>.Context context) throws IOException, InterruptedException {
// 正常处理大数据
// 获取到一行数据
// 1001 01 1
String row=value.toString();
// 切割
String[] split = row.split("\t");
// join
split[1]=pdMap.get(split[1]);
// 封装k
outk.set(split[0]+"\t"+split[1]+"\t"+split[2]);
// 写出
context.write(outk,NullWritable.get());
}
}
计数器应用
hadoop为每个作业维护若干内置计数器,以描述多项指标。例如:某些计数器记录已处理的字节数和记录数,使用户可监控已处理的输入数量和已产生的输出数据量
context.getCounter("Map join","setup").increment(1);
// context.getCount(groupName,counterName) 获取计数器对象,并且设置组和计数器名
// increment(1) 每次计数的增长
数据清洗(ETL)
在运行核心业务MapReduce程序之前,往往需要对数据进行清洗,清理掉不符合用户要求的数据。清理的过程往往只需要运行Mapper程序,不需要运行Reduce程序
满足业务需求的数据,就是干净的数据
不满足业务需求的数据,就是脏数据
数据清洗,就是对脏数据的一个清洗