大数据产业指以数据生产、采集、存储、加工、分析、服务为主的相关经济活动。比如:数据资源建设;大数据软硬件产品的开发;销售和租赁活动;相关信息技术服务
物联网、云计算和5G是大数据的底层架构,大数据依赖云计算来处理大数据,人工智能是大数据的场景应用。
大数据是物联网、Web和传统信息系统发展的必然结果,大数据在技术体系上与云计算重点都是分布式存储和分布式计算,云计算注重服务,大数据则注重数据的价值化操作。
人工智能(Artificial Intelligence,AI)是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。人工智能其实就是大数据、云计算的一个应用场景。人工智能则包含了机器学习,从被动到主动,从模式化实行指令,到自主判断根据情况实行不同的指令。
第一代Hadoop(即Hadoop 1.0)的核心由分布式文件系统HDFS和分布式计算框架MapReduce组成,为了克服Hadoop1.0中HDFS和MapReduce的架构设计和应用性能方面的各种问题,提出了第二代Hadoop(即Hadoop 2.0),Hadoop 2.0的核心包括分布式文件系统HDFS、统一资源管理和调度框架YARN和分布式计算框架MapReduce。
HDFS是谷歌文件系统GFS的开源实现,是面向普通硬件环境的分布式文件系统,适用于大数据场景的数据存储,提供了高可靠、高扩展、高吞吐率的数据存储服务。
MapReduce是谷歌MapReduce的开源实现,是一种简化的分布式应用程序开发的编程模型,允许开发人员在不了解分布式系统底层细节和缺少并行应用开发经验的情况下,能快速轻松地编写出分布式并行程序,将其运行于计算机集群上,完成对大规模数据集的存储和计算。
YARN是将MapReduce 1.0中JobTracker的资源管理功能单独剥离出来而形成,它是一个纯粹的资源管理和调度框架,并解决了Hadoop 1.0中只能运行MapReduce框架的限制,可在YARN上运行各种不同类型计算框架包括MapReduce、Spark、Storm等。
Hadoop这个名字不是单词缩写,Hadoop之父道格•卡丁(Doug Cutting)曾这样解释Hadoop名字的由来:“这个名字是我的孩子给一个棕黄色的大象玩具的取的名字。我的命名标准就是简短,容易发音和拼写,并且不会被用于别处。小孩子恰恰是这方面的高手。
Hadoop起源于开源的网络搜索引擎Apache Nutch,它本身是Lucence项目的一部分。Nutch项目开始于2002年,一个可以代替当时主流搜索产品的开源搜索引擎。但后来,它的创造者Doug Cutting和Mike Cafarella遇到了棘手难题,该搜索引擎框架只能支持几亿数据的抓取、索引和搜索,无法扩展到拥有数十亿网页的网络。
2003年,Google发表了论文“The Google File System”,可以解决大规模数据存储的问题。于是在2004年,Nutch项目借鉴谷歌GFS使用Java语言开发了自己的分布式文件系统,即Nutch分布式文件系统NDFS,也就是HDFS的前身。
2004年,Google又发表了一篇具有深远影响的论文“MapReduce: Simplifed Data Processing on Large Clusters”,阐述了MapReduce分布式编程思想。Nutch开发者们发现Google MapReduce所解决的大规模搜索引擎数据处理问题,正是他们当时面临并亟待解决的难题。于是,Nutch开发者们模仿Google MapReduce框架设计思路,使用Java语言设计并2005年初开源实现了MapReduce。
2006年2月,Nutch中的NDFS和MapReduce独立出来,形成Lucence的子项目,并命名为Hadoop,同时Doug Cutting进入雅虎,雅虎为此组织了专门的团队和资源,致力于将Hadoop发展成为能够处理海量Web数据的分布式系统。
2007年,纽约时报把存档报纸扫描版的4TB文件在100台亚马逊虚拟机服务器上使用Hadoop转换为PDF格式,整个过程所花时间不到24小时,这一事件更加深了人们对Hadoop的印象。
2008年,Google工程师Christophe Bisciglia发现把当时的Hadoop放到任意一个集群中去运行是一件很困难的事,所以与好友Facebook的Jeff Hammerbacher、雅虎的Amr Awadallah、Oracle的Mike Olson成立了专门商业化Hadoop的公司Cloudera。
2008年1月,Hadoop成为Apache顶级项目。
2008年4月,Hadoop打破世界纪录,成为最快的TB级数据排序系统。在一个910节点的集群上,Hadoop在209秒内完成了对1TB数据的排序,击败前一年的297秒冠军。
2009年4月,Hadoop对1TB数据进行排序只花了62秒。
2011年,雅虎将Hadoop团队独立出来,由雅虎主导Hadoop开发的副总裁Eric Bladeschweiler带领二十几个核心成员成立子公司Hortonworks,专门提供Hadoop相关服务。成立3年就上市。同年12月,发布1.0.0版本,标志着Hadoop已经初具生产规模。
2012年,Hortonworks推出YARN框架第一版本,从此Hadoop的研究进入一个新层面。
2013年10月,发布2.2.0版本,Hadoop正式进入2.x时代。
2014年,Hadoop 2.X更新速度非常快,先后发布2.3.0、2.4.0、2.5.0和2.6.0,极大完善了YARN框架和整个集群的功能,很多Hadoop研发公司如Cloudera、Hortonworks都与其他企业合作共同开发Hadoop新功能。
2015年4月,发布2.7.0版本。
2016年,Hadoop及其生态圈组件Spark等在各行各业落地并得到广泛应用,YARN持续发展以支持更多计算框架。同年9月,发布Hadoop 3.0.0-alpha1版本,预示着Hadoop 3.x时代的到来。
2018年11月,发布Hadoop 2.9.2,同年10月发布Ozone第一版0.2.1-alpha,Ozone是Hadoop的子项目,该项目提供了分布式对象存储,建立在Hadoop分布式数据存储HDDS上。
2019年1月,发布Hadoop 3.2.0,发布Submarine第一版0.1.0,Submarine是Hadoop的子项目,该项目旨在资源管理平台如YARN上运行深度学习应用程序如TensorFlow、PyTorch等。
高可靠性:采用冗余数据存储方式,即使一个副本发生故障,其它副本也可以保证正常对外提供服务。
高扩展性:Hadoop设计目标是可以高效稳定地运行在廉价的计算机集群上,可以方便添加机器节点,扩展到数以千计的计算机节点上
高效性:作为分布式计算平台,能够高效地处理PB级数据。
高容错性:采用冗余数据存储方式,自动保存数据的多个副本,当读取该文档出错或者某一台机器宕机,系统会调用其它节点上的备份文件,保证程序顺利运行。
低成本:Hadoop是开源的,即不需要支付任何费用即可下载安装使用。另外,Hadoop集群可以部署在普通机器上,而不需要部署在价格昂贵的小型机上,能够大大减少公司的运营成本
支持多种平台:Hadoop支持Windows和GNU/Linux两类运行平台,Hadoop是基于Java语言开发的,因此其最佳运行环境无疑是Linux,Linux的发行版本众多,常见的有CentOS、Ubuntu、Red Hat、Debian、Fedora、SUSE、openSUSE等。
支持多种编程语言:Hadoop上的应用程序可以使用Java、C++编写。
Hadoop的发行版本有两类,一类是由社区维护的免费开源的Apache Hadoop,另一类是一些商业公司如Cloudera、Hortonworks、MapR等推出的Hadoop商业版。
Hadoop 2.0主要由三部分构成:分布式文件系统HDFS、统一资源管理和调度框架YARN、分布式计算框架MapReduce;但广义上来讲,Hadoop是指以Hadoop为基础的生态系统,是一个庞大体系,Hadoop仅是其中最基础、最重要的部分,生态系统中每个组件只负责解决某一特定问题。
Hadoop集群采用主从架构(Master/Slave),NameNode与ResourceManager为Master,DataNode与NodeManager为Slaves,守护进程NameNode和DataNode负责完成HDFS的工作,守护进程ResourceManager和NodeManager则负责完成YARN的工作
理解部署Hadoop集群所需系统环境、Hadoop运行模式,熟练掌握在Linux下部署全分布模式Hadoop过程:
规划集群、
准备机器及软件环境(配置静态IP、修改主机名、编辑域名映射、安装和配置Java、安装和配置SSH免密登录)、安装和配置Hadoop集群( hadoop-env.sh 、 yarn-env.sh、 mapred-env.sh、 core-site.xml、 hdfs-site.xml、 yarn-site.xml、 mapred-site.xml、slaves )、
关闭防火墙、格式化文件系统、启动和验证Hadoop、关闭Hadoop。
HDFS(Hadoop Distributed File System)是Hadoop分布式文件系统,是Hadoop三大核心之一,是针对谷歌文件系统GFS(Google File System)的开源实现(The Google File System, 2003)。HDFS是一个具有高容错性的文件系统,适合部署在廉价的机器上,HDFS能提供高吞吐量的数据访问,非常适合大规模数据集上的应用。大数据处理框架如MapReduce、Spark等要处理的数据源大部分都存储在HDFS上,Hive、HBase等框架的数据通常也存储在HDFS上。简而言之,HDFS为大数据的存储提供了保障。经过多年的发展,HDFS自身已经十分成熟和稳定,且用户群愈加广泛,HDFS逐渐成为分布式存储的事实标准。
HDFS文件系统的基本特征包括以下几个方面
HDFS采用Master/Slave架构模型,一个HDFS集群包括一个NameNode和多个DataNode。名称节点NameNode为主节点,数据节点DataNode为从节点,文件被划分为一系列的数据块(Block)存储在从节点DataNode上,NameNode是中心服务器,不存储数据,负责管理文件系统的命名空间(Namespace)以及客户端对文件的访问。
NameNode运行在日常硬件上,通常只有一个,是整个文件系统的管理节点。它维护着整个文件系统的文件目录树,包括文件/目录的元数据和每个文件对应的数据块列表,它负责接收用户的操作请求。作为HDFS主服务节点的核心,它主要完成下面任务
HDFS命名空间(NameSpace)支持传统的层次型文件组织结构,与大多数现有文件系统类似,用户可以创建、删除、移动或重命名文件。
在HDFS中,NameNode负责管理分布式文件系统的命名空间,保存了两个核心数据结构:FsImage和EditLog。其中,FsImage用于维护文件系统树以及文件树中所有文件和目录的元数据;操作日志文件EditLog记录了所有针对文件的创建、删除、重命名等操作。NameNode记录了每个文件中各个块所在的数据节点的位置信息,但是并不持久化存储这些信息,而是在系统每次启动时扫描所有数据节点重构得到这些信息。
DataNode也运行在日常硬件上,通常有多个,它为HDFS提供真实文件数据的**存储服务。**HDFS数据存储在DataNode上,数据块的创建、复制和删除都在DataNode上执行。DataNode将HDFS数据以文件的形式存储在本地的文件系统中,但并不知道有关HDFS文件的信息。DataNode把每个HDFS数据块存储在本地文件系统的一个单独的文件中,并不在同一个目录创建所有的文件,实际上,它用试探的方法来确定每个目录的最佳文件数目,并且在适当的时候创建子目录。在同一个目录中创建所有的本地文件并不是最优的选择,这是因为本地文件系统可能无法高效地在单个目录中支持大量的文件。当一个DataNode启动时,它会扫描本地文件系统,产生一个这些本地文件对应的所有HDFS数据块的列表,然后作为报告发送到NameNode,这个报告就是块状态报告。
严格地说,客户端并不算是HDFS的一部分。客户端可以支持打开、读取、写入等常见操作,并且提供了类似Shell的命令行方式来访问HDFS中的数据,也提供了API作为应用程序访问文件系统的客户端编程接口
HDFS中的数据以文件块Block的形式存储,Block是最基本的存储单位,每次读写的最小单元是一个Block。对于文件内容而言,一个文件的长度大小是N,那么从文件的0偏移开始,按照固定的大小,顺序对文件进行划分并编号,划分好的每一个块称一个Block。
Hadoop 2.0中默认Block大小是128MB。不同于普通文件系统,HDFS中如果一个文件小于一个数据块的大小,并不占用整个数据块存储空间。
Block的大小可以根据实际需求进行配置,可以通过HDFS配置文件hdfs-site.xml中的参数dfs.blocksize来定义块大小,但要注意,数字必须是2K,文件的大小可以不是Block大小的整数倍,这时最后一个块可能存在剩余。
为什么HDFS数据块设置的这么大呢?
原因是和普通的本地磁盘文件系统不同,HDFS存储的是大数据文件,通常会有TB甚至PB的数据文件需要管理,所以数据的基本单元必须足够大才能提高管理效率。而如果还使用像Linux本地文件系统EXT3的4KB单元来管理数据,则会非常低效,同时会浪费大量的元数据空间。
ABC是机架,123是文件
A:112
B:223
C:331
客户端读取HDFS上的文件时,需要调用HDFS Java API一些类的方法,从编程角度来看,主要经过以下几个步骤。
(1)客户端生成一个FileSystem实例(DistributedFileSystem对象),并使用此实例的open()方法打开HDFS上的一个文件。
(2)DistributedFileSystem通过RPC调用向NameNode发出请求,得到文件的位置信息,即数据块编号和所在DataNode地址,对于每一个数据块,元数据节点返回保存数据块的数据节点的地址,通常按照DataNode地址与客户端的距离从近到远排序。
(3)FileSystem实例获得地址信息后,生成一个FSDataInputStream对象实例返回给客户端,此实例封装了一个DFSInputStream对象,负责存储数据块信息和DataNode地址信息,并负责后续的文件内容读取工作。
(4)客户端向FSDataInputStream发出读取数据的read()调用。
(5)FSDataInputStream收到read()调用请求后,FSDataInputStream封装的DFSInputStream选择与第一个数据块最近的DataNode,并读取相应的数据信息返回给客户端,在数据块读取完成后,DFSInputStream负责关闭到相应DataNode的链接。
(6)DFSInputStream依次选择后续数据块的最近DataNode节点,并读取数据返回给客户端,直到最后一个数据块读取完毕。DFSInputStream从DataNode读取数据时,可能会碰上某个DataNode失效的情况,则会自动选择下一个包含此数据块的最近的DataNode去读取。
(7)客户端读取完所有数据块,然后调用FSDataInputStream的close()方法关闭文件。
HDFS的设计遵循**“一次写入,多次读取”**的原则,所有数据只能添加不能更新。数据会被划分为等尺寸的块写入不同的DataNode中。每个块通常保存指定数量的副本(默认3个)。HDFS数据写入基本过程为:客户端向NameNode发送文件写请求,NameNode给客户分配写权限,并随机分配块的写入地址——DataNode的IP,兼顾副本数量和块Rack自适应算法,例如副本因子是3,则每个块会分配到三个不同的DataNode,为了提高传输效率,客户端只会向其中一个DataNode复制一个副本,另外两个副本则由DataNode传输到相邻DataNode。
数据写入过程
从编程角度来说,将数据写入HDFS主要经过以下几个步骤。
(1)创建和初始化FileSystem,客户端调用create()来创建文件
(2)FileSystem用RPC调用元数据节点,在文件系统的命名空间中创建一个新的文件,元数据节点首先确定文件原来不存在,并且客户端有创建文件的权限,然后创建新文件。
(3)FileSystem返回DFSOutputStream,客户端开始写入数据。
(4)DFSOutputStream将数据分成块,写入data queue。data queue由Data Streamer读取,并通知名称节点分配数据节点,用来存储数据块(每块默认复制3块)。分配的数据节点放在一个数据流管道(pipeline)里。Data Streamer将数据块写入pipeline中的第一个数据节点,一个数据节点将数据块发送给第二个数据节点,第二个数据节点将数据发送给第三个数据节点。
(5)DFSOutputStream为发出去的数据块保存了ack queue,等待pipeline中的数据节点告知数据已经写入成功。
(6)当客户端结束写入数据,则调用stream的close函数。此操作将所有的数据块写入pipeline中的数据节点,并等待ack queue返回成功。
(7)通知名称节点写入完毕。
为了方便用户使用HDFS,HDFS提供三种类型接口:
1.HDFS Web UI
2.HDFS Shell
3.HDFS API
在服务器系统中,发生硬件故障或者软件错误是难以避免的,所以需要对重要数据进行备份。元数据是HDFS的核心数据,通过它对整个HDFS进行管理,FsImage和EditLog是最重要的元数据文件。所以,NameNode通常会配置支持维护多个FsImage和EditLog的副本。任何对FsImage或EditLog的修改,都将同步到它们的副本上。这种多副本的同步操作可能会降低NameNode每秒处理的名字空间事务数量,然而这个代价是可以接受的,因为即使HDFS的应用是数据密集的,它们不一定是元数据密集的。当NameNode重启时,它会选取最近的完整的FsImage和EditLog来使用。
当NameNode运行很长时间后,EditLog文件会变得很大。在这种情况下就会出现以下问题:
为了克服这些问题,我们需要一个易于管理的机制来帮助减小EditLog文件的大小和得到一个最新的FsImage文件,这样也会减小NameNode的计算压力。而Secondary NameNode就是为了帮助解决上述问题提出的,它的主要职责是合并NameNode的EditLog到FsImage文件中,即对元数据进行定期更新和备份
Hadoop 2.0以后版本新提供了一个真正意义上的备用节点,即Backup Node。Backup Node在内存中维护了一份从NameNode同步过来的FsImage,同时它还从NameNode接收EditLog文件的日志流,并把它们持久化到硬盘。Backup Node在内存中维护与NameNode一样的元数据。
Backup Node的启动命令是“hdfs namenode -backup”。在配置文件hdfs-site.xml中进行设置,主要包括两个参数:dfs.backup.address、dfs.backup.http.address。
HDFS NameNode HA体系架构
在HDFS NameNode的高可用体系架构中,NameNode的主备切换主要由故障恢复控制器(ZKFailoverController)、健康监视器(HealthMonitor)和主备选举器(ActiveStandbyElector)这3个组件来协同实现。
HDFS Federation概述
Hadoop集群的元数据信息是存放在NameNode的内存中的,当集群扩大到一定规模后,NameNode内存中存放的元数据信息可能会非常大。由于HDFS所有操作都会和NameNode进行交互,当集群很大时,NameNode的内存限制将成为制约集群横向扩展的瓶颈。在Hadoop 2.0诞生之前,HDFS中只能有一个命名空间,对于HFDS中的文件没有办法完成隔离。正因为如此,在Hadoop 2.0中引入了HDFS Federation联邦机制,可以解决如下问题。
(1)集群扩展性。
(2)性能更高效。
(3)良好隔离性。
HDFS的数据存储采用两层分层结构,分别为命名空间(Namespace)和块存储服务(Block Storage Service)。其中命名空间由目录、文件、块组成,支持创建、删除、修改、列举命名空间等相关操作。块存储服务包括两部分,分别是块管理Block Management和存储Storage,块管理在NameNode中完成,通过控制注册和阶段性心跳来保证DataNode正常运行,可以处理块的信息报告和维护块的位置信息,可以创建、修改、删除、查询块,还可以管理副本和副本位置;存储在数据节点DataNode上,提供对块的读写操作。
HDFS Snapshots概述
HDFS快照是文件系统在某一时刻的只读镜像,可以是一个完整的文件系统,也可以是某个目录的镜像。快照分两种:一种是建立文件系统的索引,每次更新文件不会真正的改变文件,而是新开辟一个空间用来保存更改的文件;另一种是拷贝所有的文件系统。HDFS快照属于前者。
HDFS快照常用于以下场景:
(1)防止用户的错误操作。
(2)备份。
(3)试验/测试。
(4)灾难恢复。
通过HDFS快照机制可以定时或按固定时间间隔创建文件快照,并删除过期的文件快照,减少业务误操作造成的数据损失。快照的操作远低于外部备份的开销,可以作为备份HDFS最常用的方式。
键值对:
Step1 将数据抽象为键值对形式,接着map函数会以键值对作为输入,经过map函数的处理,产生一系列新的键值对作为中间结果输出到本地。
Step2 MapReduce框架自动将这些中间结果数据按照键做聚合处理,并将键相同的数据分发给reduce函数处理。
Step3 reduce函数以键和对应的值的集合作为输入,经过reduce函数处理后,产生另外一系列键值对作为最终输出。
{key1,value1}→{key2,List}→{key3,value3}
Hadoop提供了一个MapReduce入门案例“WordCount”,用于统计输入文件中每个单词出现的次数。该案例源码保存在$HADOOP_HOME/share/hadoop/mapreduce/sources/hadoop-mapreduce-examples-2.9.2.jar的WordCount.java中,其源码共分为TokenizerMapper类、IntSumReducer类和main()函数三个部分。
从类名可知,该类是Map阶段的实现。并且能够发现,在MapReduce中Map阶段的业务代码需要继承自org.apache.hadoop.mapreduce.Mapper类。Mapper类的四个泛型分别表示输入数据的key类型、输入数据的value类型、输出数据的key类型、输出数据的value类型。
以本次“WordCount”为例,每次Map阶段需要处理的数据是文件中的一行数据,而默认情况下这一行数据的偏移量(该行起始位置距离文件初始位置的位移)就是输入数据的key类型(一般而言,偏移量是一个长整型,也可以写成本例中使用的Object类型);输入数据的value类型就是这行数据本身,因此是Text类型(即hadoop中定义的字符串类型);输出数据的key类型是每个单词本身,因此也是Text类型;而输出数据的value类型,就表示该单词出现了一次,因此就是数字1,可以表示为IntWritable类型(即Hadoop中定义的整数类型)。
使用如下命令向Hadoop集群提交并运行WordCount。
hadoop jar /usr/local/hadoop-2.9.2/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.9.2.jar wordcount /InputDataTest /OutputDataTest3
上述命令中,/InputDataTest表示输入目录,/OutputDataTest3表示输出目录。执行该命令前,假设HDFS的目录/InputDataTest下已存在待分析词频的3个文件,而输出目录/OutputDataTest3不存在,在执行过程中会自动创建
MapReduce使用了Text定义字符串,使用了IntWritable定义整型变量,而没有使用Java内置的String和int类型。这样做主要是有两个方面的原因:
Map阶段的产物会经过一个名为Shuffle的阶段。
在Shuffle阶段中进行排序和分区操作,Shuffle阶段的产物是以“key=单词,value=出现次数的数组”形式输出。
在Shuffle阶段,会对数据进行以下操作。
MapReduce Web接口面向管理员。可以在页面上看到已经完成的所有MR-App执行过程中的统计信息,该页面只支持读,不支持写。在MapReduce程序运行时,我们除了观察控制台打印的日志以外,还可以通过Web界面查看具体的运行情况。MapReduce Web UI的默认地址为http://JobHistoryServerIP:19888,可以查看MapReduce的历史运行情况。
MapReduce Shell接口面向MapReduce程序员。程序员通过Shell接口能够向YARN集群提交MR-App,查看正在运行的MR-App,甚至可以终止正在运行的MR-App。
MapReduce Shell命令统一入口为:mapred,语法格式如下:
mapred [–config confdir] [–loglevel loglevel] COMMAND
若$HADOOP_HOME/bin未加入到系统环境变量PATH中,则需要切换到Hadoop安装目录下,输入“bin/mapred”
其中,MRApplBusinessLogic编写步骤如下:
(1)确定
(2)定制输入格式。
(3)Mapper阶段。
(4)Reducer阶段。
(5)定制输出格式。
编写类后,main方法里,按下述过程依次指向各类即可:
(1)实例化配置文件类。
(2)实例化Job类。
(3)指向InputFormat类。
(4)指向Mapper类。
(5)指向Partitioner类。
(6)指向Reducer类。
(7)指向OutputFormat类。
(8)提交任务。
针对MapReduce 1.0在可用性、可扩展性、资源利用率、框架支持等方面的不足,对MapReduce 1.0的架构进行了重新设计,提出了全新的资源管理和调度框架YARN。YARN是Hadoop 2.0的资源管理和调度框架,是一个通用的资源管理系统,在其上可以部署各种计算框架,它可为上层应用提供统一的资源管理和调度,它的引入为集群高可用性、可扩展性、资源利用率和数据共享等方面带来了很大好处。
在Hadoop 1.0中,MapReduce采用Master/Slave架构,有两类守护进程控制作业的执行过程,即一个JobTracker和多个TaskTracker。JobTracker负责资源管理和作业调度;TaskTracker定期向JobTracker汇报本节点的健康状况、资源使用情况、任务执行情况以及接受来自JobTracker的命令并执行。随着集群规模负载的增加,MapReduce JobTracker在内存消耗、扩展性、可靠性、性能等方面暴露出各种缺点,具体包括以下几个方面。
(1)单点故障问题。JobTracker只有一个,它负责所有MapReduce作业的调度,若这个唯一的JobTracker出现故障就会导致整个集群不可用。
(2)可扩展性瓶颈。业内普遍总结出当节点数达到4000,任务数达到40000时,MapReduce 1.0会遇到可扩展性瓶颈,这是由于JobTracker“大包大揽”任务过重,既要负责作业的调度和失败恢复,又要负责资源的管理分配。当执行过多的任务时,需要巨大的内存开销,这也潜在增加了JobTracker失败的风险。
(3)资源划分不合理。资源(CPU、内存)被强制等量划分为多个Slot,每个TaskTracker都配置有若干固定长度的Slot,这些Slot是静态分配的,在配置的时候就被划分为Map Slot和Reduce Slot,且Map Slot仅能用于运行一个Map任务,Reduce Slot仅能用于运行一个Reduce任务,彼此之间不能使用分配给对方的Slot。这意味着,当集群中只存在单一Map任务或Reduce任务时,会造成资源的极大浪费。
(4)仅支持MapReduce一个计算框架。MapReduce是一个基于Map和Reduce、适合批处理、基于磁盘的计算框架,不能解决所有场景问题,而一个集群仅支持一个计算框架,不支持其他类型的计算框架如Spark、Storm等,造成集群多、管理复杂,且各个集群不能共享资源,造成集群间资源浪费。
为了解决MapReduce 1.0存在的问题,Hadoop 2.0以后版本对其核心子项目MapReduce的体系架构进行了重新设计,生成了MRv2和YARN。
Apache Hadoop YARN(Yet Another Resource Negotiator,另一种资源协调者)是Hadoop 2.0的资源管理和调度框架,是一个通用资源管理系统,可为上层应用提供统一的资源管理和调度,它的引入为集群在利用率、资源统一管理和数据共享等方面带来了很大好处
YARN设计的基本思路就是“放权”,即不让JobTracker承担过多功能,把MapReduce 1.0中JobTracker三大功能资源管理、任务调度和任务监控进行拆分,分别交给不同的新组件承担。重新设计后得到的YARN包括ResourceManager、ApplicationMaster和NodeManager,其中,ResourceManager负责资源管理,ApplicationMaster负责任务调度和任务监控,NodeManager负责承担原TaskTracker功能,且原资源被划分的Slot重新设计为容器Container,NodeManager能够启动和监控容器Container。另外,原JobTracker也负责存储已完成作业的作业历史,此功能也可以运行一个作业历史服务器作为一个独立守护进程来取代JobTracker,YARN中与之等价的角色是时间轴服务器Timeline Server。
MapReduce 1.0 | YARN |
---|---|
JobTracker | ResourceManager、ApplicationMaster、Timeline Server |
TaskTracker | NodeManager |
Slot | Container |
总之,Hadoop 1.0中,MapReduce既是一个计算框架,又是一个资源管理和调度框架,到了Hadoop 2.0以后,MapReduce中资源管理和调度功能被单独分割出来形成YARN,YARN是一个纯粹的资源管理调度框架,而被剥离了资源管理调度功能的MapReduce变成了MRv2,MRv2是运行在YARN上的一个纯粹的计算框架。
从MapReduce 1.0发展到YARN,客户端并没有发生变化,其大部分API及接口都保持兼容,因此,原来针对Hadoop 1.0开发的代码不需做大的改动,就可以直接放在Hadoop 2.0平台上运行。
(1)可扩展性(Scalability)
与MapReduce 1.0相比,YARN可以在更大规模的集群上运行。当节点数达到4000、任务数达到40000时,MapReduce 1.0会遇到可扩展性瓶颈,瓶颈来源于JobTracker负载过重,既要负责作业的调度和失败恢复,又要负责资源的管理分配。YARN利用ResourceManager和ApplicationMaster分离的架构优点克服了这个局限性,可以扩展到将近10000个节点和100000个任务。另外,YARN Federation联邦机制进一步增强了集群水平横向扩展性。
(2)可用性(Availability)
当守护进程失败时,通常需要另一个守护进程复制接管工作所需的状态以便其继续提供服务,从而可以获得高可用性(High Available)。但是,由于MapReduce 1.0中JobTracker内存中存在大量快速变化的复杂状态,使得改进JobTracker以使其获得高可用性非常困难。
YARN对MapReduce 1.0的体系架构进行了重新设计,ResourceManager和ApplicationMaster分别承担MapReduce 1.0中JobTracker的功能,那么高可用的服务随之称为一个分而治之的问题:先为ResourceManager提供高可用性,再为YARN应用提供高可用性。YARN的ResourceManager HA特性通过Active/Standby ResourceManager保证了YARN高可用性,ResourceManager Restart特性保证了若ResourceManager发生单点故障,ResourceManager能尽快自动重启。
(3)利用率(Utilization)
MapReduce 1.0使用Slot表示各个节点上的计算资源,Slot分为Map Slot和Reduce Slot两种,且不允许共享。对于一个作业,刚开始运行时,Map Slot资源紧缺而Reduce Slot空闲,当Map Task全部运行完成后,Reduce slot紧缺而Map slot空闲。很明显,这种区分Slot类别的资源管理方案在一定程度上降低了Slot的利用率。同时,这种基于无类别Slot的资源划分方法的划分粒度过大,往往会造成节点资源利用率过高或者过低。
YARN中,一个NodeManager管理一个资源池,而不是指定固定数目的Slot。YARN上运行的MapReduce不会出现MapReduce 1.0中由于集群中只有Map Slot可用而导致Reduce Task必须等待的情况。如果能够获取运行任务的资源,那么应用程序就会正常进行。更进一步,YARN中的资源是精细化管理的,这样一个应用程序能够按需请求资源,而不是请求一个不可分割的、对于特定任务而言可能太大(浪费资源)或太小(可能导致失败)的Slot。
(4)多租户(Multitenancy)
在某种程度上可以说,YARN最大的优点是向MapReduce以外的其他分布式计算框架开放了Hadoop,MapReduce仅是许多YARN应用中的一个,Spark、Tez、Storm等计算框架也都可以运行在YARN上。另外,用户甚至可以在同一个YARN集群上运行不同版本的MapReduce,这使得升级MapReduce更好管理。
YARN的提出并非仅仅为了解决MapReduce 1.0中存在的问题,实际上YARN有着更加伟大的目标,即实现“一个集群多个框架”,也就是说在一个集群上部署一个统一的资源管理调度框架YARN,打造以YARN为核心的生态圈,在YARN之上可以部署其他各种计算框架,满足一个公司各种不同的业务需求,如离线计算框架MapReduce、DAG计算框架Tez、流式计算框架Storm、内存计算框架Spark等,由YARN为这些计算框架提供统一的资源管理调度服务,并且能够根据各种计算框架的负载需求,调整各自占用的资源,实现集群资源共享和资源弹性收缩。
YARN采用主从架构(Master/Slave),其核心组件包括三个:ResourceManager、NodeManager和ApplicationMaster。其中,ResourceManager是主进程,NodeManager是从进程,一个ResourceManager对应多个NodeManager,每个应用程序拥有一个ApplicationMaster。
此外,YARN中引入了一个逻辑概念——容器Container,它将各类资源(如CPU、内存)抽象化,方便从节点NodeManager管理本机资源,主节点ResourceManager管理集群资源,如规定<1核,2G>为1个Container
Client:Client向ResourceManager提交任务、终止任务等。
ResourceManager:整个集群只有一个ResourceManager,负责集群资源的统一管理和调度。具体承担功能包括:
(1)处理来自客户端请求,包括启动/终止应用程序。
(2)启动/监控ApplicationMaster,一旦某个ApplicationMaster出现故障,ResourceManager将会在另一个节点上启动该ApplicationMaster。
(3)监控NodeManager,接收NodeManager汇报的心跳信息并分配任务给NodeManager去执行,一旦某个NodeManager出现故障,标记该NodeManager的任务,来告诉对应的ApplicationMaster如何处理。
NodeManager
整个集群有多个NodeManager,负责单节点资源的管理和使用。具体承担功能包括:
(1)周期性向ResourceManager汇报本节点上的资源使用情况和各个Container的运行状态。
(2)接收并处理来自ResourceManager的Container启动/停止的各种命令。
(3)处理来自ApplicationMaster的命令。
ApplicationMaster
每个应用程序拥有一个ApplicationMaster,负责管理应用程序。具体承担功能包括:
(1)数据切分。
(2)为应用程序/作业向ResourceManager申请资源(Container),并分配给内部任务。
(3)与NodeManager通信,以启动/停止任务。
(4)任务监控和容错,在任务执行失败时重新为该任务申请资源并重启任务。
(5)接收并处理ResourceManager发出的命令,如终止Container、重启NodeManager等。
.Container
Container是YARN中新引入的一个逻辑概念,是YARN对资源的抽象,是YARN中最重要的概念之一。Container封装了某个节点上一定量的资源(CPU和内存两类资源),它与Linux Container没有任何关系,仅仅是YARN提出的一个概念。
Container由ApplicationMaster向ResourceManager申请,由ResouceManager中的资源调度器异步分配给ApplicationMaster;Container的运行是由ApplicationMaster向资源所在的NodeManager发起的,Container运行时需提供内部执行的任务命令(可以是任何命令,比如Java、Python、C++进程启动命令等)以及该命令执行所需的环境变量和外部资源(比如词典文件、可执行文件、jar包等)。
另外,一个应用程序所需的Container分为以下两大类:
(1)运行ApplicationMaster的Container:这是由ResourceManager和其内部的资源调度器申请和启动的,用户提交应用程序时,可指定唯一的ApplicationMaster所需的资源。
(2)运行各类任务的Container:这是由ApplicationMaster向ResourceManager申请的,并由ApplicationMaster与NodeManager通信以启动之。该类Container上运行的任务类型可以是Map Task、Reduce Task或Spark Task等。
以上两类Container可能在任意节点上,它们的位置通常而言是随机的,即ApplicationMaster可能与它管理的任务运行在一个节点上。
① Client向YARN提交MapReduce应用程序,提交的内容包括ApplicationMaster程序、启动ApplicationMaster的命令、用户程序等。
② ResourceManager接收到Client应用程序请求后,为应用程序分配第一个Container,并与对应的NodeManager通信,要求它在这个Container中启动该应用程序的ApplicationMaster(即图中的“MRAppMaster”)。
③ ApplicationMaster被创建后会首先向ResourceManager注册,从而使得用户可以直接通过ResourceManager查询应用程序的运行状态。接下来的步骤④-⑦是具体应用程序的执行步骤。
④ ApplicationMaster采用轮询的方式通过RPC请求向ResourceManager申请资源。
⑤ ResourceManager以“容器Container”的形式向提出申请的ApplicationMaster分配资源,一旦ApplicationMaster申请到资源,便与对应的NodeManager通信,要求它启动任务。
⑥ 当ApplicationMaster要求容器启动任务时,它会为任务设置好运行环境,包括环境变量、JAR包、二进制程序等,然后将任务启动命令写到一个脚本中,最后NodeManager在容器中运行该脚本以启动任务。
⑦ 各个任务通过RPC协议向ApplicationMaster汇报自己的状态和进度,以便ApplicationMaster随时掌握各个任务的运行状态,从而可以在任务失败时重启任务;在应用程序运行过程中,用户可以随时通过RPC向ApplicationMaster查询应用程序当前运行状态。
⑧ 应用程序运行完成后,ApplicationMaster向ResourceManager的应用程序管理器ApplicationManager注销并关闭自己。若ApplicationMaster因故失败,ResourceManager中的应用程序管理器ApplicationManager会监测到失败的情形,然后将其重新启动,直到所有的任务都执行完毕为止。
YARN Web UI接口面向管理员。从页面上,管理员能看到“集群统计信息”、“应用程序列表”、“调度器”等功能模块,此页面支持读,不支持写。YARN Web UI的默认地址为http://ResourceManagerIP:8088。
YARN Shell接口面向YARN管理员。通过Shell接口,管理员能够查看YARN系统级别统计信息,提交YARN-App等。
YARN Shell命令统一入口为:yarn,语法格式如下:
yarn [–config confdir] [COMMAND | CLASSNAME]
注意:若$HADOOP_HOME/bin未加入到系统环境变量PATH中,则需要切换到Hadoop安装目录下,输入“bin/yarn”。
YARN Java API接口面向Java开发工程师。程序员可以通过该接口编写YARN-App。
YARN-App三大模块包括:
(1)ApplicationBusinessLogic:应用程序的业务逻辑模块。
(2)ApplicationClient:应用程序客户端,负责提交和监管应用程序。
(3)ApplicationMaster:负责整个应用程序的运行,是应用程序并行化指挥地,需要指挥所有container并行执行ApplicationBusinessLogic。
YARN-App三大模块对应不同范式的类
YARN应用程序标准模块 | DistributedShell框架对应类 | MapReduce框架对应类 | Giraph框架对应类 |
---|---|---|---|
ApplicationBussinessLogic | 用户编写的Shell命令 | 用户自定义Mapper类、Partition类、和Reduce类 | 用户自定义BasicComputation类 |
ApplicationClient | Client.java | YARNRunner.java | GiraphYarnClient.java |
ApplicationMaster | ApplicationMaster.java | MRAPPMaster.java | GiraphApplicationMaster.java |
在YARN体系架构中,ResourceManager地位极其重要,它负责集群资源的统一管理和调度。若ResourceManager发生单点故障,为了减少生产集群中作业执行失败的可能性,YARN提供了新特性——ResourceManager自动重启,该特性保证ResourceManager能尽快自动重启,且重启的过程用户感知不到。
. ResourceManager Restart实现原理
ResourceManager自动重启机制在不同版本的Hadoop中有两种不同的实现,两种实现的原理不同,配置相同。
1)Non-work-preserving RM restart(Hadoop 2.4.0实现)
Non-work-preserving RM restart,即在重启过程中任务不保留。其原理是当Client提交一个Application给ResourceManager时,ResourceManager会将该Application的相关信息存储起来,具体存储位置是可以在配置文件中指定的,可以存储到本地文件系统、HDFS或是ZooKeeper上,此外ResourceManager也会保存Application的最终状态信息(failed,killed,finished),如果是在安全环境下运行,ResourceManager还会保存相关证书文件。
当ResourceManager被关闭后,NodeManager和Client由于发现连接不上ResourceManager,会不断向ResourceManager发送消息,以便能及时确认ResourceManager是否已经恢复正常,当ResourceManager重新启动后,它会发送一条re-sync(重新同步)的命令给所有的NodeManager和ApplicationMaster,NodeManager收到重新同步的命令后会杀死所有的正在运行的Containers并重新向ResourceManager注册,从ResourceManager的角度来看,每台重新注册的NodeManager跟一台新加入到集群中NodeManager是一样的。ApplicationMaster收到重新同步的命令后会自行将自己杀掉。接下来,ResourceManager会将存储的关于Application的相关信息读取出来,并重新提交运行在ResourceManager关闭之前最终状态为正在运行中的Application。
ResourceManager Restart实现原理
2)Work-preserving RM restart(Hadoop 2.6.0实现)
Work-preserving RM restart,即在重启过程中任务是保留的。它与第一种实现的区别在于,ResourceManager会记录下Container整个生命周期的数据,包括Application运行的相关数据、资源申请状况、队列资源使用状况等数据。如此一来,当ResourceManager重启之后,会读取之前存储的关于Application的运行状态数据,同时发送re-sync命令,与第一种方式不同的是,NodeManager在接受到重新同步的命令后并不会杀死正在运行的Containers,而是继续运行Containers中的任务,同时将Containers的运行状态发送给ResourceManager,之后,ResourceManager根据自己所掌握的数据重构Container实例和相关的Application运行状态,如此一来,就实现了在ResourceManager重启之后,会紧接着ResourceManager关闭时任务的执行状态继续执行。
对比以上两种实现方式,第一种只保存了Application提交的信息和最终执行状态,并不保存运行过程中的相关数据,所以ResourceManager重启后,会先杀死正在执行的任务,再重新提交,从零开始执行任务。第二种方式则保存了Application运行中的状态数据,所以在ResourceManager重启之后,不需要杀死之前的任务,而是接着原来执行到的进度继续执行。ResourceManager将应用程序的状态及其他验证信息保存到一个可插拔的状态存储中,ResourceManager重启时从状态存储中重新加载这些信息,然后重新开始之前正在运行的应用程序,用户不需要重新提交应用程序。
在YARN中,ResourceManager负责整个集群资源的管理和应用程序的调度,Hadoop 2.4之前,ResourceManager存在单点故障问题,一旦出现故障,就会影响到整个集群的正常运行。在Hadoop 2.4中,增加了Active/Standby ResourceManager,以解决ResourceManager单点故障问题,这就是ResourceManager High Availability(ResourceManager高可用机制,简称ResourceManager HA)。
1)手工切换
当Active ResourceManager发生故障时,管理员可通过命令手工切换,首先查看当前RM状态,然后手工切换RM,依次使用命令如下所示:
yarn rmadmin -getServiceState rm1
yarn rmadmin -transitionToStandby rm1
2)自动切换
通过内嵌的基于ZooKeeper的ActiveStandbyElector来决定哪个ResourceManager处于Active状态。当Active ResourceManager出现故障时,其他的ResourceManager将会被自动选举并切换成Active状态。
众所周知,YARN可以扩展到数千个节点。YARN的可伸缩性由ResourceManager确定,并且与节点数、活跃的应用程序、活跃的容器和心跳频率成比例。降低心跳可以提高可扩展性,但对利用率有害。基于联邦(Federation)的方法,通过联合多个YARN子集,可以将单个YARN集群扩展到数万个节点。YARN Federation是将大的(10-100k节点)集群划分成称为子集群的较小单元,每个集群具有其自己的ResourceManager和NodeManager。联合系统(Federation System)将这些子集群拼接在一起,使它们成为应用程序的一个大型YARN集群。在此联合环境中运行的应用程序将看到单个大型YARN集群,并且能够在联合集群的任何节点上计划任务。联合系统将与子集群的ResourceManager协商并为应用程序提供资源,目标是允许单个作业无缝地“跨越”子集群。
这种设计在结构上是可扩展的,因为通过限制每个ResourceManager负责的节点数量,并且采用适当的策略将会保证大多数应用程序驻留在单个子集群中,因此每个ResourceManager看到的应用程序数量也是有限的。这意味着几乎可以通过简单地添加子集来线性扩展(因为它们之间需要很少的协调)。
什么是分布式协调技术?
分布式协调技术主要用来解决分布式环境中多个进程之间的同步控制,让它们有序地访问某种临界资源,防止造成“脏数据”。为了防止分布式系统中的多个进程之间相互干扰,就需要一种分布式协调技术来对这些进程进行调度,而分布式协调技术的核心就是实现分布式锁。
假设在Server1上挂载了一个资源,三个物理分布的进程都要竞争这个资源,但不希望它们同时进行访问,这时就需要一个协调器,来让三个进程有序地访问这个资源。这个协调器就是经常提到的锁,比如“Process-1”在使用该资源的时候会先去获得锁,“Process-1”获得锁之后会对该资源保持独占,这样其他进程就无法访问该资源,“Process-1”用完该资源后就将锁释放掉,让其他进程来获得锁。通过这个锁机制,就能保证分布式系统中多个进程能够有序地访问该临界资源。把这个分布式环境下的锁叫做分布式锁,这个分布式锁就是分布式协调技术实现的核心内容。
那么,如何实现分布式锁呢?在分布式环境中,由于网络的不可靠,对一个服务调用的失败并不表示一定是失败的,可能是执行成功了,但是响应返回的时候失败了。另外,A和B都去调用C服务,在时间上A先调用,B后调用,那么最后的结果是不是一定A的请求就先于B。这些在同一台机器上的种种假设都要重新思考,还要思考这些问题给的设计和编码带来了哪些影响。还有,在分布式环境中为了提升可靠性,往往会部署多套服务,但是如何在多套服务中达到一致性,这在同一台机器上多个进程之间的同步相对来说是比较容易的,但在分布式环境中确实一个难题。
目前,已实现分布式协调技术的有Google Chubby,Apache ZooKeeper,它们都是分布式锁的实现者。
Apache ZooKeeper于2010年11月成为Apache顶级项目。ZooKeeper是Google Chubby的开源实现,是一个分布式的、开放源码的分布式应用程序协调框架,为大型分布式系统提供了高效且可靠的分布式协调服务,提供了诸如统一命名服务、配置管理、分布式锁等分布式基础服务,并广泛应用于大型分布式系统如Hadoop、HBase、Kafka等开源系统。
Apache ZooKeeper是一个分布式的、开放源码的分布式应用程序协调框架,是Google Chubby的开源实现,它为大型分布式系统中的各种协调问题提供了一个解决方案,主要用于解决分布式应用中经常遇到的一些数据管理问题,如配置管理、命名服务、分布式同步、集群管理等。
ZooKeeper易于编程,使用文件系统目录树作为数据模型,提供Java和C的编程接口。众所周知,协调服务非常容易出错,但却很难恢复正常,例如,协调服务很容易出现死锁。ZooKeeper的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用
ZooKeeper最早起源于雅虎研究院的一个研究小组。当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开户人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。
雅虎模仿Google Chubby开发出了ZooKeeper,实现了类似的分布式锁功能,并且将ZooKeeper捐献给了Apache,ZooKeeper于2010年11月正式成为了Apache的顶级项目。
ZooKeeper采用类似标准文件系统的数据模型,其节点构成了一个具有层次关系的树状结构。其中每个节点被称为数据节点ZNode,ZNode是ZooKeeper中数据的最小单元,每个节点上可以存储数据,同时也可以挂载子节点,因此构成了一个层次化的命名空间。
ZNode通过路径引用,如同Unix中的文件路径。路径必须是绝对的,因此它们必须由斜杠“/”来开头。在ZooKeeper中,路径由Unicode字符串组成,并且有一些限制。例如ZooKeeper系统的保留ZNode“/zookeeper”用以保存管理信息,比如关键配额信息。
节点类型
在ZooKeeper中,每个数据节点都是有生命周期的,其生命周期的长短取决于数据节点的节点类型。ZNode类型在创建时即被确定,并且不能改变。节点可以分为持久节点(PERSISTENT)、临时节点(EPHEMERAL)和顺序节点(SEQUENTIAL)三大类型。在节点创建过程中,通过组合使用,可以生成以下四种组合型节点类型:
1)持久节点PERSISTENT
持久节点是ZooKeeper中最常见的一种节点类型。所谓持久节点,是指此类节点的生命周期不依赖于会话,自节点被创建就会一直存在于ZooKeeper服务器上,并且只有在客户端显式执行删除操作时,它们才能被删除。
2)持久顺序节点PERSISTENT_SEQUENTIAL
持久顺序节点的基本特性与持久节点相同,额外特性表现在顺序性上。在ZooKeeper中,每个父节点都会为它的第一级子节点维护一份顺序,用于记录每个子节点创建的先后顺序。基于这个顺序特性,在创建子节点的时候,可以设置这个标记,那么在创建节点过程中,ZooKeeper会自动为给定节点名加上一个数字后缀,作为一个新的、完整的节点名。不过ZooKeeper会给此类节点名称进行顺序编号,自动在给定节点名后加上一个数字后缀。这个数字后缀的上限是整型的最大值,其格式为“%10d”(10位数字,没有数值的数位用0补充,例如“0000000001”),当计数值大于232-1时,计数器将溢出。
3)临时节点EPHEMERAL
与持久节点不同的是,临时节点的生命周期依赖于创建它的会话,也就是说,如果客户端会话失效,临时节点将被自动删除,当然也可以手动删除。注意,这里提到的是客户端会话失效,而非TCP连接断开。另外,ZooKeeper规定临时节点不允许拥有子节点。
4)临时顺序节点EPHEMERAL_SEQUENTIAL
临时顺序节点的基本特性和临时节点也是一致的,同样是在临时节点的基础上,添加了顺序的特性。
ZooKeeper命名空间中的ZNode,兼具文件和目录两种特点,既能像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又能像目录一样可以作为路径标识的一部分。每个ZNode由3部分组成:
(1)stat:状态信息,描述该ZNode的版本、权限等信息。
(2)data:与该ZNode关联的数据。
(3)children:该ZNode下的子节点。
ZooKeeper虽然可以关联一些数据,但并没有被设计为常规的数据库或者大数据存储,相反的是,它用来管理调度数据,比如分布式应用中的配置文件信息、状态信息、汇集位置等。这些数据的共同特性就是它们都是很小的数据,通常以KB为大小单位。ZooKeeper的服务器和客户端都被设计为严格检查并限制每个ZNode的数据大小至多1M,但常规使用中应该远小于此值。
每个数据节点都具有三种类型的版本信息,对数据节点的任何更新操作都会引起版本号的编号。三种类型的版本分别为:
(1)version:当前数据节点数据内容的版本号。
(2)cversion:当前数据节点子节点的版本号。
(3)aversion:当前数据节点的ACL版本号。
注意:version表示的是数据节点数据内容的变更次数,强调的是变更次数,因此即使前后两次变更并没有使得数据内容的值发生变化,version的值依然会变更。
事实上,在ZooKeeper中,version属性正是用来实现乐观锁机制中的“写入校验”的。
ZooKeeper提供了分布式数据的发布/订阅功能。一个典型的发布/订阅模型系统定义了一种一对多的订阅关系,能够让多个订阅者同时监听某一个主题的对象,当这个主题对象自身状态变化时,会通知所有订阅者,使它们能够做出相应的处理。
在ZooKeeper中,引入了Watcher机制来实现这种分布式的通知功能。ZooKeeper允许客户端向服务端注册一个Watcher监听,当服务端的一些指定事件触发了这个Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能。
ZooKeeper的Watcher机制主要包括客户端线程、客户端WatcherManager和ZooKeeper服务器三部分。
在工作流程上,简单地讲,客户端在向ZooKeeper服务器注册的同时,会将Watcher对象存储在客户端的WatcherManager当中。当ZooKeeper服务器触发Watcher事件后,会向客户端发送通知,客户端线程从WatcherManager中取出对应的Watcher对象来执行回调逻辑。
在ZooKeeper中,接口类Watcher用于表示一个标准的事件处理器,其定义了事件通知相关的逻辑,包含KeeperState和EventType两个枚举类,分别代表了通知状态和事件类型,同时定义了事件的回调方法:process(WatchedEvent event)。同一事件类型在不同的通知状态中代表的含义有所不同。
其中,NodeDataChanged事件的触发条件是数据内容的变化变更,此处所说的变更包括节点的数据内容和数据版本号version的变化,因此,即使使用相同的数据内容来更新,还是会触发这个事件,因为对于ZooKeeper来说,无论数据内容是否变更,一旦有客户端调用了数据更新的接口且更新成功,就会更新version值。
NodeChildrenChanged事件会在数据节点的子节点列表发生变更的时候被触发,这里说的子节点变化特指子节点个数和组成情况的变更,即新增子节点或删除子节点,而子节点数据内容的变化时不会触发这个事件的。
对于AuthFailed事件,需要注意的是,它的触发条件并不是简单因为当前客户端会话没有权限,而是授权失败。
权限模式用来确定权限验证过程中使用的检验策略。在ZooKeeper中,开发人员使用最多的就是以下四种权限模式。
1)IP
IP模式通过IP地址粒度进行权限控制,例如配置了“ip:192.168.18.130”,就表示权限控制都是针对这个IP地址的。同时,IP模式也支持按照网段的方式进行配置,例如“ip:192.168.18.1/24”表示针对192.168.18.*的IP段进行权限控制。
2)Digest
Digest是最常用的控制权限模式,也更符合对于权限控制的认识,其以类似于“username:password”形式的权限标识来进行权限配置,便于区分不同应用来进行权限控制。当通过“username:password”形式配置了权限标识后,ZooKeeper会对其先后进行两次编码处理,分别是SHA-1算法加密和BASE64编码。
3)World
World是一种最开放的权限控制模式,事实上这种权限控制方式几乎没有任何作用,数据节点的访问权限对所有用户开发,即所有用户都可以在不进行任何权限校验的情况下操作该数据节点。另外,World模式也可以看作是一种特殊的Digest模式,它只有一个权限标识“world:anyone”。
4)Super
Super模式顾名思义就是超级用户的有意思,也是一种特殊的Digest模式。在Super模式下,超级用户可以对任意ZooKeeper上的数据节点进行任何操作。
授权对象指的是权限赋予的用户或一个指定实体,例如IP地址或机器等。在不同的授权模式下,授权对象是不同的
权限就是指那些通过权限检查后可以被允许执行的操作。在ZooKeeper中,所有对数据的操作权限分为以下五大类:
(1)CREATE(C):数据节点的创建权限,允许授权对象在该数据节点下创建子节点。
(2)DELETE(D):子节点的删除权限,允许授权对象删除该数据接地点的子节点。
(3)READ(R):数据节点的读取权限,允许授权对象访问该数据节点并读取其数据内容或子节点列表等。
(4)WRITE(W):数据节点的更新权限,允许授权对象对该数据节点进行更新操作。
(5)ADMIN(A):数据节点的管理权限,允许授权对象对数据节点进行ACL相关的设置操作。
Leader服务器是整个ZooKeeper集群工作机制中的核心,其主要工作包括以下两个方面:
(1)事务请求的唯一调度和处理者,保证集群事务处理的顺序性。
(2)集群内部各服务器的调度者。
Follower服务器是ZooKeeper集群状态的跟随者,其主要工作包括以下三个方面:
(1)处理客户端非事务请求,转发事务请求给Leader服务器。
(2)参与事务请求Proposal的投票。
(3)参与Leader选举投票。
Observer是ZooKeeper自3.3.0版本开始引入的一个全新的服务器角色。从字面意思看,该服务器充当了一个观察者的角色——其观察ZooKeeper集群的最新状态变化并将这些状态变更同步过来。Observer服务器在工作原理上和Follower基本是一致的,对于非事务请求,都可以进行独立的处理,而对于事务请求,则会转发给Leader服务器进行处理。和Follower唯一的区别在于,Observer不参与任何形式的投票,包括事务请求Proposal的投票和Leader的选举投票。简单地说,Observer服务器只提供非事务服务,通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力。
选举机制中的术语
1)SID:服务器ID
SID是一个数字,用来唯一标识一台ZooKeeper集群中机器,每台机器不能重复,和myid值一致。
2)ZXID:事务ID
ZXID是一个事务ID,用来唯一标识一次服务器状态的变更。在某一个时刻,集群中的每台服务器的ZXID值不一定全都一致,这和ZooKeeper服务器对于客户端“更新请求”的处理逻辑有关。
3)Vote:投票
Leader选举必须通过投票来实现。当集群中的机器发现自己无法检测到Leader机器时,就会开始尝试进行投票。
4)Quorum:过半机器数
这是整个Leader选举算法中最重要的一个术语,可以把它理解为一个量词,指的是ZooKeeper集群中过半的机器数,如果集群中总的机器数是n的话,那么可以通过下面公式计算quorum值:
quorum = n/2 + 1
5)服务器状态
服务器状态有4种:LOOKING竞选状态,FOLLOWING随从状态,OBSERVING观察状态,LEADING领导者状态。
Leader选举概述
以3台机器组成的服务器集群为例介绍,假设3台机器的myid依次为1、2、3,称它们依次为Server1、Server 2和Server3,那么Server1的SID为1,Server2的SID为2,Server3的SID为3。
1)服务器启动时期的Leader选举
在服务器集群初始化阶段,当只有服务器Server1启动时,它是无法完成Leader选举的,当第二台服务器Server2也启动后,此时这两台机器已经能够进行互相通信,每台机器都试图找到一个Leader,于是便进入了Leader选举流程。
(1)每个Server会发出一个投票。
由于是初始状态,因此Server1和Server2都会将自己作为Leader服务器来进行投票,每次投票包含的最基本的元素包括:所推举的服务器的SID和ZXID,以(SID,ZXID)形式表示。因为是初始化阶段,因此无论是Server1还是Server2,都会投给自己,即Server1的投票为(1,0),Server2的投票为(2,0),然后各自将这个投票发给集群中其他所有机器。
(2)接收来自各个服务器的投票。
每个服务器都会接收来自其他服务器的投票。集群中的每个服务器在接收到投票后,首先会判断该投票的有效性,包括检查是否是本轮投票、是否来自LOOKING状态的服务器。
(3)处理投票。
在接收到来自其他服务器的投票后,针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK规则如下:
优先检查ZXID。ZXID比较大的服务器优先作为Leader。
如果ZXID相同,那么就比较SID。SID比较大的服务器作为Leader服务器。
根据以上规则,对于Server1,它自己的投票是(1,0),而接收到的投票为(2,0)。首先对比两者的ZXID,因为都是0,所以无法决定谁是Leader;接下来会对比两者的SID,很显然,Server1发现接收到的投票中的SID是2,大于自己,于是就会更新自己的投票为(2,0),然后重新将投票发出去。而对于Server2来说,不需要更新自己的投票信息,只是再一次向集群中所有机器发出上一次投票信息即可。
(4)统计投票。
每次投票后,服务器都会统计所有投票,判断是否已经有过半的机器收到相同的投票信息。对于Server1和Server2服务器来说,都统计出集群中已经有2台机器接受了(2,0)这个投票信息,对于由3台机器构成的集群,2台即达到了“过半”要求(≥n/2+1)。那么,当Server1和Server2都收到相同的投票信息(2,0)时,即认为已经选出了Leader。
(5)改变服务器状态。
一旦确定了Leader,每个服务器都会更新自己的状态:如果是Follower,那么就变更为FOLLOWING;如果是Leader,那么久变更为LEADING。
2)服务器运行期间的Leader选举
在ZooKeeper集群正常运行过程中,一旦选出一个Leader,那么所有服务器的集群角色一般不会再发生变化——也就是说,Leader服务器将一直作为集群的Leader,即使集群中有非Leader宕机或是有新机器加入集群,也不会影响Leader。但是一旦Leader宕机,那么整个集群将暂时无法对外服务,而是进入新一轮的Leader选举。服务器运行期间的Leader选举和启动时期的Leader选举基本过程是一致的
假设当前正在运行的ZooKeeper集群有Server1、Server2、Server3三台机器组成,当前Leader是Server2,假设某一瞬间,Leader宕机,这个时候便开始了新一轮Leader选举,具体过程如下所示。
(1)变更服务器状态。
当Leader宕机后,余下的非Observer服务器就会将自己的服务器状态变更为LOOKING,然后开始进入Leader选举流程。
(2)每个Server都会发出一个投票。
在这个过程中,需要生成投票信息(SID,ZXID)。因为是运行期间,因此每个服务器上的ZXID可能不同,假定Server1的ZXID为123,而Server3的ZXID为122。在第一轮投票中,Server1和Server3都会投自己,即分别产生投票(1,123)和(3,122),然后各自将投票发给集群中所有机器。
(3)接收来自各个服务器的投票。
(4)处理投票。
对于投票的处理,和上面提到的服务器启动期间的处理规则是一致的。在这个例子中,由于Server1的ZXID值大于Server3的ZXID值,因此,Server1会成为Leader。
(5)统计投票。
(6)改变服务器状态。
ZooKeeper是一个典型的发布/订阅模式的分布式数据管理与协调框架,开发人员可以使用它进行分布式数据的发布与订阅。另一方面,通过ZooKeeper中丰富的数据节点类型进行交叉使用,配合Watcher事件通知机制,可以非常方便地构建一系列分布式应用中都会涉及的核心功能,如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁、分布式队列等。
所谓集群管理,包括集群监控和集群控制两大块,前者侧重对集群运行时状态的收集,后者则是对集群进行操作与控制。
在日常开发和运维过程中,经常会有类似于如下的需求:希望知道当前集群中究竟有多少机器在工作;对集群中每台机器的运行时状态进行数据收集;对集群中机器进行上下线操作。
在传统的基于Agent的分布式集群管理体系中,都是通过在集群中的每台机器上部署一个Agent,由这个Agent负责主动向指定的一个监控中心系统汇报自己所在机器的状态。在集群规模适中的场景下,这确实是一种在生产实践中广泛使用的解决方案,能够快速有效地实现分布式环境集群监控,但是一旦系统的业务场景增多,集群规模变大之后,该解决方案的弊端就显现出来了,例如大规模升级困难、统一的Agent无法满足多样的需求、编程语言多样性。
ZooKeeper具有以下两大特性:
客户端如果对ZooKeeper的一个数据节点注册Watcher监听,那么当该数据节点的内容或是其子节点列表发生变更时,ZooKeeper服务器就会向订阅客户端发送变更通知。
对在ZooKeeper上创建的临时节点,一旦客户端与服务器之间的会话失效,那么该临时节点也就被自动清除。
利用ZooKeeper的这两个特性,就可以实现另一种集群机器存活性监控的系统。例如,监控系统在/clusterServers节点上注册一个Watcher监听,那么但凡进行动态添加机器的操作,就会在/clusterServers节点下创建一个临时节点/clusterServers/[Hostname]。这样一来,控制系统就能够实时检测到机器的变动情况,置于后续的处理就是监控系统的业务了。
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统同或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要通过一些互斥手段来防止彼此之间的干扰,以保证一致性,在这种情况下,就需要使用分布式锁了。
在平时的项目开发中,人们往往很少会去在意分布式锁,而是依赖于关系型数据库固有的排他性来实现不同进程之间的互斥。这确实是一种非常简单且被广泛使用的分布式锁实现方式。然而,目前绝大多数大型分布式系统的性能瓶颈都集中在数据库操作上,因此,如果上层业务再给数据库添加一些额外的锁,例如行锁、表锁甚至是繁重的事务处理,那么是不是会让数据库更加不堪重负呢?
**排它锁(Exclusive Locks,简称X锁),又称为写锁或独占锁。**如果事务T1对数据对象O1加上了排它锁,那么在整个加锁期间,只允许事务T1对O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作,直到T1释放了排它锁。排它锁的核心是如何保证当前有且仅有一个事务获得锁,并且锁被释放后,所有正在等待获取锁的事务都能够被通知到。借助ZooKeeper实现排它锁可以通过以下步骤完成。
1)定义锁
通过ZooKeeper上的临时数据节点来表示一个锁,例如/exclusive_lock/lock节点就可以被定义为一个锁。
2)获取锁
在需要获取排它锁时,所有客户端都会试图在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。ZooKeeper会保证在所有的客户端中,最终只有一个客户端能够创建成功,那么就可以认为该客户端获取了锁。同时,所有没有获取到锁的客户端就需要在/exclusive_lock节点上注册一个子节点变更的Watcher监听,以便实时监听到lock节点的变更情况。
3)释放锁
在“定义锁”部分,已经提到,/exclusive_lock/lock是一个临时节点,因此在以下两种情况下,都有可能释放锁:
当前获取锁的客户端机器发生宕机,那么ZooKeeper上的这个临时节点就会被移除。
正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除。
共享锁(Shared Locks,简称S锁),又称为读锁。如果事务T1对数据对象O1加上了共享锁,那么当前事务T1对O1进行读取操作,其他事务也只能对这个数据对象加共享锁,直到该数据对象上的所有共享锁都被释放。共享锁和排他锁的最根本区别在于,加上排他锁后,数据对象只对一个事务可见,而加上共享锁后,数据对所有事务都可见。借助ZooKeeper实现共享锁可以通过以下步骤完成。
1)定义锁
与排他锁相同,通过ZooKeeper上的数据节点来表示一个锁,是一个类似于“/shared_lock/[hostname]-请求类型-序号”的临时顺序节点。例如,/shared_lock/192.168.18.1-R-0000000001就代表了一个共享锁。
2)获取锁
在需要获取共享锁时,所有客户端都会到/shared_lock节点下创建一个临时顺序节点。如果当前是读请求,那么就创建/shared_lock/192.168.18.1-R-0000000001;如果当前是写请求,那么就创建/shared_lock/192.168.18.1-W-0000000001。
3)判断读写顺序
根据共享锁的定义,不同的事务都可以同时对同一个数据对象进行读取操作,而更新操作必须在当前没有任何事务进行读写操作的情况下进行。基于这个原则,通过ZooKeeper的节点来确定分布式读写顺序的步骤如下所示:
(1)创建完节点后,获取/shared_lock节点下的所有子节点,并对该节点注册子节点变更的Watcher监听。
(2)确定自己的节点序号在所有子节点中的顺序。
(3)对读和写请求分类处理。对于读请求,若没有比自己序号小的子节点,或是所有比自己序号小的子节点都是读请求,那么表明自己已经成功获取到了共享锁,同时开始执行读取操作;若比自己序号小的子节点中有写请求,那么就需要进入等待。对于写请求,若自己不是序号最小的子节点,那么就需要进入等待。
(4)接收到Watcher通知后,重复步骤(1)。
4)释放锁
释放锁的逻辑和排他锁相同。
对于大部分Java开源产品而言,在部署与运行之前,总是需要搭建一个合适的环境,通常包括操作系统和Java环境两方面。同样,ZooKeeper部署与运行所需要的系统环境,同样包括操作系统和Java环境两部分。
1)操作系统
ZooKeeper支持不同平台,在当前绝大多数主流的操作系统上都能够运行,例如GNU/Linux、Sun Solaris、FreeBSD、Windows、Mac OS X等。需要注意的是,ZooKeeper官方文档中特别强调,不建议在Mac OS X系统上部署生成环境的ZooKeeper服务器。本书采用的操作系统为Linux发行版CentOS 7。
2)Java环境
ZooKeeper使用Java语言编写,因此它的运行环境需要Java环境的支持,对于ZooKeeper 3.4.13,需要Java 1.6及以上版本的支持。
ZooKeeper有两种运行模式:单机模式和集群模式。单机模式是只在一台机器上安装ZooKeeper,主要用于开发测试,而集群模式则是在多台机器上安装ZooKeeper,实际的生产环境中均采用集群模式。无论哪种部署方式,创建ZooKeeper的配置文件zoo.cfg都是至关重要的。单机模式和集群模式部署的步骤基本一致,只是在zoo.cfg文件的配置上有些差异。
读者需要注意的是,假设你拥有一台比较好的机器(CPU核数大于10,内存大于等于8GB),如果作为单机模式进行部署,资源明显有点浪费,如果按照集群模式进行部署,需要借助硬件上的虚拟化技术,把一台物理机器转换成几台虚拟机,这样操作成本太高。幸运的是,和其他分布式系统如Hadoop一样,ZooKeeper也允许在一台机器上完成一个伪集群的搭建。所谓伪集群,是指集群所有的机器都在一台机器上,但是还是以集群的特性来对外提供服务。这种模式和集群模式非常类似,只是把zoo.cfg文件中配置项“server.id=host:port:port”略做修改。