04_MapReduce编程一

目录

 

一、MapReduce基本原理

1.1 基本概念

1.2、Map流程

1.3、Reduce流程

1.4、其他说明

二、 Shuffle(重点 )

2.1、 shuffle简图

2.2、 shuffle细节图​

2.3、 map端的shuffle

2.4、reduce端的shuffle

三、MR编程—词频统计

1、main

3.2、map

3.3、Reduce

3.4、编写运行步骤

1、创建MAVEN工程

2、代码编写

3、本地运行

4、集群运行

四、MR编程-数据清洗

4.1、需求

4.2、逻辑分析                             

4.4、MR代码

五、MapReduce编程—用户搜索次数

5.1 需求

5.2 分析


 

准备:

  1. 准备3节点hadoop集群

  2. windows下hadoop环境配置好

  3. windows下安装IDEA编程工具

  4. 安装maven并配置环境变量

目标

  1. 理解MapReduce编程模型

  2. 描述MR的shuffle全流程(面试)

  3. 搭建MAVEN工程,统计词频,并提交集群运行,查看结果

  4. 利用搜狗数据,找出所有独立的uid并写入HDFS

  5. 利用搜狗数据,找出所有独立的uid出现次数,并写入HDFS,并要求使用Map端的Combine操作

  6. 谈谈什么是数据倾斜,什么情况会造成数据倾斜?(面试)

  7. 对MR数据倾斜,如何解决?(面试)

一、MapReduce基本原理

1.1 基本概念

  • 1、MapReduce是采用一种分而治之的思想设计出来的分布式计算框

    • 分而治之:比如一复杂、计算量大、耗时长的的任务,暂且称为“大任务”;此时使用单台服务器无法计算或较短时间内计算出结果时,可将此大任务切分成一个个小的任务,小任务分别在不同的服务器上并行的执行,最终再汇总每个小任务的结果。

  • 2、MapReduce由两个阶段组成:

    • Map阶段(切分成一个个小的任务)

    • Reduce阶段(汇总小任务的结果)

  • 3、MR输入的文件有几个block,就会生成几个map任务

  • 4、MR的reduce任务的个数,由程序中编程指定:job.setNumReduceTasks(4)

  • 5、map任务:

    • map任务一次读取block的一行数据,以kv对的形式输入map()

    • map()的输出作为reduce()的输入

  • 6、reduce任务:

    • reduce任务通过网络将各map任务输出结果中,属于自己的数据取过来

    • key相同的键值对作为一组,调用一次reduce()

    • reduce任务生成一个结果文件

    • 文件写入HDFS

 

MapReduce的词频统计为例:统计一批英文文章当中,每个单词出现的总次数

 假设MR的输入文件“Gone With The Wind”有三个block;block1、block2、block3;

MR编程时,每个block对应一个分片split,每一个split对应一个map任务(map task);

如图共3个map任务(map1、map2、map3);这3个任务的逻辑一样,所以以第一个map任务(map1)为例分析;

reduce任务个数,由自己写的程序编程指定,如job.setNumReduceTasks(4)指定reduce任务是4个(reduce1、reduce2、reduce3、reduce4)

 

 

1.2、Map流程

(1)input split

        map1读取block1的数据;一次读取block1的一行数据,产生键值对(key/value),作为map()的参数传入。

(2)map()函数:(自定义)

  • 假设当前所读行是第一行,将当前所读行的行首相对于当前block开始处的字节偏移量作为key(0)
  • 当前行的内容作为value(Dear Bear River),将value当前行内容按空格切分,得到三个单词Dear | Bear | River
  • 将每个单词变成键值对,输出出去(Dear, 1) | (Bear, 1) | (River, 1);
  • block的第一行的数据被处理完后,接着处理第二行;逻辑同上
  • 当map任务将当前block中所有的数据全部处理完后,此map任务即运行结束

(3)存数据:(shuffle)

      结果最终写入map任务所在节点的本地磁盘中(内里还有细节,讲到shuffle时,再细细展开)

 

1.3、Reduce流程

(1)拉取:(shuffle)

  • map1任务完成后,reduce1通过网络,连接到map1,将map1输出结果中属于reduce1的分区的数据,通过网络获取到reduce1端(拷贝阶段),同样也如此连接到map2、map3获取结果

  • 最终reduce1端获得4个(Dear, 1)键值对;由于key键相同,它们分到同一组;

  • 4个(Dear, 1)键值对,转换成[Dear, Iterable(1, 1, 1, )],作为参数传入reduce()

(2)reduce()

  • 在reduce()内部,计算Dear的总数为4,并将(Dear, 4)作为键值对输出

(3)写入HDFS

    每个reduce任务最终输出文件(内里还有细节,讲到shuffle时,再细细展开),文件写入到HDFS.

 

1.4、其他说明:

1、Map任务数 = block数

2、Reduce任务数 = 自定义

3、key有特殊的作用

  • 数据中,若要针对某个值进行分组、聚合时,需将此值作为MR中的reduce的输入的key

  • 如当前的词频统计例子,按单词进行分组,每组中对出现次数做聚合(汇总计算总和);所以需要将每个单词作为reduce输入的key,MapReduce框架自动按照单词分组,进而求出每组即每个单词的总次数

  • 另外,key还具有可排序的特性,因为MR中的key类需要实现WritableComparable接口;而此接口又继承Comparable接口(可查看源码)

 

二、 Shuffle(重点 )

2.1、 shuffle简图

  • shuffle主要指的是map端的输出作为reduce端输入的过程

  • 为了确保每个reducer的的输入都是按键排序的,系统执行排序的过程,即将map task的输出通过一定规则传给reduce task,这个过程成为shuffle。

    Shuffle阶段一部分是在map task 中进行的, 这里称为Map shuffle , 还有一部分是在reduce task 中进行的, 这里称为Reduce shffle。

2.2、 shuffle细节图

 

  • 分区用到了分区器,默认分区器是HashPartitioner,源码:

public class HashPartitioner<K2, V2> implements Partitioner<K2, V2> {
​
  public void configure(JobConf job) {}
​
  /** Use {@link Object#hashCode()} to partition. */
  public int getPartition(K2 key, V2 value,
                          int numReduceTasks) {
    return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
  }
​
}

 

 

2.3、 map端的shuffle

  • (1)环形内存缓冲:

    • 每个map任务都有一个对应的环形内存缓冲区;map()输出kv对,先写入到环形缓冲区(默认大小100M),当内容占据80%缓冲区空间后,由一个后台线程将缓冲区中的数据溢出写到一个磁盘文件。

    • 在溢出写的过程中,map任务可以继续向环形缓冲区写入数据;但是若写入速度大于溢出写的速度,最终造成100m占满后,map任务会暂停向环形缓冲区中写数据的过程;只执行溢出写的过程;直到环形缓冲区的数据全部溢出写到磁盘,才恢复向缓冲区写入

  • (2)后台线程溢写磁盘过程,

    • (1)分区:先对每个溢写的kv对做分区;分区的个数由MR程序的reduce任务数决定;默认使用HashPartitioner计算当前kv对属于哪个分区;计算公式:(key.hashCode() & Integer.MAX_VALUE) % numReduceTasks

    • (2)排序: 每个分区中,根据kv对的key做内存中排序

    • (3)combine(可选):若设置了map端本地聚合combiner,则对每个分区中,排好序的数据做combine操作;

    • (4)压缩(可选):若设置了对map输出压缩的功能,会对溢写数据压缩

(3)说明:

  • 随着不断的向环形缓冲区中写入数据,会多次触发溢写(每当环形缓冲区写满100m),本地磁盘最终会生成多个溢出文件

  • 合并溢写文件:在map task完成之前,所有溢出文件会被合并成一个大的溢出文件;且是已分区、已排序的输出文件

  • 小细节:

    • 在合并溢写文件时,如果至少有3个溢写文件,并且设置了map端combine的话,会在合并的过程中触发combine操作;

    • 但是若只有2个或1个溢写文件,则不触发combine操作(因为combine操作,本质上是一个reduce,需要启动JVM虚拟机,有一定的开销)

2.4、reduce端的shuffle

  • (1)拉取

    • reduce task会在每个map task运行完成后,通过HTTP获得map task输出中,属于自己的分区数据(许多kv对)

    • 如果map输出数据比较小,先保存在reduce的jvm内存中,否则直接写入reduce磁盘

  • (2)归并merge:

    • 一旦内存缓冲区达到阈值(默认0.66)或map输出数的阈值(默认1000),则触发归并merge,结果写到本地磁盘

  • (3)combine(可选):若MR编程指定了combine,在归并过程中会执行combine操作

(4)说明:

  • 随着溢出写的文件的增多,后台线程会将它们合并大的、排好序的文件

  • reduce task将所有map task复制完后,将合并磁盘上所有的溢出文件

  • 默认一次合并10个

  • 最后一批合并,部分数据来自内存,部分来自磁盘上的文件

  • 进入“归并、排序、分组阶段”

  • 每组数据调用一次reduce方法

 

三、MR编程—词频统计

  • 创建包com.kaikeba.hadoop.wordcount

  • 在包中创建自定义mapper类、自定义reducer类、包含main类

1、main

 

3.2、map

3.3、Reduce

 

 

package com.kaikeba.hadoop.wordcount;

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.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;

/**
 *
 * MapReduce程序入口
 * 注意:
 *  导包时,不要导错了;
 *  另外,map\reduce相关的类,使用mapreduce包下的,是新API,如org.apache.hadoop.mapreduce.Job;;
 */
public class WordCountMain {
    //若在IDEA中本地执行MR程序,需要将mapred-site.xml中的mapreduce.framework.name值修改成local
    //参数 c:/test/README.txt c:/test/wc
    public static void main(String[] args) throws IOException,
            ClassNotFoundException, InterruptedException {

        //判断一下,输入参数是否是两个,分别表示输入路径、输出路径
       if (args.length != 2 || args == null) {
            System.out.println("please input Path!");
            System.exit(0);
        }

        Configuration configuration = new Configuration();
        //configuration.set("mapreduce.framework.name","local");


        //告诉程序,要运行的jar包在哪
        //configuration.set("mapreduce.job.jar","/home/hadoop/IdeaProjects/Hadoop/target/com.kaikeba.hadoop-1.0-SNAPSHOT.jar");

        //调用getInstance方法,生成job实例
        Job job = Job.getInstance(configuration, WordCountMain.class.getSimpleName());

        //设置job的jar包,如果参数指定的类包含在一个jar包中,则此jar包作为job的jar包; 参数class跟主类在一个工程即可;一般设置成主类
//        job.setJarByClass(WordCountMain.class);
        job.setJarByClass(WordCountMain.class);

        //通过job设置输入/输出格式
        //MR的默认输入格式是TextInputFormat,输出格式是TextOutputFormat;所以下两行可以注释掉
//        job.setInputFormatClass(TextInputFormat.class);
//        job.setOutputFormatClass(TextOutputFormat.class);

        //设置输入/输出路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        //设置处理Map阶段的自定义的类
        job.setMapperClass(WordCountMap.class);
        //设置map combine类,减少网路传出量
        job.setCombinerClass(WordCountReduce.class);
        //设置处理Reduce阶段的自定义的类
        job.setReducerClass(WordCountReduce.class);
        //注意:如果map、reduce的输出的kv对类型一致,直接设置reduce的输出的kv对就行;如果不一样,需要分别设置map, reduce的输出的kv类型
        //注意:此处设置的map输出的key/value类型,一定要与自定义map类输出的kv对类型一致;否则程序运行报错
//        job.setMapOutputKeyClass(Text.class);
//        job.setMapOutputValueClass(IntWritable.class);

        //设置reduce task最终输出key/value的类型
        //注意:此处设置的reduce输出的key/value类型,一定要与自定义reduce类输出的kv对类型一致;否则程序运行报错
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 提交作业
        job.waitForCompletion(true);

    }
}
package com.kaikeba.hadoop.wordcount;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

/**
 * 类Mapper<LongWritable, Text, Text, IntWritable>的四个泛型分别表示
 * map方法的输入的键的类型kin、值的类型vin;输出的键的类型kout、输出的值的类型vout
 * kin指的是当前所读行行首相对于split分片开头的字节偏移量,所以是long类型,对应序列化类型LongWritable
 * vin指的是当前所读行,类型是String,对应序列化类型Text
 * kout根据需求,输出键指的是单词,类型是String,对应序列化类型是Text
 * vout根据需求,输出值指的是单词的个数,1,类型是int,对应序列化类型是IntWritable
 *
 */
public class WordCountMap extends Mapper<LongWritable, Text, Text, IntWritable> {

    /**
     * 处理分片split中的每一行的数据;针对每行数据,会调用一次map方法
     * 在一次map方法调用时,从一行数据中,获得一个个单词word,再将每个单词word变成键值对形式(word, 1)输出出去
     * 输出的值最终写到本地磁盘中
     * @param key 当前所读行行首相对于split分片开头的字节偏移量
     * @param value  当前所读行
     * @param context
     * @throws IOException
     * @throws InterruptedException
     */
    public void map(LongWritable key, Text value, Context context)
            throws IOException, InterruptedException {
        //当前行的示例数据(单词间空格分割):Dear Bear River
        // 1、取得当前行的数据
        String line = value.toString();
        
        // 2、按照\t进行分割,得到当前行所有单词
        String[] words = line.split("\t");

        for (String word : words) {
            //将每个单词word变成键值对形式(word, 1)输出出去
            //同样,输出前,要将kout, vout包装成对应的可序列化类型,如String对应Text,int对应IntWritable
            context.write(new Text(word), new IntWritable(1));
        }
    }
}
package com.kaikeba.hadoop.wordcount;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

/**
 *
 * Reducer<Text, IntWritable, Text, IntWritable>的四个泛型分别表示
 * reduce方法的输入的键的类型kin、输入值的类型vin;输出的键的类型kout、输出的值的类型vout
 * 注意:因为map的输出作为reduce的输入,所以此处的kin、vin类型分别与map的输出的键类型、值类型相同
 * kout根据需求,输出键指的是单词,类型是String,对应序列化类型是Text
 * vout根据需求,输出值指的是每个单词的总个数,类型是int,对应序列化类型是IntWritable
 *
 */
public class WordCountReduce extends Reducer<Text, IntWritable, Text, IntWritable> {
    /**
     *
     * key相同的一组kv对,会调用一次reduce方法
     * 如reduce task汇聚了众多的键值对,有key是hello的键值对,也有key是spark的键值对,如下
     * (hello, 1)
     * (hello, 1)
     * (hello, 1)
     * (hello, 1)
     * ...
     * (spark, 1)
     * (spark, 1)
     * (spark, 1)
     *
     * 其中,key是hello的键值对被分成一组;merge成[hello, Iterable(1,1,1,1)],调用一次reduce方法
     * 同样,key是spark的键值对被分成一组;merge成[spark, Iterable(1,1,1)],再调用一次reduce方法
     *
     * @param key 当前组的key
     * @param values 当前组中,所有value组成的可迭代集和
     * @param context reduce上下文环境对象
     * @throws IOException
     * @throws InterruptedException
     */
    public void reduce(Text key, Iterable<IntWritable> values,
                          Context context) throws IOException, InterruptedException {
        //定义变量,用于累计当前单词出现的次数
        int sum = 0;

        for (IntWritable count : values) {
            //从count中获得值,累加到sum中
            sum += count.get();
        }

        //将单词、单词次数,分别作为键值对,输出
        context.write(key, new IntWritable(sum));// 输出最终结果
    };
}

3.4、编写运行步骤

1、创建MAVEN工程

所有编程操作,在hadoop集群某节点的IDEA中完成

  • 使用IDEA创建maven工程

  • pom文件参考提供的pom.xml,主要用到的dependencies有

<properties>
        <cdh.version>2.6.0-cdh5.14.2</cdh.version>
    </properties>

    <repositories>
        <repository>
            <id>cloudera</id>
            <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
        </repository>
    </repositories>

    <dependencies>

        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>2.6.0-mr1-cdh5.14.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>${cdh.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>${cdh.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-mapreduce-client-core</artifactId>
            <version>${cdh.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

2、代码编写

 

3、本地运行

在windows的IDEA中运行

初次运行WordCountMain,先设置main方法参数,根据图示操作即可

 

 

查看结果

①成功标识文件

②结果文件

4、集群运行

  • 用maven插件打jar包;①点击Maven,②双击package打包

 

控制台打印结果;①表示打包成功;②是生成的jar所在路径  

 

  • 将jar包上传到node01用户主目录  /home/hadoop下

  • 用hadoop jar命令运行mr程序

[hadoop@node01 ~]$ cd
[hadoop@node01 ~]$ hadoop jar com.kaikeba.hadoop-1.0-SNAPSHOT.jar com.kaikeba.hadoop.wordcount.WordCountMain /README.txt /wordcount01

 

说明:

com.kaikeba.hadoop-1.0-SNAPSHOT.jar是jar包名

com.kaikeba.hadoop.wordcount.WordCountMain是包含main方法的类的全限定名

/NOTICE.txt和/wordcount是main方法的两个参数,表示输入路径、输出路径

确认结果

[hadoop@node01 ~]$ hadoop fs -ls /wordcount01

四、MR编程-数据清洗

mapreduce在企业中,可以用于对海量数据的数据清洗;

当然,随着新一代大数据框架的出现,也可以使用spark、flink等框架,做数据清洗

4.1、需求

  • 现有一批日志文件,日志来源于用户使用搜狗搜索引擎搜索新闻,并点击查看搜索结果过程;但是,日志中有一些记录损坏,现需要使用MapReduce来将这些损坏记录(如记录中少字段、多字段)从日志文件中删除,此过程就是传说中的数据清洗。并且在清洗时,要统计损坏的记录数。

  • MapReduce程序一般分为:map阶段,将任务分而治之;reduce阶段,将map阶段的结果进行聚合;而,有些mapreduce应用不需要数据聚合的操作,也就是说不需要reduce阶段。即编程时,不需要编写自定义的reducer类;在main()中调用job.setNumReduceTasks(0)设置。而本例的数据清洗就是属于此种情况。

  • 日志格式:每行记录有6个字段;分别表示时间datetime、用户ID userid、新闻搜索关键字searchkwd、当前记录在返回列表中的序号retorder、用户点击链接的顺序cliorder、点击的URL连接cliurl

4.2、逻辑分析                             

  • 统计损坏的记录数,可自定义map方法中,使用自定义计数器的方式进行;默认reduce阶段。

  • map方法的逻辑:取得每一行数据,与每条记录的固定格式比对,是否符合;若符合,则是完好的记录;否则是损坏的记录。并对自定义计数器累加。

4.4、MR代码

若要集群运行,需先将sogou.50w.utf8上传到HDFS根目录

[hadoop@node01 soft]$ pwd

/kkb/soft

[hadoop@node01 soft]$ hadoop fs -put sogou.50w.utf8 /

 

 

 

 

package com.kaikeba.hadoop.dataclean;

import org.apache.hadoop.conf.Configuration;
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.Counter;
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.output.FileOutputFormat;

import java.io.IOException;

/**
 *
 * 现对sogou日志数据,做数据清洗;将不符合格式要求的数据删除
 * 每行记录有6个字段;
 * 分别表示时间datetime、用户ID userid、新闻搜索关键字searchkwd、当前记录在返回列表中的序号retorder、用户点击链接的顺序cliorder、点击的URL连接cliurl
 * 日志样本:
 * 20111230111308  0bf5778fc7ba35e657ee88b25984c6e9        nba直播 4       1       http://www.hoopchina.com/tv
 *
 */
public class DataClean {
    /**
     *
     * 基本上大部分MR程序的main方法逻辑,大同小异;将其他MR程序的main方法代码拷贝过来,稍做修改即可
     * 实际开发中,也会有很多的复制、粘贴、修改
     *
     * 注意:若要IDEA中,本地运行MR程序,需要将resources/mapred-site.xml中的mapreduce.framework.name属性值,设置成local
     * @param args
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //判断一下,输入参数是否是两个,分别表示输入路径、输出路径
        if (args.length != 2 || args == null) {
            System.out.println("please input Path!");
            System.exit(0);
        }

        Configuration configuration = new Configuration();

        //调用getInstance方法,生成job实例
        Job job = Job.getInstance(configuration, DataClean.class.getSimpleName());

        //设置jar包,参数是包含main方法的类
        job.setJarByClass(DataClean.class);

        //设置输入/输出路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        //设置处理Map阶段的自定义的类
        job.setMapperClass(DataCleanMapper.class);

        //注意:此处设置的map输出的key/value类型,一定要与自定义map类输出的kv对类型一致;否则程序运行报错
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(NullWritable.class);

        //注意:因为不需要reduce聚合阶段,所以,需要显示设置reduce task个数是0
        job.setNumReduceTasks(0);

        // 提交作业
        job.waitForCompletion(true);
    }

    /**
     *
     * 自定义mapper类
     * 注意:若自定义的mapper类,与main方法在同一个类中,需要将自定义mapper类,声明成static的
     */
    public static class DataCleanMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
        //为了提高程序的效率,避免创建大量短周期的对象,出发频繁GC;此处生成一个对象,共用
        NullWritable nullValue = NullWritable.get();

        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            //自定义计数器,用于记录残缺记录数
            Counter counter = context.getCounter("DataCleaning", "damagedRecord");

            //获得当前行数据
            //样例数据:20111230111645  169796ae819ae8b32668662bb99b6c2d        塘承高速公路规划线路图  1       1       http://auto.ifeng.com/roll/20111212/729164.shtml
            String line = value.toString();

            //将行数据按照记录中,字段分隔符切分
            String[] fields = line.split("\t");

            //判断字段数组长度,是否为6
            if(fields.length != 6) {
                //若不是,则不输出,并递增自定义计数器
                counter.increment(1L);
            } else {
                //若是6,则原样输出
                context.write(value, nullValue);
            }
        }
    }
}

 

五、MapReduce编程—用户搜索次数

5.1 需求

  • 使用MR编程,统计sogou日志数据中,每个用户搜索的次数;结果写入HDFS

  • 数据来源自“MapReduce编程:数据清洗”中的输出结果

  • 仍然是sogou日志数据;不再赘述

5.2 分析

  • MR中key的作用

  • MR编程时,若要针对某个值对数据进行分组、聚合时,需要将每个单词作为reduce输入的key,如当前的词频统计例子,从而按照单词分组,进而求出每组即每个单词的总次数

  • 那么此例也是类似的。

    • 统计每个用户的搜索次数,将userid放到reduce输入的key的位置;

    • 对userid进行分组,进而统计每个用户的搜索次数

 

 

package com.kaikeba.hadoop.searchcount;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
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;

import java.io.IOException;

/**
 *
 * 本MR示例,用于统计每个用户搜索并查看URL链接的次数
 */
public class UserSearchCount {

    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //判断一下,输入参数是否是两个,分别表示输入路径、输出路径
        if (args.length != 2 || args == null) {
            System.out.println("please input Path!");
            System.exit(0);
        }

        Configuration configuration = new Configuration();
        //configuration.set("mapreduce.job.jar","/home/hadoop/IdeaProjects/Hadoop/target/com.kaikeba.hadoop-1.0-SNAPSHOT.jar");

        //调用getInstance方法,生成job实例
        Job job = Job.getInstance(configuration, UserSearchCount.class.getSimpleName());

        //设置jar包,参数是包含main方法的类
        job.setJarByClass(UserSearchCount.class);

        //通过job设置输入/输出格式
        //MR的默认输入格式是TextInputFormat,输出格式是TextOutputFormat;所以下两行可以注释掉
//        job.setInputFormatClass(TextInputFormat.class);
//        job.setOutputFormatClass(TextOutputFormat.class);

        //设置输入/输出路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        //设置处理Map阶段的自定义的类
        job.setMapperClass(SearchCountMapper.class);
        //设置map combine类,减少网路传出量
        //job.setCombinerClass(WordCountReduce.class);
        //设置处理Reduce阶段的自定义的类
        job.setReducerClass(SearchCountReducer.class);

        //如果map、reduce的输出的kv对类型一致,直接设置reduce的输出的kv对就行;如果不一样,需要分别设置map, reduce的输出的kv类型
        //注意:此处设置的map输出的key/value类型,一定要与自定义map类输出的kv对类型一致;否则程序运行报错
//        job.setMapOutputKeyClass(Text.class);
//        job.setMapOutputValueClass(IntWritable.class);

        //设置reduce task最终输出key/value的类型
        //注意:此处设置的reduce输出的key/value类型,一定要与自定义reduce类输出的kv对类型一致;否则程序运行报错
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        //提交作业
        job.waitForCompletion(true);
    }

    public static class SearchCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {

        //定义共用的对象,减少GC压力
        Text userIdKOut = new Text();
        IntWritable vOut = new IntWritable(1);

        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

            //获得当前行的数据
            //样例数据:20111230111645  169796ae819ae8b32668662bb99b6c2d        塘承高速公路规划线路图  1       1       http://auto.ifeng.com/roll/20111212/729164.shtml
            String line = value.toString();

            //切分,获得各字段组成的数组
            String[] fields = line.split("\t");

            //因为要统计每个user搜索并查看URL的次数,所以将userid放到输出key的位置
            //注意:MR编程中,根据业务需求设计key是很重要的能力
            String userid = fields[1];

            //设置输出的key的值
            userIdKOut.set(userid);
            //输出结果
            context.write(userIdKOut, vOut);
        }
    }

    public static class SearchCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {

        //定义共用的对象,减少GC压力
        IntWritable totalNumVOut = new IntWritable();

        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            int sum = 0;

            for(IntWritable value: values) {
                sum += value.get();
            }

            //设置当前user搜索并查看总次数
            totalNumVOut.set(sum);
            context.write(key, totalNumVOut);
        }
    }
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值