Hadoop学习1--关于MapReduce

来源:互联网 发布:个人博客推荐 知乎 编辑:程序博客网 时间:2024/06/05 11:12

Hadoop学习–关于MapReduce

前言

因为工作原因,正好需要学习Hadoop相关的姿势,那么开始解锁。。。以下所有的内容都是根据看Hadoop权威指南第三版的笔记,做的一些记录。

随着硬盘存储容量多年来不断提升的同时,访问速度却没有质的提升。因此,试想可以有100个硬盘,每个硬盘存储1%的数据,并行读取,那么不到两分钟,就可以读取完1TB的数据。但是对多个硬盘的数据并行进行读写,有很多问题需要解决:

  1. 首先是硬件故障问题,一旦开始使用多个硬盘,那么万一某个硬件发生故障,就会导致数据丢失等问题。因此常见的做法就是使用“复制,replication”。系统保存数据的副本,冗余硬盘阵列RAID就是依照这个原理实现的,Hadoop的“HDFS,Hadoop Distributed FileSystem”也是这一类,但是采取的方法略有不同。

  2. 第二个问题是大多数分析任务需要以某种方式结合大部分数据来共同完成分析,通俗点说就是分析任务可能会涉及到所有硬盘中的数据。因此如何保证这种分析任务的正确性,是个很大的挑战。而MapReduce提出一个编程模型,它抽象出硬盘读写的问题,并且将其转化为对一个数据集的计算。模型由map和reduce两部分组成,且只有这两部分提供对外的接口,保证数据读取的正确性,MapReduce也有很高的可靠性。

总而言之,Hadoop提供了可靠的共享存储和分析的功能,HDFS实现数据的分布式存储,而MapReduce实现了数据的分析和处理。这两块是Hadoop的核心。

为啥不用关系型数据库?公司之前一直都是把数据放在oracle上,然后考虑到每天数据量海量,不用来分析浪费,而且用oracle的话,有时候那查询时间的酸爽。书上的解释是,MapReduce比较适合以批处理的方式处理需要分析的问题,尤其是动态分析,它适合一次写入,然后多次读取数据的应用,这和公司的要求很符合。而关系型数据库更适合持续更新的数据集。

Web服务器日志是典型的非规范化数据记录,因为每次都需要记录客户端主机全名,这回导致同一客户端的全名可能会出现多次。而这也是MapReduce非常适用于分析各种日志文件的原因之一 (这句话没太懂,是因为MapReduce提供是一种键值对的存储方式么?比如key就是主机名,然后value就是对应的日志内容?)

后面一些介绍就不做记录了,直接到第二章看MapReduce吧。。。
权威指南中涉及到的Hadoop项目,我觉得基本包含我后面要学以及用到的所有东西:
1. Common: 一系列组件和接口,用于分布式文件系统和IO(序列化、JavaRPC和持久化数据结构)
2. Avro:一种序列化系统,用于支持高效、跨语言的RPC和持久化数据。
3. MapReduce:分布式数据处理模型和执行环境,运行于集群中。
4. HDFS:分布式文件管理系统,也是运行于集群中。
5. Pig:数据流语言和运行环境,用以探究非常庞大的数据集。运行在HDFS和MapReduce上。
6. Hive:分布式,按列存储的数据仓库。它是用来管理HDFS中存储的数据,并提供基于SQL的查询语言用来查询数据,在运行过程中,sql会由引擎翻译成MapReduce作业。
7. Hbase:一种分布式的、按列存储的数据库。Hbase使用HDFS作为底层存储,同时支持MapReduce的批量式计算和点查询。
8. Zookeeper:一种分布式的、高可用性的协调服务。提供分布式锁之类的基本服务,用于构建分布式的应用。
9. Sqoop:这个工具用于在结构化数据存储和HDFS之间高效批量的传输数据。
10. Oozie:运行和调度Hadoop作业。MapReduce作业啊,Pig作业啊,之类的。

现在 Hadoop已经更新到了2.8版本了。书中也比较了2版本和以前1版本的区别。主要有:
- 新的YARN系统。
- HDFS联邦管理。
- HDFS高可用性。

MapReduce章节

它是一个用来处理数据的编程模型,书上分别用了Java、Ruby、Python和C++版本来写了程序,但是第一个程序我就没跑起来,忧桑啊。。。。MapReduce的优势就在于利用集群,处理大规模的数据集。书上的数据集是用的天气气象数据集,可以直接去天气预报网上下载。之前做一个比赛的时候,就是这样的,爬虫拿下来就好。书上用的NCDC,然后在书的网站上也能找到源码和样例数据。

书中使用了一个Unix工具awk来分析数据,以此为基础,比较HadoopMapReduce在数据分析上的优势。awk在做数据分析的时候,依然面临这一些问题:
1. 将任务划分成大小相同的作业并不是一件容易实现的事。因为不同年份数据文件的大小差异很大,这就导致了部分线程会比其他线程更早的完成任务,这样整个任务的运行时间,取决于处理最长文件的线程的运行时间。
2. 如何合并各个独立线程的运行结果。
3. 运行的时间最终还是受限于单台计算机的运行能力。这是上限,无论采取什么方法,始终会有个天花板。

结论是,使用awk工具并行处理是可行的,但是实际上会很麻烦,因此,hadoop华丽登场了。。。

使用Hadoop分析数据

首先我们要将任务表示成MapReduce的作业,这样才能在Hadoop上计算。
MapReduce任务过程分成两个处理阶段,Map和Reduce.(这话感觉说着好傻,哈哈)。每个阶段都以键值对作为输入和输出。类型由你自己选择。因此,我们需要写两个函数,Map和Reduce函数。

其中Map阶段,输入是天气数据,输出是一个预处理的数据(供Reduce使用)。
- key : 偏移量。某一行的起始位置相对于文件起始位置的偏移量。
- value: 提取的年份和气温。

map函数的输出经MapReduce框架处理后,送到reduce函数,然后基于键值对进行排序和分组。reduce的输入大概是这样的:

箭头前面的是map函数的输出,然后箭头就是框架处理了,然后剪头后就是reduce的输入。

(1950,0),(1950,22),(1950,-11),(1950,111),(1950,78) --> (1950,[0,22,-11,111,78])

流程图如下:
这里写图片描述

然后书中开始了代码实现,分别要三个函数,map,reduce,和运行作业的函数。
其中,map由Mapper类类实现,类中声明了一个map()虚方法 (好像越来越使用虚方法而不是接口,因为考虑到扩展性等,这个可以去查查)

import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.LongWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Mapper;/* Mapper类是一个泛型类型,具有四个形参 * 分别是map函数的输入键,输入值,输出键,输出值得类型。 * 以书上的例子来说,输入键就是偏移量,没啥用。 * 输入值是一行文本,包含年份,温度等。 * 输出键是年份。 * 输出值是温度。 * 同时,Hadoop有自己的基本类型,例如text,LongWritable这些,并不使用Java的数据类型 * 目的就是为了优化网络序列化传输。 */public class MaxTemperatureMapper  extends Mapper<LongWritable, Text, Text, IntWritable> {  private static final int MISSING = 9999;  @Override  public void map(LongWritable key, Text value, Context context)      throws IOException, InterruptedException {    String line = value.toString();    String year = line.substring(15, 19);    int airTemperature;    // 这部分很像我之前做文本预处理时候的一些操作,根据输入文本的格式,提取我想要的内容。    if (line.charAt(87) == '+') { // parseInt doesn't like leading plus signs      airTemperature = Integer.parseInt(line.substring(88, 92));    } else {      airTemperature = Integer.parseInt(line.substring(87, 92));    }    String quality = line.substring(92, 93);    if (airTemperature != MISSING && quality.matches("[01459]")) {      context.write(new Text(year), new IntWritable(airTemperature));    }  }}

同时,reduce也有四个形参接收和输出。reduce代码不贴了,没啥需要备注的。

// cc MaxTemperature Application to find the maximum temperature in the weather dataset// vv MaxTemperatureimport 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;public class MaxTemperature {  public static void main(String[] args) throws Exception {    if (args.length != 2) {      System.err.println("Usage: MaxTemperature <input path> <output path>");      System.exit(-1);    }    // Job对象指定作业执行规范。用它来控制整个作业的运行。    Job job = new Job();    job.setJarByClass(MaxTemperature.class);//如果要在集群上运行job,则需要将代码打包成一个jar文件。hHadoop利用这个类来超找包含它的jar文件,进而找到相关的JAR文件。    job.setJobName("Max temperature");    // 指定输入输出的数据路径。因为本书用命令行来在本地运行,所以分别用args数组来存储路径参数。    FileInputFormat.addInputPath(job, new Path(args[0]));//可以多次调用add来实现多路径输入。    FileOutputFormat.setOutputPath(job, new Path(args[1]));// 写入目录应该是不存在的,Hadoop会自动创建,这是为了防止意外覆盖导致的文件丢失。所以如果目录存在,会报错并无法运行作业。    //指定map、reduce、输入和输出的类型。    job.setMapperClass(MaxTemperatureMapper.class);    job.setReducerClass(MaxTemperatureReducer.class);    job.setOutputKeyClass(Text.class);    job.setOutputValueClass(IntWritable.class);    System.exit(job.waitForCompletion(true) ? 0 : 1);  }}// ^^ MaxTemperature

按照书上的办法试了一下,总是失败,也不知道为啥,很无奈啊,错误都是主类找不到。。。心累。。。先过吧,卡太久了也不好,等以后再回过头来试试,可能是环境问题,也可能是命令行弄错了。

hadoop MaxTemperature input/ncdc/sample.txt output

其实不知道这个input和output路径怎么设的,我就在当前目录下新建了input/nddc,把文件放进去了。output没管。结果没提示路径出错,而一直是MaxTemperature类找不到。。。无奈哦。。。然后直接在eclipse上跑了一下,设好了路径,然而也失败,尴尬

如果hadoop第一个参数是类名,那么Hadoop会启动一个JVM来运行这个类。也需要定义一个HADOOP_CLASSPATH环境变量用于添加应用程序类的路径。然后由Hadoop脚本来执行相关操作。(这个部分有点不是很清楚,path按照代码设定的是一个jar文件,这块不知道啥意思)

输出数据写入output目录下,一个reducer都有一个输出文件,因为书中只有reducer,所以只有一个文件。


书中也比较了MapReduce的新旧API的不同。大致看看吧。
旧的印象最深的应该是JobConf对象配置作业,而不是现在的Job。


横向扩展(Scaling out)

之前是针对少量数据的操作,然后来鸟瞰一下针对大量输入时的数据流。

术语定义

这部分要仔细看了,因为都是一些术语名词,要不然说的不专业也很尴尬。。。
1. 首先MapReduce作业,也就是Job,是客户端需要执行的一个工作单元,它包含了很多东西,比如数据啊,程序啊,配置信息等。
2. 然后Hadoop将Job分成很多个task,小任务。来执行。包括map任务和reduce任务。 **job**tracker和**task**tracker这两个tracker控制着Job的执行过程。
3. Hadoop将MapReduce的输入数据划分成等长的小数据块,称为输入分片(input split)简称分片。Hadoop为每个分片构建一个map任务,并由该任务来运行用户自定义的map函数,从而处理分片中的每条记录。
4. 因此,分片的大小,也决定着效率,分片小的话,可以并行处理,能有更好的负载平衡。但是呢太小的话,管理分片的时间和构建map任务的时间就大。合理的分片一般就是HDFS一个块的大小,64M。

Hadoop在存储输入数据的节点上运行map任务,可以获得最佳性能,这就是“数据本地化优化” 好处是不需要占用带宽资源。

后面也解释了为啥最佳分片的大小应该和块大小相同,这是为了确保可以存储在单个节点上的最大输入块的大小。因为如果分片的大小,大于一个数据块大小,那么针对任何一个节点,都不可能同时存储这分片对应的数据。这就导致了分片中的部分数据,需要通过网络传输,影响效率。

map是将输出结果写入本地硬盘,而不是HDFS哦!!!而且reduce不具备数据本地化的优势,也就是它的数据输入,通常需要占用带宽资源传输

下图是完整的一个reduce任务的MapReduce数据流

这里写图片描述

任务的数量和输入输出无关,是独立指定的,后续章节会有相关介绍。

也要了解一下partition分区的概念。

下图是完整**多个**reduce任务的MapReduce数据流

这里写图片描述

混洗了shuffle,每个reduce任务的输入都来自多个map任务的输出。而且调整混洗参数对Job总执行时间的影响巨大。

当然,数据处理也可以不要混洗,完全并行,甚至出现没有reduce任务的情况。。。直接将map输出写入hdfs。


Combiner函数

为了考虑节省带宽资源,而使用,但是也不能替代reduce。需要用的时候,指定一个combiner

public class MaxTemperatureWithCombiner {  public static void main(String[] args) throws Exception {        ...        job.setCombinerClass(MaxTemperatureReducer.class)        ...        }    }    

Hadoop Streaming

这个非常适合处理文本啊文本~!支持任何可以从标准输入读取和写入到标准输出中的编程语言。书中pyhon自己看了一下。

# !/usr/bin/env pythonimport sys# 定义个元组,键值对。其中键为空,值为最小。sys.maxint 是系统最大值(last_key, max_val) = (None, -sys.maxint)# 开始读取文本行for line in sys.stdin:  (key, val) = line.strip().split("\t")  # 判断key是否为空,若为空,则说明刚开始,那么else赋初始值  # 如果key值有,则接着判断元组中key和读入的那一行的key值是否相等。  # key是年份,如果不相等,说明年份变化了,也就是在if里面重新赋值。  # 如果相等,说明还是在同一年内,则进入else,比较同一年的温度,取最大。  if last_key and last_key != key:    print "%s\t%s" % (last_key, max_val)    (last_key, max_val) = (key, int(val))  else:    (last_key, max_val) = (key, max(max_val, int(val)))if last_key:  print "%s\t%s" % (last_key, max_val)

Hadoop Pipes

这个是MapReduce的C++接口名称。 直接扫了一眼,不看了。。。。。
这样第二章这部分就算看完了,感觉刚开始所以很多东西都不难,看的也很顺利,由浅入深吧,后面看完一章就更新一下,做个笔记,也做一个记录,一方面是加深印象,一方面也是个复习。

原创粉丝点击