1、Shuffle机制的大致讲解
1)、数据在环形缓冲区中排序的流程
在环形缓冲区的数据是已经序列化好的数据,在此缓冲区内compare去比较这两个数,得到一个结果(可能需要交换也可能不需要交换,如果交换的话),再按照此结果去交换这两个数的索引(数据的长度是不一定的,索引的长度是固定的),执行此过程后写出来的数据就是排完序的数据;
分区排序一并完成:先按照分区号排序,之后再按照K值大小排序。(二次排序)
2)、默认大小和上限
环形缓冲区的默认大小为100M,上限为80%
3)、第一次使用Combiner(可选)
将文件进行一个初步的合并,减小文件的大小,从而减小整个磁盘的IO负担
4)、归并处理
多个溢写文件合并成一个文件(如果采用分区就会按照分区归并)
5)、第二次使用Combiner(可选)
经过combiner的合并处理后,最终将文件放在磁盘上(这样放在磁盘上的文件就是分区且区内有序的文件)
注意:这里只是一个Maptask的输出
6)、Reduce拉取数据并归并操作
reduce会主动去maptask中拉取数据(下载自己对应分区的数据),先下载到内存缓存中(如果空间够就直接归并操作),如果内存缓存不够再写到磁盘上(归并操作)
注意:从maptask下载数据会有一个并行的上限;有几个ReduceTask就有几个分区
7)、归并结束按照key分组
全部归并结束后再按照相同的key分组输入Reduce方法内部
2、OutputFormat数据输出
1)、默认
2)、自定义OutputFormat案例实操
①、需求
过滤输入的log日志,包含atguigu的网站输出到e:/atguigu.log,不包含atguigu的网站输出到e:/other.log。
②、需求分析
③、代码实现
a、MyOutputFormat.java
public class MyOutputFormat extends FileOutputFormat<LongWritable, Text> {
/**
* 返回一个处理数据的Record Writer
*
* @param job
* @return
* @throws IOException
* @throws InterruptedException
*/
@Override
public RecordWriter<LongWritable, Text> getRecordWriter(TaskAttemptContext job) throws IOException, InterruptedException {
return new MyRecordWriter(job);
}
}
b、MyRecordWriter.java
/**
* 将数据按照包不包含atguigu,分别输出到两个文件
*/
public class MyRecordWriter extends RecordWriter<LongWritable, Text> {
FSDataOutputStream atguigu = null;
FSDataOutputStream other = null;
public MyRecordWriter(TaskAttemptContext job) throws IOException {
Configuration configuration = job.getConfiguration();
//获取文件的输出位置
String outDir = configuration.get(FileOutputFormat.OUTDIR);
FileSystem fileSystem = FileSystem.get(configuration);
atguigu = fileSystem.create(new Path(outDir + "/atguigu.log"));
other = fileSystem.create(new Path(outDir + "/other.log"));
}
/**
* 接收key value对,并按照值的不同写出到不同的文件
*
* @param key 读取的一行的偏移量
* @param value 这一行的内容
* @throws IOException
* @throws InterruptedException
*/
@Override
public void write(LongWritable key, Text value) throws IOException, InterruptedException {
//获取一行的内容
String line = value.toString() + "\n";
//判断包不包含atguigu
if (line.contains("atguigu")) {
//在atguigu文件写数据
atguigu.write(line.getBytes());
} else {
//在other文件写数据
other.write(line.getBytes());
}
}
/**
* 关闭资源
*
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
public void close(TaskAttemptContext context) throws IOException, InterruptedException {
IOUtils.closeStream(atguigu);
IOUtils.closeStream(other);
}
}
c、OutputDriver.java
public class OutputDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Job job = Job.getInstance(new Configuration());
job.setJarByClass(OutputDriver.class);
job.setOutputFormatClass(MyOutputFormat.class);
FileInputFormat.setInputPaths(job, new Path("d:/DATA/input"));
FileOutputFormat.setOutputPath(job, new Path("d:/DATA/output"));
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
d、待处理文件(log.txt)
http://www.baidu.com
http://www.google.com
http://cn.bing.com
http://www.atguigu.com
http://www.sohu.com
http://www.sina.com
http://www.sin2a.com
http://www.sin2desa.com
http://www.sindsafa.com
3、Reduce Join
1)、工作原理
2)、Reduce Join案例实操
①需求
将商品信息表中数据根据商品pid合并到订单数据表中。最终格式为下表:
id | pname | amount |
---|---|---|
1001 | 小米 | 1 |
1004 | 小米 | 4 |
1002 | 华为 | 2 |
1005 | 华为 | 5 |
1003 | 格力 | 3 |
1006 | 格力 | 6 |
②、需求分析
③、代码实现
a、OrderBean.java
public class OrderBean implements WritableComparable<OrderBean> {
private String id;
private String pid;
private int amout;
private String pname;
@Override
public String toString() {
return id + '\t' + pname + '\t' + amout;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public int getAmout() {
return amout;
}
public void setAmout(int amout) {
this.amout = amout;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
/**
* 按照pid分组,组内按照Pname降序排列
* @param o
* @return
*/
@Override
public int compareTo(OrderBean o) {
int i = this.pid.compareTo(o.pid);
if (i != 0) {
return i;
} else {
return o.pname.compareTo(this.pname);
}
}
@Override
public void write(DataOutput out) throws IOException {
out.writeUTF(id);
out.writeUTF(pid);
out.writeInt(amout);
out.writeUTF(pname);
}
@Override
public void readFields(DataInput in) throws IOException {
this.id = in.readUTF();
this.pid = in.readUTF();
this.amout = in.readInt();
this.pname = in.readUTF();
}
}
b、OrderMapper.java
public class OrderMapper extends Mapper<LongWritable, Text, OrderBean, NullWritable> {
private OrderBean order = new OrderBean();
private String filename;
@Override
protected void setup(Context context) throws IOException, InterruptedException {
//获取输入文件的文件名
FileSplit fs = (FileSplit) context.getInputSplit();
filename = fs.getPath().getName();
}
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//切分一行内容
String[] fileds = value.toString().split("\t");
//封装
if ("order.txt".equals(filename)) {
//封装order
order.setId(fileds[0]);
order.setPid(fileds[1]);
order.setAmout(Integer.parseInt(fileds[2]));
order.setPname("");
} else {
//封装pd
order.setPid(fileds[0]);
order.setPname(fileds[1]);
order.setId("");
order.setAmout(0);
}
context.write(order, NullWritable.get());
}
}
c、OrderComparator.java
/**
* 分组比较器
*/
public class OrderComparator extends WritableComparator {
//关于true的说明是为了生成新的对象
protected OrderComparator() {
super(OrderBean.class, true);
}
/**
* 按照pid分组比较a和b
* @param a
* @param b
* @return
*/
@Override
public int compare(WritableComparable a, WritableComparable b) {
OrderBean oa = (OrderBean) a;
OrderBean ob = (OrderBean) b;
return oa.getPid().compareTo(ob.getPid());
}
}
d、OrderReducer.java
public class OrderReducer extends Reducer<OrderBean, NullWritable, OrderBean, NullWritable> {
/**
* 收到的数据,pd的一行在开头,order的紧随其后
*
* @param key
* @param values
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(OrderBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
//拿到迭代器
Iterator<NullWritable> iterator = values.iterator();
//迭代器第一组
iterator.next();
String pname = key.getPname();
//迭代剩下的数据写入并输出
while (iterator.hasNext()) {
iterator.next();
//剩下的数据没有pname,直接设置第一行的pname
key.setPname(pname);
context.write(key, NullWritable.get());
}
}
}
e、OrderDriver.java
public class OrderDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Job job = Job.getInstance(new Configuration());
job.setJarByClass(OrderBean.class);
job.setMapperClass(OrderMapper.class);
job.setReducerClass(OrderReducer.class);
job.setGroupingComparatorClass(OrderComparator.class);
job.setMapOutputKeyClass(OrderBean.class);
job.setMapOutputValueClass(NullWritable.class);
job.setOutputKeyClass(OrderBean.class);
job.setOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job, new Path("d:/DATA/input"));
FileOutputFormat.setOutputPath(job, new Path("d:/DATA/output"));
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
f、待处理文件
order.txt
1001 01 1
1002 02 2
1003 03 3
1004 01 4
1005 02 5
1006 03 6
pd.txt
01 小米
02 华为
03 格力
4、Map Join
1)、介绍
①、使用场景
Map Join适用于一张表十分小、一张表很大的场景
②、优点
在Map端缓存多张表,提前处理业务逻辑,这样增加Map端业务,减少Reduce端数据的压力,尽可能的减少数据倾斜
③、具体实施(DistributedCache)
a、缓存集合
在Mapper的setup阶段,将文件读取到缓存集合中
b、在驱动函数中加载缓存
job.addCacheFile(new URI("file://e:/cache/pd.txt"));
2)、Map Join案例实操
①、需求
将商品信息表中数据根据商品pid合并到订单数据表中(同reduce join实操相同文件)。
②、需求分析
③、代码实现
a、MJDriver.java
/**
* 将商品信息表中数据根据商品pid合并到订单数据表中。
*/
public class MJDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Job job = Job.getInstance(new Configuration());
job.setJarByClass(MJDriver.class);
job.setMapperClass(MJMapper.class);
//设置为0就不会有Reduce阶段
job.setNumReduceTasks(0);
//添加分布式缓存 file://指文件协议
job.addCacheFile(URI.create("file:///d:/DATA/input/pd.txt"));
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job, new Path("d:/DATA/input/order.txt"));
FileOutputFormat.setOutputPath(job, new Path("d:/DATA/output"));
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
b、MJMapper.java
public class MJMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
private Map<String, String> pMap = new HashMap<>();
private Text k = new Text();
@Override
protected void setup(Context context) throws IOException, InterruptedException {
//读取pd到pMap
//开流
URI[] cacheFiles = context.getCacheFiles();
FileSystem fileSystem = FileSystem.get(context.getConfiguration());
FSDataInputStream pd = fileSystem.open(new Path(cacheFiles[0]));
//将文件按行处理,读取到pMap中
BufferedReader br = new BufferedReader(new InputStreamReader(pd));
String line;
//这里为什么不写(line=br.readLine())!=null因为要判断line不等于null且这一行不为空
while (StringUtils.isNotEmpty(line = br.readLine())) {
String[] fileds = line.split("\t");
pMap.put(fileds[0], fileds[1]);
}
IOUtils.closeStream(br);
}
/**
* 处理order.txt的数据
*
* @param key
* @param value
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] fileds = value.toString().split("\t");
//将order.txt文件中的pid替换
k.set(fileds[0] + "\t" + pMap.get(fileds[1]) + "\t" + fileds[2]);
context.write(k, NullWritable.get());
}
}
c、待处理文件
同reduce join实例待处理文件
5、数据清洗(ETL)
1)、介绍
在运行核心业务MapReduce程序之前,往往要先对数据进行清洗,清理掉不符合用户要求的数据。清理的过程往往只需要运行Mapper程序,不需要运行Reduce程序。
一般最好清洗控制在5%以下,要不然得不偿失,清洗太多。
2)、数据清洗案例实操-简单解析版
①、需求
去除日志中字段个数小于等于11的日志。
②、需求分析
需要在Map阶段对输入的数据根据规则进行过滤清洗。
③、代码实现
a、ETLMapper.java
public class ETLMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
//计数器
private Counter pass;//记录所有成功通过验证的数
private Counter fail;//记录没有通过验证的数
/**
* 用来声明计数器
*
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void setup(Context context) throws IOException, InterruptedException {
pass = context.getCounter("ETL", "Pass");
fail = context.getCounter("ETL", "Fail");
}
/**
* 判断日志需不需要清洗
*
* @param key
* @param value 一行日志
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//切分
String[] fileds = value.toString().split(" ");
//只要长度大于11的数据
if (fileds.length > 11) {
context.write(value, NullWritable.get());
pass.increment(1);
} else {
fail.increment(1);
}
}
}
b、ETLDriver.java
public class ETLDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Job job = Job.getInstance(new Configuration());
job.setJarByClass(ETLDriver.class);
job.setMapperClass(ETLMapper.class);
job.setNumReduceTasks(0);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job, new Path("D:/DATA/input"));
FileOutputFormat.setOutputPath(job, new Path("D:/DATA/output"));
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
c、待处理文件(web.log)
文件内容过多,不宜展示,具体查看资源文件中的——关于Hadoop自学视频的对照文档
④、拓展(枚举类实现计数器)
a、枚举类ETL
public enum ETL {
PASS, FAIL
}
b、使用枚举类
只需要在原来的Mapper类中setup方法内修改如下代码即可
@Override
protected void setup(Context context) throws IOException, InterruptedException {
pass = context.getCounter(ETL.PASS);
fail = context.getCounter(ETL.FAIL);
}
6、MapReduce开发总结