Hadoop技术梗概(一)

Hadoop技术梗概(一)

概要

Hadoop的核心主要包括几个子项目。Hadoop common、Hadoop HDFS、以及Hadoop MapReduce。这三个部分是Hadoop最重要的三个部分。

Hadoop common是Hadoop的核心,是曾经Hadoop项目的Core部分。很多其他版块都依赖于Hadoop common。Hadoop common主要负责Hadoop的配置以及Hadoop的远程控制、序列化的机制、抽象的文件系统等等基础功能呢,也就是一些底层API的提供。

Hadoop HDFS是一个Hadoop的分布式文件系统,是一种高吞吐量的文件系统。也是支持Hadoop预算的文件系统。

Hadoop MapReduce是一种计算模型,这个计算模型分为两个阶段,一个是Map阶段,这个阶段我们将数据集上的一个个元素进行操作,生成一个个键值对这样的结果。Reduce是这个计算模型的规约阶段,就是将我们计算的一个个键值对的结果进行一个规约,最后成为一个大结果。

Common的实现

配置文件前的处理

Hadoop Common的一个非常主要的功能就是处理配置文件,Java和Windows现在都有比较成熟的配置文件处理方案,但是有意思的是Hadoop使用的是自己的一套配置文件处理方案,也拥有不同的配置文件处理规则。

Hadoop因为配置文件都不大,所以都是采用了将配置文件文本一口气读入内存,然后再解析的方式。并且在一开始就有默认的配置文件路径,比如HDFS就有hdfs-default.xml和hdfs-site.xml,而MapReduce框架中有mapred-default.xml以及mapred-site.xml两个配置文件。

Hadoop提供了大量get和set方法来进行属性的获取和设置,并且会记录上final常量属性。值得一提的是Hadoop提供Configable接口,任何实现了这个接口的对象都能被Hadoop common写到配置文件中。


系列化的相关工作

序列化是Hadoop Common的一个重要工作。所谓序列化,就是使用字节流解决对象传送的问题。我们使用一定的序列化算法将对象中的所有信息编程二进制码。一个完整的序列化信息包含所有成员的数据成员的名字和内容。这里我们说的是Java传统的序列化方式。但是Hadoop的序列化方式与Java的序列化方式后很大的区别。

首先,因为Hadoop有比较大的通信负载,所以我们需要尽可能传送少的内容来表达相同的意思。对于Java原有的序列化方案来说,首先对于“元数据”的处理就显得太过冗长了。在Java的序列化来说,它保留了一个对象事无巨细的信息,但是很多这些信息在Java强大的反射机制下是不足为惧的,实际上我们需要的只是一个对象的类型名就好了。这里需要提一下Hadoop的writable接口,这个接口和Java的Serializable接口一样,需要序列化的东西就需要打上writable的接口,不过Hadoop和Java不同的是,Java的Serialization只要打上这个“标签”一样的声明就可以直接进行序列化的,程序员不再需要做任何事情。

class TestClass implement Serialization{
  ......
}

但是Hadoop的writable就不仅仅是这样了,实现这个接口需要我们实现两个函数,一个是write,用以将数据成员序列化写入,一种叫做readField,将序列化流中的数据库反序列化读出,而这两个方法中写入数据成员的速度和读出数据成员的顺序是一样的,这就是为什么Hadoop的序列化传可以更加简洁的另一个原因,那就是不在序列化串中声明每一个数据成员的名字,方便反序列化的时候一一赋值,而是认为规定了数据成员序列化与反序列化的顺序,从而在另一种形式上做到一一对应。

当然Hadoop也提供了现成的、高度健壮的数据序列化方案,比如基本类型的Writable改造以及ObjectWritable对象。这都是现成的。这两个都是将普通的数据类型传入他们的构造函数来初始化,然后就可以调用序列化方法了。这种现成方案的处理更加全面和现先进,实现比较复杂,但是解决了所有数据类型序列化的问题。值得一提的是,这种方式提供了基本数据类型显式变长存储的方式,是的对于带宽的占用更小了。

此外,Java对于序列化的处理还有其他的不足。在Hadoop中我们时常要对数据进行分片,交给不同的Map task去处理。而Java会共用一个元数据,也就是说,Java对象的序列化会将元数据写在一起,然后具体的数据使用一个索引来引用这些元数据,实际上这个是Java用来节省空间用的,因为很多数据会共用一个索引。Hadoop的元数据(对象的类名)就在这个对象的具体数值的前面。

最后为了降低反序列化的开销,Hadoop会不断重用已经创建好的序列化对象,而Java只会不断创建新的对象(因为对象的内部还有其他的对象,所以序列化是一个递归的过程,如果不断创建新的对象来处理一层层的序列化,开销很大)。


压缩

压缩也是Hadoop通信非常重要的部分,Hadoop可以创建一个“压缩器”,然后在这个“压缩器”的压缩流中进行压缩。为了提高效率,Java在压缩算法上会调用本地的C库(Native Library)。JNI是完成这一切的工具,这个库的用法就是我们为C的特定函数生成一个“存根”,JNI会负责这个库和Java虚拟机的通信工作,将本地库的调用结果返回。


Hadoop的远程调用方式

远程调用时Hadoop不可或缺的一个部分。Hadoop在Java自带的远程调用上做出了一些改进。Java的远程调用就是在远程建立一个rmi服务器,将远端的一个函数注册在一个格式为“rmi:XXXXX”的域名之下,我们通过访问这个域名来获取这个对象,然后调用这个对象的方法。这个过程使用一个叫做“动态代理”的设计模式封装。

Hadoop使用了自己的远程调用方法,主要的不同是在于通道与连接复用。通道是以“块”为传输单位,使用一个缓冲区来存储要发送的东西和接收到的东西,一个“选择器”会不断监听所有的通道(实际上就是不断监听所有的缓冲区)。这种方式可以降低网络的开销,当一个通道已经好久没有被人使用的时候,这个通道就会被回收。此外对于到同一个服务器的同一个远程调用的通道也会被复用。

整个远程调用的过程,一般分为这么几个部分,一个是客户存根和服务器骨架。客户存根实际上就是服务器函数的接口,这个存根会被动态代理这个设计模式绑定,在每次调用这个远程对象的方法的时候,这个代理都会使用一套我们设定好的统一逻辑和服务器进行通信,并返回结果。对于客户端存根来说,他并不知道代理的存在,就好像这个方法就是在本地调用运行的一样。

此外还有在远程调用的过程中,客户端要将对应方法的“版本号”发给服务器端。只有“版本号”一致,才能成功进行调用。

我们可以看出,Hadoop的远程调用的设计实现目标就是:轻量、复用。


抽象文件系统

就像Linux的VFS一样,Hadoop也有一个抽象文件系统,这个文件系统抽象了不同的文件系统,并将其统一在一个接口之上。

在《Hadoop技术内幕》中,详细介绍了ex2这个文件系统,虽然这个文件系统和Hadoop并没有直接关系,Hadoop主要使用的是HDFS,当然在VFS的加持下,Hadoop支持很多的文件系统,包括在内存中的文件系统,也包括本地的文件系统。

说到ex2文件系统,我们就不得不提i-node这个东西,这个文件系统的结构是这样的。

我们看到在数据块区(也就是真正存储文件的地方)前面有一个叫做i-node的东西,这个东西就是文件的“门牌号”。这个“门牌号”包括了一个这个文件的基本信息和这个文件所有文件块的磁盘物理块索引。

我们知道文件是由一个个连续的“文件块”组成的,而“文件块”只是一种逻辑上的结构,实际上一个文件块对应的是一个磁盘上面的一个“物理块”。而一个i-node上面就有通向一个文件所有物理块的指针。

这个就是i-node的内容。我们可以看到有“直接指针”和“间接指针”。这样子对于小文件来说有比较高的存取效率,大文件有不会让i-node占用过大的空间。

目录也是一种特殊的文件,这个文件的主要的内容就是维护一个表,这个表里面的主要内容就是一个目录的所有文件名或者子目录名与他们i-node的一个映射。因为所有的文件都是一个目录加一个文件名构成,我们已知根目录在i-node2。那么我们就可以一层一层定位出我们的文件元数据所在的i-node了,然后我们将这个i-node放到内存中,然后告知系统我们打开这个文件了,系统就会在“文件结构表”中登记这个文件,“文件结构表”包含了文件的基本信息,是一个操作系统级别的表,这个系统就这一个表。每个进程也会维护一个“文件描述符”表,使用open接口打开一个文件的时候会返回一个整数,这个整数就是“文件描述符”而这个表就是一个int和一个指向“文件结构表”中表项的索引的映射。

这个就是ex2这个文件系统的所有内容,其实和hadoop之间没有什么直接关系,但是与HDFS有一定的相关。

现在来说抽象文件系统的事情,抽象文件系统的设计思想和VFS虚拟文件系统息息相关,是Hadoop Common的重要组成部分,提供的功能和Java的File类非常想,只不过是假设在很多具体的文件系统之上。

在Hadoop支持的具体文件系统中,有一种本地文件系统子类叫做ChecksunFileSystem,这个文件系统最大的特点就是引入了循环冗余校验法。这种验证法叫做CRC。这个链接讲了很多细节,我们就不再赘述了循环冗余校验(CRC)算法入门引导。

而ChecksunFileSystem这种文件系统实际上和我们之前说的像ex2的这种本地文件系统并没有本质区别,实际上就是在这个系统中的文件会附带一个“校验文件”,这个校验文件是隐藏文件,文件名和源文件是一样的,但是前面有一个”.”代表这个是隐藏文件,并且后面这个文件的文件后缀名就是.crc。这就是与传统的本地文件系统相比全部的不同。

实际上这种文件系统就是每个文件每512字节的数据算一个crc,然后将算出的值不断放到crc这个文件中就好了。

HDFS的实现

概述

HDFS是一个分布式文件系统,主要处理对响应要求不高,大量数据的存取有关的场景。数据块是HDFS存取的基本单位,与Linux的本地文件系统不同,一个数据块有64M的大小,这个是远大于Linux本地文件系统的4096字节的大小。而之所以要制定出那么大的数据块大小,是因为HDFS是为了存储特大的数据文件而设计的,在HDFS中,甚至我们可以看到它对最小的文件大小是有限制的,太小的文件HDFS反而还不接受。所以较大的数据块就意味着一个客户端与数据节点的通信次数可以比较少,一次通信可以获取一个数据块。这个就是HDFS拥有较大数据块设置的原因。

这里这个“数据块”的概念和我们之前提到的“文件快”、“物理块”不是同一个东西。这个数据块指的是HDFS这个层面的东西,因为HDFS是分布式的存储系统,每一个数据块都是存储系统一个文件。数据块指的是这个“文件”的大小,而实际上因为在具体的存储节点上还是使用的ex3、ex2这样的文件系统,所以实际上这个文件在磁盘上面的表现同样是依照前文介绍的Linux文件的存储方式存储的。也就是说HDFS这个文件系统的层次是在ex2、ex3这样的文件系统之上的,这种文件系统不讨论具体到硬盘这种维度的事情,而是仅仅停留在“数据存储节点”这个层次。而HDFS主要就是解决在不同的节点之间存储和调度的问题。

组成

HDFS在结构上分为这么几个部分,一个是客户端,一个名字节点,一个“第二名字节点”,还有众多“数据节点”。数据节点就是真正存储数据的地方。在HDFS中,一个大型文件的在客户端会被分片成一个个我们前文所提到的数据块,这些数据块会被分散放到不同的数据节点上。

名字节点存储和HDFS的元数据,这个节点上保存了一个目录表,这个目录表保存了HDFS文件目录和具体数据节点中的数据块的映射关系。HDFS的名字节点上有两个文件,一个叫做“命名空间镜像”还有一个叫做“编辑日志”。这个“命名空间镜像”就是这个HDFS的目录结构和数据块的映射,而我们对于HDFS的修改都会保存在“编辑日志”中,而“命名空间镜像”和“编辑日志”中内容的叠加就是在“数据节点”中的真实情况,随着“编辑日志”越来越大,“名字节点”的负担也会越来越重。所以“第二名字节点”就有他的作用了。第二名字节点会定期和“名字节点”进行通信,然后将“命名空间镜像”和“编辑日志”进行合并,最后传送回“名字节点”。此外第二节点也是“名字节点”的一个很好的备份。

客户端就是用户使用的程序,他会首先和名字节点进行通信,然后按照需要,依照名字节点的指引去访问对应的数据节点。所以说和文件存取相关的通信是直接和数据节点通信的,这在有大量吞吐量的分布式文件系统说,客户端向数据节点直接拿数据,这种方式可以分担集群的负载,而不是将所有的负载施加于“名字节点”之上。

验证

HDFS也是有验证环节的,在数据节点上一个数据块会待着一个与数据块同名、后缀名为meta的验证文件。HDFS使用的是CRC的验证方式。它每512个字节进行一次验证。512字节在验证和网络传输是一个很重要的东西,数据块的传输是以512字节为单位的,而验证也是512字节为一个节点进行一次验证。

你可能感兴趣的:(hadoop)