Hadoop底层原理、Hadoop配置、Hadoop命令 和 Hadoop API 的基本使用、Hadoop案例代码

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)


大数据组件使用 总文章

================= Hadoop底层原理 ================== 

1.客户端执行hdfs fs put 本地文件系统中的文件路径 hdfs文件系统中的目录路径:hdfs fs put ./a.txt / 发送上传请求给namenode。
2.namenode根据元数据中的文件系统目录树 检测是否存在“该指定的接收上传文件的”目录,检测成功则返回成功信息给客户端。
3.客户端根据上传文件被分为多少份文件块,向namenode请求获取对应多少个datanode,每个datanode负责接收一份文件块。
4.namenode根据元数据中的datanode信息池 检测出可用的N台datanode,每个datanode负责接收一份文件块,namenode把可用的N台datanode的IP地址信息返回给客户端,
  每台datanode根据所离客户端的距离远近进行排序插入到一个列表中,离客户端距离最近的datanode排在最前面,比如顺序是 datanode1、datanode2、datanode3。
5.客户端根据离自己最近的datanode逐一建立pipeline管道连接,客户端 和 每个datanode 建立成一条 pipeline管道链接是:客户端 --> datanode1 --> datanode2 --> datanode3。
6.客户端每发送完一个文件块,其所存储该文件块的某个datanode就会返回 ack(命令正确应答)给客户端。
    1.客户端给某个datanode发送文件块时,按照在建立好的 pipeline管道链接中查找对应保存该文件块的datanode,
      逐级把文件块从一个datanode传输到另外一个databode,最终保存到指定保存该文件块的datanode中。
    2.当某个datanode保存完一个文件块后,该datanode返回 ack(命令正确应答),并按照建立好的 pipeline管道链接进行 原路返回ack(命令正确应答)给客户端,
      客户端才会继续发送下一个文件块。
7.所有datanode保存完所有的文件块并数据校验完整之后,返回成功信息给客户端


1.客户端执行hadoop fs -get hdfs文件系统中的文件路径 本地文件系统中的目录路径:hadoop fs -get /a.txt /root/ 
  客户端发送请求给namenode,请求获取文件
2.namenode根据客户端所请求下载的文件路径,到hdfs文件系统中找到对应的文件是否存在,存在则返回该文件对应的每个文件块所存储在的每个datanode的IP地址信息,
  和 同时包括返回该datanode中保存的对应该文件的所有的每个文件块,每个datanode的IP地址信息按照离客户端的距离远近进行排序插入到一个列表中,
  距离客户端近的datanode则把他的IP地址信息插在前面。
3.客户端则根据每个datanode的IP地址信息到对应的datanode中取出对应该文件的所有的每个文件块,最终进行合并校验完整。


 

1.Reduce阶段 默认只有一个ReduceTask,那么Reduce阶段只输出一个文件
2.job.setNumReduceTasks(N):设置ReduceTask的个数,那么Reduce阶段输出的文件个数也为N,和ReduceTask的个数相同
3.Map阶段的多个MapTask各自输出的<key,value>根据“key.hashcode % ReduceTask个数”的规则,决定把<key,value>输出到哪个ReduceTask中,那么也即存储在哪个输出文件中。
  根据“key.hashcode % ReduceTask个数”规则所展现的效果显示: 相同key的 <key,value>都会被分配到同一个ReduceTask中,并且在同一个输出文件中。
4.切片大小默认等于块大小,即对文件以128M为一个block块进行切分,切片个数决定了MR程序启动多少个MapTask。

5.Map阶段(MapTask):map(起始偏移量key, 这一行内容value, Context context){ context.write(new Text(单词),new IntWritable(1)); # <单词,1> }
    1.第一步:write到内存缓冲区,直到溢出后存储到磁盘中(带有IO)。
         并且根据 “job.setNumReduceTasks(N)所设置ReduceTask个数的”规则 在磁盘中 产生相同的分区数,分区规则同为“key.hashcode % ReduceTask个数”,
         把相同key的 <key,value>都分配到同一个磁盘中的分区中,然后按照key的字典序对多个<key,value>进行排序。
         并且磁盘中每个分区各自对应一个ReduceTask,比如磁盘中第一个分区对应第一个ReduceTask,如此类推。
    2.第二步:磁盘中每个分区各自把<key,value>输出到对应的ReduceTask,比如磁盘中第一个分区输出数据到第一个ReduceTask,如此类推。

6.Reduce阶段(ReduceTask):reduce(单词keyIn, Iterable [1,1,...], Context context){ context.write(单词,总数); # <单词,总数> }
    1.第一步:ReduceTask把磁盘分区中送过来的数据按照key的字典序对多个<key,value>进行排序,然后把key相同的作为一组汇总成 <单词,[1,1,...]>,
         并调用reduce函数进行统计汇总单词的总次数
    2.第二步:ReduceTask把<单词,总数>输出到hdfs中的文件中


 

------------- Hadoop MapReduce--MapReduce的输入和输出 -----------------

 

------- Hadoop MapReduce--初识mapreduce数据分区&分区规则--------

//这里设置 reduce阶段中 运行 reduceTask 的个数 
//getPartition 返回的分区个数 = NumReduceTasks   正常执行 
//getPartition 返回的分区个数 > NumReduceTasks   报错:Illegal partition 非法分割
//getPartition 返回的分区个数 < NumReduceTasks   可以执行 ,多出空白文件 
job.setNumReduceTasks(10); 


1.设置 reduce阶段中 运行 reduceTask 的个数为 N,那么便会同样生成 N个同等数量的 结果文件。
  一个 reduceTask 负责生成 一个结果文件。每个reduceTask 就相当于 单独一个分区,而每个分区 即代表单独一个 结果文件。
  那么也即 reduceTaskNum 的数量 等于 结果文件 的数量 等于 分区的数量。
2.不管设置多少个reduceTask 所生成的 N个结果文件 实际和 设置1个reduceTask所生成的 一个结果文件 的效果相同的,
  只是把一个结果文件的数据 分到好几个 结果文件中。
3.每个结果文件 就是 单独一个 区分,那么分区规则是 根据Mapper阶段map函数输出的键值对中的key.hashcode % reduceTaskNum:
  % 表示模,那么整体表示的是 使用key的哈希值 对 reduceTaskNum 进行 取模 所得出的余数,该余数的值 对应的就是 第几个reduceTask,
  也即把处理后的数据 对应存储到 第几个 结果文件中。

 

--------- Hadoop MapReduce--处理流程--Mapper任务执行流程解析------------

1.对于目录下的多个要处理的数据文件先进行切片,每个切片的大小是块的大小128M,那么如果一个数据文件的大小刚好是128M的话,
  那么该文件即存到该切片中,然后等待被mapreduce 程序中的maptask(mapper阶段的map函数)所处理,
  那么有多少个切片便会有多少个maptask(mapper阶段的map函数)

2.maptask中通过TextInputFormat从切片中读取文件数据,返回key和value信息 并发送给mapper阶段中的map函数中进行处理。
    key:每一行的起始偏移量,即从文件头开始偏移多少字节到当前这一行,偏移量单位为字节,window下的换行符为2个字节
    value:一行内容的字符串数据

3.mapper阶段的map函数通过(mapreduce 程序中的 Context上下文)context.write(k,v) 把处理后的数据进行输出,,
  负责把 mapper阶段处理后的数据发送出去给 Reducer阶段的类来进行计算。
  此时先会把处理后的数据发送到内存缓冲区中,当内存缓冲区中写满了的话,然后把数据写出到磁盘中。
  而磁盘中会根据 reduceTaskNum的数量值 先分出对应数量的 分区,即把内存中的数据 写出到对应的 分区中。
  最终分区中的数据等待 reduceTask来读取继续进行处理计算。                  


-------- Hadoop MapReduce--处理流程--Reducer任务执行流程解析-------

1.mapreduce 程序中的 每个maptask 都会输出数据到对应分区中,而每个相同编号的分区中的数据 都会交由同一个reducetask 来处理,
  比如每个p1分区中的数据 都会交由reducetask1来处理。
2.在reducetask1中 按照key的字典序对 多个p1分区中的数据 进行排序,把相同key的多个键值对作为同一组数据(<key, [1,1,...]>) 传入到同一个我们所重写的 reduce函数中处理.
3.根据reduceTaskNum所设置的值,就有多少个reduceTask,那么就有多少个结果文件。
  reduceTask通过 (mapreduce 程序中的 Context上下文)context.write(key, v) 把 Reducer阶段处理后的数据 写出到 HDFS集群中的 输出目录下的 结果文件中。


 

================== 深入 MapReduce ====================

1.深入 MapReduce
    1.MapReduce 的输入和输出
        MapReduce 框架运转在<key,value>键值对上,也就是说,框架把作业的输入看成是一组<key,value>键值对,
        同样也产生一组<key,value>键值对作为作业的输出,这两组键值对可能是不同的。 
        一个 MapReduce 作业的输入和输出类型如下图所示:可以看出在整个标准的流程中,会有三组<key,value>键值对类型的存在。


    

    2.MapReduce 的处理流程解析
        在整个 MapReduce 程序的开发过程中,我们最大的工作量是 覆盖 map 函数 和 覆盖 reduce 函数。 

        1.Mapper 任务执行过程详解
            1.第一阶段是把输入目录下文件按照一定的标准逐个进行逻辑切片,形成切片规划。
              默认情况下,Split size = Block size。每一个切片由一个MapTask 处理。(getSplits) 

            2.第二阶段是对切片中的数据按照一定的规则解析成<key,value>对。默认规则是把每一行文本内容解析成键值对。
              key 是每一行的起始位置(单位是字节),value 是本行的文本内容。(TextInputFormat) 

            3.第三阶段是调用 Mapper 类中的 map 方法。上阶段中每解析出来的一个<k,v>,调用一次 map 方法。
              每次调用 map 方法会输出零个或多个键值对。 

            4.第四阶段是按照一定的规则对第三阶段输出的键值对进行分区。默认是只有一个区。分区的数量就是 Reducer 任务运行的数量。
              默认只有一个Reducer 任务。 

            5.第五阶段是对每个分区中的键值对进行排序。首先,按照键进行排序,对于键相同的键值对,按照值进行排序。
              比如三个键值对<2,2>、<1,3>、<2,1>,键和值分别是整数。那么排序后的结果是<1,3>、<2,1>、<2,2>。
              如果有第六阶段,那么进入第六阶段;如果没有,直接输出到文件中。 

            6.第六阶段是对数据进行局部聚合处理,也就是 combiner 处理。键相等的键值对会调用一次 reduce 方法。经过这一阶段,数据量会减少。
              本阶段默认是没有的。 
    
        2.Reducer 任务执行过程详解 
            1.第一阶段是 Reducer 任务会主动从 Mapper 任务复制其输出的键值对。Mapper 任务可能会有很多,因此 Reducer 会复制多个 Mapper 的输出。 
            2.第二阶段是把复制到 Reducer 本地数据,全部进行合并,即把分散的数据合并成一个大的数据。再对合并后的数据排序。 
            3.第三阶段是对排序后的键值对调用 reduce 方法。键相等的键值对调用一次reduce 方法,每次调用会产生零个或者多个键值对。
              最后把这些输出的键值对写入到 HDFS 文件中。


 

2.MapReduce 的序列化 
    1.概述 
        1.序列化(Serialization)是指把结构化对象转化为字节流。 
        2.反序列化(Deserialization) 是序列化的逆过程。 把字节流转为结构化对象。  
          当要在进程间传递对象或持久化对象的时候,就需要序列化对象成字节流,反之当要将接收到或从磁盘读取的字节流转换为对象,就要进行反序列化。 
        3.Java 的序列化 (Serializable) 是一个重量级序列化框架, 一个对象被序列化后,会附带很多额外的信息(各种校验信息,header,继承体系…) ,
          不便于在网络中高效传输;所以,hadoop 自己开发了一套序列化机制(Writable) ,精简,高效。
          不用像 java 对象类一样传输多层的父子关系,需要哪个属性就传输哪个属性值,大大的减少网络传输的开销。 
        4.Writable是Hadoop的序列化格式, hadoop定义了这样一个Writable接口:
            public interface Writable 
            {   
                void write(DataOutput out) throws IOException;   //序列化 write
                void readFields(DataInput in) throws IOException; //反序列化 readFields
            } 
        5.一个类要支持可序列化只需实现这个接口即可。 

    2.Writable 序列化接口 
        如需要将自定义的 bean 放在 key 中传输,则还需要实现 comparable接口(表示可比较的接口),因为 mapreduce 框中的 shuffle 过程一定会对 key 进行排序,
        此时,自定义的 bean 实现的接口应该是: public  class  FlowBean  implements WritableComparable<FlowBean>  
        需要自己实现的方法是:
            /** 
             *反序列化的方法,反序列化时,从流中读取到的各个字段的顺序应该与序列化时写出去的顺序保持一致 
             */ 
            @Override
            public void readFields(DataInput in) throws IOException 
            { 
                upflow = in.readLong(); 
                dflow = in.readLong(); 
                sumflow = in.readLong(); 
            } 
 
            /** 
             * 序列化的方法 
             */ 
            @Override 
            public void write(DataOutput out) throws IOException 
            { 
                out.writeLong(upflow); 
                out.writeLong(dflow); 
                out.writeLong(sumflow); 
            } 
 
            @Override 
            public int compareTo(FlowBean o) 
            { 
                //实现按照 sumflow 的大小 倒序排序(从大到小排序) 
                return sumflow > o.getSumflow() ? -1 : 1; 
            } 

        compareTo方法:用于将 当前对象 与 方法的参数 进行比较,从小到大排序。
            如果 指定的数 与   参数相等 返回 0。 
            如果 指定的数 小于 参数 返回 -1。 
            如果 指定的数 大于 参数 返回 1。 

        例如:o1.compareTo(o2); 
              返回正数的话,当前对象(调用 compareTo 方法的对象 o1)要排在比较对象(compareTo 传参对象 o2)的 后面;
              返回负数的话,当前对象(调用 compareTo 方法的对象 o1)要排在比较对象(compareTo 传参对象 o2)的 前面。

3.Mapreduce 的排序初步
    1.需求:在得出统计每一个用户(手机号)所耗费的总上行流量、下行流量,总流量结果的基础之上再加一个需求:将统计结果按照总流量 倒序排序(从大到小排序)。 
    2.基本思路: 
        实现自定义的 bean 来封装流量信息,并将 bean 作为 map 输出的 key 来传输,MapReduce程序 在处理数据的过程中会对数据排序
        (map 输出的 kv 对传输到 reduce 之前,会排序),排序的依据是 map 输出的 key。
        所以,我们如果要实现自己需要的排序规则,则可以考虑将排序因素放到 key 中,让 key 实现接口:WritableComparable,然后重写 key 的 compareTo 方法。

    3.实现
        public class FlowBean implements WritableComparable<FlowBean>
        { 
                private long upFlow;   //上行流量
                    private long downFlow; //下行流量
                    private long sumFlow;  //总流量
     
            //这里反序列的时候会用到 
                    public FlowBean() { } 
                public FlowBean(long upFlow, long downFlow, long sumFlow) 
                { 
                        this.upFlow = upFlow; 
                        this.downFlow = downFlow; 
                        this.sumFlow = sumFlow; 
                    } 
     
                    public FlowBean(long upFlow, long downFlow) 
                { 
                        this.upFlow = upFlow; 
                        this.downFlow = downFlow; 
                        this.sumFlow = upFlow+downFlow; 
                } 
     
                    public void set(long upFlow, long downFlow) 
                { 
                        this.upFlow = upFlow; 
                        this.downFlow = downFlow; 
                        this.sumFlow = upFlow+downFlow; 
                    } 
 
                    @Override 
                   public String toString() 
                { 
                        return upFlow+"\t"+downFlow+"\t"+sumFlow; 
                    } 
 
                    //这里是序列化方法 
                    @Override 
                    public void write(DataOutput out) throws IOException 
                { 
                          out.writeLong(upFlow); 
                          out.writeLong(downFlow); 
                          out.writeLong(sumFlow); 
                    } 
 
                    //这里是反序列化方法 
                @Override 
                    public void readFields(DataInput in) throws IOException 
                { 
                        //注意反序列化的顺序跟序列化的顺序一致 
                       this.upFlow = in.readLong(); 
                       this.downFlow = in.readLong(); 
                       this.sumFlow = in.readLong(); 
                } 
 
                    //这里进行 bean 的自定义比较大小 
                    @Override 
                    public int compareTo(FlowBean o) 
                { 
                        //实现按照 sumflow 的大小 倒序排序(从大到小排序) 
                        return this.sumFlow > o.getSumFlow() ? -1 : 1; 
                    } 
            } 

        /*
        extends Mapper<keyin, valuein, keyout, valueout>:
            比如:Mapper<LongWritable, Text, Text, FlowBean>
                LongWritable keyin:每一行的起始偏移量,即从文件头开始偏移多少字节到当前这一行,偏移量单位为字节,window下的换行符为2个字节
                Text valuein:一行内容的字符串数据
                Text keyout:总流量
                FlowBean valueout:FlowBean类对象 
        */
        public class FlowSumMapper extends Mapper<LongWritable, Text, Text, FlowBean>
        { 
                Text k = new Text(); 
                    FlowBean v = new FlowBean(); 
     
                    @Override //map(LongWritable keyin, Text valuein, Context上下文对象 context)
                    protected void map(LongWritable key, Text value,Context context) throws IOException, InterruptedException 
                { 
                    String line = value.toString(); 
                        String[] fields = line.split("\t"); //"\t" 表示 空格/table键
                        String phoneNum = fields[1]; //手机号        
                        long upFlow = Long.parseLong(fields[fields.length-3]); //上行流量
                        long downFlow = Long.parseLong(fields[fields.length-2]); //下行流量
                        k.set(phoneNum); 
                        v.set(upFlow, downFlow); 
                // mapreduce 程序中的 Context上下文,负责把 mapper阶段处理后的数据发送出去给 Reducer阶段的类来进行计算
                context.write(k, v); 
                } 
        } 

         /*
        extends Reducer<keyin, valuein, keyout, valueout>:
            比如:Reducer<Text, FlowBean, Text, FlowBean>
                Text keyin:同Mapper阶段(map函数)输出的keyout的类型和值
                FlowBean valuein:同Mapper阶段(map函数)输出的valueout的类型和值 
                Text keyout:总流量
                FlowBean valueout:FlowBean类对象 
        */
        public class FlowSumReducer extends Reducer<Text, FlowBean, Text, FlowBean> 
        { 
            FlowBean v = new FlowBean(); 
 
                @Override // reduce(Text keyin, 迭代器Iterable<FlowBean> valuein, Context上下文对象 context)函数
                protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException 
            { 
                long upFlowCount = 0; 
                        long downFlowCount = 0; 
                        for (FlowBean bean : values) 
                { 
                            upFlowCount += bean.getUpFlow(); //计算累加 上行 总流量
                            downFlowCount += bean.getDownFlow(); //计算累加 下行总流量
                        } 
                        v.set(upFlowCount, downFlowCount); 
                //mapreduce 程序中的 Context上下文,负责把 Reducer阶段处理后的数据发送出去
                        context.write(key, v); 
                } 
        } 


4.Mapreduce 的分区 — Partitioner
    1.需求:将流量汇总统计结果,按照手机归属地不同省份,输出到不同文件中。
    2.分析: 
        Mapreduce 中会将 map 输出的 kv 对,按照相同 key 分组,然后分发给不同的 reducetask。 
        默认的分发规则为:根据 key 的 hashcode%reducetask 数来分发。
        所以,如果要按照我们自己的需求进行分组,则需要改写数据分发(分组)组件 Partitioner抽象类,自定义一个 CustomPartitioner 继承 Partitioner抽象类。
        然后在 job 对象中,设置自定义 partitioner:job.setPartitionerClass(CustomPartitioner.class) 
    3.实现
        public class ProvincePartitioner extends Partitioner<Text, FlowBean>  
        {    
            public static HashMap<String, Integer> provinceMap = new HashMap<String, Integer>(); 
            static
            { 
                //key("134"、"135")表示手机号的头3个数字,代表的是 某个地区。
                //value(0、1等)表示 第几个分区
                        provinceMap.put("134", 0); 
                        provinceMap.put("135", 1); 
                     provinceMap.put("136", 2); 
                     provinceMap.put("137", 3); 
                     provinceMap.put("138", 4); 
                } 

                      //getPartition 返回的分区个数 = NumReduceTasks   正常执行 
                      //getPartition 返回的分区个数 > NumReduceTasks   报错:Illegal partition 非法分割
                      //getPartition 返回的分区个数 < NumReduceTasks   可以执行 ,多出空白文件 
                @Override 
                public int getPartition(Text key, FlowBean value, int numPartitions) 
            { 
                //根据 key(手机号的头3个数字代表某个地区)等 取出对应的 value(0、1等)
                        Integer code = provinceMap.get(key.toString().substring(0, 3)); 
         
                        if (code != null) 
                { 
                            // 返回的为provinceMap中的value,而该value即代表的是某个分区(某个数据输出文件),
                             // 那么Reduce阶段(reduce函数) 输出的数据便存储到对应的分区(数据输出文件)中
                            return code; //返回的数字就是第几个分区
                        } 
                        return 5; //第5个分区
                } 
        }

        public class FlowSumProvince 
        { 
            /*
            extends Mapper<keyin, valuein, keyout, valueout>:
                比如:Mapper<LongWritable, Text, Text, FlowBean>
                    LongWritable keyin:每一行的起始偏移量,即从文件头开始偏移多少字节到当前这一行,偏移量单位为字节,window下的换行符为2个字节
                    Text valuein:一行内容的字符串数据
                    Text keyout:总流量
                    FlowBean valueout:FlowBean类对象 
            */
            public static class FlowSumProvinceMapper extends Mapper<LongWritable, Text, Text, FlowBean>
            { 
                     Text k = new Text(); 
                     FlowBean  v = new FlowBean(); 
      
                     @Override 
                     protected void map(LongWritable key, Text value,Context context) throws IOException, InterruptedException 
                { 
                             //拿取一行文本转为 String 
                             String line = value.toString(); 
                             //按照分隔符\t 进行分割 
                             String[] fileds = line.split("\t"); 
                             //获取用户手机号 
                             String phoneNum = fileds[1]; //手机号
                             long upFlow = Long.parseLong(fileds[fileds.length-3]); //上行流量
                             long downFlow = Long.parseLong(fileds[fileds.length-2]); //下行流量
                             k.set(phoneNum); 
                             v.set(upFlow, downFlow); 
                              // mapreduce 程序中的 Context上下文,负责把 mapper阶段处理后的数据发送出去给 Reducer阶段的类来进行计算
                             context.write(k,v); 
                        } 
                } 

             /*
            extends Reducer<keyin, valuein, keyout, valueout>:
                比如:Reducer<Text, FlowBean, Text, FlowBean>
                    Text keyin:同Mapper阶段(map函数)输出的keyout的类型和值
                    FlowBean valuein:同Mapper阶段(map函数)输出的valueout的类型和值 
                    Text keyout:总流量
                    FlowBean valueout:FlowBean类对象 
            */
                 public static class FlowSumProvinceReducer extends Reducer<Text, FlowBean, Text, FlowBean>
            { 
                FlowBean  v  = new FlowBean();  
         
                        @Override  // reduce(Text keyin, 迭代器Iterable<FlowBean> valuein, Context上下文对象 context)函数
                        protected void reduce(Text key, Iterable<FlowBean> flowBeans,Context context) throws IOException, InterruptedException 
                { 
                    long upFlowCount = 0; 
                            long downFlowCount = 0; 
             
                            for (FlowBean flowBean : flowBeans) 
                    { 
                        upFlowCount += flowBean.getUpFlow(); //计算累加 上行 总流量
                        downFlowCount += flowBean.getDownFlow(); //计算累加 下行 总流量
                            } 
                              v.set(upFlowCount, downFlowCount); 
                    //mapreduce 程序中的 Context上下文,负责把 Reducer阶段处理后的数据发送出去
                              context.write(key, v); 
                      } 
     
                  public static void main(String[] args) throws Exception
                  { 
                      Configuration conf = new Configuration(); 
                          conf.set("mapreduce.framework.name", "local"); 
 
                          Job job = Job.getInstance(conf); 
 
                          //指定我这个 job 所在的 jar 包位置 
                          job.setJarByClass(FlowSumProvince.class); 
         
                          //指定我们使用的 Mapper 是那个类  reducer 是哪个类 
                          job.setMapperClass(FlowSumProvinceMapper.class); 
                          job.setReducerClass(FlowSumProvinceReducer.class); 
                          //job.setCombinerClass(FlowSumProvinceReducer.class); 
         
                          // 设置我们的业务逻辑 Mapper 类的输出 key 和 value 的数据类型 
                              job.setMapOutputKeyClass(Text.class); 
                              job.setMapOutputValueClass(FlowBean.class); 
         
                              // 设置我们的业务逻辑 Reducer 类的输出 key 和 value 的数据类型 
                              job.setOutputKeyClass(Text.class); 
                              job.setOutputValueClass(FlowBean.class); 
 
                              //这里设置 reduce阶段中 运行 reduceTask 的个数 
                              //getPartition 返回的分区个数 = NumReduceTasks   正常执行 
                              //getPartition 返回的分区个数 > NumReduceTasks   报错:Illegal partition 非法分割
                              //getPartition 返回的分区个数 < NumReduceTasks   可以执行 ,多出空白文件 
                              job.setNumReduceTasks(6); 
 
                              //这里指定使用我们自定义的分区组件 
                              job.setPartitionerClass(ProvincePartitioner.class); 
 
                              FileInputFormat.setInputPaths(job, new Path("D:\\flowsum\\input")); 
                              // 指定处理完成之后的结果所保存的位置 
                              FileOutputFormat.setOutputPath(job, new Path("D:\\flowsum\\outputProvince")); 
         
                              boolean res = job.waitForCompletion(true); 
                              System.exit(res ? 0 : 1); 
                          } 
            } 
        } 


5.Mapreduce 的 Combiner 
    1.每一个 map 都可能会产生大量的本地输出, Combiner 的作用就是对 map 端的输出先做一次合并,以减少在 map 和 reduce 节点之间的数据传输量,
      以提高网络 IO 性能,是 MapReduce 的一种优化手段之一。 
    2.Combiner 是 MapReduce 程序中 Mapper 和 Reducer 之外的一种组件 
    3.Combiner 组件的父类就是 Reducer 
     4.Combiner 和 Reducer 的区别在于运行的位置: 
          Combiner 是在每一个 maptask 所在的节点运行 ;
        Reducer 是接收全局所有 Mapper 的输出结果 ; 
     5.Combiner 的意义就是对每一个 maptask 的输出进行局部汇总,以减小网络传输量 
     6.具体实现步骤: 
        1.自定义一个 Combiner 继承 Reducer,重写 reduce 方法 
        2.在 job 中设置:job.setCombinerClass(CustomCombiner.class) 
     7.Combiner 能够应用的前提是不能影响最终的业务逻辑,而且,Combiner 的输出 kv 应该跟 reducer 的输入 kv 类型要对应起来

================= Hadoop配置 ================= 

1.一旦 Hadoop 集群启动并运行,可以通过 web-ui 进行集群查看,如下所述:  
    NameNode(HDFS管理界面):192.168.25.100:50070 或 node1:50070 
    ResourceManager(MR管理界面):http://192.168.25.100:8088 或 node1:8088 

2.配置 windows 平台 Hadoop 环境 
    在 windows 上做 HDFS 客户端应用开发,需要设置 Hadoop 环境,而且要求是
    windows 平台编译的 Hadoop,不然会报以下的错误: 
        Failed to locate the winutils binary in the hadoop binary path java.io.IOException: Could not 
        locate executable null\bin\winutils.exe in the Hadoop binaries. 

    解决:为此我们需要进行如下的操作: 
    A、在 windows 平台下编译 Hadoop 源码(可以参考资料编译,但不推荐)  
    B、使用已经编译好的 Windows 版本 Hadoop: hadoop-2.7.4-with-windows.tar.gz 
    C、解压一份到 windows 的任意一个目录下 
    D、在 windows 系统中配置 HADOOP_HOME 指向你解压的安装包目录 
    E、在 windows 系统的 path 变量中加入 HADOOP_HOME 的 bin 目录


 

================= Hadoop API  ================= 

        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>2.7.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>2.7.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>2.7.4</version>
        </dependency>

================= HdfsClient:操作Hadoop的API方法  ================= 

 

================= hdfs定时上传shell的案列  ================= 

[root@NODE1 ~]# chmod 777 uploadFile2Hdfs.sh
[root@NODE1 ~]# ./uploadFile2Hdfs.sh

假如有这样的需求:要求在凌晨 24 点开始操作前一天产生的日志文件,准实时上传至 HDFS 集群上
    1.HDFS SHELL:hadoop fs –put  //满足上传文件,不能满足定时、周期性传入。 
    2.Linux crontab:  
        crontab -e 
        0 0 * * * /shell/ uploadFile2Hdfs.sh   //每天凌晨 12:00 执行一次

=============  Hadoop API 的基本使用 ============

指定本次MapReduce程序中 数据输入的路径(hdfs文件系统中的路径) 和 数据最终输出 存放在什么位置(hdfs文件系统中的路径)
        1.创建数据输入的路径(hdfs文件系统中的路径):hadoop fs -mkdir -p /Hadoop_daima/input
        2.把要计算的文件放到数据输入的路径(hdfs文件系统中的路径)中:hadoop fs -put xx.txt yy.txt /Hadoop_daima/input
        3.注意:不需要创建 数据最终输出目录(hdfs文件系统中的路径),否则会报错:FileAlreadyExistsException: Output directory,
               数据最终输出目录会由MapReduce程序创建

 Hadoop MapReduce--程序运行模型--集群运行模式

将 mapreduce 程序提交给 yarn 集群的命令:
    格式一:hadoop jar xx.jar mapreduce程序的全限定类名 args参数
    例子:hadoop jar wordcount.jar cn.itcast.bigdata.mrsimple.WordCountDriver args

    格式二:hadoop jar xx.jar
        (无需配置mapreduce程序的全限定类名,因为在pom.xml中的<mainClass>标签体中配置了mapreduce程序的全限定类名)
    例子:hadoop jar wordcount.jar
 

 

 ----- Hadoop MapReduce 程序运行模型 本地运行模式-----

conf.set("mapreduce.framework.name","local")代码语句 为设置本地模式运行,但要注意的是 mapred-default.xml中已经默认配置是本地模式,
所以即使不配置conf.set(“mapreduce.framework.name”,“local”),只要右键run运行该程序仍然是本地模式

指定本次MapReduce程序中 数据输入的路径(本地文件系统中的路径) 和 数据最终输出 存放在什么位置(本地文件系统中的路径)
注意:不需要创建 数据最终输出目录(本地文件系统中的路径),否则会报错:FileAlreadyExistsException: Output directory,
      数据最终输出目录会由MapReduce程序创建。

 

======== Hadoop MapReduce编程案例--流量汇总--序列化机制Writable ============

---------mapper编写--------------

把一行信息看作为一个数组中,此处取出某一列的数据(某个字段的数据/数组中某个元素值),使用数组[length-N]的方式获取,
因为从左到右数出现某列(某个字段)缺失数据,而从右到左数则不会出现某列(某个字段)缺失数据

------------Reducer编写---------------

---------运行主类编写------------

-------本地模式 运行 Mapreduce程序中的 FlowSumDriver类:右键直接run-------

 

======= Hadoop MapReduce编程案例--流量汇总排序--需求分析&comopareTo方法重写============

在上面的基础之上再加一个需求:
    将统计结果按照总流量倒序排序(从大到小排序),因为此处MapReduce程序中的Reduce阶段中的 Context上下文对象把一个类对象作为valueout进行输出到文件中,
    所以重写 该类中的comopareTo方法,comopareTo方法中进行总流量倒序排序(从大到小排序),那么结果文件中的输出数据便是按照总流量倒序排序(从大到小排序)。


 

实现步骤:
    1.Mapper阶段(map函数)中是默认根据 keyin 进行 字典序进行排序的,所以当keyin 是bean对象时,
      只需要bean对象所在的类中implements实现 WritableComparable<类名> 接口,并在类中重写comopareTo方法,
      那么便会根据comopareTo方法定义的排序规则进行keyin 的排序,便不会再使用默认的字典序排序规则进行排序。
      例子:public class FlowBean implements WritableComparable<FlowBean>
      原理:interface WritableComparable<T> extends Writable, Comparable<T>

    2.Mapper阶段(map函数)中的 (keyin)bean对象所在的类中重写comopareTo方法:
          那么每个keyin(bean对象)之间不再使用默认的字典序排序规则进行排序,每个keyin(bean对象)之间使用 comopareTo方法中定义的排序规则进行排序。
      代码如下:   
        @Override
            public int compareTo(FlowBean o) 
        {
                    //实现按照总流量的倒序排序
                    return this.sumFlow >o.getSumFlow()?-1:1; //顺序排序(从小到大)
                    //return this.sumFlow >o.getSumFlow()?1:-1  //倒序排序(从大到小)
            }

 

=========Hadoop MapReduce编程案例--流量汇总分区--需求分析&HashPartitioner讲解============

需求:将流量汇总统计结果按照手机归属地不同省份输出到不同文件中
Mapper阶段(map函数)中:
      读一行,切分字段
     抽取手机号,上行流量 下行流量
     context.write(手机号,bean)
    map输出的数据要分成6个区
    重写partitioner,让相同归属地的号码返回相同的分区号int

实现原理:
    1.job.setNumReduceTasks(N):
        设置了 NumReduceTasks 之后,便会生成 N 个输出结果文件,
        那么 Reduce阶段(reduce函数)按照默认的分区规则 (key.hashcode & Integer.MAX_VALUE) % numReduceTasks 来进行分发到哪个输出结果文件中。
         (key.hashcode & Integer.MAX_VALUE) % numReduceTasks:
            获取Reduce阶段(reduce函数)输出的key的hashcode值,再 & 上 Integer的最大值 所得出的数值 再模(%)上 所设置的numReduceTasks数量值,
            所得出的 余数 对应的就是 第几个 输出结果文件。


    
    

    2.当没有执行 job.setNumReduceTasks(N),即没有显式设置 numReduceTasks 时,那么numReduceTasks 默认为 1,任何数 % 1 的值 都为 0,
      因此(key.hashcode & Integer.MAX_VALUE) % numReduceTasks 的结果值 便为 0,所以Reduce阶段(reduce函数)输出的数据 都存到同一个文件中。

 

 

======== Hadoop MapReduce--combiner组件介绍&使用注意事项============

ReduceTask(Reduce阶段)负责全局合并,合并所有的MapTask传过来的数据。
而每个combiner进行的是局部合并,一个combiner只针对单独某个对应的MapTask(Mapper阶段)的数据进行合并。
combiner再把局部合并后的数据发送给ReduceTask(Reduce阶段)继续进行全局合并。


比如此处例子中的9个<hello,1>的数据:
    1.如果不使用combiner把9个<hello,1>合并为1个<hello,9>再传给ReduceTask(Reduce阶段)的话,那么MapTask就需要传输9次<hello,1>给ReduceTask(Reduce阶段)
    2.如果使用combiner把9个<hello,1>合并为1个<hello,9>再传给ReduceTask(Reduce阶段)的话,那么MapTask就只需要传输1次<hello,9>给ReduceTask(Reduce阶段)


 

实现步骤:
    1.自定义一个 Combiner类 继承 Reducer,重写 reduce 方法 
    2.在 job 中设置:job.setCombinerClass(CustomCombiner.class) 
    
实现捷径:
    因为 自定义的Combiner类 的写法 类似于 全局的Reducer阶段(reducer函数),两者都是同样的需要继承 extends Reducer<keyin,valuein,keyout,valueout>,
    并且两者都需要重写reduce(keyin key, Iterable<valuein的类型> values, Context context),
    因此可以直接把 全局的Reducer阶段(reducer函数)的类 作为 自定义的Combiner类 来使用,因为全局的合并 和 局部的合并 两者的 合并操作的逻辑 都是一样,
    所以便可以直接把 全局的Reducer阶段(reducer函数)的类 作为 自定义的Combiner类 来使用,使用 全局的Reducer阶段(reducer函数)的类来对 每个的MapTask都进行数据局部合并。
    那么只需要在Driver类 设置job.setCombinerClass(XxxReducer.class) 即可。

实现例子如下:

直接把 全局的Reducer阶段(reducer函数)的类 作为 自定义的Combiner类 来使用,使用 全局的Reducer阶段(reducer函数)的类来对 每个的MapTask都进行数据局部合并

 

 

 

 

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

あずにゃん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值