打怪升级之小白的大数据之旅(四十八)<初识MapReduce>

打怪升级之小白的大数据之旅(四十八)

初识MapReduce

上次回顾

在Hadoop开篇的时候,说过,大数据的主要用途就是对海量数据的存储与计算分析,通过学习Hadoop的HDFS,我们已经知道了海量数据可以通过集群的方式来进行分块存储,今天将为大家带来Hadoop的第二个核心模块–MapReduce,我们通常称它为MR

MapReduce概述

MapReduce定义

  • 我们再次回顾一下Hadoop开篇的那张图
    在这里插入图片描述
  • 我在前面说过,Hadoop2.0之后,MapReduce就对它自己内部的计算和资源调度进行了拆分,所以我们现在学习的MapReduce就是管理数据的计算这一块
  • HDFS存储数据是利用分布式进行存储,那么MapReduce对数据的计算同样也是,MapReduce是一个分布式运算程序的编程框架
  • 它的核心功能是将用户编写的逻辑代码和自带的默认组件整合成一个完整的分布式运算程序,并发运行在Hadoop集群上
  • 业务逻辑代码就是我们需要对数据进行怎样的运算得到我们想要的结果,自带默认模块就是它内部定义的一些辅助我们进行运算的模块

MapReduce的优缺点

每个程序的设计都有它的应用场景,所以不可能事事完美,下面就为大家介绍一下MR的优缺点

优点
  • 易于编程
    • 它简单的实现一些接口,可以完成一个分布式程序,简单来说,我们写一个分布式程序就和前面学习的程序逻辑是一样的,通过简单的串行程序通过MR就可以完成分布式运行
  • 扩展性强
    • 当我们计算的资源不够用时,可以通过增加节点的方法扩展它的计算能力而无需修改我们的编程逻辑
  • 高容错性
    • 就和HDFS存储时一样,当其中一个节点中挂掉了,我们可以将它的计算任务转移到另一个节点上运行,这样就不至于这个任务运行失败,这个过程是Hadoop内部完成的
  • 适合PB级以上海量数据的离线处理
    • 可以实现上千服务器集群并发工作,提供数据处理能力
缺点
  • 不擅长实时计算
    • MR无法像Mysql一样,在毫秒内返回结果
    • 实时计算我们后面会学习fink框架,它就是专门用来进行实时的数据计算
  • 不擅长流式计算
    • 流式计算的意思就是输入的数据是动态的,它会动态变化
    • MR的输入数据是静态的,不能动态变化,这是因为它自身的设计特点
  • 不擅长有向图(DAG)计算
    • 多个程序存在依赖关系,后一个程序的输入为前一个程序的输出
    • 因为MR在运算后会将输出结果写入到磁盘中,这么做会造成大量的磁盘IO,所以也不建议这么做

好了,总结一下,MR是一个用于离线的静态数据的计算框架,它可以通过简单的串行编程来实现分布式计算的功能,举个栗子,我们可以用它来计算分析我们历史的数据,比如去年一年的用户消费情况、历史中某某股票的交易走势等等

MapReduce核心思想

  • 核心思想就是它整个的运行逻辑,我先为大家通过一个示例来说明它整个的运行流程,然后针对里面的每个知识点进行讲解,这样通过整体性的认知来学习MapReduce可以有一个清晰地思路
  • MapReduce的工作分为Map和Reduce两个阶段,Map阶段的任务就是分,将同一个任务进行分布式的运行,reduce阶段的任务就是合,将分类运算后的结果进行统计,然后对完成的数据进行存储
    在这里插入图片描述
  • 我有三斤芝麻,里面是白芝麻和黑芝麻混装,我需要将它们区分开,并使用两个容器进行存储起来,并且统计一下不同芝麻粒的总数
  • 如果我一个人来做这个事情,那么我可能需要很多的时间,MR的核心思想就是下面这样:
    • 我找了西游记的几人来帮忙,我将我的混装芝麻分成了三分,分别给了悟空、八戒、唐僧三人,让他们来帮我进行分类和统计,他们做的这个工作就是MR中的map阶段
    • 白龙马和沙僧的任务就是对分好的芝麻进行存储和整个数量的统计

MapReduce进程

前面是MapReduce的运行原理,下面就是MapReduce在运行时具体工作的模块
一个完整的MapReduce程序在分布式运行时有三类实例进程:
(1)MrAppMaster:负责整个程序的过程调度及状态协调。
(2)MapTask:负责Map阶段的整个数据处理流程。
(3)ReduceTask:负责Reduce阶段的整个数据处理流程

Hadoop序列化

序列化这个知识点是在Java中的IO流时介绍过,我们再来回顾一下

  • 序列化就是把内存中的对象转换成字节序列,这样就可以在磁盘中进行持久化的保存,我们也可以通过网络来进行对象的传输
  • 通俗一点来说,我们程序中所创建、使用的对象都是运行在内存中的,该对象只能在我们当前的电脑中生存,如果需要持久化保存或者将该对象传输到别的电脑中,就需要利用序列化来进行
  • 因为Java序列化是重量级的,它里面还有封装一些我们Hadoop用不到的接口、API,所以Hadoop为自己量身定制了一个序列化的机制

常用数据序列化类型

我们在IO的学习时说到过,我们要对程序中的对象进行保存就需要使用序列化,所以在Hadoop中,它定义了自己的序列化数据类型

Java类型Hadoop Writable类型
BooleanBooleanWritable
ByteByteWritable
IntegerIntWritable
FloatFloatWritable
LongLongWritable
DoubleDoubleWritable
StringText
MapMapWritable
ArrayArrayWritable

自定义序列化

在企业开发中,我们只使用基本的序列化类型当然满足不了我们的需求,因此我们就要学习如何自定义序列化,我们以如何序列化一个Java Bean来举例,实现对象序列化的步骤如下:

  • 必须实现Writable接口

  • 反序列化时,需要通过反射调用空参构造函数,因此,我们必须实现空参构造器(下面的例子是我们下面的实例中的)

  • 重写序列化的方法

    @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 {
    	upFlow = in.readLong();
    	downFlow = in.readLong();
    	sumFlow = in.readLong();
    }
    
  • 反序列化和序列化的顺序必须完全一致

  • 要想把结果显示在文件中,需要重写toString(),可用”\t”分开,方便后续用

  • 如果需要将自定义的bean放在key中传输,则还需要实现Comparable接口,因为MapReduce框中的Shuffle过程要求对key必须能排序,Shuffle我后面会详细讲解

    @Override
    public int compareTo(FlowBean o) {
    	// 倒序排列,从大到小
    	return this.sumFlow > o.getSumFlow() ? -1 : 1;
    }
    

案例实操

学习完上面的知识点,为了便于后面对MapReduce框架原理的讲解,我们首先要进行下面两个案例的实操

MapReduce程序的核心流程

前面核心思想是按照一个现实的例子来举例的,下面我按照MapReduce程序真正的流程来为大家介绍一下
在这里插入图片描述
(1)分布式的运算程序往往需要分成至少2个阶段。
(2)第一个阶段的MapTask并发实例,完全并行运行,互不相干。
(3)第二个阶段的ReduceTask并发实例互不相干,但是他们的数据依赖于上一个阶段的所有MapTask并发实例的输出。
(4)MapReduce编程模型只能包含一个Map阶段和一个Reduce阶段,如果用户的业务逻辑非常复杂,那就只能多个MapReduce程序,串行运行
(5)map阶段中真正实现任务的模块就是MapTask,Reduce阶段真正实现任务的模块是ReduceTask

实现官方的WordCount

  • 还记得在本地模式、集群模式我们一直使用的那个统计一个文件中单词出现次数的案例么?今天我们就来自己实现一个WordCount,下面的编码步骤是框架规定的,大家多写几遍,等我后面讲解MapReduce框架原理的时候就懂了

测试数据

// 文件名称:
hello.txt
// 内容如下
hello hello
bigdata hello
I like bigdata
I like java
I like Hadoop

编码思路

  • 按照前面我们学习的MapReduce核心思想,我们可以将这个需求分成三步
    • 一个主程序,我们定义为WcDriver类
    • 一个用于实现map的类 WcMapper
    • 一个用于实现reduce的类 WcReducer
  • WcMapper
    • 将MapTask传给我们的文本内容转换成java的数据类型
    • 根据空格来将每一行的单词进行切分
    • 将单词输出为k,v(hello,1)(hello,1)
  • WcReducer
    • 汇总各个key的个数
    • 输出key的总次数
  • WcDriver
    • 获取配置信息,获取job实例
    • 指定jar包所在路径
    • 关联WcMapper/WcReducer类
    • 指定WcMapper输出的k,v类型
    • 指定最终输出的k,v类型
    • 指定输入文件路径
    • 指定输出文件路径
    • 提交作业

环境准备

  • 创建mavern工程(这个我写了不少了,就省略了)
  • pom.xml添加依赖
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>3.1.3</version>
        </dependency>
    </dependencies>
    
  • 在项目的src/main/resources目录下,新建一个文件,命名为“log4j2.xml”,在文件中填入(HDFS案例中说过)
    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="error" strict="true" name="XMLConfig">
        <Appenders>
            <!-- 类型名为Console,名称为必须属性 -->
            <Appender type="Console" name="STDOUT">
                <!-- 布局为PatternLayout的方式,
                输出样式为[INFO] [2018-01-22 17:34:01][org.test.Console]I'm here -->
                <Layout type="PatternLayout"
                        pattern="[%p] [%d{yyyy-MM-dd HH:mm:ss}][%c{10}]%m%n" />
            </Appender>
    
        </Appenders>
    
        <Loggers>
            <!-- 可加性为false -->
            <Logger name="test" level="info" additivity="false">
                <AppenderRef ref="STDOUT" />
            </Logger>
    
            <!-- root loggerConfig设置 -->
            <Root level="info">
                <AppenderRef ref="STDOUT" />
            </Root>
        </Loggers>
    
    </Configuration>
    

编写程序

  • WcMapper类
    package com.company.wordcount;
    
    import org.apache.hadoop.io.LongWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Mapper;
    
    import java.io.IOException;
    
    /*
        作用 :用来实现需要在MapTask中实现的功能
    
        Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>:
            两组:
                第一组:
                    KEYIN :读取数据的偏移量的类型
                    VALUEIN : 读取的一行一行的内容的类型
                第二组:
                     KEYOUT : 写出的Key的类型(在这指的是单词)
                     VALUEOUT :写出的Value的类型(在这指的是单词的数量)
     */
    //自定义的类想要实现MapTask中实现的功能必须继承Mapper
    public class WCMapper extends Mapper<LongWritable, Text,Text,LongWritable> {
        //key中存放的就是单词
        private Text outKey = new Text();
        //value中存放的是单词的数量
        private LongWritable outValue = new LongWritable();
        /**
         * 在map方法中去实现需要在MapTask中实现的功能
         * 注意:map方法在被循环调用(MR框架-MapTask程序)
         *      每调用一次就会传入一行内容
         * @param key :读取数据的偏移量
         * @param value :读取的一行一行的内容
         * @param context :上下文(在这用来通过上下文将K,V写出)
         * @throws IOException
         * @throws InterruptedException
         */
        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {
            //1.将内容进行分割
            //1.1将Text转成字符串为了方便操作
            String line = value.toString();
            String[] words = line.split(" ");
            //2.封装K,V
            for (String word : words) {
                //设置key的值
                outKey.set(word);
                //设置value的值
                outValue.set(1);
                //3.将K,V写出去
                context.write(outKey,outValue);
            }
    
        }
    }
    
  • WcReducer类
    package com.company.wordcount;
    
    import org.apache.hadoop.io.LongWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Mapper;
    import org.apache.hadoop.mapreduce.Reducer;
    
    import java.io.IOException;
    
    /*
        作用 :用来实现需要在RedcueTask中实现的功能
    
        Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
            两组:
                第一组:
                    KEYIN :读取的Key的类型(Mapper输出的Key的类型)
                    VALUEIN :读取的Value的类型(Mapper输出的Value的类型)
                第二组:
                    KEYOUT :输出的Key的类型(在这指的是单词)
                    VALUEOUT :输出的Value的类型(在这指的是单词的数量)
    
     */
    //自定义的类想要实现ReduceTask中实现的功能必须继承Reducer
    public class WCReducer extends Reducer<Text, LongWritable,Text,LongWritable> {
        //输出的value在这用来设置单词的数量
        private LongWritable outValue = new LongWritable();
        /**
         * 在reduce方法中去实现需要在ReduceTask中实现的功能
         * 注意::reduce方法在被循环调用(MR框架-ReduceTask程序)
         *      每调用一次就会传入一组数据(key值相同为一组)
         * @param key :读取的key值
         * @param values :一组数据中所有的value
         * @param context : 上下文(在这用来将K,V写出去)
         * @throws IOException
         * @throws InterruptedException
         */
        @Override
        protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
            long sum = 0;
            //1.累加所有的value
            for (LongWritable value : values) {
                //将LongWritable转换成long
                long v = value.get();
                sum += v;
            }
            //2.封装K,V
            //设置单词的数量
            outValue.set(sum);
            //3.写出K,V
            context.write(key,outValue);
        }
    }
    
  • WcDriver类
    package com.company.wordcount;
    
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.Path;
    import org.apache.hadoop.io.LongWritable;
    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;
    
    /*
        程序的入口(主程序)
     */
    public class WCDriver {
        public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
            //1.创建Job对象(提交的任务)
            Job job = Job.getInstance(new Configuration());
    
            //2.设置参数
            //2.1设置Jar加载路径 --在本地运行不需要设置,在集群上运行需要设置
            //作用 :用来告诉集群主类是谁。
            job.setJarByClass(WCDriver.class);
            //2.2设置Mapper和Reducer类
            job.setMapperClass(WCMapper.class);
            job.setReducerClass(WCReducer.class);
            //2.3设置Mapper输出的K,V类型
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(LongWritable.class);
            //2.4设置最终输出的K,V类型(在这是Redcuer的K,V类型)
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(LongWritable.class);
            //2.5设置输入(数据)和输出(结果)的路径
            FileInputFormat.setInputPaths(job,new Path("E:\\io\\input"));
            //注意:输出的路径必须不存在-否则报错
            FileOutputFormat.setOutputPath(job,new Path("E:\\io\\output2"));
    
            //3.提交Job
            /*
                参数 :是否打印进度
                返回值 :如果成功返回true
             */
            boolean result = job.waitForCompletion(true);
            //退出JVM的方式 :0正常退出,1非正常退出
            System.exit(result ? 0 : 1);
        }
    }
    

输出结果:

Hadoop	1
I	3
bigdata	2
hello	3
java	1
like	3
在集群中运行wordcount
  • 还是前面的案例,我们现在实现一下在集群中运行,我们需要将打成Jar包在集群上运行,使用Maven中学习到的知识,将它进行package,然放到我们的服务器上
  • 运行前,还需要修改一下我们的WcDriver类,作用是让它识别我们jar包的位置
  • 集群运行wordcount的命令
        命令:hadoop jar xxx.jar 主类的全类名  参数1(输入路径)  参数2(输出路径)
    
  • WcDriver类
    package com.company.wordcount;
    
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.Path;
    import org.apache.hadoop.io.LongWritable;
    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;
    
    /*
        打成Jar包在集群上运行
    
        命令:hadoop jar xxx.jar 主类的全类名  参数1(输入路径)  参数2(输出路径)
     */
    public class WCDriver2 {
        public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
            //1.创建Job对象(提交的任务)
            Job job = Job.getInstance(new Configuration());
    
            //2.设置参数
            //2.1设置Jar加载路径 --在本地运行不需要设置,在集群上运行需要设置
            //作用 :用来告诉集群主类是谁。
            job.setJarByClass(WCDriver2.class);
            //2.2设置Mapper和Reducer类
            job.setMapperClass(WCMapper.class);
            job.setReducerClass(WCReducer.class);
            //2.3设置Mapper输出的K,V类型
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(LongWritable.class);
            //2.4设置最终输出的K,V类型(在这是Redcuer的K,V类型)
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(LongWritable.class);
            //2.5设置输入(数据)和输出(结果)的路径
            FileInputFormat.setInputPaths(job,new Path(args[0]));
            //注意:输出的路径必须不存在-否则报错
            FileOutputFormat.setOutputPath(job,new Path(args[1]));
    
            //3.提交Job
            /*
                参数 :是否打印进度
                返回值 :如果成功返回true
             */
            boolean result = job.waitForCompletion(true);
            //退出JVM的方式 :0正常退出,1非正常退出
            System.exit(result ? 0 : 1);
        }
    }
    
从Windows向集群提交Job

前面我们是将jar包放到了集群上,我们也可以在windows本地来向集群提交job,来进行wordcount的任务

  • WcDriver类
    package com.company.wordcont;
    
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.Path;
    import org.apache.hadoop.io.LongWritable;
    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;
    
    
    /*
        从Windows向集群提交Job
    
        1.配置:
             //设置在集群运行的相关参数-设置HDFS,NAMENODE的地址
            conf.set("fs.defaultFS", "hdfs://hadoop102:9820");
            //指定MR运行在Yarn上
            conf.set("mapreduce.framework.name","yarn");
            //指定MR可以在远程集群运行
            conf.set("mapreduce.app-submission.cross-platform","true");
            //指定yarn resourcemanager的位置
            conf.set("yarn.resourcemanager.hostname", "hadoop103");
        2.打包
        3.将job.setJarByClass(WCDriver3.class);注释掉
          添加job.setJar(jar包路径)
        4.在EditConfigurations中
            VM Options : -DHADOOP_USER_NAME=atguigu
            Program Arguments : hdfs://hadoop102:9820/input hdfs://hadoop102:9820/output3
     */
    public class WCDriver3 {
        public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
            Configuration conf = new Configuration();
            //设置在集群运行的相关参数-设置HDFS,NAMENODE的地址
            conf.set("fs.defaultFS", "hdfs://hadoop102:9820");
            //指定MR运行在Yarn上
            conf.set("mapreduce.framework.name","yarn");
            //指定MR可以在远程集群运行
            conf.set("mapreduce.app-submission.cross-platform","true");
            //指定yarn resourcemanager的位置
            conf.set("yarn.resourcemanager.hostname", "hadoop103");
    
            Job job = Job.getInstance(conf);
            //job.setJarByClass(WCDriver3.class);
            //指定向集群提交的Job包路径
            job.setJar("D:\\class_video\\0323\\06-hadoop\\3.代码\\MRDemo\\target\\MRDemo-1.0-SNAPSHOT.jar");
    
            job.setMapperClass(WCMapper.class);
            job.setReducerClass(WCReducer.class);
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(LongWritable.class);
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(LongWritable.class);
            FileInputFormat.setInputPaths(job,new Path(args[0]));
            FileOutputFormat.setOutputPath(job,new Path(args[1]));
    
    
            boolean result = job.waitForCompletion(true);
            System.exit(result ? 0 : 1);
        }
    }
    
    

统计手机流量(序列化实例)

我们通过手机流量这个实例来说明如何自定义序列化以及如何使用序列化完成我们的MapReduce任务,这个案例就以本地运行来举例

  • 下面文件中,我们可以看到,每部手机访问了不同的网站,不同的网站也会被不同的手机访问
  • 我的需求就是:统计每一个手机号耗费的总上行流量、下行流量、总流量,
  • 测试数据
// 文件名: phone_data.txt
1	13736230513	192.196.100.1	www.company.com	2481	24681	200
2	13846544121	192.196.100.2			264	0	200
3 	13956435636	192.196.100.3			132	1512	200
4 	13966251146	192.168.100.1			240	0	404
5 	18271575951	192.168.100.2	www.company.com	1527	2106	200
6 	84188413	192.168.100.3	www.company.com	4116	1432	200
7 	13590439668	192.168.100.4			1116	954	200
8 	15910133277	192.168.100.5	www.hao123.com	3156	2936	200
9 	13729199489	192.168.100.6			240	0	200
10 	13630577991	192.168.100.7	www.shouhu.com	6960	690	200
11 	15043685818	192.168.100.8	www.baidu.com	3659	3538	200
12 	15959002129	192.168.100.9	www.company.com	1938	180	500
13 	13560439638	192.168.100.10			918	4938	200
14 	13470253144	192.168.100.11			180	180	200
15 	13682846555	192.168.100.12	www.qq.com	1938	2910	200
16 	13992314666	192.168.100.13	www.gaga.com	3008	3720	200
17 	13509468723	192.168.100.14	www.qinghua.com	7335	110349	404
18 	18390173782	192.168.100.15	www.sogou.com	9531	2412	200
19 	13975057813	192.168.100.16	www.baidu.com	11058	48243	200
20 	13768778790	192.168.100.17			120	120	200
21 	13568436656	192.168.100.18	www.alibaba.com	2481	24681	200
22 	13568436656	192.168.100.19			1116	954	200
  • 编码思路

    • 首先我们要知道我们需要数据的字段都有哪些:手机号,上行流量、下行流量
    • map阶段
      • 读取一行数据,切分字段
      • 抽取手机号、上行/下行流量
      • 以手机号为key, 流量为value,因此我们需要定义一个javaBean
      • 因为对象需要进行传输,所以需要进行序列化操作
    • reduce阶段
      • 累加上行和下行流量就可得到该手机号的上行、下行总流量
  • 知道了编写思路,我们就可以定义我们的类了

    • FlowBean
      • 对数据进行序列化
    • FlowMapper
      • 抽取所需字段
    • FlowReducer
      • 统计手机的流量
    • FlowDriver
      • 主程序,用于进行配置信息、提交job等,前面wordcount写过了
  • FlowBean类

    package com.company.phoneflow;
    import org.apache.hadoop.io.Writable;
    
    import java.io.DataInput;
    import java.io.DataOutput;
    import java.io.IOException;
    
    /*
        自定义一个类并实现Hadoop的序列化框架
    
        步骤:
            1.自定义类并实现Writable接口
            2.重写wrtie和readFileds方法
            3.注意:读取数据时必须和写的顺序保持一致
     */
    public class FlowBean implements Writable {
        private long upFlow;
        private long downFlow;
        private long sumFlow;
    
        public FlowBean() {
        }
    
        public FlowBean(long upFlow, long downFlow) {
            this.upFlow = upFlow;
            this.downFlow = downFlow;
            this.sumFlow = upFlow + downFlow;
        }
    
        /*
                序列化时调用的方法
             */
        public void write(DataOutput out) throws IOException {
            out.writeLong(upFlow);
            out.writeLong(downFlow);
            out.writeLong(sumFlow);
        }
    
        /*
            反序列化时调用的方法
         */
        public void readFields(DataInput in) throws IOException {
            //注意:读取数据时必须和写的顺序保持一致
            upFlow = in.readLong();
            downFlow = in.readLong();
            sumFlow = in.readLong();
        }
    
        @Override
        public String toString() {
            return upFlow + " " + downFlow + " " + sumFlow;
        }
    
        public long getUpFlow() {
            return upFlow;
        }
    
        public void setUpFlow(long upFlow) {
            this.upFlow = upFlow;
        }
    
        public long getDownFlow() {
            return downFlow;
        }
    
        public void setDownFlow(long downFlow) {
            this.downFlow = downFlow;
        }
    
        public long getSumFlow() {
            return sumFlow;
        }
    
        public void setSumFlow(long sumFlow) {
            this.sumFlow = sumFlow;
        }
    }
        @Override
    public String toString() {
        return "上行流量: " + upFlow + " 下行流量: " + downFlow + " 总流量: " + sumFlow;
    }
    
  • FlowMapper

    package com.company.phoneflow;
    
    import org.apache.hadoop.io.LongWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Mapper;
    
    import java.io.IOException;
    
    /*
        作用 :用来实现需要在MapTask中实现的功能
    
        Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>:
            两组:
                第一组:
                    KEYIN :读取数据的偏移量的类型
                    VALUEIN : 读取的一行一行的内容的类型
                第二组:
                     KEYOUT : 写出的Key的类型(在这指的是手机号)
                     VALUEOUT :写出的Value的类型(在这指的是存储流量数据的对象)
     */
    public class FlowMapper extends Mapper<LongWritable,Text, Text,FlowBean> {
        /**
         * 在map方法中去实现需要在MapTask中实现的功能
         * 注意:map方法在被循环调用(MR框架-MapTask程序)
         *      每调用一次就会传入一行内容
         * @param key :读取数据的偏移量
         * @param value :读取的一行一行的内容
         * @param context :上下文(在这用来通过上下文将K,V写出)
         * @throws IOException
         * @throws InterruptedException
         */
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            //1.将内容进行切割
            String[] line = value.toString().split("\t");
            //2.封装K,V
            Text outKey = new Text(line[1]);
            FlowBean outValue = new FlowBean(Long.parseLong(line[line.length - 3]),
                    Long.parseLong(line[line.length - 2]));
            //3.写出K,V
            context.write(outKey,outValue);
        }
    }
    
    
  • FlowReducer

    package com.company.phoneflow;
    
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Reducer;
    
    import java.io.IOException;
    
    /*
        作用 :用来实现需要在RedcueTask中实现的功能
    
        Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
            两组:
                第一组:
                    KEYIN :读取的Key的类型(Mapper输出的Key的类型)
                    VALUEIN :读取的Value的类型(Mapper输出的Value的类型)
                第二组:
                    KEYOUT :输出的Key的类型(在这指的是单词)
                    VALUEOUT :输出的Value的类型(在这指的是单词的数量)
    
     */
    public class FlowReducer extends Reducer<Text,FlowBean,Text,FlowBean> {
    
        /**
         * 在reduce方法中去实现需要在ReduceTask中实现的功能
         * 注意::reduce方法在被循环调用(MR框架-ReduceTask程序)
         *      每调用一次就会传入一组数据(key值相同为一组)
         * @param key :读取的key值
         * @param values :一组数据中所有的value
         * @param context : 上下文(在这用来将K,V写出去)
         * @throws IOException
         * @throws InterruptedException
         *
         * key                 value
         * 153211111111     100 200 300
         * 153211111111     80  20  100
         * 153211111111     80  20  100
         */
        @Override
        protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
            long sumUpFlow = 0;//总上行流量
            long sumDowFlow = 0;//总下行流量
            //遍历所有的value
            for (FlowBean value : values) {
                sumUpFlow += value.getUpFlow();
                sumDowFlow += value.getDownFlow();
            }
            //2.封装K,V
            FlowBean outValue = new FlowBean(sumUpFlow, sumDowFlow);
            //3.写出K,V
            context.write(key,outValue);
        }
    }
    
  • FlowDriver

    package com.company.phoneflow;
    
    import com.atguigu.wc.WCDriver;
    import com.atguigu.wc.WCMapper;
    import com.atguigu.wc.WCReducer;
    
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.Path;
    import org.apache.hadoop.io.LongWritable;
    
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Job;
    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;
    
    public class FlowDriver {
        public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
            //1.创建Job对象(提交的任务)
            Job job = Job.getInstance(new Configuration());
            //2.设置参数
            //2.2设置Mapper和Reducer类
            job.setMapperClass(FlowMapper.class);
            job.setReducerClass(FlowReducer.class);
            //2.3设置Mapper输出的K,V类型
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(FlowBean.class);
            //2.4设置最终输出的K,V类型(在这是Redcuer的K,V类型)
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(FlowBean.class);
            //2.5设置输入(数据)和输出(结果)的路径
            //注意:输出的路径必须不存在-否则报错
            FileInputFormat.setInputPaths(job,new Path("D:\\io\\input2"));
            FileOutputFormat.setOutputPath(job,new Path("D:\\io\\output22"));
            //3.提交Job
            /*
                参数 :是否打印进度
                返回值 :如果成功返回true
             */
            job.waitForCompletion(true);
        }
    }
    

输出结果:

84188413	上行流量: 4116 下行流量: 1432 总流量: 5548
13470253144	上行流量: 180 下行流量: 180 总流量: 360
13509468723	上行流量: 7335 下行流量: 110349 总流量: 117684
13560439638	上行流量: 918 下行流量: 4938 总流量: 5856
13568436656	上行流量: 3597 下行流量: 25635 总流量: 29232
13590439668	上行流量: 1116 下行流量: 954 总流量: 2070
13630577991	上行流量: 6960 下行流量: 690 总流量: 7650
13682846555	上行流量: 1938 下行流量: 2910 总流量: 4848
13729199489	上行流量: 240 下行流量: 0 总流量: 240
13736230513	上行流量: 2481 下行流量: 24681 总流量: 27162
13768778790	上行流量: 120 下行流量: 120 总流量: 240
13846544121	上行流量: 264 下行流量: 0 总流量: 264
13956435636	上行流量: 132 下行流量: 1512 总流量: 1644
13966251146	上行流量: 240 下行流量: 0 总流量: 240
13975057813	上行流量: 11058 下行流量: 48243 总流量: 59301
13992314666	上行流量: 3008 下行流量: 3720 总流量: 6728
15043685818	上行流量: 3659 下行流量: 3538 总流量: 7197
15910133277	上行流量: 3156 下行流量: 2936 总流量: 6092
15959002129	上行流量: 1938 下行流量: 180 总流量: 2118
18271575951	上行流量: 1527 下行流量: 2106 总流量: 3633
18390173782	上行流量: 9531 下行流量: 2412 总流量: 11943

总结

  • 本章我们学习了MapReduce的基本概述、核心思想以及Hadoop的序列化
  • 通过两个实例来认识MapReduce的工作流程,这两个案例大家一定要多写几遍,我们后面学习的知识点、案例都是在此基础上进行扩展的
  • 下一章,就正式对MapReduce的框架原理进行讲解哈,学习了框架原理就会更加清晰地了解MapReduce
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值