第一章 初识hadoop
大量的数据胜于好的算法。
一、数据存储与分析
实现多个磁盘的并行读写,需要解决的问题:
1、硬件故障,一旦使用多个硬件,任一硬件发生故障的概率很高,避免数据丢失的办法就是进行数据备份。
RAID:冗余磁盘阵列是按数据备份的原理实现的;
Hadoop的文件系统,即HDFS也是一类
2、大多数分析任务需要以某种方式结合大部分数据共同完成分析任务,即从一个磁盘读取的数据可能需要从另外99个磁盘中读取的数据结合使用。
Hadoop提供一个可靠的共享存储和分析系统,HDFS实现存储,而MapReduce实现分析处理,这两部分是它的核心
二、MapReduce是一个批量查询处理器,并且能够在合理的时间范围内处理针对整个数据集的即时查询。
三、为什么不使用数据库对大量磁盘上的大规模数据进行批量分析?为什么需要MapReduce?
原因是:寻址时间的提高远远慢于传输速率的提高;
寻址时间是磁头移动到特定磁盘位置进行读写操作的过程,它是导致磁盘操作延迟的主要原因,而传输速率取决于磁盘的带宽。
MapReduce适合以批处理的方式处理需要分析整个数据集的问题,适合一次读入、多次读取数据的应用
RDBMS适合于点查询和更新,更适合持续更新的数据集。
MapReduce适合大数据更新、RDBMS适合小数据更新。
结构化数据是指具有既定格式的实体化数据;非结构化数据没有什么特别的内部结构。
MapReduce适合处理非结构化或者半结构化数据。因为MapReduce输入的键和值并不是数据固有的属性,而是由分析数据的人员来选择的。
四、网格计算
高性能计算:将作业分到集群的各台机器上,这些机器访问由访问区域网络SAN组织的共享文件,这比较适合计算密集型作业;但是访问的数据量增大时,由于带宽限制,会造成很多计算节点闲置;
MapReduce会尽量在计算节点上存储数据,以实现数据的本地访问。
数据本地化访问是MapReduce的核心特征,并因此获得很好的性能。
数据带宽是数据中心环境最珍贵的资源。
MapReduce采用无共享框架:意味着各个人物之间彼此独立,因此MapReduce能够检测到失败的map或者reduce任务,并让正常运行的机器重新执行这些失败的任务。
第二章 关于MapReduce
MapReduce是一种可用于数据处理的编程模型;可以用多种语言编写(java、ruby、python、和C++),优势在于大规模数据处理。
一、map阶段和reduce阶段
1、MapReduce任务过程分为两个处理阶段:map阶段和reduce阶段,每个阶段都以键/值作为输入输出,并由程序员选择他们的类型。程序员还需定义两个函数:map和reduce函数。
2、map阶段输入数据,经过MapReduce框架处理后,最后被发送到reduce函数,这一处理过程中需要根据键/值对进行排序和分组。
二、数据流
MapReduce作业是客户端需要执行的一个工作单元:它包括输入数据、MapReduce程序和配置信息。
Hadoop将作业分成若干个小任务来执行,其中包括两类任务:map任务和reduce任务。
Hadoop的MapReduce将输入数据划分成等长的小数据块,称为输入分片;Hadoop为每个分片构建一个map任务,并由该任务来运行用户自定义的map函数从而处理分片中的每条记录。
1、拥有很多分片,意味着处理每个分片所需要的时间少于处理整个输入数据所花的时间;因此,如果我们并行处理每个分片,并且每个分片的任务比较小,那么整个处理过程将获得更好的负载平衡。
2、如果分片太小,那么管理切片的时间和构建map任务的总时间将决定作业的整个执行时间。对于大多数作业来说,一个合理的分片大小趋向于HDFS的一个块的大小,默认是64MB,不过可以针对集群调整整个默认值。在新建所有文件或新建每个文件时具体指定即可。
3、Hadoop在存储有输入数据(HDFS的数据)的节点上运行map任务,可以获得最佳性能;这就是所谓的数据本地化优化。
现在我们应该清楚为什么最佳分片的大小应该与块大小相同:因为它确保可以存储在单个节点上最大输入块的大小。如果分片太大,那么就会分配到两个数据块,那么对于任何一个HDFS节点,基本上上都不可能同时存储这两个数据块,则这一个分片就会存放在至少两个HDFS节点上,因此分片中的部分数据需要网络传输到map任务节点;这与使用本地数据运行整个map任务相比,这种方法显然效率更低。
4、map任务将其输出写入本地硬盘而非HDFS,为什么?
因为map输出的是中间结果:该中间结果由reduce任务处理后才产生最终输出结果,而且一旦作业完成,map输出结果可以被删除;因此,如果把它存储在HDFS中并实现备份,未免小题大做。如果该节点上运行的map任务在将map中间结果传送给reduce任务之前失败,Hadoop将在另一个节点重新运行这个map任务以再次构建map中间结果。
5、Reduce任务并不具备数据本地化的优势:单个reduce的任务输入通常来自于所有mapper的输出。
6、排过序的map输出需通过网络传输发送到运行reduce任务的节点上;数据在reduce端合并,然后由用户定义的reduce函数处理。
reduce输出通常存储在HDFS中以实现可靠存储。对于每个reduce输出的HDFS块,第一个副本存储在本地节点上,其他副本存储在其他机构节点上,因此reduce输出写入HDFS确实需要占用网络带宽,但这与正常的HDFS流水线写入 消耗一样。
7、reduce任务的数量并非由输入数据的大小决定,而是特别指定。
8、如果有多个reduce任务,则每个map任务都会对其输出进行分区,即为每一个reduce建一个分区。每个分区有许多个键及其对应值,但每个键对应的键/值记录都在同一分区中。分区由用户定义的分区函数控制,但通常用默认的分区器借用哈希函数来分区。
9、为了避免map任务和reduce任务之间的数据传输,hadoop允许用户针对map任务输出指定一个合并函数------合并函数的输出作为reduce函数的输入。
10、Hadoop的streaming使用UNIX标准流作为hadoop和应用程序之间的接口。
第三章 Hadoop分布式文件系统
当数据集的大小超过一台物理计算机的存储能力时,就有必要对它进行分区并存储到若干台单独的计算机上;管理网络上跨多台计算机存储的文件系统称为分布式文件系统。
HDFS:Hadoop Distributed Filesystem
一、HDFS的设计
HDFS以流式数据访问模式存储超大文件,运行于商用硬件集群上。
1、超大文件:至少几百MB
2、流式数据访问:一次写入、多次读取时最高效的访问模式;数据集通常由数据源圣城或从数据源复制而来,接着长时间在此数据集上进行各种分析;每次分析都涉及该数据集的大部分数据甚至全部,因此读取整个数据集的时间延迟比读取第一条记录的时间延迟更重要。
3、低时间延迟的数据访问:低时间延迟的数据访问不适合在HDFS上运行;HDFS是为高数据吞吐量应用优化的。
4、大量的小文件:由于namenode将文件系统的元数据存储在内存中,因此该文件系统所能存储的文件总数受限于namenode的内存容量。
5、多用户写入,任意修改文件:HDFS中只有一个writer,而且写操作总是将数据添加在文件的末尾;它不支持具有多个写入者的操作,也不支持在文件的任意位置进行修改。
二、HDFS概念
1、数据块
每个磁盘都有默认的数据块大小,这是磁盘进行读写的最小单位。构建于磁盘之上的文件系统通过管理磁盘之中的数据块来管理文件系统中的块,该文件系统块的大小可以是磁盘块的整数倍;
文件系统块一般几千字节,而磁盘块一般为512字节。
HDFS同样也有块的概念,但是大得多,默认为64MB。与单一磁盘上的文件系统相似,HDFS的文件也被划分为块大小的多个分块,作为独立的存储单元。但与其他文件系统不同的是,HDFS中小于一个块大小的文件不会占整个块的空间。
2、为何HDFS的块如此大?
HDFS的块比磁盘的块大,目的是最小化寻址开销。如果设置的足够大,从磁盘传输数据的时间可以明显大于寻址时间;这样,传输一个由多个块组成的文件的时间取决于磁盘的传输速率。
3、对分布式系统进行抽象有很多好处:
(1)一个文件的大小可以大于网络中任何一个磁盘的容量。因为文件的所有块并不需要存储在同一个磁盘上,因此可以利用集群上的任意一个磁盘进行存储;极端的情况:一个文件的块占满所有的磁盘。
(2)使用块抽象而非整个文件作为存储单元,可以简化存储子系统的设计。因为块的大小是固定的,因此计算单个磁盘能够存储多少个块相对容易;同时也消除了对元数据的顾虑;
(3)块非常适合用于数据备份进而提供数据容错能力和可用性。
与磁盘系统类似,HDFS中的fsck指令可以显式块信息。
4、namenode和datanode
HDFS集群有两类节点,并以管理者-工作者模式运行:
一个是namennode:管理者
多个是datanode:工作者
5、namenode:
(1)namenode管理文件系统的命名空间,它维护文件系统树及整棵树内所有的文件和目录,这些信息以文件形式永久保存在本地磁盘上。
(2)namenode也记录着每个文件中每个数据块的数据节点信息,但它并不永久保存块的位置信息,因为这些信息会在系统启动时由数据节点重建。
6、客户端(client)代表用户与namenode和datanode交互来访问整个文件系统;客户端提供一个类似POSIX的文件系统接口,因此,用户无需知道namenode和datanode也可实现其功能。
7、datanode
datanode是文件系统的工作节点;它们根据需要存储并检索数据块(受客户端namenode调度),并且定期向nanmenode发送它们所存储的块的列表。
8、namenode的重要性:
没有namenode,文件系统将无法使用;因为没有namenode,文件系统上的所有文件将会消失,不知道如何根据datanode的块进行重建文件。
因此,对namenode实现容错很重要,hadoop提供俩种机制:
(1)备份那些组成文件系统元数据持久状态的文件。hadoop可以通过配置使namenode在多个文件系统保存元素据的持久状态。这些写操作是实时同步的,原子操作的;一般的配置是,将持久状态写入本地磁盘的同时,写入一个远程挂载的网络文件系统NFS
(2)运行一个辅助namenode,但是它不能用作namenode。这个辅助namenode的重要作用是定期通过编辑日志合并命名空间镜像,以防止编辑日志过大。这个辅助namenode需要在另一台单独的计算机上运行,因为它需要占用大量的CPU时间与namenode相同容量的内存来执行合并操作;它会保存合并后的命名空间镜像的副本,并在namenode发生故障时启用;但是辅助namenode保存的状态总是滞后于主节点,所以当主节点全部失效时,难免会丢失部分数据,如果发生这种事情,一般把存储在NFS上的namenode元数据复制到辅助namenode并作为新的主namenode运行。
9、文件读取剖析:
步骤1、客户端调用FileSystem对象的open方法来打开希望读取的文件;
对于HDFS来说,这个对象是使分布式系统DistributedFileSystem类的一个实例;
步骤2、Distributed FileSystem通过使用RPC来调用namenode,以确定文件起始块的位置。
对于每一块,namenode返回存有该块复本的datanode地址;
此外,这些datanode根据它们与客户端的距离来排序,如果该客户端本身就是一个datanode,并保存有相应数据块的一个复本时,该节点将从本地datanode读取数据;
步骤3、 DistributedFileSystem类返回一个FSDataInputStream对象(支持文件定位的输入流)给客户端并读取数据;
FSDataInputStream对象转而封装DFSInputStream对象,该对象管理着datanode和namenode的I/O。
客户端对输入流(即FSDataInputStream对象)用read()方法;
步骤4、存储着文件起始块的datanode地址的DFSInputStream随机连接距离最近的datanode;
通过对数据流的反复调用read()方法,可以将数据从datanode传输到客户端;
步骤5、达到块的末尾时,DFSInputStream会关闭与该datanode的连接,然后寻找下一个块的最佳datanode;客户端只需要读取连续的流,并且对于客户端都是透明的
步骤6、客户端从流中读取数据时,块是按照打开DFSInputStream与datanode新建连接的顺序读取的;
它也需要询问namenode来检索下一批所需块的datanode位置。一旦读取完毕,就会FSDataInputStream调用close()方法。
注:
在读取数据时,DFSInputStream在于datanode通信时遇到错误,它便会尝试从这个块的另外一个最邻近的datanode读取数据。他也会记住那个故障datanode,以保证以后不会反复读取该节点上后续的块。
DFSInputStream也会通过校验和确认从datanode发来的数据是否完整。如果发现一个损坏的块,他就会在DFSInputStream试图从其他datanode读取该损坏块的副本之前通知namenode。
当块损坏时,会不会从新对损坏的块进行更新呢?
10、文件写入剖析
步骤1:客户端通过调用DistributedFileSystem对象调用creat()方法来创建文件;
步骤2:DistributedFileSystem对namenode创建一个RPC调用,在文件系统的命名空间中创建一个新文件,此时该文件中还没有相应的数据块;
namenode执行各种不同的检查以确保这个文件不存在,并且客户端有创建该文件的权限;
如果这些检查通过,namenode会为创建新文件记录一条记录;否则,文件创建失败并向客户端抛出一个IOException异常;
DistributedFileSystem向客户端返回一个FSDataOutputStream对象,由此,客户端可以写入数据;就像读取数据一样,FSDataOutputStream封装着一个
DFSOutputStream对象,该对象负责datanode和namenode之间的通信。
步骤3:在客户端写入数据时,DFSOutputStream将它分成一个个的数据包,并写入内部队列,称为数据队列(data queue);
步骤4:DataStreamer处理数据队列,它的责任是根据datanode列表来要求namenode分配合适的块来存储数据备份;
这一组datanode构成一个管道----假设副本数为3,所以管线中有3个节点:DataStreamer将数据包流逝传输到管道第一个datanode,同样第2个datanode存储该数据包并发送给管道中的第3个(也是最后一个)datanode。
步骤5:DFSDataOutputStream也维护着一个内部数据包队列,用来等待datanode收到后发出的确认回执,这个队列称为确认队列ack queue;
当收到管线中所有datanode确认信息后,该数据包才会从确认队列删除。
注:如果在数据写入期间,datanode发生故障,则执行一下操作(此操作对客户端来说是透明的):
首先关闭管道,确认把队列中的任何数据包都添加回答哦数据队列的最前端,以确保故障点下游的datanode不会漏掉任何一个数据包;
然后,为存储在另一个正常的datanode的当前数据块制定一个新的标识,并将该标识传送给namenode,以便有故障的datanode在恢复后可以删除存储的部分数据块;
从管道中删除故障数据节点并把余下的数据块写入管道中两个正常的datanode;
namenode注意到块副本数不足时,会在另一个节点上创建一个新的副本;后续的数据块接受正常处理;
在一个块被写入期间有多个datanode同时发生故障,这种概率很小;
步骤6:客户端在写完数据后,会对数据流调用close()方法;
步骤7:该操作将剩余的所有 数据包写入datanode管道中,并在联系namenode以及发送完成写入信号之前等待确认;
namenode已经知道文件由哪些块组成(这是通过DataStreamer询问数据块分配),所以它在返回成功前只需要等待数据块进行最小量的复制;
11、复本的布局
默认策略是在运行客户端的节点上方第1个复本:如果客户端运行在集群之外,就随机选择一个节点,倾向于选择不太满或者不太忙的节点;
第2个复本放在与第1个复本不同的机架节点上,且机架是随机选择的;
第3个复本放在与第2个复本相同的机架上,且随机选择另一个节点;
如果复本的数量大于3个,则其他节点随机选择节点,不过系统会尽量避免在相同的机架上放太多复本;
一旦选择复本的存放位置,就会根据网络拓扑创建一个管道;