MapReduce编程

MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算。MapReduce主要包括两项操作:Map和Reduce。

  • Map是把一组数据一对一地映射为另外一组数据,其映射的规则由一个函数来指定。
  • Reduce是对一组数据进行归约,归约的规则由一个函数来指定。

在MapReduce中,没有一个值是单独存在的,每个值都会有一个键与其关联,键是值的标识。值和键成对出现,称为键值对(key-value)
在MapReduce编程中,键值对表示为<key, value>的形式。其核心的函数则是Map和Reduce函数,这两个函数由用户负责实现,功能是按一定的映射规则将输入的<key, value>对转换成另一个或一批<key, value>对输出。

为什么需要MapReduce

由于MapReduce采用了分布式的计算模型,因此可以避免传统多线程处理方案的缺陷。

  1. 不需要人为地进行任务划分。MapReduce框架可以根据数据存储的位置进行任务的分配,提高计算速度。
  2. 无论是拆分、排序,还是合并、输出,MapReduce都提供了默认的方法,在不需要重写这些方法时可以直接调用默认方法,节约编程时间,提高效率。
  3. 计算速度将不再受计算机硬件的限制。 MapReduce是一个分布式的框架,程序分布式执行,仅依赖于集群的数量,能大大提高数据的处理速度。

MapReduce的优势

  1. 可以处理各种类型的数据
  2. 本地计算
  3. 动态灵活的资源分配和调度
  4. 简化程序员的编程工作
  5. 高可扩展性
  6. 高可靠性

MapReduce框架组成

在这里插入图片描述

  1. ResourceManager(RM),集群的资源管理器。负责整个系统的资源管理和分配。
  2. ApplicationMaster(AM),MapReduce应用程序管家。管理在集群中运行的每个应用程序实例。
  3. NodeManager(NM),节点管理器。管理集群中各个子节点。
  4. Container,容器。集群中的资源抽象,它封装了某个节点上的多维度资源,如内存、CPU、磁盘、网络等。

截止目前,MapReduce共有二个版本,MapReduce v1和MapReduce v2。
在Hadoop 2.X中,我们将资源管理功能剥离为一种通用的分布式应用管理框架YARN。在YARN中,通过加入ApplicationMaster这个可变更的部分,用户可以针对不同的编程模型编写自己的ApplicationMaster,让更多的编程模型运行在Hadoop集群中。

MapReduce主要分为Map操作和Reduce操作,Reduce会接收从不同节点输出的Map中间数据。为了方便计算,MapReduce框架会确保每个Reduce的输入都是按key排序的。系统执行排序的过程(将Map输出作为输入传给Reduce)称为Shuffle(即混洗)。 Shuffle的职责就是:把Map的输出结果有效地传送到Reduce端。

MapReduce Java API

Java API主要位于org.apache.hadoop.mapreduceorg.apache.hadoop.io 包中,这些API能够支持的操作包括:启动作业、设置作业信息、设置Map、设置Reduce等。

MapReduce API编程思路

MapReduce Java API 为客户端提供了并行编程的接口,以便于实现分布式计算。

  1. 实例化Configuration

Configuration 类位于 org.apahce.hadoop.conf 包中,它封装了客户端或服务器的配置。默认情况下,Configuration 的实例会自动加载HDFS 的配置文件 core-site.xml,从中获取 Hadoop 集群的配置信息。实例化代码如下:

Configuration conf = new Configuration();
  1. 实例化Job

在 MapReduce 中,用户创建应用程序后,会通过 Job 来描述它各个方面的工作,然后提交工作并检测其进展。

Job job = Job.getInstance(conf, "word_count"); // 通过 confg 配置获取Job实例,并将作业命名为 word_count
  1. 设置作业任务

得到 Job 实例后,就可以使用该实例提供的成员方法来设置作业执行的任务和一些需要的类型等信息来,例如 map 任务、reduce 任务、输入格式、输出格式等。

返回类型方法名及参数功能说明
voidsetJarByClass(Class<?> cls)通过传入的 class 找到 job 的 jar 包
voidsetJobName(String name)设置 job 的名字
voidsetInputFormatClass(Class<? extends InputFormat> cls)设置 job 的输入文件格式
voidsetOutputFormatClass(Class<? extends OutputFormat> cls)设置 job 的输出文件格式
voidsetMapperClass(Class<? extends Mapper> cls)设置 job 的 Mapper 类,该类执行 map 函数
voidsetReducerClass(Class<? extends Reducer> cls)设置 job 的 Reduce 类,该类执行 reduce 函数
voidsetMapOutputKeyClass(Class<?> theClass)设置 map 输出 key 的数据类型
voidsetMapOutputValueClass(Class<?> theClass)设置 map 输出 value 的数据类型
voidsetOutputKeyClass(Class<?> theClass)设置 job 输出 key 的数据类型
voidsetOutputValueClass(Class<?> theClass)设置 job 输出 value 的数据类型
job.setJarByClass(MyJob.class);
job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class);
  1. 设置输入输出路径

执行 MapReduce 任务依赖的存储系统是 HDFS ,因此需要使用 Path 类来封装文件路径。此外还要使用文件输入格式类 FIleInputFormat 和文件输出格式类 FileOutFormat 进行路径的添加。

FileInputFormat.addInputPath(job,new Path("/input"));
FileOutputFormat.setOutputPath(job,new Path("/output"));
  1. 作业的启动与执行

作业配置完成后,便可以启动作业。

boolean flg = job.waitForCompletion(true);
System.exit(flg?0:1);
  1. 作业的监控与管理
MapReduce的数据类型

序列化:序列化(serialization)是指将结构化对象转化为字节流以便在网络上传输或写到磁盘进行永久存储的过程。
反序列化(deserialization)是指将字节流转换回结构化对象的逆过程。
Hadoop使用自身的序列化存储格式,实现Writable接口: Writable接口位于 org.apahce.hadoop.io 包中,定义了两个方法:
一个将数据写到DataOutput二进制流,另一个从DataInput二进制流读取数据。

  • Java基本类型封装: Writable类的内部都包含一个对应基本类型的成员变量value,用户可以通过get()和set()方法获取或存储value的值。
Java基本类型Writable类Java基本类型Writable类
booleanBooleanWritablebyteByteWritable
shortShortWritablefloat/doubleFloatWritable/DoubleWritable
intIntWritablelongLongWritable
  • Text类型: Text类使用UTF-8编码存储文本,可以看作Java中String类型的Writable封装。它提供了序列化、反序列化和在字节级别上比较文本的方法。
返回类型方法名功能描述
voidappend(byte[] utf8, int start, int len)在给定的 text 末尾追加一个字节数组
intcharAt(int position)返回 position 位置上字符串的 UTF-8 编码值
voidclear()清空字符
static Stringdecode(byte[] utf8)将提供的字节数组转换为 UTF-8 编码的字符串
static ByteBufferencode(String string)将提供的字符串转换为 UTF-8 编码的字节数组
booleanequals(Object o)判断两个 text 的内容是否相同
intfind(String what)返回指定字符串在 text 中的位置
intfind(String what, int start)返回从 start 位置后指定字符串在 text 中的位置
byte[]getBytes()返回原始字节
intgetLength()返回字节数组里字节的数量
voidreadField(DataInput in)反序列化
static StringreadString(DataINput in)从输入读取一个 UTF-8 编码的字符串
voidset(String string)设置值为一个字符串
voidset(byte[] utf-8)设置值为一个 UTF-8 编码的字节数组
static voidskip(DataInput in)在输入中跳过一个 text
StringtoString()将 text 转换为字符串
voidwrite(DataOutput out)序列化
static intwrite(DataOutput out, String s)将一个 UTF-8 编码的字符串写入输出
  • NullWritable类型: 是一个非常特殊的Writable类型,序列化不包含任何字符,不会从数据流中读数据也不会写入数据。

文件输入

  • CombineTextInputFormat
    • 如果一个文件的大小比Block小,它将不会被划分。当Hadoop处理很多小文件的时候,由于FileInputFormat不会对小文件进行划分,所以每一个小文件都会被当做一个split并分配一个Map任务,导致效率低下。CombineTextInputFormat 可以将多个文件打包到一个输入单元中,这样每次Map操作就会有更多的数据来处理。
  • TextInputFormat
    • TextInputFormat是默认的InputFormat,它把输入文件的每一行作为单独的一个记录(默认以换行符或回车符作为一行记录)。键是LongWritable类型,存储该行在整个文件中的字节偏移量;值是这行的内容,不包括行终止符(换行符、回车符),它被打包成一个Text对象。
  • KeyValueTextInputFormat
    • KeyValueTextInputFormat会用分隔符将一行的数据分割为一个key-value键值对,分隔符前面的字符为key,后面的字符为value。它的默认值是一个制表符(\t)。

MapReduce的输出

处理输出格式的类都继承自OutputFormat类。OutputFormat位于org.apache.hadoop.mapreduce包,它的功能跟前面描述的InputFormat类相似,OutputFormat的实例会将输出的键值对写到本地磁盘或HDFS上。一般来说,Mapper类和Reducer类都会用到OutputFormat类。Mapper类用它存储中间结果,Reducer类用它存储最终结果。

  • TextOutputFormat
    • TextOutputFormat是默认的输出格式,它将每条记录写为文本行。它的键和值可以是任意的类型,因为TextOutputFormat会调用toString()方法将它们转换为字符串。每个key-value键值对由制表符进行分割。
  • 延迟输出
    • FileOutputFormat的子类在即使没有数据的情况下也会产生一个空文件。但有时候我们并不想创建空文件,这时候就可以使用LazyOutputFormat。LazyOutputFormat类只有在第一条数据真正输出的时候才会创建文件。

搭建 Window Hadoop环境

  1. 下载 HadoopOnWindows 将 解压到一个**没有中文没有空格的路径 ****D:/devtools**,并改名为 hadoop 2.7.3
  2. apache 官方镜像仓库下载 apache-hadoop2.7.3 替换上述目录中的 binetc 目录文件
  3. 在 window 上面配置配置 hadoop 的环境变量:HADOOP_HOME,并将 %HADOOP_HOME%/bin 配置到 Path 中
  4. hadoop2.7.3 文件 bin 目录下的 hadoop.dll 文件放在系统盘 C:\Windows\System32 目录
  5. 重启 window 系统

开发步骤

这里采用 IDEA开发工具和 Maven 工程开发。

  1. 添加 hadoop 依赖和配置打包插件
  2. 继承 Mapper 类实现自己的 Mapper 类,并重写 map() 方法
  3. 继承 Reduce 类实现自己的 Reduce 类,并重写 reduce() 方法
  4. 程序主入口类编写,创建Job 和 任务入口
  5. 通过执行 mvn clean package 对工程进行构建
  6. 上传 jar 到 Linudx远程服务器任意目录,并执行程序输出到 output2 目录下
  7. 查询执行后的结果

Window经典案例单词统计开发

<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>

<repositories>
  <repository>
    <id>alimaven</id>
    <name>aliyun maven</name>
    <url>https://maven.aliyun.com/repository/public</url>
  </repository>
  <repository>
    <id>alimaven2</id>
    <name>aliyun maven2</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
  </repository>
</repositories>

<!--指定项目所需依赖库-->
<dependencies>
  <dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>2.7.3</version>
  </dependency>
  <dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-hdfs</artifactId>
    <version>2.7.3</version>
  </dependency>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.73</version>
  </dependency>
</dependencies>
public class MapReduceApplication {

    public static class WordCountMapper extends Mapper<LongWritable, Text, Text, LongWritable> {
        // key 行偏移量 value 每一行文本数据 context 上下文对象
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            // 将 k1,v1 转为 k2,v2
            String[] lines = value.toString().split(" ");
            for (String word : lines) {
                context.write(new Text(word),new LongWritable(1));
            }
        }
    }	
    public static class WordCountReduce extends Reducer<Text, LongWritable, Text, LongWritable> {
        // key: k2 values: 数字集合 v2
        @Override
        protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
            //遍历集合,将集合中的数值相加得到 v3
            long count = 0;
            for (LongWritable value : values) {
                count += value.get();
            }
            context.write(key,new LongWritable(count));
        }
    }
    public static void main(String[] args) throws Exception {
        //创建一个Job任务
        Job job = Job.getInstance(new Configuration());
        job.setJarByClass(MapReduceApplication.class); //main 方法所在的Class
        //指定 Job 的 Mapper 和 Mapper的key与value的输出类型
        job.setMapperClass(WordCountMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);
        //指定 Job 的 Reduce 和 Mapper的key与value的输出类型
        job.setReducerClass(WordCountReduce.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
        //指定 Job 的输入文件路径和输出文件路径
        FileInputFormat.addInputPath(job,new Path("hdfs://localhost:8020/input/*"));
        FileOutputFormat.setOutputPath(job,new Path("/home/cdhongit/output/"));
        //执行 Job 任务
        job.waitForCompletion(true);
    }
}

分析案例

猫眼电影网TOP10

public class DataMRJob {

    public static class MyMapper extends Mapper<LongWritable, Text, Text, Text> {
        private List<String> list = new ArrayList<String>();
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            JSONObject object = JSON.parseObject(value.toString());
            // 我不是药神,2018-07-05,徐峥|周一围|王传君,9.6
            String title = object.getString("片名");
            if(list.contains(title)){
                return;
            }
            list.add(title);
            String time = object.getString("上映时间");
            StringBuffer performerName = new StringBuffer();
            JSONObject performers = JSON.parseObject(object.getString("主演"));
            for (Object o : performers.values()) {
                performerName.append((String) o);
                performerName.append("@");
            }
            String performerNameStr = performerName.substring(0, performerName.length() - 1);
            String score = object.getString("评分");

            Text data = new Text(title + "," + time + "," + performerNameStr + "," + score);

            context.write(data, new Text());
        }
    }

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

        job.setJarByClass(DataMRJob.class);
        FileInputFormat.addInputPath(job, new Path("hdfs://localhost:8020/data/maoyanfull.json"));
        job.setMapperClass(MyMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);

        Path path = new Path("hdfs://localhost:8020/output");
        FileSystem fs = FileSystem.get(URI.create("hdfs://localhost:8020"),conf);
        if(fs.exists(path)){
            fs.delete(path,true);
        }

        FileOutputFormat.setOutputPath(job,path);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);

        boolean flg = job.waitForCompletion(true);
        System.exit(flg ? 0 : 1);
    }

}
-- 龙猫,2018-12-14,岛本须美@糸井重里@秦,9.2
create table maoyan(
  title string,
  create_time date,
  performers array<string>,
  score float
)
row format delimited
fields terminated by ','
collection items terminated by '@' ;
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

CDHong.it

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

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

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

打赏作者

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

抵扣说明:

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

余额充值