大数据基础学习-2.Hadoop1.0、MapReduce

一、Hadoop1.0

Hadoop是一个由Apache基金会所开发的分布式系统基础架构。Hadoop的框架最核心的设计就是:HDFS和MapReduce。HDFS为海量的数据提供了存储,MapReduce为海量的数据提供了计算。Hadoop是Doug Cutting根据Google的三篇论文开源出来的。为了能对Hadoop有更好的理解,先从Hadoop1.0开始,再过度到Hadoop2.0。

1.HDFS系统架构

分布式文件系统,Hadoop Distributed File System,简称HDFS。核心作用就是用来存储文件,架构图如下。主要有namenode、DataNode、secondary namenode和block等概念需要掌握。

http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html#Introduction


1)NameNode

NameNode主要是用来保存HDFS的元数据信息,比如命名空间信息,块信息等。当它运行的时候,这些信息是存在内存中的。

• NameNode保存的元信息具体如下:

    • 文件名、目录名及它们之间的层级关系

    • 文件目录的所有者及其权限

    • 每个文件块的名及文件有哪些块组成

运行NameNode会占用大量内存和I/O资源,一般运行NameNode进程的节点上不会存储用户数据或执行MapReduce任务。

• Hadoop1.0系统只有一个NameNode,会导致单点问题

两种解决方案:

1.将hadoop元数据写入到本地文件系统的同时再实时同步到一个远程挂载的网络文件系统(NFS),比较烧钱。

2.运行一个secondary NameNode,它的作用是与NameNode进行交互,定期通过编辑日志文件合并命名空间镜像,当NameNode发生故障时它会通过自己合并的命名空间镜像副本来恢复。由于secondaryNameNode保存的状态总是滞后于NameNode,所以这种方式难免会导致丢失部分数据。

• 元信息持久化

NameNode的元数据也会往磁盘上去存,但是真正对外提供服务的是在内存中的元数据。本地磁盘还存放namenode的备份文件,即fsimage和edits。在系统运行期间,所有对元信息的操作都会保存在内存中并被持久化到另一个文件edits中,edits文件和fsimage文件会被SecondaryNameNode周期性的合并(默认1小时或者edits文件大小达到64M时),此时的edits文件停止更新,系统产生edits.new用来记录新的元数据操作。合并完后,edits.new会变为edits,此时的fsimage就是最新的元数据备份。

• hadoop偏向于存大文件

一般来说,一条元信息记录会占用200byte内存空间。假设块大小为64MB,备份数量是3 ,那么一个1GB大小的文件将占用16*3=48个文件块。如果现在有1000个1MB大小的文件,则会占用1000*3=3000个文件中。如果文件越小,存储同等大小文件所需要的元信息就越多,所以,hadoop更喜欢大文件。

如果集群规模特别大的话,多个DataNode节点上有很多个文件,那这文件的背后又有很多的block,完全存在NameNode内存里面肯定是吃不消的。一方面是它不能够持久化,另外一方面不可能永远庞大下去,所以它就制约着整个集群的规模的扩大,只有在hadoop2.0才能解决(使用多个namenode)。

2)DataNode

• 负责存储数据块,为系统客户端提供数据块的读写服务

• 根据NameNode的指示进行创建、删除和复制等操作

• 通过心跳机制,定期报告文件块列表信息

• DataNode之间也会进行通信,进行块的副本处理

3)Block

• block是HDFS文件存储的最小单位,默认大小是64M。

为什么不能远小于64M(普通文件系统的数据块大小一般为4KB)?

1.减少硬盘寻道时间

HDFS设计前提是支持大容量的流式数据操作,所以即使是一般的数据读写操作,涉及到的数据量都是比较大的。假如数据块设置过少,那需要读取的数据块就比较多,由于数据块在硬盘上非连续存储,普通硬盘因为需要移动磁头,所以随机寻址较慢,读越多的数据块就增大了总的硬盘寻道时间。当硬盘寻道时间比io时间还要长的多时,那么硬盘寻道时间就成了系统的一个瓶颈。合适的块大小有助于减少硬盘寻道时间,提高系统吞吐量。

2.减少Namenode内存消耗,也就是上文中,hadoop偏向于存大文件的原因。

为什么不能远大于64M?

这里主要从上层的MapReduce框架来讨论。

1.Map崩溃问题:系统需要重新启动,启动过程需要重新加载数据,数据块越大,数据加载时间越长,系统恢复过程越长。

2.监管时间问题:主节点监管其他节点的情况,每个节点会周期性的把完成的工作和状态的更新报告回来。如果一个节点保持沉默超过一个预设的时间间隔,主节点记录下这个节点状态为死亡,并把分配给这个节点的数据发到别的节点。对于这个“预设的时间间隔”,这是从数据块的角度大概估算的。假如是对于64MB的数据块,我可以假设你10分钟之内无论如何也能解决了吧,超过10分钟也没反应,那就是死了。可对于640MB或是1G以上的数据,我应该要估算多长的时间内?估算的时间短了,那就误判死亡了,分分钟更坏的情况是所有节点都会被判死亡。估算的时间长了,那等待的时间就过长了。所以对于过大的数据块,这个“预设的时间间隔”不好估算。

3.问题分解问题:数据量大小是问题解决的复杂度是成线性关系的。对于同个算法,处理的数据量越大,它的时间复杂度也就越大。

4.约束Map输出:在Map Reduce框架里,Map之后的数据是要经过排序才执行Reduce操作的。想想归并排序算法的思想,对小文件进行排序,然后将小文件归并成大文件的思想,然后就会懂这点了。

4)Secondary NameNode

• 用来保存HDFS的元数据信息,比如命名空间信息、块信息等,由于这些信息是在内存中的,SecondNameNode就是负责将这些信息持久化到磁盘。一般情况下,secondary namenode和namenode需要运行在不同的机器上。SecondNameNode的作用就是为了减少namenode启动的时间,以及当namenode挂掉时可以做数据恢复用。

    – fsimage,它是在NameNode启动时对整个文件系统的快照

    – edit logs,它是在NameNode启动后,对文件系统的改动的记录

• SecondaryNameNode所做的不过是在文件系统中设置一个检查点来帮助NameNode更好的工作。它不是要取代掉NameNode也不是NameNode的备份。


• 定时到NameNode去获取edit logs,并更新到fsimage[Secondary NameNode自己的fsimage], 一旦它有了新的fsimage文件,它将其拷贝回NameNode中。NameNode在下次重启时会使用这个新的fsimage文件,从而减少重启的时间。

2.机架感知策略

1)副本存放策略

为了数据存储的安全,数据的副本将会存在不同的机器和机架上。

• 第一个副本,在客户端相同的节点(如果客户端是集群外的一台机器,就随机选择负载较低的节点)

• 第二个副本,放在不同机架(随机选择)的节点

• 第三个副本,放在与第二个副本同机架但是不同节点上

2)Hadoop中的网络拓扑

当用户访问Hadoop文件时,Hadoop会优先将离用户节点近的数据提供给用户,但是在Hadoop集群中如何衡量两个节点的远近呢?要知道,在高速处理数据时,数据处理速率的唯一限制因素就是数据在不同节点间的传输速度:这是由带宽的可怕匮乏引起的。所以我们把带宽作为衡量两个节点距离大小的标准。

但是计算两个节点之间的带宽是比较复杂的,而且它需要在一个静态的集群下才能衡量,但Hadoop集群一般是随着数据处理的规模动态变化的(且两两节点直接相连的连接数是节点数的平方)。于是Hadoop使用了一个简单的方法来衡量距离,它把集群内的网络表示成一个树结构,两个节点之间的距离就是他们离共同祖先节点的距离之和。树一般按数据中心(datacenter),机架(rack),计算节点(datanode)的结构组织。计算节点上的本地运算速度最快,跨数据中心的计算速度最慢(现在跨数据中心的Hadoop集群用的还很少,一般都是在一个数据中心内做运算的)。

下面是不同情况下两个节点的距离:

• distance(/D1/R1/H1,/D1/R1/H1)=0 相同的DataNode

• distance(/D1/R1/H1,/D1/R1/H2)=2 同一rack(机架)下的不同DataNode

• distance(/D1/R1/H1,/D1/R1/H4)=4 同一IDC(互联网数据中心)下的不同DataNode

• distance(/D1/R1/H1,/D2/R3/H7)=6 不同IDC下的DataNode

3.数据完整性策略

转: https://blog.csdn.net/andrewgb/article/details/50626086

Hadoop 用户肯定都不希望系统在存储和处理数据时不会丢失或者损坏任何数据。接下来,我们来考究一下 HDFS 在为了保证数据完整性,所做的工作。

两种检验方法:

    – 校验和:  检测损坏数据的常用方法是在第一次进入系统时计算数据的校验和,在通道传输过程中,如果新生成的校验和不完全匹配原始的校验和,那么数据就会被认为是被损坏的。

    – 数据块检测程序 DataBlockScanner:   在DataNode节点上开启一个后台线程,来定期验证存储在它上所有块,防止物理介质出现损减情况而造成的数据损坏。

总的来说,HDFS 会对写入的数据计算校验和,并在读取数据时验证校验和。datanode 负责收到数据后存储该数据及其校验和。datanode 的数据来源可分为两种,其一为是从客户端收到的数据,其二为从其他 datanode 复制来的数据。还有一种情况,正在些数据的客户端将数据及其校验和发送到由一系列 datanode 组成的管线,管线中最后一个 datanode 负责验证校验和。客户端从 datanode 读取数据时,也会验证校验和,将他们与 datanode 中存储的校验和进行比较。每个 datanode 都持久保存一个用于验证的校验和日志,所以会知道每个数据块的最后一次验证时间。客户端成功验证一个数据块后,会告诉这个 datanode 来更新日志。对于检测损坏的磁盘很有价值。

不只是客户端读取数据库时会验证校验和,每个 datanode 也会在一个后台进程中运行一个 DataBlockScanner ,从而定期验证存储在这个 datanode 的所有数据库。该措施是解决物理存储媒体上位损坏的有力措施。

HDFS 会存储每个数据块的复本,可以通过数据复本来修复损坏的数据块。 客户端在读取数据块时,如果检测到错误首先向 namenode 报告已损坏的数据块及其正在尝试读取操作的这个 datanode 。namenode 会将这个数据块标记为已损坏,对这个数据块的请求会被 namenode 安排到另一个复本上。之后,它安排这个数据块的另一个复本复制到另一个 datanode 上,如此,数据块的复本因子又回到期望水平。此后,已损坏的数据块复本会被删除。

4.数据读写

转:http://www.cnblogs.com/beanmoon/archive/2012/12/17/2821548.html

写:首先客户端通过DistributedFileSystem上的create()方法指明一个欲创建的文件的文件名(第一步),DistributedFileSystem再通过RPC调用向NameNode申请创建一个新文件(第二步,这时该文件还没有分配相应的block)。namenode检查是否有同名文件存在以及用户是否有相应的创建权限,如果检查通过,namenode会为该文件创建一个新的记录,否则的话文件创建失败,客户端得到一个IOException异常。DistributedFileSystem返回一个FSDataOutputStream以供客户端写入数据,与FSDataInputStream类似,FSDataOutputStream封装了一个DFSOutputStream用于处理namenode与datanode之间的通信。
当客户端开始写数据时(第三步),DFSOutputStream把写入的数据分成包(packet), 放入一个中间队列——数据队列(data queue)中去。DataStreamer从数据队列中取数据,同时向namenode申请一个新的block来存放它已经取得的数据。namenode选择一系列合适的datanode(个数由文件的replica数决定)构成一个管道线(pipeline),这里我们假设replica为3,所以管道线中就有三个datanode。DataSteamer把数据流式的写入到管道线中的第一个datanode中(第四步),第一个datanode再把接收到的数据转到第二个datanode中(第四步),以此类推。
DFSOutputStream同时也维护着另一个中间队列——确认队列(ack queue),确认队列中的包只有在得到管道线中所有的datanode的确认以后才会被移出确认队列(第五步)。
如果某个datanode在写数据的时候当掉了,下面这些对用户透明的步骤会被执行:
  1)管道线关闭,所有确认队列上的数据会被挪到数据队列的首部重新发送,这样可以确保管道线中当掉的datanode下流的datanode不会因为当掉的datanode而丢失数据包。
  2)在还在正常运行的datanode上的当前block上做一个标志,这样当挂掉的datanode重新启动以后namenode就会知道该datanode上哪个block是刚才当机时残留下的局部损坏block,从而可以把它删掉。
  3)已经当掉的datanode从管道线中被移除,未写完的block的其他数据继续被写入到其他两个还在正常运行的datanode中去,namenode知道这个block还处在under-replicated状态(也即备份数不足的状态)下,然后他会安排一个新的replica从而达到要求的备份数,后续的block写入方法同前面正常时候一样。
有可能管道线中的多个datanode当掉(虽然不太经常发生),但只要dfs.replication.min(默认为1)个replica被创建,我们就认为该创建成功了。剩余的replica会在以后异步创建以达到指定的replica数。
当客户端完成写数据后,它会调用close()方法(第六步)。这个操作会冲洗(flush)所有剩下的package到pipeline中,等待这些package确认成功,然后通知namenode写入文件成功(第七步)。这时候namenode就知道该文件由哪些block组成(因为DataStreamer向namenode请求分配新block,namenode当然会知道它分配过哪些blcok给给定文件),它会等待最少的replica数被创建,然后成功返回。

读:客户端通过调用调用DistributedFileSystem对象的open()方法来打开文件(也即图中的第一步),DistributedFileSystem通过RPC(Remote Procedure Call)调用询问NameNode来得到此文件最开始几个block的文件位置(第二步)。对每一个block来说,namenode返回拥有此block备份的所有datanode的地址信息(按集群的拓扑网络中与客户端距离的远近排序)。如果客户端本身就是一个datanode(如客户端是一个mapreduce任务)并且此datanode本身就有所需文件block的话,客户端便从本地读取文件。
以上步骤完成后,DistributedFileSystem会返回一个FSDataInputStream(支持文件seek),客户端可以从FSDataInputStream中读取数据。FSDataInputStream包装了一个DFSInputSteam类,用来处理namenode和datanode的I/O操作。
客户端然后执行read()方法(第三步),DFSInputStream(已经存储了欲读取文件的开始几个block的位置信息)连接到第一个datanode(也即最近的datanode)来获取数据。通过重复调用read()方法(第四、第五步),文件内的数据就被流式的送到了客户端。当读到该block的末尾时,DFSInputStream就会关闭指向该block的流,转而找到下一个block的位置信息然后重复调用read()方法继续对该block的流式读取。这些过程对于用户来说都是透明的,在用户看来这就是不间断的流式读取整个文件。
当整个文件读取完毕时,客户端调用FSDataInputSteam中的close()方法关闭文件输入流(第六步)。
如果在读某个block是DFSInputStream检测到错误,DFSInputSteam就会连接下一个datanode以获取此block的其他备份,同时他会记录下以前检测到的坏掉的datanode以免以后再无用的重复读取该datanode。DFSInputSteam也会检查从datanode读取来的数据的校验和,如果发现有数据损坏,它会把坏掉的block报告给namenode同时重新读取其他datanode上的其他block备份。
这种设计模式的一个好处是,文件读取是遍布这个集群的datanode的,namenode只是提供文件block的位置信息,这些信息所需的带宽是很少的,这样便有效的避免了单点瓶颈问题从而可以更大的扩充集群的规模。


二、MapReduce

简单来说,MapReduce就是一个并行的计算框架。它极大地方便了编程人员在不会分布式并行编程的情况下,将自己的程序运行在分布式系统上。

1.MapReduce运行流程架构


2.MapReduce运行流程

为了更好地理解MapReduce运行,结合经典的Wordcount程序进行讲解,参考上面两张图进行理解。

假设有源数据文件file,file包含的内如如下:

the weather is good

today is good

good weather is good

1)file文件split后调用Map函数

文件file以多个block的形式存储在DataNode上,运行MapReduce程序时,MapReduce框架将会启动inputformate类(【包含数据分割(Data Splits)和记录读取器(Record Reader)】,将file拆分成多个split随后MapReduce框架的另一个类RecordReader会读取这些split,每读取1个split就会调用1次map函数。可以看出Map的个数依赖于这个split个数。

假设在本例中file文件按行切分成3个split,调用了3个map函数。

2)Map阶段

在map阶段将进行业务逻辑处理。在Wordcount例中进行词频统计,输出结果为k-v形式,其中<key,value>对应着<词,词频>。所以在三个map任务中,运算的结果如下。

• (the 1),(weather 1), (is 1), (good 1)

• (today 1),(is 1), (good 1)

• (good 1),(weather 1), (is 1), (good 1)

3)Shuffle处理

经过map函数处理之后,将会进行shuffle(包括Partition,Sort, Spill, Meger, Combiner,Copy, Memery, Disk等等操作)。

• Partition,决定数据由哪个Reducer处理。比如采用Hash法,有n个Reducer,那么数据{“is”: 1}的key“is”对n进行取模,返回m,而生成{partition,key, value},这里的m值可以理解为第m个reduce。例如 返回(2,is,1)代表着“is”将会分到reduce2中进行处理,is的出现次数为1。

• MemoryBuffer,内存缓冲区,每个map的结果和partition处理的key value结果都保存在缓存区中,缓冲区大小:默认100M,溢写阈值:100M *0.8 = 80M,缓冲区中的数据为:{ partition key value }三元组数据,例如:{“1” , “are” : 1}。当内存缓冲区写到80M的时候,这80M区域将会被锁住,数据只能往剩下的20M进行写入。此时被锁住的80M空间将会溢写到本地磁盘,这个过程会进行一次排序,排序的依据就是partition的值,写完后80M的内存空间就会被清空。由于这个spill to disk过程会产生很多小文件,系统会根据partition的值,再进行归并排序,将小文件再整合成大文件,即上面图中的merge on disk。具体过程如下。


回到我们的例子,经过shuffle阶段后,原来输入的file数据,这时候将会变成如下,并且保持在本地磁盘。【这里假设每个map得到的结果按照英文字母的大小顺序进行partition,即得到不同的reduce,并且经过shuffle阶段后,文件够大,没有再进行合并】

•  (1,good ,1),(2,is,1),(3,the,1),(4,weather,1)

•  (2,is,1),(1 ,good 1),(3,today,1)

•  (1,good,1),(1,good 1) ,(2,is,1),(4,weather,1)

4)reduce

经过map和shuffle之后,每条数据都已经通过partition知道该分到哪个reduce去进行后续处理,但是需要注意的是,这个时候磁盘上存放着很多spill文件,每个spill文件内部的key是有序的,但是全局来看,并不是。所以数据进入reduce处理前,还需要进行一次sort处理,这个过程将会把所有相同partition的结果都拷贝到reduce中,进行1次merge,然后再按照key进行1次排序。回到我们的例子,这时候我们的数据将会变成如下形式。

good, 1 ;good, 1;good, 1;good, 1

is, 1;is, 1;is, 1;

the, 1;today, 1

weather, 1;weather, 1

之后在reduce阶段进行词频统计的逻辑处理,得到最终每个词的词频结果。

5)Python代码模拟MapReduce的Wordcount

run.sh

HADOOP_CMD="/usr/local/src/hadoop-2.6.0-cdh5.7.0/bin/hadoop" #引入hadoop命令
STREAM_JAR_PATH="/usr/local/src/hadoop-2.6.0-cdh5.7.0/share/hadoop/tools/lib/hadoop-streaming-2.6.0-cdh5.7.0.jar" #引入streaming 工具

INPUT_FILE_PATH_1="/1.data" #指定输入目录,数据需先放到dfs文件系统上
OUTPUT_PATH="/output"       #指定输出目录

$HADOOP_CMD fs -rmr -skipTrash $OUTPUT_PATH #如果存在输出目录就先删除,否则会出错

$HADOOP_CMD jar $STREAM_JAR_PATH \
    -input $INPUT_FILE_PATH_1 \ 
    -output $OUTPUT_PATH \
    -mapper "python map.py" \
    -reducer "python red.py" \
    -file ./map.py \     #将map.py文件进行分发
    -file ./red.py       #将red.py文件进行分发

【注意:\ 后面不能有多余的空格,否则会产生路径找不到的问题

map.py

import sys
for line in sys.stdin:
	ss = line.strip().split(' ')
	for word in ss:
		print '\t'.join([word.strip(), '1'])

red.py

import sys

cur_word = None
sum = 0
for line in sys.stdin:
	ss = line.strip().split('\t')
	if len(ss) != 2:
		continue
	word, cnt = ss
	if cur_word == None:
		cur_word = word
	if cur_word != word:
		print '\t'.join([cur_word, str(sum)])
		cur_word = word
		sum = 0
	sum += int(cnt)
print '\t'.join([cur_word, str(sum)])

可以先在本地进行模拟

[root@master mapreduce_wordcount_python]# cat 1.data | python map.py | sort -k1 | python red.py > result.data

本地运行无误,运行run.sh

[root@master mapreduce_wordcount_python]# bash run.sh #通过hadoop,查看output文件下生成的文件,与本地模拟结果进行对比

三、两个重要的进程(Hadoop1.0)


1.JobTracker

• 主进程,负责接收客户作业提交,调度任务到作节点上运行,并提供诸如监控工作节点状态及任务进度等管理功能,一个MapReduce集群有一个jobtracker一般运行在可靠的硬件上。

• tasktracker是通过周期性的心跳来通知jobtracker其当前的健康状态,每一次心跳包含了可用的map和reduce任务数目、占用的数目以及运行中的任务详细信息。 Jobtracker利用一个线程池来同时处理心跳和客户请求。

2.TaskTracker

• 由jobtracker指派任务,实例化用户程序,在本地执行任务并周期性地向jobtracker汇报状态。在每一个工作节点上永远只会有一个tasktracker。


Hadoop1.0namenode存在单点故障,且单NameNode制约HDFS的扩展性。Hadoop2.0即第二代Hadoop为克服Hadoop1.0中的不足:针对Hadoop1.0单NameNode制约HDFS的扩展性问题,提出HDFS Federation,它让多个NameNode分管不同的目录进而实现访间隔离和横向扩展,同时彻底解决了NameNode单点故障问题。

针对Hadoop1.0中的MapReduce在扩展性和多框架支持等方面的不足,Hadoop2.0中将JobTracker中的资源管理和作业控制分开,分别由ResourceManager(负责所有应用程序的资源分配)和ApplicationMaster(负责管理一个应用程序)实现,即引入了资源管理框架Yarn。同时Yarn作为Hadoop2.0中的资源管理系统,它是一个通用的资源管理模块,可为各类应用程序进行资源管理和调度,不仅限于MapReduce一种框架【MapReducer2.0具有与MRv1相同的编程模型和数据处理引擎,唯一不同的是运行时环境】,也可以为其他框架使用,如Tez、Spark、Storm等。下面就进入Hadoop2.0和yarn的学习。

猜你喜欢

转载自blog.csdn.net/qq_25560849/article/details/80071035