发现一篇不错的文章,转一下。http://www.cnblogs.com/xuekyo/p/3386610.htmlhtml
1.流式数据访问node
HDFS的构建思想是这样的:一次写入,屡次读取是最高效的访问模式。数据集一般有数据源生成或从数据源复制而来,接着长时间在此数据集上进行各种分析。每次分析都将设计数据集的大部分数据甚至所有,所以读取整个数据集的时间延迟比读取第一条数据的时间延迟更重要。算法
2.关于时间延迟的数据访问缓存
要求低时间延迟数据访问的应用,例如几十毫秒的范围,不适合在HDFS上运行,记住,HDFS是为高数据吞吐量应用优化的,这可能会以高时间延迟为代价。目前对于低时间延迟的数据访问应用,HBase是更好的选择。网络
3.大量的小文件并发
因为namenode将文件系统的元文件存储在内存中,所以该文件系统所能存储的文件总数受限于namenode的内存容量。根据经验,每一个文件、目录和 数据快的存储信息大约占150字节。所以,举例来讲,若是有一百万个文件,且每一个文件占一个数据块,那么至少须要300MB的内存,尽管存储上百万的文件 是可行的,可是存储数十亿个文件就超出了当前硬件的能力。框架
4.HDFS中的块分布式
HDFS中的块默认大小为64MB,与单一磁盘上的文件系统相似,HDFS中的文件也被划分为块大小的多个分块(chunk),做为独立的存储单 元。但与其余的文件系统不一样的是,HDFS中小于一个块大小的文件不会占据整个块的空间。HDFS的块比磁盘块(通常为512KB)大,目的是为了最小化 寻址开销。若是块设置的足够大,从磁盘传输数据的时间明显大于定位这个块起始位置所需的时间。这样传输一个由多个块组成的文件的时间取决于磁盘传输速率。 可是该参数也不该过大,MapReduce中map任务一般一次处理一个块的数据,所以若是任务数太少(少于集群中的节点数),做业的运行速度就会比较慢。函数
对分布式文件系统中的块进行抽象会带来许多好处。第一个明显的好处是,一个文件的大小能够大于网络中任意一个磁盘的容量。文件的全部块并不须要存储 在同一个磁盘上,所以他们能够利用集群中的任意一个磁盘进行存储。事实上,尽管不常见,但对于HDFS集群而言,也能够仅存储一个文件,该文件的块占满集 群中的全部磁盘。工具
第二个好处是,使用块而非整个文件做为存储单元,大大简化了存储子系统的设计。
不只如此,块很是适合用于数据备份进而提供数据容错能力和可用性。将每一个块复制到少数几个独立的机器上(默认是3个),能够确保块、磁盘或机器故障时数据不丢失。
与磁盘文件系统相似,HDFS中fsck指令能够显示块信息。
hadoop fsck / -files -blocks
在HDFS里面,data node上的块大小默认是64MB(或者是128MB或256MB)
为何不能远少于64MB(或128MB或256MB) (普通文件系统的数据块大小通常为4KB)?
一、减小硬盘寻道时间(disk seek time)
HDFS设计前提是支持大容量的流式数据操做,因此即便是通常的数据读写操做,涉及到的数据 量都是比较大的。假如数据块设置过少,那须要读取的数据块就比较多,因为数据块在硬盘上非连续存储,普通硬盘由于须要移动磁头,因此随机寻址较慢,读越多 的数据块就增大了总的硬盘寻道时间。当硬盘寻道时间比io时间还要长的多时,那么硬盘寻道时间就成了系统的一个瓶颈。合适的块大小有助于减小硬盘寻道时间,提升系统吞吐量。
二、减小Namenode内存消耗
对于HDFS,他只有一个Namenode节点,他的内存相对于Datanode来讲,是极 其有限的。然而,namenode须要在其内存FSImage文件中中记录在Datanode中的数据块信息,假如数据块大小设置过少,而须要维护的数据 块信息就会过多,那Namenode的内存可能就会伤不起了。
为何不能远大于64MB(或128MB或256MB)?(这里主要从上层的MapReduce框架来讨论)
一、Map崩溃问题
系统须要从新启动,启动过程须要从新加载数据,数据块越大,数据加载时间越长,系统恢复过程越长。
二、监管时间问题
主节点监管其余节点的状况,每一个节点会周期性的把完成的工做和状态的更新报告回来。若是一个节点保持沉默超过一个预设的时间间隔,主节点记录下这个节点状 态为死亡,并把分配给这个节点的数据发到别的节点。对于这个“预设的时间间隔”,这是从数据块的角度大概估算的。假如是对于64MB的数据块,我能够假设 你10分钟以内不管如何也能解决了吧,超过10分钟也没反应,那就是死了。可对于640MB或是1G以上的数据,我应该要估算个多长的时间内?估算的时间 短了,那就误判死亡了,分分钟更坏的状况是全部节点都会被判死亡。估算的时间长了,那等待的时间就过长了。因此对于过大的数据块,这个“预设的时间间隔” 很差估算。
三、问题分解问题
数据量大小是问题解决的复杂度是成线性关系的。对于同个算法,处理的数据量越大,它的时间复杂度也就越大。
四、约束Map输出
在Map Reduce框架里,Map以后的数据是要通过排序才执行Reduce操做的。想一想归并排序算法的思想,对小文件进行排序,而后将小文件归并成大文件的思想,而后就会懂这点了....
5.namenode和datanode
HDFS集群中有两类节点,并以管理者-工做者模式运行,即一个namenode(管理者)和多个datanode(工做者)。
namenode管理文件系统的命名空间。它维护文件系统树和文件系统数中全部文件和目录。这些信息以两种方式永久保存在本地磁盘上:命名空间镜像文件和编辑日志文件。
datanode是文件系统的工做者。它们存储并提供定位块的服务(被用户或名称节点调用时),而且定时的向名称节点发送它们存储的块的列表。
没有namenode,文件系统将没法使用。若是namenode机器损坏,那么文件系统上的文件将会丢失,所以对实现namenode的容错很是重要,Hadoop为此提供了两种机制:
第一种机制是备份那些组成文件系统元数据持久状态的文件。通常配置是,将持久态写入本地磁盘的同时,写入一个远程挂在的网络文件系统(NFS)。
另外一种方式是运行一个辅助的namenode。虽然它不能做为名称节点使用。这个二级名称节点的重要做用就是按期的经过编辑日志合并命名空间镜像, 以防止编辑日志过大。这个二级名称节点通常在其余单独的物理计算机上运行,由于它也须要占用大量 CPU 和内存来执行合并操做。它会保存合并后的命名空间镜像的副本,在名称节点失效后就可使用。
可是,二级名称节点的状态是比主节点滞后的,因此主节点的数据若所有丢失,损失仍在所不免。在这种状况下,通常把存在 NFS 上的主名称节点元数据复制到二级名称节点上并将其做为新的主名称节点运行。
6.客户端读取HDFS中的数据
(1)客户端经过调用FileSystem对象的open()方法来打开但愿读取的文件,对于HDFS来讲,这个对象是分布式文件系统的一个实例。
(2)DistributedFileSystem经过RPC(远程过程调用)来调用namenode,以肯定文件起始块的位置。对于每个块,namenode返回存有该块副本的datanode地址。此外这些datanode根据他们与namenode的距离来排序。
DistributedFileSystem类返回一个FSDataInputStream对象(一个支持文件定位的输入流)给客户端并读取数据。 FSDataInputStream类转而封装DFSInputStream对象,该对象管理着namenode和datanode的I/O。
(3)接着客户端对这个输入流调用read()方法。存储着文件起始块的datanode地址的DFSInputStream随即链接距离最近的datanode。
(4)经过对数据流反复调用read()方法,能够将数据从datanode传输到客户端。
(5)到达块的末端时,DFSInputStream会关闭与该datanode的链接,而后寻找下一个块的最佳datanode。客户端只需连续的读取连续的流,而且对于客户端都是透明的。
客户端从流中读取数据时,块是按照打开DFSInputStream与datanode新建链接的顺序读取的。它也须要询问namenode来检索下一批所需块的datanode的位置。
(6)一旦客户端完成读取,就对DFSInputStream调用close()方法。
在读取数据的时候,若是DFSInputStream与datanode的通讯出现错误,它便会尝试从这个块的另一个最邻近的datanode读取数据。它也会记住出现故障的datanode,以保证之后不会反复读取该节点上后续的块。DFSInputStream也会经过“校验和”确认从 datanode发来的数据是否完整。若是发现一个损坏的块,它就会在DFSInputStream试图从其余datanode读取一个块副本以前通知 namenode。
7.写入HDFS
咱们要考虑的状况是如何建立一个新文件,并把数据写入该文件,最后关闭该文件。
(1)客户端对DistributedFileSystem对象调用create()方法来建立文件。
(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,该datanode存储数据并将数据发送到管线的第二个datanode。一样的,第二个datanode存储该数据包并发送给管线中 的第三个(也就是最后一个)datanode。
(5)FSOutputStream也维护着一个内部数据包队列来等待datanode的收到确认回执,成为“确认队列”(ack queue)。当管线中全部datanode确认信息后,该数据包才会从确认队列中删除。
若是在写入期间,datanode遇到故障,则执行一下操做,这对于写入客户端是透明的。首先关闭管线,确认把队列中的任何数据包都放回到数据队列的最前 端,以保证故障点下游的datanode不会漏掉任何一个数据包。为存储在另外一个正常datanode的当前数据块指定一个新的标识,并将标识传递给 namenode,以便故障datanode在恢复后能够删除存储的部分数据块。从管线中删除故障节点并把余下的数据块写入管线中的两个正常 datanode。namenode注意到块副本量不足,会在另外一个节点上建立一个新的副本。后续的数据块继续正常接受处理。
(6)客户端完成写入后,会对数据流调用close()方法。该操做将剩余的全部数据包写入datanode管线中,并在联系namenode且发送文件写入完成信号以前,等待确认。
(7)namenode已经知道文件由那些数据块组成(经过DataStreamer询问数据块的分配),因此它在返回成功以前只需等待数据块进行最小量的复制。
8.复本的布局
namenode如何选择在哪一个datanode存储复本(replica)?这里须要在可靠性,写入带宽和读取带宽之间进行权衡。
Hadoop的默认布局策略是在运行客户端的节点上放第一个复本。第二个复本与第一个不一样且随机另外选择的机架中节点上(离架)。第三个复本与第二 个复本放在相同的机架上,且随机选择另一个节点。其余的复本放在集群中随机选择的节点上,不过系统会避免在相同的机架上放太多复本。
9.一致模型
HDFS提供一个方法来强制全部缓存与数据节点同步,及对DataOutputStream调用sync()方法。当sync()方法放回成功后,对全部新的reader而言,HDFS能保证到目前为止写入的数据均一致且可见。
10.HDFS的数据完整性
HDFS会对写入的全部数据计算校验和(checksum),并在读取数据时验证校验和。它针对每一个有io.bytes.per.checksum 指定字节的数据计算校验和。默认状况下为512字节,因为CRC -32校验和是4个字节,因此存储校验和的额外开销小于1%。
datanode负责在验证收到的数据后存储数据及其校验和。它在收到客户端数据或复制其它datanode数据期间执行这个操做。正在写数据的客户端将数据及其校验和发送到由一系列datanode组成的管线,管线的最后一个datanode负责验证校验和。
客户端从datanode中读取数据时也会验证校验和,将它们与datanode中的校验和进行比较。每一个datanode都会持久化存储一个用户 验证的校验和日志,因此它知道每一个块最后一次验证时间。客户端成功验证一个数据块后,会告诉这个datanode,datanode由此更新日志。
不仅是客户端读取数据时会验证校验和,每一个datanode也会在一个后台线程中运行一个DataBlockScanner,从而按期检查存储在这个datanode上的全部数据块。
可使用RawLocalFileSystem类来禁用校验和。
11.压缩
压缩格式 | 工具 | 算法 | 文件扩展名 | 是否包含多个文件 | 是否可切分 |
DEFLATE | N/A | DEFLATE | .deflate | 否 | 否 |
Gzip | gzip | DEFLATE | .gz | 否 | 否 |
bzip2 | bzip2 | bzip2 | .bz2 | 否 | 是 |
LZO | Lzop | LZO | .lzo | 否 | 否 |
全部压缩算法都要权衡时间/空间:压缩和解压缩速度更快,其代价一般只是节省少许空间。表中列出的压缩工具都提供9个不一样的选项来控制压缩时必须考虑的权衡:选项-1为优化速度,-9为优化压缩空间。
gzip是一个通用的压缩工具,在空间/时间权衡中,居于其余两种压缩方法之间。bzip2更高效,可是更慢。LZO优化压缩速度,可是压缩效率稍逊一筹。
在hadoop中可使用CompressionCodec对数据流进行压缩和解压缩。若是要对写入输出流的数据进行压缩,可用 createOutputStream(OutputStream out)方法在在底层的数据流中对须要以压缩格式写入在此以前还没有压缩的数据创建一个CompressionOutputStream对象,相反,对输入 数据流读取数据进行解压缩时,调用createInputStream(InputStream in)获取CompressionInputStream。
12.序列化
所谓序列化(serialization),是将结构化对象转化成字节流,以便在网络上传输或写入磁盘永久保存。反序列化,是将字节流转化回结构化对象的过程。
序列化在分布式数据处理的两大领域中普遍出现:进程间通讯(RPC)和永久储存。
hadoop只用本身的序列化格式Writable,它格式紧凑,速度快。
13.Writable
Writable类的层次结构:
Java基本类型 | Writable实现 | 序列化大小(字节) |
boolean | BooleanWritable | 1 |
byte | ByteWritable | 1 |
int | IntWritable | 4 |
VintWritable | 1~5 | |
float | FloatWritable | 4 |
long | LongWritable | 8 |
VlongWritable | 1~9 | |
double | DoubleWritable | 8 |
String(UTF-8) | Text |
14.MapReduce做业运行机制
能够只用一行代码来运行一个MapReduce做业:JobClient.runJob(conf)(若是是较新的版本,其实质也是调用这个方法)。分析其过程细节:
整个过程如图所示,包含以下4个独立的实体:
(1)做业的提交:
JobClient的runjob()方法是建立JobClient实例并调用它的submitJob()方法的快捷方式(步骤1)。做业提交 后,runJob()每秒轮询做业的进度,若是发现自上次报告后有变化,便把进度报告到进度台。做业完成后,若是成功,就显示做业计数器。若是失败,致使 做业失败的错误被记录到控制台。
JobClient实现的submitJob()方法实现的做业提交过程以下:
(2)做业的初始化
当JobTracker接收到jobclient的submitJob()方法调用后,会把此调用放入一个内部队列中,交由做业调度器(job scheduler)进行调度,并对其进行初始化。初始化包括创建一个正在运行做业的对象——封装任务和记录信息,以便跟踪任务的状态和进程。
为了建立任务运行列表,做业调度器首先从共享文件系统获取JobClient已经计算好的输入分片信息。而后为每一个分片建立一个map任务。建立的 reduce任务数有JobConf的mapred.reduce.task属性决定。而后调度器建立相应数量的reduce任务。任务ID在此时被指 定。
(3)任务的分配
tasktracker运行一个简单的循环来按期发送“心跳”(heartbeat)给jobtracker。心跳告诉 jobtracker,tasktracker是否还存活,同时也充当二者之间的消息通道。做为“心跳”的一部分,tasktracker会指明它是否已 经准备好运行新的任务,若是是,jobtracker会为它分配一个任务,并使用“心跳”的返回值与tasktracker进行通讯(步骤7)。
在jobtracker为tasktracker选定任务以前,jobtracker必须先选定任务所在的做业。默认的方法是维护一个简单的做业优先级列表。固然还有各类调度算法。
对于map任务和reduce任务,tasktracker有固定数量的任务槽。默认调度器会在处理reduce任务槽以前,先填满map任务槽。
为了选择一个reduce任务,jobtracker简单的从待运行的reduce任务列表中选取下一个来执行,用不着考虑数据的本地化。而后,对 于一个map任务,jobtracker会考虑tasktracker的网络位置,并选取一个距离其输入分片最近的tasktracker。
(4)任务的执行
如今,tasktracker已经被分配了一个任务,下一步是运行任务。第一步,经过共享文件系统将做业的JAR复制到tasktracker所在 的文件系统,从而实现JAR文件本地化。同时,tasktracker将程序所需的所有文件从分布式缓存复制到本地磁盘(步骤8)。第二 步,tasktracker为任务新建一个本地工做目录,并把JAR文件解压到这个文件夹下。第三步,tasktracker新建一个 TaskRunner实例来运行该任务。
TaskRunner启动一个新的JVM(步骤9)来运行每一个任务(步骤10),以便用户定义的map和reduce函数的任何软件问题都不会影响 到tasktracker(例如致使崩溃或挂起等)。可是在不一样的任务间共享JVM是可能的。子进程经过umbilical接口与父进程进行通讯。任务的 子进程每隔几秒便告诉父进程它的进度,直到任务完成。
(5)做业的完成
当jobtracker收到做业最后一个任务完成的通知后,便把做业的状态设为“成功”。而后,JobClient查看做业状态时,便知道任务已完成,因而JobClient打印一条消息告知用户,而后从runJob()方法返回。
最后jobtracker清空做业的工做状态,指示tasktracker也清空工做状态(如删除中间输出等)。