三、shuffle的优化
1.减少溢写次数︰
a.增大缓冲区,实际过程中缓冲区的大小一般是在250~400M之间b.增大缓冲区阈值,同时增加了写入阻塞的风险–不建议
c.增加Combine的过程
2.可以考虑将Map的结果文件进行压缩,这个方案是在网络资源和CPU资源之
间的取舍
3.增加fetch线程的数量4.增大merge因子
InputFormat(格式处理【处理一行变处理多行】)
一、概述
1.InputFormat中定义了2个抽象方法∶
a. getSplits用于产生切片
b. createRecordReader产生输入流读取切片
2. InputFormat会把结果给到MapTask
3.实际过程中,如果需要自定义输入格式类,一般不是直接继承InputFormat而是继承它的子类FileInputFormat,这个子类中已经覆盖了getSplits方法,而只需要考虑如何读取数据即可
4.如果没有指定输入格式,那么默认使用的TextInputFormat。除了第一个切
片对应的MapTask意外,其余的MapTask都是从当前切片的第二行开始读取到下一个切片的第一个行【因为数据的完整性,读数据要读完整的,切则按规定的切】
案例:
package cn.tedu.authinput;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
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.output.FileOutputFormat;
public class AuthDriver {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "JobName");
job.setJarByClass(cn.tedu.authinput.AuthDriver.class);
job.setMapperClass(AuthMapper.class);
job.setReducerClass(AuthReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Score.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//指定输入格式
job.setInputFormatClass(AuthInputFormat.class);
FileInputFormat.setInputPaths(job, new Path("hdfs://192.168.253.129:9000/mr/score4/score4.txt"));
FileOutputFormat.setOutputPath(job, new Path("hdfs://192.168.253.129:9000/result/score4"));
if (!job.waitForCompletion(true))
return;
}
}
package cn.tedu.authinput;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import org.apache.hadoop.util.LineReader;
public class AuthInputFormat extends FileInputFormat<Text, Text>{
@Override
public RecordReader<Text, Text> createRecordReader(InputSplit arg0, TaskAttemptContext arg1)
throws IOException, InterruptedException {
return new AuthReader();
}
}
class AuthReader extends RecordReader<Text,Text>{
private LineReader reader;
private Text key=new Text();
private Text value=new Text();
private static final Text blank=new Text(" ");
//初始化
//在这个初始化方法中先获取到流
@Override
public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
//从切片中确定要读取的文件
FileSplit fSplit=(FileSplit) split;
//获取文件路径
Path p=fSplit.getPath();
//连接hdfs
FileSystem fs=FileSystem.get(URI.create(p.toString()),context.getConfiguration() );
//获取到针对文件的输入流
InputStream in = fs.open(p);
//将字节流转化为字符流----这个字符流最好能按行读
reader=new LineReader(in);
}
//读取文件
//如果读到了,则表示还有键和值需要处理
//如果没有读到,则表示已经没有数据了
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
//按照当前说法,这个方法,只需要试着读取三行
//如果读到了三行表示有数据需要处理,需要返回true
Text tmp=new Text();
//表示会将读到的这一行数据放入传的Text参数中
// readLine方法返回值表示读取的这一行的字节数
if(reader.readLine(tmp)==0)
return false;
//读取完第一行,需要将第一行的数据作为键来使用
key.set(tmp.toString());
//第二行和第三行拼接作为值来使用
if(reader.readLine(tmp)==0)
return false;
value.set(tmp.toString());
value.append(blank.getBytes(), 0, blank.getLength());
if(reader.readLine(tmp)==0)
return false;
value.append(tmp.getBytes(), 0, tmp.getLength());
return true;
}
@Override
public Text getCurrentKey() throws IOException, InterruptedException {
return key;
}
@Override
public Text getCurrentValue() throws IOException, InterruptedException {
return value;
}
//获取执行进度
@Override
public float getProgress() throws IOException, InterruptedException {
return 0;
}
@Override
public void close() throws IOException {
if(reader!=null)
reader.close();
key=null;
value=null;
}
}
package cn.tedu.authinput;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class AuthMapper
extends Mapper<Text, Text, Text, Score> {
public void map(Text key, Text value, Context context) throws IOException, InterruptedException {
//key tom
//value math 100 english 100
String[] arr=value.toString().split(" ");
Score s=new Score();
s.setMath(Integer.parseInt(arr[1]));
s.setEnglish(Integer.parseInt(arr[3]));
context.write(key, s);
}
}
package cn.tedu.authinput;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class AuthReducer extends Reducer<Text, Score, Text, IntWritable> {
public void reduce(Text key, Iterable<Score> values, Context context) throws IOException, InterruptedException {
int sum=0;
for (Score val : values) {
sum=val.getMath()+val.getEnglish();
}
context.write(key, new IntWritable(sum));
}
}
package cn.tedu.authinput;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.Writable;
public class Score implements Writable{
private int math;
private int english;
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
public int getEnglish() {
return english;
}
public void setEnglish(int english) {
this.english = english;
}
@Override
public String toString() {
return "Score [math=" + math + ", english=" + english + "]";
}
@Override
public void readFields(DataInput in) throws IOException {
this.math=in.readInt();
this.english=in.readInt();
}
@Override
public void write(DataOutput out) throws IOException {
out.writeInt(math);
out.writeInt(english);
}
}
5.多源输入下,允许输入不同格式的文件,但是文件格式可以不同Mapper类也可以不一样,但是最后交给Reducer处理的时候要一样(import org.apache.hadoop.mapreduce.lib.input.MultipleInputs;)
自定义输出文件名
conf.set("mapreduce.output.basename", "auth");
设置输出文件key 和 value之间的连接符,迷人是制表符
conf.set( "mapreduce.output.textoutputformat.separator","+++");
数据倾斜
一、概述
1.在开发MR程序时,可能遇到的数据分配不均匀,造成程序性能下降的问题,这个问题称之为数据倾斜问题
二、解决方案
1.如果是因为shuffle分配数据不均匀造成数据倾斜,重写parition均匀分配数据即可
2.如果是数据本身带有倾斜的特点,无法通过修改parition来解决倾斜问题,可以采取如下方案进行:
a.利用combiner减轻倾斜的情况
b.将造成倾斜的数据拿出单独处理
c.将—个mr拆分成多个mr降低倾斜造成的危害
d.多表联查
一、数据倾斜
1.数据本身就有倾斜特性,即日常生活中所产生的数据本身就是不均等的2实际过程中,绝大部分的数据倾斜都会产生在Reduce端
3. Map端产生倾斜的条件:多源输入、文件不可切且文件大小不均,map端的倾斜一旦产生,无法解决----如果真要解决,在特定条件下可以考虑缓存存根问题。
4. Reduce端的倾斜的本质是因为数据的倾斜性,但是直观原因是因为对数据进行了分类-分类规则往往是不可变的,所以在实际过程中往往考虑的是使用两阶段聚合–数据先打散再聚合
5000 | a | 5*1000 | a | |
---|---|---|---|---|
600 | A | 5*120 | A | |
1500 | + | 5*300 | + | |
扩展:
##map任务数据倾斜原理分析
对于map/reduce任务,数据倾斜一般出现在reduce阶段,后文在将对其进行着重分析,但map过程同样会出现数据倾斜。
map过程产生数据倾斜的原因只有一个——map任务读取不支持sp)littable的原始文件且原始文件大小不均匀,有个别文件特别大。比如部分文件大小为2G,部分文件只有2K,假设数据处理时间和文件大小成正比,那么处理2G大小文件的task的时间是2K大小文件的100万倍。
这里先解释一下“splittable”。以HDFS分帮式存储系统为例,splittable指的是一个文件是否可以被多个map同时读取,每个map读取文件的一部分数据。对于支持splittable的文件,若干文件由N个block组成,那么其可以被N个map任务同时处理,每个map处理一个block的数据,因此不管单个文件大小是多少,这种支持splittable的文件都不会产生数据倾斜。
##reduce任务数据倾斜产生场景
对于给定的未知数据,如果预先对数据特点进行分析,那么很容易发现所有可能产生数据倾斜的key,这也是本文在一开头就强调的处理大数据任务时要“优先分析数据特点”。那么现实生产环境中哪些场景下key可能会倾斜呢?根据我们的经验,主要有两大类场景︰
1)存在业务默认填充值【性别单选框默认是男或者女,默认填充】
如用户的imei在获取不到时被填充了默认值;如广告系统在请求不到广告时播放了默认的广告,这些默认的广告的订单号都相同;如某个业务字段当前只有一个可选值。
2)业务本身存在热点
如热播剧的广告曝光量会显著大于一般的剧;视频前贴片这个广告位类型的曝光量会显著大于其他广告位类型;国内的广告曝光量显著大于国外。
3)存在恶意数据
如同一个ID刷了海量广告曝光。
两阶段聚合案例
package cn.tedu.join;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.DoubleWritable;
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 JoinDriver {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "JobName");
job.setJarByClass(cn.tedu.join.JoinDriver.class);
job.setMapperClass(JoinMapper.class);
job.setReducerClass(JoinReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Order.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(DoubleWritable.class);
//将小文件缓存,处理大文件
URI[] uri={URI.create("hdfs://192.168.253.129:9000/mr/union/product.txt")};
job.setCacheFiles(uri);
//输入路径中给定的应该是大文件
FileInputFormat.setInputPaths(job, new Path("hdfs://192.168.253.129:9000/mr/union/order.txt"));
FileOutputFormat.setOutputPath(job, new Path("hdfs://192.168.253.129:9000/result/unionprice"));
if (!job.waitForCompletion(true))
return;
}
}
package cn.tedu.join;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
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, Text, Order> {
private Map<String, Order> map=new HashMap<>();
//在处理大文件的时候需要小文件中的数据
//那也就意味着在处理大文件之前需要先把小文件解析
//小文件解析一次之后放入内存中供我们进行查询
@Override
protected void setup(Mapper<LongWritable, Text, Text, Order>.Context context)
throws IOException, InterruptedException {
//先将小文件从缓存中取出来
URI file = context.getCacheFiles()[0];
//连接hdfs 读取小文件
FileSystem fs=FileSystem.get(file, context.getConfiguration());
//获取针对这个文件的输入流
InputStream in=fs.open(new Path(file.toString()));
//考虑将字节流转化为字符流---最好能按行读取
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
String line;
while((line=reader.readLine())!=null){
//1 chui 3999
String[] arr=line.split(" ");
Order o=new Order();
o.setProid(arr[0]);
o.setName(arr[1]);
o.setPrice(Double.parseDouble(arr[2]));
map.put(o.getProid(), o);
//System.out.println(map);
}
reader.close();
}
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1001 20170710 4 2
String[] arr = value.toString().split(" ");
Order o = new Order();
o.setOrderid(arr[0]);
o.setDate(arr[1]);
o.setProid(arr[2]);
o.setNum(Integer.parseInt(arr[3]));
o.setName(map.get(o.getProid()).getName());
o.setPrice(map.get(o.getProid()).getPrice());
context.write(new Text(o.getOrderid()),o);
}
}
package cn.tedu.join;
import java.io.IOException;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class JoinReducer extends Reducer<Text, Order, Text, DoubleWritable> {
public void reduce(Text key, Iterable<Order> values, Context context) throws IOException, InterruptedException {
double sum=0;
for (Order val : values) {
sum=val.getNum()*val.getPrice();
}
context.write(key, new DoubleWritable(sum));
}
}
package cn.tedu.join;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.Writable;
public class Order implements Writable{
private String orderid;//商品id
private String date;
private String proid;//商品编号
private int num;//数量
private String name;
private double price;//单价
public String getOrderid() {
return orderid;
}
public void setOrderid(String orderid) {
this.orderid = orderid;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getProid() {
return proid;
}
public void setProid(String proid) {
this.proid = proid;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Order [orderid=" + orderid + ", date=" + date + ", proid=" + proid + ", num=" + num + ", name=" + name
+ ", price=" + price + "]";
}
@Override
public void readFields(DataInput in) throws IOException {
this.orderid=in.readUTF();
this.date=in.readUTF();
this.proid=in.readUTF();
this.num=in.readInt();
this.name=in.readUTF();
this.price=in.readDouble();
}
@Override
public void write(DataOutput out) throws IOException {
out.writeUTF(orderid);
out.writeUTF(date);
out.writeUTF(proid);
out.writeInt(num);
out.writeUTF(name);
out.writeDouble(price);
}
}
二、小文件
1.小文件的危害∶
a.存储:大量小文件会产生大量的元数据,就导致内存被大量占用
b.计算:大量小文件就产生大量的切片,大量切片则意味着有大量的MapTask,会导致服务器的执行效率变低甚至会导致服务器崩溃
2.针对小文件的处理手段常见的有2种∶合并和压缩
Hadoop提供了一种原生的合并手段:Hadoop Archive,将多个小文件打成一个har包
合并txt目录下的所有小文件到根目录下的result下,名命为txt.har
hadoop archive -archiveName txt.har -p /txt /result
案例:隐藏好友
package cn.tedu.friend;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
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.output.FileOutputFormat;
public class FriendDriver {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "JobName");
job.setJarByClass(cn.tedu.friend.FriendDriver.class);
job.setMapperClass(FriendMapper.class);
job.setReducerClass(FriendReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
FileInputFormat.setInputPaths(job, new Path("hdfs://192.168.253.129:9000/result/friend/"));
FileOutputFormat.setOutputPath(job, new Path("hdfs://192.168.253.129:9000/result/friend2"));
if (!job.waitForCompletion(true))
return;
}
}
package cn.tedu.friend;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class FriendMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] arr=value.toString().split("\t");
context.write(new Text(arr[0]), new IntWritable(Integer.parseInt(arr[1])));
}
}
package cn.tedu.friend;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class FriendReducer extends Reducer<Text, IntWritable, Text, Text> {
public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
// process values
for (IntWritable val : values) {
if(val.get()==1)
return;
}
String[] arr=key.toString().split("-");
context.write(new Text(arr[0]), new Text(arr[1]));
}
}
package cn.tedu.friend;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
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.output.FileOutputFormat;
public class RelationDriver {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "JobName");
job.setJarByClass(cn.tedu.friend.RelationDriver.class);
job.setMapperClass(RelationMapper.class);
job.setReducerClass(RelationReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.setInputPaths(job, new Path("hdfs://192.168.253.129:9000/mr/friend/friend.txt"));
FileOutputFormat.setOutputPath(job, new Path("hdfs://192.168.253.129:9000/result/friend"));
if (!job.waitForCompletion(true))
return;
}
}
package cn.tedu.friend;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class RelationMapper extends Mapper<LongWritable, Text, Text, Text> {
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] arr=value.toString().split(" ");
context.write(new Text(arr[0]), new Text(arr[1]));
}
}
package cn.tedu.friend;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class RelationReducer extends Reducer<Text, Text, Text, IntWritable> {
public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
//tom
//value=rose jim smith lucy
String name=key.toString();
List<String> fs=new ArrayList<String>();
//记录真实好友
//在MapReducer中,这个迭代器只能被遍历一次
for (Text val : values) {
String f=val.toString();
fs.add(f);
if(name.compareTo(f)<0)
context.write(new Text(name+"-"+f), new IntWritable(1));
else
context.write(new Text(f+"-"+name), new IntWritable(1));
}
//根据好友列表来推测关系
for (int i = 0; i < fs.size()-1; i++) {
for (int j = i+1; j < fs.size(); j++) {
String f1=fs.get(i);
String f2=fs.get(j);
if(f1.compareTo(f2)<0)
context.write(new Text(f1+"-"+f2), new IntWritable(0));
else
context.write(new Text(f2+"-"+f1), new IntWritable(0));
}
}
}
}