Hadoop之Mapreduce

MR执行过程-map阶段
  • map任务处理
    • 框架使用InputFormat类的子类把输入文件(夹)划分为很多InputSplit,默认,每个HDFS的block对应一个InputSplit。通过RecordReader类,把每个InputSplit解析成一个个<k1,v1>。默认,框架对每个 InputSplit中的每一行,解析成一个<k1,v1>。
    • 框架调用Mapper类中的map(...)函数,map函数的形参是<k1,v1>对,输出是<k2,v2>对。一个InputSplit对应一个map task。程序员可以覆盖map函数,实现自己的逻辑。
    • (假设reduce存在)框架对map输出的<k2,v2>进行分区。不同的分区中的<k2,v2>由不同的reduce task处理。默认只有1个分区。 (假设reduce不存在)框架对map结果直接输出到HDFS中。
    • (假设reduce存在)框架对每个分区中的数据,按照k2进行排序、分组。分组指的是相同k2的v2分成一个组。注意:分组不会减少<k2,v2>数量。
    • (假设reduce存在,可选)在map节点,框架可以执行reduce归约。
    • (假设reduce存在)框架会对map task输出的<k2,v2>写入到linux 的磁盘文件中。 至此,整个map阶段结束
  • shuffle过程
    • map_shuffle
      • 每个map有一个环形内存缓冲区,用于存储map的输出。默认大小100MB(io.sort.mb属性),一旦达到阀值0.8(io.sort.spill.percent),一个后台线程把内容溢写到(spilt)磁盘的指定目录(mapred.local.dir)下的一个新建文件中。
      • 写磁盘前,要partition,sort。如果有combiner,combine排序后数据。
      • 等最后记录写完,合并全部文件为一个分区且排序的文件。
    • reduce_shuffle
      • Reducer通过Http方式得到输出文件的特定分区的数据。
      • 排序阶段合并map输出。然后走Reduce阶段。
      • reduce执行完之后,写入到HDFS中。

 

 

  • MapReduce默认数据处理类
      • InputFormat 抽象类,只是定义了两个方法。
    • FileInputFormat
      • FileInputFormat是所有以文件作为数据源的InputFormat实现的基类,FileInputFormat保存作为job输入的所有文件,并实现了对输入文件计算splits的方法。至于获得记录的方法是有不同的子类——TextInputFormat进行实现的。
    • TextInputFormat
      • 是默认的处理类,处理普通文本文件
      • 文件中每一行作为一个记录,他将每一行在文件中的起始偏移量作为key,每一行的内容作为value
      • 默认以\n或回车键作为一行记录
 
  • RecordReader
    • 每一个InputSplit都有一个RecordReader,作用是把InputSplit中的数据解析成Record,即<k1,v1>。
    • 在TextInputFormat中的RecordReader是LineRecordReader,每一行解析成一个<k1,v1>。其中,k1表示偏移量,v1表示行文本内容
  • InputSplit
    • 在执行mapreduce之前,原始数据被分割成若干split,每个split作为一个map任务的输入。
    • 当Hadoop处理很多小文件(文件大小小于hdfs block大小)的时候,由于FileInputFormat不会对小文件进行划分,所以每一个小文件都会被当做一个split并分配一个map任务,会有大量的map task运行,导致效率底下
      • 例如:一个1G的文件,会被划分成8个128MB的split,并分配8个map任务处理,而10000个100kb的文件会被10000个map任务处理
    • Map任务的数量
      • 一个InputSplit对应一个Map task
      • InputSplit的大小是由Math.max(minSize, Math.min(maxSize,blockSize))决定
      • 单节点建议运行10—100个map task
      • map task执行时长不建议低于1分钟,否则效率低
    • 特殊:一个输入文件大小为140M,会有几个map task?1个
    • FileInputFormat类中的getSplits
 
  • 输入类-FileInputFormat-切片解析
if (isSplitable(job, path)) {
 
         long blockSize = file.getBlockSize();
 
         long splitSize = computeSplitSize(blockSize, minSize, maxSize);

 
         long bytesRemaining = length;
 
         while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
 
           int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
 
           splits.add(makeSplit(path, length-bytesRemaining, splitSize,
                        blkLocations[blkIndex].getHosts(),
 
                        blkLocations[blkIndex].getCachedHosts()));
  
          bytesRemaining -= splitSize;
 
         }

 

  • 什么是序列化,为什么要序列化
    • 序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
    • 当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为对象。
      • 把对象转换为字节序列的过程称为对象的序列化。
      • 把字节序列恢复为对象的过程称为对象的反序列化。
      • 说的再直接点,序列化的目的就是为了跨进程传递格式化数据
  • 编写MapReduce代码
    • map 继承mapper,重写map方法
    • 分区继承partitioner,重写getPartitioner方法
    • 排序实现WritableComparable接口,写word,num属性,实现write(),readFile()(这俩个就是序列化和反序列化),comparato
    • 方法
    • 规约Combiner实现reducer,重写reduce方法(实际就是做了reduce的工作)
    • reduce基础reducer,重写reduce方法
    • 主类:
      • //hadoop配置文件
                Configuration configuration = new Configuration();
                //创建job任务
                Job job = Job.getInstance(configuration);
                //获取job任务名称
                job.setJobName("MRPratition");
                //mian函数所在类
                job.setJarByClass(Mymapred.class);
                //获取map端
                job.setMapperClass(Mymap.class);
                //mapkey
                job.setMapOutputKeyClass(Text.class);
                //mapvalue
                job.setMapOutputValueClass(NullWritable.class);
                //分区
                job.setPartitionerClass(Mypartition.class);
        //规约
        job.setCombinerClass(Combiner.class);
        
                //获取reduce端
                job.setReducerClass(Myreduce.class);
                //k3
                job.setOutputKeyClass(Text.class);
                //v3
                job.setOutputValueClass(NullWritable.class);
                //reduce数目
                job.setNumReduceTasks(2);
                //指定读取路径
                job.setInputFormatClass(TextInputFormat.class);
                TextInputFormat.addInputPath(job,new Path("/MRpartition/partition.txt"));
                TextOutputFormat.setOutputPath(job,new Path("/MRpartition/part"));
                //开启任务
                job.waitForCompletion(true);

         

  • 整个mapreduce过程
    • Fileinputstream读取源文件,把输入文件(夹)划分为很多InputSplit(默认,每个HDFS的block对应一个InputSplit),每一个InputSplit都有一个RecordReader,作用是把InputSplit中的数据解析成Record,即<k1,v1>。
    • 每个InputSplit都有一个map task,程序员可以覆盖map函数,实现自己的逻辑。
    • 进入 map_shuffle阶段,map输入的数据会进入一个环形缓冲区,当达到一定条件(见2),会进行溢写操作(写入磁盘),在写入磁盘前,进行分区,排序(快排),规约->小文件->合并小文件->排序(归并)->临时文件,之后写入磁盘
    • 通过网络传输,进入reduce缓冲区(磁盘和内存中文件会混合)->溢写->小文件->合并->排序(归并),分组->中间文件->reducetask->输出

 

  • IDEA中显示mapredu程序过程配置文件 在resources文件夹中创建log4j.properties文件
  • # log4j.rootLogger日志输出类别和级别:只输出不低于该级别的日志信息DEBUG < INFO < WARN < ERROR < FATAL
    # A1是一个“形参”代表输出位置,具体的值在下面
    log4j.rootLogger=DEBUG, A1
    
    # 配置A1输出到控制台
    log4j.appender.A1=org.apache.log4j.ConsoleAppender
    
    # 配置A1设置为自定义布局模式
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    # 配置日志的输出格式 %r耗费毫秒数 %p日志的优先级 %t线程名 %C所属类名通常为全类名 %x线程相关联的NDC %m日志 %n换行
    log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

     

  • marpreduce中实现join

例:

package hadoop;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
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.output.FileOutputFormat;

import java.io.IOException;
import java.util.ArrayList;

public class Demo06Join {
    public static class JoinMapper extends Mapper<LongWritable, Text, Text, Text> {
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            //1 获取数据的路径
            InputSplit inputSplit = context.getInputSplit();
            FileSplit fs = (FileSplit) inputSplit;
            String url = fs.getPath().toString();
            if (url.contains("students")) {
                String id = value.toString().split(",")[0];
                String line = "*" + value.toString();
                context.write(new Text(id), new Text(line));
            } else {
                if (url.contains("score")) {
                    String id = value.toString().split(",")[0];
                    String line = "#" + value.toString();
                    context.write(new Text(id), new Text(line));
                }
            }
        }
    }

        public static class JoinReducer extends Reducer<Text,Text,Text,NullWritable>{
            @Override
            protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
                //数据在循环外保存
                String stuInfo="";
                ArrayList<String> scores = new ArrayList<String>();
                for (Text value : values) {
                    String line = value.toString();
                    if (line.startsWith("*")){
                        stuInfo = line.substring(1);
                    }else {
                        scores.add(line.substring(1));
                    }
                }


                // 数据拼接(成绩总分)
                long sum=0l;
                for (String score : scores) {
                    String sc = score.split(",")[2];
                    Integer scc = Integer.valueOf(sc);
                    sum+=scc;
                }
                String end = stuInfo+","+sum;
                context.write(new Text(end),NullWritable.get());

                //数据拼接 (各科成绩)
//                for (String score : scores) {
//                    String subject = score.split(",")[1];
//                    String s = score.split(",")[2];
//                    String end = stuInfo+","+subject+","+s;
//                    context.write(new Text(end),NullWritable.get());
//                }
            }
        }

        public static void main(String[] args) throws Exception{
            Job job = Job.getInstance();

            job.setJobName("Joinmapreduce");
            job.setJarByClass(Demo06Join.class);

            job.setMapperClass(JoinMapper.class);
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(Text.class);

            job.setReducerClass(JoinReducer.class);
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(NullWritable.class);

            FileInputFormat.addInputPath(job,new Path("/data"));
            FileOutputFormat.setOutputPath(job,new Path("/join"));

            job.waitForCompletion(true);
            System.out.println("join正在执行");
        }
    }

 

  • map_join
  • 之所以存在reduce side join,是因为在map阶段不能获取所有需要的join字段,即:同一个key对应的字段可能位于不同map中。Reduce side join是非常低效的,因为shuffle阶段要进行大量的数据传输。
  • Map side join是针对以下场景进行的优化:两个待连接表中,有一个表非常大,而另一个表非常小,以至于小表可以直接存放到内存中。这样,我们可以将小表复制多份,让每个map task内存中存在一份(比如存放到hash table中),然后只扫描大表:对于大表中的每一条记录key/value,在hash table中查找是否有相同的key的记录,如果有,则连接后输出即可。
  • 为了支持文件的复制,Hadoop提供了一个类DistributedCache,使用该类的方法如下:
    • 用户使用静态方法DistributedCache.addCacheFile()指定要复制的文件,它的参数是文件的URI(如果是HDFS上的文件,可以这样:hdfs://namenode:9000/home/XXX/file,其中9000是自己配置的NameNode端口号)。JobTracker在作业启动之前会获取这个URI列表,并将相应的文件拷贝到各个TaskTracker的本地磁盘上。
    • 用户使用DistributedCache.getLocalCacheFiles()方法获取文件目录,并使用标准的文件读写API读取相应的文件

例:

package BigData;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import sun.nio.cs.ext.SimpleEUCEncoder;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.logging.SimpleFormatter;

public class Demo1 {
    public static class CallMapper extends Mapper<LongWritable,Text,Text,NullWritable>{
        String name="";
        String name2="";
        String start="";
        String end="";
        String Time="";
        String locat1="";
        String locat2="";
        HashMap<String,String> people = new HashMap<String, String>();
        HashMap<String,String> locat = new HashMap<String, String>();

        public void bfUserPhone(){
            try {
                BufferedReader bf = new BufferedReader(new FileReader("E:\\IDEJAVA\\hadoop\\data\\userPhone.txt"));
                String line;
                while ((line=bf.readLine())!=null){
                    String[] split = line.split(",");
                    people.put(split[1],split[2]);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public void bfLocation(){
            try {
                BufferedReader bf = new BufferedReader(new FileReader("E:\\IDEJAVA\\hadoop\\data\\location.txt"));
                String line;
                while ((line=bf.readLine())!=null){
                    String[] split = line.split(",");
                    locat.put(split[1],split[2]);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            bfUserPhone();
            bfLocation();
            SimpleDateFormat sdf = new SimpleDateFormat("YY-MM-DD HH-mm-ss");

            String[] split = value.toString().split(",");
            name = people.get(split[0]);
            name2 = people.get(split[1]);

            start = sdf.format(Long.parseLong(split[2]));
            end = sdf.format(Long.parseLong(split[3]));

            Time = Long.parseLong(split[3])-Long.parseLong(split[2])+"秒";

            locat1 = locat.get(split[4]);
            locat2 = locat.get(split[5]);

            String s = name+","+name2+","+start+","+end+","+Time+","+locat1+","+locat2+",";

            context.write(new Text(s),NullWritable.get());
        }
    }

    public static void main(String[] args) throws Exception{
        Job job = Job.getInstance();
        job.setJobName("MapJoin");

        job.setNumReduceTasks(0);

        job.setMapperClass(CallMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(NullWritable.class);

        FileInputFormat.addInputPath(job,new Path("E:\\IDEJAVA\\hadoop\\data\\calls.txt"));
        FileOutputFormat.setOutputPath(job,new Path("E:\\IDEJAVA\\hadoop\\data\\output"));

        job.waitForCompletion(true);
        System.out.println("任务执行");
    }
}

 

 

 

posted @ 2021-09-24 19:58  钟心意  阅读(188)  评论(0)    收藏  举报