学习Hadoop有一段时间了,主要是通过《Hadoop权威指南》,同时参考了网上的很多文章。静下心来,盘点下这一段时间的收获,归纳总结,做一个学习笔记,因为可以记录的东西实在太多了,所以这里就着重记录我在学习过程中花费比较多时间去理解的内容。
说到Hadoop就不能不提到Lucene和Nutch。Lucene并不是一个应用程序,只是提供了一个Java的全文索引引擎工具包,可以方便的嵌入到各种实际应用中实现全文搜索、索引功能。Nutch是一个以Lucene为基础实现的搜索引擎应用。在Nutch0.8.0版本之前,Hadoop还属于Nutch的一部分,而从Nutch0.8.0开始,将其中实现的NDFS和MapReduce剥离出来成立一个新的开源项目,这就是Hadoop。Hadoop中实现了Google的GFS和MapReduce算法,使之成为了一个分布式的计算平台。
Hadoop不仅仅是一个用于存储的分布式文件系统,而且是设计用来在由通用计算设备组成的大型集群上执行分布式应用的框架。
1. HDFS
先来看看HDFS,即Hadoop Distributed File System。它是为以流的方式存取大文件而设计的,适用于几百MB,GB以及TB,并一次写入多次读取的场合。HDFS是为达到高数据吞吐量而优化的,这有可能会以延迟为代价,所以,对于低延时数据访问、大量小文件、同时写和任意的文件修改,它并不是十分适合。
HDFS是以block-sized chunk组织其文件内容的,默认的block大小为64MB。与单一磁盘上的文件系统不同的是,HDFS中小于一个块大小的文件不会占据整个块的空间。构成HDFS主要是Namenode(master)和一系列的Datanodes(workers)。Namenode是管理HDFS的目录树和相关的文件元数据,这些信息是以“namespace image”和“edit log”两个文件形式存放在本地磁盘,但这些文件是在HDFS每次重启的时候重新构造出来。Datanode则是存取文件实际内容的节点,Datanodes会定时地将block的列表汇报给Namenode。
Namenode是元数据存放的地方,所以其错误容忍能力相当重要,一般使用将元数据持久存储在本地或远程的机器上,或使用secondary namenode来定期同步Namenode的元数据。如果Namenode出现了故障,一般会将原Namenode中持久化的元数据拷贝到secondary namenode中,使secondary namenode作为新的Namenode运行。
HDFS的结构如图 1所示。从图中可以看到,Namenode, Datanode, Client之间的通信都是建立在TCP/IP的基础之上。当Client要执行一个读取操作的时候,向远程的Namenode发起RPC请求,Namenode会视情况返回文件的部分或全部block列表;对每个block,Namenode都会返回有该block拷贝的Datanode地址,客户端开发库会选取离客户端最接近的Datanode来读取block;读完当前block后,关闭与当前Datanode连接,为读取下一个block寻找最佳Datanode;读取完一个block都会进行checksum验证,如果读取有误,Client会通知Namenode,然后再从下一批拥有该block拷贝的Datanode继续读取。
2. Map-Reduce
Map-Reduce是一个使用简易的软件框架,基于它写出来的应用程序能够运行在大型集群上,并以一种可靠容错的方式并行处理TB级别的数据集。Map-Reduce把分布式的业务逻辑从复杂的细节(如何分布,调度,监控及容错等)中抽离出来,使程序员可以只关心应用逻辑,关心根据哪些key把问题分解,哪些操作是map操作,哪些是reduce操作,其它并行计算中的分布、工作调度、机器间通信等问题都交给Map-Reduce Framework去做,很大程度上简化了整个编程模型。
一个Map-Reduce 作业(job) 通常会把输入的数据集切分为若干独立的数据块,由 map任务(task)以完全并行的方式处理它们。框架会对map的输出先进行排序, 然后把结果输入给reduce任务。通常作业的输入和输出都会被存储在文件系统中。 整个框架负责任务的调度和监控,以及重新执行已经失败的任务。Map过程通过对输入列表中的每一条记录执行map函数,生成一系列的输出列表,如图2。Reduce过程对一个输入的列表进行扫描工作,随后生成一个聚集值,最为最后的输出,如图3。若map的输出进行了分区处理,则在同一个分区的数据将被发送到同一个Reduce任务中。
整个MapReduce的数据流程如图4所示。
图4 MapReduce数据流程
数据从map函数输出到作为reduce函数的输入的过程被称为shuffle(混洗或洗牌)。在这个过程中,框架对数据进行了很多处理,有人将shuffle称为MapReduce的心脏,是奇迹发生的地方。我在学习这里的时候也是花了比较长的时间去理解。了解shuffle如何工作,对于优化MapReduce大有帮助。
Map函数产生的输出结果,不是简单的将它写到磁盘,这个过程更复杂。每个map任务都有一个环形内存缓冲区,任务会把输出写到此,当缓冲内容达到指定大小时,一个后台进程开始把内容溢写到磁盘中。在溢写过程中,map的输出将会继续被写入缓冲区中,但如果缓冲区在这时满了,那map就会阻塞直到溢写完成。在写到磁盘之前,线程首先对数据分区已决定将数据发送到哪一个reduce任务。在每个分区中,后台线程按键进行内排序。
Reduce任务首先要复制取得map任务输出,map任务可以不再同一时间完成,所以只要有一个任务结束,reduce任务就开始复制其输出。内存缓冲区达到阈值大小或达到map输出阈值时,会被合并,进而被溢写到磁盘中。随着磁盘上的副本增多,后台线程将他们合并成一个更大的,排好序的文件,这将是reduce函数真正的输入文件。Reduce阶段的合并阶段也被称为排序阶段,它的具体操作与map阶段的内排序有所不同,我在理解这点的时候就出现了很多问题。
以WordCount为例,看看mapreduce的执行过程和shuffle的流程。假设有640M的文件,默认block大小为64M,10台hadoop机器,使用5个reduce任务:
1、每个map任务按行读取文件,读取的行数据交给map函数执行,每一行数据调用一次map函数,以单词为key,以1为value输出。
2、 如果有combiner,就对第一步的输出进行combiner操作。它将map输出结果做一次归并,将相同word的计数累加。这样可以将中间结果大大减少,减少后续partitioner, sort, copy的开销,提高性能。
3、 每个map任务对自己的输出进行分区,默认的分区操作是对key进行hash,并对reduce任务数(5)求余,这样相同的单词都被分在同一分区内。之后将对同一分区内的数据按key进行排序,使之成为各分区内有序。
4、 Reduce任务各自从map机器上copy属于自己的文件,并且进行合并。合并好后进行sort操作,再次把不同小文件中的同一单词聚合在一起,作为提供给reduce操作的数据。
5、 进行reduce操作,对同一个单词的value列表再次进行累加,最终得到每个单词的词频数。
6、 最后Reduce把结果写到磁盘。
3. Hadoop I/O
Hadoop附带了一个基本类型的数据集的I/O,其中某些技术比Hadoop本身更具普遍意义。在这里只总结下和MapReduce框架紧密相关的InputFormat和OutputFormat,以及SequenceFile和MapFile文件。
Hadoop中的MapReduce框架依赖InputFormat提供数据,依赖OutputFormat输出数据;每一个MapReduce程序都离不开他们。Hadoop提供了一系列InputFormat和OutputFormat方便开发。
TextInputFormat:用于读取纯文本文件,文件被分为一系列以LF或CR结束的行,key是每一行的偏移量(LongWritable),value是每一行的内容(Text)。
KeyValueTextInputFormat:用于读取文件,如果行被分隔符分割为两部分,第一部分为key,剩下的为value;若没有分隔符,整行作为key,value为空。
SequenceFileInputFormat:用于读取SequenceFile。关于SequenceFile后面再说。
SequenceFileInputFilter:根据filter从SequenceFile中取得满足条件的数据,通过setFilterClass指定Filter,内置了三种Filter,RegexFilter取key值满足指定的正则表达式的记录;PercentFilter通过指定参数f,取记录行数f%==0的记录;MD5Filter通过指定参数f,取MD5(key)%f==0的记录。
TextOutputFormat:输出到纯文本文件,格式为key + “ ”+ value。
NullOutputFormat:hadoop中的/dev/null,将输出送进黑洞。
SequenceFileOutputFormat,输出SequenceFile文件。
MultipleSequenceFileOutputFormat, MultipleTextOutputFormat:根据key将记录输出到不同的文件。
SequenceFile & MapFile
序列化(serialization)指的是将结构化对象转为字节流以便于通过网络进行传输或写入持久存储的过程。序列化用于分布式数据处理中两个截然不同的领域:1、进程进通信,2、持久存储。SequenceFile类为二进制键/值对提供一个持久化的数据结构。其实在Map-Reduce过程中,map处理文件的临时输出就是用SequenceFile处理过的。
虽然很多MapReduce程序都是用writable键/值类型,但这并不是MapReduce的API指定的,事实上,任何类型都可以用,只要每种类型都可以转换为二进制表示并从二进制表示中转换出来。
理解好SequenceFile,MapFile就容易明白了,它其实是经过排序的带着索引的SequenceFile,可以根据键进行查找。将SequenceFile进行排序,再创建索引即可转换为MapFile。
Hadoop的内部工作机制是复杂的,相互依赖的,因为它运行在分布式系统的理论、实用技术和技术常识这些复杂的基础之上。学习Hadoop也将是一个漫长的过程,以上只是我在这段时间的一点学习总结,希望能在工作中不断地深入理解hadoop的思想,并将其运用到实际工作中。