第三阶段:数据存储与计算(离线场景):3.2 数据存储hdfs

云计算

云计算的看法,是忽悠?还是能带来真实价值的东西?

云计算是对现有资源集中优化后,对客户提供服务,从现在的情况来看云计算真实的为大家提供了服务,比如:网盘等。至于云计算更为准确的定义为美国国家标准与技术研究院(NIST)定义:云计算是一种按使用量付费的模式,这种模式提供可用的、便捷的、按需的网络访问,进入可配置的计算资源共享池(资源包括网络,服务器,存储,应用软件,服务),这些资源能够被快速提供,只需投入很少的管理工作或与服务供应商进行很少的交互。

 

云计算特点如下:

(1) 超大规模:“云”具有相当的规模,赋予用户前所未有的计算能力;

(2) 虚拟化:云计算支持用户在任意位置、使用各种终端获取应用服务;

(3) 高可靠性:“云”使用了数据多副本容错、计算节点同构可互换等措施来保障服务的高可靠性;

(4) 通用性:云计算不针对特定的应用,同一个“云”可以同时支撑不同的应用运行;

(5) 高可扩展性:“云”的规模可以动态伸缩,满足应用和用户规模增长的需要;

(6) 按需服务:“云”是一个庞大的资源池,可以需购买;

(7) 极其廉价:由于“云”的特殊容错措施可以采用极其廉价的节点来构成云,“云”的自动化集中式管理使大量企业无需负担日益高昂的数据中心管理成本;

(8) 潜在的危险性

 

云计算可以认为包括以下几个层次的服务:基础设施即服务(IaaS),平台即服务(PaaS)和软件即服务(SaaS)。

1.IaaS(Infrastructure-as-a-Service):基础设施即服务。消费者通过Internet可以从完善的计算机基础设施获得服务。例如:硬件服务器租用。

2.PaaS:PaaS(Platform-as-a- Service):平台即服务。PaaS实际上是指将软件研发的平台作为一种服务,以SaaS的模式提交给用户。因此,PaaS也是SaaS模式的一种应用。但是,PaaS的出现可以加快SaaS的发展,尤其是加快SaaS应用的开发速度。例如:软件的个性化定制开发。

3.SaaS:SaaS(Software-as-a- Service):软件即服务。它是一种通过Internet提供软件的模式,用户无需购买软件,而是向提供商租用基于Web的软件,来管理企业经营活动。例如:阳光云服务器。

Hadoop介绍

数据的介绍

数据来源

纽约证券交易所每天产生1 TB的交易数据。

著名社交网站Facebook的主机存储着约100亿张照片,占据PB级存储空间。

Ancestry.com,一个家谱网站,存储着2.5 PB数据。

互联网档案馆(The Internet Archive)存储着约2 PB数据,并以每月至少20 TB的速度增长。

瑞士日内瓦附近的大型强子对撞机每年产生约15 PB的数据。

 

数据的存储和分析

问题很简单:多年来硬盘存储容量快速增加的同时,访问速度-- 数据从硬盘读取的速度-- 却未能与时俱进。1990年,一个普通的硬盘驱动器可存储1370 MB的数据并拥有4.4 MB/s的传输速度 ,所以,只需五分钟的时间就可以读取整个磁盘的数据。20年过去了,1 TB级别的磁盘驱动器是很正常的,但是数据传输的速度却在100 MB/s左右。所以它需要花两个半小时以上的时间读取整个驱动器的数据。

 

什么是“大数据”?

Ÿ 2012年淘宝数据总量是20PB,每天大概会扫描900TB数据,每月会增加1.5P,日增0.06P数据,一天高峰阶段每秒要处理30G的数据。

Ÿ 2015年腾讯数据总量有100PB存储容量,有8800台单集群服务器,每天扫描8.5PB数据量,10000亿日接入消息数,10000个并发分拣业务接口。

 

 

 

认识Hadoop

Hadoop 就是为了解决面向互联网及其他来源的大数据的分析和并行处理计算模型。她的诞生引起了学术界、金融界以及商业界的广泛关注。其创建之初的宗旨就是让使用者和用户能够通过使用大量普通的服务器搭建相应的服务器集群来实现大数据的并行处理能力,其优先考虑的是数据扩展性和系统的可用性。

 

Hadoop 是一个粗暴的数据处理工具。可能在学习 Hadoop 以前,习惯了用精巧的算法,优雅的程序对数据进行处理。但是到 Hadoop 这里,就是通过蛮力对数据进行处理。一台计算机处理速度慢,那么就找十台计算机进行处理。十台计算机处理慢,找一百台进行处理。一百台计算机还是处理慢,那么就找一千台进行处理。这也是 Hadoop 处理数据的精髓。

 

Hadoop 由开源的 Java 程序所编写,由 Apache 基金会开发的完全免费使用的开源程序( Open Source)。 Hadoop 开创性地使用了一种从最低层结构上就与现有技术完全不同但是更加具有先进性的数据存储和处理技术。使用 Hadoop 无需掌握系统的低层细节,同时更不需要使用者购买和支付价格不菲的软硬件平台,无限制的在价格低廉的商用 PC 上搭建所需要规模的评选数据分析平台。

 

通过使用自带的数据格式和自定义的特定数据格式, Hadoop 基本上可以按照程序设计人员的要求处理任何数据,不论这个数据类型是什么样的。数据可以是音乐、电影、文本文件、 Log 记录等,都可以做出输入存储在 Hadoop 中。通过编写相应的 MapReduce 处理程序,她会帮助你获得任何你想要的答案。

 

Hadoop历史

Hadoop是Doug Cutting-- Apache Lucene创始人-- 开发的使用广泛的文本搜索库。

Hadoop这个名字不是一个缩写,它是一个虚构的名字。该项目的创建者,Doug Cutting如此解释Hadoop的得名:"这个名字是我孩子给一头吃饱了的棕黄色大象命名的。我的命名标准就是简短,容易发音和拼写,没有太多的意义,并且不会被用于别处。小孩子是这方面的高手。Googol就是由小孩命名的。"

雏形开始于2002年的Apache的Nutch,Nutch是一个开源Java 实现的搜索引擎。它提供了我们运行自己的搜索引擎所需的全部工具。包括全文搜索和Web爬虫。

随后在2003年Google发表了一篇技术学术论文谷歌文件系统(GFS)。GFS也就是google File System,google公司为了存储海量搜索数据而设计的专用文件系统。

2004年Nutch创始人Doug Cutting基于Google的GFS论文实现了分布式文件存储系统名为NDFS。

2004年Google又发表了一篇技术学术论文MapReduce。MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行分析运算。

2005年Doug Cutting又基于MapReduce,在Nutch搜索引擎实现了该功能。

2006年,Yahoo雇用了Doug Cutting,Doug Cutting将NDFS和MapReduce升级命名为Hadoop,Yahoo开建了一个独立的团队给Goug Cutting专门研究发展Hadoop。

     

Hadoop能干什么

数据清洗功能

在大数据应用技术中,前端的数据清洗功能远比我们想象的更重要。没有好的清洗自然也不可能有后续的数据建模和数据挖掘。数据清洗功能不仅受技术发展的限制,也和数据类型以及数据量息息相关。

 

 
   

在大数据分析平台必须拥有兼容性强、查询速率快的数据清洗模块。面对海量的待处理数据和非结构化数据的增加,数据清洗功能的工作量和工作强度也必然会增加。正是认识到数据清洗功能的重要性,国云数据的研发工程师才会不遗余力地加强大数据前端数据清理和数据兼容性的建设。

第三阶段:数据存储与计算(离线场景):3.2 数据存储hdfs_第1张图片

数据分析功能

数据分析能力是大数据平台的建设重心,也是大数据分析工具的存在意义。大数据分析平台的数据分析功能受到多个方面的影响,除了软件设计技术和内建架构,搭载的数据分析模型也相当重要,直接决定了大数据分析平台所能承担的数据分析任务。

 

例如在大数据魔镜中,除了有聚类分析模型、数据预测模型、关联分析模型三大主流数据分析模型外,还有相关性分析、决策树等数据分析模型,甚至可以根据需求自己设定分析路径的代码模式,从而能满足不同用户的多种目的数据分析要求。

 

数据可视化功能

数据可视化是当下最热门的大数据应用技术,数据可视化就是将数据或者数据分析结果以图表的形式展示在各种平台上。这要求大数据分析平台有着强大的数据图表渲染功能,并且要内置丰富的可视化效果,以满足用户的不同展示需求。

第三阶段:数据存储与计算(离线场景):3.2 数据存储hdfs_第2张图片


除了末端展示的需要,数据可视化也是数据分析时不可或缺的一部分,即返回数据时的二次分析。而数据可视化也利于大数据分析平台的学习功能建设,让没有技术背景和初学者也能很快掌握大数据分析平台的操作。

 

Apache Hadoop项目介绍

今天,Hadoop是一个分布式计算基础架构这把"大伞"下的相关子项目的集合。这些项目属于Apache软件基金会(http://hadoop.apache.org),后者为开源软件项目社区提供支持。虽然Hadoop最出名的是MapReduce及其分布式文件系统(HDFS,从NDFS改名而来),但还有其他子项目提供配套服务,其他子项目提供补充性服务。

第三阶段:数据存储与计算(离线场景):3.2 数据存储hdfs_第3张图片

Core

一系列分布式文件系统和通用I/O的组件和接口(序列化、Java RPC和持久化数据结构)。

Avro

一种提供高效、跨语言RPC的数据序列系统,持久化数据存储。(在本书写作期间,Avro只是被当作一个新的子项目创建,而且尚未有其他Hadoop子项目在使用它。)

MapReduce

分布式数据处理模式和执行环境,运行于大型商用机集群。

HDFS

分布式文件系统,运行于大型商用机集群。

Pig

一种数据流语言和运行环境,用以检索非常大的数据集。Pig运行在MapReduce和HDFS的集群上。

Hbase

一个分布式的、列存储数据库。HBase使用HDFS作为底层存储,同时支持MapReduce的批量式计算和点查询(随机读取)。

ZooKeeper

一个分布式的、高可用性的协调服务。ZooKeeper提供分布式锁之类的基本服务用于构建分布式应用。

Hive

分布式数据仓库。Hive管理HDFS中存储的数据,并提供基于SQL的查询语言(由运行时引擎翻译成MapReduce作业)用以查询数据。

Chukwa

分布式数据收集和分析系统。Chukwa运行HDFS中存储数据的收集器,它使用MapReduce来生成报告。(在写作本书期间,Chukwa刚刚从Core中的"contrib"模块分离出来独立成为一个独立的子项目。)

 

Hadoop的优缺点

优点:

(一)高可靠性:Hadoop按位存储和处理数据的能力值得人们信赖;

(二)高扩展性:Hadoop是在可用的计算机集簇间分配数据并完成计算任务的,这些集簇可以方便地扩展到数以千计的节点中。

(三)高效性:Hadoop能够在节点之间动态地移动数据,并保证各个节点的动态平衡,因此处理速度非常快。

(四)高容错性:Hadoop能够自动保存数据的多个副本,并且能够自动将失败的任务重新分配。

缺点:

(一)不适合低延迟数据访问。

(二)无法高效存储大量小文件。

(三)不支持多用户写入及任意修改文件。

 

Hadoop核心

Hadoop的核心就是HDFS和MapReduce,而两者只是理论基础,不是具体可使用的高级应用,Hadoop旗下有很多经典子项目,比如HBase、Hive等,这些都是基于HDFS和MapReduce发展出来的。

 

HDFS

HDFS(Hadoop Distributed File System,Hadoop分布式文件系统),它是一个高度容错性的系统,适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问,适合那些有着超大数据集(large data set)的应用程序。

MapReduce

通俗说MapReduce是一套从海量源数据提取分析元素最后返回结果集的编程模型,将文件分布式存储到硬盘是第一步,而从海量数据中提取分析我们需要的内容就是MapReduce做的事了。

 

HDFS的设计特点

1、大数据文件,非常适合上T级别的大文件或者一堆大数据文件的存储,如果文件只有几个G甚至更小就没啥意思了。

2、文件分块存储,HDFS会将一个完整的大文件平均分块存储到不同计算器上,它的意义在于读取文件时可以同时从多个主机取不同区块的文件,多主机读取比单主机读取效率要高得多得都。

3、流式数据访问,一次写入多次读写,这种模式跟传统文件不同,它不支持动态改变文件内容,而是要求让文件一次写入就不做变化,要变化也只能在文件末添加内容。

4、廉价硬件,HDFS可以应用在普通PC机上,这种机制能够让给一些公司用几十台廉价的计算机就可以撑起一个大数据集群。

5、硬件故障,HDFS认为所有计算机都可能会出问题,为了防止某个主机失效读取不到该主机的块文件,它将同一个文件块副本分配到其它某几个主机上,如果其中一台主机失效,可以迅速找另一块副本取文件。

 

HDFS的关键元素

Block:将一个文件进行分块,通常是64M。

NameNode:保存整个文件系统的目录信息、文件信息及分块信息,这是由唯一一台主机专门保存,当然这台主机如果出错,NameNode就失效了。在Hadoop2.*开始支持activity-standy模式----如果主NameNode失效,启动备用主机运行NameNode。

DataNode:分布在廉价的计算机上,用于存储Block块文件。

第三阶段:数据存储与计算(离线场景):3.2 数据存储hdfs_第4张图片

 

MapReduce介绍

通俗说MapReduce是一套从海量源数据提取分析元素最后返回结果集的编程模型,将文件分布式存储到硬盘是第一步,而从海量数据中提取分析我们需要的内容就是MapReduce做的事了。

MapReduce的基本原理就是:将大的数据分析分成小块逐个分析,最后再将提取出来的数据汇总分析,最终获得我们想要的内容。当然怎么分块分析,怎么做Reduce操作非常复杂,Hadoop已经提供了数据分析的实现,我们只需要编写简单的需求命令即可达成我们想要的数据。

MapReduce是一种用于数据处理的编程模型。该模型非常简单。同一个程序Hadoop可以运行用各种语言编写的MapReduce程序。在本章中,我们将看到用Java,Ruby,Python和C++这些不同语言编写的不同版本。最重要的是,MapReduce程序本质上是并行的,因此可以将大规模的数据分析交给任何一个拥有足够多机器的运营商。MapReduce的优势在于处理大型数据集,所以下面首先来看一个例子。

 

MapReduce案例

一个气象数据集

在我们这个例子里,要编写一个挖掘气象数据的程序。分布在全球各地的气象传感器每隔一小时便收集当地的气象数据,从而积累了大量的日志数据。它们是适合用MapReduce进行分析的最佳候选,因为它们是半结构化且面向记录的数据。

例如:

0184010010999992015010100004+70933-008667FM-12+000999999V0200401N01701000301CN000100199-00631-00741098801ADDAA106002031AY171021AY231021GF109991999999999999999999MA1999999098681MD1110121+9999MW1731OD139902601999REMSYN07001001 11/01 90417 11063 21074 39868 49880 51012 60021 77373 333 91126=

 

例2-1:国家气候数据中心数据记录的格式

1. 0057  

2. 332130      # USAF weather station identifier  

3. 99999       # WBAN weather station identifier  

4. 19500101    # observation date  

5. 0300        # observation time  

6. 4  

7. +51317      # latitude (degrees × 1000)  

8. +028783     # longitude (degrees × 1000)  

9. FM-12  

10. +0171       # elevation (meters)  

11. 99999  

12. V020  

13. 320         # wind direction (degrees)  

14. 1           # quality code  

15. N  

16. 0072  

17. 1  

18. 00450       # sky ceiling height (meters)  

19. 1           # quality code  

20. C  

21. N  

22. 010000      # visibility distance (meters)  

23. 1           # quality code  

24. N  

25. 9  

26. -0128       # air temperature (degrees Celsius × 10)  

27. 1           # quality code  

28. -0139       # dew point temperature (degrees Celsius × 10)  

29. 1           # quality code  

30. 10268       # atmospheric pressure (hectopascals × 10)  

31. 1           # quality code

数据文件按照日期和气象站进行组织。从1901年到2001 年,每一年都有一个目录,每一个目录都包含一个打包文件,文件中的每一个气象站都带有当年的数据。例如,1990年的前面的数据项如下:

1. % ls raw/1990 | head  

2. 010010-99999-1990.gz  

3. 010014-99999-1990.gz  

4. 010015-99999-1990.gz  

5. 010016-99999-1990.gz  

6. 010017-99999-1990.gz  

7. 010030-99999-1990.gz  

8. 010040-99999-1990.gz  

9. 010080-99999-1990.gz  

10. 010100-99999-1990.gz  

11. 010150-99999-1990.gz

因为实际生活中有成千上万个气象台,所以整个数据集由大量较小的文件组成。通常情况下,我们更容易、更有效地处理数量少的大型文件,因此,数据会被预先处理而使每年记录的读数连接到一个单独的文件中。

由于源文件中的气温值按比例增加到10倍,所以结果1901年的最高气温是31.7°C(在本世纪初几乎没有多少气温读数会被记录下来,所以这是可能的)。为完成对跨越一世纪这么长时间的查找,程序在EC2 High-CPU Extra Large Instance机器上一共运行了42分钟。

为加快处理,我们需要并行运行部分程序。从理论上讲,这很简单:我们可以通过使用计算机上所有可用的硬件线程来处理在不同线程中的各个年份的数据。但是这之中存在一些问题。

首先,划分成大小相同的作业块通常并不容易或明显。在这种情况下,不同年份的文件,大小差异很大,所以一些线程会比其他线程更早完成。即使它们继续下一步的工作,但是整个运行中占主导地位的还是那些运行时间很长的文件。另一种方法是将输入数据分成固定大小的块,然后把每块分配到各个进程。

其次,独立线程运行结果在合并后,可能还需要进一步的处理。在这种情况下,每年的结果是独立于其他年份,并可能通过连接所有结果和按年份排序这两种方式来合并它们。如果使用固定大小的块这种方法,则此类合并会更紧凑。对于这个例子,某年的数据通常被分割成几个块,每个进行独立处理。我们将最终获得每个数据块的最高气温,所以最后一步是寻找这些每年气温值中的最大值。

最后,我们仍然受限于一台计算机的处理能力。如果手中所有的处理器都使用上都至少需要20分钟,那就只能这样了。我们不能使它更快。另外,一些数据集的增长会超出一台计算机的处理能力。当我们开始使用多台计算机时,整个大环境中的许多其他因素将发挥作用,可能由于协调性和可靠性的问题而出现当机等错误。谁运行整个作业?我们如何处理失败的进程?

因此,尽管并行处理可行,但实际上它非常复杂。使用Hadoop之类的框架非常有助于处理这些问题。

关系型数据库和MapReduce的比较

 

传统关系型数据库

MapReduce

数据大小

GB

PB

访问

交互型和批处理

批处理

更新

多次读写

一次写入多次读取

结构

静态模式

动态模式

集成度

伸缩性

非线性

线性

MapReduce和关系型数据库之间的另一个区别是它们操作的数据集中的结构化数据的数量。结构化数据是拥有准确定义的实体化数据,具有诸如XML文档或数据库表定义的格式,符合特定的预定义模式。这就是RDBMS包括的内容。另一方面,半结构化数据比较宽松,虽然可能有模式,但经常被忽略,所以它只能用作数据结构指南。例如,一张电子表格,其中的结构便是单元格组成的网格,尽管其本身可能保存任何形式的数据。非结构化数据没有什么特别的内部结构,例如纯文本或图像数据。MapReduce对于非结构化或半结构化数据非常有效,因为它被设计为在处理时间内解释数据。换句话说:MapReduce输入的键和值并不是数据固有的属性,它们是由分析数据的人来选择的。

关系型数据往往是规范的,以保持其完整性和删除冗余。规范化为MapReduce带来问题,因为它使读取记录成为一个非本地操作,并且MapReduce的核心假设之一就是,它可以进行(高速)流的读写。

Web服务器日志是记录集的一个很好的非规范化例子(例如,客户端主机名每次都以全名来指定,即使同一客户端可能会出现很多次),这也是MapReduce非常适合用于分析各种日志文件的原因之一。

MapReduce是一种线性的可伸缩的编程模型。程序员编写两个函数--  map函数和Reduce函数-- 每一个都定义一个键/值对集映射到另一个。这些函数无视数据的大小或者它们正在使用的集群的特性,这样它们就可以原封不动地应用到小规模数据集或者大的数据集上。更重要的是,如果放入两倍的数据量,运行的时间会少于两倍。但是如果是两倍大小的集群,一个任务任然只是和原来的一样快。这不是一般的SQL查询的效果。

随着时间的推移,关系型数据库和MapReduce之间的差异很可能变得模糊。关系型数据库都开始吸收MapReduce的一些思路(如ASTER DATA的和GreenPlum的数据库),另一方面,基于MapReduce的高级查询语言(如Pig和Hive)使MapReduce的系统更接近传统的数据库编程人员。

 

网格计算

高性能计算(High Performance Computing,HPC)和网格计算社区多年来一直在做大规模的数据处理,它们使用的是消息传递接口(Message Passing Interface,MPI)这样的API。从广义上讲,高性能计算的方法是将作业分配给一个机器集群,这些机器访问共享文件系统,由一个存储区域网络(Storage Area Network,SAN)进行管理。这非常适用于以主计算密集型为主的作业,但当节点需要访问的大数据量(数百GB的数据,这是MapReduce实际开始"发光"的起点)时,这会成为一个问题,因为网络带宽成为"瓶颈",所以计算节点闲置下来了。

MapReduce尝试在计算节点本地存储数据,因此数据访问速度会因为它是本地数据而比较快。 这项"数据本地化"功能,成为MapReduce的核心功能并且也是它拥有良好性能的原因之一。意识到网络带宽在数据中心环境是最有价值的资源(到处复制数据会很容易的把网络带宽饱和)之后,MapReduce便通过显式网络拓扑结构不遗余力地加以保护。请注意,这种安排不会排除MapReduce中的高CPU使用分析。

MPI赋予程序员很大的控制,但也要求显式控制数据流机制,需要使用传统的C语言的功能模块完成(例如socket),以及更高级的算法来进行分析。而MapReduce却是在更高层面上完成任务,即程序员从键/值对函数的角度来考虑,同时数据流是隐含的。

在一个大规模分布式计算平台上协调进程是一个很大的挑战。最困难的部分是恰当的处理失效与错误-- 在不知道一个远程进程是否已经失败的时候-- 仍然需要继续整个计算。MapReduce将程序员从必须考虑失败任务的情况中解放出来,它检测失败的map或者reduce任务,在健康的机器上重新安排任务。MapReduce能够做到这一点,因为它是一个无共享的架构,这意味着各个任务之间彼此并不依赖。(这里讲得稍微简单了一些,因为mapper的输出是反馈给reducer的,但这由MapReduce系统控制。在这种情况下,相对于返回失败的map,应该对返回reducer给予更多关注,因为它必须确保它可以检索到必要的map输出,如果不行,必须重新运行相关的map从而生成必要的这些输出。)因此,从程序员的角度来看,执行任务的顺序是无关紧要的。相比之下,MPI程序必须显式地管理自己的检查点和恢复机制,从而把更多控制权交给程序员,但这样会加大编程的难度。

MapReduce听起来似乎是一个相当严格的编程模型,而且在某种意义上看的确如此:我们被限定于键/值对的类型(它们按照指定的方式关联在一起),mapper和reducer彼此间的协作有限,一个接一个地运行(mapper传输键/值对给reducer)。对此,一个很自然的问题是:你是否能用它做点儿有用或普通的事情?

答案是肯定的。MapReduce作为一个建立搜索索引产品系统,是由Google的工程师们开发出来的,因为他们发现自己一遍又一遍地解决相同的问题(MapReduce的灵感来自传统的函数式编程、分布式计算和数据库社区),但它后来被应用于其他行业的其他许多应用。我们惊喜地看到许多算法的变体在MapReduce中得以表示,从图像图形分析,到基于图表的问题,再到机器学习算法 。它当然不能解决所有问题,但它是一个很普遍的数据处理工具。

 

分布式和数据流

分布化

前面展示了MapReduce针对小量输入的工作方式,现在是时候整体了解系统并进入大数据流作为输入了。为简单起见,我们的例子到目前为止都使用本地文件系统中的文件。然而,为了分布化,我们需要把数据存储在分布式文件系统中,典型的如HDFS(详情参见第3章),以允许Hadoop把MapReduce的计算移到承载部分数据的各台机器。下面我们就来看看这是如何工作的。

 

数据流1

首先是一些术语的说明。MapReduce作业(job)是客户端执行的单位:它包括输入数据、MapReduce程序和配置信息。Hadoop通过把作业分成若干个小任务(task)来工作,其包括两种类型的任务:map任务和reduce任务。

有两种类型的节点控制着作业执行过程:jobtracker和多个tasktracker。jobtracker通过调度任务在tasktracker上运行,来协调所有运行在系统上的作业。Tasktracker运行任务的同时,把进度报告传送到jobtracker,jobtracker则记录着每项任务的整体进展情况。如果其中一个任务失败,jobtracker可以重新调度任务到另外一个tasktracker。Hadoop把输入数据划分成等长的小数据发送到MapReduce,称为输入分片(input split)或分片。Hadoop为每个分片(split)创建一个map任务,由它来运行用户自定义的map函数来分析每个分片中的记录。

拥有许多分片就意味着处理每个分片的时间与处理整个输入的时间相比是比较小的。因此,如果我们并行处理每个分片,且分片是小块的数据,那么处理过程将有一个更好的负载平衡,因为更快的计算机将能够比一台速度较慢的机器在作业过程中处理完比例更多的数据分片。即使是相同的机器,没有处理的或其他同时运行的作业也会使负载平衡得以实现,并且在分片变得更细时,负载平衡质量也会更佳。

另一方面,如果分片太小,那么管理分片的总时间和map任务创建的总时间将决定作业的执行的总时间。对于大多数作业,一个理想的分片大小往往是一个HDFS块的大小,默认是64 MB,虽然这可以根据集群进行调整(对于所有新建文件)或在新建每个文件时具体进行指定。

map任务的执行节点和输入数据的存储节点是同一节点,Hadoop的性能达到最佳。这就是所谓的data locality optimization(数据局部性优化)。现在我们应该清楚为什么最佳分片的大小与块大小相同:它是最大的可保证存储在单个节点上的数据量。如果分区跨越两个块,那么对于任何一个HDFS节点而言,基本不可能同时存储这两数据块,因此此分布的某部分必须通过网络传输到节点,这与使用本地数据运行map任务相比,显然效率更低。

map任务把输出写入本地硬盘,而不是HDFS。这是为什么?因为map的输出作为中间输出:而中间输出则被reduce任务处理后产生最终的输出,一旦作业完成,map的输出就可以删除了。因此,把它及其副本存储在HDFS中,难免有些小题大做。如果该节点上运行的map任务在map输出给reduce任务处理之前崩溃,那么Hadoop将在另一个节点上重新运行map任务以再次创建map的输出。

reduce任务并不具备数据本地读取的优势-- 一个单一的reduce任务的输入往往来自于所有mapper的输出。在本例中,我们有一个单独的reduce任务,其输入是由所有map任务的输出组成的。因此,有序map的输出必须通过网络传输到reduce任务运行的节点,并在那里进行合并,然后传递到用户定义的reduce函数中。为增加其可靠性,reduce的输出通常存储在HDFS中。如第3章所述,对于每个reduce输出的HDFS块,第一个副本存储在本地节点上,其他副本存储在其他机架节点中。因此,编写reduce的输出确实十分占用网络带宽,但是只和正常的HDFS写管线的消耗一样。

一个单一的reduce 任务的整个数据流如图2-2所示。虚线框表示节点,虚线箭头表示数据传输到一个节点上,而实线的箭头表示节点之间的数据传输。

reduce任务的数目并不是由输入的大小来决定,而是单独具体指定的。在第7章的7.1节中,将介绍如何为一个给定的作业选择reduce任务数量。

如果有多个reducer,map任务会对其输出进行分区,为每个reduce任务创建一个分区(partition)。每个分区包含许多键(及其关联的值),但每个键的记录都在同一个分区中。分区可以通过用户定义的partitioner来控制,但通常是用默认的分区工具,它使用的是hash函数来形成"木桶"键/值,这种方法效率很高。

一般情况下,多个reduce任务的数据流如图2-3所示。此图清楚地表明了map和reduce任务之间的数据流为什么要称为"shuffle"(洗牌),因为每个reduce任务的输入都由许多map任务来提供。shuffle其实比此图所显示的更复杂,并且调整它可能会对作业的执行时间产生很大的影响。

 

MapReduce中单一Reduce任务的数据流

第三阶段:数据存储与计算(离线场景):3.2 数据存储hdfs_第5张图片

 

多个reduce任务的MapReduce数据流

第三阶段:数据存储与计算(离线场景):3.2 数据存储hdfs_第6张图片

 

数据流2

最后,也有可能不存在reduce任务,不需要shuffle的时候,这样的情况是可能的,因为处理可以并行进行(第7章有几个例子讨论了这个问题)。在这种情况下,唯一的非本地节点数据传输是当map任务写入到HDFS中.

 

第三阶段:数据存储与计算(离线场景):3.2 数据存储hdfs_第7张图片

 

 

 

 

 

 

 

 

 

 

集群的可用带宽限制了MapReduce作业的数量,因此map和reduce任务之间数据传输的代价是最小的。Hadoop允许用户声明一个combiner,运行在map的输出上-- 该函数的输出作为reduce函数的输入。由于combiner是一个优化方法,所以Hadoop不保证对于某个map的输出记录是否调用该方法,调用该方法多少次。换言之,不调用该方法或者调用该方法多次,reducer的输出结果都一样。

combiner的规则限制着可用的函数类型。我们将用一个例子来巧妙地加以说明。以前面的最高气温例子为例,1950年的读数由两个map处理(因为它们在不同的分片中)。假设第一个map的输出如下:

1. (1950, 0)  

2. (1950, 20)  

3. (1950, 10)

第二个map的输出如下:

1. (1950, 25)  

2. (1950, 15)

reduce函数再调用时被传入以下数字:

1. (1950, [0, 20, 10, 25, 15])

因为25是输入值中的最大数,所以输出如下:

1. (1950, 25)

我们可以用combiner,像reduce函数那样,为每个map输出找到最高气温。reduce函数被调用时将被传入如下数值:

1. (1950, [20, 25])

然而,reduce输出的结果和以前一样。更简单地说,我们可以像下面这样,对本例中的气温值进行如下函数调用:

1. max(0, 20, 10, 25, 15) = max(max(0, 20, 10),

max(25, 15)) = max(20, 25) = 25

并非所有函数都有此属性。例如,如果我们计算平均气温,便不能用mean作为combiner,因为:

1. mean(0, 20, 10, 25, 15) = 14

但是:

1. mean(mean(0, 20, 10), mean(25, 15)) = mean(10, 20) = 15

combiner并不能取代reduce函数。(为什么呢?reduce函数仍然需要处理来自不同的map给出的相同键记录。)但它可以帮助减少map和reduce之间的数据传输量,而正因为此,是否在MapReduce作业中使用combiner是需要慎重考虑的。

2.4.2  具体定义一个combiner

让我们回到Java MapReduce程序,combiner是用reducer接口来定义的,并且该应用程序的实现与MaxTemperatureReducer中的函数相同。唯一需要强调的不同是如何在JobConf上设置combiner类。

例2-7:使用combiner高效查找最高气温

1. public class MaxTemperatureWithCombiner {   

2.  

3.   public static void main(String[] args) throws IOException {  

4.     if (args.length != 2) {  

5.         System.err.println("Usage: MaxTemperatureWithCombiner

6.           path> " +"");  

7.         System.exit(-1);  

8.     }  

9.  

10.    

11.     JobConf conf = new JobConf(MaxTemperatureWithCombiner.class);  

12.     conf.setJobName("Max temperature");  

13.  

14.     FileInputFormat.addInputPath(conf, new Path(args[0]));  

15.     FileOutputFormat.setOutputPath(conf, new Path(args[1]));  

16.  

17.     conf.setMapperClass(MaxTemperatureMapper.class);  

18.     conf.setCombinerClass(MaxTemperatureReducer.class);  

19.     conf.setReducerClass(MaxTemperatureReducer.class);  

20.  

21.     conf.setOutputKeyClass(Text.class);  

22.     conf.setOutputValueClass(IntWritable.class);  

23.  

24.     JobClient.runJob(conf);  

25.   }  

26. }

 

运行分布式MapReduce作业

  同一个程序将在一个完整的数据集中直接运行而不做更改。这是MapReduce的优势之一:它扩充数据大小和硬件规模。这里有一个运行结果:在一个10节点EC2 High-CPU Extra Large lnstance,程序只用6分钟就运行完了。

 

2.5  Hadoop流

Hadoop提供了一个API来运行MapReduce,并允许你用除java以外的语言来编写自己的map和reduce函数。Hadoop流使用Unix标准流作为Hadoop和程序之间的接口,所以可以使用任何语言,只要编写的MapReduce程序能够读取标准输入,并写入到标准输出。

流适用于文字处理(尽管0.21.0版本也可以处理二进制流),在文本模式下使用时,它有一个面向行的数据视图。map的输入数据把标准输入流传输到map函数,其中是一行一行的传输,然后再把行写入标准输出。一个map输出的键/值对是以单一的制表符分隔的行来写入的。reduce函数的输入具有相同的格式-- 通过制表符来分隔的键/值对-- 传输标准输入流。reduce函数从标准输入流读入行,然后为保证结果的有序性用键来排序,最后将结果写入标准输出。

下面使用流来重写按年份寻找最高气温的MapReduce程序。

 

Hadoop分布式文件系统

当数据集超过一个单独的物理计算机的存储能力时,便有必要将它分布到多个独立的计算机。管理着跨计算机网络存储的文件系统称为分布式文件系统。因为它们是基于网络的,所有网络编程的复杂性都会随之而来,所以分布式文件系统比普通磁盘文件系统更复杂。举例来说,使这个文件系统能容忍节点故障而不损失数据就是一个极大的挑战。

Hadoop有一个被称为HDFS的分布式系统,全称为Hadoop Distributed Filesystem。(有时可能简称为DFS,在非正式情况或是文档和配置中,其实是一样的。) HDFS是Hadoop的旗舰级文件系统,也是本章的重点,但Hadoop实际上有一个综合性的文件系统抽象,因而接下来我们将看看Hadoop如何与其他文件系统集成(如本地文件系统或Amazon S3)。

 

HDFS的设计

HDFS是为以流式数据访问模式存储超大文件而设计的文件系统,在商用硬件的集群上运行。让我们仔细看看下面的明。

超大文件    

"超大文件"在这里指几百MB,几百GB甚至几百TB大小的文件。目前已经有Hadoop集群存储PB(petabytes)级的数据了。

流式数据访问    

HDFS建立在这样一个思想上:一次写入、多次读取模式是最高效的。一个数据集通常由数据源生成或复制,接着在此基础上进行各种各样的分析。每个分析至少都会涉及数据集中的大部分数据 (甚至全部),因此读取整个数据集的时间比读取第一条记录的延迟更为重要。

商用硬件    

Hadoop不需要运行在昂贵并且高可靠性的硬件上。它被设计运行在商用硬件(在各种零售店都能买到的普通硬件)的集群上,因此至少对于大的集群来说,节点故障的几率还是较高的。HDFS在面对这种故障时,被设计为能够继续运行而让用户察觉不到明显的中断。

同时,那些并不适合HDFS的应用也是值得研究的。在目前,HDFS还不太适用于某些领域,不过日后可能会有所改进。

低延迟数据访问    

需要低延迟访问数据在毫秒范围内的应用并不适合HDFS。HDFS是为达到高数据吞吐量而优化的,这有可能会以延迟为代价。目前,对于低延迟访问,HBase(参见第12章)是更好的选择。

大量的小文件    

名称节点(namenode)存储着文件系统的元数据,因此文件数量的限制也由名称节点的内存量决定。根据经验,每个文件,索引目录以及块占大约150个字节。因此,举例来说,如果有一百万个文件,每个文件占一个块,就至少需要300 MB的内存。虽然存储上百万的文件是可行的,十亿或更多的文件就超出目前硬件的能力了。

多用户写入,任意修改文件   

HDFS中的文件只有一个写入者,而且写操作总是在文件的末尾。它不支持多个写入者,或是在文件的任意位置修改。(可能在以后这些会被支持,但它们也相对不那么高效。)

 

HDFS的概念

一个磁盘有它的块大小,代表着它能够读写的最小数据量。文件系统通过处理大小为一个磁盘块大小的整数倍数的数据块来运作这个磁盘。文件系统块一般为几千字节,而磁盘块一般为512个字节。这些信息,对于仅仅在一个文件上读或写任意长度的文件系统用户来说是透明的。但是,有些工具会维护文件系统,如df 和 fsck, 它们都在系统块级上操作。

HDFS也有块的概念,不过是更大的单元,默认为64 MB。与单一磁盘上的文件系统相似,HDFS上的文件也被分为以块为大小的分块,作为单独的单元存储。但与其不同的是,HDFS中小于一个块大小的文件不会占据整个块的空间。如果没有特殊指出,"块"在本书中就指代HDFS中的块。

为何HDFS中的一个块那么大?

HDFS的块比磁盘的块大,目的是为了减小寻址开销。通过让一个块足够大,从磁盘转移数据的时间能够远远大于定位这个块开始端的时间。因此,传送一个由多个块组成的文件的时间就取决于磁盘传输送率。

我们来做一个速算,如果寻址时间在10毫秒左右,传输速率是100兆/秒,为了使寻址时间为传输时间的1%,我们需要100 MB左右的块大小。而默认的大小实际为64 MB,尽管很多HDFS设置使用128 MB的块。这一数字将在以后随着新一代磁盘驱动带来的传输速度加快而继续调整。

当然这种假定不应该如此夸张。MapReduce过程中的map任务通常是在一个时间内运行操作一个块,因此如果任务数过于少(少于集群上的节点数量),作业的运行速度显然就比预期的慢。

在分布式文件系统中使用抽象块会带来很多好处。第一个最明显的好处是,一个文件可以大于网络中任意一个磁盘的容量。文件的分块(block,后文有些地方也简称为"块")不需要存储在同一个磁盘上,因此它们可以利用集群上的任意一个磁盘。其实,虽然不常见,但对于HDFS集群而言,也可以存储一个其分块占满集群中所有磁盘的文件。

第二个好处是,使用块抽象单元而不是文件会简化存储子系统。简单化是所有系统的追求,但对于故障种类繁多的分布式系统来说尤为重要的。存储子系统控制的是块,简化了存储管理。(因为块的大小固定,计算一个磁盘能存多少块就相对容易),也消除了对元数据的顾虑(块只是一部分存储的数据-而文件的元数据,如许可信息,不需要与块一同存储,这样一来,其他系统就可以正交地管理元数据。)

不仅如此,块很适合于为提供容错和实用性而做的复制操作。为了应对损坏的块以及磁盘或机器的故障,每个块都在少数其他分散的机器(一般为3个)进行复制。如果一个块损坏了,系统会在其他地方读取另一个副本,而这个过程是对用户透明的。一个因损坏或机器故障而丢失的块会从其他候选地点复制到正常运行的机器上,以保证副本的数量回到正常水平。(参见第4章的"数据的完整性"小节,进一步了解如何应对数据损坏。)同样,有些应用程序可能选择为热门的文件块设置更高的副本数量以提高集群的读取负载量。

与磁盘文件系统相似,HDFS中 fsck 指令会显示块的信息。例如,执行以下命令将列出文件系统中组成各个文件的块(参见第10章的"文件系统查看(fsck)"小节):

1. % hadoop fsck / -files -blocks

 

名称节点与数据节点

HDFS集群有两种节点,以管理者-工作者的模式运行,即一个名称节点(管理者)和多个数据节点(工作者)。名称节点管理文件系统的命名空间。它维护着这个文件系统树及这个树内所有的文件和索引目录。这些信息以两种形式将文件永久保存在本地磁盘上:命名空间镜像和编辑日志。名称节点也记录着每个文件的每个块所在的数据节点,但它并不永久保存块的位置,因为这些信息会在系统启动时由数据节点重建。

客户端代表用户通过与名称节点和数据节点交互来访问整个文件系统。客户端提供一个类似POSIX(可移植操作系统界面)的文件系统接口,因此用户在编程时并不需要知道名称节点和数据节点及其功能。

数据节点是文件系统的工作者。它们存储并提供定位块的服务(被用户或名称节点调用时),并且定时的向名称节点发送它们存储的块的列表。

没有名称节点,文件系统将无法使用。事实上,如果运行名称节点的机器被毁坏了,文件系统上所有的文件都会丢失,因为我们无法知道如何通过数据节点上的块来重建文件。因此,名称节点能够经受故障是非常重要的,Hadoop提供了两种机制来确保这一点。

第一种机制就是复制那些组成文件系统元数据持久状态的文件。Hadoop可以通过配置使名称节点在多个文件系统上写入其持久化状态。这些写操作是具同步性和原子性的。一般的配置选择是,在本地磁盘上写入的同时,写入一个远程NFS挂载(mount)。

另一种可行的方法是运行一个二级名称节点,虽然它不能作为名称节点使用。这个二级名称节点的重要作用就是定期的通过编辑日志合并命名空间镜像,以防止编辑日志过大。这个二级名称节点一般在其他单独的物理计算机上运行,因为它也需要占用大量CPU和内存来执行合并操作。它会保存合并后的命名空间镜像的副本,在名称节点失效后就可以使用。但是,二级名称节点的状态是比主节点滞后的,所以主节点的数据若全部丢失,损失仍在所难免。在这种情况下,一般把存在NFS上的主名称节点元数据复制到二级名称节点上并将其作为新的主名称节点运行。

 

命令行接口

现在我们将通过命令行与HDFS交互。HDFS还有很多其他接口,但命令行是最简单的,同时也是许多开发者最熟悉的。

我们将在一台机器上运行HDFS,所以请先参照附录A中在伪分布模式下设置Hadoop的说明。稍后将介绍如何在集群上运行HDFS从而为我们提供伸缩性与容错性。

在我们设置伪分布配置时,有两个属性需要进一步解释。首先是fs.default.name,设置为hdfs://localhost/, 用来为Hadoop设置默认文件系统。文件系统是由URI指定的,这里我们已使用了一个hdfs URI 来配置HDFS为Hadoop的默认文件系统。HDFS的守护程序将通过这个属性来决定HDFS名称节点的宿主机和端口。我们将在localhost上运行,默认端口为8020。这样一来,HDFS用户将通过这个属性得知名称节点在哪里运行以便于连接到它。

第二个属性dfs.replication,我们设为1,这样一来,HDFS就不会按默认设置将文件系统块复制3份。在单独一个数据节点上运行时,HDFS无法将块复制到3个数据节点上,所以会持续警告块的副本不够。此设置可以解决这个问题。

基本文件系统操作

文件系统已经就绪,我们可以执行所有其他文件系统都有的操作,例如,读取文件,创建目录,移动文件,删除数据,列出索引目录,等等。输入hadoop fs -help命令即可看到所有命令详细的帮助文件。

首先将本地文件系统的一个文件复制到HDFS:

1. % hadoopfs -copyFromLocal input/docs/quangle.

txt hdfs://localhost/user/tom/quangle.txt

该命令调用Hadoop文件系统的shell命令fs,提供一系列的子命令。在这里,我们执行的是-copyFromLocal。本地文件quangle.txt被复制到运行在localhost上的HDFS实体中的/user/tom/quangle.txt文件。其实我们可以省略URI的格式与主机而选择默认设置,即省略hdfs://localhost,就像core-site.xml中指定的那样。

1. % hadoop fs -copyFromLocal input/docs/quangle.

txt /user/tom/quangle.txt

也可以使用相对路径,并将文件复制到home目录,即/user/tom:

1. % hadoop fs -copyFromLocal input/docs/quangle.txt quangle.txt

我们把文件复制回本地文件系统,看看是否一样:

1.     % hadoop fs -copyToLocal quangle.txt quangle.copy.txt  

2. % md5 input/docs/quangle.txt quangle.copy.txt  

3. MD5 (input/docs/quangle.txt) = a16f231da6b05e2ba7a339320e7dacd9  

4. MD5 (quangle.copy.txt) = a16f231da6b05e2ba7a339320e7dacd9

MD5分析结果是一样的,表明这个文件在HDFS之旅中得以幸存并完整。

最后,我们看一下HDFS文件列表。我们创建一个目录来看看它在列表中如何显示:

1. % hadoop fs -mkdir books  

2. % hadoop fs -ls .  

3. Found 2 items  

4. drwxr-xr-x   - tom supergroup          0

2009-04-02 22:41 /user/tom/books  

5. -rw-r--r--   1 tom supergroup        118

2009-04-02 22:29 /user/tom/quangle.txt

返回的信息结果与Unix命令ls -l的输出非常相似,仅有细微差别。第一列显示的是文件格式。第二列是这个文件的副本数(这在Unix文件系统是没有的)。由于我们设置的默认副本数在网站范围内为1,所以这里显示的也都是1。这一列的开头目录栏是空的,因为副本的概念并没有应用-- 目录是作为元数据并存在名称节点中的,而非数据节点。第三列和第四列显示文件的所属用户和组别。第五列是文件的大小,以字节显示,目录大小为0。第六列和第七列是文件的最后修改日期与时间。最后的第八列是文件或目录的绝对路径。

HDFS中的文件许可

HDFS对于文件及目录有与POSIX非常相似的许可模式。

共有三种形式的许可:读取许可(r)、写入许可(w)和执行许可(x)。读取文件或列出目录内容时需要读取许可。写入一个文件,或是在一个目录上创建或删除文件或目录,需要写入许可。对于文件而言执行许可可以忽略因为HDFS中不能执行文件(与POSIX不同),但在访问一个目录的子项时是需要的。

每个文件和目录都有一个所属用户、所属组别和模式。这个模式是由所属用户的许可、组内其他成员的许可及其他用户的许可组成。

客户端的标识是通过它正在运行的进程的username(名称)和groups(组别)来确定的。由于客户端是远程的,任何人都可以简单地在远程系统上创建一个账户来进行访问。因此,许可只能在一个合作的团体中的用户中使用,作为共享文件系统资源和防止数据意外损失的机制,而不能在一个敌意的环境中保护资源。但是,除去这些缺点,为防止用户或自动工具及程序意外修改或删除文件系统的重要部分,使用许可还是值得的(这也是默认的配置,参见dfs.permissions属性)。

如果启用了许可检查,所属用户许可与组别许可都会被检查,以确认用户的用户名与所属用户许可是否相同,确认他是否属于此用户组的成员;若不符,则检查其他许可。

这里有一个超级用户的概念,超级用户是名称节点进程的标识。对于超级用户,系统不会执行任何许可检查。

 

Hadoop文件系统(1)

Hadoop有一个抽象的文件系统概念,HDFS只是其中的一个实现。Java抽象类 org.apache.hadoop.fs.FileSystem展示了Hadoop的一个文件系统,而且有几个具体实现。

文件系统

URI
方案

Java

(全部在
org.apache.hadoop)

描述

Local

file

fs.LocalFileSystem

针对有客户端校验和

的本地连接磁盘使用

的文件系统。针对没

有校验和的本

地文件系统使用

RawLocalFileSystem

详情参见第4

HDFS

hdfs

hdfs.Distributed-

FileSystem

Hadoop的分布式

文件系统。HDFS

被设计为结合使用

Map-Reduce实现高

效工作

HFTP

hftp

hdfs.HftpFileSystem

一个在HTTP上提

供对HDFS只读访

问的文件系统(虽然

其名称为HFTP,但

它与FTP无关)。通

常与distcp结合使用

(参见第3),在运

行不同版本HDFS

集群间复制数据

HSFTP

hsftp

hdfs.Hsftp-
FileSystem

HTTPS上提供对

HDFS只读访问的

文件系统(同上,与

FTP无关)

HAR

har

fs.HarFileSystem

一个构建在其他文

件系统上来存档文

件的文件系统。Hadoop

存档一般在HDFS

的文件存档时使用,

以减少名称节点内存的使用

KFS(Cloud-Store)

kfs

fs.kfs.Kosmos-
FleSystem

cloudstore(其前身是

Kosmos文件系统)

是相似于HDFS或是

GoogleGFS的文件

系统,用C++

写。详

情可参见http://kosmosfs.
sourceforge.net/

FTP

ftp

fs.ftp.FTP-
FileSystem

FTP服务器支持的

文件系统

S3(本地)

s3n

fs.s3native.Native-
S3FileSystem.

Amazon S3

持的文件

系统。可参见

http://wiki.apache.org
/hadoop/AmazonS3

S3(基于
)

s3

fs.s3.S3FileSystem

 Amazon S3

持的文件系统

以块格式存储文件

(HDFS很相似)

来解决S35 GB

文件大小限制

 

Hadoop文件系统(2)

接口

Hadoop是用Java编写的,所有Hadoop文件系统间的相互作用都是由Java API调解的。 举个例子,文件系统的shell就是一个Java应用,它使用Java文件系统类来提供文件系统操作。这些接口在HDFS中被广泛应用,因为Hadoop中的其他文件系统一般都有访问基本文件系统的工具(FTP的FTP 客户,S3的S3工具等),但它们大多数都能和任意一个Hadoop文件系统协作。

Thrift

因为Hadoop的文件系统接口是Java API,所以其他非Java应用访问Hadoop文件系统会比较麻烦。在"Thriftfs"分类单元中的Thrift API通过将Hadoop文件系统展示为一个Apache Thrift服务来弥补这个不足,使得任何有Thrift绑定的语言都能轻松地与Hadoop文件系统互动,如HDFS。

使用Thrift API,需要运行提供Thrift服务的Java服务器,以代理的方式访问Hadoop文件系统。你的应用程序在访问Thrift服务时,后者实际上就和它运行在同一台机器上。

Thrift API包含很多其他语言的预生成stub,包含C++,Perl, PHP, Python及Ruby。Thrift支持不同版本,因此我们可以从同一个客户代码中访问不同版本的Hadoop文件系统(不过必须运行针对每个版本的代理)。

关于安装与使用教程,请参阅src/contrib/thriftfs目录中关于Hadoop分布的文档。

C语言库

Hadoop提供了反映Java文件系统接口的名为libhdfs的C语言库(它被编写为一个访问HDFS的C语言库,但其实可以访问任意Hadoop文件系统)。它会使用Java本地接口(JNI)调用一个Java文件系统客户。

C API与Java的非常相似,但它一般比Java的滞后,因此目前还不支持一些新特征。相关资料可参见libhdfs/docs/api目录中关于Hadoop分布的C API文档。

Hadoop中有预先建好的32位Linux的libhdfs二元码,但对于其他平台,需要使用http://wiki.apache.org/hadoop/LibHDFS的教程自己编写。

FUSE

用户空间文件系统(Filesystem in Userspace,FUSE)允许一些文件系统整合为一个Unix文件系统在用户空间中执行。通过使用Hadoop的Fuse-DFS分类模块,任意一个Hadoop文件系统(不过一般为HDFS)都可以作为一个标准文件系统进行挂载。我们随后便可以使用Unix的工具(如ls和cat)与这个文件系统交互,还可以通过任意一种编程语言使用POSIX库来访问文件系统。

Fuse-DFS是用C语言实现的,使用libhdfs作为与HDFS的接口。要想了解如何编译和运行Fuse-DFS,可参见src/contrib./fuse-dfs中的Hadoop分布目录。

WebDAV

WebDAV是一系列支持编辑和更新文件的HTTP的扩展。在大部分操作系统中,WebDAV共享都可以作为文件系统进行挂载,因此借由WebDAV来向外提供HDFS(或其他Hadoop文件系统),可以将HDFS作为一个标准文件系统进行访问。

在本书写作期间,Hadoop中的WebDAV支持(通过对Hadoop调用Java API来实现)仍在开发中,要想了解最新动态,可访问https://issues,apache.org/jira/browse/ HADOOP-496。

其他HDFS接口

对于HDFS有两种特定的接口。

HTTP

HDFS定义了一个只读接口用来在HTTP上检索目录列表和数据。目录列表由名称节点的嵌入式Web服务器(运行在50070端口)以XML格式提供服务,文件数据由数据节点通过它们的Web服务器 (运行在50075端口)传输。这个协议并不拘泥于某个HDFS版本,因此用户可以编写使用HTTP从运行不同版本Hadoop的HDFS集群中读取数据的客户端应用。HftpFileSystem就是其中一种:一个通过HTTP与HDFS交流的Hadoop文件系统(HsftpFileSystem是HTTPS的变体)。

FTP

尽管本书写作期间尚未完成(https://issues.apache.org/jira/browse/HADOOP-3199),但我们还是要提一下,还有一个对HDFS的FTP接口,它允许使用FTP协议与HDFS交互。这个接口很方便,它使用现有FTP客户端与HDFS进行数据的传输。

对HDFS的FTP接口与FTPFileSystem不可混为一谈,此接口的目的是将任意FTP服务器向外暴露为Hadoop文件系统。

3.5  Java接口

在本小节,我们要深入探索Hadoop的Filesystem类:与Hadoop的文件系统交互的API。 虽然我们主要关注的是HDFS的实现DistributedFileSystem,但总体来说,还是应该努力编写不同于FileSsytem抽象类的代码,以保持其在不同文件系统中的可移植性。这是考验编程能力的最佳手段,因为我们很快就可以使用存储在本地文件系统中的数据来运行测试了。

3.5.1  从Hadoop URL中读取数据

要从Hadoop文件系统中读取文件,一个最简单的方法是使用java.net.URL对象来打开一个数据流,从而从中读取数据。一般的格式如下:

1.     InputStream in = null;  

2. try {  

3.      in = new URL("hdfs://host/path").openStream();  

4.      // process in  

5. } finally {  

6.      IOUtils.closeStream(in);  

7. }

这里还需要一点工作来让Java识别Hadoop文件系统的URL 方案,就是通过一个FsUrlStreamHandlerFactory实例来调用在URL中的setURLStreamHandler-Factory方法。这种方法在一个Java虚拟机中只能被调用一次,因此一般都在一个静态块中执行。这个限制意味着如果程序的其他部件(可能是不在你控制中的第三方部件)设置一个URLStreamHandlerFactory,我们便无法再从Hadoop中读取数据。下一节将讨论另一种方法。

例3-1展示了以标准输出显示Hadoop文件系统的文件的程序,它类似于Unix的cat命令。

例3-1:用URLStreamHandler以标准输出格式显示Hadoop文件系统的文件

1. public class URLCat {  

2.  

3.   static {  

4.     URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());  

5.   }  

6.     

7.   public static void main(String[] args) throws Exception {  

8.     InputStream in = null;  

9.     try {  

10.       in = new URL(args[0]).openStream();  

11.       IOUtils.copyBytes(in, System.out, 4096, false);  

12.     } finally {  

13.       IOUtils.closeStream(in);  

14.     }  

15.   }  

16. }

我们使用Hadoop中简洁的IOUtils类在finally子句中关闭数据流,同时复制输入流和输出流之间的字节(本例中是System.out)。copyBytes方法的最后两个参数,前者是要复制的缓冲的大小,后者表示复制结束后是否关闭数据流。这里是将输入流关掉了,而System.out不需要关闭。

下面是一个运行示例:

1.     % hadoop URLCat hdfs://localhost/user/tom/quangle.txt  

2. On the top of the Crumpetty Tree  

3. The Quangle Wangle sat,  

4. But his face you could not see,  

5. On account of his Beaver Hat.

3.5.2  使用FileSystem API读取数据

如前一小节所解释的,有时不能在应用中设置URLStreamHandlerFactory。这时,我们需要用FileSystem API来打开一个文件的输入流。

文件在Hadoop文件系统中显示为一个Hadoop Path对象(不是一个java.io.File对象,因为它的语义与本地文件系统关联太紧密)。我们可以把一个路径视为一个Hadoop文件系统URI,如hdfs://localhost/user/tom/quangle.txt。

FileSystem是一个普通的文件系统API,所以首要任务是检索我们要用的文件系统实例,这里是HDFS。取得FileSystem实例有两种静态工厂方法:

1. public static FileSystem get(Configuration conf)

throws IOException  

2. ublic static FileSystem get(URI uri,

Configuration conf) throws IOException

Configuration对象封装了一个客户端或服务器的配置,这是用从类路径读取而来的配置文件(如conf/core-site.xml)来设置的。第一个方法返回的是默认文件系统(在conf/core-site.xml中设置的,如果没有设置过,则是默认的本地文件系统)。第二个方法使用指定的URI方案及决定所用文件系统的权限,如果指定URI中没有指定方案,则退回默认的文件系统。

有了FileSystem实例后,我们调用open()来得到一个文件的输入流:

1. public FSDataInputStream open(Path f) throws IOException  

2. ublic abstract FSDataInputStream open(Path f,

int bufferSize) throws IOException

第一个方法使用默认4 KB的缓冲大小。

将它们合在一起,我们可以在例3-2中重写例3-1。

例3-2:直接使用FileSystem以标准输出格式显示Hadoop文件系统的文件

1. public class FileSystemCat {  

2.   public static void main(String[] args) throws Exception {  

3.     String uri = args[0];  

4.     Configuration conf = new Configuration();  

5.     FileSystem fs = FileSystem.get(URI.create(uri), conf);  

6.     InputStream in = null;  

7.     try {  

8.       in = fs.open(new Path(uri));  

9.       IOUtils.copyBytes(in, System.out, 4096, false);  

10.     } finally {  

11.       IOUtils.closeStream(in);  

12.     }  

13.   }  

14. }

程序运行结果如下:

1.     % hadoop FileSystemCat hdfs://localhost/user/tom/quangle.txt  

2. On the top of the Crumpetty Tree  

3. The Quangle Wangle sat,  

4. But his face you could not see,  

5. On account of his Beaver Hat.  

6. FSDataInputStream

FileSystem中的open()方法实际上返回的是一个FSDataInputStream,而不是标准的java.io类。这个类是java.io.DataInputStream的一个子类,支持随机访问,这样就可以从流的任意位置读取数据了。

1.     package org.apache.hadoop.fs;  

2.  

3. public class FSDataInputStream extends DataInputStream  

4.      implements Seekable, PositionedReadable {  

5.      // implementation elided  

6. }

Seekable接口允许在文件中定位,并提供一个查询方法,用于查询当前位置相对于文件开始处的偏移量(getPos()):

1. public interface Seekable {  

2.   void seek(long pos) throws IOException;  

3.   long getPos() throws IOException;  

4.   boolean seekToNewSource(long targetPos) throws IOException;  

5. }

调用seek()来定位大于文件长度的位置会导致IOException异常。与java.io.InputStream中的skip()不同,seek()并没有指出数据流当前位置之后的一点,它可以移到文件中任意一个绝对位置。

应用程序开发人员并不常用seekToNewSource()方法。此方法一般倾向于切换到数据的另一个副本并在新的副本中寻找targetPos指定的位置。HDFS内部就采用这样的方法在数据节点故障时为客户端提供可靠的数据输入流。

例3-3是例3-2的简单扩展,它将一个文件两次写入标准输出:在写一次后,定位到文件的开头再次读入数据流。

例3-3:通过使用seek两次以标准输出格式显示Hadoop文件系统的文件

1. public class FileSystemDoubleCat {  

2.  

3.   public static void main(String[] args) throws Exception {  

4.     String uri = args[0];  

5.     Configuration conf = new Configuration();  

6.     FileSystem fs = FileSystem.get(URI.create(uri), conf);  

7.     FSDataInputStream in = null;  

8.     try {  

9.       in = fs.open(new Path(uri));  

10.       IOUtils.copyBytes(in, System.out, 4096, false);  

11.       in.seek(0); // go back to the start of the file  

12.       IOUtils.copyBytes(in, System.out, 4096, false);  

13.     } finally {  

14.       IOUtils.closeStream(in);  

15.     }  

16.   }  

17. }

在一个小文件上运行得到以下结果:

1.     % hadoop FileSystemDoubleCat hdfs://localhost/user/tom/quangle.txt  

2. On the top of the Crumpetty Tree  

3. The Quangle Wangle sat,  

4. But his face you could not see,  

5. On account of his Beaver Hat.  

6. On the top of the Crumpetty Tree  

7. The Quangle Wangle sat,  

8. But his face you could not see,  

9. On account of his Beaver Hat.

FSDataInputStream也实现了PositionedReadable接口,从一个指定位置读取一部分数据:

1. public interface PositionedReadable {  

2.  

3.   public int read(long position, byte[] buffer,

int offset, int length)  

4.         throws IOException;  

5.  

6.   public void readFully(long position, byte[]

buffer, int offset, int length)  

7.         throws IOException;  

8.  

9.   public void readFully(long position, byte[]

buffer) throws IOException;  

10. }

read()方法从指定position读取指定长度的字节放入缓冲buffer的指定偏离量offset。返回值是实际读到的字节数:调用者需要检查这个值,它有可能小于指定的长度。readFully()方法会读出指定字节由length指定的数据到buffer中或在只接受buffer字节数组的版本中,再读取buffer.length字节(这儿指的是第三个函数),若已经到文件末,将会抛出EOFException。

所有这些方法会保留文件当前位置并且是线程安全的,因此它们提供了在读取文件(可能是元数据)的主要部分时访问其他部分的便利方法。其实,这只是使用Seekable接口的实现,格式如下:

1.     long oldPos = getPos();  

2. try {  

3.   seek(position);  

4.   // read data  

5. } finally {  

6.   seek(oldPos);  

7. }

最后务必牢记,seek()是一个相对高开销的操作,需要慎重使用。我们需要依靠流数据构建应用访问模式(如使用MapReduce),而不要大量执行seek操作。

3.5.3  写入数据

FileSystem类有一系列创建文件的方法。最简单的是给拟创建的文件指定一个路径对象,然后返回一个用来写的输出流:

1. public FSDataOutputStream create(Path f) throws IOException

这个方法有重载的版本允许我们指定是否强制覆盖已有的文件、文件副本数量、写入文件时的缓冲大小、文件块大小以及文件许可。

注意:create()方法为需要写入的文件而创建的父目录可能原先并不存在。虽然这样很方便,但有时并不希望这样。如果我们想在父目录不存在时不执行写入,就必须在调用exists()首先检查父目录是否存在。

还有一个用于传递回调接口的重载方法Progressable,如此一来,我们所写的应用就会被告知数据写入数据节点的进度:

1. package org.apache.hadoop.util;  

2. ublic interface Progressable {  

3.       public void progress();  

新建文件的另一种方法是使用append()在一个已有文件中追加(也有一些其他重载版本):

1. public FSDataOutputStream append(Path f) throws IOException

这个操作允许一个写入者打开已有文件并在其末尾写入数据。有了这个API,会产生无边界文件的应用,以日志文件为例,就可以在重启后在已有文件上继续写入。此添加操作是可选的,并不是所有Hadoop文件系统都有实现。HDFS支持添加,但S3就不支持了。

例3-4展示了如何将本地文件复制到Hadoop文件系统。我们在每次Hadoop调用progress()方法时,也就是在每64 KB数据包写入数据节点管道后打印一个句号来展示整个过程。(注意,这个动作并不是API指定的,因此在Hadoop后面的版本中大多被改变了。API仅仅是让我们注意到"发生了一些事"。)

例3-4:将本地文件复制到Hadoop文件系统并显示进度

1. public class FileCopyWithProgress {  

2.   public static void main(String[] args) throws Exception {  

3.     String localSrc = args[0];  

4.     String dst = args[1];  

5.     InputStream in = new BufferedInputStream(new

FileInputStream(localSrc));  

6.       

7.    

8.     Configuration conf = new Configuration();  

9.     FileSystem fs = FileSystem.get(URI.create(dst), conf);  

10.     OutputStream out = fs.create(new Path(dst), new Progressable() {  

11.       public void progress() {  

12.         System.out.print(".");  

13.       }  

14.     });  

15.       

16.     IOUtils.copyBytes(in, out, 4096, true);  

17.   }  

18. }

典型用途:

1. % hadoop FileCopyWithProgress input/docs/1400-8.txt

hdfs://localhost/user/tom/1400-8.txt  

2. ...............

目前,其他Hadoop文件系统在写入时都不会调用progress()。通过后面几章的描述,我们会感到进度之于MapReduce应用的重要性。

FSDataOutputStream

FileSystem中的create()方法返回了一个FSDataOutputStream,与FSDataInputStream类似,它也有一个查询文件当前位置的方法:

1. package org.apache.hadoop.fs;  

2.  

3. public class FSDataOutputStream extends

DataOutputStream implements Syncable {  

4.  

5.   public long getPos() throws IOException {  

6.     // implementation elided  

7.   }  

8.     

9.    // implementation elided  

10. }

但是,与FSDataInputStream不同,FSDataOutputStream不允许定位。这是因为HDFS只允许对一个打开的文件顺序写入,或向一个已有文件添加。换句话说,它不支持除文件尾部的其他位置的写入,这样一来,写入时的定位就没有什么意义。

3.5.4  目录

fIlesystem提供了一个创建目录的方法:

1. public boolean mkdirs(Path f) throws IOException

这个方法会创建所有那些必要但不存在的父目录,就像java.io.File的mkdirs()。如果目录(以及所有父目录)都创建成功,它会返回true。

我们常常不需要很确切地创建一个目录,因为调用create()写入文件时会自动生成所有的父目录。

3.5.5  查询文件系统(1)

文件元数据:Filestatus

任何文件系统的一个重要特征是定位其目录结构及检索其存储的文件和目录信息的能力。FileStatus类封装了文件系统中文件和目录的元数据,包括文件长度、块大小、副本、修改时间、所有者以及许可信息。

FileSystem的getFileStatus()提供了获取一个文件或目录的状态对象的方法。例3-5展示了它的用法。

例3-5:展示文件状态信息

1. public class ShowFileStatusTest {  

2.     

3.   private MiniDFSCluster cluster; // use an

in-process HDFS cluster for testing  

4.   private FileSystem fs;  

5.  

6.   @Before  

7.   public void setUp() throws IOException {  

8.     Configuration conf = new Configuration();  

9.     if (System.getProperty("test.build.data") == null) {  

10.       System.setProperty("test.build.data", "/tmp");  

11.     }  

12.     cluster = new MiniDFSCluster(conf, 1, true, null);  

13.     fs = cluster.getFileSystem();  

14.     OutputStream out = fs.create(new Path("/dir/file"));  

15.     out.write("content".getBytes("UTF-8"));  

16.     out.close();  

17.   }  

18.     

19.   @After  

20.   public void tearDown() throws IOException {  

21.     if (fs != null) { fs.close(); }  

22.     if (cluster != null) { cluster.shutdown(); }  

23.   }  

24.     

25.   @Test(expected = FileNotFoundException.class)  

26.   public void throwsFileNotFoundForNonExistentFile()

throws IOException {  

27.     fs.getFileStatus(new Path("no-such-file"));  

28.   }  

29.     

30.   @Test  

31.   public void fileStatusForFile() throws IOException {  

32.     Path file = new Path("/dir/file");  

33.     FileStatus stat = fs.getFileStatus(file);  

34.     assertThat(stat.getPath().toUri().getPath(), is("/dir/file"));  

35.     assertThat(stat.isDir(), is(false));  

36.     assertThat(stat.getLen(), is(7L));  

37.     assertThat(stat.getModificationTime(),  

38.         is(lessThanOrEqualTo(System.currentTimeMillis())));  

39.     assertThat(stat.getReplication(), is((short) 1));  

40.     assertThat(stat.getBlockSize(), is(64 * 1024 * 1024L));  

41.     assertThat(stat.getOwner(), is("tom"));  

42.     assertThat(stat.getGroup(), is("supergroup"));  

43.     assertThat(stat.getPermission().toString(), is("rw-r--r--"));  

44.   }  

45.     

46.   @Test  

47.   public void fileStatusForDirectory() throws IOException {  

48.     Path dir = new Path("/dir");  

49.     FileStatus stat = fs.getFileStatus(dir);  

50.     assertThat(stat.getPath().toUri().getPath(), is("/dir"));  

51.     assertThat(stat.isDir(), is(true));  

52.     assertThat(stat.getLen(), is(0L));  

53.     assertThat(stat.getModificationTime(),  

54.         is(lessThanOrEqualTo(System.currentTimeMillis())));  

55.     assertThat(stat.getReplication(), is((short) 0));  

56.     assertThat(stat.getBlockSize(), is(0L));  

57.     assertThat(stat.getOwner(), is("tom"));  

58.     assertThat(stat.getGroup(), is("supergroup"));  

59.     assertThat(stat.getPermission().toString(), is("rwxr-xr-x"));  

60.   }  

61.     

62. }

如果文件或目录不存在,即会抛出FileNotFoundException异常。如果你只对文件或目录是否存在有兴趣,exists()方法会更方便:

1. public boolean exists(Path f) throws IOException

列出文件

查找一个文件或目录的信息很实用,但有时我们还需要能够列出目录的内容。这就是listStatus()方法的功能:

1.     public FileStatus[] listStatus(Path f)

throws IOException  

2. public FileStatus[] listStatus(Path f,

PathFilter filter) throws IOException  

3. public FileStatus[] listStatus(Path[] files)

throws IOException  

4. public FileStatus[] listStatus(Path[] files,

PathFilter filter) throws IOException

传入参数是一个文件时,它会简单地返回长度为1的FileStatus对象的一个数组。当传入参数是一个目录时,它会返回0或者多个FileStatus对象,代表着此目录所包含的文件和目录。

重载方法允许我们使用PathFilter来限制匹配的文件和目录,示例参见后文。如果把路径数组作为参数来调用listStatus方法,其结果是与依次对每个路径调用此方法,再将FileStatus对象数组收集在一个单一数组中的结果是相同的,但是前者更为方便。这在建立从文件系统树的不同部分执行的输入文件的列表时很有用。例3-6是这种思想的简单示范。注意FIleUtil中stat2Paths()的使用,它将一个FileStatus对象数组转换为Path对象数组。

例3-6:显示一个Hadoop文件系统中一些路径的文件信息

1. public class ListStatus {  

2.  

3.   public static void main(String[] args) throws Exception {  

4.     String uri = args[0];  

5.     Configuration conf = new Configuration();  

6.     FileSystem fs = FileSystem.get(URI.create(uri), conf);  

7.       

8.     Path[] paths = new Path[args.length];  

9.     for (int i = 0; i < paths.length; i++) {  

10.       paths[i] = new Path(args[i]);  

11.     }  

12.       

13.     FileStatus[] status = fs.listStatus(paths);  

14.     Path[] listedPaths = FileUtil.stat2Paths(status);  

15.     for (Path p : listedPaths) {  

16.       System.out.println(p);  

17.     }  

18.   }  

19. }

3.5.5  查询文件系统(2)

我们可以使用这个程序得到一个路径集的整个目录列表。

1.     % hadoop ListStatus hdfs://localhost/ hdfs://localhost/user/tom  

2. hdfs://localhost/user  

3. hdfs://localhost/user/tom/books  

4. hdfs://localhost/user/tom/quangle.txt

文件格式

在一步操作中处理批量文件,这个要求很常见。举例来说,处理日志的MapReduce作业可能会分析一个月的文件,这些文件被包含在大量目录中。Hadoop有一个通配的操作,可以方便地使用通配符在一个表达式中核对多个文件,不需要列举每个文件和目录来指定输入。Hadoop为执行通配提供了两个FileSystem方法:

1. public FileStatus[] globStatus(Path pathPattern) throws IOException  

2. ublic FileStatus[] globStatus(Path pathPattern,

PathFilter filter) throws IOException

globStatus()返回了其路径匹配于所供格式的FileStatus对象数组,按路径排序。可选的PathFilter命令可以进一步指定限制匹配。

Hadoop支持的一系列通配符与Unix bash相同。

删除数据

使用FileSystem中的delete()可以永久性删除文件或目录。

1. public boolean delete(Path f, boolean recursive) throws IOException

如果传入的f是一个文件或空目录,那么recursive的值就会被忽略。只有在recrusive值为true时,一个非空目录以及其内容才会被删除(否则会抛出IOException异常)。

3.6  数据流

3.6.1  文件读取剖析

为了了解客户端及与之交互的HDFS、名称节点和数据节点之间的数据流是怎样的,我们可参考图3-1,其中显示了在读取文件时一些事件的主要顺序。

第三阶段:数据存储与计算(离线场景):3.2 数据存储hdfs_第8张图片

客户端通过调用FileSystem对象的open()来读取希望打开的文件,对于HDFS来说,这个对象是分布式文件系统(图3-1中的步骤1)的一个实例。DistributedFileystem通过使用RPC来调用名称节点,以确定文件开头部分的块的位置(步骤2)。对于每一个块,名称节点返回具有该块副本的数据节点地址。此外,这些数据节点根据它们与客户端的距离来排序(根据网络集群的拓扑;参见后文补充材料"网络拓扑与Hadoop")。如果该客户端本身就是一个数据节点(比如在一个MapReduce任务中),便从本地数据节点中读取。

Distributed FilesyStem返回一个FSData InputStream对象(一个支持文件定位的输入流)给客户端读取数据。FSData InputStream转而包装了一个DFSInputStream对象。

接着,客户端对这个输入流调用read()(步骤3)。存储着文件开头部分的块的数据节点地址的DFSInputStream随即与这些块最近的数据节点相连接。通过在数据流中重复调用read(),数据会从数据节点返回客户端(步骤4)。到达块的末端时,DFSInputStream会关闭与数据节点间的联系,然后为下一个块找到最佳的数据节点(步骤5)。客户端只需要读取一个连续的流,这些对于客户端来说都是透明的。

客户端从流中读取数据时,块是按照DFSInputStream打开与数据节点的新连接的顺序读取的。它也会调用名称节点来检索下一组需要的块的数据节点的位置。一旦客户端完成读取,就对文件系统数据输入流调用close()(步骤6)。

在读取的时候,如果客户端在与数据节点通信时遇到一个错误,那么它就会去尝试对这个块来说下一个最近的块。它也会记住那个故障的数据节点,以保证不会再对之后的块进行徒劳无益的尝试。客户端也会确认从数据节点发来的数据的校验和。如果发现一个损坏的块,它就会在客户端试图从别的数据节点中读取一个块的副本之前报告给名称节点。

这个设计的一个重点是,客户端直接联系数据节点去检索数据,并被名称节点指引到每个块中最好的数据节点。因为数据流动在此集群中是在所有数据节点分散进行的,所以这种设计能使HDFS可扩展到最大的并发客户端数量。同时,名称节点只不过是提供块位置请求(存储在内存中,因而非常高效),不是提供数据。否则如果客户端数量增长,名称节点会快速成为一个"瓶颈"。

网络拓扑与Hadoop

两个节点在一个本地网络中被称为"彼此的近邻"是什么意思?在高容量数据处理中,限制因素是我们在节点间传送数据的速率--带宽很稀缺。这个想法便是将两个节点间的带宽作为距离的衡量标准。

衡量节点间的带宽,实际上很难实现(它需要一个稳定的集群,并且在集群中成对的节点的数量的增长要是节点数量的平方),不及Hadoop采用一个简单的方法,把网络看作一棵树,两个节点间的距离是距离它们最近的共同祖先的总和。该树中的等级是没有被预先设定的,但是它对于相当于数据中心、框架和一直在运

行的节点的等级是共同的。这个想法是,对于以下每个场景,可用带宽依次减少:

相同节点中的进程

同一机架上的不同节点

同一数据中心的不同机架上的节点

不同数据中心的节点

例如,假设节点n1在数据中心d1中的机架r1上。这被表示成/d1/r1/n1。利用这种标记,这里给出四种描述的距离:

距离(/d1/r1/n1, /d1/r1/n1)=0(相同节点中的进程)

距离(/d1/r1/n1, /d1/r1/n2)=2(同一机架上的不同节点)

距离(/d1/r1/n1, /d1/r2/n3)=4(同一数据中心的不同机架上的节点)

距离(/d1/r1/n1, /d2/r3/n4)=6(不同数据中心的节点)

这在图3-2中用图示形式表达(数学爱好者会注意到这是一个距离公制的例子)。

第三阶段:数据存储与计算(离线场景):3.2 数据存储hdfs_第9张图片

 

我们必须意识到,Hadoop无法预测网络拓扑结构。不过在默认情况下,假设网络是平的(一个单层的等级制),或者换句话说,所有节点都在同一数据中心的同一机架。小的集群可能如此,所以不需要进一步的配置。

 

文件写入剖析

接下来我们要看文件是如何写入HDFS的。尽管比较详细,但对于理解数据流还是很有用的,因为它清楚地说明了HDFS的连贯模型(coherency model)。

我们考虑的情况是创建一个新的文件,向其写入数据后关闭该文件。

 

第三阶段:数据存储与计算(离线场景):3.2 数据存储hdfs_第10张图片

客户端通过在DistributedFilesystem中调用create()来创建文件(图3-3的步骤1)。DistributedFilesystem一个RPC去调用名称节点,在文件系统的命名空间中创建一个新的文件,没有块与之相联系(步骤2)。名称节点执行各种不同的检查以确保这个文件不会已经存在,并且客户端有可以创建文件的适当的许可。如果这些检查通过,名称节点就会生成一个新文件的记录;否则,文件创建失败并向客户端抛出一个IOException异常。分布式文件系统返回一个文件系统数据输出流,让客户端开始写入数据。就像读取事件一样,文件系统数据输出流控制一个DFSoutPutstream,负责处理数据节点和名称节点之间的通信。

在客户端写入数据时(步骤3),DFSoutPutstream将它分成一个个的包,写入内部的队列,称为数据队列。数据队列随数据流流动,数据流的责任是根据适合的数据节点的列表来要求这些节点为副本分配新的块。这个数据节点的列表形成一个管                    线-- 我们假设这个副本数是3,所以有3个节点在管线中。数据流将包分流给管线中第一个的数据节点,这个节点会存储包并且发送给管线中的第二个数据节点。同样地,第二个数据节点存储包并且传给管线中第三个(也是最后一个)数据节点(步骤4)。

DFSoutputStream也有一个内部的包队列来等待数据节点收到确认,称为确认队列。一个包只有在被管线中所有节点确认后才会被移出确认队列(步骤5)。

如果在有数据写入期间,数据节点发生故障,则会执行下面的操作,当然这对写入数据的客户端而言,是透明的。首先管线被关闭,确认队列中的任何包都会被添加回数据队列的前面,以确保数据节点从失败的节点处是顺流的,不会漏掉任意一个包。当前的块在正常工作的数据节点中被给予一个新的身份并联系名称节点,以便能在故障数据节点后期恢复时其中的部分数据块会被删除。故障数据节点会从管线中删除并且余下块的数据会被写入管线中的两个好的数据节点。名称节点注意到块副本不足时,会在另一个节点上安排创建一个副本。随后,后续的块会继续正常处理。

在一个块被写入期间多个数据节点发生故障的可能性虽然有但很少见。只要dfs.replication.min的副本(默认为1)被写入,写操作就是成功的,并且这个块会在集群中被异步复制,直到满足其目标副本数(dfs.replication的默认设置为3)。

客户端完成数据的写入后,就会在流中调用close()(步骤6)。在向名称节点发送完信息之前,此方法会将余下的所有包放入数据节点管线并等待确认(步骤7)。名称节点已经知道文件由哪些块组成(通过Data streamer询问块分配),所以它只需在返回成功前等待块进行最小量的复制。

副本的放置

名称节点如何选择哪个数据节点来保存副本?我们需要在可靠性与写入带宽和读取带宽之间进行权衡。例如,因为副本管线都在单独一个节点上运行,所以把所有副本都放在一个节点基本上不会损失写入带宽,但这并没有实现真的冗余(如果节点发生故障,那么该块中的数据会丢失)。同样,离架读取的带宽是很高的。另一个极端,把副本放在不同的数据中心会最大限度地增大冗余,但会以带宽为代价。即使在相同的数据中心(所有的Hadoop集群到目前为止都运行在同一个数据中心),也有许多不同的放置策略。其实,Hadoop在发布的0.17.0版中改变了放置策略来帮助保持块在集群间有相对平均的分布(第10章详细说明了如何保持集群的平衡)。

Hadoop的策略是在与客户端相同的节点上放置第一个副本(若客户端运行在

集群之外,就可以随机选择节点,不过系统会避免挑选那些太满或太忙的节点)。

第二个副本被放置在与第一个不同的随机选择的机架上(离架)。第三个副本被放置在与第二个相同的机架上,但放在不同的节点。更多的副本被放置在集群中的随机节点上,不过系统会尽量避免在相同的机架上放置太多的副本。

一旦选定副本放置的位置,就会生成一个管线,会考虑到网络拓扑。副本数为3的管道看起来如图3-4所示。

 

第三阶段:数据存储与计算(离线场景):3.2 数据存储hdfs_第11张图片

总的来说,这样的方法在稳定性(块存储在两个机架中)、写入带宽(写入操作只需要做一个单一网络转换)、读取性能(选择从两个机架中进行读取)和集群中块的分布(客户端只在本地机架写入一个块)之间,进行了较好的平衡。

 

Hadoop 2.x目录结构


第三阶段:数据存储与计算(离线场景):3.2 数据存储hdfs_第12张图片

bin

Hadoop最基本的管理脚本和使用脚本,这些脚本是sbin目录下管理脚本的基础实现,用户可以用这些脚本管理和使用hadoop

 

etc

hadoop配置文件所在的目录,包括core-site.xml、hdfs-site.xml、mapred-site.xml等从hadoop1.0继承而来的配置文件和yarn-site.xml等hadoop2.0新增的文件。.template是模板文件

 

include

对外提供的编程库头文件(具体动态库和静态库在lib目录中),这些头文件均是C++定义的,通常用于C++程序访问HDFS或者编写MR程序

 

lib

该目录提供了对外编程的静态库和动态库,与include目录下的头文件结合使用(并不是一些依赖jar包)

 

libexec

各个服务所对应的shell配置文件所在目录,可用于配置日志输出目录、启动参数(比如JVM参数)等基本信息

 

sbin

hadoop管理脚本所在目录,主要包括HDFS和YARN中各类服务的启动/关闭脚本

 

share

hadoop 各个模块编译后的jar包所在目录

 

 

1、$HADOOP_HOME/bin目录

文件名称

说明

hadoop

用于执行hadoop脚本命令,被hadoop-daemon.sh调用执行,也可以单独执行,一切命令的核心

hadoop-config.sh

Hadoop的配置文件

hadoop-daemon.sh

通过执行hadoop命令来启动/停止一个守护进程(daemon)

该命令会被bin目录下面所有以“start”或“stop”开头的所有命令调用来执行命令,hadoop-daemons.sh也是通过调用hadoop-daemon.sh来执行优命令的,而hadoop-daemon.sh本身由是通过调用hadoop命令来执行任务。

hadoop-daemons.sh

通过执行hadoop命令来启动/停止多个守护进程(daemons),它也是调用hadoop-daemon.sh来完成的。

rcc

The Hadoop record compiler

slaves.sh

该命令用于向所有的slave机器上发送执行命令

start-all.sh

全部启动,它会调用start-dfs.shstart-mapred.sh

start-balancer.sh

启动balancer

start-dfs.sh

启动NamenodeDatanodeSecondaryNamenode

start-jobhistoryserver.sh

启动Hadoop任务历史守护线程,在需要执行历史服务的机器上执行该命令。

原文:

Start hadoop job history daemons.  Run this on node where history server need to run

start-mapred.sh

启动MapReduce

stop-all.sh

全部停止,它会调用stop-dfs.shstop-mapred.sh

stop-balancer.sh

停止balancer

stop-dfs.sh

停止NamenodeDatanodeSecondaryNamenode

stop-jobhistoryserver.sh

停止Hadoop任务历史守护线程

stop-mapred.sh

停止MapReduce

task-controller

任务控制器,这不是一个文本文件,没有被bin下面的shell调用

 

2、$HADOOP_HOME/conf目录

文件名称

说明

capacity-scheduler.xml

 

configuration.xsl

 

core-site.xml

Hadoop核心全局配置文件,可以其它配置文件中引用该文件中定义的属性,如在hdfs-site.xmlmapred-site.xml中会引用该文件的属性。

该文件的模板文件存在于$HADOOP_HOME/src/core/core-default.xml,可将模板文件拷贝到conf目录,再进行修改。

fair-scheduler.xml

 

hadoop-env.sh

Hadoop环境变量

hadoop-metrics2.properties

 

hadoop-policy.xml

 

hdfs-site.xml

HDFS配置文件,该模板的属性继承于core-site.xml。

该文件的模板文件存在于$HADOOP_HOME/src/hdfs/hdfs-default.xml可将模板文件拷贝到conf目录,再进行修改。

log4j.properties

Log4j的日志属于文件

mapred-queue-acls.xml

MapReduce的队列

mapred-site.xml

MapReduce的配置文件,该模板的属性继承于core-site.xml。

该文件的模板文件存在于$HADOOP_HOME/src/mapred/mapredd-default.xml可将模板文件拷贝到conf目录,再进行修改。

masters

用于设置所有secondaryNameNode的名称或IP,每一行存放一个。如果是名称,那么设置的secondaryNameNode名称必须在/etc/hostsip映射配置。

slaves

用于设置所有slave的名称或IP,每一行存放一个。如果是名称,那么设置的slave名称必须在/etc/hostsip映射配置。

ssl-client.xml.example

 

ssl-server.xml.example

 

taskcontroller.cfg

 

task-log4j.properties

 

 

 

 

 

 

 

Hadoop项目主要包括以下四个模块

 Hadoop Common:

  为其他Hadoop模块提供基础设施。

 Hadoop DFS:

   一个高可靠、高吞吐量的分布式文件系统

 Hadoop MapReduce:

   一个分布式的离线并行计算框架

 Hadoop YARN:

  一个新的MapReduce框架,任务调度与资源管理

 

 

总的来说Hadoop适合应用于大数据存储和大数据分析的应用,适合于服务器几千台到几万台的集群运行,支持PB级的存储容量。

Hadoop典型应用有:搜索、日志处理、推荐系统、数据分析、视频图像分析、数据保存等。


 

你可能感兴趣的:(数据科学入门到精通)