Hadoop是Apache软件基金会旗下的一款开源软件框架,它主要
因此,Hadoop可以有效的解决大数据的基础3V问题,即:
a) Volume (Scale of data):Hadoop可轻易处理PB量级的数据
b) Velocity (Speed):并行式的数据处理效率更高,且实时性可以得到保障
c) Variety (Diversity):Hadoop可存储任意类型的数据
综上所述,我们不难总结出,Hadoop主要解决大数据中的两个关键问题:1) 分布式存储;2) 并行处理。 针对这两个关键问题,Hadoop提供以下两个核心服务:
从Hadoop v2.0开始,在以上2个核心服务之外,又加入了一个新的框架YARN。在这里我们需要注意的是,YARN并不是一个完完全全的新框架,实际上我们可以把它看作是对Hadoop v1.0的MapReduce进行任务分割的产物。
在Hadoop v1.0中,MapReduce除了数据处理 (Data Processing) 之外,还要进行集群资源管理 (Cluster resource management),即如何把底层的CPU,内存,带宽等资源分配给上层的计算任务。这样会使得MapReduce的工作效率很低,因此在Hadoop v2.0时,将资源调度这部分的任务单独划分出来,就是现在我们看到的YARN,如此一来,MapReduce就可专心进行数据处理的任务。
如今,Hadoop已经形成了一个相当完整且庞大的生态系统。在当下的Hadoop中,它的主要组成部分可以看做有3个:
1. HDFS:分布式文件存储系统
2. MapReduce:分布式并行处理框架
3. YARN (Yet Another Resource Negotiator):工作调度与资源分配框架
除此以外还有诸如Hive, Pig, Spark等众多的服务框架。
Hadoop对于使用者屏蔽了所有大数据技术的底层实现细节,因此,用户无需担心:
Hadoop采用Master-Slave/Manager-Worker结构。这一结构的作用我们用一张图就可以很好理解:
即主节点 (Master/Manager Node) 将任务分发 (Distribute) 给多个工作节点 (Slave/Worker Node) 进行处理,这就是分布式处理思想的具象化,比起传统单一节点慢慢处理,效率有了巨大的提升。但若仅仅是这样,仍然有一个巨大隐患。
我们可以想象一下,如果此时有某个员工C(工作节点)罢工了:
那么,分发给该员工(工作节点)的任务将会面临无法验收的境地,老板对此必然气得跳脚。当然,老板(主节点)也可以把员工C的任务再分配给员工A或者其他“在职”的员工,但如果每次都如此处理,那么会很不方便,因此老板有一套新的策略:
把同一个任务分配给两个员工来做(鸡蛋不放在一个篮子里,反正工作的也不是老板),这样一来:
即使有某个员工“罢工”了,老板仍能收到所有任务的成果,不必担心因“罢工”带来的损失。
Hadoop的结构特征体现在它提供的众多框架之中,比如我们接下来要介绍的HDFS。在这里我们还要注意一点,在Hadoop中,工作节点 (Slave Node/Worker Node) 不必是具有高性能的服务器,它可以是任何一台普通的PC机,这也从另一个层面体现出Hadoop的易用性。
HDFS (Hadoop Distributed File System) 是一个:
文件系统 (File System),该系统支持:
这里特别介绍一下什么是水平扩展性 (Horizontal Scalability),既然有水平扩展 (Horizontal Scaling),那么就有垂直扩展 (Vertical Scaling)。我们用一张图来体会它们的区别:
垂直扩展 (Vertical Scaling) 指的是单纯通过升级硬件性能来增大存储空间。但是这里有两个问题,一是硬件性能是有上限的,二是升级硬件时,必须暂停运行。与之相对的,就是水平扩展 (Horizontal Scaling),水平扩展指的是对现有的工作集群添加更多的工作节点 (Node) 以增加集群整体的容量,提升工作性能,这样的操作打破了硬件的上限,我们可以尽可能多地给集群添加新的节点,从理论上来说,可以无限增长容量,而且在添加新的节点 (Node) 时,我们不需要暂停集群中的节点的工作。
我们首先给出HDFS的结构图:
其中Client很好理解,就是用户使用的机器,因此我们主要把目光放在NameNode, DataNode以及SecondaryNameNode上。
NameNode是一个Master Node,它主要用来维持和管理DataNodes(Slave Nodes)中的存储的Blocks。 它的主要功能有:
DataNode是Slave Node,主要是用于存储数据的商业硬件 (commodity hardware)。它有以下的功能:
下面用一张表格直观对比NameNode和DataNode:
接下来,我们思考一个问题。在之前介绍NameNode时,我们提到了,它可以在DataNode失效时进行数据恢复,那么如果NameNode失效了,该怎么办?
NameNode失效带来的后果远比DataNode严重得多,一旦NameNode失效,所有的文件都将丢失,因为没有NameNode中保存的元数据 (Metadata),无法进行数据的重建。为了保证NameNode能够从失效中能迅速恢复 (Resilient),就有了两种思路:
Secondary NameNode为NameNode中的文件系统元数据提供Checkpoints。这里,我们需要注意,Secondary NameNode它并不是NameNode的备份!
它的功能主要有:
之所以周期性对FsImage调用Editlogs,除了跟踪操作以外,一个很重要的原因是为了避免Editlogs占用的空间越来越大。一般来说,Secondary NameNode会运作在一个单独的机器上,因为它需要充足的CPU资源来进行FsImage和Editlogs的合并 (Merge) 操作。
从以上Secondary NameNode的功能来看,它确实保存了FsImage和Editlogs的副本,因此可以用来帮助解决NameNode失效的问题,但是,我们要注意,SecondaryNameNode的状态实际上滞后于NameNode,所以他对于解决NameNode失效的问题实际上帮助有限。因此,实际情况中,通常会使用元数据备份至NFS的方式来解决问题(更加简单粗暴)。
那么SecondaryNameNode的主职工作以及它的NameNode的关系到底是什么样的呢,我们依然用一张图来看:
因此,NameNode实际上不会直接更新自己的FsImage,这些工作都是交给Secondary NameNode完成的(老板负责宏观调控就好了,具体任务不需要亲力亲为)。NameNode做的只是将FsImage和Editlogs关联起来,而不需要亲自使用Editlogs去更新FsImage(进行merge操作)。
在介绍了HDFS的结构以及各组成部分的具体职能之后,我们来看一看HDFS到底是如何存储文件(File)的,具体的存储策略有两种,接下来将一一介绍。
首先,我们要明确一点,HDFS在存储文件 (File) 时,并不是把一整个文件存储在某个DataNode,而是会把文件 (File) 分割为多个Blocks,分散存储。
这里用一个例子就能很好地理解:
我们可以直观地得出一个简单的公式:
因为HDFS存储的是量级巨大的数据集,如果block size很小(比如像Linux一样为4kb),那么block的数量将会变得很多,这也将造成:
这也是为什么我们不推荐使用HDFS存储小文件的原因,因此即使一个仅有4kb的小文件,它依然会占用一整个block,会极大造成资源的浪费。
好的,到目前为止我们已经知道,文件会被分割为一系列的blocks存储于DataNode之中,那么,我们再来考虑一遍那个经典的问题,如果DataNode失效了,该怎么?在Hadoop中,如果NameNode 10分钟未从DataNode接收到Heartbeat Message,就会认为知道该DataNode“死亡”。
为了保证数据的可靠性,Hadoop对数据 (data) 生成多个副本 (multiple replications),这就是我们要讨论的副本存放策略。
我们之前提到过,NameNode会在DataNode失效时,负责进行数据恢复,这里我们就具体说一说NameNode在此过程中到底做了什么。首先,正如前文所说,NameNode保存了所有DataNode的元数据,因此它知道关于所有DataNode的一切(除了DataNode保存的数据的内容)。当NameNode探测到某个DataNode失效时,它会知道该DataNode中原本保存着哪些blocks,它同样知道这些丢失的blocks的副本在其他哪些DataNodes中,因此NameNode就可以从其他的replications中复原出丢失的blocks。
这种存储策略同样有助于保证数据的完整性 (Integrity),即知悉保存于HDFS中数据是否正确。HDFS会持续使用Checksum来校验存储于DataNode中的数据是否损坏,一旦发现某些blocks损坏了,就会立刻报告给NameNode,NameNode会根据该block的其他副本 (replication) 创建一个新的replication,然后把损坏的block删除。
这里我们来看一个例子:
这里我们将一个文件 (file) 划分为5个blocks,且每个block有3个replications,此时若有一个DataNode失效:
我们可以看到,已丢失的block仍各自留有2个副本在其他的DataNode中。因此,当DataNode1重新上线,我们可以还原其中的blocks。或者寻找一个新的DataNode,比如DataNode5,把恢复的blocks存入其中,以保每个block证始终有3个副本存于HDFS中。
*关于为何默认replication factor为3,之后我会单独用一篇博客来进行解释,这里就先不赘述
我们已经考虑了DataNode一个接一个失效的情况,但仍不够全面,如果有多个DataNode同时失效 (Simultaneous Failure),那么会怎样呢?
我们用数学语言来看一下:
这里我们着重讲一下 L2(2, N) = 2 * L1(1, N) / (N-1):
这里的 L1(1, N) 表示已经失去第一个副本的blocks的数量,我们用集合A表示这些遗失的blocks。对于集合A中的这些blocks,他们仍然有2个副本保存于其他的DataNode中,我们假设这些副本均匀分布 (Uniformly distributed) 于剩下的N - 1个节点中。
因此,对于每一个DataNode,它拥有集合A中的blocks的对应副本的期望值 (expected number) 为 2 * L1(1, N) / (N-1)。
所以,当第二个DataNode(剩余 N - 1个节点中的1个) 失效时,我们预计会有 2 * L1(1, N) / (N-1) 个blocks失去它们的第二个副本。
我们可以得到一个式子:
L2(k, N) = 2 * L1(k-1, N) / (N - k + 1) + L2(k-1, N) - L2(k-1, N) / (N-k+1)
我们考虑一个实际的情况:N = 4000,B = 750,那么我们会得到以下结果:
可以明显地看出来,即使有200个节点同时失效,失去全部三个副本的blocks的数量仍在一个可以接受的范围之内,这样就从另一个层面证明了把replication factor设置为3能有效保证数据的可靠性 (Reliability)。
回顾一下HDFS的结构图,现在,我们来了解一下其中的最后一个要素Rack。我们要记住其最重要的一个概念:同一个Rack中的Nodes,在物理意义上彼此十分靠近。
所以Rack Algorithm的存在意义就是为了在保证数据不会丢失的同时,提高读写以及数据恢复的速度。同一个Rack中的Nodes之间,速度很快,处于不同Rack的Nodes之间,速度较慢。
具体来看Rack Algorithm。当我们设定了Replication Factor为3后:
用一张图来看一下:
以Block 1为例,第一副本存储于Rack 1的Node 1中,第二副本和第三部分都存储于Rack 2,但是它们所处的Node不同,一个在Node 5,一个在Node 6。
为什么我们要秉持这种Rack的思想呢?
这里我们先给出一幅“距离 (Distance)”度量图:
我们可以很清晰地看到:
而距离 (Distance),就直接影响了速度 (Speed)。
所以,我们在此秉持的这种Rack思想,可以:
HDFS的write操作有三个阶段:
这里的DistributedFileSystem和FSDataOutputStream是两个Hadoop的Java class的实例(instance),我们在此不做过多的解释。
以下为3个阶段的具体工作流程:
1. Create File
2. Write File
3. Close File
在整个过程中,我们有以下几点需要注意:
同样,read操作也有一些我们需要注意的地方:
尽管我们用3 Replications这种副本存放方式能够有效保证数据可靠性 (Data Reliability),但这种方式仍有弊端。首先,这种方式会比单纯存放数据多花费2倍的空间(在进行写入操作时,也会消耗更多的带宽资源)。其次,一些冷门的数据集,它们本身就不常被访问,但这些数据仍会和那些热门的数据占用同样的空间,这也是一种浪费。
因此,这就诞生了另一种保障数据可靠性,且能避免以上两种问题的新技术 - Erasure Coding。
Erasure Coding的思想并不复杂,比如我们想要存储一个数字39:
我们直接将39分割为x = 3和y = 9,并通过对x和y 进行编码 (encoding),生成了三个式子,我们存储这三个式子,这样,不管其中哪个式子丢失了,我们仍能用剩下的两个式子解码 (decoding) 出x和y。
可能用数字不是很直观,那么我们以File的形式重新看一遍,我们把39看做是一个File,x和y是该文件分割成的2个blocks。此时若们仍用Replications进行存储,那么我们分别对x和y建立3个副本,一共就需要6个副本来存储该文件。但我们若用Erasure Coding,那么只需要对用x和y进行编码 (encoding) 得出的3个副本进行存储,因此只需要用3个副本即可存储该文件。
接下里我们看一看具体的操作
现在考虑:
然后用X和G做内积,得到新的矩阵P:
直观表示为:
以上就是编码 (encoding) 的过程,在解码 (decoding) 时,我们只需要G和P中的任意6行 (G’和P’),即可复原出X:
我们稍微总结一下:
我们可以简单地看做把 xi 和 parities存储于不同的blocks,即使有3个blocks丢失了,最后我们仍能复原出存储的数据。这项技术已经从v3.0开始在Hadoop中使用。接下来我们来看具体的流程。
这样的写入方式 (Online writing) 同时也支持streaming input(流输入),因为数据持续流入的时候,我们并不知道它到底有多大。
即使是block 6和block 7同时失效,仍能够借助block 1- 5 + block 8先复原block 6,再复原 block 7
用户 (client) 会同时 (simutaneously) 写入一个由9个DataNode(因为9个blocks在不同的DataNode中)
从有data blocks的6个DataNode并行读取。注意,我们不会直接读取parityies block!!
但如果在读取过程中有DataNode失效,那么,就会用剩下的parity block所在的DataNode进行补位,之后,重建失效的DataNode
上表中的第一列表示一个文件 (File) 有多少个blocks。我们可以看到,使用3-Replication存储时,我们在进行读取 (read) 操作的时候,需要读取的blocks数量等于文件的block数量,比如一个文件有3个blcoks,那么,我们在读取该文件时,就需要读取3个DataNode。若使用(6, 3) - RS,我们假设一个文件 (File) 有B个blocks,那么,使用(6, 3) - RS需要读取
综上所述,我们可以看出,Erasure Coding更适合大型的以及鲜少访问的文件。HDFS的用户以及管理员对单个文件/路径可以选择是开启/关闭Erasure Coding。