Hadoop的设计架构模式,集中管理
Hadoop 几个主要产品的架构设计,它们都有相似性,都是一主多从的架构方案。HDFS,一个 NameNode,多个 DataNode;MapReduce,一个 JobTracker,多个 TaskTracker;Yarn,一个 ResourceManager,多个 NodeManager。事实上,很多大数据产品都是这样的架构方案:Storm,一个 Nimbus,多个 Supervisor;Spark,一个Master,多个Slave。因为N对N的通信是影响高性能一个重要的原因,N是服务器的数量,因此不如直接采取集中式的管理,一个服务器专门用来监管其他数据服务器。
Hadoop的设计架构模式,分布式存储
数据的存储容量,数据的读写速度,数据的可靠性。
在大数据出现之前,这些问题都是通过RAID(Redundant Arrays of Independent Disks)技术解决的。在 RAID 之前,要使用大容量、高可用、高速访问的存储系统需要专门的存储设备,这类设备价格要比 RAID 的几块普通磁盘贵几十倍。常见的RAID设计有如下几种:
首先假设有N块磁盘,我们按照需要解决的那三个问题,对不同的RAID设计进行评估,主要是从N块磁盘的利用率,读写的速度,数据的可靠性。
但其实在实际中使用的并不是磁盘利用率最高的RAID5或者的RAID6,而是RAID10,在后面会有介绍。在这里,这几种设计都属于垂直方向,N是不可能很大的,最多可能也就8块左右。通过这种设计不管怎样最后的存储空间在目前来说按照摩尔定律都不可能达到PB级别的,但实际得到的数据却很容易超过PB,因此只能寻求水平方向上的结构。
常青树Hadoop里的分布式文件系统HDFS,和 RAID 在多个磁盘上进行文件存储及并行读写的思路一样,HDFS 是在一个大规模分布式服务器集群上,对数据分片后进行并行读写及冗余存储。因为 HDFS 可以部署在一个比较大的服务器集群上,集群中所有服务器的磁盘都可供 HDFS 使用,所以整个 HDFS 的存储空间可以达到 PB 级容量。
NameNode为主控服务器,负责管理;DataNode为数据服务器,负责存储数据以及计算
HDFS 为了保证数据的高可用,会将一个数据块复制为多份(缺省情况为 3 份),并将多份相同的数据块存储在不同的服务器上,甚至不同的机架上。这样当有磁盘损坏,或者某个 DataNode 服务器宕机,甚至某个交换机宕机,导致其存储的数据块不能访问的时候,客户端会查找其备份的数据块进行访问。在这里其实就可以对之前的问题进行回答了,为什么不用RAID5和RAID6,这种设计是针对纵向的且N的大小很小只能通过这种当时提高存储空间以及访问速度;但是在分布式的文件系统下N很大,所以即使是N/2也比之前的存储空间大。还有一个就是如果遇到磁盘损坏,需要进行文件恢复,而RAID5或者RAID6的恢复时间也会影响用户的体验,而这个问题只需要多加几块便宜的磁盘进行存储备份就可以解决了。
HDFS的可靠性还主要体现在以下几个设计,
- DataNode存储错乱
针对的是DataNode,受磁盘老化长时间的环境变化影响,存储的数据可能出现错乱。解决的办法是,对于存储在 DataNode 上的数据块,计算并存储校验和(CheckSum)。在读取数据的时候,重新计算读取出来的数据的校验和,如果校验不正确就抛出异常,应用程序捕获异常后就到其他 DataNode 上读取备份数据。
2.DataNode宕机或者损坏
DataNode 会通过心跳和 NameNode 保持通信,如果 DataNode 超时未发送心跳,NameNode 就会认为这个 DataNode 已经宕机失效或者发生磁盘损坏,立即查找这个 DataNode 上存储的数据块有哪些,以及这些数据块还存储在哪些服务器上,随后通知这些服务器再复制一份数据块到其他服务器上,保证 HDFS 存储的数据块备份数符合用户设置的数目,即使再出现服务器宕机或者磁盘损坏,也不会丢失数据。
3.NameNode故障容错
NameNode 是整个 HDFS 的核心,记录着 HDFS 文件分配表信息,所有的文件路径和数据块存储信息都保存在 NameNode,如果 NameNode 故障,整个 HDFS 系统集群都无法使用;如果 NameNode 上记录的数据丢失,整个集群所有 DataNode 存储的数据也就没用了。所以,NameNode 高可用容错能力非常重要。NameNode 采用主从热备的方式提供高可用服务,请看下图。
两台服务器通过 ZooKeeper 选举,主要是通过争夺 znode 锁资源,决定谁是主服务器。而 DataNode 则会向两个 NameNode 同时发送心跳数据,但只有主 NameNode 才能向 DataNode 返回控制信息。
这些解决方式大概可以概括为冗余备份,失效转移。最后一种方式是限流,由于计算资源有限,可能无法处理如此大量的请求,进而导致资源耗尽,系统崩溃。这种情况下,可以拒绝部分请求,即进行限流。比如电商的“双十一”促销,为了保障促销活动期间应用的核心功能能够正常运行,比如下单功能,可以对系统进行降级处理,关闭部分非重要功能,比如商品评价功能或者退货功能。
Hadoop的设计架构模式,分布式计算
数据是庞大的,而程序要比数据小得多,将数据输入给程序是不划算的,那么就反其道而行之,将程序分发到数据所在的地方进行计算,也就是所谓的移动计算比移动数据更划算。
通过HDFS,文件已经分块存储好了,大数据引擎根据集群里不同服务器的计算能力,在每台服务器上启动若干分布式任务执行进程,这些进程会等待给它们分配执行任务。使用MapReduce编程模型,应用程序编写好以后,将其打包,MapReduce 是在JVM 环境中运行,所以打包出来的是一个 Java 的 JAR 包。任务执行进程收到分配的任务后,检查自己是否有任务对应的程序包,如果没有就去下载程序包,下载以后通过反射的方式加载程序。走到这里,最重要的一步,也就是移动计算就完成了。
MapReduce编程模型只包含 Map 和 Reduce 两个过程,map 的主要输入是一对值,经过 map 计算后输出一对值;然后将相同 Key 合并,形成;再将这个输入 reduce,经过计算输出零个或多个对。下面给出WordCount的例子:
#单机版本
# 文本前期处理
strl_ist = str.replace('n', '').lower().split(' ')
count_dict = {}
# 如果字典里有该单词则加1,否则添加入字典
for str in strl_ist:
if str in count_dict.keys():
count_dict[str] = count_dict[str] + 1
else:
count_dict[str] = 1
MapReduce使用的是JVM环境因此:
public class WordCount {
public static class TokenizerMapper
extends Mapper<Object, Text, Text, IntWritable>{
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(Object key, Text value, Context context
) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
public static class IntSumReducer
extends Reducer<Text,IntWritable,Text,IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<IntWritable> values,
Context context
) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
}
map 函数的计算过程是,将这行文本中的单词提取出来,针对每个单词输出一个这样的<word,1>对。MapReduce 计算框架会将这些收集起来,将相同的 word 放在一起,形成<word,<1,1,1...>> 这样的数据,然后将其输入给 reduce 函数。reduce 函数的计算过程是,将这个集合里的 1 求和,再将单词(word)和这个和(sum)组成一个<word,sum>,也就是输出。每一个输出就是一个单词和它的词频统计总和。
在两个数块下的Map Reduce过程:
完整的计算过程如下:
1. 应用进程 JobClient 将用户作业 JAR 包存储在 HDFS 中,将来这些 JAR 包会分发给 Hadoop 集群中的服务器执行 MapReduce 计算。
2. 应用程序提交 job 作业给 JobTracker。
3.JobTracker 根据作业调度策略创建 JobInProcess 树,每个作业都会有一个自己的 JobInProcess 树。
4.JobInProcess 根据输入数据分片数目(通常情况就是数据块的数目)和设置的 Reduce 数目创建相应数量的 TaskInProcess。
5.TaskTracker 进程和 JobTracker 进程进行定时通信。
6. 如果 TaskTracker 有空闲的计算资源(有空闲 CPU 核心),JobTracker 就会给它分配任务。分配任务的时候会根据 TaskTracker 的服务器名字匹配在同一台机器上的数据块计算任务给它,使启动的计算任务正好处理本机上的数据,以实现我们一开始就提到的“移动计算比移动数据更划算”。
7.TaskTracker 收到任务后根据任务类型(是 Map 还是 Reduce)和任务参数(作业 JAR 包路径、输入数据文件路径、要处理的数据在文件中的起始位置和偏移量、数据块多个备份的 DataNode 主机名等),启动相应的 Map 或者 Reduce 进程。
8.Map 或者 Reduce 进程启动后,检查本地是否有要执行任务的 JAR 包文件,如果没有,就去 HDFS 上下载,然后加载 Map 或者 Reduce 代码开始执行。
9. 如果是 Map 进程,从 HDFS 读取数据(通常要读取的数据块正好存储在本机);如果是 Reduce 进程,将结果数据写出到 HDFS。
Hadoop的设计架构模式,分布式集群的资源调度框架
在 MapReduce 应用程序的启动过程中,最重要的就是要把 MapReduce 程序分发到大数据集群的服务器上,在 Hadoop 1 中,这个过程主要是通过 TaskTracker 和 JobTracker 通信来完成。这种架构方案的主要缺点是,服务器集群资源调度管理和 MapReduce 执行过程耦合在一起,如果想在当前集群中运行其他计算任务,比如 Spark 或者 Storm,就无法统一使用集群中的资源了。针对多种计算框架,为了都能使用到数据,因此需要将资源调度和计算框架进行分离,也就产生了yarn(Yet Another Resource Negotiator)。
yarn的架构
从图上看,Yarn 包括两个部分:一个是资源管理器(Resource Manager),一个是节点管理器(Node Manager)。这也是 Yarn 的两种主要进程:ResourceManager 进程负责整个集群的资源调度管理,通常部署在独立的服务器上;NodeManager 进程负责具体服务器上的资源和任务管理,在集群的每一台计算服务器上都会启动,基本上跟 HDFS 的DataNode 进程一起出现。
具体说来,资源管理器又包括两个主要组件:调度器和应用程序管理器。调度器其实就是一个资源分配算法,根据应用程序(Client)提交的资源申请和当前服务器集群的资源状况进行资源分配。Yarn 内置了几种资源调度算法,包括 Fair Scheduler、Capacity Scheduler 等,你也可以开发自己的资源调度算法供 Yarn 调用。Yarn 进行资源分配的单位是容(Container),每个容器包含了一定量的内存、CPU 等计算资源,默认配置下,每个容器包含一个 CPU 核心。容器由 NodeManager 进程启动和管理,NodeManger 进程会监控本节点上容器的运行状况并向 ResourceManger 进程汇报。
应用程序管理器负责应用程序的提交、监控应用程序运行状态等。应用程序启动后需要在集群中运行一个 ApplicationMaster,ApplicationMaster 也需要运行在容器里面。每个应用程序启动后都会先启动自己的 ApplicationMaster,由 ApplicationMaster 根据应用程序的资源需求进一步向 ResourceManager 进程申请容器资源,得到容器以后就会分发自己的应用程序代码到容器上启动,进而开始分布式计算。