JHU-Hadoop-笔记-全-
JHU Hadoop 笔记(全)
001:大数据革命导论 🚀

在本节课中,我们将学习大数据革命背后的核心趋势与概念。我们将探讨数据爆炸的原因、数据经济的重要性、大数据分析与传统分析的区别,以及数据科学这一新兴领域。同时,我们也会了解传统数据处理技术的局限性,以及以Hadoop为代表的现代数据处理技术如何使大数据解决方案成为可能。
数据爆炸 💥
上一节我们介绍了课程概述,本节中我们来看看数据爆炸现象。
为什么市场上会产生如此多的数据?以下是主要原因:
- 数字化:几乎所有事物都在被数字化,从书籍、音乐到商业交易和社交互动。
- 物联网:传感器和设备持续生成海量数据。
- 社交媒体:数十亿用户每天都在创造内容、发布更新和进行互动。
- 移动计算:智能手机和平板电脑无处不在,成为重要的数据生成器。

数据经济 💰
理解了数据爆炸的现象后,本节我们将探讨数据经济。
理解这种新经济形态至关重要,因为它代表了信息技术的未来。数据已成为一种关键的战略资产和新的“石油”,能够驱动创新、创造价值并形成竞争优势。
大数据分析 📊
认识了数据经济的重要性,接下来我们看看大数据分析。
大数据分析与传统数据分析有何不同?以下是关键区别:
- 数据规模:传统分析处理GB/TB级数据,而大数据分析处理PB/EB级数据。
- 数据类型:传统分析主要处理结构化数据(如数据库表格),大数据分析则处理结构化、半结构化和非结构化数据(如文本、图像、日志)。
- 处理速度:大数据分析通常要求实时或近实时处理,而传统分析多为批量处理。
- 价值密度:大数据中蕴含价值的密度往往较低,需要从海量数据中挖掘洞察。
数据科学 🔬
了解了大数据分析的特点,本节我们进入数据科学这一新兴领域。
数据科学是什么?成为一名数据科学家意味着什么?数据科学是一个跨学科领域,它结合了统计学、计算机科学和领域专业知识,旨在从数据中提取知识和洞察。数据科学家的核心工作流程通常遵循一个循环:
# 数据科学工作流程示例(概念性描述)
1. 问题定义与数据收集
2. 数据清洗与预处理
3. 探索性数据分析与可视化
4. 模型构建与机器学习
5. 模型评估与解释
6. 部署与沟通结果
传统数据处理技术的局限 ⚠️
在探讨了数据科学之后,我们需要理解为什么需要新的技术。本节我们审视历史数据处理技术。
为什么传统的数据处理技术(如关系型数据库管理系统)无法有效处理大数据?主要局限如下:
- 可扩展性:传统系统难以在成本可控的情况下横向扩展(增加更多机器)。
- 架构:它们通常采用共享一切的架构,存在单点故障和性能瓶颈。
- 成本:商用硬件和软件许可成本高昂。
- 数据类型:不擅长处理非结构化和半结构化数据。
现代数据处理技术与趋势 🛠️
认识到传统技术的不足,本节我们来看看使大数据解决方案成为可能的现代技术与趋势。
以Hadoop为代表的现代数据处理技术有哪些关键趋势?以下是主要驱动力:
- 横向扩展架构:采用无共享架构,通过增加廉价商用服务器来扩展,而非升级单一昂贵机器。其核心思想可概括为:
处理能力 ∝ 节点数量 - 开源软件:如Hadoop生态系统,降低了技术采用成本。
- 商用硬件:使用低成本、标准化的硬件。
- 容错性:软件层设计能够处理节点故障,确保系统高可用。
- 数据本地化计算:将计算任务移至数据所在节点,减少网络传输开销。

本节课总结

在本节课中,我们一起学习了大数据革命的导论。我们探讨了数据爆炸的成因,认识了数据作为新经济驱动力的重要性,区分了大数据分析与传统分析,了解了数据科学家的角色与工作。更重要的是,我们分析了传统数据处理技术在面对大数据挑战时的局限性,并深入了解了以横向扩展、开源和容错为特征的现代数据处理技术趋势,这些趋势正是Hadoop等大数据解决方案得以实现的基础。
002:数据爆炸 📊

在本节课中,我们将要学习“数据爆炸”这一概念。我们将探讨数据为何以及如何以惊人的速度增长,并比较过去与现在在用户、应用和基础设施方面的差异,以理解数据爆炸背后的原因。
数据无处不在

过去五年,除非你与世隔绝,否则你一定听说过“数据”这个词,更准确地说,是“大数据”。科学杂志、时代杂志和经济学家杂志都在讨论数据。它们真正想说的是,像谷歌、脸书、推特和领英这样的公司,正在收集关于你、你的公司、你的想法的数据,并将其存储在服务器端,以有意义的方式提供给他人,并在此过程中赚取大量利润,而你的隐私则受到了影响。
甚至还有一个概念叫“数据节食”,其核心是从市场产生的海量数据中,丢弃不需要的部分,保留所需部分,从而提取出最重要的信息。
每分钟的数据生成量
以下是展示当前每分钟生成数据量的可视化图表。请暂停视频,以便更仔细地查看此图表。
让我们仔细看看这个图表:
- YouTube:每分钟上传48小时的视频。这意味着每7到10天,上传的视频量就相当于约100年的内容。
- 新网站:每分钟生成571个新网站。
- Flickr:用户每分钟添加3125张新照片。
- Instagram:用户每分钟分享3600张新照片。
- Tumblr:用户每分钟生成27778条新帖子。
由此可见,每分钟都在生成大量数据,所有这些数据都可能需要被处理和分析。
数据爆炸的原因:1975年与今天的对比
为什么市场上会产生如此多的数据?为了理解这一点,我们将从用户、应用和基础设施三个方面,比较1975年与今天的差异。
上一节我们看到了数据生成的惊人速度,本节中我们来看看背后的原因。
1. 用户
- 1975年:应用为大约2000名用户构建。例如,当时为AT&T构建的应用仅供其员工使用,用户群体是静态的。
- 今天:2000用户只是一个起点。应用发布到互联网后,用户可能来自你的城市、州、国家(如美国),进而扩展到整个北美、南美、欧洲和亚洲。你面对的是呈指数级增长的、来自全球的动态在线用户。
2. 应用
- 1975年:主要进行业务流程自动化,用户是公司内部员工(如银行职员)。
- 今天:仍在进行业务流程自动化,但用户变成了日常用户。例如,美国银行现在构建的应用供你我使用,以便进行电话银行、手机银行等操作,这些都在生成数据。
3. 数据类型
- 1975年:处理高度结构化的数据记录,通常存储在数据库中。
- 今天:处理结构化数据、半结构化数据(如XML、JSON)和非结构化数据(如音频文件、视频文件、PDF文档)。我们将在后面详细讨论这些数据类型。
4. 基础设施
- 1975年:数据网络处于起步阶段;计算以大型机或小型机为中心的集中式计算为主。
- 今天:拥有高速通用数据网络;智能手机可以高速连接互联网;计算是分布式的。你的手机本身就是一台连接服务器的客户端计算机,它本身就在生成大量数据。
正是由于用户基础、应用类型和基础设施的这些根本性变化,导致了数据的爆炸式增长。
企业中的数据
现在,让我们看看企业环境中的数据。在企业中,你通常不只有一个数据库。
以下是企业内常见的数据源:
- 多个OLTP数据库:公司可能为其提供的各种产品和服务设置多个在线事务处理数据库。
- 用户生成内容:在像脸书和推特这样的社交网络或博客网站,用户自己生成数据,如博客、推文、上传的视频和音频文件。
- 日志文件:企业和互联网规模的应用(Web级应用)可能在全世界不同地区拥有多台服务器,这些服务器会生成日志文件(如访问日志)。所有这些日志文件都需要被汇总和处理。
- 系统生成数据:企业内部的许多服务会生成需要处理的系统日志文件。例如,威瑞信公司每天处理约650亿次DNS查询,所有这些查询都会被记录并在当天结束时进行处理,以分析请求量和来源。
个人计算中的数据
最后,我们来看看个人计算中的数据。我将比较1984年我的个人电脑和今天的电脑。

以下是1984年与今天个人电脑关键指标的对比:
- CPU速度:从 1 MHz 提升到 3 GHz(增长约3000倍)。
- 内存:从 256 KB 提升到 4 GB(增长约15000倍)。
- 传输速率:从 30 字节/秒提升到 1 MB/秒(增长约30000倍)。
- 硬盘容量:从 1 MB 提升到 1 TB(增长约100万倍)。
这些数据清晰地表明,即使在你个人的生活中,也在生成大量数据。你拥有自己的文档、视频和图片。存储容量的增长幅度最大,这正好满足了存储这些爆炸性增长的数据的需求。
数据量的冰山
是的,数据量正在不断增长。我将数据量比作一座冰山,因为如果你观察一座冰山,它非常庞大,但有趣的是,冰山的大部分质量都隐藏在底部。数据领域正在发生同样的事情:有大量数据正在涌入。

我想强调的是,这仅仅是个开始。数据量正在增长,并且随着时间推移,将以指数级速度持续增长。

总结

本节课中,我们一起学习了“数据爆炸”现象。我们了解了数据正以惊人的速度生成,并通过对比1975年与今天在用户、应用和基础设施方面的变化,深入探讨了数据爆炸的根本原因。我们还查看了企业和个人层面数据增长的例子,并认识到当前的数据增长趋势仅仅是一个开始,未来数据量将继续呈指数级扩张。理解数据爆炸是学习大数据处理技术的重要第一步。
003:数据经济 📈


在本节课中,我们将探讨计算行业的发展历程,理解“数据经济”的兴起,并认识数据驱动型组织如何运作。我们将看到,数据已成为当今商业世界的核心驱动力。
计算行业的三个时代
为了解释数据经济的概念,我们可以将计算行业的时间线划分为三个主要时代。
硬件为王时代(1950s-1960s)
在20世纪50年代和60年代,硬件是市场的核心。像IBM、惠普(H)、数字设备公司(DEC)这样的企业是当时的王者。它们制造并销售大型机给AT&T、MCI等大型企业,以及INS、IRS等政府机构,并因此获得了巨额利润。对于IBM这样的公司而言,硬件至关重要,以至于在最初构建IBM个人电脑时,他们并未投入太多精力,甚至没有为其构建操作系统。对他们来说,软件只是销售硬件的一种手段。

软件为王时代(1980s)
随后,市场发生了转变。当时一位名不见经传的年轻人比尔·盖茨为IBM PC构建了操作系统,这彻底改变了市场格局。进入80年代,软件成为了新的王者。每个人都开始为IBM PC及其兼容机、为微软Windows平台开发应用程序。许多创业者开始创建初创公司,开发能在PC平台上销售的小型应用软件。大学也开始设立并发展一个全新的领域——计算机科学。
数据为王时代(1990s至今)
90年代,有趣的事情发生了,那就是万维网的出现。很快,每个人都开始转向网络。大量的产品和服务开始通过网络提供。突然间,数据成为了新的王者。尤其是进入21世纪初,像谷歌、Facebook、Twitter这样的公司开始崛起并取得巨大成功,而它们成功的基石正是数据。数据是新的王者。
什么是数据驱动型组织?

上一节我们回顾了计算行业的演变,本节中我们来看看数据驱动型组织的定义。

一个数据驱动型组织,是指能够及时地获取数据、处理数据并利用数据,以创造效率、迭代并开发新产品和服务,从而在商业竞争中导航的组织。
我可以举一个亚马逊的例子。Amazon.com是一个互联网零售商店,但实际上它在获取数据。当你在亚马逊浏览不同商品时,它会记录你查看了哪些产品、购买了哪些产品。基于这些数据,它能够向你推荐商品。它知道购买了某件商品的人也可能购买另一件商品,因此会向你推荐该商品。你可能将某物加入了愿望清单,或浏览了某个产品,这都让亚马逊知道你对这些产品感兴趣。因此,他们会不时地给你发送电子邮件、优惠券,采取各种措施吸引你在线购物。他们实际上是在有效地利用数据来改善业务。
数据驱动型组织的分类



理解了数据驱动型组织的定义后,我们可以将其进一步分类。
数据驱动型组织可以分为两类:纯粹的数据驱动型组织,以及利用数据增强业务的组织。
以下是纯粹的数据驱动型组织:
- 像谷歌、Facebook、LinkedIn、Twitter、百利(Biley)这样的公司,它们的产品完全围绕数据构建。
以下是利用数据增强业务的组织:
- 如前所述的Amazon.com。
- Netflix是另一个例子。Netflix从事流媒体视频业务,但它也记录你观看了哪些视频、浏览了哪些视频。它知道谁看了什么,以及可能向你推荐什么视频。它拥有推荐引擎,目的是与你保持互动,使你持续成为其客户。
- 同样,Orbitz(旅游预订)、Travelers(保险)、NASA等不同组织,都在尝试利用数据来改善它们自身及客户的业务。

大数据是大生意
既然数据如此重要,处理海量数据的大数据自然成为了大生意,每个人都为此感到兴奋。
在基础设施即服务层面:
- 英特尔、IBM、惠普、戴尔、甲骨文等大数据硬件制造商非常兴奋,因为他们可以销售CPU、存储等硬件。
- 大数据公共云平台供应商也很兴奋,因为你可以按需使用这些云服务处理数据,例如每六个月或每月处理一次,完成后即可离开。像谷歌、亚马逊云服务、微软Azure等公司对此都非常感兴趣。
- Cloudera、Hortonworks、DataStax等云平台软件公司也很兴奋。Cloudera为Hadoop提供支持,DataStax为Cassandra等NoSQL解决方案提供支持。处理大数据需要大数据解决方案,而这些公司正为此提供支持平台。

在平台即服务层面:
- 许多大数据商业智能软件供应商、数据分析软件供应商对此感到兴奋,例如Tableau、Aster Data、Datameer、Cloudera、Splunk,他们都希望销售自己的产品。
在数据即服务层面:
- 邓白氏公司对此感到兴奋。邓白氏拥有市场上每家公司的信息,他们可以出售这些信息。
- Infochimps(出售Twitter流数据)、Gov 2.0(开放政府数据的社区)、路透社、SimpleGeo等众多数据聚合公司也对大数据感到兴奋,因为它们可以在此过程中获利。
信息科学影响各行各业
大数据和信息科学正在影响每一个行业。
以下是受影响的行业示例:
- 生物技术行业。
- 语言学行业。
- 采矿业。
- 金融业。
- 新闻业(出现了非常流行的数据新闻学)。
- 甚至教育行业也受到影响。可以说,几乎每个行业都受到信息科学的影响。
我想花几分钟谈谈语言学行业。麻省理工学院有一位教授正在进行语言学研究。他记录了自己孩子从出生第5天带回家直到5岁期间的每一秒生活。这大约是每天18小时的视频。然后,他通过大数据处理平台处理这些数据来研究语言学。他弄清了孩子是如何学习语言的,例如如何从最初说“baba”逐渐变化到说“wawa”,最终说出“water”这个词。通过记录孩子生活的每一分钟并进行视频和音频处理,他能够进行纯粹的语言学分析。研究人员不可能坐下来观看所有视频并进行分析。他正是通过大数据处理技术做到了这一点。这就是语言学行业如何受到大数据处理影响的例证。
总结:我们身处数据经济时代
本节课中,我们一起学习了计算行业从硬件、软件到数据的演变历程,定义了数据驱动型组织并了解了其分类,认识到了大数据带来的巨大商业机遇,以及信息科学对各行业的深远影响。
醒来吧,这就是数据经济。我们实际上正处在信息科学形成的进程中。不久前,数据还难以捕获且存储成本高昂,但现在情况已不再如此。那时数据量不大,而现在数据量巨大。数据曾是人类许多努力的瓶颈,但现在我们收集和处理有价值数据的能力已没有限制。我们不再受限于数据,而是受限于洞察力。因此,那些能够处理数据、分析数据的人将会创造大量财富。我们正身处数据经济时代。
004:大数据分析 📊

在本节课中,我们将正式定义数据、大数据和传统数据分析,并讨论大数据分析与传统数据分析有何不同。
什么是数据?
虽然你可能已经接触数据一段时间了,但我们还是先看看韦氏词典的定义,以理解它在现代世界中的意义。
第一个定义是:用作推理、讨论或计算基础的测量值或统计数据等事实信息。关键在于“事实信息”。当你想到数据时,可以联想到数据库,其中存储的客户信息、产品信息、销售信息、用户信息等,都是存储在数据库中的事实信息。
第二个定义是:由传感设备或器官输出的信息,其中包含有用、无关或冗余的信息,必须经过处理才能变得有意义。这个定义有些复杂,我们需要拆解一下。它指出,由传感设备输出的重要信息,包含有用、无关或冗余的内容,必须经过处理才有意义。
理解这个概念的最佳方式是通过一个例子。例如,我提到我在威瑞信工作,我们每天收到约650亿次查询,请求返回域名的IP地址。我们在全球各地都有DNS服务器处理这些请求,你可以将这些服务器视为传感设备,它们接收请求并向客户端发送响应。所有这些请求都会被记录,我们处理这些日志。这些日志对威瑞信来说包含有用信息,因为我们可以分析哪些域名受欢迎,以及谁在查询这些域名。从某些角度看,这些信息可能无关紧要,因为我们并未直接从中获利;也可能显得冗余,因为热门网站每隔几秒就会被频繁查询其IP地址。但关键在于,所有这些数据必须经过处理才能变得有意义,这正是我们所做的——对数据进行精确的大数据分析,以理解这些信息。
第三个定义是:可以数字传输或处理的数字形式信息。这意味着数据以某种数字形式存储,必须被传输或处理。因此,数据是存储、传输和处理的对象。当你想到这一点时,可以将其视为处理所有这些特定数据的计算集群或分布式计算环境。
什么是大数据?📈
现在,让我们正式定义大数据。大数据是指其规模、分布、多样性和/或时效性要求使用新的技术架构和分析方法,以获取能够解锁新业务价值来源的洞察。这同样是一个复杂的定义,让我们仔细分析一下。

它指出,大数据的规模很重要,多样性或种类在大数据中也变得非常重要。另一个关键点是需要使用新的技术架构,这明确表明现有的数据分析技术将无法胜任。我们需要新的技术架构来处理数据并解锁新的业务价值来源。这就是大数据的真正定义。其中的关键点包括:需要新的数据架构、新的工具和新的分析方法。在本课程中,你将反复看到这些内容。
大数据的特征(3V)🔍
以下是Gartner集团定义的大数据特征,也称为大数据的三个“V”。

第一个特征是体量。我们已经看到数据量正在增长。2010年,预计数据量为1.2泽字节,到2020年预计将增长到35泽字节。因此,体量是大数据处理和大数据定义中一个非常重要的特征。
大数据的第二个重要特征是速度。速度是指数据被摄取的速度,以及你需要处理这些数据的速度。
让我举个例子。在威瑞信,我们平均每天收到650亿次查询,高峰时段每秒约100万次查询。但在遭受攻击时,我们每天可能收到3000万到6000万次查询。有时,我们的DNS系统会遭到黑客的分布式拒绝服务攻击,查询量会激增。这就是峰值数据速度的例子,我们需要处理这些特定数据。
构成大数据的第三个特征是多样性。多样性是指不同类型的数据。不同类型可以指数据格式,例如,可以是结构化、半结构化或准结构化的数据,如JSON数据、XML数据、传统的OLTP结构化数据等。但多样性也体现在数据来源上,你可能拥有Twitter数据、注册数据、博客数据、流量数据,并希望将这些数据整合在一起。因此,大数据的三大特征再次是:体量、速度和多样性。
数据量级与来源示例 💾
再次强调,据估计,到2010年我们拥有1.2泽字节的数据,1泽字节等于1万亿千兆字节。预计到2020年,全球将看到约35泽字节的数据。市场上涌入大量数据,所有这些都需要被处理和理解,这正是数据经济的核心。

以下是一些持续生成大量数据的公司示例:
- Facebook:每天60亿条消息。
- eBay:每天20亿次页面浏览量,产生约9拍字节的存储。
- Skybox Imaging的卫星图像:每天1太字节数据。
由此可见,数字世界中正在生成大量数据。
从数据结构角度看大数据特征 🗂️
在处理大数据时,我们需要处理多种类型的数据。
第一种是结构化数据。结构化数据是指具有明确定义的数据类型、格式和结构的数据,易于解析。结构化数据的典型例子来自OLTP数据库或OLAP系统。
第二种是半结构化数据。半结构化数据是具有可识别模式的文本数据,意味着它可以被处理。半结构化数据的例子包括XML数据文件或JSON文件。它们可能是自描述的,或者可能有关联的模式。
第三种是准结构化数据。准结构化数据是具有不规则数据格式的文本数据。例如,网络点击流数据。每次你在网页上点击一个项目,都会生成点击流访问日志。所有这些数据被收集后进行处理。想象一下,如果你的公司在世界各地拥有许多网络服务器,那么所有这些点击流数据必须被汇集在一起。东海岸、西海岸和欧洲的数据,它们的访问日志可能有时区差异,可能存在冗余或错误,你可能需要进行一些转换。这就是我们所说的准结构化数据。
第四种是非结构化数据。非结构化数据是指没有固有结构但存储在文件中的数据,例如纯文本文档、PDF、图像、视频等。因此,在处理大数据时,你必须应对所有这些不同类型的数据结构。
数据分析的业务驱动力 🚀
许多业务问题为组织提供了变得更加数据驱动和分析化的机会。
例如,一个驱动力可能是优化业务运营的愿望。组织可能希望查看销售数据、定价数据、盈利能力数据,并开始影响这些方面。例如,亚马逊、沃尔玛可能希望查看其网站和零售店的所有这些不同数据,以找出如何影响销售、定价和盈利能力等。
也可能是识别业务风险的愿望。例如,保险公司和银行可能希望查明是否存在欺诈行为,或者客户流失情况。例如,Netflix曾想了解他们为何失去客户。因此,通过查看数据来管理业务风险是一个重要驱动力。
还有预测新的商业机会。组织可能希望进行向上销售或交叉销售,甚至可能想在某个地方开设新店。为此,他们必须查看数据并尝试理解其含义。
遵守法律或监管要求。银行和金融机构可能需要生成报告,表明不存在洗钱行为,或存在公平的贷款实践。因此,他们需要整理、处理数据并向政府机构提交报告。
这些都是进行数据分析的不同原因。
传统数据分析与大数据分析的区别 ⚖️
理解这一点非常重要,因为在组织中,你通常已经有一个团队使用传统数据仓库进行数据分析,他们经常会问大数据分析与数据分析有何不同。让我们来看看。
在传统数据分析中,你通常处理的是干净的数据,而在大数据分析中,虽然也可能处理干净数据,但更常处理的是杂乱、有噪声的数据。这正是准结构化数据、点击流数据开始发挥作用的地方。
在传统分析中,你通常处理的是太字节级的数据,而在大数据分析中,你处理的是拍字节级的数据,数据量巨大。
在传统分析中,你通常提前知道业务将要提出的问题,因此你围绕他们提出的问题来设计你的BI或数据仓库解决方案。而在大数据分析中,你常常不知道他们会问什么问题。因此,在设计时,你只需将数据存储在平面文件中,不采用特定的范式,然后在需要时进行处理。这是两种不同的方法。
在传统数据分析中,其架构不支持高计算量。你更感兴趣的是以结构化、规范化的格式存储数据,以便进行查询,但无法在其上进行大量计算。主要是获取数据并将其返回给用户,可能附带一些报告。而在大数据分析中,你需要一个支持分布式存储和高计算量的环境,例如可能需要使用Hadoop这样的技术。
通常,在传统数据分析中,答案是事实性的。而在大数据分析中,答案可能是概率性的。例如,对于汽车保险公司,某个特定客户撞车的概率是多少?我们在这里寻找的是概率性的答案。
在传统数据分析中,你处理的是结构化数据。而在大数据分析中,正如我们之前所学,我们处理结构化和非结构化数据。
在传统分析中,你通常处理一到两个业务领域的数据。而在大数据分析中,你可能需要处理数十个领域的数据集。以威瑞信为例,我进行大数据处理,涉及互联网域名领域。为此,我需要理解DNS、EPP域名注册、网站创建和利用方式、内容下载、HTTP协议等。无论你从事哪个行业,如生物技术、金融或制造业,为了进行大数据分析,你可能需要处理多个领域的数据集。
从多维度看传统与大数据分析对比 📊
现在,让我们从硬件成本、扩展性、加载、报告分析、数据架构、敏捷性和风险等角度,再次审视传统数据分析与大数据分析的区别。
在传统数据分析中,通常使用专有硬件,而在大数据分析中,使用商用硬件。这意味着在传统分析中,成本更高,因为你处理的是专有硬件,有时甚至是专有软件。而在大数据分析中,成本较低,因为商用硬件更便宜,软件方面也常使用开源软件。
在传统数据分析中,通常采用纵向扩展,这种方式昂贵且难以通过添加更多系统来扩展。而在大数据分析中,你使用支持横向扩展的系统,这种方式更好。
在传统分析中,你处理的是批处理,对于大数据来说可能非常慢。而在大数据分析中,我们处理批处理和实时处理,并努力使其更快,可以通过添加更多CPU、内存和机器来实现。
在传统数据分析中,我们进行汇总。而在大数据分析中,我们保留所有原始数据,以便随时进行深入分析。

在传统分析中,通常侧重于业务的运营方面。而在大数据分析中,我们处理运营改进,也希望理解历史增长和变化,预测未来,了解行业将如何变化。因此,它也涉及预测或推荐方面。
在传统分析中,如前所述,我们处理结构化数据。在大数据分析中,我们处理结构化和非结构化数据。
在架构方面,传统数据分析通常使用来自供应商的物理硬件,非常专有。而在大数据分析中,你可以使用物理硬件,也可以使用虚拟硬件,例如云环境。
在传统数据分析中,你进行的是被动报告。而在大数据分析中,你是主动的,希望感知并响应,因此存在预测和推荐的成分。
传统数据分析风险稍高,因为你实际上是在向供应商投入资金,购买他们的产品和软件。而在大数据分析中,由于使用低成本商用服务器,成本更低,因此在大数据分析方面,它无疑是一个成本更低的替代方案。
组织内的数据分析生态 🌐
我不想说传统数据分析和传统数据仓库已经过时或消亡。它们必须存在,而大数据分析这一新领域(也称为数据科学,我将在下一节详细讨论)也必须存在。因此,在组织的数据分析生态中,过去我们进行数据仓库和商业智能,未来我们将继续这样做。但未来的趋势似乎指向一个名为“数据科学”的领域。
在数据仓库和商业智能领域,典型的技术和数据类型是针对结构化数据的标准即席报告和警报。典型的问题围绕“上个季度发生了什么?”——这是关于过去的。例如,“我们卖出了多少小部件?”或“问题出在哪里?”。
而在数据科学领域,典型的技术和数据类型是优化、预测建模、预测等,并且你处理结构化和非结构化数据。常见的问题不是“上个季度发生了什么?”,而是“如果我们这样做会怎样?”、“我们能推荐这个吗?”、“如果我们采取这些措施可能会发生什么?”、“最优方案是什么?”。因此,它更侧重于预测方面。
Gartner有一句名言:“数据是21世纪的石油。”我想稍作修改:数据是21世纪的原油。你必须处理这些特定的数据才能得到石油。这就像获取原油并提炼出石油一样,我们获取数据,处理它,并从中提取信息。

总结 📝

在本节关于大数据分析的内容中,我们定义了数据,定义了大数据的特征,也讨论了传统数据分析,并探讨了它与大数据分析的不同之处。
005:数据科学 🧠

在本节课程中,我们将探讨新兴的数据科学领域。我们将了解数据科学家需要具备哪些特质,以及他们完成工作所使用的工具和技术。虽然这不是一门专门的数据科学课程,但作为Hadoop开发者,你可能会与数据科学家紧密合作,甚至可能自己立志成为一名数据科学家。
什么是数据科学?
上一节我们介绍了课程背景,本节中我们来看看数据科学的定义。

数据科学有一个正式的定义:“巧妙地运用多种数据元素,以迭代方式解决数据问题,从而共同实现原本难以达成的商业目标。” 这个定义有些复杂,让我们来分解一下:
- 运用多种数据元素:数据科学家在数据科学领域,运用直觉巧妙地处理多种数据元素。
- 以迭代方式解决数据问题:他们以迭代的方式解决数据问题。
- 实现商业目标:他们与业务部门紧密合作,以实现共同的商业目标。
- 解决棘手问题:这正是数据科学洞察力发挥作用的地方。
这是数据科学的正式定义。我再给你另一个定义,一个我喜欢的、来自德鲁·康威的定义。
德鲁·康威认为,数据科学领域实际上是三件事的结合:
- 黑客技能:即编程能力。
- 领域专业知识:在特定行业(如生物学、金融、教育、制造业)的专业知识。
- 数学和统计学知识。
这里的观点是:
- 如果你拥有数学统计知识,并能将其与领域专业知识结合,你得到的是传统研究。人们一直在这样做。
- 如果你能将黑客技能(编程技能)与领域专业知识结合,德鲁·康威称之为危险区。换句话说,仅仅因为你会编程并且拥有特定领域的专业知识,并不真正使你成为数据科学家。
- 如果你能将黑客技能(编程技能)与数学和统计学知识结合,那么你可以进行机器学习。
- 根据德鲁·康威的说法,如果你能结合所有这三种技能——编程技能、领域专业知识,再加上数学和统计学知识——那才是数据科学领域。


数据科学家需要什么?

了解了数据科学的定义后,我们来看看成为一名数据科学家需要掌握哪些工具和技能。
我想从数据科学家所使用的工具角度来定义数据科学家。
数据科学家应该是一个能使用多种语言工作的人。他们懂很多语言,可能不精通所有语言,但懂很多,比如可以用Java、Python编程,懂一点Shell脚本编程,一点Perl编程。
他们还需要理解分布式计算,如Hadoop、HDFS、MapReduce(这是你将在本课程中学到的技能)。

他们还应该能够使用像Pig和Hive这样的4GL语言(这也是你将在本课程中学到的,它们是Hadoop相关产品)。R是另一种数据科学语言(我们不会在本课程中学习它),还有SQL(你们很多人可能已经知道)。
他们还应该能够进行机器学习,至少学会使用像Mahout和Weka这样的机器学习算法。
他们还应该理解传统的数据仓库和传统分析,如Oracle数据仓库。
他们还应该能够通过视觉讲故事,因此应该学会使用可视化工具,如Gephi、Highcharts、D3.js,甚至是Matplotlib。

如果你能结合所有这些不同的技能,那么你就可以成为一名数据科学家。完成本课程后,你将至少掌握其中50%的技能,所以你实际上正在向数据科学领域迈进。如果你想,未来可以成为一名数据科学家。
那么,是什么造就了数据科学家?数据科学家应该是充满好奇心的人,拥有直觉。这是两种非技术技能,它们非常关键。其余的技能,如数据收集与标准化、统计学、建模、可视化,则更多是技术技能。当你结合所有这些技能时,你就成为了一名数据科学家。

如何成为数据科学家?
你可能会问,我如何成为一名数据科学家?这里有一个非常好的网站资源,你可能想去看看。但我已经从那篇文章中总结了一些要点。
如果你想成为一名数据科学家,你需要了解分布式计算(你将在本课程中学到)。你还需要理解矩阵分解、统计分析、优化、机器学习(这些内容我们不会在本课程中讨论,但约翰霍普金斯大学有其他课程涵盖这些主题)。此外,还有信息检索、信号检测、掌握算法和数据结构。你需要了解所有这些不同的事情,才能称自己为数据科学家。
数据科学家的关键任务
明确了所需技能后,我们来看看数据科学家必须完成的一些关键任务。
数据科学家为自己和他人必须做的最重要的任务之一是记录数据集。
数据科学家经常处理数据,而且经常处理他人的数据。因此,作为数据科学家,记录数据集非常重要。每个数据集都应该有:
- 标题
- 摘要
- 数据来源:这些数据的原始所有者是谁
- 数据类型:它是什么类型的数据?是多变量数据、关系数据、时间序列数据吗?
- 数据格式
- 数据结构:它是如何存储的?
- 数据特征:例如,数据是如何收集的?何时收集的?是否存在与此数据相关的系统性偏差?
- 数据访问:它是敏感数据吗?数据访问政策是什么?
所有这些都需要为你自己和他人记录下来。在本课程中,我们将使用MovieLens数据,这些数据记录得非常完善,你在做作业时会查看这些文档来理解MovieLens数据。
数据科学家的另一个重要任务是记录数据科学测试。
因为一旦你记录下来,其他人就可以利用你所做的结果在此基础上继续工作,或者可能希望以略有不同的方式进行实验。因此,有很多理由需要你记录数据科学任务。对于你做的每一个数据科学任务,你必须记录:
- 使用的数据(可能不止一个)
- 执行的任务
- 问题描述
- 结果
将这些内容写入文档,以便他人使用。
从数据到智慧
数据科学的一个关键方面是从数据走向智慧。
假设你有一个网站,用户与这个网站互动,你获得了点击流数据。然后你解析这些数据以获得规范化数据,这就是我们所说的信息。你可以利用这些信息,运行报告来生成知识或获取知识。
但是,如果你能利用这些知识,获得洞察,并据此采取行动呢?这就是我们所说的智慧。
所以我想说的是,通过数据科学,你经常从原始数据开始,一路走向智慧。

让我用一个真实场景来解释这一点。在Netflix,客户来到网站,浏览电影,观看电影,所有这些互动数据都被收集起来,这就是规范化数据。Netflix利用这些数据生成报告,从而获得了知识:哪些用户在观看电影,他们看什么电影,哪些电影受欢迎等等。
在查看这些数据时,他们获得了一个洞察:观看电影的用户是良好的长期客户。于是他们将这一洞察付诸实践,改变了网站,使得每次你浏览电影时,都会看到一个按钮,上面写着“添加到队列”或“添加到流媒体观看列表”。他们发现,当他们在网站上做出这个改变后,越来越多的人开始将电影添加到他们的队列中,并开始成为长期客户。
你在这里看到的是Netflix如何获取数据,从中生成报告(即知识),从中获得洞察,并改变其产品以长期留住客户。这就是一个从数据到智慧的例子,为此,你需要做大量的数据科学工作。
机器学习简介
数据科学的另一个重要方面是机器学习。这不是一门关于机器学习的课程,但我想给你一个快速的介绍。
根据亚瑟·塞缪尔的说法,机器学习是一个研究领域,它赋予计算机学习能力,而无需进行明确编程。亚瑟·塞缪尔写了一个跳棋程序,该程序与自己进行了数万次对弈,记录导致胜利的位置和导致失败的位置,从而学会了跳棋游戏,最终甚至击败了亚瑟·塞缪尔。
汤姆·米切尔在1998年给出了一个更正式的定义:一个计算机程序被认为从经验E中学习某项任务T,并用性能度量P来衡量,如果它在任务T上的性能(由P度量)随着经验E的增加而提高。这是一个非常复杂的教科书式定义。
我想说的是,机器学习是一门科学学科,真正关注算法的设计和开发,这些算法允许计算机基于某种经验数据(如来自传感器的数据,甚至来自数据库的数据)来学习行为。这是一个独立的领域。正如我所说,本课程不侧重于机器学习,但你可以在Hadoop上实现机器学习算法。从这个意义上说,你可能会与机器学习专家合作,在大数据上实现算法。未来你可能想选修一门机器学习课程。
机器学习用在哪些地方呢?它用于互联网搜索聚类、知识管理系统、社交网络映射和社交网络分析、数据分类、营销分析。推荐系统是使用机器学习的另一个重要领域。此外还有日志分析、垃圾邮件过滤、欺诈检测等。有一大批领域使用机器学习。如果你感兴趣,斯坦福大学的教授在这个特定网站上提供免费的机器学习课程,你可能想去看看。
关于机器学习,有一个警示。让我读给你听:
一位父亲决定教他的儿子什么是跑车。发现用语言解释很困难,他决定举一些例子。他们站在一条高速公路桥上,每辆汽车从下面经过时,父亲就喊:“那是跑车!”当一辆跑车经过时。10分钟后,父亲问儿子是否明白了什么是跑车,儿子说:“是的,当然,这很容易。”一辆旧的红色大众甲壳虫经过,儿子喊道:“那是跑车!”父亲沮丧地问:“你为什么这么说?”儿子回答:“因为所有跑车都是红色的。”
这是机器学习出错的经典例子。很多时候,当你进行机器学习时,你可能会陷入一种情况,即算法根据数据得出错误的结论。因此,需要进行大量的训练和验证。为了做到这一点,你可以使用另一个领域,称为众包。亚马逊网络服务有一个叫做“亚马逊土耳其机器人”的服务,提供众包服务。
基本思想是,你定义所谓的“HITs”(人类智能任务),这些任务可以分发给世界各地的人,世界各地的人会为了几美分为你分类数据,识别某物是否是正面评价等等。所以你实际上是在利用人类来进行学习、分类或归类。
因此,人们将机器学习与亚马逊土耳其机器人结合使用,以证明机器学习算法是可行或有效的。所以,当涉及到大数据和推荐系统时,你可能想尝试一下。

总结

在本节课程中,我们一起探讨了新兴的数据科学领域,了解了数据科学家需要具备哪些特质,以及他们完成工作所使用的一些工具和技术。虽然这不是一门专门的数据科学课程,但作为开发者,在编写Hadoop程序时,你可能会与数据科学家合作来实现他们的算法,或者你自己可能立志成为一名数据科学家,因为你已经在本课程中学会了其中50%的技能——即使用Hadoop进行大数据处理。
006:历史数据处理技术 📜
在本节课中,我们将学习历史上如何处理大数据问题,以及这些解决方案的不足之处。我们将特别深入了解超级计算(超级计算机)、分布式或网格计算,以及关系型数据库管理系统计算,并分析它们各自的缺点。
超级计算机 💻

上一节我们介绍了大数据处理的挑战,本节中我们首先来看看超级计算机。超级计算机于20世纪60年代问世。其核心思想是:使用大量通过高速局域网连接的多处理器,以便更快地计算数据,并在CPU之间更快地移动数据。
这些系统通常放置在一个房间内,速度非常快,但造价相当昂贵。

它们是为满足科学研究社区的数据处理需求而专门创建的,例如实时天气预报系统、航空航天工业以及核聚变研究实验室。第一台超级计算机由Control Data Corporation公司的Seymour Cray制造,并在60年代后的几十年里主导了市场。随后,IBM等公司也进入市场以满足需求。
以下是1977年的一台超级计算机,这是国家大气研究中心(NCAR)使用的Cray-1A,用于满足其计算需求。这台计算机如今已不再使用,据说今天的iPhone比这台特定计算机拥有更强的计算和存储能力。
但这并不意味着超级计算机已不再使用。下图是阿贡国家实验室使用的IBM Blue Gene P超级计算机。
那么,超级计算机有哪些优点和缺点呢?
以下是其优点和缺点的总结:
- 优点:由于数据可以在进程间快速移动,所有处理器可以协同处理同一任务。这意味着它非常适用于CPU密集型任务,以及高度复杂的实时应用和模拟。
- 缺点:构建和维护成本非常高。以1970年代的Cray计算机为例,其成本约为200万至300万美元。可以想象今天的超级计算机会有多昂贵。它们使用非常专业的硬件,这意味着无法将Cray超级计算机的RAM用于IBM超级计算机。它们也使用专业软件,每台机器都有自己的编程语言和方法论,导致开发人员难以在不同平台间迁移,若需迁移则必须重新学习新系统。此外,还存在有限的垂直扩展性。公司或组织通常购买具有四分之一或一半计算能力的超级计算机,随着需求增长再购买更多。但一旦达到上限,就无法添加第二台机器来平衡两台计算机间的负载。因此,这里没有水平扩展的概念。
网格或分布式计算 🌐
了解了超级计算机的局限性后,我们来看看网格或分布式计算。网格或分布式计算于70年代末和80年代初引入。其基本思想是:使用集群中的计算机节点,并让这些节点访问集中式服务器上的同一共享文件系统。这些节点不必在同一房间内,可以分布式部署。

因此,你在这里看到的是一个带有共享存储的集中式服务器,以及许多连接到该服务器的计算机。

这些计算机连接到集中式服务器,下载数据,处理数据,然后将处理后的数据上传回集中式服务器。
SETI@home项目是网格或分布式计算成功的经典范例。SETI代表“搜寻地外文明”。该项目获取射电望远镜数据,将其分解为0.35兆字节的单位,并发送到世界各地的计算机进行处理。这些计算机安装了屏幕保护程序,即SETI屏幕保护程序。当计算机空闲时,它会下载数据、处理数据并将数据上传回集中式服务器。
那么,网格或分布式计算有哪些优点和缺点呢?
以下是其优点和缺点的总结:
- 优点:显然不需要专业硬件。事实上,异构硬件也可以工作,正如SETI项目所示。系统可以分布在多个位置,不必都在同一房间内。此外,还开发了标准API,例如来自opennpi.org的OpenMPI。这个标准API为程序员提供了更大的控制权来进行操作。
- 缺点:它要求程序员处理数据流的机制,这可能变得相当复杂。所有的线程、锁定和同步都必须由程序员完成。稍后我们将学习Hadoop,在Hadoop中,这些工作都由框架为我们处理。此外,它们适用于数据量小的计算密集型任务,但当涉及大数据时,由于网络瓶颈,它们的效果不佳。




关系型数据库管理系统计算 🗃️
接下来,我们探讨关系型数据库管理系统计算。这在80年代开始流行,我相信你们许多人都使用过RDBMS。其基本思想是:使用带有附加存储的单一服务器来存储和处理数据,因为我们希望保持数据的ACID属性。
这类系统的经典例子当然包括Oracle数据库、MySQL数据库、SQL Server、PostgreSQL等等。你通常进行纵向扩展,而不是进行太多横向扩展。因为如果进行横向扩展,你必须进行分片、反规范化甚至引入分布式缓存等操作。当你开始这样做时,会带来一些弊端。例如,如果进行分片,需要在每个服务器实例上创建和维护模式,这是额外的开销。如果进行反规范化,则会失去关系模型的一些优势。如果进行分布式缓存,则会遭受缓存一致性问题。如果数据在缓存中,效果很好;但如果不在,性能就会下降。
因此,对于RDBMS计算,有一些优点。当然,你有标准的SQL接口。它也非常适合处理结构化数据。
但也有一些缺点。当涉及到像我们讨论过的分片、反规范化或分布式缓存等扩展技术时,你无论如何都会失去RDBMS的一些优势,那么为什么还要进行RDBMS计算呢?此外,它们无法处理大数据,因为I/O(从磁盘到内存或从集中式存储到其他客户端服务器的数据移动)成本高昂。它们也不适合非结构化数据处理,如图像处理或视频处理,尽管它们在文本处理方面表现相当好。


总结与启示 🎯


我们已经看到,历史或传统技术效果不佳。


因为所有数据无法装入单台机器,所有处理也无法在单台机器上完成。即使你将数据放在单台机器上,将数据从该机器传输到各种分布式系统(如网格计算示例中)也是一项昂贵的提议,因为受限于网络带宽。
关于分布式计算,Grace Hopper有一句名言:“在拓荒时代,他们用牛来拉重物。当一头牛拉不动一根木头时,他们不会试图养一头更大的牛。我们不应该追求更大的计算机,而应该追求更多的计算机系统。”这是一句至理名言。这实际上成为了大数据处理的基础。
对于大数据处理,扩展性显然很重要。在这一点上很清楚,无论我们选择什么系统,它都必须能够为大数据进行扩展。同时,大数据处理必须具有经济性。因此,可扩展性和经济性是我们进行大数据处理真正需要的两个关键因素。


在本节课中,我们一起学习了历史或传统的大数据处理方法或技术,如超级计算机、分布式网格计算和关系型数据库管理系统计算,并了解了这些解决方案的优点和不足之处。这些历史背景为我们理解现代大数据框架(如Hadoop)为何以及如何出现奠定了重要基础。
007:现代大数据计算技术 💻
在本节课中,我们将学习支撑现代大数据处理的关键计算趋势与挑战。我们会探讨当前的计算优势、面临的问题,以及相应的解决方案,并了解这些方案如何为Hadoop等大数据技术铺平道路。
现代计算的优势与趋势 🚀
上一节我们了解了大数据的基本概念,本节中我们来看看推动其发展的技术趋势。得益于以下几个关键因素,大规模数据处理在今天成为可能。
以下是当前主要的计算发展趋势:
- 更快的处理器与更大的内存:根据摩尔定律,大约每两年,CPU的处理能力和RAM的容量就会翻倍。这为复杂计算提供了强大的硬件基础。
- 更廉价的存储:存储成本持续下降,使得组织机构能够收集并存储前所未有的海量数据,并期望对这些数据进行处理分析。
- 成熟的分布式系统设计:过去20年间,分布式系统设计日趋成熟。例如,Hadoop运动以及NoSQL(分布式数据库)运动已变得非常流行。
- 开源软件的普及:开源运动始于约20年前,并催生了许多甚至优于专有或商业软件的优秀产品,极大地降低了技术门槛和创新成本。
- 商品硬件的广泛使用:人们越来越多地使用廉价的商用硬件来构建计算集群,而非依赖昂贵的超级计算机。
- 公共云计算的兴起:亚马逊、谷歌等公司提供了云计算解决方案。组织机构无需自行购买所有硬件,可以将数据上传至云端进行处理,获取所需信息后再传回本地。
现代计算面临的挑战 ⚠️
尽管计算能力在增长,但在处理大数据时,传统的分布式或网格计算架构仍面临显著挑战。
在这种架构中,通常存在一个带共享存储的中央服务器,其他计算机通过网络连接到它,获取数据并进行处理。
以下是该架构面临的主要问题:
- 磁盘I/O速度慢:许多服务器为了成本效益使用机械硬盘,其读写速度非常慢,容易成为性能瓶颈。
- 硬件故障:磁盘存在磨损、制造缺陷等问题,可能导致故障和数据丢失。
- 网络带宽不足:数据中心内部的网络带宽有限,难以支持所有计算节点同时从中央存储高速传输海量数据。当大量计算机都需要访问中央服务器的数据时,网络极易拥堵。

应对磁盘I/O挑战的解决方案 💾
针对磁盘速度慢和可靠性问题,业界通常采用两种基础技术:条带化与镜像。

条带化 的核心思想是,将一个文件分割成多个块,并将这些块分布存储在不同的磁盘上。
例如,一个文件被分成4块,分别存储在4个磁盘上。当需要读取文件时,可以并行地从多个磁盘同时读取,从而显著提升I/O速度。其过程可以简化为:
文件 -> 分割为块 [A1, A2, A3, A4] -> 并行存储于磁盘 [Disk1, Disk2, Disk3, Disk4]
镜像 则是指将同一份文件复制到两个或更多磁盘上。
例如,同一文件被复制了4份,分别存储在4个磁盘上。镜像提供了数据冗余,当某个磁盘发生故障时,数据仍然可以从其他磁盘恢复,保障了可用性。
为了同时获得速度提升和可靠性,人们将条带化和镜像结合起来使用,这被称为 RAID 0+1 配置。
RAID代表“廉价磁盘冗余阵列”。RAID 0+1 的做法是:先将文件条带化分割并分布到一组磁盘上(RAID 0),然后将这整组条带化磁盘再完整地镜像到另一组磁盘上(RAID 1)。
下图展示了这一概念:文件被分成4块并存储在4个磁盘上,同时,这4个磁盘的内容又被整体镜像到另外4个磁盘上。

因此,RAID 0+1 同时实现了条带化和镜像。

应对网络挑战的解决方案 🌐

解决网络带宽瓶颈的思路是 将数据与计算代码放在一起。

具体方法是:先将数据分布式地存储在网络中多台计算机的本地磁盘上,然后将计算任务(处理逻辑)发送到数据所在的节点上去执行。
这是一个非常新颖且关键的理念。有趣的是,这正引领我们进入 Hadoop 的领域。“将代码移至数据”是Hadoop架构的基石。


总结 📝
本节课中,我们一起学习了现代大数据计算的技术全景。我们首先探讨了使大数据处理成为可能的几大计算趋势,包括硬件性能提升、分布式系统成熟以及云计算兴起等。接着,我们分析了传统集中式存储架构在处理大数据时面临的磁盘I/O和网络带宽挑战。最后,我们介绍了通过条带化和镜像(如RAID 0+1)来优化磁盘性能与可靠性,以及通过 “将代码移至数据” 这一核心范式来解决网络瓶颈。这些解决方案为Hadoop等分布式计算框架的出现和发展奠定了重要基础。
008:大数据革命总结 🚀

在本节课中,我们将回顾大数据革命背后的趋势与核心概念,并对整个模块的内容进行总结。
上一节我们探讨了大数据处理的技术需求,本节中我们来看看本模块的要点总结。
大数据革命概述 📈
我们审视了信息技术市场中近期大数据革命背后的趋势与概念。
我们从宏观视角俯瞰了大数据领域。
核心要点总结
以下是本模块学习的关键内容总结:
- 数据正以惊人的速度生成,这即是其被称为“大数据”的原因。
- 大数据分析与数据科学是一个新兴领域。
- 企业正在同时运用传统数据分析与新兴数据科学来改善业务并发现新机遇。
- 传统数据处理技术表现良好,但并不真正适用于大数据处理。
- 我们真正需要的是像 Hadoop 这样的现代化、革命性的分布式存储与处理系统。
后续展望 🔮
而这正是我们将在下一个模块中详细讨论的内容。

本节课中我们一起学习了大数据革命的驱动因素、核心概念以及传统技术的局限性,并引出了解决这些挑战的关键技术——Hadoop分布式系统。
009:Apache Hadoop架构与生态系统导论 🏗️

在本节课中,我们将学习Apache Hadoop的架构及其生态系统。我们将了解Hadoop的核心组件、使其适用于大数据处理的关键原则,以及围绕它构建的各种子项目和商业发行版。
概述
本模块旨在帮助您从宏观角度理解Hadoop的架构,并认识使其成为强大大数据处理工具的关键特性。我们将梳理Hadoop生态系统中的各种子项目,并区分不同的商业发行版。
Hadoop的背景与历史 📜
上一节我们介绍了本课程的目标,现在让我们首先了解Hadoop的起源。
Hadoop的诞生源于对海量网页数据进行存储和处理的挑战。它最初是Apache Nutch搜索引擎项目的一部分,后来借鉴了Google发表的关于Google文件系统(GFS)和MapReduce的论文思想,独立发展成为一个顶级Apache项目。
Hadoop架构核心组件 ⚙️
了解了Hadoop的历史后,本节中我们来看看其架构的核心组成部分。
Hadoop架构主要由两个核心组件构成:
- Hadoop分布式文件系统(HDFS):这是一个高度容错的分布式文件系统,设计用于在低成本硬件上运行。它将大数据集分割成块,并跨集群中的多个节点进行存储。
- Hadoop MapReduce:这是一个用于并行处理大量数据的编程模型。其核心思想是将计算任务分为两个阶段:Map(映射)和Reduce(归约)。
使Hadoop强大的关键原则 🔑
上一节我们介绍了Hadoop的核心组件,本节中我们来探讨其背后的设计原则。
Hadoop的成功建立在几个关键原则之上,这些原则使其特别适合处理大数据:
- 横向扩展(而非纵向升级):通过向集群添加更多普通商用服务器来增加容量和计算能力,而不是依赖更强大、更昂贵的单一机器。
- 将计算移至数据:与传统方式不同,Hadoop将计算任务发送到存储数据的节点执行,这极大地减少了数据在网络中的传输,提高了效率。
- 故障是常态,而非异常:Hadoop假设硬件故障会经常发生。其架构设计能够自动检测和处理节点故障,确保作业能够继续完成。
Hadoop技术栈与生态系统 🌐
理解了核心原则后,我们现在可以俯瞰整个Hadoop技术生态系统。
Hadoop生态系统包含了许多子项目,它们扩展了Hadoop的核心功能,提供了更丰富的数据处理方式。以下是其中一些关键项目:
- Apache Hive:一个数据仓库工具,它提供了一种类似SQL的查询语言(称为HiveQL),允许用户查询存储在HDFS中的数据。
- Apache Pig:一个高级平台,用于创建在Hadoop上运行的程序。它使用一种名为Pig Latin的脚本语言,简化了MapReduce作业的编写。
- Apache HBase:一个分布式、可扩展的NoSQL数据库,构建在HDFS之上,支持对大数据进行实时读写随机访问。
市场上的Hadoop发行版 🏪
上一节我们介绍了开源生态项目,本节中我们来看看基于Hadoop的商业发行版。
除了原始的Apache Hadoop,多家公司提供了集成的、包含额外工具和商业支持的发行版。主要发行版包括:
- Cloudera Distribution for Hadoop (CDH):由Cloudera公司提供,集成了许多生态系统工具并包含管理界面。
- MapR Distribution:由MapR公司提供,以其高性能和替代HDFS的MapR-FS文件系统而闻名。
- Hortonworks Data Platform (HDP):由Hortonworks公司提供(现已与Cloudera合并),强调100%开源。
如何获取Hadoop文档与信息 📚
最后,为了帮助您持续学习和保持知识更新,本节将指明获取官方文档和信息的途径。
保持对Hadoop最新发展的了解至关重要。您可以通过以下主要渠道获取文档和信息:
- Apache Hadoop官方网站:获取最权威的官方文档和项目更新。
- 各发行版供应商的网站(如Cloudera, Hortonworks/Cloudera, MapR):获取针对特定发行版的文档、白皮书和教程。
- 社区与论坛:如Stack Overflow、邮件列表,是解决具体问题和交流经验的好地方。
总结

本节课中,我们一起学习了Apache Hadoop的架构与生态系统。我们从其历史背景出发,深入了解了HDFS和MapReduce这两个核心组件,以及横向扩展、移动计算而非数据、容错设计等关键原则。我们还梳理了Hive、Pig、HBase等重要的生态系统项目,并比较了Cloudera、MapR、Hortonworks等主流商业发行版。最后,我们指明了持续学习和获取最新信息的官方渠道。掌握这些知识,为您进一步深入学习和使用Hadoop处理大数据奠定了坚实的基础。
010:Hadoop历史沿革 🐘


在本节课中,我们将学习Hadoop的起源与发展历程。我们将回顾其诞生的背景、关键的技术转折点,并最终给出Hadoop的正式定义及其核心特性。课程最后,我们还将了解Hadoop在当今市场的流行程度。
Hadoop背景与起源
上一节我们介绍了大数据处理的现代技术。本节中,我们来看看Hadoop是如何诞生的。
Hadoop的历史始于2002年至2003年。当时,两位工程师道格·卡丁和迈克·卡法雷拉开始致力于一个名为Nutch的开源项目。他们的目标是构建一个网络规模的分布式网络爬虫和搜索引擎。
在2002至2003年间,市场上有两个占主导地位的搜索引擎:谷歌和雅虎。其他搜索引擎如Infoseek、Alta Vista等基本上都已退出市场。因此,道格·卡丁希望构建一个Apache开源分布式爬虫和搜索引擎。他们确实做到了,并在四个节点上演示了Nutch。但他们发现,要实现可操作性且达到网络规模(即能够爬取数百万个网站、下载内容并建立索引)仍然遥不可及。
关键转折点:谷歌论文的启发
他们正在努力寻找解决这个问题的方法。幸运的是,在2003年,谷歌发表了一篇关于GFS(谷歌文件系统)的论文。你可以从 research.google.com/archive/gfs.html 下载这篇论文。后来,当我们开始学习HDFS时,会将其与GFS进行比较,届时我们会更详细地讨论GFS。
在这篇关于GFS的论文中,谷歌声称他们的GFS是一个分布式文件系统,而不是连接到单个存储的单一机器。他们还声称GFS成本低廉,因为它构建在商用硬件之上。GFS还通过让多台机器上的多个磁盘协同工作,提供了高聚合性能。我们在上一模块讨论现代数据处理技术时已经看到了这一点。因此,他们能够构建一个高性能、可扩展、容错的文件系统。最重要的是,谷歌将这个大型分布式文件系统用于数据密集型应用。
有趣的是,谷歌没有提供实现或源代码,只发布了白皮书。
2004年,谷歌又发表了一篇名为MapReduce的论文。你可以从 research.google.com/archive/mapreduce.html 下载这篇论文。我们将在后面讨论MapReduce架构时更详细地谈论这篇论文。
在谷歌的MapReduce论文中,谷歌描述了一种用于大数据处理的新编程范式。他们说这是一种函数式风格的编程,而不是面向过程或面向对象的编程模型,其核心是map和reduce函数。这些map和reduce函数将使用键值对作为输入,在大型商用机器集群上并行执行,并生成键值对作为输出。当然,谷歌没有为MapReduce提供实现或源代码,只提供了白皮书。
这一切让道格·卡丁和迈克·卡法雷拉开始思考。他们实际上修改了Nutch项目,并将Nutch项目移植到一个新的框架上。这个新框架开始使用谷歌论文中描述的许多概念,即MapReduce论文和GFS论文中的概念。他们当时还没有将这个新框架称为Hadoop,但后来它成为了Hadoop。
Hadoop发展时间线

我想以时间线的形式来讲述Hadoop历史的其余部分。
我们已经看到,在2002年至2003年间,道格·卡丁和迈克·卡法雷拉开始致力于Nutch项目。在2003年至2004年间,谷歌发表了GFS和MapReduce论文,这给了道格·卡丁如何改变Nutch架构的思路。于是他开始修改Nutch架构,在其中加入DFS和MapReduce支持。
以下是Hadoop发展过程中的关键节点:
- 2006年:雅虎雇佣了道格·卡丁。他们所做的就是将Nutch项目拆分为两个部分:Apache Hadoop项目和Nutch项目。因此,Apache Hadoop项目于2006年诞生。
- 2007年左右:许多公司开始研究Hadoop,其中一家公司是《纽约时报》。有一个关于《纽约时报》一位工程师使用100台EC2机器转换4TB图像档案的精彩故事。这是Hadoop与云计算结合的经典案例。
- 2008年左右:Facebook也开始使用Hadoop。他们不仅使用了Hadoop,还为Hadoop做出了贡献。他们实际上创建了一个名为Hive的开源Apache子项目,你可以编写类似SQL的查询,这些查询会被翻译成MapReduce程序。
- 2008年:另一件有趣的事情发生了,雅虎开始进行TB级排序测试。在2008年,雅虎使用910个节点在3.5分钟内对1TB数据进行了排序。在分布式计算中,如果你能进行TB级排序,这很好地说明了你的项目进展如何。正如你所见,Hadoop确实在朝着正确的方向发展。
- 2008年:Cloudera公司成立。Cloudera是第一家采用Apache开源项目并开始为企业提供支持的公司。
- 2009年:Hadoop峰会创立,有750名与会者参加。2009年发生的另一件有趣的事情是,雅虎使用1460个节点在62秒内对1TB数据进行了排序。他们还使用3658个节点在16.25小时内对1PB数据进行了排序。最后,大约在2009年,Hadoop之父道格·卡丁加入了Cloudera。
致谢
如果说有哪些公司值得我们感谢,那应该是谷歌,因为GFS论文和MapReduce论文为我们指明了正确的方向;还有雅虎,他们对Hadoop开源项目做出了所有贡献,他们确实投入了数百万美元以及成千上万的工程师来推动Hadoop项目的成熟。
我们也应该感谢Hadoop之父道格·卡丁。他当然是Hadoop的创造者,也是搜索引擎项目Lucene的创造者。在此之前,道格在Excite、苹果和施乐公司担任搜索技术职位。他是开源项目的积极倡导者,并且是Apache软件基金会的董事会成员。

感谢道格·卡丁为我们带来了Hadoop项目。
什么是Apache Hadoop?
上一节我们回顾了Hadoop的历史。本节中,我们来正式定义一下Hadoop。


Apache Hadoop是一个用于管理大数据的开源项目,用于管理组织的大数据。它不是一个单一的项目,而是一组协同工作的项目。因此,当我们谈论Apache Hadoop时,是的,有一个名为 hadoop.apache.org 的网站,但还有许多其他Apache子项目与Hadoop协同工作以创建一个生态系统。例如Hive、Pig、Sqoop、Flume、ZooKeeper等项目,我们将在后面的不同章节中讨论其中的许多项目。
当然,Hadoop处理大数据的三个V:速度、多样性和体量。它将商用硬件转化为连贯的存储服务,允许你存储PB级的数据,但不仅仅是存储数据,还提供连贯的处理服务,允许你以高效的方式处理数据。
Hadoop名称的由来与项目命名

那么,名字里有什么呢?Hadoop不是一个首字母缩写词,它实际上是一个虚构的名字。根据道格·卡丁的说法,这是他孩子给一只黄色毛绒大象起的名字。道格·卡丁认为Hadoop是一个很酷的名字,所以他决定将其用于他的项目。

子项目和其他贡献模块的名称往往与其功能无关,通常它们有一些与大象或其他动物相关的主题。例如,Mahout项目。Mahout在印地语中字面意思是“驯象师”。Mahout实际上是一个机器学习算法,通常在机器学习算法中,你训练数据,使用数据集来训练你的算法,所以这有点道理,我想这就是他们使用Mahout作为名字的原因。
Pig是雅虎创建的一种查询语言。你用Pig Latin语言编写程序来表达你的MapReduce程序,它们会被翻译成MapReduce程序。Pig的另一个特点是它可以消费任何类型的数据,不像我们在Hive中看到的那样有严格的数据结构要求。所以就有了Pig。
Hadoop的核心特性
了解了Hadoop是什么之后,我们来看看它的核心特性。
冗余性和可靠性是Hadoop的关键属性之一。当你将数据放入Hadoop时,它会自动复制数据,因此当一台机器出现故障时,实际上不会丢失数据。
Hadoop也是以批处理为中心的。不像数据库那样的实时系统,你可以以交互方式进行操作并获得即时响应时间。在Hadoop中,我们有一个以批处理为中心的引擎,当你运行一个Hadoop作业时,可能需要几分钟甚至几小时才能得到结果。

它使得编写分布式应用程序变得容易。编写分布式应用程序是一件相当复杂的事情,因为你必须处理多台机器,必须担心同步问题,必须担心网络问题,做这些事情相当复杂。但Hadoop允许你编写一个分布式程序,该程序不仅可以在一台机器上运行,还可以扩展到数千台机器而无需更改一行代码,而且你实际上不必担心任何这些低层同步问题,它们会自动为你处理。
Hadoop也运行在商用硬件上。你实际上不需要购买任何专用硬件或昂贵的设备或冗余硬件,其可靠性方面已内置在软件中。因此,运行商用硬件就足够了。
如今,Hadoop是市场上增长最快的技术。当今的企业将Hadoop视为其数据中心中另一项必备技术。
Hadoop的市场流行度
最后,我们来看看Hadoop在市场的流行程度。
为了好玩,我决定将Hadoop与其他一些流行的现代技术进行比较,比如Java EE、MPI编程、iPhone编程和Android编程。以下是我得到的结果。请注意,用橙色表示的Hadoop,其职位数量实际上正在上升。我只是想将其与这些其他技术进行比较,尽管它不如Android编程,甚至不如实际上正在下降的MPI编程,但它在右边,所以我想这是件好事,对吧?

总结

在本节中,我们回顾了Hadoop的历史,了解了它是如何开始的。我们也最终正式定义了Hadoop及其关键属性。我们还通过Indeed.com的职位趋势看到了Hadoop生态系统在市场上的流行程度。
011:Hadoop架构解析 🏗️


在本节课中,我们将学习Hadoop的架构。我们将探讨经典的Hadoop架构(称为MRv1)和Hadoop YARN架构(称为MRv2)。我们还将了解Hadoop的内部模块以及构成Hadoop架构的主要组件。
这将是一个非常高层次的Hadoop架构概览。在本课程后续部分,我们将更深入地探讨Hadoop架构以及各组件之间的关键交互。
Hadoop概览
Hadoop集群由物理硬件和逻辑软件共同构成。
Hadoop的物理层面是硬件,它本质上是数据中心机架上安装的一系列商用服务器阵列。每台独立的服务器本身可能拥有多个独立的磁盘和核心。
Hadoop的逻辑层面由此介入。Hadoop HDFS统一了跨服务器、跨机架的存储磁盘,使其看起来像一个单一的文件系统,尽管其底层实际上是分布式的。Hadoop YARN通过统一跨服务器的核心资源,为分布式处理提供统一的计算服务,以完成一个高层次的大数据处理作业,尽管其底层同样是分布式的。
那么,什么是Hadoop?
Hadoop是用于可靠、可扩展、分布式计算的开源软件。其“分布式”体现在你可以让Hadoop中的一大批机器、服务器协同工作以完成处理任务。其“可靠”体现在:当你将数据存入Hadoop HDFS时,数据被分割成块,不同的块被放置在不同的位置,并且这些块会被复制。因此,当一台机器崩溃时,另一台机器将接管特定的数据检索过程并提供该数据。同时,其可靠性还体现在:当你提交一个作业时,作业被分解为任务,这些任务在不同的机器上运行。如果一台机器崩溃,另一台机器将接管该特定任务。其“可扩展”体现在:你可以从10台服务器开始构建Hadoop集群,然后通过添加更多服务器来增加集群容量,实现向上扩展,也可以进行向下缩减。
Apache Hadoop的官方网站是 hadoop.apache.org。
Hadoop核心模块与版本
以下是构成Hadoop的各个模块:Common、HDFS、YARN和MapReduce,这是Hadoop的四个核心模块。Hadoop还有许多相关项目,如Ambari、Avro、Cassandra、Chukwa等,这些项目构建于Hadoop之上或与Hadoop紧密协作。
Hadoop有两个主要发布版本:Hadoop 1.x(也称为经典MapReduce或MRv1)和Hadoop 2.x(也称为YARN,即“又一个资源协调者”,或MRv2)。MRv1在市场上非常普遍,许多使用Hadoop的组织都在使用Hadoop 1.x。Hadoop 2.x/YARN是一项新兴技术,也是本课程将使用的技术。在1.x版本中完成的所有工作都可以完全移植到2.x版本。因此,学习2.x版本也涵盖了1.x版本的所有方面,只是守护进程略有不同。本课程将重点介绍2.x版本。
Hadoop 1.x 技术栈
我们用灰色表示核心Hadoop模块,用黄色表示构建在Hadoop之上的辅助或后续项目。在核心层,我们有Common库。构建在其之上的是HDFS分布式存储。再之上是MapReduce分布式处理。当你下载Hadoop时,这就是你获得的核心Hadoop模块。安装Hadoop时,你得到的就是这些。
构建在这些核心模块之上的还有其他项目。例如,Hive项目允许你编写类似SQL的查询,这些查询会被翻译成MapReduce作业。Pig项目允许你使用一种名为Pig Latin的脚本语言来编写MapReduce作业。HBase是一个构建在HDFS之上的NoSQL数据库。此外,还有几个辅助项目,如Ambari、Flume、Oozie、Zookeeper。理解的关键在于:当你下载Hadoop时,你下载的是核心模块,然后你需要为各种子项目或这些其他项目单独下载,它们共同协作。
Hadoop Common模块
Hadoop Common提供了支持Hadoop核心项目(即Hadoop代码)以及其他子项目(如Hive和Pig)的实用工具和库。
Hadoop的配置方面由配置API管理,该API是Common模块的一部分。Hadoop还提供了文件系统抽象。作为程序员或客户端,你将与Hadoop文件系统抽象进行交互,请求它创建文件、删除文件、创建目录、删除目录。其底层可能使用HDFS,甚至可能使用Amazon的S3文件系统。实际上,你可以使用任何底层文件系统。
当你在HDFS中存储内容时,你可能希望压缩它,因为存储的是大量数据,Hadoop内置了压缩算法,例如BZip2和GZip。当Hadoop组件相互通信时,它们使用RPC(远程过程调用),因此RPC通信实用工具也是Hadoop Common模块的一部分。当数据在组件之间发送或存储在HDFS中时,可能需要序列化,Hadoop内置了自己的序列化和反序列化实用工具,而不是使用Java序列化。
这些构成了Hadoop Common模块,其他模块都构建在其之上。
Hadoop HDFS模块
Hadoop HDFS模块提供了分布式、可扩展、可移植的文件系统。其“分布式”体现在多个服务器协同工作,为你提供一个统一的文件系统视图。其“可扩展”体现在你可以添加更多带有更多磁盘的服务器,从而增加文件系统的容量。其“可移植”体现在无论底层使用何种文件系统(标准的HDFS、S3文件系统或Cosmos文件系统),对于客户端API来说,它提供的是通用的文件系统抽象。
客户端可以使用Java API进行CRUD操作(创建、读取、更新、删除文件),也可以使用C API。本课程将主要使用Java API。此外,还有命令行接口(hadoop fs命令或hdfs dfs命令)以及REST API可用。
Hadoop HDFS的工作原理涉及一系列组件。NameNode是HDFS的“大脑”,它存储HDFS的元数据(关于文件的元数据)。DataNode守护进程是实际将数据读写到本地文件系统的“劳动力”,它们是NameNode的工作节点。当客户端想要创建或删除文件、创建或删除目录时,客户端直接与NameNode通信以进行元数据操作,并与DataNode通信以读写数据块。
让我们从图示角度看看HDFS架构。
这里有四台机器:一台主机器和三台从机器。在主机器上,运行着NameNode守护进程,它是HDFS的主节点。在从机器上,运行着DataNode守护进程,它们是主NameNode的工作节点。客户端与NameNode通信以创建文件的元数据。当客户端想要创建文件时,它与维护元数据的NameNode通信。客户端与DataNode通信以实际写入或读取文件系统中的文件。
DataNode之间也会相互通信,因为当文件被写入时,它被分解成数据块,并且数据块需要被复制。因此,DataNode在NameNode的指导下彼此复制数据块。
MapReduce架构
MapReduce是跨多台机器、多个节点进行分布式处理的编程范式。
MapReduce的客户端编程本质上是客户端编写我们称为Map和Reduce任务的作业,并将此特定作业提交给所谓的JobTracker。
Map和Reduce程序通常用Java编写(这也是本课程将采用的方式),但你也可以使用其他语言编写MapReduce程序,这种情况下你需要使用Hadoop Streaming和Hadoop Piping。
在MRv1架构中,我们有JobTracker,它是Hadoop处理的“大脑”。客户端将作业提交给JobTracker,然后JobTracker与从属的TaskTracker协同工作,以启动和运行各种Map和Reduce任务。
让我们看看MapReduce架构的实际运作。
这里有四台机器:一台主机器和三台从机器。主机器运行着主守护进程,即JobTracker。从机器运行着TaskTracker守护进程,即从属的TaskTracker守护进程。当客户端想要提交作业时,客户端编写作业并将其提交给JobTracker。JobTracker与TaskTracker协同工作,TaskTracker启动任务(即Map和Reduce任务)来执行作业,然后将结果返回,最终返回给客户端。
因此,作业被提交给JobTracker,JobTracker与TaskTracker协作,TaskTracker启动任务(即Map和Reduce任务),然后结果被写回。
整合HDFS与MapReduce
MapReduce构建在HDFS之上,让我们将所有内容整合起来。
客户端可以与主NameNode通信以进行NameNode相关操作(元数据操作、文件系统操作)。客户端还可以与JobTracker通信以提交作业。JobTracker在某些情况下会咨询NameNode。
JobTracker在从机器上运行着从属的TaskTracker,而NameNode在从机器上运行着从属的DataNode守护进程。因此,在一台从机器内部,你同时运行着TaskTracker守护进程和DataNode守护进程。这些任务将与本地数据协作,这就是数据本地性。你还会注意到DataNode之间相互通信,这是数据复制发生的地方。
MRv1的局限性
Yahoo使用Hadoop MapReduce版本1已有数年,他们发现MRv1存在一些问题。
存在可扩展性问题。使用MRv1,集群的最大规模约为4000个节点(至少Yahoo发现如此),最多可运行约40,000个并发任务。JobTracker的同步模型是核心同步模型,它承担了所有工作,负担过重。
还存在一些可用性问题。如果JobTracker失败,并且已有数千个作业提交给JobTracker,所有作业几乎都会停止并失败。
此外,资源被硬性划分为Map任务和Reduce任务槽位。当你有一个集群时,例如10台机器,每台机器有8个CPU核心,你可能会预分配一定数量的Mapper槽位和Reducer槽位。你只能运行那么多Mapper任务和Reducer任务,这导致了资源利用率低下。
MRv1也缺乏对替代编程范式的支持。你只能使用MRv1进行MapReduce编程,这是一种面向批处理的编程范式,对于某些迭代算法来说速度较慢。我们需要支持其他编程范式,如图计算、交互式处理甚至流处理范式。
Hadoop 2.x/YARN架构
YARN(Hadoop MRv2)背后的核心思想是将JobTracker拆分为两部分,以分离职责。他们将JobTracker分解为两部分:一部分负责调度,另一部分实际运行MapReduce作业。同时,他们提出将Hadoop接口通用化,以便可以在此集群上运行任何类型的分布式计算应用程序。
YARN代表“又一个资源协调者”,它是一种可以运行任何类型分布式处理的范式。
让我们看看Hadoop 2.x技术栈。
再次用灰色表示核心Hadoop模块,用黄色表示辅助项目。在核心模块中,我们有之前介绍过的Common,有相同的Common,有HDFS分布式文件系统,然后有YARN分布式处理层。这是一个新增的层,MapReduce运行在其之上。这就是Hadoop 2和YARN的全部意义。在其之上,你还有其他项目,如Hive、Pig。在YARN之上,你可以引入一些框架。你当然可以引入MapReduce,但也可以引入其他框架,如Giraph。
这是Hadoop YARN架构的另一个视图。你可以看到HDFS,然后是YARN。在YARN之上,我们有批处理MapReduce(这是Hadoop的一部分,也是可用的框架之一),然后你还可以引入其他框架,如Storm、Giraph、Spark、OpenMPI、Search Weer等。任何这些框架都可以引入并在YARN之上运行。当然,MapReduce非常适合大数据处理,这也是本课程将重点关注的,并且它是Hadoop 2的标准组成部分。
什么是YARN?
YARN是一个可插拔的分布式计算架构。
就客户端编程而言,客户端编写Map和Reduce任务并将其提交给所谓的ResourceManager(这是新的JobTracker)。它支持使用Streaming和Piping的其他MapReduce语言。当然,它也支持其他分布式计算技术,如用于流处理的Storm、用于图处理的Giraph、用于交互式处理的Phoenix,以及用于流处理和交互式处理的新技术Spark。
YARN工作原理
基本上,你有主ResourceManager,它管理集群中的资源。还有一个ResourceManager会分配一个ApplicationMaster。ApplicationMaster负责协商我们称为容器的资源,这些容器运行MapReduce作业。还有这些从属的NodeManager守护进程,它们运行MapReduce任务。
让我们看看YARN架构。
从高层次来看,这里有四台机器:一台主机器和三台从机器。主机器运行主ResourceManager。从机器运行从属的NodeManager守护进程。当客户端想要提交作业时,客户端编写作业并将其提交给ResourceManager。ResourceManager与NodeManager通信,获取其资源信息。然后,ResourceManager将联系NodeManager,代表客户端启动一个应用程序。

在MapReduce v1中,JobTracker是管理实际作业的组件。现在,这项工作被拆分到ResourceManager和ApplicationMaster之间。因此,ResourceManager要求NodeManager启动一个作业,作业就在那里运行。ApplicationMaster是特定作业的新管理器,它与NodeManager和ResourceManager紧密合作,协商容器(即你的任务),这些容器将运行你的Map和Reduce任务。

这就是YARN架构的工作原理。
整合YARN与HDFS
当你将YARN置于HDFS之上时,我们所拥有的就是在混合中添加了DataNode。然后,容器与本地DataNode通信,这就是数据本地性发挥作用的地方。
Hadoop 3 新特性
我们已经讨论了Hadoop 1,并更详细地介绍了Hadoop 2 YARN。现在,Hadoop 3的架构没有太大变化,但有一些新增功能。让我们看看Hadoop 3的动机和新特性。
Hadoop 3显然建立在Hadoop 2的基础上,后者有重大的架构变化,但现在Hadoop 3有一些新功能。
Hadoop 3的工作始于2011年,但直到2017年底才首次发布。在此期间,一些错误修复和改进被移植到Hadoop 2版本中,但主要更改无法轻松移植回Hadoop 2。
Hadoop 3的主要动机之一是提高平台的可扩展性和可靠性。默认的复制因子(用于容错)导致200%的存储开销。换句话说,如果你存储一个1 GB的文件,它最终会存储为3 GB。必须存储每个文件的3个副本极大地限制了HDFS的可扩展性。

另一个可靠性和可扩展性问题是仅支持两个NameNode(一个活动,一个备用)的限制。对于非常大的集群,可能需要不止一个备用NameNode。因此,在Hadoop 3中,他们为此提供了支持。
Hadoop 2发行版还存在一些普遍问题。Java 7自2015年起已停止支持,因此推动将所有代码升级到Java 8是Hadoop 3的一大推动力。现在我们可以使用JDK 8。
Hadoop 2的应用程序经常遇到类路径冲突。原因是应用程序的库依赖可能与Hadoop客户端API本身的库依赖冲突。为了解决这个问题,应用程序开发人员需要“shade”他们的依赖项,以避免与Hadoop冲突。
Hadoop 2的另一个问题是默认端口。某些服务的默认端口位于临时端口范围内。TCP临时端口是当主机上的客户端应用程序连接到远程服务时自动分配的。这可能导致Hadoop 2的启动问题。因此,在Hadoop 3中,他们更改了这一点。
Hadoop 2的YARN也有一些严重的限制。只有两种资源类型可用于制定调度决策:CPU和内存。当今的计算集群可能包含Hadoop作业所需的其他有限资源,而YARN没有提供确定这些可用资源在集群中位置的方法。这些资源可能是GPU资源,因此Hadoop 3增加了对GPU类资源的支持。
Hadoop 3 重要特性

以下是Hadoop 3发布说明中描述的16个功能中的几个值得注意的特性。
Hadoop 3的一个重大变化是支持纠删码。纠删码是一种用于磁盘阵列的冗余技术,现在被应用于存储在HDFS中的块。这种纠删码实现使用条带化和奇偶校验来提供冗余。文件被表示为存储在多个DataNode上的数据和奇偶校验块序列。
回想一下,Hadoop 2实际上是在块级别进行复制。如前所述,默认复制因子为3会导致200%的存储开销。你可以减少文件的复制因子,但这也会降低容错能力。另一个值得注意的因素是,块复制可以提高作业性能,因为同一个块存在于多个DataNode上,可以更容易地实现数据本地性。
因此,我想说明的是,是的,Hadoop 3有纠删码,但它带来了一些优点和缺点。使用纠删码,我们可以在没有块复制的情况下实现容错。实际上,纠删码将复制因子限制在不超过1.5。有多种策略可供选择,但总的来说,纠删码将存储开销降低到不超过50%。这可以极大地提高HDFS的可扩展性。
但它确实是有代价的。由于文件被条带化分布在多个DataNode上,读写操作会增加网络流量。编码和解码过程还需要客户端和服务器额外的CPU开销。还有其他可能影响性能的限制,例如,不支持追加和截断操作。
因此,尽管Hadoop 3支持纠删码,但它尚未被广泛使用,本课程也不会涉及,但我确实想提及它。
以下是两个改进应用程序开发和性能的Hadoop 3特性。
如前所述,Hadoop 2的开发人员必须使用“shading”来消除与Hadoop客户端API依赖项的任何冲突。在Hadoop 3中,客户端jar现在使用“shading”来消除依赖冲突。这意味着开发人员不会与Hadoop 3产生依赖冲突,但也意味着开发人员需要提供自己的依赖项。
Hadoop 3的另一个令人兴奋的特性是能够定义额外的YARN资源类型。例如,拥有GPU(图形处理单元)的Hadoop集群可以了解其每个节点上的GPU可用性。因此,应用程序可以根据GPU的可用性(而不仅仅是CPU和RAM)进行调度,从而利用GPU。
让我们看看一些Hadoop服务特性。
现在Hadoop服务不再使用临时端口范围,系统管理员不会遇到偶尔的启动错误。然而,Hadoop 3的用户需要了解新的默认端口。
Hadoop 3增加了对在Microsoft Azure中部署的支持。Microsoft Azure有自己的文件系统。Amazon的S3之前已受支持,现在我们支持Microsoft Azure文件系统。
Hadoop DataNode通常有多个驱动器用于本地存储冗余和并行性。Hadoop 2确实确保存储在所有驱动器上均匀分布。然而,当Hadoop 2 DataNode上的一个驱动器发生故障并被更换时,存储就不再均匀分布。Hadoop 3的节点内平衡器将重新分布数据,使新驱动器立即得到利用,并最终恢复平衡。
另一个特性是,在Hadoop 2中,必须为集群上运行的每个守护进程和其他任务预先确定Java堆大小。在Hadoop 3中,Java堆大小的确定是自动的,无需手动配置,这实际上使安装过程变得更容易一些。

Hadoop 3 发行版
既然我们已经介绍了一些新功能,让我们看看在哪里以及如何找到Hadoop 3。
Apache发行版的最新版本是Hadoop 3.2(于2019年1月发布),这也是本课程将使用的版本,除非另有说明。
其他Hadoop发行版方面,Cloudera和Hortonworks已经合并,但Cloudera和Hortonworks的发行版都将再维护三年,直到一个融合的Cloudera发行版可用。顺便说一下,它将被称为CDP。
你可以看到商业发行版稍微落后一些。例如,Cloudera在3.0.0,Hortonworks在3.1.1,但我们将使用3.2.0或更高版本。请注意,MapR仍然与Hadoop 2基线绑定。MapR有自己的HDFS实现,它过去被称为MapR FS,但现在已更名为MapR XD。由于MapR XD不像Hadoop 2那样使用块复制,因此采用纠删码对他们来说没有意义。
总结

在本节中,我们学习了Hadoop架构,包括经典的Hadoop
012:Hadoop架构设计的核心原则 🏗️

在本节课中,我们将学习Hadoop架构背后的核心设计原则。这些原则是Hadoop能够高效处理海量数据的关键所在。
概述
Hadoop的设计遵循一系列核心原则,使其成为处理大数据的强大工具。这些原则包括突破磁盘读写瓶颈、采用横向扩展而非纵向扩展、将代码移至数据处、自动处理故障以及抽象分布式应用的复杂性。接下来,我们将逐一详细探讨这些原则。
突破磁盘读写瓶颈 💾
上一节我们介绍了Hadoop的整体架构,本节中我们来看看它是如何突破传统存储系统的读写瓶颈的。
存储容量在过去二十年呈指数级增长,但磁盘的读取速度并未同步提升。例如,1990年的1.4GB硬盘读取需要约5分钟,而2010年的1TB硬盘顺序读取则需要约2.5小时。这意味着,将数据读入内存进行处理是一个巨大的瓶颈。
Hadoop的HDFS通过并行读写解决了这个问题。在Hadoop中,可以有多台机器上的多个磁盘同时工作,从而实现并行读取。例如,使用100个磁盘并行工作,读取1TB数据可能仅需约2分钟。这种方式使Hadoop HDFS成功突破了磁盘读写速度的瓶颈。
横向扩展而非纵向扩展 📈
上一节我们了解了如何突破I/O瓶颈,本节中我们来看看Hadoop在系统扩展性上的设计思路。
系统扩展主要有两种方式:
- 纵向扩展:为单台服务器增加更多的CPU、内存或硬盘。
- 横向扩展:向现有集群中添加更多的服务器节点。
纵向扩展不仅更困难、更昂贵,而且受限于摩尔定律,其性能提升速度赶不上数据的爆炸式增长。此外,单台服务器的资源扩展存在物理上限。
Hadoop采用了横向扩展的策略。使用Hadoop,你可以从一个10个节点的集群开始,然后根据需要轻松地扩展到20个、40个甚至更多节点。这种策略更具灵活性和成本效益。
在Hadoop中,我们使用廉价的商用硬件。例如,一台配备32GB内存、1TB硬盘和四核CPU的服务器成本约为5000美元。将10台这样的服务器组成集群成本约为5万美元,要扩大集群容量,只需添加更多这样的服务器即可,而无需购买昂贵的高端服务器或超级计算机。
一个典型的数据中心由多个机架组成,每个机架放置着多台廉价的商用服务器,这些机架之间相互连接。Hadoop集群通常就部署在这样一个数据中心内。
将代码移至数据处,而非反之 🚚
上一节我们讨论了横向扩展的优势,本节中我们来看看Hadoop在数据处理模式上的革新。
传统的数据处理架构将系统分为处理节点和存储节点。当需要计算时,数据从存储节点被传输到处理节点,这容易导致网络瓶颈。
Hadoop采取了不同的方法。它在设计上将处理器和存储放在一起,即在同一台服务器上既运行处理逻辑(如任务追踪器和任务),也运行存储逻辑(如从本地磁盘读取数据的数据节点)。
以下是Hadoop数据处理模式的示意图:

在这种架构下,代码被移动到数据所在的位置进行处理,这被称为数据本地性。它最大限度地减少了数据在网络中的传输,从而显著提升了处理效率。

自动处理故障 🛡️
在拥有大量机器的集群中,故障是不可避免的。大型数据中心每周甚至每天都会发生机器故障。
这里涉及一个概念:平均故障间隔时间。假设一台服务器的MTTF是3年,那么对于一个拥有1000台服务器的集群,几乎每天都会有一台服务器发生故障。像谷歌、Facebook这样拥有数十万台服务器的公司,故障的发生会更加频繁。

Hadoop被设计为能够从容应对节点故障。因为数据被复制了多份,如果存有某份数据的服务器宕机,其他存有相同数据副本的服务器可以继续提供数据。同样,对于MapReduce任务,如果运行某个任务的服务器失败,其他空闲的服务器(或具有空闲任务槽的服务器)会自动接管该任务。
Hadoop故障处理机制示意图:

抽象分布式应用的复杂性 🔧

开发分布式应用程序涉及许多复杂性,如同步、配置管理等。Hadoop通过其组件处理了所有这些分布式计算的复杂方面。
Hadoop为开发者提供了一个简单且定义良好的接口,即MapReduce编程接口。开发者只需编写业务逻辑,而无需担心分布式计算中的各种底层复杂性,例如:
- 竞态条件
- 资源饥饿
- 处理流水线
- 数据分区

这使开发者能够从系统级的挑战中解放出来,专注于实现业务逻辑。
总结

本节课我们一起学习了Hadoop架构背后的五大核心原则:
- 突破磁盘读写瓶颈:通过并行I/O大幅提升数据吞吐量。
- 横向扩展而非纵向扩展:通过添加廉价商用服务器节点来灵活、经济地扩展集群。
- 将代码移至数据处:利用数据本地性减少网络传输,提升处理效率。
- 自动处理故障:通过数据复制和任务重新调度,实现高容错性。
- 抽象分布式应用的复杂性:为开发者提供简单的编程接口,使其专注于业务逻辑。
这些原则共同构成了Hadoop强大、可靠且易于扩展的大数据处理能力的基础。
013:Hadoop技术栈剖析 🗂️
在本节课中,我们将学习Hadoop生态系统中的众多项目。面对诸如Hive、Pig、HBase、Sqoop、Zookeeper、Ambari等层出不穷的项目,初学者可能会感到不知所措。本节的目标是梳理关键的Hadoop项目,理解哪些是通用工具,哪些是解决特定问题的专用工具,并对它们进行分类。我们还将了解如何在Apache孵化器网站上获取关于新兴Hadoop项目的信息。本节将选取几个关键项目进行概览,后续课程中我们会进行更深入的探讨。
Hadoop技术栈概览
你或许以为Apache Hadoop项目就是全部。但事实上,Hadoop生态系统包含一系列项目。Apache Hadoop位于中心,周围环绕着用于安全的Apache Sentry、用于NoSQL的HBase、用于数据导入的Sqoop、用于分析的Pig和Hive、用于监控的Ambari等众多项目。这个列表还在不断增长。
为了理解这个庞大的技术栈,我们可以将其中的项目进行分组。
项目分类详解
上一节我们提到了Hadoop生态系统的庞大,本节中我们来看看如何将它们系统地分类,以便更好地理解。
以下是Hadoop技术栈的主要分类:
- 数据集成:这类项目帮助你将数据移入或移出Hadoop集群。例如,Sqoop允许你在关系型数据库和Hadoop之间传输数据。Flume、Chukwa和Kafka则专注于从不同系统导入日志文件或事件流。
- 数据序列化:这类项目定义了数据在集群中的存储格式。Avro和Thrift允许你以高级格式存储数据,这种格式可以包含数据的模式(Schema)信息。它们还提供了生成代码类的机制,便于在应用程序中读写数据。
- 数据存储(NoSQL):有时你需要以支持实时访问的格式存储数据,以便进行增删改查(CRUD)操作。HBase就是这样一个构建在Hadoop之上的NoSQL数据库。Cassandra是类似的项目,它不直接依赖Hadoop,但常与Hadoop协同工作。
- 数据访问与分析:当数据进入集群后,你需要进行分析、聚合和计算。虽然可以编写MapReduce程序,但这需要Java开发技能。Pig和Hive允许你使用更高级的语言(如Pig Latin和HiveQL)进行分析,这些语言会被转换成MapReduce作业。此外,还有基于YARN的新计算框架,如用于图处理的Giraph、用于实时处理的Storm、以及Spark等。
- 管理与编排:管理一个包含上百台机器的集群是复杂的。Ambari是一个管理工具,用于安装、管理和监控Hadoop集群服务。Oozie用于协调工作流作业的执行。Zookeeper则提供分布式配置、同步和领导者选举等协调服务。
- 数据智能与安全:Mahout是一个构建在Hadoop MapReduce之上的Java机器学习库。对于安全,有Knox和Sentry等项目来保护Hadoop集群中的重要数据。
- 开发工具:HTT(Hadoop Development Tools)是一个Eclipse插件项目,旨在简化Hadoop应用的开发。
探索新项目:Apache孵化器

了解现有项目后,你可能会想知道如何跟进最新的技术。Apache孵化器网站是发现新兴Hadoop相关项目的绝佳去处。


访问 incubator.apache.org,你可以看到所有正在孵化的项目列表。其中一些项目与Hadoop紧密相关,例如用于交互式查询的Drill、用于流处理的Storm、以及高级语言Tez等。定期浏览这个页面,有助于你了解生态系统的最新动态。
关键项目深度概览
在分类的基础上,我们选取几个最具代表性的项目进行更详细的介绍。

Hive 🐝

Hive起源于Facebook。其动机是让非Java开发人员(如数据科学家)能够轻松访问HDFS中的数据。用户使用一种类似SQL的语言——HiveQL来表达查询,Hive会将其转换为MapReduce代码执行。Hive要求数据具有模式(Schema),你可以通过类似 CREATE TABLE 的语法在Hive中定义,也可以使用Avro等序列化格式,将模式信息直接存储在数据文件中。
Pig 🐷


Pig起源于Yahoo。其动机同样是简化数据科学家对HDFS中数据的访问。用户使用一种数据流语言——Pig Latin来编写加载、转换和存储数据的脚本,这些脚本同样会被转换成MapReduce作业。与Hive相比,Pig对模式的要求更灵活,有“Pig吃一切”的说法,意味着它能处理有模式或无模式的数据。Pig被认为比Hive更强大,能够编写更复杂的数据处理脚本。
HBase 🗃️
HBase也起源于Facebook,旨在解决Facebook消息收件箱的实时访问问题。它是一个构建在Hadoop之上的分布式、可扩展的列式NoSQL数据库,支持实时的增删改查(CRUD)操作,这与MapReduce的批处理模式不同。HBase的名称来源于“Hadoop database”,其设计遵循BASE原则(基本可用、软状态、最终一致性),与传统ACID数据库形成对比。
Sqoop 🔄
Sqoop起源于Cloudera。在客户咨询过程中,Cloudera发现普遍存在将数据从关系型数据库导入Hadoop处理,然后再导回的需求。Sqoop(取自“SQL”到“Hadoop”)正是为此而生。它支持在Hadoop和关系型数据库之间高效地批量传输数据,开箱即用支持JDBC数据源,配置简单。它也可以扩展以集成其他数据存储系统。
Flume 🌊
Flume同样起源于Cloudera。许多组织需要将遍布各处的Web服务器日志或消息数据收集到Hadoop集群中。Flume就是一个分布式的、可靠的、高可用的服务,用于高效地收集、聚合和移动大量日志数据进入HDFS。它通过配置Source(源)、Channel(通道)和Sink(接收器)来构建数据流。
Zookeeper 🦁
Zookeeper是一个集中式服务,用于维护集群的配置信息、实现分布式组件间的同步以及提供领导者选举等组服务。在分布式系统中,各机器需要统一的配置信息,Zookeeper提供了这样的中心化配置服务。例如,在HBase中,Zookeeper用于管理活跃的HMaster节点。
Avro 📄
Avro起源于Cloudera,旨在提供比Thrift或Protocol Buffers更好的数据序列化方案。它是一个富含数据结构的库,允许以紧凑的二进制格式存储数据,并使用JSON定义数据模式。Avro的一个关键特性是将数据模式与数据本身存储在一起,这使得Pig、Hive等工具能更容易地读取和理解数据。

Oozie ⏰

Oozie起源于Yahoo。Yahoo集群中每天运行着成千上万的作业,需要一种机制来管理和触发这些作业。Oozie就是一个工作流协调器,允许你基于时间(类似Cron)或数据可用性来触发Hadoop作业,从而管理复杂的工作流。工作流通常用有向无环图(DAG) 来描述。


Mahout 🐘
Mahout是一个基于Java、构建在Hadoop MapReduce之上的机器学习库。其名称在印地语中意为“驯象师”,寓意训练机器学习算法。它实现了可扩展的分布式机器学习算法,主要涵盖三大领域:聚类(Clustering)、分类(Classification)和协同过滤(Collaborative Filtering)。
Ambari 🖥️
Ambari起源于Hortonworks。它是一个基于Web的GUI工具,用于供应、管理和监控Apache Hadoop集群。你可以通过它选择要在集群上安装的服务(如Hive、Pig等),并集中管理所有服务。Ambari的诞生源于Hortonworks希望提供一个完全开源的管理和监控工具,这与一些商业发行版采用专有管理软件的策略不同。


总结

本节课中,我们一起学习了Hadoop生态系统的全貌。我们首先将繁杂的项目进行了系统分类,包括数据集成、序列化、存储、分析、管理、安全等类别。接着,我们介绍了如何通过Apache孵化器网站跟踪新兴项目。最后,我们对Hive、Pig、HBase、Sqoop、Flume、Zookeeper、Avro、Oozie、Mahout和Ambari等关键项目进行了深入的概览,了解了它们的起源、动机和核心功能。这为我们后续深入学习具体技术打下了坚实的基础。
014:Hadoop发行版本 🗂️


在本节课中,我们将学习Hadoop的发行版本。我们将首先了解开源Apache Hadoop发行版的现状,然后探讨市场上包括云平台提供商在内的各种商业发行版。最后,我们将决定在本课程中将使用哪种Hadoop发行版。
Hadoop发行版的问题场景
让我们先来看一个Hadoop发行版可能带来的问题场景。假设你想使用Hadoop,于是你访问 hadoop.apache.org,下载了最新版本的Apache Hadoop,安装、配置并启动它,然后开始使用。
一段时间后,你意识到想使用Apache HBase,这是一个构建在Hadoop之上的实时NoSQL数据库。于是你下载了Apache HBase,但随后发现它需要旧版本的Hadoop,因为它依赖于旧版本的HDFS。为了同时运行Hadoop和HBase,你不得不将Apache Hadoop降级到旧版本,好在这没有影响你现有的项目。

一切运行正常后,你又想下载Pig来进行数据分析。于是你下载了Pig,但发现Pig只兼容新版本的Apache Hadoop。如果你升级Hadoop,就会破坏HBase的运行。
这就是一个典型的困境。大约四五年前,当还没有商业发行版时,我开始使用Hadoop时就遇到了类似的问题。此外,还存在技术支持的问题:当出现问题时,你该找谁?
现状存在的问题
从构建、测试、打包到部署的整个流程来看,当前Hadoop项目及其所有子项目松散耦合的特性存在一些局限性。
以下是主要问题:
- 依赖项目版本测试不足:对于像Hive、Pig、HBase、Zookeeper等依赖项目的“主干”版本,缺乏足够的构建测试。
- 子项目版本测试不足:对于各个子项目的“主干”版本,缺乏充分的测试。
- 缺乏一致的Linux服务器打包:没有提供一个统一的Hadoop数据中心平台供大家使用。
- 缺乏多节点集群的功能测试:Hadoop是一个集群项目,需要在多台机器上运行。必须有人安装并测试,以确保它在集群上正常工作。


Hadoop发行版的解决方案

Hadoop发行版旨在解决Hadoop与其子项目之间的版本兼容性问题。它们的核心价值主张是提供一个经过全面测试、有技术支持、高可用的Hadoop发行版,其中包含Hadoop及相关项目,这样用户就不必担心软件间的兼容性问题。
市场上有多个供应商提供Hadoop发行版,包括开源的Apache Bigtop发行版,以及商业发行版,如Cloudera发行版、Hortonworks发行版、MapR发行版,还有云服务商提供的Amazon AWS Elastic MapReduce和Microsoft Azure HDInsight。

主要发行版详解
上一节我们概述了发行版的存在意义,本节我们来详细看看这些主要的发行版。
Apache Bigtop 🐘
Apache Bigtop 实际上是一个开源项目,专注于Apache Hadoop及其生态系统中所有子项目的开发、打包和测试。所有主要的商业发行商都在内部使用Bigtop来进行其平台的构建、测试和打包。然而,我个人并不了解有哪家公司或企业直接在生产中使用Apache Bigtop发行版。其官方网站是 bigtop.apache.org。值得注意的是,它在大约两年前从一个孵化器项目升级为了顶级项目。
Bigtop展示了构建一个完整的Hadoop生态系统所需的各种库,以及如何将其用于持续集成和平台构建测试。


Cloudera 发行版
Cloudera 提供了第一个商业Hadoop发行版。Cloudera在Hadoop打包方面所做的,类似于Red Hat对Linux平台的贡献。Cloudera发行版是100%开源的Hadoop发行版,但有一个小“转折”:Hadoop软件本身是100%开源的,但他们提供的用于运维管理的控制台不是开源的。


Cloudera的Hadoop发行版称为 CDH,即Cloudera Distribution for Hadoop。其官方网站是 cloudera.com。该网站提供了各种产品、专业服务、培训解决方案和合作伙伴信息,其博客也是很好的信息资源。
MapR 发行版
MapR 提供了第二个商业Hadoop发行版。它也是100%开源的Hadoop,但同样有“转折”:其文件系统实现(即HDFS实现)是基于专有的C++代码,并且其管理控制台也是专有的。MapR声称其C++实现比基于Java的HDFS实现快得多。
MapR的Hadoop发行版称为 M系列(如M3, M5, M7等)。其官方网站是 mapr.com。值得一提的是,MapR的CTO曾是Google文件系统(GFS)的关键开发者之一。
Hortonworks 发行版
Hortonworks 是第三个商业Hadoop发行版。它由前雅虎的Hadoop专家创立,这些专家曾直接参与Hadoop软件的开发。因此,它是从雅虎剥离出来的。Hortonworks是真正100%开源的Hadoop软件,没有任何“转折”。Hadoop软件本身是100%开源的,其用于运维的管理控制台(名为 Ambari)也是100%开源的。
Hortonworks的Hadoop发行版称为 HDP,即Hortonworks Data Platform。其官方网站是 hortonworks.com。
云平台发行版


除了独立的商业发行版,主要的云服务商也提供了集成的Hadoop服务。
- Amazon AWS Elastic MapReduce (EMR):亚马逊AWS将其云服务扩展至包含MapReduce,称为Elastic MapReduce。它是基于MapR发行版构建的,亚马逊AWS团队与MapR合作,在云平台上实现了Hadoop。它还与S3文件系统有特殊集成。用户也可以在自己的AWS基础设施即服务(IaaS)上安装自己的Hadoop发行版,但EMR提供了开箱即用的服务。其官方网址是AWS EMR服务页面。
- Microsoft Azure HDInsight:微软Azure也将其云服务扩展至包含MapReduce,称为HDInsight。为此,微软与Hortonworks合作,因此它是基于Hortonworks发行版构建的,运行在微软的云平台上。其官方网址是
azure.microsoft.com。



本课程的选择

那么,本课程将使用哪种发行版呢?
我们可以使用任何商业发行版,因为它们都提供有限节点的免费版本。但我不想在课程中偏袒任何特定的发行版。更重要的是,我希望你能理解Hadoop的内部工作原理,学习如何下载和安装最新版本,因为后续课程会引入新的Hadoop子项目,我希望你能亲身体验这个过程。
因此,在本课程中,我们将直接使用开源的Apache Hadoop发行版及其相关的子项目发行版。我会要求你在学习过程中随时下载这些发行版并使用它们。

总结

在本节课中,我们一起探讨了使用开源Apache Hadoop发行版可能面临的问题。我们详细了解了市场上各种商业发行平台,如Cloudera CDH、MapR M系列和Hortonworks HDP,以及一些云Hadoop平台提供商,如Amazon AWS Elastic MapReduce (EMR) 和 Microsoft Azure HDInsight。最后,我们决定在本课程中使用开源的Apache Hadoop发行版,以便更深入地学习和实践。
015:Hadoop文档体系 📚

在本节课中,我们将学习如何查找和利用Hadoop的各种文档资源。掌握这些资源对于深入理解和使用Hadoop至关重要。

概述
Hadoop拥有一个庞大且不断发展的生态系统,其文档来源多样。本节将系统性地介绍官方文档、Wiki、博客、书籍、会议以及最重要的——源代码本身。
官方文档
上一节我们介绍了Hadoop的基本概念,本节中我们来看看最权威的官方文档来源。
Apache Hadoop的官方文档位于其项目网站上。访问地址为 hadoop.apache.org/common/docs/release-number。如果不指定具体的版本号,页面会列出所有已发布版本的目录。

每个目录对应一个版本号,点击即可查看该特定版本的详细文档,其中包含命令指南、快速入门指南、HDFS指南和Hadoop流指南等丰富信息。
Wiki与最佳实践
除了官方文档,社区贡献的Wiki也是重要的知识库。

Apache Hadoop Wiki页面位于 wiki.apache.org/hadoop。该页面包含了许多在官方文档中可能找不到的实用信息、操作指南和最佳实践案例。

以下是部分Apache Hadoop子项目的Wiki链接,请注意这些链接可能会随时间变化。
技术博客

网络上有大量关于Hadoop的博客,但有两个来源特别值得关注。

以下是两个主要Hadoop发行商维护的官方博客,它们提供了大量实用技巧、使用案例和注意事项。
- Cloudera博客:地址为
blog.cloudera.com。该博客基于Cloudera支持客户的经验,提供了大量实用技巧,虽然侧重于其发行版,但也包含通用的Hadoop信息。博客内容按主题分类,并支持搜索。 - Hortonworks博客:地址为
hortonworks.com/blog。同样提供实用技巧和案例,侧重于Hortonworks发行版。作为市场的后来者,其内容量虽不及Cloudera,但也在不断增长。博客内容可按开发团队公告、Hadoop生态系统等分组查看。
推荐书籍

市面上有许多关于Hadoop的书籍,以下是对其中一些的评述。
- 《Hadoop权威指南》:作者Tom White(第三版)。这是一本必读的理论书籍,深入讲解了Hadoop及其子项目(如Hive、HBase、Pig)的内部工作原理。
- 《Hadoop实战》:作者Chuck Lam(2010年12月)。这是一本较老的书籍,未有新版,目前不再推荐购买。
- 《Pro Hadoop》:作者Jason Venner。个人不推荐此书,且内容已过时。
- 《Hadoop in Practice》:作者Alex Holmes。本书包含大量使用Hadoop的实用建议,非常值得拥有。
- 《Professional Hadoop Solutions》:由Rock`s Publications于2013年9月出版。是一本不错的书,但如果你已拥有《Hadoop in Practice》,则非必需。
- 《Apache Hadoop YARN》:作者Arun Murthy(Hortonworks联合创始人)。这是目前唯一一本专注于YARN技术的书籍,对此感兴趣则值得购买。
- 《Data-Intensive Text Processing with MapReduce》:作者Jimmy Lin和Chris Dyer。这是一本专注于MapReduce方面的免费PDF电子书,非常值得下载阅读。
- 《Hadoop For Dummies》:作为“达人迷”系列丛书,表明该主题很热门,此处不作评论。
- 《Hadoop MapReduce Design Patterns》:专注于算法设计。如果已有《Hadoop in Practice》,则非必需。
- 《Programming Pig》:作者Alan Gates。如果你要进行Pig编程,这是一本极好的书。
- 《Programming Hive》:关于Hive的优秀书籍。
- 《Hadoop Operations》:作者Eric Sammer。详细讲解了数据中心运维、Hadoop内部参数设置等,适合从事Hadoop运维的人员。
- 《HBase权威指南》:作者Lars George。这是学习HBase的首选书籍。
- 《HBase in Action》:一本看起来相当不错的书。
- 《HBase Design Patterns》:即将出版的新书。

社交媒体与会议

在Twitter上关注领域专家可以获取最新动态。

以下是一些值得在Twitter上关注的Hadoop领域专家,他们会不时分享关于大数据和Hadoop的最新动态。

- Cloudera公司的相关员工。
- Hortonworks公司的Arun Murthy, Alan Gates 和 Sanjay Radia。
- MapR公司的Ted Dunning。
- 讲师本人的Twitter账号。
参与行业会议是深入学习的好方法。
以下是两个主要的Hadoop会议。
- Hadoop World:由Cloudera组织,通常在纽约秋季举行。官网是
hadoopor.com。 - Hadoop Summit:由Hortonworks组织,通常在旧金山春季举行。官网是
hadoopsmit.org。


如果你打算深入Hadoop领域,强烈建议至少参加一次其中任何一个会议。
最重要的资源:源代码

最后,我们必须强调,Apache Hadoop文档的最佳来源是其源代码本身。
当你想了解某些功能背后的工作原理,而网页、文章、博客或书籍都无法解答时,直接打开Apache Hadoop源代码,阅读、跟踪、调试代码,是弄清事情真相的根本方法。

总结

本节课中我们一起学习了Hadoop的各种文档资源,包括官方文档(Docs)、Wiki、博客、书籍和会议。但请记住,最准确、最深入的Hadoop文档始终是其源代码本身。
016:Apache Hadoop架构与生态系统总结 🏗️
在本节课中,我们将回顾Apache Hadoop的架构与生态系统。我们将总结Hadoop的发展历程、核心架构原则、关键子项目、市场发行版以及进一步学习的资源。

概述
本节将对整个模块的内容进行总结。我们回顾了Hadoop的历史演变、其核心架构、丰富的生态系统项目、不同的商业发行版,并指明了获取更多信息的途径。
Hadoop的历史与演变
上一节我们介绍了Hadoop的背景,本节我们来总结其发展历程。我们看到了Hadoop如何随着时间的推移而成熟,以适应现代大数据处理的需求。其演变过程展示了从最初的设计到如今强大生态系统的成长路径。
Hadoop架构与核心原则
我们探讨了Hadoop的架构及其关键原则。正是这些原则使得Hadoop在今天的大数据处理中如此出色。
其核心设计基于以下理念:
- 分布式存储:数据被分割并存储在集群的多个节点上。
- 分布式计算:计算任务被分发到存储数据的节点附近执行。
- 容错性:系统能够自动处理节点故障,确保任务完成。
一个简单的MapReduce思想可以表示为:
输入数据 -> Map阶段(处理)-> Shuffle & Sort -> Reduce阶段(汇总)-> 输出结果
Hadoop生态系统与子项目
我们审视了Hadoop的各种子项目,并对它们进行了梳理和归类,以理解其生态系统。
以下是主要的项目分组:
- 数据存储:如HDFS(Hadoop Distributed File System)。
- 数据处理:如MapReduce、Spark。
- 数据查询:如Hive、Pig。
- 协调与管理:如ZooKeeper、YARN。
- 数据摄取与传输:如Flume、Sqoop。
- 数据库:如HBase。
Hadoop发行版
我们了解了市场上存在的各种Hadoop发行版,并分析了每个发行版的优缺点。
主要的发行版包括:
- Apache Hadoop:社区原版,完全开源。
- Cloudera Distribution (CDH):集成度高,企业功能丰富。
- Hortonworks Data Platform (HDP):完全开源,强调社区兼容性。
- MapR:提供替代性文件系统,强调性能和可靠性。
进一步学习资源
最后,我们学习了在哪里可以找到关于Hadoop的更多信息。
获取知识的渠道包括:
- 书籍:系统学习理论与最佳实践。
- 博客与社区:获取最新动态、技术文章和问题解答。
- Wiki与官方文档:查阅最权威的API和配置指南。
- 在线课程与教程:进行实践性的学习。

总结
在本节课中,我们一起学习了Apache Hadoop的全貌。我们总结了其历史演变,理解了其分布式架构的核心原则,梳理了庞大的生态系统项目,比较了不同的商业发行版,并掌握了继续深入学习的资源路径。这为后续实际使用和深入研究Hadoop奠定了坚实的基础。
017:Hadoop环境配置与运行导论 🚀
在本节课中,我们将学习如何在虚拟化环境中安装、配置并运行Hadoop。具体来说,我们会使用VirtualBox虚拟化软件,在其上运行Ubuntu Linux操作系统,并以伪分布式模式部署Hadoop 3 YARN。本模块将以动手演示为主,引导你完成本课程所需的所有环境设置。
模块概述 📋
我们将按顺序完成以下几个主要步骤:
- 设置VirtualBox虚拟化管理器。
- 在VirtualBox上安装并运行Ubuntu Linux操作系统。
- 在Ubuntu系统上安装和配置Hadoop 3 YARN。
其中,Hadoop 3的安装与配置是本模块的重点,我们将花费最多时间在此部分。
模块目标 🎯
虽然本模块会涉及VirtualBox和Ubuntu操作系统的设置,但我们的核心目标是在VirtualBox虚拟环境中完成Hadoop的安装、配置与运行。
在安装过程中,我会尽力解释每个步骤,但关于Hadoop架构、HDFS及Hadoop内部原理的许多细节,可能需要后续多个模块的学习才能完全清晰。目前,我们首要任务是学会在桌面环境上搭建并运行Hadoop。

上一节我们明确了本模块的学习路径和目标,接下来,我们开始第一步:设置虚拟化环境。
1. 设置VirtualBox 🖥️
VirtualBox是一款功能强大的虚拟化软件,它允许我们在现有操作系统(如Windows或macOS)上创建和运行虚拟机。
以下是安装VirtualBox的基本步骤:
- 访问VirtualBox官方网站。
- 下载适用于你当前桌面操作系统的安装程序。
- 运行安装程序,并遵循向导完成安装。
安装完成后,你便可以在VirtualBox中创建新的虚拟机。
成功安装VirtualBox后,我们需要一个操作系统来运行Hadoop。接下来,我们将在虚拟机中安装Ubuntu Linux。

2. 设置与运行Ubuntu Linux 🐧

Ubuntu是一种流行的Linux操作系统发行版。我们将使用Ubuntu 20.04 LTS版本,并将其安装在VirtualBox虚拟机中。
以下是获取和安装Ubuntu的步骤:
- 从Ubuntu官网下载Ubuntu 20.04 LTS的ISO镜像文件。
- 在VirtualBox中创建一台新的虚拟机。
- 在虚拟机设置中,指定刚才下载的Ubuntu ISO镜像作为启动盘。
- 启动虚拟机,并按照屏幕提示完成Ubuntu操作系统的安装。
安装完成后,你将拥有一个运行在VirtualBox中的完整Ubuntu Linux系统。
现在,我们已经在VirtualBox上成功运行了Ubuntu Linux操作系统。接下来进入本模块的核心环节:安装和配置Hadoop。
3. 在Ubuntu上设置Hadoop 3 YARN ⚙️
这是本模块最重要的部分。我们将在Ubuntu系统中安装Hadoop 3,并以伪分布式模式进行配置。在这种模式下,Hadoop的所有守护进程(如NameNode, DataNode, ResourceManager)都运行在单个节点上,模拟一个分布式集群,非常适合学习和测试。
以下是配置Hadoop 3 YARN的关键步骤概述:
- 安装Java:Hadoop依赖Java运行环境。使用以下命令安装OpenJDK:
sudo apt update sudo apt install openjdk-11-jdk - 下载并解压Hadoop:从Apache官网下载Hadoop 3.x的二进制包,并解压到指定目录,例如
/usr/local/hadoop。 - 配置环境变量:编辑
~/.bashrc文件,添加JAVA_HOME和HADOOP_HOME等环境变量。export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 export HADOOP_HOME=/usr/local/hadoop export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin - 修改Hadoop配置文件:主要配置
etc/hadoop/目录下的核心文件,如core-site.xml,hdfs-site.xml,mapred-site.xml,yarn-site.xml,设置本地文件系统路径、副本数等参数。 - 设置SSH免密登录:Hadoop脚本需要SSH权限来管理守护进程。生成SSH密钥并添加到授权列表。
ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys - 格式化HDFS:首次启动前,需要格式化Hadoop分布式文件系统。
hdfs namenode -format - 启动Hadoop集群:使用Hadoop自带的脚本启动所有服务。
start-dfs.sh start-yarn.sh - 验证安装:使用
jps命令查看运行的Java进程,并通过Web界面(如http://localhost:9870访问HDFS)确认服务正常运行。
总结 📝

本节课中,我们一起学习了搭建Hadoop开发环境的完整流程。我们从安装VirtualBox虚拟化软件开始,接着在其上部署了Ubuntu Linux操作系统,最后完成了Hadoop 3 YARN在伪分布式模式下的安装与基础配置。通过本模块的实践,你已经拥有了一个可以运行和测试Hadoop程序的本地环境,为后续深入学习大数据处理打下了坚实的基础。
018:VirtualBox安装与配置指南 🖥️

在本节课中,我们将学习虚拟化的基本概念,了解为何在Hadoop学习中需要虚拟化技术,并详细介绍如何下载、安装和初步配置Oracle VirtualBox软件,以便为后续安装Linux和Hadoop做好准备。
什么是虚拟化?
虚拟化是指创建IT基础设施的虚拟版本,而非物理实体。这意味着你可以拥有虚拟化的服务器,而非物理服务器实例。你的存储设备(如硬盘)和网络设备(如防火墙和路由器)都可以被虚拟化。更酷的是,你的操作系统也可以被虚拟化,这意味着你可以在Windows操作系统之上运行一个虚拟化的Linux操作系统。
虚拟化已成为IT行业的一大趋势,特别是在数据中心整合、维护、管理和监控方面。
为何在本课程中需要虚拟化?
我们关心虚拟化是因为我们想要运行Hadoop。虽然Hortonworks和Microsoft在Windows平台上运行Hadoop做得相当不错,但Hadoop在Linux平台上运行得最好。
由于大家的笔记本电脑或PC可能拥有不同的硬件和操作系统,我们希望通过虚拟化方式运行Linux,这样在我们进行项目和作业时,就能有一个统一的标准环境。
基本思路是:在你的PC或笔记本电脑硬件之上,可能已安装有Windows等操作系统。我们将在此之上安装一种虚拟化软件(例如VirtualBox),然后在VirtualBox之上安装虚拟化的Linux(如CentOS Linux),最后在Linux之上安装Hadoop。这样,我们就可以运行Hadoop守护进程,并使用Hadoop进行大数据处理。
市场上有哪些虚拟化软件?
市场上有VMware和Parallels等付费版本的虚拟化软件。此外,Oracle提供了一个开源版本的虚拟化软件,名为Oracle VirtualBox。这正是我们本课程将要使用的软件。
什么是VirtualBox?
VirtualBox是一款跨平台的虚拟化软件。这意味着它可以在Windows、Mac OS X、Linux甚至Solaris平台上使用。你可以为特定的操作系统下载相应版本的VirtualBox软件,安装后即可在该系统之上运行虚拟化的操作系统。
它可以安装在任何基于Intel或AMD的计算机上(大多数同学的电脑都符合此条件)。当然,它是Oracle提供的免费开源软件,我们非常喜欢这一点。
VirtualBox的一大优点是,你可以同时运行多个操作系统。例如,你可以在Mac上运行Linux,或在Linux服务器上运行Windows Server 2008,也可以在Windows PC上运行Linux。但在本课程中,我们将在你的现有操作系统(无论是什么)之上运行Linux。
关键VirtualBox术语
在继续之前,我们需要了解一些关键的VirtualBox术语。
- 主机操作系统:指你物理计算机(PC、笔记本电脑或服务器)上安装的操作系统。
- 客户机操作系统:指将在虚拟机内部、运行于你现有操作系统之上的虚拟化操作系统。
- 虚拟机:指VirtualBox(或任何虚拟化软件)为客机操作系统创建的特殊运行环境。你甚至可以创建虚拟化网络,让所有虚拟机在该特定网络内通信。
- 增强功能:这是一个需要安装在虚拟机内部的特殊软件,是VirtualBox的一个独特概念。在客机操作系统中安装增强功能可以提升其性能,并提供一些额外功能,例如端口转发和共享USB驱动器。
VirtualBox的主要功能
以下是VirtualBox的一些关键功能:
- 共享文件夹:允许在客机和主机操作系统之间共享文件夹。
- 端口转发:意味着你可以从主机操作系统连接到客机操作系统中的端口,反之亦然。
- 64位仿真:支持64位系统。
- USB设备支持:你可以将USB驱动器插入主机计算机,并在客机操作系统中识别和使用该硬件。
VirtualBox提供了许多很酷的功能。


下载VirtualBox
如果你想下载并安装VirtualBox,可以访问 virtualbox.org 网站。

如图所示,你可以获取适用于大多数主流操作系统的VirtualBox版本,包括Windows、OS X、Linux和Solaris。

当然,我将下载适用于OS X的版本(实际上我已经完成下载并安装了)。本课程我们将使用VirtualBox 4.3版本,如果有更新的版本,你也可以使用。
有些同学可能已经安装了Linux,可能会问为什么需要在Linux之上再通过虚拟化软件运行Linux。其实并非必须如此。如果你对Linux相当熟悉,可以直接在你的Linux系统上运行Hadoop。但在虚拟化环境中运行的优势在于,这是一个受控的环境。我将展示你需要安装的不同软件,这样你就能与班上其他同学保持环境一致。这是使用虚拟化的唯一优势。但如果你确实不想安装VirtualBox和虚拟机,而想直接运行Linux,也是可以的。


安装VirtualBox
下载VirtualBox后,你可以双击安装文件(在Mac上是DMG文件)。只需双击下载的软件,然后按照安装过程进行操作。
安装程序会显示一个介绍窗口,说明VirtualBox的版本。然后,它会显示你的硬盘信息。接着点击继续,在安装位置处,我通常保持默认设置,这样它就会安装在常规位置(无论是Windows、Linux还是Mac操作系统)。然后开始安装VirtualBox,整个过程只需几秒钟即可完成。整个安装过程应该不超过几分钟。
安装完成后,你的桌面上应该会出现一个VirtualBox图标。双击该图标即可启动VirtualBox。

启动VirtualBox后,你会看到应用程序界面。顶部会显示创建新虚拟机的方式,也可以更改虚拟机的设置。左侧窗格会列出已有的虚拟机。如果是全新安装VirtualBox且尚未创建任何虚拟机,这个列表将是空的。图中显示的“CentOS”虚拟机是我已经安装好的,所以你才会看到它。

总结
在本节课中,我们一起学习了虚拟化的概念,包括一些关键术语,如主机操作系统、客机操作系统和虚拟机。由于本课程将使用VirtualBox,我们学习了如何在你的主机计算机上下载和安装VirtualBox,以便为后续安装和配置客机Linux操作系统、运行Hadoop做好准备。
019:在VirtualBox中部署Ubuntu Linux虚拟机 🖥️
在本节课中,我们将学习如何在VirtualBox虚拟化软件上,下载、安装并配置Ubuntu Linux操作系统。由于Hadoop在Linux系统上运行效果最佳,因此我们需要一个Linux发行版来运行Hadoop。
概述
我们将分步完成在VirtualBox上创建并配置Ubuntu Linux虚拟机的全过程。这包括下载系统镜像、创建虚拟机、分配资源、安装系统以及安装增强功能。
选择Linux发行版
市场上有多种Linux发行版可供选择。例如,有免费的CentOS发行版,也有另一个免费开源的Ubuntu发行版,还有需要授权的Red Hat发行版。本课程将使用免费的Ubuntu版本。
在之前的课程中,我曾使用过CentOS。因此,在未来的某些模块中,您可能会看到部分章节是在CentOS上完成的。但两者的基本命令是相同的,只是图形用户界面有所不同。
关于Ubuntu的一个有趣事实是,它的命名源于非洲的“Ubuntu”哲学,意为“人道待人”,蕴含着“我因你而存在”的寓意。
下载Ubuntu
Ubuntu的官方网站是 ubuntu.com。访问该网站,点击“下载”进入下载页面,然后下载64位版本的Ubuntu LTS软件。这是一个大约2.88 GB的ISO文件,下载后即可用于安装。
接下来,我将先讲解理论步骤,稍后进行实际操作演示。
安装Ubuntu的六个步骤
以下是安装Ubuntu的六个核心步骤。我们将先通过截图讲解理论,然后进行实际操作演示。
- 在VirtualBox中创建新的虚拟机(即基本配置)。
- 挂载从
ubuntu.com下载的ISO镜像文件。 - 为虚拟机分配更多CPU资源。由于我们是在宿主机操作系统上运行客户机操作系统,需要为Ubuntu分配足够的CPU,以确保其运行流畅,便于进行大数据处理。
- 从CD启动虚拟机。
- 安装客户机操作系统,即实际安装Ubuntu。
- 安装VirtualBox增强功能。这能实现一些实用功能,例如共享文件夹、全屏窗口或端口转发等。
第一步:创建新虚拟机
启动VirtualBox,点击“新建”图标。在弹出的对话框中:
- 在“名称”栏输入:
HD server(这是我们虚拟机的名称)。 - 在“类型”下拉列表中选择:
Linux。 - 在“版本”下拉列表中选择:
Ubuntu (64-bit)。 - 点击“继续”。


在下一个屏幕中,设置虚拟机的内存大小。默认是1024 MB,我们将其设置为 4096 MB,然后点击“继续”。
接下来,选择“现在创建虚拟硬盘”,点击“创建”。选择虚拟硬盘文件类型为 VDI,点击“继续”。选择“动态分配”存储方式,点击“继续”。设置硬盘大小,默认是10 GB,我们将其改为 25 GB,然后点击“创建”。
至此,一个基础的虚拟机(尚未安装操作系统)就创建完成了,列表中会出现名为“HD server”的条目。
第二步:挂载ISO镜像

确保“HD server”条目被选中(通常默认已选中)。右键点击它,选择“设置”。在弹出的设置窗口中,点击“存储”图标。在存储设置界面,点击“控制器:IDE”下的“空”光盘图标。然后,点击右侧属性栏中的光盘图标,选择“选择虚拟光盘文件...”,导航到您下载Ubuntu ISO文件的文件夹(通常是“下载”文件夹)并选择该文件。点击“确定”后,ISO镜像就挂载好了。



第三步:分配更多CPU资源

再次右键点击“HD server”并进入“设置”。点击“系统”,然后选择“处理器”选项卡。您会看到一个滑块,用于调整处理器数量。根据您宿主机的性能,尽可能将滑块向右拖动(变为绿色),为虚拟机分配足够的计算能力。完成后点击“确定”。


第四步:从CD启动并安装系统

选中“HD server”,点击“启动”按钮。这将启动Ubuntu操作系统的安装过程。


安装程序会询问语言,默认是“English”,点击“安装Ubuntu”。再次确认语言为“English”,点击“继续”。选择“正常安装”,点击“继续”。在安装类型界面,选择“清除整个磁盘并安装Ubuntu”(请放心,这只会清除虚拟磁盘,不会影响您的物理硬盘),点击“现在安装”,然后点击“继续”。
接下来设置时区,根据您的地理位置选择,例如“New York”,点击“继续”。现在需要创建登录账户:
- 您的姓名和计算机名:输入
HD server。 - 用户名:输入
hdadmin(代表Hadoop管理员)。 - 密码:输入
hdadmin(系统可能会提示密码强度弱,但作为本地学习环境可以接受)。 - 确认密码:再次输入
hdadmin。


点击“继续”,安装过程正式开始。这可能需要5到10分钟,请耐心等待。

安装完成后,系统会提示重启。点击“现在重启”。虚拟机重启后,按回车键继续。在登录界面,点击用户 hdadmin,输入密码 hdadmin 即可登录。

首次登录后,系统可能会询问一些初始设置,您可以点击“下一步”、“跳过”或“完成”来快速通过。


第五步:安装系统更新



登录后,系统可能会提示有可用的软件更新。建议点击“立即安装”。系统会要求输入密码进行授权,输入 hdadmin 并点击“认证”。更新安装完成后,系统可能需要重启。


第六步:安装VirtualBox增强功能
要安装增强功能,请点击VirtualBox窗口顶部的菜单栏“设备”,然后选择“安装增强功能...”。
此时,在Ubuntu桌面上可能会出现一个光盘图标。点击它,系统会询问是否打开,选择“是”。这会打开一个包含增强功能安装文件的窗口。双击运行名为 VBoxLinuxAdditions.run 的文件(或类似名称)。系统会询问是否运行,点击“运行”。再次输入密码 hdadmin 进行授权。
安装完成后,终端窗口会显示“Press Return to close this window”的提示,按回车键关闭窗口。
为了使增强功能生效,需要重启虚拟机。点击屏幕右上角的三角图标,选择“关机”,然后在弹出的对话框中选择“重启”。
重启后,您就可以使用增强功能了。例如,可以点击VirtualBox窗口顶部的“视图”菜单,选择“切换至全屏模式”,让Ubuntu虚拟机充满整个屏幕。
实际操作演示
现在,让我们进行实际操作。
首先,访问 ubuntu.com,点击“下载”,选择最新的20.04 LTS版本,开始下载64位的ISO文件。


接着,打开VirtualBox,点击“新建”。输入虚拟机名称为 HD server,类型为 Linux,版本为 Ubuntu (64-bit)。将内存设置为 4096 MB。创建虚拟硬盘,选择 VDI 格式和“动态分配”,大小设置为 25 GB。
创建完成后,右键点击新虚拟机进入“设置”。在“存储”中,将下载好的Ubuntu ISO文件挂载到虚拟光驱。在“系统”->“处理器”中,为虚拟机分配更多的CPU核心。

点击“启动”开始安装。按照屏幕提示选择语言、时区,创建用户 hdadmin(密码也为 hdadmin),计算机名设为 HD server,并完成安装。

安装完成后,安装系统更新。最后,通过“设备”->“安装增强功能...”来安装VirtualBox增强工具,并重启虚拟机。

Ubuntu使用小技巧

- 启动终端:点击屏幕左下角的“显示应用程序”(9个点图标),搜索“terminal”并打开。您还可以右键点击终端图标,选择“添加到收藏夹”,方便以后快速启动。
- 调整任务栏位置:进入“设置”->“外观”,可以将任务栏(Dock)的位置从左侧调整到底部。
- 管理虚拟机状态:您可以直接关闭VirtualBox窗口,选择“保存机器状态”,这样下次启动时虚拟机会快速恢复到关闭前的状态,无需完全重启。
- 解决显示问题:如果遇到全屏显示问题(如黑屏),可以尝试在虚拟机设置中,进入“显示”选项,增加“显存大小”。


总结


在本节课中,我们一起学习了如何在VirtualBox上逐步设置Ubuntu Linux虚拟机。我们涵盖了从下载ISO镜像、创建和配置虚拟机、安装Ubuntu操作系统,到安装VirtualBox增强功能的完整流程。现在,您已经拥有了一个可以运行Hadoop的Linux环境,为后续的大数据处理学习做好了准备。
020:Hadoop 3 YARN环境搭建 🛠️
概述
在本节课中,我们将学习如何在单个虚拟机(VM)上,基于CentOS Linux系统,搭建一个伪分布式的Hadoop 3 YARN环境。我们将完成从安装、配置到启动所有核心守护进程(Daemons)的全过程,并最终通过运行一个示例程序来验证安装是否成功。
前期准备回顾
在之前的章节中,我们学习了如何安装和设置VirtualBox虚拟机。接着,我们学习了如何在VirtualBox上安装和配置Linux系统。在本节中,我们将进入核心部分——如何在该CentOS Linux机器上搭建Hadoop YARN。
我们的目标是让所有Hadoop核心组件守护进程在一台机器上启动并运行。我们也将测试安装,以确保一切设置正确。
本节教学主要通过动手演示进行。本节内容更侧重于Hadoop的操作层面,即安装过程。我会尽力描述我们在做什么。真正的目标是安装和配置,因此我可能无法详尽解释所有守护进程和配置方面的细节,但我保证我们将在后续课程中更详细地介绍其架构。
因此,当你完成本节时,可能不会透彻理解每个组件的具体功能,但这会为你打下良好基础。当我们开始讨论HDFS及其架构和内部原理、YARN及其架构和内部原理、MapReduce及其架构和内部原理时,你将开始将这些知识点串联起来。
最后,请记住本课程的目标是让你对使用Hadoop进行大数据处理有一个全面的了解,包括安装、架构、操作、编程和分析。


Hadoop安装方式
为了安装Hadoop,我们不会依赖任何专有软件(如Cloudera Manager)或开源工具(如Ambari)。我们将进行纯手动的安装,一切从零开始。这将让我们更好地了解底层发生了什么,这对你来说将是一次宝贵的学习经历。

一旦安装Hadoop,需要对其进行配置。Hadoop可以配置为以下三种模式之一运行:
1. 独立模式
在这种模式下,没有Hadoop守护进程。所有Hadoop组件都运行在客户端的一个JVM内。这非常适合测试目的,但你无法真正了解Hadoop的内部原理和架构。
2. 伪分布式模式
所有守护进程都在运行,但它们运行在单个节点(即一台机器或服务器)上。其优点在于,你可以理解Hadoop架构的工作原理以及它们之间的相互关系。这是一个绝佳的学习环境。
3. 集群或完全分布式模式
在这种模式下,所有守护进程运行在多台主节点和从节点机器上。这是一个多机器设置或配置。这通常是生产环境中的做法。
在本课程中,因为我们想学习Hadoop内部的工作原理,所以我们将像之前所说,在伪分布式模式下安装Hadoop。这是一个学习Hadoop的好方法。
Hadoop 3 YARN 核心组件
我们之前简要介绍过各种Hadoop组件。回顾一下,它们包括:
- NameNode:分布式文件系统的大脑。
- Secondary NameNode:一个备份进程,允许你在NameNode崩溃时从磁盘恢复和重建元数据镜像。
- DataNode:NameNode的主力,负责在文件系统中存储和检索数据。
- ResourceManager:管理集群中的资源(CPU、内存、存储和磁盘)。
- NodeManager:ResourceManager的主力。ResourceManager向NodeManager发送信息,反之亦然,因此ResourceManager知道特定服务器内所有资源的状态。
- JobHistoryServer:跟踪在MapReduce架构中运行的所有作业。
现在,让我们开始设置Hadoop,以便启动所有这些不同的守护进程。
在本周模块的课程网站上,有一份名为 “配置单节点Hadoop 3 YARN伪分布式集群的步骤” 的文档。我们将以此文档为基础,在单个虚拟机中安装Hadoop YARN。
动手实践:安装与配置
现在,让我们开始实际操作。
步骤 1:登录虚拟机
首先,登录到我们的HD Server虚拟机。用户ID是 hdadmin,密码也是 hdadmin。
登录后,虚拟机已准备就绪。现在开始安装Hadoop。
步骤 2:安装OpenSSH服务器
Hadoop依赖SSH来启动其各个守护进程。因此,我们需要安装OpenSSH服务器。
打开终端,执行以下命令:
sudo apt install openssh-server
系统会提示输入密码以获取root权限。输入密码 hdadmin,并在询问是否安装时输入 yes。
安装完成后,可以运行 systemctl status ssh 来检查SSH服务是否正在运行。
步骤 3:设置免密码SSH登录
我们需要设置SSH密钥,以便Hadoop进程可以无需密码相互通信。
-
生成SSH密钥对:
ssh-keygen -t rsa连续按回车键接受默认设置。
-
将公钥添加到授权密钥文件:
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys -
设置正确的权限:
chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys

- 将密钥添加到SSH代理并测试登录本机:
首次连接时会询问是否信任主机指纹,输入ssh-add ssh localhostyes。之后应能无需密码登录。输入exit退出。


- 对主机名
hdserver也进行同样操作(因为我们的机器主机名是hdserver):
同样地,首次输入ssh hdserveryes信任。之后退出再登录,应不再需要密码。
步骤 4:创建Hadoop安装目录
我们需要创建一个目录来存放Hadoop及其相关项目。
-
切换到root用户:
sudo su - -
创建目录并设置用户组和权限:
mkdir /usr/local/hadoop groupadd hdgroup chown hdadmin:hdgroup /usr/local/hadoop -
退出root用户:
exit
步骤 5:下载Hadoop
有两种方式下载Hadoop:访问 Hadoop官网 下载,或使用 wget 命令。我们使用 wget。
-
进入下载目录:
cd ~/Downloads -
下载Hadoop 3.3.1 二进制包:
wget https://downloads.apache.org/hadoop/common/hadoop-3.3.1/hadoop-3.3.1.tar.gz下载过程可能需要一些时间(约20分钟)。
步骤 6:安装Hadoop
下载完成后,将文件解压到我们创建的安装目录。
cd /usr/local/hadoop
tar -zxvf ~/Downloads/hadoop-3.3.1.tar.gz
步骤 7:设置Hadoop环境变量
我们需要设置 HADOOP_HOME 环境变量。
-
切换到root用户:
sudo su - -
创建环境变量配置文件:
echo ‘export HADOOP_HOME=/usr/local/hadoop/hadoop-3.3.1‘ > /etc/profile.d/hadoop.sh echo ‘export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin‘ >> /etc/profile.d/hadoop.sh
步骤 8:安装Java JDK
Hadoop需要Java环境,至少需要JDK 1.8。
-
安装OpenJDK 8:
apt install openjdk-8-jdk -
设置
JAVA_HOME环境变量:echo ‘export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64‘ > /etc/profile.d/java.sh -
退出root用户并注销当前会话,使环境变量生效:
exit logout然后重新登录。
步骤 9:验证环境变量
重新登录并打开新终端,验证环境变量是否已正确设置。
echo $HADOOP_HOME
echo $JAVA_HOME
echo $PATH
确保路径中包含Hadoop的 bin 和 sbin 目录。
步骤 10:创建HDFS数据目录
为NameNode、Secondary NameNode和DataNode创建数据存储目录。

sudo mkdir -p /usr/local/hadoop/data/nn # NameNode
sudo mkdir -p /usr/local/hadoop/data/snn # Secondary NameNode
sudo mkdir -p /usr/local/hadoop/data/dn # DataNode
sudo chown -R hdadmin:hdgroup /usr/local/hadoop/data
步骤 11-15:配置Hadoop文件
现在,我们需要编辑Hadoop的几个核心配置文件。所有配置文件都位于 $HADOOP_HOME/etc/hadoop/ 目录下。
以下是需要配置的文件列表及简要说明:
core-site.xml:核心Hadoop配置。hdfs-site.xml:HDFS分布式文件系统配置。mapred-site.xml:MapReduce作业配置。yarn-site.xml:YARN资源管理配置。hadoop-env.sh:Hadoop运行环境脚本。
你可以使用 vi 或 gedit 编辑器来修改这些文件。具体配置内容请严格遵循课程提供的 “配置单节点Hadoop 3 YARN伪分布式集群的步骤” 文档。配置完成后,可以使用 xmlwf 命令检查XML文件的格式是否正确。
关键配置示例 (hadoop-env.sh):
在这个文件中,你需要找到并设置 JAVA_HOME,以及调整Hadoop堆内存大小等参数。
步骤 16:格式化HDFS文件系统
在启动HDFS之前,需要像格式化磁盘一样格式化分布式文件系统。
hdfs namenode -format
如果看到 “Shutting down NameNode at hdserver” 之类的成功信息,说明格式化成功。
步骤 17-23:启动Hadoop守护进程
现在,按顺序启动所有Hadoop守护进程。
-
启动HDFS相关守护进程:
hdfs --daemon start namenode hdfs --daemon start secondarynamenode hdfs --daemon start datanode -
启动YARN相关守护进程:
yarn --daemon start resourcemanager yarn --daemon start nodemanager -
启动MapReduce历史服务器:
mapred --daemon start historyserver -
使用
jps命令验证所有守护进程是否都在运行:jps你应该能看到
NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager和JobHistoryServer进程。
如果任何守护进程启动失败,可以检查 $HADOOP_HOME/logs/ 目录下对应的日志文件来排查问题。
步骤 24:访问Web管理界面
Hadoop提供了Web界面来监控集群状态。
- HDFS NameNode UI:在浏览器中访问
http://localhost:9870。这里可以浏览HDFS文件系统,查看数据节点状态等。 - YARN ResourceManager UI:访问
http://localhost:8088。这里可以查看和管理提交的作业。 - MapReduce JobHistory UI:访问
http://localhost:19888。这里可以查看已完成的作业历史。
验证安装:运行示例作业
为了确保我们的Hadoop环境完全正常工作,让我们运行一个经典的MapReduce示例程序——单词计数(WordCount)。
步骤 1:准备测试数据
-
下载一个文本文件(例如,古登堡计划中的《白鲸记》):
cd ~/Downloads wget http://www.gutenberg.org/files/2701/2701-0.txt -
在HDFS中创建一个输入目录,并将文件上传到HDFS:
hdfs dfs -mkdir /input hdfs dfs -copyFromLocal ~/Downloads/2701-0.txt /input/moby-dick.txt

- 验证文件是否已上传:
你也可以在NameNode的Web界面 (hdfs dfs -ls /inputhttp://localhost:9870) 中浏览文件系统进行确认。
步骤 2:运行WordCount作业
使用Hadoop自带的示例JAR包运行单词计数程序。
yarn jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.1.jar wordcount /input /output
这个命令的含义是:使用YARN运行 wordcount 程序,处理HDFS中 /input 目录下的所有文件,并将结果输出到HDFS的 /output 目录。
作业运行过程中,你可以在YARN的Web界面 (http://localhost:8088) 上看到作业执行状态。
步骤 3:查看结果
作业完成后,查看输出结果。

hdfs dfs -cat /output/part-r-00000 | head -20
这将显示输出文件的前20行,内容格式为 单词<tab>出现次数。
你还可以查找特定单词的出现次数:
hdfs dfs -cat /output/part-r-00000 | grep -i “whale”
同样,你可以在NameNode的Web界面中看到新生成的 /output 目录及其中的结果文件。



(附加)安装NetBeans IDE(可选)
为了后续编写Hadoop Java应用程序,我们可以安装一个集成开发环境(IDE)。这里演示如何安装Apache NetBeans。

- 从 NetBeans官网 下载二进制ZIP包(选择LTS版本)。
- 将ZIP包解压到主目录:
cd ~ jar -xvf ~/Downloads/apache-netbeans-*.zip - 进入bin目录并赋予执行权限:
cd ~/netbeans/bin chmod +x netbeans - 启动NetBeans:
你可以创建桌面快捷方式以便日后使用。./netbeans

总结
在本节课中,我们一起完成了Hadoop 3 YARN伪分布式环境的搭建。我们深入实践,完成了以下关键步骤:
- 环境准备:安装了必要的SSH服务并配置了免密码登录。
- 软件安装:下载并解压了Hadoop,安装了必需的Java JDK。
- 系统配置:设置了环境变量,并详细配置了Hadoop的核心XML文件和环境脚本。
- HDFS初始化:格式化了HDFS分布式文件系统。
- 服务启动:按顺序成功启动了HDFS、YARN和MapReduce的所有核心守护进程。
- 安装验证:通过Web界面监控集群状态,并通过运行一个完整的MapReduce单词计数示例作业,验证了整个Hadoop环境的功能完整性。
现在,你已经拥有了一个可以正常工作的Hadoop学习环境。虽然目前可能对某些配置细节和组件间交互的理解还不够深入,但这次动手实践为你后续学习HDFS架构、YARN原理和MapReduce编程模型奠定了坚实的操作基础。在接下来的课程中,我们将基于这个环境,深入探讨Hadoop各个组件的内部工作原理。
021:Hadoop环境配置总结 🎯

在本节课中,我们将回顾并总结如何配置和运行Hadoop环境。我们通过一系列实践步骤,学习了从虚拟机设置到Hadoop伪分布式模式安装与运行的全过程。
上一节我们介绍了Hadoop的安装与配置,本节中我们来对整个模块的学习内容进行总结。
我们通过动手实践,完成了以下三个主要步骤。
以下是本模块涵盖的核心操作:
- 设置VirtualBox虚拟机。
- 在VirtualBox上安装CentOS Linux系统。
- 在Linux系统上以伪分布式模式安装、配置并运行Hadoop。
通过这些步骤,我们成功搭建了一个可运行的Hadoop开发环境。希望本模块能让你对Hadoop的实际操作方面有所了解。
更重要的是,你现在已经熟悉了构成Hadoop的各个组件。

本节课中我们一起学习了Hadoop环境的完整配置流程,从虚拟机基础设置到Hadoop核心服务的启动。你现在已经拥有了一个可以开始进行大数据处理实验的本地Hadoop平台。
022:HDFS架构导论 🗂️
在本节课中,我们将学习Hadoop分布式文件系统(HDFS)的架构。我们将探讨构成HDFS的各个组件及其交互方式,从而深入理解HDFS背后的原理,包括其架构设计以及对大数据的实际适用性。我们将特别关注HDFS如何存储数据,以及如何在机架内的服务器之间进行数据复制,以提供一个高可用、可扩展的分布式文件系统。

课程概述 📋
本模块将涵盖以下主题:首先,我们将探讨HDFS的核心架构,包括其守护进程及其交互。接着,我们将在虚拟机中进行HDFS架构的动手演示。然后,我们会研究一些高级主题,例如如何为集群确定块大小,以及在单机架和多机架系统中的复制策略。最后,我们将从架构角度评估Hadoop。
本模块的目标是:描述HDFS的架构、组件和交互;通过动手演示展示HDFS架构;解释HDFS为何以及如何成为一个高可用、可扩展的大数据平台;并最终阐明HDFS的架构能力。
HDFS核心架构 🏗️
上一节我们概述了本课程的内容,本节中我们来看看HDFS的核心架构。HDFS主要由两个关键守护进程组成:NameNode和DataNode。
NameNode是HDFS的“大脑”,负责管理文件系统的命名空间(如目录树、文件元数据)并协调客户端的访问。DataNode则是“劳动力”,负责在物理磁盘上存储实际的数据块,并执行数据块的读写操作。
以下是HDFS架构中的核心组件及其职责:
- NameNode:管理文件系统元数据(如文件名、目录结构、块位置)。它不存储实际数据。
- DataNode:存储和检索数据块。定期向NameNode发送心跳信号和块报告。
- Secondary NameNode(注意:它不是热备份节点):定期合并NameNode的编辑日志和文件系统镜像,以辅助NameNode工作,防止编辑日志过大。
- 客户端:通过与NameNode和DataNode交互来读写文件的应用程序或用户。
HDFS架构动手演示 💻
理解了核心组件后,本节我们将在虚拟机环境中进行一个动手演示,直观地观察HDFS架构的运行。
我们将启动HDFS服务,查看NameNode和DataNode的进程状态,并通过一个简单的文件操作(如上传文件)来展示客户端、NameNode和DataNode之间的交互流程。这有助于巩固对理论知识的理解。
高级主题:块大小与复制策略 ⚙️
在了解了HDFS的基本运行方式后,本节我们来探讨两个影响集群性能和可靠性的高级配置:块大小和复制策略。
块大小决定了HDFS中文件被分割成的每个数据块的大小。默认块大小通常是128MB或256MB。选择合适的块大小需要在减少NameNode元数据开销和优化数据传输效率之间取得平衡。公式上,这通常是一个基于集群规模和典型文件大小的经验性配置。
复制策略确保了数据的可靠性。HDFS默认将每个数据块复制3份,存储在不同的DataNode上。在单机架环境中,复制策略相对简单。但在多机架集群中,HDFS采用机架感知策略来优化复制放置,通常的策略是:
- 第一个副本放在客户端所在的节点(如果客户端是集群的一部分)或随机节点。
- 第二个副本放在不同机架的一个节点上。
- 第三个副本放在与第二个副本相同机架内的另一个节点上。
这种策略在数据可靠性、读取带宽和写入带宽之间提供了良好的权衡。在代码配置中,这通常通过 dfs.replication 和网络拓扑脚本来实现。
从架构角度评估HDFS ✅
最后,基于我们之前对架构、演示和高级主题的学习,本节我们将从架构设计的角度总结评估HDFS。
HDFS的架构使其非常适合大数据处理,主要体现在:
- 高容错性:通过数据块多副本机制,单个节点或磁盘故障不会导致数据丢失。
- 高吞吐量:数据被分割成大块并并行读写,优化了批量数据处理的吞吐量。
- 可扩展性:可以通过简单地增加DataNode来线性扩展存储容量和计算能力。
- 适合一次写入、多次读取的模式:这契合了大多数数据分析场景。
然而,它也有其设计局限,例如不适合低延迟的数据访问或大量小文件的存储。
课程总结 🎯

本节课中,我们一起学习了HDFS的架构。我们从核心组件NameNode和DataNode入手,理解了它们各自的角色和交互方式。接着通过动手演示观察了架构的实际运行。然后,我们深入探讨了块大小配置和跨机架的数据复制策略这两个高级主题。最后,我们从整体上评估了HDFS架构如何使其成为一个强大、可扩展且容错的大数据存储平台。掌握这些原理是有效使用和管理Hadoop生态系统的基础。
023:HDFS架构解析 🏗️



在本节课中,我们将深入学习Hadoop,并详细探讨HDFS的架构。我们将了解HDFS的关键特性、适用场景、不适用场景,以及构成HDFS的各个组件和守护进程的角色与交互。最后,我们将学习如何管理HDFS,例如格式化文件系统以进行初始化,以及启动和停止守护进程。

HDFS概述
Hadoop集群是物理硬件和逻辑软件的结合体。物理层面是硬件,通常是一组安装在机架上的商用服务器,一个数据中心内可能有多个机架。每台服务器本身可能拥有多个独立的磁盘。Hadoop HDFS将跨服务器、跨机架的存储磁盘统一起来,使其看起来像一个单一的文件系统,尽管其底层是完全分布式的。


什么是HDFS?
HDFS是一个专门的文件系统。它并非为通用目的而设计,不像我们操作系统磁盘之上的传统文件系统(我们称之为原生文件系统)。HDFS实际上运行在原生文件系统之上。例如,你可能有一台服务器,服务器有磁盘,你在上面安装操作系统,为所有磁盘创建一个文件系统(如EXT3、EXT4或XFS),然后Hadoop运行在这个原生文件系统之上。它提供了跨机器、跨文件系统的统一视图。
HDFS是一个“一次写入”的文件系统。换句话说,文件一旦写入,你就不能真正改变其内容。你可以在文件末尾追加数据,但不能进入文件中间更改数据。如果你想这样做,需要创建一个全新的文件,重新复制所有数据,进行更改,然后追加其余部分。
HDFS具有容错性和高可用性。当你将数据放入HDFS时,它会在多台机器上进行复制。因此,如果其中一台包含数据块的机器崩溃,其他拥有该数据副本的机器将能够提供该特定数据。
HDFS是可扩展的。如果你想增加HDFS系统的硬盘容量或存储容量,只需添加更多带有更多驱动器的服务器即可。你也可以缩减规模。
HDFS实际上基于Google文件系统。有一篇非常出色的论文详细介绍了GFS架构的内部原理,HDFS架构正是基于此。
HDFS的核心概念:文件与块
HDFS背后的核心思想是文件被分割成块。例如,一个名为 customer.txt 的文件会被分解成多个块。每个块都是一个独立的存储单元,并拥有一个与之关联的唯一ID。这些块由NameNode管理,但NameNode并不真正存储块。块的实际存储由DataNode完成。块的概念是HDFS的内部机制,对作为HDFS用户的我们来说是透明的。
块在加载时(即数据被复制到HDFS时)会在多台机器间进行复制。默认的复制因子是3,这意味着每个块总共有三个副本。这有助于实现容错。
文件与块的实际运作
假设我们有一个数据中心机架,上面安装了10台服务器。我们有一个HDFS文件 customer.txt,它由两个块组成:块1和块3。我们还有另一个文件 product.txt,它由三个块组成:块2、块4和块5。这些块被放置在同一机架内不同服务器上。由于复制因子为3,每个块都会被复制两次,总共产生15个块,分布在不同的服务器上。
这种设计实现了条带化和镜像化。将文件分解成块并跨多台机器放置,实现了条带化。跨多台机器复制块,则实现了镜像化。


为什么采用块抽象?
块抽象允许文件的大小超过集群中任何单个原生文件系统磁盘的容量。例如,如果每台服务器的存储容量是2TB,那么在原生文件系统中,单个文件最大只能是2TB。但有了块抽象,HDFS中的文件可以超过2TB,对文件大小没有限制。
块抽象与复制方案配合良好。它提供了容错性和可用性。如果包含某个数据块的服务器因维护或故障而停机,拥有该块副本的其他服务器将能够提供该数据块。

HDFS的适用与不适用场景

HDFS适用场景
- 存储大文件:HDFS适合存储少量大文件,而不是大量小文件。文件大小通常在兆字节或千兆字节级别,而不是千字节级别。
- 流式数据访问:HDFS遵循“一次写入,多次读取”的模式。它针对流式读取进行了优化,而不是随机读取。
- 运行在廉价商用硬件上:HDFS不需要超级计算机或EMC等高端设备,普通的商用服务器即可。硬件不需要高度可靠,因为系统中有复制机制。
HDFS不适用场景
- 低延迟读取:HDFS不适合读取小块数据。如果需要此类功能,可以考虑HBase等项目。
- 存储大量小文件:大量小文件会给NameNode带来压力。
- 多写入者:每个文件只能有一个写入者。文件写入后,只能在其末尾追加数据,且只能由一个写入者执行。
HDFS组件与守护进程
HDFS系统由三个关键的守护进程组成:
- NameNode:管理文件系统的命名空间或元数据。它跟踪所有目录、文件名,以及文件如何分解成块、块ID、块位置等信息。NameNode通常运行在一台机器上。
- DataNode:存储和检索数据块。DataNode向NameNode报告其当前状态。每台物理服务器上都会运行一个DataNode。
- Secondary NameNode:执行清理工作,以减轻NameNode的负担。它需要与NameNode相似的硬件,但它的功能与NameNode完全不同,不能替代主NameNode。在未来,它可能被称为检查点节点。
在一个典型的设置中,会有一台主服务器运行NameNode,另一台服务器运行Secondary NameNode,其余多台从服务器运行DataNode。


客户端连接到NameNode以执行元数据操作(如创建/删除文件/目录、获取文件信息)。客户端也直接与DataNode通信以实际写入或读取数据,因为NameNode只维护元数据。
DataNode之间也会相互通信,因为数据需要复制。NameNode会指示DataNode将副本放置到何处,DataNode之间直接通信以完成复制。

NameNode持续与DataNode通信,监控其状态、数据块情况、校验和,并管理数据块的复制。
NameNode详解
NameNode是HDFS的簿记员。它创建并维护文件系统树和相关元数据。它决定文件如何分解成块,以及这些块应存储在何处。它监控整个分布式文件系统的健康状况。
NameNode对内存和磁盘I/O要求很高。所有关于文件和目录的元数据都保存在内存中以提高速度。元数据的副本也会存储到磁盘上,以防NameNode崩溃。这些被称为文件系统编辑日志,会定期检查点保存到磁盘。
在使用HDFS文件系统之前,必须格式化文件系统。格式化会初始化元数据结构。警告:一旦格式化HDFS,请不要再次格式化,因为这会丢失所有NameNode元数据,相当于丢失了整个文件系统的“目录”。
Secondary NameNode详解
Secondary NameNode中的“Secondary”一词容易引起误解。它的功能与NameNode完全不同,在主NameNode宕机时无法替代它。它的主要目的是执行定期检查点。
Secondary NameNode会定期从NameNode获取文件系统镜像和编辑日志,将它们合并成一个新的文件系统镜像,然后上传回NameNode。这样做的目的是,如果NameNode崩溃后需要重启,它可以快速加载这个合并后的新镜像,而无需花费大量时间重新应用所有编辑日志。
DataNode详解
DataNode是文件系统的主力。它在本地文件系统的本地磁盘上存储和检索实际的数据块。它是NameNode的从属节点,定期向NameNode报告其存储的块及其状态。
DataNode接收来自客户端的数据请求。当客户端想要读取或写入文件时,客户端直接与DataNode通信。DataNode还负责根据复制因子复制数据块到其他DataNode。
HDFS端口与进程管理
- NameNode端口:
50070:HTTP协议端口,用于Web UI管理。9000:IPC端口,客户端用于元数据操作。50470:HTTPS版本的admin端口(默认未配置)。
- DataNode端口:
50020:IPC端口。50010:数据传输端口。50075:Web UI端口。
- Secondary NameNode端口:
50090:admin控制台端口。
启动和停止HDFS守护进程
- 启动所有HDFS守护进程:
start-dfs.sh - 停止所有HDFS守护进程:
stop-dfs.sh - 启动单个守护进程:
hadoop-daemon.sh start [namenode|secondarynamenode|datanode] - 停止单个守护进程:
hadoop-daemon.sh stop [namenode|secondarynamenode|datanode]
总结


在本节课中,我们深入探讨了HDFS的架构。我们了解了HDFS的关键特性、适用与不适用场景。我们还详细研究了构成HDFS的各个组件和守护进程(NameNode、DataNode、Secondary NameNode)的角色及其交互方式。最后,我们学习了HDFS的一些基本管理操作,例如格式化文件系统以进行初始化,以及如何启动和停止守护进程。
024:HDFS架构实践 🖥️


在本节课中,我们将登录虚拟机,从管理员视角和文件系统交互视角,通过内置的Hadoop命令来实践HDFS架构。目标是让你对HDFS架构有实际的理解。
概述
我们将学习如何格式化HDFS文件系统、启动和停止HDFS守护进程、使用Hadoop FS命令进行基本的文件系统操作,并通过模拟不同条件来观察HDFS的内部工作机制。
登录虚拟机并格式化文件系统
首先,我们登录到虚拟机。登录后,需要格式化HDFS文件系统。如果不格式化,将无法进行任何操作。
格式化文件系统的命令是:
hdfs namenode -format
执行此命令后,系统会提示文件系统已在特定目录(例如 /usr/local/hadoop/data/nn)中格式化。
接下来,我们可以查看该目录的内容:
ls -l /usr/local/hadoop/data/nn
你会看到一个名为 current 的目录,其中存储着文件系统镜像。这就是NameNode管理并存储文件系统元数据的地方。
配置与启动守护进程

NameNode如何知道在哪里存储元数据?答案在配置文件中。让我们查看HDFS的配置文件:
cd /usr/local/hadoop/etc/hadoop
cat hdfs-site.xml
在这个配置文件中,我们指定了NameNode存储元数据的位置、Secondary NameNode存储文件系统镜像的位置,以及DataNode存储实际数据块的位置。

现在,我们可以启动HDFS守护进程。启动所有守护进程的命令是:
start-dfs.sh
这个命令会依次启动NameNode、DataNode和Secondary NameNode。你可以使用 jps 命令来检查它们是否都在运行。
如果你想停止所有守护进程,可以使用:
stop-dfs.sh
你也可以单独启动或停止每个守护进程。例如,启动NameNode:
hadoop-daemon.sh start namenode
停止DataNode:
hadoop-daemon.sh stop datanode
检查端口与日志文件
了解守护进程监听的端口对于调试和管理很有帮助。例如,要查看NameNode监听的端口,可以运行:
netstat -an | grep LISTEN | grep <namenode进程ID>
通常,NameNode会监听9000端口(用于IPC通信)和50070端口(用于管理控制台)。
如果守护进程启动失败,你需要查看日志文件。日志文件位于Hadoop安装目录的 logs 文件夹中。例如,NameNode的日志文件可能是 hadoop-<用户名>-namenode-<主机名>.log。查看日志可以帮助你诊断问题。
使用Hadoop FS命令操作文件系统
现在,我们来学习如何使用Hadoop FS命令与HDFS文件系统进行交互。Hadoop FS命令提供了丰富的文件操作功能,类似于Unix命令。
以下是Hadoop FS命令的基本用法列表:
- 列出文件:
hadoop fs -ls / - 创建目录:
hadoop fs -mkdir /data - 从本地复制文件到HDFS:
hadoop fs -copyFromLocal localfile.txt /data/ - 从HDFS复制文件到本地:
hadoop fs -copyToLocal /data/hdfsfile.txt . - 查看文件内容:
hadoop fs -cat /data/file.txt - 追加内容到文件:
hadoop fs -appendToFile localadd.txt /data/file.txt - 重命名文件:
hadoop fs -mv /data/oldname.txt /data/newname.txt - 删除文件:
hadoop fs -rm /data/file.txt - 递归删除目录:
hadoop fs -rm -r /data/olddir
你可以通过 hadoop fs 命令查看所有可用选项,或者查阅Hadoop官方文档获取更详细的指南。
模拟故障以理解架构
为了深入理解HDFS架构,我们可以模拟一些故障场景。
上一节我们介绍了如何使用命令操作文件系统,本节中我们来看看当核心组件出现问题时会发生什么。
首先,停止NameNode守护进程:
hadoop-daemon.sh stop namenode
然后尝试列出HDFS根目录的文件:
hadoop fs -ls /
命令会失败,并提示无法连接到NameNode。这是因为所有元数据操作都需要与NameNode通信。
接着,重新启动NameNode,然后停止DataNode:
hadoop-daemon.sh start namenode
hadoop-daemon.sh stop datanode
现在,尝试列出文件(hadoop fs -ls /data)可能成功,因为这是元数据操作。但尝试读取文件内容(hadoop fs -cat /data/file.txt)将会失败,因为客户端无法连接到存储实际数据块的DataNode。
这些实验清晰地展示了HDFS中客户端、NameNode和DataNode之间的协作关系。

通过Web控制台查看信息
HDFS提供了Web管理控制台,方便我们直观地查看集群状态。NameNode的Web界面通常运行在 http://<namenode-host>:50070。

在控制台中,你可以:
- 查看集群概况,如配置容量、使用情况等。
- 浏览HDFS文件系统。
- 查看文件的详细信息,例如一个文件被分成了哪些块(Block)。对于小于64MB的文件,通常只有一个块;对于大文件,你可以看到多个块的信息。


总结

在本节课中,我们一起学习了HDFS架构的实践操作。我们登录了虚拟机,格式化了HDFS文件系统,并学会了启动和停止HDFS的各个守护进程(NameNode、DataNode、Secondary NameNode)。我们使用Hadoop FS命令对HDFS进行了完整的文件操作,包括创建、删除、复制、重命名文件和目录。我们还查看了守护进程的数据存储位置和日志文件。最重要的是,我们通过模拟NameNode和DataNode宕机的情况,直观地验证了HDFS的架构设计和工作原理。希望这次实践能帮助你更好地理解HDFS。
025:HDFS块大小、副本放置与机架感知机制 🧱


在本节课中,我们将学习如何为HDFS设置合适的块大小,理解HDFS如何放置数据副本,并探讨机架感知机制及其对副本放置策略的影响。
块大小的设定原则
上一节我们介绍了HDFS的基本架构,本节中我们来看看如何设定一个合适的块大小。为了理解这一点,我们需要回顾磁盘驱动器的性能指标。
这里需要特别关注两个概念:寻道时间和数据传输率。
- 寻道时间:指磁头移动到数据所在磁道所需的时间。高端服务器的平均寻道时间约为3-5毫秒,移动硬盘约为15毫秒,台式机硬盘约为9毫秒。
- 数据传输率:指数据在磁盘和主内存(RAM)之间移动的速率。这是吞吐量的关键,因为任何处理都必须先将数据加载到内存。目前平均数据传输率约为100 MB/s,固态硬盘可达300 MB/s。
我们的目标是最小化寻道时间相对于数据传输时间的成本。换句话说,我们希望数据加载到RAM的时间远大于寻道时间,理想比例接近99:1。
让我们进行一些计算:
- 假设寻道时间为10毫秒(台式机水平)。
- 假设传输速率为100 MB/s,即1000毫秒内可读取100 MB数据。
- 那么,读取100 MB数据的总时间中,寻道时间仅占约1%。
因此,为了达到寻道时间仅占传输时间1%的目标,块大小应设为100 MB。这是我们应该努力实现的目标。

如何设置块大小

Hadoop的默认块大小是64 MB。但所有配置都是可以通过属性修改的。
可修改的属性是 dfs.blocksize,它位于 hdfs-site.xml 配置文件中。你可以将其更改为任何你需要的值。
根据当前硬件性能,一个良好的起始值是120 MB。这是你在今天构建任何系统时应该设定的最低值。根据你处理的文件大小和数量,你甚至可以将其设置为256 MB或512 MB。请记住,块越大,元数据开销就越小。
块副本与复制因子
现在我们来谈谈块副本。HDFS的默认块复制因子是3。同样,这也是可以通过属性修改的。
复制因子由 dfs.replication 属性控制,也位于 hdfs-site.xml 中。回想一下我们在上一个模块中配置集群时,因为是在伪分布式模式下运行只有一台服务器,所以将此属性设置为1。
尽管默认复制因子是3,但每个文件都可以有不同的复制因子。你可以让一个文件的复制因子是5,另一个是7,另一个是2。目前Hadoop系统的默认值就是3。
此外,还有另一个概念叫做默认最小块复制,其值设置为1。这个概念的涵义是:当你写入一个块时,只要成功写入了这个最小数量的副本,HDFS就认为写入操作成功,否则视为失败。默认最小复制为1意味着,只要写入了一个块副本,操作就被认为是成功的,其余的副本由后续的复制机制来保证。
如果你的复制因子是5,你可能希望将默认最小复制因子设置为2或3。一个常用的计算方法是:(总复制因子 / 2) + 1。例如,复制因子为5时,5 / 2 = 2.5,加1等于3.5,取整数下限,即设置为3。


副本放置策略(单机架)
我们知道NameNode是HDFS的主节点,正是由它来决定副本的放置位置。
首先,让我们看看在单个机架内,当拥有多台机器时,副本是如何放置的。
以下是写入数据到HDFS时,在一个机架内的典型放置策略:
- 第一个副本放置在某台服务器上。
- 第二个副本放置在同一机架内的另一台不同服务器上。
- 第三个副本再次放置在同一机架内的另一台不同服务器上。
让我们通过一个例子来看块复制是如何与复制因子结合工作的。
假设我们有两个文件:
customer.txt:复制因子为2,由块1和块3组成。product.txt:复制因子为3,由块2、块4和块5组成。
我们有一个机架,内含多台服务器。
对于 customer.txt:
- 块1放置在一台服务器上,其副本放置在另一台不同的服务器上。
- 块3放置在一台服务器上,其副本放置在另一台不同的服务器上。

对于 product.txt:
- 块2放置在一台服务器上,第二个副本放置在另一台服务器上,第三个副本再放置在另一台不同的服务器上。
- 块4和块5也遵循同样的三副本放置逻辑。
这种策略的优势在于高可用性。例如,如果一台包含块3和块5的服务器宕机,NameNode会指示其他DataNode重新复制这些块到健康的服务器上。

机架感知与多机架副本放置
如果拥有多个机架呢?一个机架可能成为单点故障。假设所有三个副本块都在同一个机架,而有人拔掉了这个机架的电源,Hadoop会认为我们丢失了该块的所有副本。因此,复制策略需要更智能一些。
幸运的是,Hadoop可以配置为机架感知。我们只需要告诉Hadoop哪些服务器位于哪个机架。
这是通过向Hadoop提供一个脚本实现的。我们需要在 core-site.xml 中设置一个名为 topology.script.file.name 的属性,该属性指向一个脚本。每当有节点加入或离开HDFS时,Hadoop系统都会调用这个脚本。
其工作原理是:Hadoop调用脚本并传入服务器名称(IP或主机名),脚本则返回与该服务器关联的机架名称。Hadoop只负责调用脚本并获取结果。
通常,编写这个脚本有两种常见方法:
- 配置文件映射:创建一个配置文件,其中包含服务器名到机架名的映射,脚本读取该文件并返回对应机架名。
- 主机名嵌入逻辑:服务器主机名本身包含机架信息(例如,
srv1-rack1表示服务器srv1位于rack1)。脚本只需解析主机名即可返回机架名。
从最佳实践角度,推荐使用主机名嵌入方法,因为这比维护一个配置文件更简单。每次添加新机器时,你都需要设置主机名,这样逻辑就自然包含在其中了。
重要提示:NameNode在配置后会自动具备机架感知能力,但HDFS中已存在的数据不会自动移动以符合新的机架感知策略。新写入的文件会利用机架感知,而旧文件仍保留在原有机架内。因此,在使系统具备机架感知后,你可能需要手动删除并重新复制现有文件。
机架感知下的副本放置策略
机架感知下的副本放置,实际上是可靠性与性能之间的平衡。
- 性能考量:主要是带宽性能。我们希望减少机架间的带宽消耗,尽量将数据保留在同一机架内。
- 可靠性考量:如果将副本分散在多个机架,即使整个机架故障,数据依然可用,可靠性更高。
因此,需要在减少带宽消耗和提高可靠性之间找到平衡点。
对于典型的三副本情况,在具备机架感知的多机架环境中,放置策略通常如下:
- 第一个副本放置在客户端所在的本地机架(例如Rack 1)的某台服务器上。
- 第二个副本放置在不同机架(例如Rack 2)的某台服务器上。
- 第三个副本再次放置回第一个副本所在的本地机架(Rack 1),但是另一台不同的服务器上。
这样,我们只利用了两个机架,既保证了跨机架的可靠性,又最大限度地减少了跨机架流量(只有第二个副本需要跨机架写入)。

让我们看一个在多机架环境下的实际操作例子。同样有 customer.txt(复制因子2)和 product.txt(复制因子3),以及三个各含10台服务器的机架。
对于 customer.txt 的块1:
- 第一个副本可能放在Rack 1的Server 1。
- 第二个副本会放在一个完全不同的机架,例如Rack 2。
对于 product.txt 的块2(三副本):
- 第一个副本放在Rack 3的Server 1。
- 第二个副本放在不同机架,例如Rack 2。
- 第三个副本放回第一个副本所在的机架(Rack 3),但是另一台服务器。
其他块也遵循类似的跨机架放置逻辑,确保了数据在机架间的分布。
总结
本节课中我们一起学习了HDFS的三个核心配置与机制:
- 块大小的设定:我们探讨了如何根据磁盘性能(寻道时间与传输速率)计算并设置合理的HDFS块大小(如128MB或256MB),以优化吞吐量。
- 副本放置策略:我们了解了NameNode如何管理副本放置,包括默认的三副本策略,以及如何为不同文件设置不同的复制因子。
- 机架感知机制:我们学习了如何通过配置使Hadoop感知网络拓扑(机架信息),以及这如何影响副本放置策略,从而在数据可靠性和网络带宽效率之间取得平衡。
理解这些概念对于设计和维护一个高效、可靠的Hadoop集群至关重要。
026:HDFS架构核心能力 🏗️

在本节课中,我们将从多个维度深入探讨HDFS的架构核心能力,包括性能、可扩展性、可用性、可安装性、可配置性、可操作性、易用性和安全性。
性能视角
上一节我们介绍了HDFS的整体架构,本节中我们来看看其各个守护进程的性能特点。
NameNode 性能
NameNode的性能特点是:
- 内存密集型:它必须在内存中保存所有文件和目录的元数据。
- 磁盘I/O密集型:它需要将所有编辑日志写入磁盘。
- CPU密集型:它需要与DataNode通信,向DataNode发出指令,并在元数据中进行必要的更改。有时,如果NameNode重启,它还需要重建文件系统镜像。
提升方法:可以通过添加更快的CPU和更快的磁盘来提升NameNode的性能。
Secondary NameNode 性能
Secondary NameNode的性能特点是:
- CPU密集型:它需要定期获取文件系统镜像,用所有文件系统编辑日志进行更新,然后将新镜像写回磁盘。从这个角度看,它也是I/O密集型的。
提升方法:可以通过添加更快的CPU和更快的磁盘来提升其性能。
DataNode 性能
DataNode的性能特点是:
- 磁盘I/O密集型:它需要向磁盘读写数据块。
- 网络I/O密集型:它需要不断在不同节点间复制数据,并在客户端请求时提供数据或接收客户端数据以写入文件系统。


提升方法:可以通过添加更快的磁盘和更快的网络互连(如InfiniBand)来提升DataNode的性能。
可扩展性视角
了解了性能特点后,我们来看看HDFS各个组件如何应对规模增长。
NameNode 可扩展性
在当前的默认实现中,只有一个NameNode。NameNode只能通过垂直扩展(纵向扩展)来提升能力,因为它将所有元数据保存在内存中。如果需要维护更多元数据,就必须增加更多内存、CPU和更快的磁盘。
此外,Hadoop 2引入了命名空间联邦的新概念。其核心思想是,不再只有一个NameNode管理所有DataNode(即一个命名空间),而是可以拥有多个NameNode,每个管理不同的命名空间。例如,NameNode1管理 /data 文件系统,NameNode2管理 /home,NameNode3管理 /project。它们都操作底层相同的数据块,但各自维护独立的元数据树。这实现了NameNode的水平扩展。
Secondary NameNode 可扩展性
Secondary NameNode只能通过垂直扩展(增加更多内存、CPU和更快的磁盘)来提升性能,因为它主要的工作就是获取文件系统镜像、应用编辑日志、处理并重写镜像。
DataNode 可扩展性
DataNode的可扩展性是其强大之处,可以通过水平添加更多DataNode来实现。增加一倍的DataNode数量,系统的存储容量就增加一倍。
可用性视角
在分布式系统中,高可用性至关重要。接下来我们分析HDFS各组件的可用性策略。
NameNode 可用性
在简单的默认实现中,只有一个NameNode。如果它崩溃,整个系统几乎会停止。通常的做法是使用具有高平均无故障时间的硬件来提升其可用性。当NameNode崩溃时,唯一的方法是尝试重新启动它。这个过程可能需要10到30分钟,具体取决于NameNode需要合并的文件系统镜像和编辑日志的数量。


HDFS 2提供了一种高可用性NameNode的特殊配置。在这种模式下,可以运行两个NameNode:一个处于活动模式,另一个处于备用模式。活动节点处理所有客户端请求和元数据管理,并将编辑日志写入共享存储。备用节点持续应用这些编辑日志到自己的内存中。如果活动节点崩溃,备用节点可以迅速被提升为活动节点,从而保证服务不中断。
Secondary NameNode 可用性
Secondary NameNode在可用性方面不扮演主要角色,因为它只负责下载文件系统镜像并应用编辑日志。
DataNode 可用性
单个DataNode崩溃通常不是大问题,因为数据通常被复制到另外两个节点上。当某个DataNode失效时,拥有数据副本的其他DataNode会继续提供服务。因此,高可用性内置于HDFS的架构之中。
可安装性视角
对于大规模集群,手动安装是不现实的。以下是几种常见的安装和管理方式:
以下是常见的Hadoop安装与管理工具:
- 手动安装:适用于小型集群或学习环境(如我们的虚拟机伪分布式模式)。
- Apache Ambari:一个开源工具,是Hortonworks数据平台的一部分,用于大规模集群的安装、管理和监控。
- Cloudera Manager:Cloudera提供的专有管理工具。
- 自定义脚本:许多组织使用Puppet、Chef或Ansible等配置管理工具编写自定义安装脚本。
- Amazon EMR:亚马逊的云服务,提供预配置的Hadoop集群镜像,用户无需手动安装,只需指定机器数量和Hadoop版本即可快速部署。
可配置性视角
Hadoop具有高度的可配置性,拥有数百个配置参数。
HDFS主要有两个需要配置的XML文件:
core-site.xml:属于Hadoop通用配置。对于HDFS,主要在此文件中指定NameNode的运行位置和端口。例如:fs.defaultFS属性设置为hdfs://localhost:9000。hdfs-site.xml:用于配置Hadoop分布式文件系统本身。例如,设置副本因子(dfs.replication)、NameNode和DataNode的数据存储目录等。
可操作性视角
在拥有数百台机器的生产环境中,确保所有守护进程正常运行是一项挑战。
以下是提升HDFS可操作性的工具:
- Apache Ambari:在运维方面表现出色,帮助监控和管理大型集群。
- 集成监控工具:如Ganglia和Nagios,可以与Hadoop集成,实现高效监控。
- 商业产品:如Cloudera Manager、Hortonworks的Ambari、MapR的Heatmap等,都提供了强大的运维管理功能。
易用性视角
用户可以通过多种方式与HDFS进行交互。
命令行工具
hadoop fs命令:允许用户执行基本的文件系统操作,如列出文件、创建目录。例如:hadoop fs -mkdir /newdir或hadoop fs -ls /。hdfs dfs命令:功能与hadoop fs类似,但额外包含了管理命令,例如重新平衡集群、使节点上线等。
编程接口
HDFS提供了两种程序化访问模式:
- 语言绑定API:支持Java和C++。在下一个模块中,我们将讨论如何使用HDFS Java API进行编程。
- WebHDFS REST API:可以通过流行的REST接口访问HDFS文件系统。

安全性视角

早期版本的Hadoop几乎没有安全性,它假设组织内的用户会友好协作。现在,Hadoop已经支持基于用户身份验证和授权的安全概念。
认证与授权基础
Hadoop的安全基础通过两种机制实现:
- 基于标准UNIX的认证:使用UNIX用户ID进行身份验证和授权。这是默认的、相对较弱的机制。
- 基于Kerberos的认证:这是一种更强大的安全方法。
HDFS 安全特性
HDFS采用基于令牌的安全方法:
- 令牌机制:当客户端想要读写文件时,必须从NameNode获取令牌,DataNode会验证这些令牌。客户端也可以预先请求令牌以供后续使用(例如在MapReduce作业中,避免同时请求大量令牌造成网络风暴)。
- 配额管理:可以为用户设置存储空间和文件数量的配额,防止单个用户占用过多资源导致NameNode内存不足或磁盘写满。
- 安全通信:可以配置HDFS守护进程通过SSL进行通信,以加密节点间的网络传输。
- 数据加密:Hadoop本身不加密磁盘上的文件。如果需要对磁盘上的数据进行加密,需要从应用程序层面实现。


本节课总结:
在本节课中,我们一起从性能、可扩展性、可用性、可安装性、可配置性、可操作性、易用性和安全性等多个角度,全面学习了HDFS的架构核心能力。我们了解了每个守护进程(NameNode, Secondary NameNode, DataNode)在不同维度下的特点、限制以及提升方法,并熟悉了与HDFS交互和管理的各种工具与配置选项。
027:HDFS架构总结 🗂️
在本节课中,我们将总结Hadoop分布式文件系统(HDFS)的架构。我们将回顾其核心组件、它们之间的关系,以及这种架构如何为大数据存储和处理奠定基础。

上一节我们介绍了HDFS的各个组件,本节中我们来看看这些组件如何共同构成一个完整的系统。
HDFS架构是一种基于组件的架构。
其中,组件之间存在主从关系。
例如,名称节点(NameNode)是主节点,而数据节点(DataNode)是从属于主名称节点的从节点。
这些组件也协同工作,以提供跨服务器的统一磁盘文件系统视图。
HDFS还具有强大的架构特性,使其非常适合存储大数据。
后续我们将看到,这种强大的架构特性如何成为处理大数据的基础。
以下是HDFS架构的核心要点总结:
- 组件化设计:HDFS由多个定义清晰的组件构成。
- 主从关系:核心关系是
NameNode(主)与DataNode(从)之间的关系。 - 统一视图:多个物理服务器上的磁盘被整合,对用户呈现为单一的文件系统。
- 为大数据构建:其架构设计(如数据块、副本机制)专门针对大规模数据存储而优化。
- 处理的基础:HDFS的存储架构是后续进行大数据计算(如MapReduce)的基石。

本节课中,我们一起学习了HDFS架构的总结。我们了解到HDFS是一个采用主从模式的组件化系统,它通过名称节点和数据节点的协作,将跨服务器的存储资源抽象为一个统一的文件系统。这种为海量数据存储而设计的强健架构,正是整个Hadoop生态系统能够高效处理大数据的关键起点。
028:HDFS编程基础导论 🚀

在本节课中,我们将学习如何通过编程方式访问HDFS。我们将从C API的概述和一个快速演示开始,然后探讨Hadoop的配置,并学习如何为Java Hadoop编程设置IDE。接着,我们将概述用于访问HDFS的Java API,并学习如何在文件上执行CRUD(创建、读取、更新、删除)操作。
C API概述
上一节我们介绍了课程的整体安排,本节中我们来看看C API的概述。我将展示如何编写一个小的C程序,编译并运行它。
以下是C API概述的主要内容:
- 编写一个简单的C程序。
- 编译该C程序。
- 运行编译后的程序。
Hadoop通用配置与IDE设置
了解了C API的基本概念后,本节我们将重点讨论Hadoop的通用配置以及如何设置集成开发环境。我将展示如何实例化配置对象,这是进行任何Hadoop操作的基础对象。同时,作为本部分内容的一部分,我将演示如何设置你的IDE、配置类路径和设置,以便你能编译和运行你的Hadoop Java程序。
以下是配置与设置的主要步骤:
- 实例化Hadoop的配置对象。
- 为Java Hadoop编程设置IDE(以NetBeans为例,也可使用Eclipse或其他IDE)。
- 配置IDE的类路径和必要设置。
Java API概述
完成了开发环境的基础配置,现在我们可以深入了解用于访问HDFS的Java API。本节我将概述各种HDFS相关的包和类,为你提供一个高层次的视角。
以下是Java API的核心组成部分:
- HDFS相关的主要包结构。
- 关键类及其功能简介。
文件CRUD操作
在掌握了Java API的概貌之后,本节我们将进入实践环节,学习如何实际编写Java程序来对文件进行创建、读取、更新和删除操作。
以下是文件CRUD操作的关键方法:
- 创建:使用
FileSystem.create(Path)方法创建新文件。 - 读取:使用
FileSystem.open(Path)方法打开并读取文件内容。 - 更新:通过写入操作覆盖或追加内容到现有文件。
- 删除:使用
FileSystem.delete(Path, boolean)方法删除文件或目录。
总结

本节课中,我们一起学习了HDFS编程的基础知识。你现在应该能够描述HDFS的编程模型,能够应用Hadoop配置对象进行编程,能够设置Java编程环境以进行Hadoop开发,并且最终能够对文件应用CRUD功能。
029:HDFS C API解析 🖥️


在本节课中,我们将学习如何使用C语言编程接口访问Hadoop分布式文件系统。我们将通过一个具体的示例来解析HDFS C API的工作原理、编译和运行过程。
概述
HDFS提供了多种编程接口,允许开发者对文件和目录执行创建、读取、更新和删除操作。本节重点介绍C API,它是一个基于Java本地接口的封装层,使得C/C++程序能够调用底层的Java实现。
HDFS编程接口简介
上一节我们介绍了HDFS的基本概念,本节中我们来看看其编程接口。HDFS主要提供三种编程接口用于文件操作。
以下是三种主要的编程接口:
- C API:通过JNI桥接调用底层Java实现。
- Java API:由于HDFS本身用Java编写,因此这是最直接的调用方式。
- REST API:一种基于HTTP的接口,需要显式配置并启动REST服务。由于其通用性,几乎支持任何语言和客户端。
HDFS C API详解
现在,让我们深入了解HDFS C API。C API本质上是Java实现的一个JNI包装器。
JNI代表Java本地接口。它允许Java程序调用C/C++程序,反之亦然。由于Hadoop HDFS是用Java编写的,C API利用JNI让C/C++程序能够调用Java程序。

使用C API编程时,需要遵循以下步骤:
以下是使用HDFS C API的关键步骤:
- 包含头文件:在C程序中包含
hdfs.h头文件,该文件位于Hadoop安装目录的include文件夹中。 - 链接库文件:编译时需要链接HDFS库
libhdfs.a(静态库)或libhdfs.so(动态库),它们位于Hadoop安装目录的lib/native子目录中。 - 包含JNI头文件:为了编译,需要包含JNI的头文件。
- 链接JVM库:运行时需要JVM库。
示例程序解析
接下来,我们通过一个简单的示例程序来具体说明。
#include <hdfs.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
// 连接到运行在localhost:9000的NameNode
hdfsFS fs = hdfsConnect("localhost", 9000);
if (!fs) {
fprintf(stderr, "连接失败\n");
exit(1);
}
// 以写入模式打开(或创建)文件
const char* writePath = "/c_test_file.txt";
hdfsFile writeFile = hdfsOpenFile(fs, writePath, O_WRONLY|O_CREAT, 0, 0, 0);
if (!writeFile) {
fprintf(stderr, "打开文件失败\n");
hdfsDisconnect(fs);
exit(1);
}
// 向文件写入数据
const char* buffer = "Hello, World from C API!";
tSize num_written_bytes = hdfsWrite(fs, writeFile, (void*)buffer, strlen(buffer)+1);
if (num_written_bytes < 0) {
fprintf(stderr, "写入失败\n");
}
// 关闭文件并断开连接
hdfsCloseFile(fs, writeFile);
hdfsDisconnect(fs);
return 0;
}
这个程序演示了如何连接到HDFS、创建文件、写入数据以及清理资源。所有HDFS C API函数都以 hdfs 为前缀。
编译与运行

编写完C程序后,下一步是将其编译成可执行文件。编译过程需要指定正确的头文件和库路径。
以下是一个编译脚本示例:
#!/bin/bash
export HADOOP_PREFIX=/usr/local/hadoop
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
gcc create_file.c \
-I${HADOOP_PREFIX}/include \
-I${JAVA_HOME}/include \
-I${JAVA_HOME}/include/linux \
-L${HADOOP_PREFIX}/lib/native \
-L${JAVA_HOME}/jre/lib/amd64/server \
-lhdfs -ljvm \
-o create_file
编译成功后,运行程序前需要设置正确的环境变量,特别是 CLASSPATH,因为C API需要通过JNI调用Java类。
以下是运行前需要设置的环境变量示例:
#!/bin/bash
export HADOOP_PREFIX=/usr/local/hadoop
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
export LD_LIBRARY_PATH=${HADOOP_PREFIX}/lib/native:${JAVA_HOME}/jre/lib/amd64/server
# 构建包含所有Hadoop Jar文件的CLASSPATH
CLASSPATH="."
for jar in `find ${HADOOP_PREFIX}/share/hadoop -name "*.jar"`; do
CLASSPATH=${CLASSPATH}:$jar
done
export CLASSPATH
./create_file
运行脚本后,程序将在HDFS的根目录创建名为 c_test_file.txt 的文件,并写入“Hello, World from C API!”。
核心API函数
最后,让我们简要了解一下 hdfs.h 头文件中定义的一些核心函数。这些函数为C程序员提供了完整的HDFS文件操作能力。
以下是部分关键函数:

hdfsConnect/hdfsConnectBuilder:建立与HDFS的连接。hdfsOpenFile:打开(或创建)HDFS上的一个文件。hdfsWrite/hdfsRead:向文件写入数据或从文件读取数据。hdfsCloseFile:关闭一个已打开的文件。hdfsExists:检查文件或目录是否存在。hdfsSeek:在文件中定位读取/写入位置。hdfsDisconnect:断开与HDFS的连接。
总结

本节课中我们一起学习了HDFS C API。我们了解到C API是建立在JNI之上的一个包装层,使得C/C++应用程序能够与用Java编写的HDFS进行交互。我们通过一个完整的示例,从编写代码、编译到运行,逐步解析了如何使用C语言在HDFS上创建和写入文件。掌握这些知识后,开发者就可以利用熟悉的C语言环境来处理Hadoop分布式文件系统中的数据了。
030:Hadoop配置API与开发环境搭建 🛠️

在本节课中,我们将学习Hadoop配置API的核心概念,并完成Hadoop开发环境的搭建。配置对象是进行任何Hadoop操作(无论是HDFS编程还是YARN MapReduce编程)时都必须实例化并传递引用的关键对象。作为演示的一部分,我们还将学习如何在NetBeans中设置开发环境,当然你也可以使用Eclipse或其他你喜欢的IDE。
Hadoop配置概述 📋
Hadoop配置使用XML格式完成。观察一个XML配置文件,你会发现一个名为configuration的根元素,以及多个property子元素。每个property元素都包含一个名称-值对,即属性的名称和值。你可以在一个配置文件中定义任意多个这样的属性。
所有Hadoop配置都采用这种格式。回想安装Hadoop时,你曾修改过几个配置文件,如core-site.xml、mapred-site.xml、yarn-site.xml和hdfs-site.xml。
主要的Hadoop配置文件 📁
Hadoop有四个主要的配置文件,对应其内部的四个主要层次:
- core:对应通用层。
- hdfs:构建在通用层之上的HDFS层。
- yarn:YARN层。
- mapred:MapReduce层。
默认的Hadoop配置文件是core-default.xml、hdfs-default.xml、yarn-default.xml和mapred-default.xml。这四个文件是包含许多属性默认值的默认XML文件,它们位于Hadoop的JAR文件库中。
此外,系统管理员在Hadoop配置中还会进行系统范围的配置,这些配置文件位于Hadoop安装目录的etc/hadoop文件夹中,分别称为core-site.xml、hdfs-site.xml、yarn-site.xml和mapred-site.xml。这些文件会覆盖上述默认文件中定义的属性。你的应用程序也可以通过其关联的配置文件来覆盖这些名称-值对。
Configuration类 🧩
Hadoop中的Configuration类代表了Hadoop配置。你可以通过调用Configuration对象的构造函数来创建一个新的配置实例,例如使用默认构造函数:new Configuration(),从而获得一个全新的配置对象。
在Hadoop中做任何事情几乎都需要实例化一个配置对象。当你实例化一个配置对象时,它会默认加载两个文件:从Hadoop JAR库中加载core-default.xml,以及如果类路径中存在则加载core-site.xml。
实例化配置对象后,你可以查看其中的值。一种方法是转储配置。你可以调用Configuration类中的静态方法dumpConfiguration,将配置对象和一个Writer对象传递给它。
你也可以遍历配置对象。因为Configuration类实现了Iterable接口,所以你可以使用循环(例如for循环)进行遍历。在遍历时,你会得到一个Map.Entry对象,它是一个包含字符串键和字符串值的键值对组合。
此外,你还可以通过调用配置对象的get方法并传递参数名,来获取特定参数的值,该方法会返回对应的值。


以下是演示Hadoop配置对象使用的Configuration1.java类:
import org.apache.hadoop.conf.Configuration;
import java.io.PrintWriter;
import java.util.Map;
public class Configuration1 {
public static void main(String[] args) throws Exception {
// 声明并实例化一个Configuration对象
Configuration conf = new Configuration();
// 将配置转储到标准输出
Configuration.dumpConfiguration(conf, new PrintWriter(System.out));
// 遍历并打印配置中的所有键值对
for (Map.Entry<String, String> entry : conf) {
System.out.printf("%s = %s%n", entry.getKey(), entry.getValue());
}
// 获取并打印特定属性的值
String fsDefaultName = conf.get("fs.default.name");
System.out.println("fs.default.name = " + fsDefaultName);
}
}
在这个示例中,我们声明了一个Configuration类型的变量conf并实例化了一个配置对象。然后,我们通过调用Configuration类的静态方法dumpConfiguration来转储配置,传递我们刚刚创建的配置对象和一个新的PrintWriter对象(它接受一个Writer对象作为参数,这里我们传递了代表输出流的System.out)。接着,我们使用一个for循环遍历配置对象中的所有值,每次迭代获取一个Map.Entry对象,并打印出该条目的键和值。最后,我们使用之前讨论过的get方法,传入属性名"fs.default.name",获取并打印其字符串表示形式的值。
添加应用程序配置 ➕
你也可以将自己的应用程序配置添加到配置对象中。方法是调用配置对象上的addResource方法,并传递一个文件名。该文件是一个XML格式的应用程序自定义配置文件,但此文件必须位于类路径中才能被加载。
让我们看一个例子。这里有另一个名为Configuration2的示例。我实例化了一个配置对象并将其赋值给变量conf。这次,我想添加一个名为myapp-conf.xml的资源。注意,我是在配置对象上调用此方法。这里的思路是,配置对象已经加载了默认配置,而我想加载自己的配置。

以下是该文件的内容示例:
<?xml version="1.0"?>
<configuration>
<property>
<name>fs.default.name</name>
<value>hdfs://anotherhost:9020</value>
</property>
<property>
<name>my.app.parameter1</name>
<value>blue</value>
</property>
</configuration>
该文件包含两个属性:一个名为fs.default.name,值为hdfs://anotherhost:9020;另一个名为my.app.parameter1,值为blue。

然后,我可以转储配置,也可以通过for循环打印配置内容,现在还可以打印fs.default.name和my.app.parameter1的值,这些值都来自配置对象。
默认加载应用程序配置 ⚙️
默认情况下,你可以通过调用一个非常特殊的方法addDefaultResource,将你的配置加载到系统(JVM)中。这是Configuration对象的一个静态方法。思路是,如果你进行此调用并传递文件名,那么这个文件将被加载和解析(当然是从你的类路径中加载)。假设该文件将以XML格式包含你的配置。其奇妙之处在于,现在当你实例化一个配置对象时,这个文件将被自动加载,你无需显式调用addResource方法。
让我们看看这个。这里有一个名为Configuration3的程序。注意,在我的静态初始化块中,我调用了Configuration类的静态方法addDefaultResource,并传递了XML文件的名称。

import org.apache.hadoop.conf.Configuration;

public class Configuration3 {
static {
// 在静态初始化块中注册默认资源
Configuration.addDefaultResource("myapp-conf.xml");
}
public static void main(String[] args) {
// 实例化时自动加载默认资源和myapp-conf.xml
Configuration conf1 = new Configuration();
// ... 使用conf1
Configuration conf2 = new Configuration();
// ... 使用conf2,也自动包含了myapp-conf.xml的配置
}
}
现在,每当我实例化一个配置对象时,它都会自动加载默认属性,同时也会加载我的myapp-conf.xml属性。这样做的思路是,如果你实例化配置对象3次、4次、5次甚至10次,你无需每次都去添加资源,因为它已被加载一次并自动为你加载。通常,你会在静态初始化块中做这件事。
搭建Hadoop开发环境 💻

现在,让我们将所有这些付诸实践,并搭建开发环境。
我将以hdadmin用户身份登录到我的Hadoop服务器虚拟机。我将使用NetBeans来设置我的开发环境,所以我现在要启动NetBeans。当然,你也可以使用Eclipse或任何其他你喜欢的IDE。
为了编写程序,我自然需要Hadoop库。所以,我要做的是进入“工具”->“库”,并将Hadoop库添加到我的路径中。我将创建自己的库,NetBeans IDE(Eclipse同理)有内置库,但我要添加一个名为“Hadoop”的新库。
问题是,哪些是Hadoop相关的JAR文件?让我们看一下。进入Hadoop安装目录(在我的例子中是/usr/local/hadoop,其中有一个指向当前Hadoop安装目录的链接hadoop)。其下有一个名为share/hadoop的文件夹。基本上,在你的Hadoop安装目录下,都有一个名为share/hadoop的文件夹,其中包含几个子目录,存放着进行Hadoop编程所需的各种库。
记住,在Hadoop中,我们有通用层、HDFS层、YARN层和MapReduce层,它们彼此构建。每一层的JAR文件都位于这些不同的目录中,但情况比这稍微复杂一些。如果你进入通用层(common),你会看到通用层的JAR文件(有三个),但在通用层下的lib目录中还有依赖的JAR文件。你不仅需要添加通用库,还需要添加依赖库。对于MapReduce层也是如此,如果你进入mapreduce目录,这些是MapReduce的JAR文件,然后还有依赖的JAR文件(这些依赖来自其他Apache项目或Hadoop项目依赖的其他开源项目)。
我们需要为所有四个模块(common、hdfs、yarn、mapreduce)都进行此操作。
以下是添加JAR文件的步骤:
- 添加通用层(common)的所有JAR文件及其依赖库(
lib目录下的)。 - 添加HDFS层的所有JAR文件及其依赖库。
- 添加YARN层的所有JAR文件及其依赖库。(注意:这里并非所有文件都是必需的,有些是示例或测试文件,但全部添加更简单。)
- 添加MapReduce层的所有JAR文件及其依赖库。
完成添加后,我们大致拥有了:common及common-lib、hdfs及hdfs-lib、yarn及yarn-lib、mapreduce及mapreduce-lib。
编写配置演示程序 ✍️
现在,我想编写一个小的Hadoop程序来展示配置概念。创建一个全新的Java应用程序项目,命名为hadoop-config-demo。
项目创建后,会出现HadoopConfigDemo类。删除一些默认内容以便查看整个程序。
首先,我想实例化一个新的配置对象:Configuration conf = new Configuration();。这可能会报错,提示导入Configuration类。我们需要的是Hadoop的Configuration类。它找不到该类是因为我们只添加了一个名为“Hadoop”的库,但还没有将该库添加到我们的项目中。所以,我需要右键单击我的项目,选择“添加库”,然后添加我们之前创建的包含所有Hadoop JAR文件的“Hadoop”库。
添加后,点击错误提示,它会显示多个Configuration类(来自Java安全、Courthouse、Jetty、Commons配置等)。我们真正需要的是org.apache.hadoop.conf.Configuration,选择它。
现在,我们创建了配置对象。接下来,我想转储配置。正确的方法是调用静态方法:Configuration.dumpConfiguration(conf, new PrintWriter(System.out));。这里,我传递配置对象引用和一个Writer对象(我创建了一个新的PrintWriter,并传入System.out作为输出流)。这意味着我想将配置转储到标准输出。PrintWriter是Writer基类的一个具体类。
这可能会提示导入PrintWriter类并处理IOException(因为new PrintWriter实例化可能抛出该异常)。我们添加throws子句即可。
保存并运行程序,它应该会转储配置。输出会将所有内容以一大行的形式转储,实际上它是在转储一个Map,其中包含键值对组合。例如,一个键是ipc.client.fallback-to-simple-auth-allowed,值为false。还有一个属性叫final,用于指示该特定属性是否可以被其他人覆盖。
遍历和获取属性值 🔍
接下来,我想编写自己的for循环来遍历配置。代码如下:

for (Map.Entry<String, String> entry : conf) {
System.out.printf("%s = %s%n", entry.getKey(), entry.getValue());
}
运行程序,它会打印出所有的键值对,例如io.seqfile.compress.blocksize、dfs.replication等。这些是配置对象的一部分属性。
问题是,它从哪里获取所有这些属性?正如之前提到的,有core-default.xml和core-site.xml。core-default.xml预定义了一堆属性,而我们在core-site.xml中覆盖了特定的属性。

如果你查看Hadoop文档,在左侧的“Configuration”部分有一个指向core-default.xml的链接。点击它,你可以看到属性的名称、默认值及其描述。我们看到的许多值都是默认值。我们在系统范围的配置(etc/hadoop下)中覆盖这些默认值。
例如,查看core-site.xml文件,你会发现fs.default.name的值被设置为hdfs://localhost:9000。而在core-default.xml中,它的默认值是file:///。
回到我们的程序,如果我们尝试打印fs.default.name的值:System.out.println(conf.get("fs.default.name"));,它打印出file:///。这是因为它是从core-default.xml获取的值。
有趣的是,当我实例化一个配置对象时,我说过它会加载core-default.xml和core-site.xml。从技术上讲,它应该从core-site.xml获取值并覆盖默认值。它没有这样做的原因是core-site.xml不在我们的类路径中。所以我们需要将其添加到类路径中。
我喜欢的方法是:进入“工具”->“库”,添加一个新库,例如命名为HadoopConf。在这个新库中,我添加文件夹,即放置所有配置文件的etc/hadoop目录。
然后,回到我的项目,添加这个HadoopConf库。现在,当我运行程序时,实例化配置对象会从Hadoop JAR文件加载core-default.xml,并从我的Hadoop配置库加载core-site.xml。我希望当我打印fs.default.name时,它会显示core-site.xml中的值。运行程序,确实如此。
创建自定义配置文件 🎨
你还可以创建自己的配置文件来覆盖core-site.xml中的内容。方法是创建你自己的XML文件。
例如,创建一个名为my-app-conf.xml的新XML文档。根元素是configuration,内部包含property元素。我们定义两个属性:
<?xml version="1.0"?>
<configuration>
<property>
<name>fs.default.name</name>
<value>hdfs://anotherhost:9020</value>
</property>
<property>
<name>my.app.parameter1</name>
<value>blue</value>
</property>
</configuration>
现在,我想在实例化配置对象时加载这个文件。加载自定义配置的方法是调用配置对象的addResource方法,并传递配置文件名。由于此文件位于一个包中,我需要给出目录路径:conf.addResource("hadoop/config/demo/my-app-conf.xml");。
这样,当我实例化配置对象时,它会加载core-default.xml、etc/hadoop下的core-site.xml,然后加载我自己的配置资源。最终,当我打印配置的fs.default.name时,它将从这里获取值,应该打印出anotherhost:9020。运行程序,确实如此。
我们还可以尝试打印我们自己的属性my.app.parameter1,它应该打印出blue。这就是你向应用程序传递参数的基本方式。你可以将其类比为Java Web应用程序编程中的web.xml。
使用addDefaultResource简化加载 🔄
最后,我想演示addDefaultResource的概念。想象一下,我后来实例化了第二个配置对象Configuration conf2 = new Configuration();,然后我想打印那些值。如果我运行程序,你会注意到第一个配置对象加载了资源my-app-conf.xml,但第二个配置对象没有加载该资源,因为我没有为它调用addResource方法。
每次实例化配置对象时,你都不希望进去调用addResource方法。这时addDefaultResource的概念就派上用场了。在我的静态初始化块中,我可以写:Configuration.addDefaultResource("my-app-conf.xml");,然后完全移除之前的addResource调用。
作为此类加载的一部分,当这个类在静态初始化块中被加载时,它会自动将此XML文件注册为默认资源。现在,当我实例化一个配置对象时,我会自动获得这个XML文件。这个思路后来被文件系统层、YARN层和MapReduce层所使用。关键是它让你更容易地实例化任意多个配置对象。
当我运行这个程序时,你会注意到它为两个配置都打印了正确的值。我确实不必为conf和conf2对象实例化并加载资源,我只需要实例化它,它就会默认加载。这就是添加默认资源作为配置的概念。
总结 📝
在本节中,我们深入探讨了Hadoop配置API。这显然是一个我们必须实例化并为其传递引用的对象,因为我们在Hadoop中做的几乎所有事情都会用到它,随着我们开始进行HDFS编程,我们将看到这一点。

我们还学习了如何在NetBeans中设置Hadoop开发环境。正如我提到的,我使用的是NetBeans,但如果你习惯使用Eclipse,你也可以配置你的IDE以支持Hadoop,其中有一个类似的概念可以用来添加库。
最后,回顾一下Apache Hadoop配置概念:我们有core-default.xml,它是你实例化配置对象时加载的一部分;还有hdfs-default.xml、mapred-default.xml和yarn-default.xml。这些XML文件只有在你引入与HDFS、MapReduce或YARN相关的类时才会被加载。例如,当你引入位于HDFS包中的FileSystem类时,它会自动加载那些默认值,并加载相应的-site.xml文件。你无需担心,因为它会自动完成所有这些事情。对你来说,最重要的一点是,配置对象是一个非常重要的对象,在Hadoop中做任何事情几乎都需要实例化它。现在我们知道如何做到这一点,你也知道了默认配置文件的位置以及默认值是什么。
031:HDFS Java API解析 🗂️

在本节课中,我们将学习如何使用Java API来操作HDFS文件系统。我们将概述关键的Java API包和类,并了解API如何向客户端传递警告、错误和异常信息。

HDFS Java API概述
上一节我们学习了如何为Hadoop设置Java开发环境以及如何实例化Hadoop配置对象。本节中,我们将重点学习用于HDFS编程的Java API。
正如之前提到的,对HDFS中的文件和目录进行CRUD操作有三种编程接口。我们已经讨论过使用JNI桥调用底层Java实现的C API。本节我们将概述Java API。
我们需要熟悉几个关键的HDFS Java API包。
以下是主要的HDFS Java API包:
org.apache.hadoop.fs:处理文件系统API。如其名,FS代表文件系统。它包含一个非常重要的抽象类FileSystem,以及随Hadoop JAR文件提供的多个FileSystem类的具体实现。org.apache.hadoop.io:处理IO操作,特别是IO的序列化方面,即将对象序列化为网络流或文件流。该包包含Writable接口和WritableComparable接口。对于基本类型(如Integer、Float、Double、String),有多个WritableComparable接口的具体实现,我们将在Hadoop序列化章节中介绍。org.apache.hadoop.io.compress:处理压缩API。该包包含压缩编解码器(Codec,即编码器/解码器)。Hadoop提供了多种压缩实现,它们都是这个包的一部分。
我们还需要熟悉几个关键的HDFS Java API类。
以下是关键的HDFS Java API类:
FileSystem类:这是一个抽象的文件系统类。你获取对此文件系统对象的引用,实际上会得到一个真实文件系统对象的实例,以执行所有文件系统操作。这是一个非常重要的类,几乎所有操作都通过FileSystem类完成。Path类:表示一个文件。FSDataInputStream类:用于从文件中读取数据。FSDataOutputStream类:用于将数据写入文件。FileStatus类:提供文件信息,例如文件大小、所有者、文件权限。
FileSystem类详解
FileSystem类是一个抽象类,是通用文件系统的基类。核心思想是,由于FileSystem类是抽象类,你需要通过使用FileSystem对象中可用的工厂风格get方法来获取一个具体的文件系统对象。
这个get方法基于默认配置或你传递给get方法的URI方案,将返回你的文件系统对象。我们将在下一节(创建和删除文件)中看到相关示例。
一旦你在代码中获取了FileSystem对象,就可以使用它来操作文件系统。这里的“操作”包括:创建新文件、打开现有文件进行读取、打开文件进行追加操作、删除文件、创建和删除目录、检查文件或目录是否存在、获取文件状态信息(如文件长度、权限、所有者),以及我们将要讨论的用于列出或选择文件的“Globbing”概念。
Hadoop提供了多个具体的文件系统类。
以下是Hadoop中可用的具体文件系统类:
LocalFileSystem类:位于fs包中,处理本地文件系统。每台机器都有一个本地文件系统(即原生文件系统)。它代表你机器上的本地文件系统,URI方案是file:///。DistributedFileSystem类:代表HDFS。这是最重要的文件系统之一,因为我们将使用HDFS。分布式文件系统的URI方案是hdfs:///。NativeS3FileSystem类:代表Amazon S3文件系统。其URI方案是s3n:///。S3FileSystem类:这是S3文件系统的一个变体。注意,这个叫S3文件系统,而前一个叫原生S3文件系统。这是一个基于块的文件系统,就像HDFS一样。原生文件系统对文件大小有限制(只能为5GB),而这个基于块的S3文件系统没有限制。其URI方案通常是s3:///。如果你要在Amazon环境中进行MapReduce编程,通常会使用S3文件系统,而不是原生S3文件系统,因为你的文件通常会超过5GB。
我想强调的是,Hadoop的fs包中提供了多种文件系统实现。
Path类详解
Path类是一个具体类,代表HDFS中的一个文件或目录。它只表示文件名,因为你不能实例化一个Path对象然后直接用它来读取或写入文件。但是,你可以获取一个代表HDFS或本地文件系统中文件名的Path对象,然后将其传递给FileSystem对象以打开文件进行读取或写入。
有几种方法可以创建Path对象:你可以向Path对象传递一个字符串(相对或绝对路径,取决于是否以斜杠开头),它会创建一个代表该特定文件或目录的Path对象。你也可以通过传递一个父Path对象(目录)和一个子项(可能是父路径目录中的子目录或文件)来创建Path对象。还可以是Path和String、String和Path,或String和String的组合,有多种变体可供使用。
输入/输出流与文件状态类
FSDataInputStream类是用于读取文件的流。你不能直接实例化FSDataInputStream类,但可以通过打开一个现有文件进行读取来获取它的一个实例,这将返回一个FSDataInputStream对象。
一旦你有了与文件关联的FSDataInputStream对象,就可以从文件中读取数据。你可以读取Integer、Float、Char、Boolean等任何基本类型,也可以将字节读入数组,从而一次读取多个字节。你还可以获取当前读取位置,定位到特定位置,跳过字节,几乎可以在该特定文件内进行某种随机访问。
FSDataOutputStream类是用于写入文件的流。就像FSDataInputStream类一样,你不能实例化FSDataOutputStream类,但你可以使用FileSystem对象创建新文件或打开现有文件,当你这样做时,它会返回一个FSDataOutputStream对象。
一旦你有了FSDataOutputStream对象,就可以向文件写入字节。因此,基本类型(Integer、Float、Char、Boolean等)都可以写入。如果你有一个字节数组,可以写出整个数组。你可以获取当前位置,也可以同步所有缓冲区,确保你写入的所有内容都已写入底层文件系统。
FileStatus类代表文件的客户端信息。这里的“客户端信息”是指用于操作文件系统或获取文件系统信息的Java HDFS API客户端。因此,这是文件状态对象的客户端表示。
核心思想是,你通过在具体的文件系统对象上调用listStatus方法来获取FileStatus对象的实例。你访问FileSystem对象并调用listStatus方法,它将返回一个FileStatus对象。一旦你有了FileStatus对象,就可以获取文件长度、最后访问时间、修改日期和时间、块大小、复制因子等。当你执行ls -l操作时获得的所有文件信息,都可以通过FileStatus对象获得。
异常处理与返回值
你注意到,HDFS API中的许多方法都会抛出java.io.IOException。这是合理的,因为你正在读取文件、写入文件、删除文件,这些都是IO操作,因此抛出IOException是合理的。
如果一个方法抛出异常该怎么办?很简单,你可以检查异常堆栈跟踪,这通常是我们会做的。但你也可以检查NameNode的日志文件,该文件位于Hadoop安装目录的logs子目录下($HADOOP_PREFIX/logs)。查看namenode日志文件,看看服务器端是否有任何特定信息,因为请记住,在HDFS编程中,有客户端和服务器端(即执行所有元数据操作的HDFS NameNode)。
你还会注意到,一些FileSystem方法返回一个布尔值。例如,delete、rename和mkdirs方法都返回布尔值。
为什么这些方法返回布尔值?想一想,如果你要求文件系统API删除一个文件,而该文件不存在(因为文件已被删除),它应该怎么做?是应该抛出异常,还是应该保持沉默?这里的方法,比如delete方法,会返回一个布尔值,指示它是否实际执行了操作。如果delete方法返回true,意味着它确实删除了文件(因为文件存在)。如果delete方法返回false,意味着它说“我没有删除文件,但你想让我删除的文件反正不存在”。这就是布尔返回类型背后的思想。
如果方法返回false该怎么办?你当然可以查看NameNode日志文件,看看是否有任何特定信息可供参考。


高级抽象:文件操作与Globbing
除了用于创建、删除和操作文件的低级API外,还有更高级的抽象,允许你在HDFS之间复制和移动文件。
如果你还记得使用hadoop fs命令,我们能够将文件从本地文件系统复制到Hadoop文件系统,从本地文件系统移动到Hadoop文件系统,反之亦然(从Hadoop分布式文件系统复制到本地,从Hadoop分布式文件系统移动到本地)。文件系统API中提供了类似的功能,因此有一些非常好的高级方法可供你使用,允许你将文件从本地文件系统移动或复制到Hadoop文件系统,反之亦然。
HDFS还支持一个称为“Globbing”的概念。Globbing是一种文件匹配模式概念,在Unix shell中经常使用。HDFS支持POSIX文件匹配模式,当然在此基础上还增加了大括号支持。这是一种通过使用模式遍历文件子集的好技术。如果你不知道什么是glob模式,你可能想在Google上阅读更多相关内容,网上有很多关于此的教程。

HDFS支持多种glob模式。
以下是HDFS支持的几种glob模式:
?:匹配任何单个字符。*:匹配零个或多个字符。[abc]:匹配括号内的任意单个字符(此处为a、b或c)。[a-b]或[a-z]:范围匹配。[^abc]:取反,匹配不在此特定集合中的任何字符。\:用于转义字符的含义。{ab, cd}:大括号概念,匹配字符串集合中的字符串(此处为“ab”或“cd”)。
HDFS支持许多glob概念。再次强调,如果你不理解Globbing,请参考互联网上的Globbing概念,有很多教程可用。
官方文档速览
我们刚刚看了各种HDFS包和文件。让我们看看Hadoop HDFS文档。
如果你访问Apache Hadoop页面,有API文档。这里列出了所有的包。我们看了hadoop.configuration包,也看了hadoop.fs包。我们还提到有hadoop.io包,它包含hadoop.io.compress包以及IO包中的一些其他子包。这些是HDFS编程所需的主要包。
让我们快速看一下hadoop.configuration包。它包含Configuration类。注意,Configuration是一个具有名称、值和final元素(指示属性是否可以更改)的属性。有像addDefaultResource(我们看过的静态方法)这样的方法,还有我们看过的dumpConfiguration方法。

让我们回过头来看看FileSystem包。在FileSystem包中,有FileSystem类,它是一个抽象的基类。有几个类实现或扩展了FileSystem类,比如S3FileSystem、NativeS3FileSystem或RawLocalFileSystem。然后是FileStatus类,它提供文件信息,例如文件长度、文件所有者、文件路径、文件权限。我们还提到了GlobFilter概念,所以这是支持GlobFilter概念的GlobFilter类。
让我们看看io.compress包。它包含所有各种编解码器、压缩编解码器、接口以及这些编解码器的几种实现。还有基本的io包,处理序列化。这里的关键类或关键接口是Writable接口和WritableComparable接口,我们稍后会介绍。这里是Writable接口和WritableComparable接口。
希望这能让你对HDFS文件系统中的所有各种包和类有一个概述。
总结

在本节中,我们学习了用于操作HDFS文件系统的关键Java API包和类。我们介绍了处理文件系统API的hadoop.fs包,处理序列化API的hadoop.io包,以及处理压缩API的io.compress包。我们还了解了像FileSystem类(允许你操作文件系统)、Path类(允许你表示HDFS或任何其他文件系统中的文件)、FSDataInputStream类和FSDataOutputStream类(允许你读写文件)、FileStatus类(允许你获取文件信息)这样的类,并且我们了解了HDFS中如何处理异常。
032:HDFS 文件增删改查操作 📁


在本节课中,我们将学习如何使用 Java API 对 Hadoop 分布式文件系统(HDFS)中的文件进行创建、读取、更新和删除操作。我们将通过具体的代码示例来理解每个步骤,并探讨 HDFS 内部如何处理这些操作。
概述
HDFS 提供了丰富的 Java API 来管理文件。我们将学习如何配置 Hadoop 环境、获取文件系统对象,并执行基本的文件操作。理解这些操作背后的机制对于高效使用 HDFS 至关重要。
创建文件
上一节我们介绍了 HDFS 的基本概念,本节中我们来看看如何创建一个新文件并向其中写入内容。
以下是创建文件并写入内容的步骤:
- 创建一个 Hadoop 配置对象。
- 使用配置对象获取一个文件系统对象。
FileSystem类有一个静态方法get,它接收配置对象,并根据配置返回相应的具体文件系统实例。 - 在文件系统对象上调用
create方法,并传入一个Path对象(代表文件系统中的路径)。如果创建成功,将返回一个FSDataOutputStream对象,用于向文件写入内容。 create方法有多个重载版本,允许你根据覆盖标志、副本级别、缓冲区大小等进行自定义设置。- 获得
FSDataOutputStream对象后,调用其write方法将内容写入文件。有多个重载的write方法可供使用。 - 写入完成后,关闭
FSDataOutputStream对象和文件系统。
示例代码:HDFSFileCreate.java
Path file = new Path("/records.txt");
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
FSDataOutputStream out = fs.create(file);
out.writeBytes("record1\n");
out.writeBytes("record2\n");
out.writeBytes("record3\n");
out.close();
fs.close();
读取文件
了解了如何创建文件后,接下来我们学习如何读取已存在的文件内容。
以下是读取文件的步骤:
- 创建一个 Hadoop 配置对象。
- 使用配置对象获取文件系统对象。
- 使用文件系统对象的
open方法打开文件,传入要打开的文件的Path对象。如果成功,将返回一个FSDataInputStream对象。 - 使用
FSDataInputStream对象的read方法读取文件内容。基本的read方法返回一个整数(字符)。当到达文件末尾时,read方法会返回 -1,因此通常需要在循环中读取。 - 读取完成后,关闭
FSDataInputStream对象和文件系统。
示例代码:ReadHDFSFile.java
Path file = new Path("/records.txt");
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
FSDataInputStream in = fs.open(file);
int bytesRead;
while ((bytesRead = in.read()) != -1) {
System.out.print((char) bytesRead);
}
in.close();
fs.close();
追加内容到文件
除了创建新文件,我们还可以向现有文件追加内容。


以下是向文件追加内容的步骤:
- 创建一个 Hadoop 配置对象。
- 使用配置对象获取文件系统对象。
- 在文件系统对象上调用
append方法,传入要追加内容的文件的Path对象。这将返回一个FSDataOutputStream对象。 - 使用
FSDataOutputStream对象的write方法写入要追加的内容。 - 写入完成后,关闭
FSDataOutputStream对象和文件系统。
示例代码:HDFSFileUpdate.java
Path file = new Path("/records.txt");
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
FSDataOutputStream out = fs.append(file);
out.writeBytes("record4\n");
out.writeBytes("record5\n");
out.writeBytes("record6\n");
out.close();
fs.close();
删除文件
文件管理也包括删除不再需要的文件。
以下是删除文件的步骤:
- 创建一个 Hadoop 配置对象。
- 使用配置对象获取文件系统对象。
- 在文件系统对象上调用
delete方法,传入要删除的文件的Path对象。第二个参数recursive对于目录更有意义,如果设为true则会递归删除目录及其内容。 - 删除操作完成后,关闭文件系统。
delete方法返回一个布尔值,指示操作是否成功(文件存在并成功删除则返回true,否则返回false)。
示例代码:HDFSFileDelete.java
Path file = new Path("/records.txt");
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
boolean isDeleted = fs.delete(file, false); // false 表示不递归
System.out.println("Delete successful: " + isDeleted);
fs.close();
重命名文件
有时我们需要更改文件的名称或位置。
以下是重命名文件的步骤:
- 创建一个 Hadoop 配置对象。
- 使用配置对象获取文件系统对象。
- 在文件系统对象上调用
rename方法,传入旧文件的Path对象和新文件的Path对象。新路径可以在不同目录甚至不同文件系统中。 - 重命名操作完成后,关闭文件系统。
rename方法也返回一个布尔值指示操作状态。
示例代码:HDFSFileRename.java
Path oldFile = new Path("/records.txt");
Path newFile = new Path("/details.txt");
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
boolean isRenamed = fs.rename(oldFile, newFile);
System.out.println("Rename successful: " + isRenamed);
fs.close();
带进度回调的文件操作
对于长时间运行的操作(如写入大文件),提供进度反馈对用户体验很重要。
FileSystem 类的 create 和 append 方法有重载版本,可以接收一个回调接口作为参数。这个回调接口用于在写入操作进行时报告进度。


示例概念:ProgressibleCopy.java
// 在调用 create 方法时传入一个 Progressable 对象
FSDataOutputStream out = fs.create(destFile, new Progressable() {
@Override
public void progress() {
System.out.print("."); // 写入过程中定期打印点号
}
});
// ... 然后进行写入操作
在写入操作期间,progress 方法会被定期调用。
HDFS 文件操作内部机制

理解 API 调用背后发生了什么,有助于我们编写更健壮的程序。
文件写入的解剖
当客户端调用 create 方法时:
DistributedFileSystem客户端向 NameNode 发起 RPC 调用。- NameNode 检查权限,如果允许,则创建文件元数据(此时不分配数据块)。
- 客户端通过
FSDataOutputStream写入数据。输出流已从 NameNode 获取了数据块应写入哪些 DataNode 的信息。 - 第一个数据块被写入一个 DataNode(例如 DataNode1),然后该 DataNode 会异步地将副本复制到其他 DataNode(例如 DataNode2, DataNode3)。
- 客户端继续写入更多数据,副本在后台异步复制。
- 客户端关闭文件。
文件读取的解剖
当客户端调用 open 方法时:
DistributedFileSystem客户端向 NameNode 发起 RPC 调用,获取文件块的位置信息。- NameNode 返回包含每个块副本位置的列表。
- 客户端通过
FSDataInputStream读取数据。输入流会从最近的 DataNode 读取第一个块。 - 如果读取某个 DataNode 失败,它会自动尝试从该块的其他副本读取。
- 读取完所有数据后,客户端关闭输入流。

HDFS 一致性模型
HDFS 有其特定的文件一致性保证。
- 正在写入文件的可见性:当客户端写入文件时,其他客户端只能看到已完全写完的数据块。例如,Client1 正在写 Block2,那么 Client2 只能读取已完成的 Block1。
- 独占写入:HDFS 支持独占写入。当第一个客户端打开文件进行写入时,NameNode 会授予一个“租约”。在此期间,其他客户端尝试写入同一文件将被拒绝。
- 文件修改:HDFS 不支持在文件中间进行修改。唯一“修改”文件的方式是重写整个文件,或者使用
append操作在文件末尾追加内容(需确保dfs.support.append属性为true)。

总结

本节课中,我们一起学习了使用 Java API 对 HDFS 文件进行增删改查操作。我们掌握了创建、读取、追加、删除和重命名文件的方法,并通过代码示例加深了理解。此外,我们还探讨了文件操作背后的内部机制,包括写入和读取的流程,以及 HDFS 的一致性模型。这些知识是构建高效、可靠的大数据应用的基础。
033:HDFS编程基础总结 🗂️

在本节课中,我们将总结HDFS编程的基础知识。我们将回顾访问HDFS的三种编程方式,重点探讨Java API的使用,并介绍配置对象、开发环境设置以及关键API类。通过本课,你将掌握使用Java进行HDFS文件操作的核心概念和步骤。
概述
在本模块中,我们学习了三种以编程方式访问HDFS的途径:C语言、Java和RE。我们快速了解了如何使用C语言编程、编译和运行程序,但大部分时间我们专注于研究Java API。我们还了解到,配置对象是进行Hadoop Java编程的关键,几乎任何操作都需要获取配置对象的实例。
配置对象与开发环境
上一节我们提到了配置对象的重要性,本节中我们来看看如何具体设置开发环境。
首先,我们学习了如何获取配置对象的实例。接着,我们学习了如何在集成开发环境中设置开发环境,特别是使用NetBeans IDE。我们学习了如何设置类路径和配置,以便能够编译和运行Hadoop Java程序。
关键Java API类
在掌握了环境配置后,我们现在来探讨HDFS编程中的一些关键Java API类。
我们看到FileSystem类是一个抽象类,也是一个关键类。获取FileSystem类实例的方法是使用工厂风格的get方法,并传入我们已学会创建的配置对象。
文件操作
一旦我们获得了FileSystem对象,就可以执行多种文件操作。
我们可以通过调用create方法来创建文件,通过调用open方法来打开文件进行读取,可以向文件追加内容,也可以重命名文件甚至删除文件。
支持类
除了核心类,HDFS编程还有几个重要的支持类。
以下是主要的支持类及其作用:
Path类:用于表示文件系统中的文件路径。FSDataInputStream类:允许你使用流机制读取数据。FSDataOutputStream类:允许你向文件写入数据。
总结

本节课中,我们一起学习了HDFS编程的基础总结。我们回顾了三种编程访问方式,深入探讨了Java API,理解了配置对象的核心作用,并实践了开发环境的设置。我们认识了FileSystem、Path、FSDataInputStream和FSDataOutputStream等关键类,并学会了如何利用它们进行文件的创建、读取、追加、重命名和删除等基本操作。这些知识为进行更复杂的Hadoop大数据处理编程奠定了坚实的基础。
034:HDFS高级编程导论 🚀

在本节课中,我们将学习HDFS的一些高级编程概念。我们将从学习如何对目录执行增删改查操作开始,然后探讨压缩、序列化等概念,以及基于文件的专用数据结构,如序列文件和映射文件。
目录操作 📁
上一节我们介绍了课程概述,本节中我们来看看如何对HDFS目录执行创建、读取、更新和删除操作。
以下是目录操作的核心方法:
- 创建目录:使用
FileSystem.mkdirs(Path)方法。 - 检查目录/文件是否存在:使用
FileSystem.exists(Path)方法。 - 删除目录/文件:使用
FileSystem.delete(Path, boolean)方法,其中布尔值参数表示是否递归删除。 - 列出目录内容:使用
FileSystem.listStatus(Path)方法,它返回一个FileStatus对象数组。

文件压缩与解压 🗜️
掌握了目录操作后,我们来看看如何对HDFS文件进行压缩与解压。压缩可以有效减少存储空间和网络传输开销。
Hadoop支持多种压缩编解码器,每种都有其特点。选择编解码器时,需要在压缩速度、压缩比和是否支持分片之间权衡。
以下是Hadoop中常用的压缩编解码器:
- DEFLATE:使用
.deflate扩展名,压缩比和速度均衡,但不可分片。 - gzip:使用
.gz扩展名,基于DEFLATE,压缩比较好,但通常不可分片。 - bzip2:使用
.bz2扩展名,压缩比高且支持分片,但压缩速度慢。 - LZO:使用
.lzo扩展名,压缩速度快,支持分片(需建立索引)。 - Snappy:使用
.snappy扩展名,压缩速度快,但压缩比一般,且不可分片。
在MapReduce作业中启用压缩通常很简单,可以通过配置属性完成。例如,设置 mapreduce.output.fileoutputformat.compress 为 true 并指定编解码器。
序列化与反序列化 🔄
了解了文件压缩,我们接下来探讨Hadoop中的序列化机制。序列化是将对象转换为字节流以便存储或传输的过程,反序列化则是其逆过程。
Hadoop拥有自己的序列化框架,它要求序列化格式紧凑、快速且可扩展。核心接口是 Writable。
实现可序列化类需要遵循以下步骤:
- 实现
Writable接口。 - 重写
write(DataOutput out)方法,将对象字段写入流。 - 重写
readFields(DataInput in)方法,从流中读取字段并填充对象。
例如,一个自定义的 MyWritable 类结构如下:
public class MyWritable implements Writable {
private int value;
private String text;
@Override
public void write(DataOutput out) throws IOException {
out.writeInt(value);
out.writeUTF(text);
}
@Override
public void readFields(DataInput in) throws IOException {
value = in.readInt();
text = in.readUTF();
}
// 省略getter和setter方法
}
基于文件的数据结构 📊
最后,我们来学习Hadoop中两种重要的专用文件格式,它们常用于解决海量小文件存储效率低下的问题。
序列文件(SequenceFile) 是存储二进制键值对数据的文件格式。它支持三种存储格式:未压缩、记录压缩和块压缩。你可以使用 SequenceFile.Writer 和 SequenceFile.Reader 类来读写。
映射文件(MapFile) 是经过排序并带有索引的序列文件,它由两部分组成:数据文件(/data)和索引文件(/index)。映射文件允许基于键进行快速查找。你可以使用 MapFile.Writer 和 MapFile.Reader 类来操作。
总结 🎯

本节课中我们一起学习了HDFS的高级编程概念。我们掌握了如何操作HDFS目录,了解了文件压缩的编解码器及其应用场景,实现了Hadoop Writable 接口以进行序列化,并认识了序列文件和映射文件这两种用于高效处理小文件的专用数据结构。这些技能将帮助你更有效地在大数据项目中管理和处理数据。
035:HDFS目录操作管理 📁

在本节课中,我们将学习如何使用Java API对HDFS(Hadoop分布式文件系统)中的目录进行创建、读取和删除操作。请注意,我们不会讨论“更新”目录,因为更新目录本质上就是在目录内创建文件或子目录,而创建文件的操作我们已经介绍过了。本节我们将重点介绍在目录内创建子目录,这涵盖了更新的概念。


创建HDFS目录
上一节我们介绍了文件操作,本节中我们来看看如何创建目录。创建HDFS目录的前几个步骤与文件操作类似。
以下是创建目录的步骤:
- 创建一个Hadoop配置对象。
- 获取一个文件系统对象。一旦你获得了一个具体的文件系统对象,就可以调用
mkdirs方法。 - 向
mkdirs方法传递一个路径对象,即你想要创建的目录路径。
mkdirs 方法有几个重载版本:一个只接收路径;一个接收路径和权限;还有一个静态版本的 mkdirs,它接收一个文件系统对象作为参数。
你可以一次性创建多级目录(例如,目录、子目录、子子目录)。你只需要让路径对象表示出完整的层级结构,mkdirs 方法会自动为你创建所有这些目录。
mkdirs 方法会返回一个布尔值标志。如果无法创建目录(例如目录已存在),它会返回 false。操作完成后,记得关闭文件系统。
示例代码:创建目录
// 定义要创建的目录路径,例如 /data/subdir/subsubdir
Path dirPath = new Path("/data/subdir/subsubdir");
// 获取文件系统对象
FileSystem fs = FileSystem.get(conf);
// 调用 mkdirs 方法创建目录
boolean isCreated = fs.mkdirs(dirPath);
// 检查创建是否成功
System.out.println("Directory created: " + isCreated);
// 关闭文件系统
fs.close();
读取(列出)目录内容
现在我们已经知道如何创建目录,接下来看看如何读取或列出一个目录的内容。
以下是读取目录内容的步骤:
- 创建Hadoop配置对象。
- 获取文件系统对象。
- 在文件系统对象上调用
listStatus方法。该方法接收一个路径作为参数。 listStatus方法返回一个FileStatus对象数组。它实际上接收一个目录作为参数,并返回该目录内所有文件和子目录的列表,每个项都封装为一个FileStatus对象。
FileStatus 对象代表一个文件或目录,包含文件名、路径、权限、所有者、副本数和文件大小等信息。获取数组后,只需遍历这些 FileStatus 对象即可。
listStatus 方法也有几个重载版本:一个接收单个目录路径;一个接收路径数组,可以一次性获取多个目录中所有文件的信息;还有一个版本允许传递路径过滤器,以排除你不感兴趣的文件。
操作完成后,关闭文件系统。


示例代码:列出目录内容
// 定义要读取的目录路径,例如根目录 "/"
Path listPath = new Path("/");
// 获取文件系统对象
FileSystem fs = FileSystem.get(conf);
// 调用 listStatus 方法获取文件状态数组
FileStatus[] statusList = fs.listStatus(listPath);
// 遍历数组并打印信息
for (FileStatus status : statusList) {
System.out.print("Path: " + status.getPath());
// 判断是文件还是目录
if (status.isDirectory()) {
System.out.println(" [Directory]");
} else {
System.out.println(" [File]");
}
}
// 关闭文件系统
fs.close();
删除HDFS目录
了解了目录的创建和读取后,我们来看看如何删除目录。
删除目录的步骤如下:
- 创建Hadoop配置对象。
- 获取文件系统对象。
- 在文件系统对象上调用
delete方法。
delete 方法接收一个路径对象和一个布尔类型的递归标志 recursive。请注意,这个 delete 方法与删除文件时使用的是同一个方法。区别在于,删除目录时,recursive 标志指示是否递归删除目录及其所有内容。
delete 方法同样返回一个布尔值,指示是否成功删除了目录。如果目录原本就不存在,则会返回 false。操作完成后,关闭文件系统。
示例代码:删除目录
// 定义要删除的目录路径,例如 "/data"
Path deletePath = new Path("/data");
// 获取文件系统对象
FileSystem fs = FileSystem.get(conf);
// 调用 delete 方法删除目录,true 表示递归删除
boolean isDeleted = fs.delete(deletePath, true);
// 检查删除是否成功
System.out.println("Directory deleted: " + isDeleted);
// 关闭文件系统
fs.close();
使用通配符(Globbing)匹配文件
在HDFS API概述中我们提到了通配符(Globbing)的概念,现在让我们具体看看其步骤。
使用通配符匹配HDFS文件的步骤如下:

- 创建Hadoop配置对象。
- 使用配置对象获取文件系统对象。
- 调用
globStatus方法,并向其传递路径。
globStatus 方法有趣之处在于,你传递给它的路径实际上是一个通配符模式。该方法会返回一个与通配符模式匹配的 FileStatus 对象数组。

globStatus 方法也有重载版本:一个接收单个通配符路径;一个可以接收通配符路径对象数组。操作完成后,关闭文件系统。

示例代码:使用通配符
// 定义一个通配符路径,匹配根目录下所有三个字符的文件或目录
Path globPath = new Path("/???");
// 获取文件系统对象
FileSystem fs = FileSystem.get(conf);
// 调用 globStatus 方法获取匹配的文件状态数组
FileStatus[] globList = fs.globStatus(globPath);
// 遍历并打印匹配项
for (FileStatus status : globList) {
System.out.println("Matched: " + status.getPath().getName());
}
// 关闭文件系统
fs.close();
操作演示
现在,我将以用户 “ah amin” 的身份登录HDFS服务器,演示上述目录的CRUD操作。

1. 创建目录
首先,我想创建一个名为 /data/subdir/subsubdir 的目录。运行创建目录的程序前,先使用 hadoop fs -ls / 命令查看,确认该目录不存在。然后运行程序,程序显示目录创建成功。使用 hadoop fs -ls -R / 命令递归查看,可以看到 /data、/data/subdir 和 /data/subdir/subsubdir 都已成功创建。
2. 读取目录
接下来,运行读取目录内容的程序。程序告诉我根目录 / 下有四个内容:data(目录)、dedo.txt(文件)、rating.data(文件)和 records_start.txt(文件)。使用 hadoop fs -ls / 命令验证,data 确实是目录,其他三项是文件。
3. 通配符匹配
为了演示通配符,我先创建一些测试环境:
- 使用
hadoop fs -mkdir /ful创建一个名为ful的三字母目录。 - 使用
hadoop fs -copyFromLocal命令将本地文件AAA、BBB、CCC复制到根目录/。
现在,根目录下有了三个三字母文件(AAA, BBB, CCC)和一个三字母目录(ful)。运行通配符程序,指定模式为/???,程序成功列出了AAA、BBB、CCC三个文件和ful目录。

4. 删除目录
最后,运行删除目录的程序,目标是删除 /data 目录。程序运行后返回 true,表示删除成功。再次执行 hadoop fs -ls /,/data 目录已消失。如果再次运行删除程序,由于目录已不存在,程序会返回 false。
总结
在本节课中,我们一起学习了HDFS目录管理的CRUD API:
- 创建目录:使用
FileSystem.mkdirs(Path)方法,可一次性创建多级目录。 - 读取目录:使用
FileSystem.listStatus(Path)方法获取目录内容列表,返回FileStatus对象数组。 - 删除目录:使用
FileSystem.delete(Path, recursive)方法,通过recursive参数控制是否递归删除。 - 通配符匹配:使用
FileSystem.globStatus(Path)方法,通过路径模式批量匹配文件或目录。

通过理解这些核心方法和步骤,你已经掌握了在HDFS中进行基本目录操作的能力。
036:Hadoop文件压缩与解压缩技术 📦

在本节课中,我们将正式定义数据压缩及其相关术语,分析文件压缩的优缺点,并重点学习Hadoop HDFS对压缩的支持,包括如何压缩和解压缩HDFS中的文件。
什么是数据压缩? 🤔
数据压缩是使用更少的比特位对原始数据进行编码的技术。其核心思想是减少数据的原始大小。
市场上有多种压缩算法,它们都遵循一种称为“空间-时间权衡”的原则。有些算法在压缩和解压缩速度上更快,但生成的文件可能占用更多空间;而另一些算法压缩速度可能较慢,但最终占用的磁盘空间更小。


压缩有多种文件格式,例如:T F、自实、Bs of2、LG、snappy等。需要明确的是,数据压缩不是数据加密,它们是两个不同的概念。尽管两者都可能生成二进制格式,但本质不同。
压缩的类型 🔄
压缩主要分为两种类型:有损压缩和无损压缩。
有损压缩通过移除相对不重要的信息来减少比特数。关键在于“移除”。你移除了认为不那么重要的信息。在有损压缩中,你无法重建原始文件,因为部分非关键内容已被移除。音频和视频文件的处理是典型的有损压缩例子,例如在存储或传输视频文件时,可能会移除每第10帧或第15帧。
无损压缩则是通过统计冗余来减少内容。换句话说,它会检查内容的哪些部分是冗余的,然后在目标文件中只存储一次,并记录这些信息片段出现的位置。在这种格式下,你可以重建原始文件。对于纯粹的数据处理,我们关心的是无损压缩,因此在数据分析中,大多数时候我们使用无损压缩。
编解码器与HDFS压缩支持 🛠️
“编解码器”是压缩器和解压缩器的别称。其思想是,你可以将原始文本或内容通过压缩器处理,得到压缩后的内容;然后可以再通过解压缩器处理压缩内容,以恢复原始文本。
在Java中,有一系列用于压缩和解压缩的编解码器类。
HDFS内置了多种压缩类。这些压缩编解码器可以是纯Java实现,事实上它们大多是纯Java实现。如果你想使用原生实现,也可以找到一些,但需要将其包含在你的LD库路径中。同样,也可以引入其他实现,无论是Java实现还是原生实现。例如,很多人会引入LZ实现,LZF是一个相当好的实现,但由于其LG许可证问题,Hadoop并未随其分发,但市场上很多人使用LZU实现。
文件压缩的优缺点 ⚖️
上一节我们介绍了压缩的基本概念和类型,本节中我们来看看压缩文件的具体好处和需要考虑的代价。
压缩文件的好处如下:
- 减少存储空间:压缩减少了存储文件所需的空间。特别是在HDFS中,文件通常有三份副本,文件大小会倍增,因此在HDFS中压缩文件是一个很好的做法。
- 减少数据传输带宽:压缩减少了数据传输所需的带宽,尤其是在写入数据块时,数据需要在节点间复制。
- 提升数据传输速度:压缩加快了磁盘与主内存之间的数据传输速度。考虑到磁盘的数据传输速率并不高,通过减小文件大小,你实际上减少了需要加载到主内存的数据量。因为要进行任何处理,都必须将数据加载到主内存。例如,一个54GB的文件如果压缩到17GB,那么只需加载17GB到内存,这就在无形中提升了磁盘的有效数据传输率。
压缩的缺点如下:
- 消耗CPU资源:压缩和解压缩是CPU密集型操作,会占用CPU周期。因此,在决定压缩之前需要权衡。如果你需要反复多次读取某个文件,可能不值得压缩,因为每次解压缩都会消耗CPU周期。这是一个权衡,我们将在Hadoop MapReduce部分进一步讨论。
压缩编解码器接口与使用步骤 💻
Hadoop Java API中有一个名为compression的包,它定义了一个名为CompressionCodec的接口。该接口是所有各种编解码器的基接口。例如,Hadoop自带了用于默认类型的默认编解码器、GzipCodec和BZip2Codec。
在代码中,只要有机会,就应该使用CompressionCodec接口,而不是具体的编解码器类。具体做法是:当你实例化一个具体的编解码器时,应将其赋值给CompressionCodec类型的变量,然后使用CompressionCodec接口中提供的方法来调用所有功能。
CompressionCodec接口中有几个非常重要的方法,一个是createInputStream,另一个是createOutputStream。这就是你获取压缩输入流和压缩输出流的方式。我们将在示例中查看这些内容。
压缩HDFS文件的步骤
基本思路是,假设HDFS中已有一个文件,你想压缩它。你需要做的就是读取这个现有的HDFS文件,然后创建一个新的压缩文件。
以下是具体步骤:
- 创建Hadoop配置对象,获取文件系统对象。
- 打开常规文件(即要压缩的输入文件),获取输入流。
- 打开第二个文件(即要写入压缩信息的目标压缩文件),获取输出流。
- 关键步骤:将常规输出流转换为压缩输出流。为此,你将使用
CompressionCodec。 - 从输入流读取数据,写入压缩输出流。
- 关闭输入流、输出流和文件系统。
让我们通过一个程序片段来理解。假设有一个名为HDFSCompression.java的程序,其中最重要的部分是处理输出流:
// ... 获取文件系统等前期代码
FSDataOutputStream fsDataOutputStream = fs.create(outputPath); // 这是要写入压缩数据的输出流,但还不是压缩输出流
// 创建GzipCodec对象并赋值给CompressionCodec接口
CompressionCodec codec = new GzipCodec();
// 使用编解码器创建压缩输出流
CompressionOutputStream compressedOutputStream = codec.createOutputStream(fsDataOutputStream);


// 现在,使用IOUtils工具类复制数据
IOUtils.copyBytes(fsDataInputStream, compressedOutputStream, conf);
// ... 关闭流等后续代码
IOUtils.copyBytes方法是一个辅助方法,允许你将数据从一个流复制到另一个流,无需像之前章节那样逐字节读取。
解压缩HDFS文件的步骤
假设HDFS中已有一个压缩文件,你想解压它。思路是打开该文件,获取压缩格式的输入流,然后在读取时将其解压缩。你需要将压缩输入流转换为解压缩后的输入流,然后打开一个新文件来写入解压后的数据。
以下是具体步骤:
- 获取Hadoop配置对象。
- 使用配置对象获取文件系统对象。
- 打开压缩文件用于读取,获取输入流(这是一个压缩输入流)。
- 打开一个新文件用于写入解压后的数据(这是常规输出文件)。
- 关键步骤:使用
CompressionCodec将压缩输入流转换为解压缩输入流。 - 读取解压缩输入流,并写入输出流。
- 关闭输入流、输出流和文件系统。
以下是一个解压缩程序的示例片段:


// ... 获取文件系统等前期代码
FSDataInputStream fsDataInputStream = fs.open(inputPath); // 压缩输入流
FSDataOutputStream fsDataOutputStream = fs.create(outputPath); // 解压后的输出流
CompressionCodec codec = new GzipCodec();
// 使用编解码器创建解压缩输入流
CompressionInputStream decompressedInputStream = codec.createInputStream(fsDataInputStream);
// 将解压缩后的数据写入输出文件
IOUtils.copyBytes(decompressedInputStream, fsDataOutputStream, conf);
// ... 关闭流等后续代码

总结 📝
在本节课中,我们一起学习了数据压缩的核心知识。我们首先正式定义了压缩及其相关术语,并区分了有损和无损压缩。接着,我们探讨了文件压缩在节省存储空间、降低带宽和提升I/O速度方面的优点,也指出了其消耗CPU资源的缺点。最后,我们深入了解了HDFS对压缩的支持,并通过概念和代码示例,掌握了如何在HDFS中压缩和解压缩文件的具体步骤和编程方法。后续我们还将学习如何在Hadoop MapReduce中使用压缩技术。
037:Hadoop序列化与反序列化机制 📦

在本节课中,我们将学习Hadoop核心库中的序列化与反序列化机制,并了解如何将其与HDFS文件系统结合使用。
概述
序列化是将结构化对象转换为字节流的过程,而反序列化则是其逆过程。Hadoop在其内部通信和MapReduce框架中广泛使用了序列化。与Java内置的序列化机制不同,Hadoop设计了自己的方案,旨在更紧凑、更高效。
什么是序列化与反序列化?
序列化是将内存中的结构化对象(如客户对象、员工对象、三维点对象)转换为字节流的过程。这样做的目的是为了通过网络传输对象以实现进程间通信,或者将对象持久化存储到像HDFS这样的存储系统中。
反序列化是相反的过程,即将字节流转换回内存中的结构化对象。在行业中,序列化器和反序列化器通常成对出现,称为“序列化/反序列化对”。
Hadoop与序列化
Hadoop在其MapReduce框架中使用了序列化,我们将在后续的MapReduce编程课程中深入探讨。此外,Hadoop各组件间的内部通信也依赖于序列化。例如,当NameNode与DataNode通信,或DataNode与客户端节点通信时,所有传递的对象都会被序列化为字节流。
Hadoop没有使用Java内置的java.io.Serializable接口,而是发明了自己的序列化机制,认为Java原生的序列化不够紧凑且较为复杂。
Writable接口
Hadoop序列化的基础是Writable接口。如果一个类实现了Writable接口,就意味着该类的对象可以被转换为字节流,也可以从字节流中读取并重建。
Writable接口包含两个核心方法:
readFields(DataInput in): 从输入流中读取数据来填充对象字段。write(DataOutput out): 将对象的状态写入输出流。


以下是这两个方法的示意代码:
public interface Writable {
void write(DataOutput out) throws IOException;
void readFields(DataInput in) throws IOException;
}
IntWritable、LongWritable等类都是Writable接口的具体实现。
WritableComparable接口
除了序列化,Hadoop在处理数据时经常需要比较对象(例如在排序阶段)。因此,Hadoop定义了一个WritableComparable接口。
这个接口同时继承了Writable接口和Java的Comparable<T>接口。Comparable接口要求实现compareTo(T o)方法,用于比较两个对象:
- 如果当前对象等于参数对象,返回
0。 - 如果当前对象小于参数对象,返回负值。
- 如果当前对象大于参数对象,返回正值。
因此,任何实现WritableComparable接口的类都必须实现三个方法:write()、readFields()和compareTo()。
许多基础类型都有对应的WritableComparable实现,如IntWritable、LongWritable。Text类则相当于字符串的WritableComparable实现。其他支持类还包括BytesWritable、ObjectWritable等。
实践:实现一个可序列化的3D点类
上一节我们介绍了WritableComparable接口的理论,本节中我们来看看如何实际应用它。我们将创建一个表示三维空间点的Point3D类,并让它实现WritableComparable接口。
以下是Point3D类的关键实现步骤:
- 声明与构造器:类包含三个浮点型坐标
x,y,z,并提供带参数和无参数的构造器。 - 实现
write()方法:将x,y,z三个浮点数按顺序写入DataOutput输出流。@Override public void write(DataOutput out) throws IOException { out.writeFloat(x); out.writeFloat(y); out.writeFloat(z); } - 实现
readFields()方法:从DataInput输入流中按写入的相同顺序读取三个浮点数,并赋值给对象的字段。@Override public void readFields(DataInput in) throws IOException { x = in.readFloat(); y = in.readFloat(); z = in.readFloat(); } - 实现
compareTo()方法:为了比较两个Point3D对象,我们计算每个点到原点(0,0,0)的欧几里得距离,然后比较距离。距离公式为:
distance = sqrt(x² + y² + z²)
在compareTo方法中,我们比较当前对象和参数对象的这个距离值。 - 辅助方法:实现
toString()方法用于友好显示,重写equals()和hashCode()方法也是良好实践。

序列化对象到HDFS文件

现在我们已经有了可序列化的Point3D类,接下来看看如何将它的实例写入HDFS文件。
以下是创建文件并写入对象的核心步骤:
- 获取HDFS的
FileSystem实例。 - 使用
FileSystem.create()方法在HDFS上创建文件,并获得一个FSDataOutputStream(它是DataOutput的实现)。 - 创建几个
Point3D对象。 - 对每个对象调用其
write()方法,并传入FSDataOutputStream,将对象状态序列化到文件中。 - 关闭输出流。
运行此程序后,会在HDFS上生成一个二进制文件(例如point3Ds.data)。由于是二进制格式,用hadoop fs -cat命令查看会显示乱码,这证明了其存储的高效性。
从HDFS文件反序列化对象
最后,我们学习如何从刚才创建的HDFS文件中读取并重建Point3D对象。
以下是从文件读取对象的核心步骤:
- 获取HDFS的
FileSystem实例。 - 使用
FileSystem.open()方法打开HDFS上的文件,并获得一个FSDataInputStream(它是DataInput的实现)。 - 创建一个“空”的
Point3D对象(使用无参构造器)。 - 在循环中,对该对象调用
readFields()方法,并传入FSDataInputStream。每次调用都会从流中读取一组x, y, z值来填充该对象。 - 使用对象的
toString()方法打印出读取的内容。 - 当到达文件末尾时,关闭输入流。

运行此程序,它将从二进制文件中读取三个点坐标,并以(x, y, z)的清晰格式打印出来,成功完成反序列化过程。
总结
本节课中我们一起学习了Hadoop的序列化与反序列化机制。我们了解到Hadoop通过Writable和WritableComparable接口提供了自己高效、紧凑的序列化方案,这与Java原生序列化不同。我们通过实现一个Point3D类,完整实践了如何使一个对象可序列化、可比较,并最终将其序列化到HDFS文件以及从文件中反序列化读取的全过程。这是理解Hadoop数据内部表示和传输的基础。
038:课程 16: 基于文件的数据结构 📁


在本节课中,我们将学习Hadoop HDFS支持的两种高级文件数据结构:序列文件(Sequence Files)和映射文件(Map Files)。我们将探讨它们如何解决HDFS中的小文件问题。
基于文件的数据结构简介
在深入了解具体的文件数据结构之前,我们先来看几个实际场景。
假设我们有一个全球访问的热门网站,服务分布在全球各地。这意味着我们有多个边缘站点和服务器。例如,我们有30个边缘站点和20台Web服务器。这里的“边缘站点”指的是位于特定位置的数据中心,比如纽约、伦敦、东京等。每个边缘站点大约有20台Web服务器。每小时整点,访问日志文件会进行轮转,这意味着每台Web服务器每小时都会生成一个全新的文件。
作为公司,我们需要存储和处理这些Web服务器访问日志文件,以分析热门页面、独立访客数量及身份等信息。为此,我们需要将数据导入Hadoop并存储。从架构角度看,我们面临几个问题:如何存储这些每小时生成的文件?是每个边缘站点、每台服务器的每个文件单独存储,还是合并存储?是否要压缩这些文件?
另一个场景是,假设我们有一个用户分享照片的应用,每天接近一百万张照片。公司需要存储和处理这些照片。我们可能需要处理元数据(如GIF和JPEG文件中包含的地理位置、拍摄时间、相机型号、光照等信息),或进行图像识别(如人脸识别、品牌识别)。同样,从架构角度看,我们面临问题:如何存储数据?每张照片单独存储还是合并到一个文件中?如何应用压缩?是压缩每张照片还是整个文件?
实际上,在大数据世界中,小文件是一个问题。首先,我们定义什么是小文件:小文件是指小于Hadoop块大小的文件。默认块大小为64MB(可配置为128MB、256MB或512MB)。HDFS中小文件的问题在于,NameNode为每个文件、块或目录的元数据开销约为150字节。这意味着,如果有100万个文件,就需要约150MB内存;1000万个文件需要约1.5GB内存;1亿个文件则需要约15GB内存。
在第一个场景中,我们有30个站点、20台服务器、每天24小时,每天生成约14,400个文件,每年约550万个文件,需要约1.5GB内存存储一年的数据。在第二个照片分享场景中,问题更大:每天约100万张照片,一年365天就是3.65亿个文件,需要接近128GB内存来存储所有数据。
即使在MapReduce中,小文件也是个问题(尽管我们尚未讨论MapReduce)。在MapReduce中,每个文件通常由一个单独的容器(JVM)处理。如果每个文件只有8-10MB,单独启动JVM来处理是不划算的。
Hadoop提供了几种基于文件的数据结构来解决这个问题,主要是序列文件和映射文件。
序列文件(Sequence Files)🔢
序列文件是一种由键值对组成的平面文件,每个键值对是一条记录。它是一个记录容器,其中每条记录都是一个键值对组合。键和值都是二进制的,因此它们必须是可序列化的。我们可以使用Java序列化机制或Hadoop序列化机制,但后者更优,因为它便于后续的MapReduce处理。键和值的长度可以不同。序列文件还包含称为同步点(sync points)的标记,每隔几条记录就会出现,用于指示记录的起始位置。
序列文件的一般结构包括头部、记录和同步标记。头部包含版本号(以“SQ”开头)、键类名、值类名、是否压缩、是否块压缩以及同步标记字符等信息。记录包含记录长度、键长度、键和值。同步标记大约占文件的1%,每隔几百字节放置一个。


序列文件有三种类型:未压缩、记录压缩和块压缩。
- 未压缩序列文件:键和值都不压缩,每隔几百字节放置一个同步标记。记录结构为:记录长度 -> 键长度 -> 键 -> 值。
- 记录压缩序列文件:只有值被压缩。记录结构为:记录长度 -> 键长度 -> 键 -> 压缩后的值。
- 块压缩序列文件:键和值被收集到块中,然后对整个块进行压缩。块大小是可配置的。每个块开始前都有同步标记。文件结构为:头部 -> 同步标记 -> 块 -> 同步标记 -> 块...。一个块包含以下字段:块内记录数、压缩后的键长度、压缩后的所有键、压缩后的值长度、压缩后的所有值。
现在,让我们看看如何将序列文件应用于之前的场景。
对于场景一(Web访问日志),我们可以将每小时生成的整个访问日志文件作为一条记录的值存入序列文件。键可以是边缘服务器ID(如“London-server12-20140812-13”),其中包含位置、服务器ID和时间戳(年-月-日-时)。这样,整个文件以压缩形式存储。
对于场景二(图像存储),我们可以将用户ID作为键,图像作为值存入序列文件,图像可以选择压缩或未压缩存储。
序列文件的优势包括:
- 存储效率高,可以存储二进制数据(如图像、音频、视频、PDF等)。
- 对NameNode的开销较小,尤其适合小文件。
- 在MapReduce处理中效率高,支持在块级别进行文件分割。
序列文件的用途包括:
- 作为其他文件的容器。
- 作为存储键值对组合的数据存储机制,为MapReduce处理奠定基础。
- 被MapReduce框架本身使用。
创建和读取序列文件的步骤如下:
- 创建:使用
SequenceFile.Writer类(SequenceFile的内部类)的实例。创建时需要指定序列文件格式、压缩类型和编解码器。然后调用append方法添加键值对。 - 读取:使用
SequenceFile.Reader类(同样是内部类)的实例。通过调用next方法并传入键和值对象进行迭代,读取器会填充这些对象。
映射文件(Map Files)🗺️


现在我们已经了解了序列文件,接下来看看映射文件。
映射文件实际上是一种排序的序列文件。它是一个带有索引的序列文件,因此允许通过键进行查找。你可以将其视为Java util.Map 数据结构的持久化版本。Java Map受限于内存大小,而映射文件可以存储在平面文件中,大小可以超过内存。
映射文件中的键必须是 WritableComparable 类型(即可写入、可比较)。这是因为它们需要被序列化到文件流中,并且为了排序目的必须可比较。值必须是 Writable(可写入)类型,如果也是 WritableComparable 则更好。
映射文件的用途包括:
- MapReduce框架内部使用映射文件。
- 应用程序可以将其用于快速查找和连接操作。
- 可以作为小文件的容器,以文件名作为键(如场景一中的边缘ID、服务器ID和时间戳组合)。
映射文件的结构需要注意:映射文件实际上是一个目录。当你创建映射文件时,会生成一个目录,其中包含两个文件:
data文件:这是一个完整的序列文件,包含所有键及其关联值。index文件:这是一个较小的序列文件,也包含键值对。键是实际的键,而值是data文件中的字节偏移量,起到了索引的作用。为了效率,它通常只存储一部分键的索引(可配置)。
创建和查找映射文件的步骤如下:
- 创建:创建
MapFile.Writer对象的实例,然后调用append方法添加有序的键值对。 - 查找:创建
MapFile.Reader对象(MapFile类的内部类)的实例,然后调用get方法,传入键和一个值对象,get方法会填充该值对象。
总结 📝


在本节课中,我们一起学习了Hadoop HDFS支持的两种基于文件的数据结构:序列文件和映射文件。我们探讨了它们如何通过将多个小文件合并到更大的、可索引的容器中,有效解决HDFS中的小文件问题,从而减少NameNode的内存开销并提升MapReduce的处理效率。
039:HDFS高级编程总结 📚

在本节课中,我们将总结HDFS高级编程模块的核心内容。我们将回顾目录操作API、文件压缩与解压缩、Hadoop序列化机制以及基于文件的数据结构。这些知识对于高效处理HDFS上的数据至关重要。
目录操作API 📁
上一节我们介绍了HDFS的基本文件操作,本节中我们来看看目录相关的API。
FileSystem类提供了多个方法用于目录管理。
以下是关键方法及其功能:
mkdirs:用于创建目录。delete:用于删除目录。该方法返回一个布尔值。如果成功删除目录,则返回true。如果因为目录不存在而无法删除,则返回false。listStatus:用于获取目录中所有文件的列表。globStatus:允许根据模式筛选文件,有选择地提取文件。
listStatus和globStatus方法都返回一个FileStatus对象数组。该对象提供了文件的详细信息,例如文件名、文件大小、所有者、权限和副本因子。
压缩与解压缩 💾
了解了目录操作后,我们转向数据存储优化技术:压缩与解压缩。
我们探讨了压缩的优缺点。
优点是节省存储空间。
缺点是每次解压缩(事实上压缩和解压缩都需要)都会消耗CPU资源。
我们学习了如何压缩和解压缩文件。压缩的基本思想是将常规流转换为专门的压缩流和解压缩流,以便对内容进行压缩和解压缩。
Hadoop序列化机制 🔄
接下来,我们研究了Hadoop序列化。
我们了解到Hadoop拥有自己的序列化机制,它不使用Java的序列化机制。为了支持此机制,Writable和WritableComparable是两个关键接口。WritableComparable接口继承自Writable接口。
Writable接口包含两个方法:write和readFields。
WritableComparable接口除了继承自Writable的两个方法外,还包含来自Comparable接口的第三个方法:compareTo。
基于文件的数据结构 📄
最后,我们看了一些基于文件的数据结构,例如SequenceFile和MapFile。

这些数据结构解决了Hadoop HDFS中的小文件问题。

本节课中我们一起学习了HDFS高级编程的几个核心方面:使用FileSystem API进行目录管理、通过压缩流优化存储、理解Hadoop特有的Writable序列化机制,以及利用SequenceFile和MapFile处理小文件问题。掌握这些技术将帮助你更高效地在HDFS上管理和处理大数据。
040:YARN与MapReduce架构导论 🚀
在本节课中,我们将学习YARN平台架构、其架构能力,以及MapReduce框架如何构建在YARN之上。最后,我们将探讨MapReduce编程范式。

概述 📋
本模块包含四个部分。首先,我们将介绍YARN架构,包括其各个组件及其交互方式。接着,我们将探讨YARN的架构能力,涵盖性能、可用性、可扩展性、可操作性和安全性等方面。然后,我们将学习MapReduce框架如何集成到YARN平台上。最后,我们将深入了解MapReduce编程模型。
模块目标 🎯
本模块的学习目标如下:
- 描述YARN平台的架构组件。
- 评估YARN的架构能力。
- 解释MapReduce框架如何构建在YARN之上。
- 阐述MapReduce编程范式。
第一部分:YARN架构 🏗️
上一节我们概述了本课程内容,本节中我们来看看YARN平台的具体架构。
YARN(Yet Another Resource Negotiator)是Hadoop 2.0引入的核心资源管理系统。它的主要功能是将资源管理与作业调度/监控分离。
以下是YARN架构的核心组件:
- ResourceManager (RM):集群资源的最终仲裁者。它负责调度和分配系统资源给各个应用程序。
- NodeManager (NM):每个节点上的代理,负责管理单个计算节点。它监控容器资源使用情况(CPU、内存、磁盘、网络)并向ResourceManager报告。
- ApplicationMaster (AM):每个应用程序特有的框架组件。它负责向ResourceManager协商资源,并与NodeManager协作以执行和监控应用程序的任务。
- Container:资源的抽象封装,包括节点上的CPU核心、内存等。任务在容器中运行。
第二部分:YARN架构能力 ⚙️
了解了YARN的基本组件后,本节我们来评估其关键的架构能力。
YARN的设计旨在提供企业级的数据处理平台,其核心能力包括:
- 性能:通过细粒度的资源管理和调度,提高集群资源利用率。
- 可用性:ResourceManager的高可用性配置可以防止单点故障。
- 可扩展性:支持数千个节点和数万个容器的超大规模集群。
- 可操作性:提供了丰富的监控和管理接口,便于集群运维。
- 安全性:通过Kerberos认证、访问控制列表(ACLs)等机制保障集群安全。
第三部分:MapReduce在YARN上的集成 🔗

前面我们介绍了YARN作为通用资源管理平台的能力,本节中我们来看看经典的MapReduce框架如何“构建”在YARN之上。
在YARN架构下,MapReduce作为一个应用程序框架运行。其集成方式如下:
- 用户提交一个MapReduce作业。
- ResourceManager为该作业启动一个特定的ApplicationMaster(即MRAppMaster)。
- MRAppMaster向ResourceManager申请运行Map和Reduce任务所需的容器资源。
- 获得资源后,MRAppMaster与对应的NodeManager通信,在容器中启动MapTask或ReduceTask。
- MRAppMaster监控所有任务的执行状态,并在任务失败时重新申请资源运行。

这个过程可以用一个简单的代码逻辑表示:
// 伪代码示意
MRAppMaster am = new MRAppMaster(job);
am.negotiateResourcesWithRM(); // 向RM申请资源
am.launchTasksOnContainers(); // 在NM上启动任务
am.monitorAndManageTasks(); // 监控和管理任务
第四部分:MapReduce编程模型 💻
在了解了MapReduce如何运行在YARN上之后,最后我们来学习其核心的编程模型。
MapReduce是一种用于处理海量数据集的编程范式。它将计算过程分为两个主要阶段:Map(映射)和Reduce(归约)。
以下是MapReduce模型的关键概念:
- 输入与输出:数据以键值对
<key, value>的形式被处理和传递。 - Map阶段:每个输入记录被一个Map函数处理,生成一组中间键值对。
- 公式表示:
Map (k1, v1) -> list(k2, v2)
- 公式表示:
- Shuffle与Sort阶段:框架将Map输出的中间结果按键进行排序和分组,并发送给对应的Reducer。
- Reduce阶段:每个Reduce函数接收一个键及其对应的所有值列表,进行处理后产生最终的输出。
- 公式表示:
Reduce (k2, list(v2)) -> list(k3, v3)
- 公式表示:

总结 📝
本节课中我们一起学习了YARN与MapReduce架构。我们首先剖析了YARN的组件化架构,包括ResourceManager、NodeManager、ApplicationMaster和Container。接着,我们评估了YARN在性能、可用性等方面的架构能力。然后,我们解释了MapReduce框架如何作为YARN上的一个应用程序运行,理解了从作业提交到任务执行的完整流程。最后,我们深入探讨了MapReduce编程模型的核心思想,即通过Map和Reduce两个阶段对大规模数据进行并行处理。掌握这些基础知识是理解现代大数据处理生态系统运作原理的关键。
041:YARN 架构解析 🏗️


在本节课中,我们将学习 Hadoop 2 的核心组件——YARN 的架构。我们将了解构成 YARN 的各个组件、它们的功能职责以及它们之间的交互过程。


从 Hadoop 1 到 Hadoop 2 的演进
上一节我们回顾了 Hadoop 的整体架构。现在,我们来看看 YARN 是如何出现的。
在 Hadoop 1.X 的架构中,HDFS 之上直接是 MapReduce 框架,用于分布式数据处理。这个框架将平台(资源管理)和应用程序(MapReduce 任务)紧密耦合在一起。
然而,Hadoop 2.X 引入了一个新的中间层——YARN 层。这个新层将资源管理与应用程序框架分离开来,使得 Hadoop 能够支持除 MapReduce 之外的其他计算框架。


Hadoop 1 经典架构回顾


在 Hadoop 1 中,我们采用的是主从架构。主节点上运行着 JobTracker 守护进程,而从节点上运行着 TaskTracker 守护进程。
以下是其工作流程:
- JobTracker:接收客户端提交的作业,将作业分解成多个任务,并根据数据位置将任务分配给各个 TaskTracker。
- TaskTracker:与 JobTracker 通信,接收任务,并启动独立的 JVM 进程(如 Map 任务或 Reduce 任务)来执行这些任务。

在这种架构中,JobTracker 同时负责资源调度和作业/任务管理,这导致了可扩展性和灵活性的限制。
Hadoop 2 与 YARN 架构
Hadoop 2 用一组新的守护进程取代了旧的 JobTracker 和 TaskTracker,它们分别是 ResourceManager 和 NodeManager。


关键的变化在于职责的分离:
- ResourceManager:作为主节点,只负责集群资源的全局调度。
- ApplicationMaster:这是一个为每个提交的应用程序(如一个 MapReduce 作业)专门创建的组件,负责该应用程序的任务管理和容错。
- NodeManager:作为从节点,负责管理单个节点上的资源,并执行来自 ResourceManager 和 ApplicationMaster 的指令。
这种分离使得 YARN 成为一个通用的资源管理平台,可以运行多种计算框架。
YARN 核心概念详解
在深入架构细节之前,我们需要明确几个核心概念。
资源与容器
资源 指的是物理计算资源,在 YARN 中主要指 CPU 和内存。例如,一台服务器可能拥有 4 个 CPU 核心和 8GB 内存。
容器 是 YARN 中的基本资源分配单位。它是一个封装了特定数量 CPU 和内存资源的抽象。例如,一个容器可以被定义为拥有 1 个 CPU 核心和 2GB 内存。
一台物理服务器可以被划分为多个容器。例如,上述服务器可以划分为 3 个容器(各 1 核心,2GB 内存),剩余的资源则留给操作系统和其他守护进程使用。
YARN 架构组件
现在,让我们详细看看 YARN 架构中的每个组件。
ResourceManager
ResourceManager 是 YARN 的主守护进程,整个集群中只有一个实例。


它的主要职责是:
- 调度:它是集群资源的最终仲裁者,负责将容器资源分配给各个 ApplicationMaster。
- 资源管理:它监控集群资源使用情况,并可以根据需要从 ApplicationMaster 回收容器资源,以便分配给其他应用程序。
NodeManager
NodeManager 是运行在每个从节点上的守护进程,是 ResourceManager 的从属。
它的主要职责是:
- 节点代理:启动时向 ResourceManager 注册,并定期发送心跳报告节点状态。
- 容器生命周期管理:根据 ResourceManager 或 ApplicationMaster 的指令,启动或停止容器。
- 资源监控:监控容器对资源(CPU、内存)的使用情况。
ApplicationMaster


ApplicationMaster 是每个应用程序的“管家”。每当客户端提交一个作业(如一个 MapReduce 作业),就会为该作业启动一个专属的 ApplicationMaster。
它的主要职责是:
- 作业协商:向 ResourceManager 申请运行任务所需的容器资源。
- 任务管理:与 NodeManager 通信,在获得的容器中启动具体的计算任务(如 Map 任务)。
- 容错:监控任务的执行状态,并在任务失败时重新申请资源以重试。
ApplicationMaster 本身也是一个运行在容器中的特殊进程,通常被称为 Container 0。
容器
容器是 NodeManager 根据指令启动的一个独立进程环境,是实际执行用户代码(如 Map 或 Reduce 函数)的地方。ApplicationMaster 和具体的计算任务都运行在容器中。
YARN 应用执行流程
理解了各个组件后,我们来看看它们是如何协作完成一个作业的。以下是简化的执行步骤:

- 提交应用:客户端向 ResourceManager 提交应用程序。
- 启动 AM:ResourceManager 找到一个 NodeManager,并指令它为该应用启动一个 ApplicationMaster 容器。
- 注册与协商:ApplicationMaster 启动后,向 ResourceManager 注册,并为其任务申请所需数量和规格的容器资源。
- 分配容器:ResourceManager 根据调度策略,将可用的容器资源分配给 ApplicationMaster。
- 启动任务:ApplicationMaster 与持有这些容器的 NodeManager 通信,指令它们启动容器来执行实际的计算任务。
- 执行与监控:任务在容器中执行。ApplicationMaster 监控任务进度和状态。
- 完成与清理:所有任务完成后,ApplicationMaster 向 ResourceManager 注销,然后自身容器终止。所有任务容器也被清理。
YARN 服务管理与应用提交

启动与停止守护进程
在命令行中,可以管理 YARN 服务:
- 启动所有 YARN 守护进程:
start-yarn.sh - 停止所有 YARN 守护进程:
stop-yarn.sh - 启动/停止特定守护进程:
yarn-daemon.sh start/stop [resourcemanager|nodemanager]
提交 YARN 应用


使用 yarn 命令提交应用程序,基本格式如下:
yarn jar <jar文件路径> <应用程序主类> [参数...]
例如,运行 Hadoop 自带的 MapReduce 词频统计示例:
yarn jar /usr/local/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar wordcount /input /output

Web 管理界面

YARN 提供了 Web 界面用于监控:
- ResourceManager Web UI:默认运行在
http://<rm-host>:8088。可以查看所有应用程序的状态、集群资源使用情况等。 - NodeManager Web UI:默认运行在
http://<nm-host>:8042。可以查看单个节点上容器的运行状态。
总结


在本节课中,我们一起学习了 YARN 的架构。我们首先回顾了从 Hadoop 1 到 Hadoop 2 的演进过程,理解了引入 YARN 的必要性。接着,我们深入剖析了 YARN 的核心组件:负责全局调度的 ResourceManager、管理单个节点的 NodeManager、管理具体应用的 ApplicationMaster 以及执行实际任务的容器。我们还详细跟踪了一个 YARN 应用程序从提交到完成的完整执行流程。最后,我们学习了如何启动/停止 YARN 服务、如何通过命令行提交作业,以及如何通过 Web 界面监控集群状态。YARN 作为 Hadoop 2 的资源管理核心,其灵活的架构为大数据生态支持多种计算框架奠定了坚实的基础。
042:课程 42: YARN 核心架构能力 🏗️

在本节课中,我们将从多个架构能力的角度,深入探讨 YARN 的核心组件——资源管理器(Resource Manager)和节点管理器(Node Manager)。我们将分析其性能、可扩展性、可用性、可安装性、可配置性、可操作性、易用性、可编程性以及安全性。
性能 ⚡
上一节我们介绍了 YARN 的组件,本节中我们来看看它们的性能表现。
首先,资源管理器是一个被重度使用的组件。它维护着集群中所有资源的完整映射,并跟踪它们的使用情况。同时,它需要持续接收来自节点管理器和应用主进程(Application Master)的心跳信号。这意味着资源管理器在内存、CPU 和网络方面都可能面临压力。
你可以通过以下方式提升资源管理器的性能:
- 升级硬件:使用拥有更多内存和 CPU 的服务器。
- 增加线程数:通过配置文件调整线程数量。
相比之下,节点管理器是一个轻量级进程。它主要负责管理节点本身,并不执行实际的计算任务,所有实际工作都由容器(Container)完成。因此,节点管理器的性能通常不是瓶颈。如果需要,同样可以通过升级硬件和增加线程数来提升其性能。
可扩展性 📈
接下来,我们探讨 YARN 的可扩展性。
对于资源管理器,垂直扩展(Scale Up)不是问题,你可以使用更强大的硬件。然而,水平扩展(Scale Out)则无法实现,因为每个集群只能运行一个资源管理器。但这通常不是大问题,因为在 YARN 架构中,资源管理器的工作负载相比 Hadoop 1.0 的 JobTracker 已大幅减轻。根据雅虎的数据,一个资源管理器足以管理一个包含 40,000 个节点的集群。
对于节点管理器,你可以轻松地进行垂直和水平扩展:
- 垂直扩展:为节点管理器所在服务器升级硬件。
- 水平扩展:向集群中添加更多节点。你可以从 20 个节点开始,逐步扩展到 40、100 甚至 200 个节点,从而实现节点管理器的水平扩展。


可用性 🛡️
现在,我们来了解 YARN 组件的可用性。
资源管理器是 YARN 中非常关键的一个进程。如果它崩溃,将无法提交新作业,现有作业也会停滞。应用主进程会尝试重新连接资源管理器,如果重试次数(可配置,默认约10次)耗尽仍无法连接,应用将会失败。
为了提高资源管理器的可用性,可以将其设置为高可用(HA)模式。这需要借助 ZooKeeper 来维护资源管理器的状态并进行主节点选举。你可以启动多个资源管理器实例(采用主备模式),当活动的资源管理器崩溃时,ZooKeeper 会选举出一个新的主节点来接管工作。
对于节点管理器,其可用性设计是:如果一个节点管理器崩溃,资源管理器会检测到并无法与其通信。随后,资源管理器会将该节点管理器上运行的所有容器标记为失败,并在其他健康的节点上重新启动这些容器对应的应用主进程和任务容器。
可安装性 🛠️
以下是关于 YARN 安装方式的说明:
对于小型集群,手动安装是可以接受的。但对于拥有数百台机器的大规模集群,手动安装则不可行。你可以使用以下工具或方法:
- Ambar i安装工具:一个开源的管理工具。
- 自定义脚本:组织可以使用 Puppet、Chef 或 Ansible 等工具编写自动化安装脚本。
- 云服务:例如 Amazon Elastic MapReduce,这是一种基于虚拟机的服务。你只需在控制台指定所需节点数量和配置,服务便会自动为你启动所有虚拟机。但请注意,一些 Hadoop 专家不推荐基于虚拟机的安装方式。
可配置性 ⚙️
YARN 具有高度的可配置性,它采用基于 XML 的配置系统。

YARN 的配置主要通过两个文件管理:
yarn-default.xml:包含 YARN 的所有默认属性,位于 Hadoop JAR 文件中。yarn-site.xml:用于覆盖默认属性,通常放置在$HADOOP_PREFIX/etc/hadoop/目录下。

在配置文件中,资源管理器的相关属性以 yarn.resourcemanager 开头,节点管理器的相关属性以 yarn.nodemanager 开头。你可以配置内存、服务端口、应用管理器、调度器、故障转移以及我们之前提到的线程数等众多参数。
根据 Apache Hadoop 官方文档,资源管理器有近 82 个可配置属性,节点管理器有约 66 个可配置属性,这充分体现了 YARN 的高度可配置性。

可操作性 🖥️
从运维操作的角度来看,YARN 提供了丰富的工具。

资源管理器提供了一个运行在 8088 端口的管理控制台。例如,如果资源管理器运行在本地,你可以通过访问 http://localhost:8088 来查看。此外,还有一个命令行工具 yarn rmadmin,系统管理员经常使用它。
节点管理器同样提供了一个管理控制台,运行在 8042 端口,并配有 yarn nodemanager 命令行工具。
同时,也有许多商业和开源运维产品可供选择,例如 Cloudera Manager、Hortonworks 的 Ambari(开源)等。


易用性与可编程性 💻
本节我们讨论 YARN 的易用性和可编程性。
易用性主要体现在作业提交上。使用 yarn 命令提交作业非常简单直接,基本格式为:
yarn jar <jar文件路径> [应用参数]
可编程性方面,主要涉及两类开发者:
- 框架开发者:他们编写能在 YARN 上运行的框架(如 MapReduce、Spark、Giraph)。Java 是开发这些框架的主要语言。
- 应用开发者:他们使用运行在 YARN 上的框架来编写具体应用。这类应用可以用多种语言编写,如 Shell、Scala、Java 等,具体取决于所使用的框架支持哪些语言。


安全性 🔒
最后,我们来看 YARN 的安全性。
YARN 的安全性措施包括:
- 双向认证:YARN 框架在用户向资源管理器提交作业时对用户进行身份验证。只有经过身份验证的用户才能提交、修改或终止作业。
- 用户身份继承:应用程序以提交作业的用户身份运行。这意味着,如果该用户无法访问 HDFS 上的某些文件或目录,那么为其作业运行的 YARN 进程也无法访问这些数据。
- 受保护的工作目录和日志:YARN 使用的工作目录和生成的日志文件都受到保护,通常只有作业提交者本人可以访问。
- 调度器防止滥用:为了防止单个用户提交的作业独占整个集群资源,YARN 提供了调度器(如公平调度器 Fair Scheduler 和容量调度器 Capacity Scheduler)。系统管理员可以通过配置调度策略,来设定资源分配规则,例如用户或队列能使用多少容器(Container)等。
本节课总结

在本节课中,我们一起深入学习了 YARN 的多种核心架构能力。我们从性能、可扩展性、可用性、可安装性、可配置性、可操作性、易用性、可编程性以及安全性等方面,全面剖析了资源管理器和节点管理器。总而言之,YARN 被设计为一个高性能、高可用、可扩展、高度可配置且安全的分布式资源管理框架。
043:YARN上的MapReduce框架 🧩

在本节课中,我们将学习MapReduce框架,并了解它如何集成到YARN平台上。
概述

MapReduce是一个用于在商用硬件上并行处理大量数据的编程模型。它源自函数式编程,其核心思想是将数据处理过程分解为Map(映射)和Reduce(归约)两个阶段。开发者只需编写这两个函数,框架便会自动处理分布式计算中的复杂性,如任务调度、容错和数据同步。
什么是MapReduce?
MapReduce是一个用于在多个节点上并行处理海量数据的精确模型。它源自函数式编程。从字面上看,“MapReduce”由两个词组成:Map和Reduce。在函数式编程语言中,Map和Reduce是两种特殊的函数,我们稍后会详细讨论。因此,MapReduce在某种程度上是函数式编程与分布式编程的结合。使用MapReduce框架,开发者编写Map函数和Reduce函数,正是这些函数被应用于输入数据。

MapReduce编程可以用多种语言实现。Java是首选语言,因为有原生的Java API可用。但你也可以使用称为MapReduce Pipes的工具在C++中进行MapReduce编程。此外,你还可以使用Hadoop Streaming或MapReduce Streaming在Ruby、Python、Perl等语言中进行MapReduce编程。
MapReduce技术基于谷歌的MapReduce论文。让我们快速浏览一下这篇论文。


这篇论文由Jeffrey Dean和Sanjay Ghemawat撰写。我强烈建议你访问这个URL,快速阅读并了解其大意。虽然我们将在本课程中深入探讨这个架构的细节,但先看看这篇论文是个好主意。你可以看到输入如何被分割成多个分片,以及运行的不同工作器任务(即Map任务)。在Shuffle和Sort发生的屏障之后,是运行的Reduce任务,最终输出结果。我们在Hadoop中的整个架构,即Hadoop MapReduce框架,就是基于这篇论文构建的。

MapReduce框架与YARN的关系
别忘了,MapReduce框架运行在YARN之上。在Hadoop 2中,Common模块位于底层,HDFS构建在Common之上,YARN又构建在HDFS之上。YARN实际上是一个分布式计算平台,上面可以运行多个框架。MapReduce只是众多框架中的一个,它是一个非常流行的、面向批处理的框架,但也有其他团队在研究其他框架。
MapReduce内置于Hadoop中,并随Hadoop一起分发。MapReduce的配置是可调的。如果你还记得,Hadoop是高度可配置的,MapReduce作为一个框架也是可配置的。MapReduce的XML配置文件是mapred-default.xml和mapred-site.xml。mapred-default.xml包含了所有MapReduce属性的默认值,它是Hadoop jar文件的一部分,默认加载。mapred-site.xml是你覆盖所有想要修改的属性的文件,它被放置在Hadoop的安装目录/etc/hadoop下。让我们看看mapred-default.xml配置,了解其中有哪些不同的属性。

我正在查看Hadoop文档。如果你向左滚动,可以看到mapred-default.xml。它列出了所有属性及其默认值和描述。如你所见,这里有很多属性,你可以根据需要覆盖它们。
在我们的安装中,我们也有mapred-site.xml文件。在我们的安装配置中,我们设置了一个名为mapreduce.framework.name的属性,并将其值设为yarn。这就是在告诉YARN,我们将MapReduce框架集成在其之上。换句话说,我们是在告诉MapReduce框架,它将在YARN上运行。
这个属性可以取三个不同的值之一:local、classic或yarn。当设置为local时,MapReduce作业将在单台机器的单个JVM中运行,没有NodeManager,没有ResourceManager,也没有NameNode,所有东西都在客户端JVM内运行。这用于调试和测试目的。当设置为classic模式时,你是在Hadoop 1模式下运行。当设置为yarn时,你实际上是将它集成在YARN框架之上,这正是我们正在做的。
这是我们设置的唯一属性,但我们可以在mapred-site.xml中设置其他属性,这些我们将在后续更详细的MapReduce编程中讨论。
MapReduce处理流程
现在,让我们看看MapReduce的处理过程。框架将输入分解为分片,我们也称之为输入分片。框架还将处理过程分解为多个阶段,即Map阶段和Reduce阶段。
程序员编写Map函数和Reduce函数,并将其打包成Jar文件。然后,程序员将其作为作业的一部分提交。框架在Map阶段使用Map函数,在Reduce阶段使用Reduce函数。框架会对它接收到的每一个输入应用Map函数,并对Map函数的所有输出应用Reduce函数。
每个阶段,无论是Map阶段还是Reduce阶段,都使用键值对作为输入和输出。Map阶段接收输入文件。如你所知,输入文件实际上被分解为键值对组合。我们稍后会看到它是如何将输入分解为键和值的,因为输入可能一次只是一行。Map的输出是一个键值对组合,这个输出被发送到Reduce阶段,然后Reduce阶段处理它并发送最终输出。
Map的输出在被发送到Reduce阶段之前,实际上会由框架进行处理。因此,输入进入Map阶段,Map阶段处理输入并产生输出。Map阶段的输出由框架处理,然后产生一个新的输出,这个新输出成为Reduce阶段的输入。我想在这里强调的是,MapReduce框架正在对Map的输出进行一些处理。
如果你查看yarn-site.xml,其中有一个属性叫yarn.nodemanager.aux-services。这是我们在yarn-site.xml中实际设置的一个属性,我们将其值设置为mapreduce_shuffle。mapreduce_shuffle是框架在Map输出上所做的特殊工作。这是集成在YARN框架上的一个附加功能,以便它能进行洗牌操作。大多数框架没有辅助服务,但YARN中引入了辅助服务以支持MapReduce框架。我们稍后将讨论Shuffle和Sort框架,到那时会更容易理解。现在只需记住,为了将MapReduce集成到YARN之上,我们必须将yarn-site.xml中的yarn.nodemanager.aux-services属性设置为mapreduce_shuffle。
MapReduce的内置优势
MapReduce框架内置了许多应用特性。首先是并行性:框架将输入分解为分片,并自动将作业分解为任务。其次是自动故障处理:当任务因节点崩溃或磁盘损坏而失败时,框架会自动处理,将任务转移到其他节点。它实际上是在YARN平台的帮助下完成这一点的。如果你注意到,我们在YARN平台中看到过称为容器的概念,这些容器会自动重新启动,这正是这里发生的情况。它还简化了分布式处理:如果你必须进行分布式处理,并且必须管理所有这些数据并同步它们,你需要执行许多复杂的机制才能以正确的方式处理数据。所有这些复杂的锁定机制不再需要由开发者管理。开发者只需专注于编程逻辑,实际上是专注于组织的业务逻辑。
MapReduce非常适合并行处理大量数据,但别忘了它是一个面向批处理的框架,非常适合批处理系统。它擅长处理大量数据和面向批处理的场景。它之所以适合批处理,是因为顺序读取非常高效,而不是在文件的不同部分之间跳转、寻址和前后移动,那样成本非常高。磁头移动的成本很高。我们在HDFS架构中讨论过这一点,在介绍现代分布式计算架构时也提到过。
MapReduce的局限性
然而,MapReduce不太适合在线数据访问,因为它是一个批处理框架,不适合需要几毫秒甚至几秒钟内得到快速答案的场景。如果你需要快速的随机访问,有称为HBase的工具,我们稍后会讨论。此外,如果你只想处理大型数据集中的一小部分,MapReduce也不适合。它最适合读取整个文件并进行处理,而不是处理小数据集。它也不适合实时或基于流的处理。假设有数据不断流入你的系统,你试图实时处理这些数据并获得实时分析,你无法用MapReduce做到这一点。现在正在开发许多更新的框架,如Storm,用于实时处理,但这些框架并非构建在Hadoop 2之上。不过,这些框架正在被改造以在Hadoop上运行,特别是在YARN上运行,因为YARN允许你运行任何框架。
MapReduce术语
让我们看看MapReduce的术语。在MapReduce中,我们使用术语客户端。客户端是一个程序,一个独立的程序,它提交一个要处理的作业。这就引出了一个问题:什么是作业?作业是一个完整的程序。所谓完整程序,是指一个包含主方法的类,以及所有支持类,包括Mapper类和Reducer类,以及处理数据所需的其他库类,所有这些都打包在一个Jar文件中。然后你提交这个Jar文件作为作业。作业还有一个与之关联的作业ID。
当作业运行时,作业被分解为任务,每个任务在一个单独的进程中运行。每个任务要么是Map任务,要么是Reduce任务。因此,任务实际上是Mapper或Reducer的一次执行,并且它实际上是在处理一片数据。还有一个术语叫TIP,即“进行中的任务”。还有任务尝试这个术语。任务尝试是尝试执行某个任务的特定实例。为什么说是“特定尝试”?因为一个任务可能被执行多次。任务可能被执行多次,因为第一次执行时失败了,或者因为MapReduce发现某个特定任务非常慢,所以在不同的机器上启动相同的任务。这就是我们所说的推测执行。我们将在接下来的三到四个模块中学习Hadoop MapReduce时讨论所有这些内容。




例如,客户端是一个打包为Jar文件的Java程序。一个作业就像跨15个文件运行词频统计。假设我们下载了15本小说,我们想找出所有不同书籍中独特的单词,并统计这些不同单词的数量。一个任务基本上就是一个MapReduce任务。这15个输入文件至少会转换为15个Map任务,每个文件成为一个Map任务。如果文件足够大,文件可能会被分解为多个块,每个块将作为单独的任务运行。我们现在先简化一下:15个文件,15个任务。然后,在Reduce方面,会有一定数量的Reduce任务运行。默认情况下,只有一个Reducer,但我们可以自定义Reducer的数量,这将在我们学习MapReduce编程和高级MapReduce编程时讨论。


每个任务至少被尝试执行一次。如果任务崩溃,它可能会被尝试执行多次。任务崩溃的原因有很多:可能因为收到了错误的输入。你甚至可以配置MapReduce框架,比如说:“我有10亿条记录,即使有1%的记录出错也没关系。”你可以配置为,如果有一部分或一定比例的记录是坏的,就忽略它。我想在这里强调的是,一个任务可以被尝试执行多次。
YARN上的MapReduce架构

现在,让我们看看YARN上的MapReduce架构。如果你看这张图,它看起来和你之前看到的非常相似,这是有意为之的。它看起来像YARN架构,原因是我要将MapReduce架构集成到YARN架构之上。

如果你还记得,在YARN架构中,可以有一个客户端,客户端向ResourceManager提交一个应用程序。ResourceManager随后启动一个Application Master作为进程或容器来执行客户端提交的作业。在MapReduce的情况下,Application Master实际上就是一个MR作业。想一想,Application Master实际上与ResourceManager通信,作为通信的一部分,它应该协商资源。换句话说,它将请求它需要的容器,以便执行任务。
在MapReduce框架中,数据和处理应该放在一起。这就是Application Master(即MR作业主节点)将与NameNode通信的地方,获取该特定作业数据放置的所有主机、所有机器的列表,然后它将去主节点请求仅在这些数据所在的容器上运行。这就是MapReduce中数据和业务处理共置的理念。这不是YARN的特性,更多的是MapReduce的特性。我想强调的是,从节点可能不仅运行NodeManager和各种容器,它们还可能运行DataNode守护进程。回想一下,DataNode守护进程是管理节点内数据的。因此,这些从节点不仅可以作为计算节点,也可以是数据节点。在这种情况下,任务实际上与本地DataNode通信,并进行共置处理。
现在,MapReduce作业已经获得了所有容器。然后,MapReduce作业将启动各种任务。在这个例子中,它在三台不同的机器上启动了三个任务。正如我所说,这些任务将在本地数据上工作。一旦它们完成处理,MapReduce框架现在将做一些内部工作。记住我说过,Map的输出由框架处理,然后框架将获取结果并启动一个Reduce任务。所以现在它启动了一个Reduce任务,在这个例子中,它在那个容器上启动了一个Reduce任务。一旦Reduce任务完成,Reduce任务就结束了,MR作业也结束了,最终整个作业完成。
YARN上的MapReduce作业流程
让我们看看YARN上的MapReduce作业流程。客户端在第一步提交作业。ResourceManager接收作业,确定Application Master需要在哪里运行。假设在这个例子中,它选择了这个特定的从节点作为Application Master。因此,它通过NodeManager启动一个Application Master,也就是MR作业。然后,Application Master将与各个NodeManager通信以启动各个容器。各个容器将处理Map和Reduce作业。一旦Map和Reduce作业完成,当然,它是通过咨询NameNode获得容器的。一旦工作完成,它就告诉ResourceManager它已经完成了工作,然后应用程序结束。
YARN上的MapReduce框架还引入了一个新的守护进程,称为作业历史服务器。这是一个全新的守护进程,不是YARN框架的一部分。它实际上是MapReduce框架的守护进程。作业历史服务器的理念是,它将跟踪所有正在进行的MapReduce作业,并为你提供有关MapReduce作业中正在发生什么的详细信息。如果你还记得,ResourceManager管理资源,它不提供应用程序级别的状态详情,只提供高级别的应用程序状态,而不是详细信息。应用程序级别状态的真正详细信息在MapReduce框架中是从作业历史服务器获取的。
这里的理念是,在YARN中,Application Master不仅与NameNode通信以获取应运行其计算任务的所有数据节点列表,还与这个称为作业历史服务器的新服务器通信。作业历史服务器是它提交所有与作业相关信息的地方。因此,有一个与作业历史服务器关联的HTTP Web控制台,你可以在其中获取有关作业的更多详细信息,所有不同的Map任务、所有不同的Reduce任务、所有日志文件、所有信息都可在作业历史服务器中找到。
启动和停止作业历史服务器
启动作业历史服务器的方法是执行mr-jobhistory-daemon.sh命令,并传递start historyserver作为参数。这将启动作业历史服务器。停止它的方法是mr-jobhistory-daemon.sh stop historyserver。请注意,当你启动作业历史服务器时,使用的术语是historyserver,而不是jobhistoryserver。
还有一个作业历史服务器控制台可供你使用。控制台的默认端口是19888。因为作业历史服务器在本地运行,你可以访问localhost:19888来查看作业的历史记录。现在,让我们登录到我们的虚拟机,启动作业历史服务器,启动一个MapReduce作业,并观察整个流程。

好的,我已经以hdadmin用户身份登录到我的Hadoop服务器虚拟机。让我执行jps命令。我的NameNode正在运行,Secondary NameNode正在运行,DataNode也在运行,这是我的HDFS架构。然后,我的NodeManager和ResourceManager也从我的YARN架构中运行。现在是我启动历史服务器的时候了。启动历史服务器的方法是:mr-jobhistory-daemon.sh start historyserver。这应该会为我启动历史服务器。
历史服务器应该已经启动了,让我再执行jps。历史服务器在那里,它正在运行。我们还知道历史服务器在端口19888上有一个管理控制台,所以让我们访问它。
我已经在这里标记了历史服务器。19888,这是历史服务器。这里是我到目前为止运行的所有作业。这些是所有不同的作业,它正在跟踪我运行过的所有作业。让我把它设置为显示100条。现在让我开始一个作业。


启动一个作业。好的,这是将要执行MapReduce的YARN作业。点击它。它在那里,作业正在运行。让我看看是否有作业ID。这里有一个作业ID:job_1305_0001。好的,让我们返回并刷新。它在那里:job_1305_0001,所以这是作业ID。
我可以点击作业,它会给我作业的详细信息。这是应用程序特定的,它会告诉我运行了多少个Mapper,多少个Reducer。点击Map,它会给你那个特定Map任务的详细信息。请注意,这与ResourceManager的管理控制台不同。让我转到ResourceManager的管理控制台。

这是ResourceManager的管理控制台。让我刷新一下。从ResourceManager的角度来看,有一个应用程序被启动,它被赋予了一个ID:application_..._0001。如果你想知道这个数字是什么,它是基于ResourceManager的启动时间的。这就是这里的数字。然后0001是序列号。下一个作业将是0002,然后是0003,依此类推。所以这是ResourceManager对客户端提交的特定作业(或应用程序)的看法。
但它随后启动了Application Master,也就是一个MR作业。这个MR作业与作业历史服务器通信以获取有关MR作业本身的信息,这是另一个不同的控制台,就是这里的这个。

它也有一个ID,而且它的ID非常相似。它是job而不是application,后面跟着ResourceManager启动时的一个数字(即时间戳),然后是与之关联的编号。好的,你现在明白这个意思了。

总结
在本节中,我们学习了MapReduce框架,并看到了MapReduce平台如何集成到YARN架构之上。我们探讨了MapReduce的基本概念、处理流程、内置优势、局限性以及关键术语。我们还了解了YARN上MapReduce的架构和作业流程,并介绍了作业历史服务器的作用和使用方法。通过实际启动历史服务器和提交作业,我们观察了从作业提交到完成的整个流程,以及如何通过不同控制台监控作业状态。
044:MapReduce编程范式 🧩

在本节课中,我们将学习MapReduce编程范式。我们将从函数式语言中“Map”和“Reduce”术语的起源讲起,并探讨函数式编程与分布式编程之间的相似之处。接着,我们将深入MapReduce编程模型,包括作为Map和Reduce阶段输入输出的键值对、框架的Shuffle和Sort过程,以及框架为完成一个作业而并行执行任务的一系列步骤。
函数式编程中的Map与Reduce


MapReduce编程范式借鉴了Lisp、Scheme等函数式语言中的Map和Reduce概念。在这些语言中,map和reduce函数接收一个集合作为输入,并对集合中的每个元素应用一个函数。这种范式非常适合解决某一类问题,并且易于在多个节点间进行分布式计算。当你为单机编写程序时,同样的编程范式可以应用到上百台机器上。MapReduce框架还在重试和容错方面提供了良好的语义支持,例如当一个节点崩溃导致任务失败时,相同的任务可以在另一台机器上重新执行。
Map函数示例
以下是一个Scheme语言中调用map函数的语句:
(map square ‘(1 2 3 4))
这段代码的含义是:map函数接收一个square函数和一个列表(1 2 3 4)作为参数。map函数将对列表中的每个元素应用square函数。因此,1的平方是1,2的平方是4,3的平方是9,4的平方是16。最终结果是得到一个新列表(1 4 9 16)。其核心思想是:获取输入的每个条目,应用作为参数传入的map函数,然后得到结果。
Reduce函数示例
以下是reduce函数的一个例子:
(reduce + 0 ‘(1 4 9 16))
这段代码的含义是:reduce函数接收一个+函数(加法运算符)、一个初始值0以及一个列表(1 4 9 16)作为参数。reduce函数的目标是将传入的列表值归约为一个单一的值。由于+是二元运算符,计算过程如下:0+1=1,1+4=5,5+9=14,14+16=30。最终结果是30。其核心思想是:通过应用一个归约函数,将一个大的列表归约为一个新值。
MapReduce编程模型
在MapReduce编程模型中,它强制要求输入和输出都采用键值对的形式。换句话说,Map函数和Reduce函数都接收键值对作为输入,并产生键值对作为输出。整个过程都围绕着以键值对为输入和输出展开。


实际上,情况要稍微复杂一些。Map函数接收一个键K1和值V1作为输入,并产生一个键K2和值V2的列表作为输出。而Reduce函数则接收一个键K2和一个值V2的列表作为输入,并产生一个键K3和值V3的列表作为输出。这里使用的K1、V1等符号实际上代表类型,它们可以是完全不同的类型。
通过示例理解:单词计数
为了更好理解,我们通过一个具体问题来演示:给定一个文本文件,计算文件中所有单词及其出现次数。
假设文本文件包含以下三行内容:
one fish two fish
red fish blue fish
black fish blue fish
我们的目标是输出每个单词及其出现次数,例如:one: 1, fish: 6。
第一步:框架读取输入
MapReduce框架从输入源(这里是一个文本文件)读取数据。由于文本文件本身不是键值对格式,框架需要将其解析为键值对记录。对于文本文件,它使用字节偏移量作为键,整行内容作为值。
- 记录1:键是
0(字节偏移量),值是"one fish two fish"。 - 记录2:键是
18,值是"red fish blue fish"。 - 记录3:键是
37,值是"black fish blue fish"。
第二步:Map阶段
框架会为每个键值对记录依次调用Map函数。Map函数处理一条记录后,可以产生零个、一个或多个输出记录。
在我们的单词计数例子中,Map函数的逻辑是:将一行文本拆分成单词,为每个单词输出一个中间键值对,其中键是单词本身,值是数字1(表示该单词出现了一次)。
以下是Map阶段处理三条记录后的中间输出:
- 处理记录1后输出:
(one,1),(fish,1),(two,1),(fish,1) - 处理记录2后输出:
(red,1),(fish,1),(blue,1),(fish,1) - 处理记录3后输出:
(black,1),(fish,1),(blue,1),(fish,1)
第三步:Shuffle与Sort阶段
框架会收集所有Map任务产生的中间输出,并进行分组和排序。
- 分组:将所有具有相同键的键值对聚集在一起。
- 排序:通常按键的字典序进行排序。
经过Shuffle和Sort后,数据被组织成如下形式,准备发送给Reduce函数:
(black, [1])(blue, [1, 1])(fish, [1, 1, 1, 1, 1, 1])(one, [1])(red, [1])(two, [1])
第四步:Reduce阶段
Reduce函数接收一个键和该键对应的值列表作为输入。在我们的例子中,Reduce函数的逻辑是:将这个值列表中的所有数字1相加,得到该单词的总出现次数,然后输出最终的键值对。

Reduce阶段处理后的最终输出为:
(black, 1)(blue, 2)(fish, 6)(one, 1)(red, 1)(two, 1)
这正是我们想要的单词计数结果。
从顺序执行到并行执行
以上描述的是一个顺序执行的模型:一个Map任务顺序处理所有输入记录。MapReduce的强大之处在于其内置的并行处理能力。
在并行执行模型中:
- 多个Map任务:输入数据被分割成多个部分(分片),每个部分由一个独立的Map任务处理。这些Map任务可以运行在不同的机器上,处理各自本地的数据。
- 分区:所有Map任务产生的中间输出会根据键被分区。框架确保所有相同键的键值对都会被发送到同一个Reduce任务。
- 多个Reduce任务:多个Reduce任务可以并行运行,每个处理分配给自己的那一部分键。
开发者只需编写Map和Reduce函数的业务逻辑,而无需关心数据分割、任务调度、节点间通信、故障恢复等复杂的并行计算细节,这些都由MapReduce框架自动处理。
总结 🎯
在本节课中,我们一起学习了MapReduce编程范式。


我们首先了解了Map和Reduce概念如何源于函数式编程。接着,我们深入探讨了MapReduce编程模型,认识到该框架在整个数据处理流程中——从Map阶段,经过Shuffle和Sort阶段,再到Reduce阶段——都强制使用键值对的概念。我们通过单词计数的例子,清晰地看到了数据如何从输入流经各个阶段最终产生输出。最后,我们了解到这个编程模型既可以顺序执行,也可以并行执行,而所有的并行化细节都由框架负责,开发者只需专注于核心的业务逻辑编写。
通过本节课的学习,你应该对MapReduce如何将一个大问题分解为可并行处理的Map和Reduce任务,并最终合并结果有了基本的理解。
045:YARN与MapReduce架构总结 🧠

在本节课中,我们将总结YARN平台和MapReduce框架的核心架构与关键组件。我们将回顾YARN的主要组成部分、其架构能力,以及MapReduce如何构建在YARN之上运行。
YARN平台关键组件 🏗️
上一节我们介绍了YARN的整体角色,本节中我们来看看其具体的核心组件。
YARN平台包含几个关键组件。
- 资源管理器:这是YARN架构的主节点,负责所有调度工作。
- 节点管理器:作为主资源管理器的从节点,负责向资源管理器提供节点健康信息。
- 应用管理器:这是提交给资源管理器的作业的“作业管理器”。
- 容器:容器是真正的“工作者”,它们在独立的JVM或独立进程中执行实际任务。
YARN的架构能力 💪
了解了组件之后,我们来看看YARN架构所具备的重要能力。
我们探讨了YARN的几项能力,包括安全性、可用性、可扩展性和可操作性。我们发现YARN具有高度的可扩展性和高可用性。
MapReduce框架 🗺️➡️📉
现在,让我们将目光转向构建在YARN之上的MapReduce框架。
MapReduce框架构建在YARN之上。该框架有两个关键组件。
- MapReduce框架应用管理器:负责与资源管理器协商获取各种容器,以便实现数据与处理的协同定位。
- 作业历史服务器:此组件已加入MapReduce框架,用于跟踪作业的所有应用程序状态信息,因为资源管理器并不真正关心应用程序状态。

MapReduce编程范式 ⌨️
最后,我们来回顾MapReduce的核心编程思想。
我们还研究了MapReduce编程范式。我们看到了一个作业如何被分解为Map阶段和Reduce阶段的任务。在Map和Reduce阶段中,所有这些不同的任务都以键值对作为每个阶段输入和输出的基础。
总结 📚
本节课中,我们一起学习了YARN架构的核心组件(资源管理器、节点管理器、应用管理器、容器)及其关键能力(如可扩展性与高可用性)。同时,我们回顾了MapReduce框架如何基于YARN运行,其核心组件包括MapReduce应用管理器和作业历史服务器,并再次明确了MapReduce以键值对处理和分阶段(Map/Reduce)执行任务的编程范式。
046:MapReduce编程基础导论 🚀
在本节课中,我们将学习MapReduce编程的基础知识,包括MapReduce API的概述、编写MapReduce程序的步骤,以及如何实现Mapper和Reducer的并行化。

MapReduce编程API概览 🧭
上一节我们介绍了课程的整体目标,本节中我们来看看MapReduce编程API的核心组成部分。
MapReduce API提供了一系列关键类,用于定义和运行数据处理任务。以下是主要类的简要说明:
- Job类:代表一个MapReduce作业。它用于配置作业参数、指定输入输出路径以及提交作业到集群。
- Mapper类:用户需要继承这个抽象类,并重写其
map方法来定义数据映射逻辑。其核心公式为:map(, )。 - Reducer类:用户需要继承这个抽象类,并重写其
reduce方法来定义数据归约逻辑。其核心公式为:reduce(, , )。 - InputFormat类:定义了如何读取输入数据(例如,从HDFS文件),并将其分割成供多个Mapper处理的逻辑输入分片。
- OutputFormat类:定义了如何将Reducer的输出写入存储系统(如HDFS)。
编写MapReduce程序的步骤 📝
了解了核心API后,本节我们将一步步学习如何编写一个完整的MapReduce程序。
编写一个MapReduce程序通常遵循以下六个主要步骤:
- 编写Mapper类:继承
Mapper类,实现map方法,在其中编写将输入键值对转换为中间键值对的业务逻辑。 - 编写Reducer类:继承
Reducer类,实现reduce方法,在其中编写对具有相同键的中间值集合进行归约处理的业务逻辑。 - 编写驱动程序(Driver):这是主程序,负责组装和配置整个MapReduce作业。
- 配置Job对象:在驱动程序中,创建一个
Job实例,并为其设置Mapper类、Reducer类、输入/输出格式、输入/输出路径等属性。 - 打包程序:将编写好的Java程序及其依赖打包成一个JAR文件。
- 提交并运行作业:使用Hadoop命令行工具将JAR包提交到集群上运行。
以下是一个驱动程序配置的代码示例框架:
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "My MapReduce Job");
job.setJarByClass(MyDriver.class);
job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
Mapper并行化 ⚙️
我们已经学会了如何编写程序,本节我们来探讨如何通过并行化来提升处理速度。首先看看Mapper的并行化。
Mapper的并行化是指一个MapReduce作业可以启动多个Mapper任务同时处理数据。其核心机制如下:
- 输入分片(Input Split):Hadoop根据作业的输入数据(如一个大文件)和配置的
InputFormat,自动将数据逻辑划分为多个分片。每个分片由一个独立的Mapper任务处理。 - 控制Mapper数量:Mapper的数量通常等于输入分片的数量。用户可以通过调整输入数据块的大小或直接设置最大/最小分片大小来间接影响Mapper的数量。
Reducer并行化 ⚙️
理解了Mapper如何并行工作后,本节我们来看看Reducer的并行化设置。
与Mapper不同,Reducer的数量默认是1个,但用户可以显式设置以实现并行处理。以下是关键点:
- 设置Reducer数量:在驱动程序中,通过
job.setNumReduceTasks(int n)方法来指定Reducer的任务数量。 - 分区(Partitioning):设置多个Reducer后,Mapper产生的中间键值对需要被分发到不同的Reducer。这是通过分区器(Partitioner) 实现的,它根据键的哈希值(默认)将数据分配到不同的分区,每个分区对应一个Reducer任务。
- 影响:合理设置Reducer数量可以显著影响作业的负载均衡和最终输出文件的数量(每个Reducer产生一个输出文件)。
总结 🎯

本节课中,我们一起学习了MapReduce编程的基础知识。我们首先概览了MapReduce API的核心类,然后详细讲解了编写和提交一个MapReduce作业的六个步骤。接着,我们深入探讨了Mapper的并行化机制,了解了输入分片如何决定Mapper任务的数量。最后,我们学习了如何通过设置Reducer任务数来实现Reducer的并行化,并了解了分区在其中扮演的角色。掌握这些基础知识是进行高效大数据处理的关键。
047:MapReduce编程API解析 🧩

在本节课中,我们将学习Hadoop MapReduce编程API的核心组成部分。我们将了解关键的类、接口和方法,以及它们如何协同工作来构建一个MapReduce作业。通过本教程,你将熟悉编写MapReduce程序所需的基本组件。
MapReduce编程API概述
MapReduce编程API概述。本节将概述MapReduce编程API。这将使你熟悉MapReduce库中各种重要的类、接口和方法。
新旧API对比
新旧API对比。MapReduce API有两种风格。旧包是org.apache.hadoop.mapred包的一部分。新包是org.apache.hadoop.mapreduce包。注意区别。
我们将专门使用新API,也就是这里的这个包。你需要确保使用来自这个mapreduce包的所有类。
因为有些类在旧包和新包中具有完全相同的名称。导入时,你必须小心一点,因为如果你尝试混合使用旧类和新类,可能会遇到麻烦。
新API是旧API的重新设计版本,它被重新设计以便于未来的演进。

你可能会问,为什么旧API仍然存在?这是因为新类依赖于旧类。他们并没有真正将旧API完全迁移到新API中。大部分内容已经迁移,但有些东西仍未迁移,因此旧API仍然存在。
API分类注意事项

API分类注意事项。当你查看Hadoop源代码或Hadoop文档时,你会注意到有些类被标注了InterfaceAudience。
InterfaceAudience.Public意味着特定的类、接口或方法可以被任何人使用。
如果它说InterfaceAudience.LimitedPrivate,那么意味着它只能被有限的一组人使用,并且可以使用该类、接口或方法的人员被标记为某个值。你可能会看到类似LimitedPrivate({"HBase"})的内容,这意味着只有HBase开发人员才被允许使用它。
如果你看到任何标记为Private的类、接口或方法,这意味着它仅供Hadoop内部使用。因此,请避免在你的代码中使用这些类、接口或方法。
同样,还有一个概念叫做InterfaceStability。接口稳定性可以是Stable,这意味着你可以使用它,没有问题。它也可以是Evolving,这意味着该类将来可能会破坏兼容性。它也可以是Unstable,这意味着他们仍在开发中,不能保证这个特定的类、方法或接口将来会存在。
Hadoop API快速浏览
Hadoop API快速浏览。这是Hadoop API文档。我将向你展示mapreduce包。这是mapreduce包。让我们进入mapreduce包。
在mapreduce包中,有Job类。如果你查看Job类,它是Evolving。它是一个公共类,但它仍在演进,他们还没有完全改变它。
如果你查看JobID类,它说它是公共的并且是Stable的,所以JobID没有问题。你可以看到这些类是如何被标注的。
MapReduce API概览
MapReduce API概览。这是API中所有对我们重要的类的一个鸟瞰图。
JobID类代表一个作业的ID。Job类代表一个将要提交的作业。你通常实例化一个Job对象,然后自动会有一个JobID对象与之关联。你从不实例化JobID对象。- 还有一个与作业关联的
JobConfiguration对象。 - 还有一个
JobContext对象,它为你提供关于该作业的信息。当你实例化一个Job对象时,也会有一个JobContext与之关联。你通常不直接使用JobContext,它通过MapperContext和ReducerContext间接使用,Mapper和Reducer类将其作为参数获取。
你会注意到中间有一大堆类,比如TaskAttemptContext、TaskOutputContext、MapContext和ReduceContext。这些都是你不需要太担心的中间类。我把它放在这里只是为了向你展示层次结构的一部分。我用矩形标记的那些是我们关心的类。
Mapper类是一个非常重要的类,这可能是最重要的类之一,因为作为MapReduce作业的作者,你将编写扩展自Mapper类的类,或者编写扩展自Reducer类的类。所以,MyMapper和MyReducer是你将要编写的类,并且你将覆盖这些类的方法。- 还有一个名为
InputFormat的类和一个名为FileInputFormat的类。FileInputFormat是一种特殊类型的InputFormat。 - 有几种输入格式。
TextInputFormat是最重要的输入格式类,因为它代表纯文本文件。通常,在进行MapReduce编程时,你使用纯文本文件,所以这是一个非常重要的类。当然,你也可以使用序列文件或映射文件,所有这些类都继承自FileInputFormat类。
所以我们并不直接关心FileInputFormat类或InputFormat类,因为它们是中间类。FileInputFormat类中有一些方法你会在TextInputFormat类中使用。
- 还有处理HBase输入的
DBInputFormat类。所以,如果你想在HBase上进行MapReduce编程,你可以这样做。 - 同样,对于
DBOutputFormat,我们现在先不担心输出格式,让我们先关心处理纯文本文件的TextOutputFormat。纯文本输入和纯文本输出。我希望他们把这个叫做FileTextInputFormat。他们在这里叫它InputFormat,然后在这里叫FileInputFormat。我想TextFileInputFormat会是一个更好的名字,而TextFileOutputFormat在这里会是一个更好的名字。
无论如何,他们就是这么叫的。好了,这基本上从高层次的角度涵盖了所有的类。现在让我们看看其中一些重要的类,并看看与它们相关的方法。
JobID 类
JobID类代表作业的唯一标识符。JobID是不可变的。作业的标识符格式为单词job,后跟服务器的启动时间,再后跟序列号。所以,每当你启动一个作业时,JobID就是这样生成的。下一个作业将获得job_..._6的JobID,末尾是序列号6,然后是7,依此类推。
JobID对象是拥有这些信息的对象。你可能想使用JobID来打印日志消息,以便跟踪你的作业,并且你也可以使用你的JobID转到作业历史服务器并查看该特定作业的详细信息。
应用程序永远不应该实例化JobID对象。事实上,当你提交作业时,它会自动为你实例化。如果你有一个字符串形式的JobID,有一个工厂风格的get方法:调用forName,这将允许你获得一个JobID对象的实例,但你很少使用它。
JobContext 类
JobContext类是作业的上下文视图或作业的上下文视图。它是一个只读视图,换句话说,你不能更改作业上下文的任何内容。
应用程序不应该实例化JobContext对象。
但是JobContext对象将被传递给应用程序。实际上,当我说应用程序时,在MapReduce的情况下,就是你的Mapper和Reducer在运行。所以Mapper和Reducer任务会自动传递JobContext对象,但有趣的是,它不是作为JobContext对象传递的,而是作为MapperContext对象或ReducerContext对象传递的,这在某种程度上是一个继承自JobContext对象的类。
但重要的是,一旦你手中有了一个上下文对象,无论是MapperContext还是ReducerContext,由于它也是一个JobContext,你可以获取关于作业的信息。你可以获取JobID,这就是你找出JobID是什么的方式。你可以获取作业名称,作业在创建和提交时被赋予了一个名称。你可以获取与作业关联的配置信息。你还可以获取Mapper键类、Reducer键类、输出键类、输出值类。所有不同的键和值类也可以通过这些get方法获取,这些方法实际上返回一个类类型。
正如我所说,JobContext类实际上是MapperContext和ReducerContext的基类,这对我们来说非常关键,因为Mapper的map方法和Reducer的reduce方法都获取MapperContext和ReducerContext,它们有一个对我们来说非常非常重要的方法,叫做write方法。
Mapper 类
Mapper类是Hadoop API中的关键类之一。
Mapper类处理输入键、输入值、输出键和输出值。因此,它适当地接受KeyIn、ValueIn、KeyOut和ValueOut类型,用于它需要经历的各种参数化。
Mapper类有一些重要的方法:
- 它有一个叫做
setup的方法,它被调用一次。这就像你的初始化方法,这是你进行初始化的地方。 - 然后,
Mapper类最重要的方法是map方法。注意,它获取一个键和一个值,并且还获取一个上下文对象。这是你进行转换实际工作的地方,将输入转换为输出。 - 然后还有
cleanup方法,这是你清理Mapper任务的地方。所以,任何你需要做的反初始化或任何你真正需要做的清理工作,你都会在这个特定方法中完成。 - 还有一个叫做
run的方法,一些专家用户在多线程情况下会使用它。所以你可以实际控制输入的到来。通常你不这样做,你通常使用map方法进行处理,其中map方法由框架自动调用。
正如我提到的,MapperContext类是一个重要的类,它为你提供了Mapper工作的上下文。它为你提供了MapperContext,这也是一个JobContext。
现在,如果你回到上一张关于Mapper类的幻灯片,你会注意到Mapper类的map方法获取一个上下文对象。甚至setup方法和cleanup方法也获取一个上下文对象。所以上下文对象非常重要,它包含关于作业的信息。别忘了,一个作业可能被分解成任务,任务可能跨多个机器运行,所以每个任务都必须知道它们到底在做什么,上下文是什么,我的作业上下文是什么。这就是MapperContext的作用。MapperContext为你提供关于Mapper的信息,也为你提供关于作业本身的信息。
MapperContext有一个非常重要的方法,这个方法叫做write方法。write方法接受一个键和一个值,这就是map方法用来将输出作为中间键值对输出的方法。所以这对我们来说是一个极其重要的方法,当我们编写map方法时,我们将持续使用它。
然后还有一个叫做setStatus的方法,你可以设置MapReduce作业的状态。如果你设置了状态,那么它就会出现在管理控制台的UI控制台中。所以从这个角度来看,它非常有用。
然后这里还有三个方法,分别是getCurrentKey、getCurrentValue和nextKeyValue。再一次,如果你想控制获取下一条记录、再下一条记录,那么你会使用这些方法,但你通常不这样做。你通常在map方法中被给予键和值,这对你来说已经足够了,这就是你进行处理的方式。
开发者如何使用 Mapper 类
开发者如何使用Mapper类?这是最重要的类。开发者必须将输入键值对映射到一组中间键值对。这就是开发者必须做的事情。
这意味着开发者必须做的是扩展Mapper类。
- 覆盖
setup方法以初始化Mapper。你可能需要查找数据库或某种参考数据,无论你想做什么,都必须在setup方法中完成。 - 然后你覆盖
map方法,这是你执行所有业务逻辑的地方。将输入记录转换为中间记录的实际技巧就是在这个方法中完成的。
别忘了,一个输入键值对可能映射到零个输出键值对,或一个输出键值对,或多个输出键值对。它具体映射到什么取决于你将在上下文对象上调用write方法的次数。
另外,你生成的中间记录。记住,你正在将键和值输入到Mapper中,然后你将生成这个中间键值对。中间键值对不必是相同的类型。它们不必是相同的类型,这就是为什么当你定义Mapper类时,最终将KeyIn、ValueIn、KeyOut和ValueOut作为四个参数类型的原因。
现在先了解大致的轮廓。然后当我们开始查看代码示例时,你会开始更清楚地理解。
- 开发者必须做的下一步是覆盖
cleanup方法以清理任何资源。
在我深入探讨框架如何使用Mapper类之前,让我们快速查看一下文档。所以我要查看Mapper类。这是Mapper类。
注意我说过,Mapper类将KeyIn、ValueIn、KeyOut和ValueOut作为参数化类型。当你编写Mapper类时,你会注意到你实际上是在传递参数化。这是你的KeyIn、ValueIn、KeyOut、ValueOut。
注意在这种情况下,你所做的只是覆盖map方法。你没有覆盖setup方法或cleanup方法。你不必这样做。map方法是最重要的方法。
所以你在这里覆盖map方法。map方法接受一个键、一个值和一个上下文对象。正是这个上下文对象,你用它来写入你的输出。所以注意,上下文对象有一个叫做write的方法,这就是你用来写入输出的方法。输出类型必须是Text类型,输出键类型必须是Text类型,输出值类型必须是IntWritable类型。
框架如何使用 Mapper 类
框架如何使用你刚刚编写的Mapper类?框架的工作是将输入键值对传递给Mapper。
所以框架实际上实例化了一个Mapper对象,也就是你编写的Mapper类。

然后它调用setup方法一次。这是Mapper必须进行初始化的地方。
然后,对于输入分片中的每个键值对,map方法都会被调用。所以它只是不断地调用map方法,这就是你执行所有业务逻辑的地方。
然后,一旦完成,它就调用cleanup方法。然后我们就完成了。

Reducer 类
好了,这就是Mapper。现在让我们看看Reducer。同样,这是一个非常相似的故事。
Reducer是一个参数化类。它接受KeyIn、ValueIn、KeyOut、ValueOut作为输入。这些是它接受的各种数据类型。

它有setup方法、reduce方法、cleanup方法和run方法。
setup方法是你为Reducer进行初始化的地方。reduce方法是最重要的方法,这是你将生成输出的地方。cleanup方法是你将进行任何需要做的清理工作的地方。这与我们在Mapper类中拥有的非常相似。

这里要记住的最重要的事情是,reduce方法是关键方法,并且reduce方法接受一个键和一个值列表。这是不同的地方。注意它接受一个值列表。它接受一个Iterable<ValueIn>,这是一个值列表。
所以一旦你得到键和值列表,你就可以决定做任何你想做的事情。如果你回想一下,当我们做单词计数示例的场景时,我们看到我们得到了所有数字的列表,我们得到了单词fish为[1,1,1,1,1],这将是那个列表。所以你必须遍历这个列表并把它们加起来,这就是你将在reduce方法中做的事情。
然后还有ReducerContext类,它与MapperContext类非常相似。ReducerContext类有一个叫做write的方法,这就是你用来写出键值对组合的方法。
然后它还有一个叫做setStatus的方法,所以你可以设置作业的状态。
当然,如果你想控制自己获取键和值,你可以使用getCurrentKey和getValues方法。
大多数时候,我们都在使用这两个,特别是只使用write方法,这就是我们关心的。
开发者如何使用 Reducer 类
开发者如何使用Reducer类?开发者负责获取中间键值对组合并生成最终的键值对,这些键值对成为输出的一部分。
所以开发者必须扩展Reducer类。
- 再次覆盖
setup方法,这是你进行所有初始化的地方。 - 然后覆盖
reduce方法,这是你执行所有业务逻辑的地方,这是你执行归约逻辑的地方。 - 然后作为归约逻辑的一部分,将输入记录转换为最终输出记录。别忘了,转换后的记录类型可能与输入类型不同。
- 最终为
Reducer进行清理。
框架如何使用 Reducer 类
框架如何使用Reducer类?再一次,在我们看这个之前,让我们继续查看文档。
让我们看看Reducer类。Reducer就在那里。很好。
我们讨论了shuffle和sort。所以这里是Reducer类。注意Reducer类接受KeyIn、ValueIn、KeyOut、ValueOut,这就是你的参数化类型。


reduce方法接受一个键,它还接受一个Iterable值列表(键和值),并且还接受上下文对象。然后它遍历这个列表。这是经典的单词计数示例,我们接下来会讨论。
框架如何使用你编写的Reducer类?框架应该将中间键值对传递给Reducer。
所以框架将实例化一个Reducer对象,然后它将调用setup方法,这是你可以进行初始化的地方。
然后它将最终调用reduce方法,这是最重要的方法。当它调用reduce方法时,它将作为reduce方法的一部分传递键和值列表。
然后,一旦完成,在处理完所有输入记录后,它将最终调用cleanup方法。
Job 类
然后是Job类。Job类是一个非常重要的类,因为当你想向Hadoop框架提交作业时,你需要实例化一个Job对象。所以它代表了作业提交者对作业的视图。它允许用户配置作业。
所以想法是,你实例化一个Job对象,你可以配置它,你可以提交它,你还可以控制它的执行。换句话说,如果你愿意,你可以杀死作业。你可以查询作业的状态,你可以看到作业处于什么状态。
它有几个set方法。set方法只在作业提交之前有效。如果你尝试在作业提交后调用set方法,它会抛出一个叫做IllegalStateException的异常。所以你在提交作业之前进行设置。
让我们看看一些方法。Job对象中有很多方法,但一些重要的方法是:
setJobName:你可以设置作业名称,以便它在管理控制台中以正确的名称可用。- 你还可以设置
Jar类,这是我们在下一节要讨论的内容。 - 然后你还可以设置
InputFormat、Mapper类、Reducer类、输出键类、输出值类。什么是输出键和输出值?注意你没有设置输入键和输入值。原因是它根据文件获取该信息。如果是文本输入文件,它会自动知道键是字节偏移量(被视为LongWritable),值是字符串,所以变成Text。
注意我在这里使用像Text、LongWritable和IntWritable这样的词。这是因为这些键和值必须是Hadoop序列化类型。我们将在下一节中更多地讨论这个问题。


好了,所以你设置了输出键类、输出值类。分区类是一大堆你可以设置的东西。我们将在Hadoop MapReduce编程部分讨论所有这些不同的东西。
你还可以获取作业的进度。什么是设置进度?这是当你提交作业时。你还可以找出map进度,你可以找出reduce进度。当你运行MapReduce作业
048:MapReduce 程序开发步骤 📝

在本节课中,我们将学习如何编写一个完整的 MapReduce 程序。我们将通过一个经典的“词频统计”问题,一步步地讲解从问题分析、设计到代码实现、配置和运行的完整流程。
上一节我们概述了 Hadoop MapReduce API,本节中我们来看看编写一个 MapReduce 程序所需的具体步骤。
问题场景与设计思路


为了学习编写 MapReduce 程序的步骤,我们从一个具体的问题场景入手。
假设在 HDFS 中有一个名为 /IN 的目录,其中包含多本以文本文件形式存储的书籍。我们的目标是:列出所有这些书籍中出现的所有唯一单词,并统计它们各自出现的总次数。
在计算机科学中,我们通常将问题分解为输入、处理和输出三部分。对于 MapReduce 作业,输入是文本文件,输出是单词及其计数的列表,而处理过程则由 Map 和 Reduce 两个阶段完成。
因此,我们需要设计:
- 作业的输入和输出。
- Mapper 的输入、处理和输出。
- Reducer 的输入、处理和输出。
作业输入与输出设计



以下是作业输入与输出的设计细节:
- 作业输入:HDFS 中的
/IN目录,包含多个文本文件。 - 输入格式:由于是文本文件,我们使用
TextInputFormat类。它将每个文本行作为一条记录。 - 输入键值对:根据
TextInputFormat,键是文件中的字节偏移量(LongWritable类型),值是文本行内容(Text类型)。 - 作业输出:将写入一个指定的输出目录(例如
/OUT)。 - 输出格式:我们希望输出为文本文件,因此使用
TextOutputFormat类。默认情况下,它会用制表符分隔键值对。
Mapper 设计
接下来,我们设计 Mapper 部分。
- Mapper 输入:键为字节偏移量(
LongWritable),值为一行文本(Text)。 - Mapper 处理:对于词频统计,Mapper 会忽略输入键。它解析输入的文本行,为行中的每个单词生成一个中间键值对。
- Mapper 输出:键为单词(
Text类型),值为数字 1(IntWritable类型),表示该单词出现了一次。公式可以表示为:map(line) -> list of (word, 1)。
Reducer 设计
最后,我们设计 Reducer 部分。
- Reducer 输入:键为单词(
Text),值为该单词对应的所有计数 1 的列表(Iterable<IntWritable>类型)。 - Reducer 处理:遍历输入值列表,将所有计数相加,得到该单词的总出现次数。
- Reducer 输出:键为单词(
Text),值为该单词的总计数(IntWritable类型)。公式可以表示为:reduce(word, list[1,1,...]) -> (word, sum(list))。
程序实现步骤


现在我们已经完成了整体设计,接下来开始实现。编写 MapReduce 程序通常包含以下六个步骤:
以下是实现 MapReduce 程序的六个核心步骤:

- 实现 Mapper 类
- 实现 Reducer 类
- 配置 Job 对象(驱动类)
- 编译 Java 类
- 打包成 JAR 文件
- 运行作业
步骤一:实现 Mapper 类
首先,我们需要创建一个继承自 Mapper 类的类。
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.util.StringTokenizer;
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
// 为效率考虑,将常量 1 声明为实例变量
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
@Override
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 将 Text 类型的行内容转换为 String
String line = value.toString();
// 使用 StringTokenizer 分词(此处可替换为更复杂的分词逻辑)
StringTokenizer tokenizer = new StringTokenizer(line);
while (tokenizer.hasMoreTokens()) {
word.set(tokenizer.nextToken());
// 输出中间键值对: (单词, 1)
context.write(word, one);
}
}
}

步骤二:实现 Reducer 类
接下来,我们实现 Reducer 类。
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;

public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
@Override
public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
// 遍历所有值(都是1),求和
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
// 输出最终结果: (单词, 总次数)
context.write(key, result);
}
}


步骤三:配置 Job 对象(驱动类)
现在,我们需要编写一个包含 main 方法的驱动类来配置和提交作业。
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
public class WordCount {
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println("Usage: WordCount <input path> <output path>");
System.exit(-1);
}
// 1. 创建配置和作业实例
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "Word Count");
// 2. 设置 Jar 包主类
job.setJarByClass(WordCount.class);
// 3. 设置 Mapper 和 Reducer 类
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 4. 设置输入输出格式
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
// 5. 设置输出键值类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6. 设置输入和输出路径(从命令行参数获取)
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 7. 提交作业并等待完成
boolean success = job.waitForCompletion(true);
System.exit(success ? 0 : 1);
}
}


步骤四与五:编译与打包
完成代码编写后,需要编译并打包。


以下是编译和打包的注意事项:
- 编译:需要使用 Hadoop 库进行编译。需要将 Hadoop 的 JAR 文件(通常位于
$HADOOP_HOME/share/hadoop/下的各个子目录中)添加到类路径。 - 打包:Hadoop 要求将作业的所有类文件及其依赖打包成一个单独的 JAR 文件,以便框架能将其分发到各个计算节点。可以使用
jar命令或 Maven/Ant 等构建工具。
步骤六:运行作业

打包完成后,即可提交作业到 Hadoop 集群运行。推荐使用 yarn 命令,因为它会自动设置 Hadoop 所需的类路径。
# 使用 yarn 命令提交作业
yarn jar wordcount.jar WordCount /IN /OUT
命令解释:
yarn jar: 用于提交 MapReduce 作业的 YARN 命令。wordcount.jar: 你打包好的 JAR 文件路径。WordCount: 包含main方法的驱动类全限定名。/IN: 输入目录在 HDFS 中的路径。/OUT: 输出目录在 HDFS 中的路径(该目录必须不存在)。
查看运行结果

作业成功运行后,可以查看输出结果。
- 成功标记:输出目录(如
/OUT)下会生成一个名为_SUCCESS的空文件,标志着作业成功完成。 - 结果文件:实际结果保存在以
part-r-开头的文件中(例如part-r-00000)。文件数量等于 Reducer 的数量(默认一个)。每个文件内包含由制表符分隔的键值对(单词和计数)。


可以使用以下 HDFS 命令查看结果:
# 查看输出目录内容
hadoop fs -ls /OUT
# 查看结果文件内容
hadoop fs -cat /OUT/part-r-00000
总结
本节课中我们一起学习了编写 MapReduce 程序的完整步骤。我们通过“词频统计”这个例子,详细讲解了从问题分析、Mapper/Reducer 设计、代码实现、作业配置到编译打包和运行的整个流程。关键点在于理解数据在 Map 和 Reduce 阶段的流动与转换,并正确使用 Hadoop MapReduce API 进行实现和配置。掌握这些步骤是进行大数据处理开发的基础。
049:MapReduce Mapper并行机制 🚀



在本节课中,我们将学习MapReduce框架中Mapper任务的并行机制。我们将了解如何实现和控制Mapper的并行执行,以及影响并行度的关键因素。
Mapper并行机制概述
到目前为止,我们已经学习了如何编写一个MapReduce作业。现在,让我们来看看并行性。在本节中,我们将探讨如何为一个作业实现和控制Mapper的并行性。
为了理解Mapper并行性,让我们回顾一下MapReduce框架。在MapReduce框架中,你有输入文件。这些输入文件可以被多个Mapper处理。到目前为止,我们只关注了一个Mapper,但实际上可以有多个Mapper,例如Mapper1、Mapper2、Mapper3、Mapper4。它们可以在同一台机器上运行,也可以在多台机器上运行。
输入被分解成不同的片段,不同的Mapper可以处理不同的输入片段,然后它们各自产生输出。最终,Mapper的输出被发送到Hadoop框架的Shuffle和Sort阶段,然后传递给Reducer。目前我们暂时不关心Reducer,先专注于Mapper。让我们弄清楚输入文件是如何被分解成多个片段,以便多个Mapper可以处理不同的片段。


如何确定Mapper任务数量
那么,问题是如何为一个作业定义或确定Mapper的数量?一个作业的Mapper任务总数基于两个因素。
第一个因素是输入文件的数量。输入文件的数量等于Mapper任务的数量。因此,如果你有15个输入文件,那么该特定作业将有15个Mapper任务。所以,你可以认为输入文件越多,并行性就越高,因为会有更多任务并行运行。
第二个因素是输入分片(Input Splits)的数量。输入分片是文件被切分的方式。通常,一个文件按其拥有的块(Block)数进行分片。因此,如果一个文件有七个块,那么意味着将有七个Mapper任务运行。
这两个因素共同决定了作业的Mapper任务数量。现在,这两个因素需要协同工作,因此我们需要一个公式将两者结合起来。
Mapper任务数量计算公式
让我们来看看这个公式。一个作业的Mapper任务数量实际上是文件数量的总和,以及每个文件的块数。对于每个文件,你需要计算它有多少个块。你可以通过文件大小除以块大小来得到块数,然后将所有文件的块数相加,就得到了该作业将要执行的总Mapper任务数。
我想强调的是,你不能直接设置作业的Mapper任务数量。它实际上是由框架根据你的输入间接计算出来的。换句话说,它基于你拥有的文件数量和每个文件的块数。
不同场景示例
让我们看几个场景。如果你要运行一个词频统计作业,输入是一个文件和一个块,那么将有一个Mapper任务。
如果你要运行词频统计,输入是10个文件,每个文件是一个块,那么将有10个Mapper任务。

如果你要运行词频统计,输入是一个文件,但该文件有五个块,那么你将拥有五个Mapper任务。
如果你要运行词频统计,输入是两个文件,一个文件有三个块,另一个文件有六个块,那么就是3加6,总共九个Mapper任务。

输入文件过多的问题

然而,你可能会遇到输入文件过多的问题。让我展示一个场景。假设你的块大小是128兆字节(这是默认块大小),并且你的平均文件大小是10兆字节。如你所见,你的文件大小远小于块大小,这并不理想,但假设这就是我们导入HDFS的情况。
假设我们有一个作业,有10,000个文件,每个文件当然是10兆字节。这意味着将启动10,000个Mapper任务,也就是将启动10,000个进程。
因此,输入文件过多的问题在于,在这种情况下,我们有10,000个非常小的、小于一个块的输入文件,这会导致NameNode中存储过多的数据结构。我们已经知道这对HDFS来说是个问题,但在MapReduce中,我们还有另一个问题,即为非常小的任务启动过多的容器。在这种情况下,10,000个JVM、10,000个子进程、10,000个容器将在多台机器上启动以执行你的作业。
输入分片过多的问题
同样存在输入分片过多的问题。让我为你模拟一个场景。假设块大小是1兆字节。我知道你不会将块大小设置为1兆字节,这太小了,正如我所说,默认块大小是128兆字节。假设文件数量是10,所以我们有很少的文件。平均文件大小是1千兆字节。
因此,我们有一个作业有10个1千兆字节的文件。让我们看看将有多少个Mapper任务。每个文件大约是1千兆字节,所以1千兆字节除以块大小(1兆字节),大约是1000。然后乘以10个文件,得到10,000个Mapper任务。再次强调,输入分片过多也会导致问题,而输入分片过多也可能基于块大小。稍后,我将讨论在MapReduce框架中,针对不同输入格式如何进行输入分片。到那时,你会更理解这一点,但现在只需明白,你可能会遇到输入分片过多的情况,最终导致Mapper任务过多。
这里的问题再次是NameNode中过多的数据结构,以及启动过多的容器。所以,这一切都需要平衡。你必须考虑如何导入数据以及如何存储数据,这就是为什么我深入探讨了HDFS部分,并要求你思考如何设计你的系统。
实践演示
好了,让我们登录一个Hadoop服务器作为Hadoop管理员,并进行一些演示。
我以Hadoop管理员用户ID登录到我的虚拟机Hadoop服务器。首先,我想向你展示我的HDFS文件系统中有什么。在我的HDFS文件系统中,我有一个名为in_one_file_one_block的目录。这是一个输入目录,包含一个文件,并且该文件只有一个块。然后我有另一个目录叫in_10_files_one_block_each,在这个目录中我有10个文件,每个文件都是一个块。还有一个目录in_one_file_many_blocks,里面有一个文件,它包含多个块。我将运行这三种不同的场景。
首先运行第一个场景:一个目录中有一个文件和一个块。
让我执行hadoop fs -ls /in_one_file_one_block。好的,有这个文件夹,里面有一个文件MobyDick.txt。如果我执行ls -lh,你可以看到它大约是1.2兆字节。如果我去我的文件系统查看in_one_file_one_block,1.2兆字节,块大小是128兆字节,点击它,它显示只有一个块,这很合理,实际上远小于一个块。
现在我想运行我的MapReduce程序。在此之前,我需要清理输出目录。让我清理输出目录。现在我将运行MapReduce程序。我将运行yarn jar命令,指定jar文件和词频统计程序,并传递输入路径/in_one_file_one_block,它包含一个文件且只有一个块。这意味着它将只使用一个Mapper,这正是我们应该看到的。现在让我们运行它。
总输入路径数是1,分片数是1,因此将只有一个Mapper。Mapper阶段完成,Reducer阶段完成,程序结束。现在让我们查看输出目录。hadoop fs -ls /out。我们有_SUCCESS文件和part文件。程序运行正常,我可以展示输出内容。执行hadoop fs -cat /out/part-r-00000,确保我们的程序运行了。是的,程序运行了。
然后我将执行一个grep命令。我们执行hadoop fs -cat /out/part-r-00000 | grep congregational。没有输出?等等,这里有单词“congregational”,它在我的输出中出现了三次。有一个Reducer运行,有一个输出文件。
现在我想查看作业历史服务器。让我看看我运行的作业名称是什么。如果我稍微向上滚动,你可以看到作业ID是job_22...。让我们看看在历史服务器中这个MapReduce作业是什么样子。这是我的历史服务器,刷新一下,这里有22号作业。点击它。你可以看到有一个Mapper任务运行,有一个Reducer任务运行。我们看到了输出。这是标准内容,你已经见过了。
现在我想展示下一个场景。让我清屏。下一个场景是一个目录中有10个文件,每个文件1兆字节。这意味着是10个文件,10个块。执行hadoop fs -ls /in_10_files_one_block_each。有10个文件,每个文件一个块。它们是《白鲸记》文本的10个副本。如果我执行ls -lh,你可以看到有10个1.2兆字节的文件。如果我看我的文件系统in_10_files_one_block_each,有10个文件(01到10),它们每个都是一个块,因为1.2兆字节小于块大小。
现在我想运行程序。但在运行程序之前,我需要清理输出目录。让我清理输出目录,我已经做了。我想运行我的程序。我程序中唯一的区别是输入文件夹。所以输入文件夹是/in_10_files_one_block_each。输入文件夹包含10个文件,每个文件一个块。根据公式,由于是10个文件且每个都是一个块,应该需要10个Mapper。


让我们运行这个。总输入路径数是10,分片数是10,因此将有10个不同的Mapper运行。它们正在运行时,让我执行jps。注意你看到这些名为YarnChild的进程,它们是容器。这是NodeManager,这是DataNode,这是MR AppMaster(容器0),然后这些是被启动并正在执行的不同子节点。将有10个Mapper任务运行,所以最终将启动10个不同的进程。在这个例子中,它们都在同一台机器上运行,但如果你有多机架构,它将在多台机器上运行。
程序结束,作业完成,那是作业23。让我们返回验证一下,因为之前运行了22号作业。那是作业23。让我们查看历史服务器。我在这里,刷新,点击它。有10个Mapper任务运行。点击它,这里有运行的10个Mapper任务。启动了10个不同的容器,它们是并行完成的。至于Reducer,仍然是一个。
让我们回去查看输出。hadoop fs -ls /out,有输出文件,因为只有一个Reducer,所以生成了一个文件。现在我将执行grep查找“congregational”。hadoop fs -cat /out/part-r-00000 | grep congregational。出现了,“congregational”出现了30次。在这个例子中,“congregational”出现了30次,而在前一个例子中,它出现了3次。在这个例子中,我有10个不同的文件,这就是为什么是30次。为了向你证明上次是3次,让我再次运行之前的作业。所以hadoop fs -rm -r /out,然后我将运行之前的作业。之前的作业是/in_one_file_one_block。那是作业24。所以在这种情况下,我生成一个新的输出,它应该只有三次“congregational”。hadoop fs -ls /out,然后如果我看输出,执行grep,应该只有三次。是的,只有三次。所以当你有一个文件时,是3次;当你有10个文件副本时,是10个不同的Mapper,但最终你得到30次,因为Reducer介入并将所有计数合并在一起。我们还没有深入Reducer部分,但我想在这里说明一点。
我还想让你注意另一件事。让我执行hadoop fs -cat /out/part-r-00000。你还会注意到输出实际上是先排序的,首先是大写A到Z,然后是小写a到z,所以输出是排序过的。
现在我将展示第三个场景:我有一个文件,并且它包含多个块。让我们看看那个文件夹。执行hadoop fs -ls /in_one_file_many_blocks。我有一个文件,它有很多块。让我执行ls -lh。那是599兆字节,这让我相信它大约有5个块,因为128兆字节一个块。但让我们验证一下。我将转到HDFS浏览器,查看in_one_file_many_blocks,599兆字节,每个块120兆字节,点击文件,这里有五个不同的块。好的,所以那是五个块。
现在我想运行这个程序。hadoop fs -rm -r /out,然后我想运行相同的程序。但这次,我将指定输入为/in_one_file_many_blocks。运行程序。让我们思考一下:五个块,每个块128兆字节,所以我将有五个不同的JVM运行,每个JVM将处理一个大约128兆字节的块,然后它们将在Shuffle和Sort阶段被合并,之后发送给Reducer。我们现在讨论Mapper并行性,下一节我们将讨论Reducer并行性。
让我们运行这个。这将需要大量的计算,因为我有很多数据,而且我只有一台简单的笔记本电脑在这里运行,所以需要一些时间。让我们运行它。
好的,输入分片数是5。所以将有五个不同的任务运行。让我执行jps。一、二、三、四、五,这些是正在运行的五个不同任务。我有NameNode、DataNode,那是我的HDFS。然后我还有Secondary NameNode作为HDFS的一部分,这是三个守护进程。然后我有NodeManager和ResourceManager,这是YARN框架的守护进程。然后我有支持MapReduce框架的历史服务器。当然,YARN框架也支持MapReduce框架。在这种情况下,我有五个进程正在运行,它们正在工作,所以将有五个Mapper任务,它们正在运行。每个Mapper任务处理大约128兆字节的数据,因此正在进行大量处理。每个任务一次读取一行,解析该行,提取单词,然后将其写入Mapper输出,格式为“单词 1”、“单词 1”、“单词 1”,依此类推,最终进入Shuffle和Sort阶段。这将运行一段时间,所以我将暂停并在几分钟后回来。
好的,作业完成了。让我回退一下。这是作业号25。输入分片数是5,因为我们有一个包含五个块的文件,所以Mapper继续运行,启动了大约五个Mapper任务,也就是YarnChild。然后在这里,你注意到Mapper尚未完成?实际上Mapper已经完成了,但Reducer显示完成了7%,这并不是因为Reducer已经启动,而是因为Shuffle和Sort阶段正在发生,而Shuffle和Sort阶段被认为是Reducer阶段的一部分,所以你看到了这个情况。一旦Mapper 100%完成,Reducer才会启动。实际上,在Mapper阶段和Reducer阶段之间存在一个屏障,Reducer阶段在Mapper阶段完成之前无法工作。
程序结束了。如果我执行hadoop fs -ls /out,我应该看到输出。输出就在那里。现在我将执行cat命令,然后执行grep查找“congregational”。hadoop fs -cat /out/part-r-00000 | grep congregational。我得到1,500。
如果我执行hadoop fs -ls /in_one_file_many_blocks,我想向你展示这个文件是什么。它实际上是《白鲸记》文本重复了500次。当我们有一个文件时,我们看到“congregational”出现了三次,现在我们有500次重复,所以是3乘以500,这就是为什么你看到“congregational”的总计数是1500。
让我们查看作业历史服务器。这是作业历史服务器,作业25,点击它。你会注意到有五个Mapper任务,每个Mapper任务运行了大约九分钟,但其中一个只运行了六分钟,那可能是块大小最小的那个,可能是第五个块或第四个块。

本节总结

在本节中,我们研究了如何控制作业中Mapper任务的并行性。我们了解到,不能直接控制Mapper任务的并行性,它是通过文件数量和每个文件的块数间接控制的。
050:Reducer并行机制 🚀


在本节课中,我们将学习Hadoop MapReduce作业中Reducer并行性的实现与控制方式。我们将探讨如何设置多个Reducer,以及这如何影响作业的执行和输出。
概述

MapReduce编程模型的核心思想是将大规模数据处理任务分解为可并行执行的Map和Reduce阶段。默认情况下,一个作业只有一个Reducer。然而,通过配置,我们可以启动多个Reducer并行工作,这可以显著提高处理速度。本节将详细解释Reducer并行机制的工作原理、配置方法及其对输出的影响。
MapReduce模型回顾
为了理解Reducer并行性,让我们先回顾一下MapReduce编程模型。
在MapReduce编程中,输入数据被分割成多个输入分片。假设我们有四个输入分片:1、2、3和4。每个输入分片由一个Mapper处理。因此,我们可能有Mapper 1、Mapper 2、Mapper 3和Mapper 4。这些Mapper可能在同一台机器上运行,也可能分布在多台机器上,它们会并行处理各自的分片,但在单个Mapper内部,数据是按顺序处理的。
假设Mapper 1生成键值对组合:键A对应值1,键B对应值2。Mapper 2生成:键C对应值3和值6。Mapper 3生成:键A对应值5,键C对应值2。Mapper 4生成:键B对应值7,键C对应值8。
每个Mapper独立生成其输出。Mapper的输出随后由框架处理,这个阶段我们称之为Shuffle和Sort阶段。
Reducer并行执行
你可以配置一个、两个、十五个甚至零个Reducer。到目前为止,我们看到的例子都只有一个Reducer。现在,我们讨论如何配置多个Reducer。

如果你有多个Reducer,例如三个Reducer并行运行,Shuffle和Sort框架会保证两件事:
- 没有两个Reducer会看到相同的键。
- 呈现给一个Reducer的所有键都是按排序顺序排列的。


这意味着,如果你有两个Reducer,它们将看到不同的键集合。同时,当一个Reducer接收其键值对(实际上是键和对应的值列表)时,这些键是按顺序提供的。
让我们看一个并行执行的例子。假设有三个Map任务(Map Task 1, 2, 3)处理不同的分片。每个Map任务顺序处理记录并生成键值对。不同的Map任务可能生成相同的键(例如,K1)。

现在,假设我们有两个Reducer任务(Reducer Task 1和2)在运行。框架保证没有两个Reducer会看到相同的键。因此,Reducer 1可能只看到键K2、K4和K5,而Reducer 2看到键K1和K3。同时,Reducer接收到的键是按顺序排列的(例如,K2先于K4,K4先于K5)。
默认与设置Reducer数量
默认情况下,一个作业只有一个Reducer。我们之前的作业从未设置过Reducer数量,因此都只产生一个输出文件,其名称格式为 part-r-00000。
你可以为作业设置Reducer的数量,这可以通过Job API实现。
以下是设置Reducer数量的两种主要方法:
方法一:使用Job对象
Job类有一个便捷方法 setNumReduceTasks,它接受一个整数参数。你可以在提交作业之前,使用Job对象调用此方法来设置Reducer的数量。

Job job = Job.getInstance(conf, "word count");
// ... 其他配置 ...
job.setNumReduceTasks(3); // 设置为3个Reducer
// ... 提交作业 ...

重要提示:必须在提交作业之前设置Reducer数量,否则可能不生效或抛出IllegalStateException异常。
方法二:使用Configuration对象
你也可以通过Configuration API来设置Reducer数量。Configuration对象有一个setInt方法,可以设置属性mapreduce.job.reduces。
Configuration conf = new Configuration();
conf.setInt("mapreduce.job.reduces", 3); // 设置为3个Reducer
Job job = Job.getInstance(conf, "word count");
// ... 其他配置和提交作业 ...
最佳实践:不要在程序中硬编码Reducer数量。最好从外部配置文件(如.properties文件或XML配置文件)中读取这个值,以提高程序的灵活性和可维护性。
Reducer输出
每个Reducer都会生成一个独立的输出文件。如果你有N个Reducer,就会得到N个part文件。输出文件的命名格式为 part-r-xxxxx,其中xxxxx是一个从0开始的Reducer编号。
例如,如果有5个Reducer,输出文件将是:
part-r-00000part-r-00001part-r-00002part-r-00003part-r-00004
零个Reducer的特殊情况


你也可以将Reducer的数量设置为零。这意味着作业将不运行任何Reducer。
那么,作业的输出是什么?此时,作业的输出就是Mapper的输出。如果你有M个Mapper,每个Mapper都会生成一个输出文件。Mapper输出文件的命名格式为 part-m-xxxxx,其中xxxxx是从0开始的Mapper编号。
关键区别:Mapper的输出不是排序的。因为Shuffle和Sort阶段是Reducer的一部分,当Reducer数量为零时,这个阶段不会发生。因此,Mapper的输出文件中,相同的键可能会出现多次,并且记录的顺序就是Mapper处理它们的顺序。
实践演示
为了巩固以上概念,我们通过三个场景进行演示:
- 通过Configuration对象设置3个Reducer:运行一个WordCount作业,输出目录中会生成三个
part-r-xxx文件。检查文件内容,可以确认键是排序的,且相同的键只出现在一个文件中。 - 通过Job对象设置3个Reducer:运行另一个WordCount作业,结果与场景1相同,验证了两种设置方法效果一致。
- 设置0个Reducer并处理10个输入文件:运行WordCount作业,输入目录包含10个文件(每个文件一个HDFS块)。由于没有Reducer,输出目录中会生成10个
part-m-xxx文件(每个Mapper一个)。检查这些文件,可以发现输出内容未排序,且相同的单词(如“congregational”)分散在多个Mapper输出文件中,每个出现都计为1,尚未被累加。
总结
在本节课中,我们一起学习了Hadoop中Reducer并行机制的实现与控制。我们了解到:

- 默认情况下,每个作业只有一个Reducer。
- 可以通过
Job.setNumReduceTasks()方法或Configuration.setInt("mapreduce.job.reduces", N)属性来设置Reducer的数量(N)。 - 必须在提交作业之前进行设置。
- 每个Reducer产生一个独立的、内部按键排序的输出文件(
part-r-xxxxx),且框架保证不同的Reducer处理不同的键。 - 可以将Reducer数量设置为零,此时作业没有Reduce阶段,Mapper的原始输出(
part-m-xxxxx)将直接作为最终结果,且这些输出是未排序的。 - 建议从配置文件读取Reducer数量,而非硬编码在程序中。
理解并合理配置Reducer并行度,对于优化MapReduce作业的性能至关重要。
051:MapReduce编程基础总结 🧠

在本节课中,我们将对MapReduce编程的基础知识进行总结。我们将回顾MapReduce API中的关键类、编写MapReduce程序的六个步骤,以及Mapper和Reducer的并行度概念。
MapReduce API关键类回顾
上一节我们介绍了MapReduce编程的基本流程,本节中我们来看看MapReduce API中的几个核心类。
- Mapper类:这是我们需要继承并扩展以编写自定义Mapper的基类。
- Reducer类:这是我们需要继承并扩展以编写自定义Reducer的基类。
- Job类:我们实例化此对象,用于配置和提交一个MapReduce作业。
- Mapper Context与Reducer Context:这两个对象会自动实例化,并分别传递给Mapper的
map方法和Reducer的reduce方法,以便我们获取关于作业和任务的上下文信息。 - TextInputFormat与TextOutputFormat类:这两个类用于表示和处理纯文本输入文件与输出文件。
编写MapReduce程序的六个步骤
理解了核心类之后,以下是编写一个MapReduce程序需要遵循的六个具体步骤。
- 实现Mapper类:通过继承
Mapper类,并重写其map方法。 - 实现Reducer类:通过继承
Reducer类,并重写其reduce方法。 - 实现Driver类:在此类中实例化
Job对象,配置作业参数,并提交作业。 - 编译类文件:编译上述三个核心类以及任何依赖类。
- 打包为JAR文件:将编译后的类文件打包成一个JAR文件。这是必需的,因为Hadoop需要将此文件分发到运行不同任务的各个节点上。
- 运行程序:使用
yarn命令来提交并运行打包好的JAR文件。
Mapper与Reducer的并行度
在程序运行层面,我们探讨了Mapper和Reducer的并行执行机制。
Mapper并行度取决于两个因素:输入文件的数量和输入分片(Input Split)的数量。实际Mapper任务数是这两者的综合结果。
Reducer并行度的默认值为1,即每个作业默认只有一个Reducer任务。但我们可以通过配置将此数值设置为任何我们需要的值,包括0。当Reducer数量设为0时,程序将不会运行任何Reducer阶段。
总结

本节课中我们一起学习了MapReduce编程的基础总结。我们回顾了MapReduce API中的关键类,明确了编写MapReduce程序的六个标准步骤,并理解了控制Mapper和Reducer并行度的基本原理。这些知识是构建更复杂大数据处理任务的基础。
052:MapReduce中级编程导论 🚀

在本节课中,我们将深入探讨MapReduce API的细节,包括Combiner、Partitioner、压缩和计数器等核心概念。这些技术能帮助我们优化作业性能,并更好地控制数据处理流程。
概述 📋
MapReduce编程模型不仅包含基础的Map和Reduce阶段。为了提升效率和控制力,Hadoop框架提供了多种高级组件。本节将逐一介绍Combiner、Partitioner、压缩和计数器,解释它们的工作原理及适用场景。
1. Combiner:本地聚合优化 ⚙️
上一节我们介绍了Map和Reduce的基本流程。本节中我们来看看如何优化Map阶段的输出,以减少网络传输开销。Combiner扮演着关键角色。
Combiner是一种运行在Map任务节点本地的“迷你Reducer”。它在Map输出被发送到Reducer之前,先在本地对相同键的数据进行聚合。这能显著减少需要跨网络传输的数据量。
以下是Combiner的核心概念:
- 作用:在Map端执行局部聚合,减少Map输出到Reduce端的网络I/O。
- 工作流程:
Map -> Combiner (Local) -> Partition & Shuffle -> Reduce - 约束:Combiner的输入/输出键值类型必须与Reducer一致,且其操作必须是可结合、可交换的(例如求和、求最大值)。求平均值则不适用。
2. Partitioner:控制数据分发 🗂️
了解了如何优化数据量后,我们来看看如何控制这些数据的流向。Partitioner决定了Map输出的每条记录将被发送给哪个Reduce任务。
默认的Partitioner是HashPartitioner,它根据键的哈希值对Reducer数量取模来分配。通过自定义Partitioner,我们可以实现更符合业务逻辑的数据分发,例如确保某个范围的所有数据都由同一个Reducer处理,这对于需要全局状态的运算至关重要。
以下是Partitioner的核心要点:
- 默认行为:
partition = (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks - 自定义目的:实现数据倾斜的优化,或满足特定业务逻辑的分组需求。
- 关键方法:需要重写
getPartition方法,返回目标Reducer的索引号。
3. 压缩:提升存储与传输效率 📦
在HDFS中应用压缩可以节省存储空间。在MapReduce中,压缩同样能带来性能提升,尤其是在Shuffle阶段,减少数据读写量。
MapReduce支持在Map输出、Reduce输出等多个阶段启用压缩。选择合适的压缩编解码器(如Snappy、Gzip)需要在压缩比和解压速度之间取得平衡。
配置压缩通常很简单,以下是一个启用Map输出压缩的示例代码:
<!-- 在 mapred-site.xml 中配置 -->
<property>
<name>mapreduce.map.output.compress</name>
<value>true</value>
</property>
<property>
<name>mapreduce.map.output.compress.codec</name>
<value>org.apache.hadoop.io.compress.SnappyCodec</value>
</property>
4. 计数器:收集作业统计信息 📊
最后,我们来学习如何监控和收集作业运行时的详细统计信息。计数器(Counter)为此提供了强大支持。
Hadoop内置了多种计数器(如FileSystem计数器、Map-Reduce框架计数器),用于统计读取的字节数、处理的记录数等。我们也可以定义自定义计数器,来跟踪业务相关的指标,例如统计无效记录的数量或特定事件的发生次数。
在代码中,可以通过上下文(Context)对象来递增计数器:
context.getCounter("MyGroup", "INVALID_RECORD_COUNT").increment(1);
总结 🎯

本节课我们一起学习了MapReduce的四个中级编程概念:
- Combiner用于在Map端进行本地聚合,减少网络传输。
- Partitioner让我们能够精细控制Map输出到哪个Reducer,优化负载均衡。
- 压缩技术应用于MapReduce流程,有效节省磁盘和网络带宽。
- 计数器是强大的诊断工具,既能利用内置计数器监控系统,也能创建自定义计数器跟踪业务指标。

掌握这些组件,将使你能够编写出更高效、更可控的MapReduce程序,以应对更复杂的大数据处理任务。
053:组合器应用 🧩


在本节课中,我们将学习MapReduce框架中的一个重要组件——组合器。我们将了解组合器的概念、如何实现它、如何为作业设置组合器,以及组合器能为MapReduce作业带来哪些性能上的好处。
什么是组合器?
上一节我们回顾了MapReduce的基本流程,本节中我们来看看组合器在其中扮演的角色。
组合器是MapReduce框架的一部分,它在Map阶段被调用。组合器本质上是一个函数,用于在数据发送给Reducer之前,对单个Mapper的输出进行局部的聚合处理。
MapReduce流程与组合器
为了更好地理解组合器,让我们仔细审视一下MapReduce的数据流。
以下是MapReduce流程的简化视图:

假设输入被分割成四个部分:
- 输入分片1
- 输入分片2
- 输入分片3
- 输入分片4


每个分片由一个Mapper处理:
- Mapper1 处理 输入分片1
- Mapper2 处理 输入分片2
- Mapper3 处理 输入分片3
- Mapper4 处理 输入分片4
每个Mapper处理其输入记录并生成键值对输出。假设输出如下:
- Mapper1 输出:
A, 1和B, 2 - Mapper2 输出:
C, 3和C, 6 - Mapper3 输出:
A, 5和C, 2 - Mapper4 输出:
B, 7和C, 8
在Shuffle和Sort阶段,这些键值对会根据键被发送到对应的Reducer。假设有三个Reducer:
- Reducer1 处理键
A - Reducer2 处理键
B - Reducer3 处理键
C
因此,Reducer接收到的输入是:
- Reducer1:
A, [1, 5] - Reducer2:
B, [2, 7] - Reducer3:
C, [3, 6, 2, 8]
然而,请注意Reducer3实际看到的是 C, [9, 2, 8],而不是 C, [3, 6, 2, 8]。这是因为 C, 3 和 C, 6 在到达Reducer之前被合并成了 C, 9。
这个合并操作就是由组合器完成的。在上图中,每个Mapper都关联了一个组合器。Mapper的输出会先发送给组合器进行处理,组合器的输出才会进入Shuffle和Sort阶段。对于Mapper2,其输出 C, 3 和 C, 6 被组合器合并为 C, 9,然后才发送给Reducer3。
组合器的形式化定义与优势
现在,让我们更正式地定义组合器并探讨其优势。
组合器在Mapper之后被调用。实际上,每个Mapper都可以关联一个组合器。你需要在配置作业时显式地设置组合器类。
组合器处理单个Mapper的输出(每个Mapper处理一个分片)。Mapper处理分片并生成输出,该输出会传递给组合器。组合器在Reducer阶段之前运行,它产生的输出将作为Reducer阶段的输入。
这意味着:
- 组合器的输入类型与Reducer的输入类型相同。
- 组合器的输出类型与Mapper的输出类型相同。
组合器是重要的优化手段,主要带来两大好处:
- 减少Map输出IO:Mapper的输出通常需要写入磁盘。组合器将多个输出记录合并为更少的记录,从而减少了写入磁盘的数据量。
- 减少Shuffle网络IO:经过Shuffle阶段,数据需要通过网络发送到正确的Reducer。更少的输出记录意味着需要传输的网络数据量也更少。
组合器的内部机制与注意事项
了解组合器的内部工作机制有助于理解其一些限制。
Mapper的输出被序列化后,首先放入一个内存缓冲区,而不是直接写入磁盘。当这个缓冲区被填满时,框架会对缓冲区内的数据进行排序,然后,如果为该MapReduce作业指定了组合器,则调用组合器处理这部分数据。组合器的输出才会被写出。
这个过程的关键点在于:
- 组合器可能被调用多次(针对缓冲区每次满的数据),而不是一次性处理Mapper的所有输出。
- 框架可能会判断在某些情况下运行组合器并不高效,从而选择不运行它。
- 即使使用了组合器,Mapper的最终输出中,同一个键仍可能出现多次(因为组合器是分批次处理缓冲区的)。例如,一个Mapper可能最终输出
C, 9和C, 16两个记录,它们会在Reducer端再次被合并。


因此,组合器并不能保证每个键在Map端只输出一个记录,它只是进行局部聚合以减少数据传输量。
如何实现组合器?
实现一个组合器非常简单。你需要编写一个类,它继承自 Reducer 类。
重要提示:MapReduce框架中没有专门的 Combiner 类,组合器本身就是一种特殊的Reducer。
在Reducer类的 reduce 方法中,你需要编写组合器的业务逻辑。
关于组合器的输入输出类型,记住这个核心关系:
- 组合器的输入 = Reducer的输入 =
(K2, List<V2>) - 组合器的输出 = Mapper的输出 =
(K2, V2)
以下是为词频统计(Word Count)问题编写的一个组合器示例:
public class WordCountCombiner extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable count = new IntWritable();
@Override
public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int i = 0;
// 对传入的列表中的所有值进行求和
for (IntWritable val : values) {
i += val.get();
}
count.set(i);
// 输出合并后的键值对,例如 ("apple", 3)
context.write(key, count);
}
}
在这个 reduce 方法中,我们接收一个键(单词)和一个值的列表(通常是数字1),然后将这些值相加,得到该单词在当前Mapper输出中的局部计数,并输出这个结果。

如何为作业设置组合器?
为作业设置组合器非常直接。你只需要在作业配置中调用 setCombinerClass 方法。
Job job = Job.getInstance(conf, "Word Count Using Combiner");
...
job.setMapperClass(WordCountMapper.class);
job.setCombinerClass(WordCountCombiner.class); // 设置组合器
job.setReducerClass(WordCountReducer.class);
...

使用Reducer作为组合器
很多时候,你可以直接使用Reducer类作为组合器。但这有一个重要的前提条件:Reducer执行的运算必须是可交换的和可结合的。
- 可结合:
(x + y) + z = x + (y + z) - 可交换:
x + y = y + x
词频统计中的加法运算满足这两个条件,因此我们可以将同一个Reducer类同时设置为Reducer和Combiner。
但是,我们之前编写的简单Reducer(只是遍历值列表并+1)在作为组合器时会有问题,因为组合器接收到的值可能已经是局部和(如 [3, 6]),而不是一堆 [1, 1, 1...]。因此,一个健壮的、可兼作组合器的Reducer应该像下面这样实现:
public class WordCountCombinerReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable count = new IntWritable();
@Override
public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
// 正确做法:累加传入的每个值
for (IntWritable val : values) {
sum += val.get(); // 这里获取的是值,可能是1,也可能是局部和如3
}
count.set(sum);
context.write(key, count);
}
}
然后,在作业配置中:
job.setMapperClass(WordCountMapper.class);
job.setCombinerClass(WordCountCombinerReducer.class); // 使用同一个类
job.setReducerClass(WordCountCombinerReducer.class); // 使用同一个类
注意:对于求平均值这类运算,因为不满足结合律,通常不能直接用Reducer作为组合器,需要特殊处理。
组合器效果演示 🎯
为了直观展示组合器的效果,我们运行两个词频统计作业进行对比:
- 使用组合器
- 不使用组合器

两个作业处理相同的输入(10个文本文件)。
在作业历史服务器的计数器页面,我们关注 “Reduce input records” 这个指标:
- 不使用组合器的作业:Reducer接收了约 2,151,190 条记录。
- 使用组合器的作业:Reducer仅接收了约 226,000 条记录。
使用组合器后,Reducer需要处理的记录数减少了约90%!这显著降低了磁盘I/O和网络传输开销,极大地提升了作业效率。
总结 📚
在本节课中,我们一起学习了MapReduce框架中的组合器。
- 组合器是一个在Map阶段被调用的函数,用于对单个Mapper的输出进行局部聚合。
- 组合器处理Mapper的输出,其输入类型与Reducer相同,输出类型与Mapper相同。
- 组合器通过减少Map端的输出数据量,显著优化了磁盘I/O和网络传输效率。
- 实现组合器就是编写一个继承自
Reducer的类。 - 如果一个Reducer的运算逻辑满足可结合性和可交换性(如加法),那么它可以同时用作Combiner和Reducer。
- 实际演示表明,组合器能大幅减少传输到Reducer的数据量,从而提升整体作业性能。

理解并合理使用组合器,是编写高效MapReduce程序的关键一步。
054:分区器实现原理 🧩


在本节课中,我们将学习MapReduce框架中的分区器。我们将了解分区器的作用、默认分区器的工作原理,以及如何编写和设置自定义分区器。
概述
分区器是MapReduce框架中的一个关键组件,它决定了每个Mapper输出的键值对应该被发送到哪个Reducer进行处理。理解分区器对于控制数据在Reducer间的分布至关重要。
MapReduce流程回顾
上一节我们介绍了MapReduce的整体流程,本节中我们来看看分区器是如何介入这个流程的。
输入数据首先被分割成多个分片。每个分片由一个Mapper任务处理。Mapper处理数据后产生输出,如果作业配置了Combiner,输出会先经过Combiner进行本地聚合。之后,无论是Mapper的直接输出还是Combiner的输出,都需要经过分区器。
分区器的作用是:为每一个输出的键值对计算一个分区号(即Reducer编号),从而确保具有相同键的所有记录最终都会被发送到同一个Reducer。
什么是分区器?
分区器运行在Map输出阶段(或Combiner输出阶段)。它接收一个键值对(类型为K2, V2)以及作业设置的Reducer总数作为输入,并输出一个整数,这个整数代表该键值对应被发送到的Reducer编号(从0开始)。
其核心功能可以用以下伪代码描述:
分区号 = 分区逻辑(键, 值, Reducer总数)
默认分区器:HashPartitioner
在之前的例子中,我们并没有显式设置分区器,但作业依然能正常运行。这是因为框架提供了一个默认的分区器:HashPartitioner。
HashPartitioner的工作原理基于键的哈希码。其分区逻辑的公式如下:
分区号 = (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks
这个公式确保:
- 通过
& Integer.MAX_VALUE操作,将哈希码转换为一个非负整数。 - 通过
% numReduceTasks(取模)操作,将结果均匀地映射到所有可用的Reducer上。
例如,假设一个键的 hashCode() 返回17,且 numReduceTasks 为5,那么计算过程为:17 % 5 = 2,该键值对将被发送到编号为2的Reducer。
自定义分区器
如果默认的哈希分区策略不能满足业务需求(例如,需要根据键的特定范围或属性进行分区),我们可以编写自定义分区器。
以下是实现自定义分区器的步骤:
- 创建类:编写一个类,继承
org.apache.hadoop.mapreduce.Partitioner类。 - 重写方法:重写
getPartition方法,在其中实现自定义的分区逻辑。 - 设置分区器:在作业驱动程序中,使用
Job.setPartitionerClass方法将自定义的分区器类设置给作业。

自定义分区器示例:按字母范围分区

假设我们有一个词频统计作业,并希望将所有首字母小于或等于‘M’的单词发送到Reducer 0,其余单词发送到Reducer 1。我们可以编写如下分区器:
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
public class WordCountPartitioner extends Partitioner<Text, IntWritable> {
@Override
public int getPartition(Text key, IntWritable value, int numReduceTasks) {
// 将键(单词)转换为字符串并获取首字母
String word = key.toString().toUpperCase();
char firstLetter = word.charAt(0);
// 自定义分区逻辑:按字母‘M’划分
if (firstLetter <= 'M') {
return 0; // 发送到Reducer 0
} else {
return 1; // 发送到Reducer 1
}
// 注意:此示例假设numReduceTasks为2。实际应用中应做更健壮的检查。
}
}
在驱动程序中的设置方法如下:
Job job = Job.getInstance(conf, "Word Count with Custom Partitioner");
// ... 其他设置(如Mapper、Reducer类等)
job.setPartitionerClass(WordCountPartitioner.class); // 设置自定义分区器
job.setNumReduceTasks(2); // 必须设置Reducer数量,且与分区逻辑匹配
运行此作业后,输出目录中会生成两个结果文件(part-r-00000 和 part-r-00001),分别包含了首字母为A-M和N-Z的单词统计结果。
总结
本节课中我们一起学习了MapReduce分区器的核心概念。

我们了解到,分区器负责在Shuffle阶段前,为Map或Combiner的每个输出键值对分配一个目标Reducer编号。框架默认使用 HashPartitioner,它根据键的哈希值进行均匀分配。当默认策略不满足需求时,我们可以通过继承 Partitioner 类并重写 getPartition 方法来实现自定义的分区逻辑,从而精确控制数据的流向。

掌握分区器是进行高效、定制化大数据处理的关键一步。
055:MapReduce压缩技术集成 🗜️



在本节课中,我们将学习如何在MapReduce作业中应用压缩技术。压缩对于处理海量数据的MapReduce至关重要,它能有效提升性能。我们将探讨输入压缩、输出压缩以及中间Map和Reduce阶段的压缩。
概述
MapReduce压缩建立在Hadoop核心模块(Common和HDFS)的压缩功能之上。它主要在三种场景下发挥作用:作业输入压缩、作业输出压缩以及中间Mapper和Reducer的压缩。理解如何配置这些压缩选项,对于优化大数据处理作业的性能和资源利用率非常关键。
压缩在MapReduce中的重要性
MapReduce旨在处理大量数据,因此压缩技术至关重要。MapReduce的压缩功能基于Hadoop核心模块构建,这意味着它依赖于核心Common模块的压缩功能以及支持文件压缩的Hadoop HDFS模块。
MapReduce压缩的应用场景
MapReduce压缩在Hadoop中主要应用于以下三种情况:
以下是三种主要的压缩应用场景:
- 作业输入压缩:MapReduce作业的输入文件可以是压缩文件。
- 作业输出压缩:MapReduce作业运行后产生的输出文件(通常是
part-r-00000等)也可以被压缩。 - 中间Mapper和Reducer压缩:Mapper运行时生成的输出会写入中间临时文件,这些文件在Shuffle和Sort阶段被处理,压缩这些文件可以提升性能。
配置作业输入压缩
作业输入压缩由一个名为 io.compression.codecs 的属性管理。默认情况下,该属性在 core-site.xml 中定义,其值是一个逗号分隔的列表,包含了Hadoop支持的所有编解码器。
查看默认属性值,你会发现它支持所谓的默认编解码器、Gzip编解码器、Bzip2编解码器以及Snappy编解码器。因此,默认情况下,作业输入压缩可以自动处理这四种格式中的任何一种。
核心概念:只要输入文件是这四种格式之一,输入压缩就会自动生效。你唯一需要修改 io.compression.codecs 属性的情况是,当你决定使用Hadoop默认不支持的压缩格式(例如LZ4压缩)时。这时,你需要引入LZ4压缩的JAR文件,并将编解码器类添加到该属性的逗号分隔列表中。
配置作业输出压缩
你可以通过以下两种方式之一来设置作业输出压缩:
第一种方式是通过作业配置对象设置属性。你可以设置一个布尔属性 mapreduce.output.fileoutputformat.compress 为 true,这表示你希望作业输出被压缩。你还需要设置另一个字符串属性 mapreduce.output.fileoutputformat.compress.codec,为其指定用于压缩和解压缩作业输出的编解码器类。
第二种方式是调用输出格式类(OutputFormat)上的方法。首先调用 setCompressOutput 静态方法,传入作业对象并将布尔参数 compressed 设为 true。接着,调用 setOutputCompressorClass 静态方法,传入作业对象和压缩编解码器类。调用这两个方法后,你的作业输出将被压缩,并使用你指定的编解码器。

配置中间Mapper和Reducer压缩
Mapper的输出被发送到文件,这些文件在传递给Reducer之前会经过Shuffle和Sort处理。你可以对这些中间Map输出文件应用压缩,以节省网络传输时间。
设置Mapper输出压缩的方法是:在配置对象中设置布尔属性 mapreduce.map.output.compress 为 true。你还可以通过设置字符串属性 mapreduce.map.output.compress.codec 来指定压缩编解码器,为其传入完全限定的压缩编解码器类的字符串表示形式。
实践示例:使用压缩的词频统计程序

下面是一个名为“使用压缩的词频统计”的程序示例,它将展示如何配置作业输入压缩、作业输出压缩以及中间Mapper和Reducer压缩。
对于作业输入压缩,实际上通常不需要做任何额外操作,因为默认情况下,只要输入文件是支持的压缩格式,作业输入压缩就会自动启用。只有在使用像LZ4这样非Hadoop框架内置的编解码器时,才需要显式设置。

作业输出压缩可以通过之前提到的两种方式实现。在下面的代码片段中,我们通过配置对象来设置:
// 启用作业输出压缩
conf.setBoolean("mapreduce.output.fileoutputformat.compress", true);
// 设置输出压缩编解码器为GzipCodec
conf.set("mapreduce.output.fileoutputformat.compress.codec", "org.apache.hadoop.io.compress.GzipCodec");

你还可以设置中间Mapper和Reducer压缩:
// 启用Mapper输出压缩
conf.setBoolean("mapreduce.map.output.compress", true);
// 设置中间文件压缩编解码器为GzipCodec
conf.set("mapreduce.map.output.compress.codec", "org.apache.hadoop.io.compress.GzipCodec");
你可以为应用程序的不同方面使用不同的编解码器,但在此示例中,为了简单起见,我们全部使用了Gzip编解码器。
压缩与输入分片
由于压缩的流式特性,压缩文件通常无法被分割。这意味着如果一个大型压缩文件不能被分割,就会失去并行处理的能力,因为整个文件只能由一个Mapper处理。
如果你希望在压缩的同时保持并行性,有几种选择:
以下是两种支持分片的压缩方案:
- 块压缩序列文件:我们之前讨论过序列文件,其中一种类型是块压缩序列文件。在这种格式中,数据被写入块中,每个块单独压缩,并使用同步标记分隔。这样,文件就可以被分割成不同的部分,因为每个块是独立压缩的。
- 可分割的LZO压缩:LZO压缩由于其许可证限制,不能作为Hadoop的一部分分发。但你可以自行下载安装。LZO压缩提供了“可分割的LZO压缩”功能,它通过为每个文件生成LZO索引,来记录每个块的起始和结束位置,从而实现文件的分割。

总结
在本节课中,我们一起学习了如何将压缩技术应用到MapReduce作业中。
我们了解了应用压缩是多么容易:可以应用输入压缩、输出压缩和中间压缩。实际上,对于输入压缩,你通常无需做任何事,因为它已经内置支持。对于输出压缩,你只需将布尔属性设置为 true,并设置输出编解码器类。你还可以进行中间Mapper和Reducer压缩,这样Mapper的输出会被压缩,而Reducer输入知道如何解压它,这减少了MapReduce程序占用的带宽和空间。

然而,在考虑压缩时,必须权衡空间和时间开销。总的来说,在Hadoop MapReduce编程中,应用压缩通常是一个很好的主意,但在决定之前,应根据具体的应用场景和运行频率进行考量。
056:计数器机制 📊

概述
在本节课中,我们将要学习Hadoop MapReduce中的计数器机制。计数器是一种用于为MapReduce作业收集统计信息的工具。我们将了解什么是计数器,如何创建和设置计数器值,以及如何通过编程方式和历史服务器用户界面读取计数器值。最后,我们还将学习内置计数器组,并学习如何创建用户自定义的计数器。
什么是计数器?
计数器,简单来说,是用于维护和汇总作业计数的工具。它们用于记录作业的度量指标。这里的“作业度量指标”指的是收集关于作业的统计数据,例如,你可能想知道作业读取了多少字节、输出了多少字节、处理了多少条记录。计数器也可用于质量控制,例如确认作业是否处理了1.2亿条记录;或用于诊断,例如跟踪作业中是否出现了任何错误。
内置计数器
Hadoop框架提供了多种内置的度量指标作为计数器。例如,你可以跟踪创建的Mapper和Reducer的总数,可以跟踪作业处理的字节数和作业生成的输出字节数。你还可以创建自己的计数器,例如跟踪作业消耗的唯一记录数,或者跟踪作业中某些特定的应用程序错误或警告的数量。
计数器被划分为组,每个计数器实际上都属于一个组。系统提供了多个内置的计数器组。以下是主要的几个内置计数器组:
- 作业计数器:记录启动的MapReduce任务数量。
- 文件系统计数器:跟踪读取或写入的字节数。
- MapReduce框架计数器:跟踪Mapper、Reducer和Combiner的输入/输出记录数,以及它们在不同处理阶段所花费的时间。
- Shuffle错误计数器:与数据Shuffle和排序相关的错误数量。
- 文件输入格式计数器:读取的字节数。
- 文件输出格式计数器:写入的字节数。
通过历史服务器查看计数器
历史服务器的Web图形用户界面会展示这些计数器。让我们登录到一个Hadoop服务器虚拟机并查看计数器。
在历史服务器界面中,选择一个作业(例如“使用压缩的单词计数”作业),点击进入作业详情。在左侧,你会看到一个名为“计数器”的选项。点击“计数器”,你将看到所有内置计数器的列表。界面会显示计数器组以及与该组关联的具体计数器,例如文件系统计数器、作业计数器、MapReduce框架计数器等。在每个组下,你可以看到具体的计数器(如读取的字节数)及其对应的值。
创建用户自定义计数器
如果内置计数器不能满足你的应用程序需求,你可以创建自己的、特定于应用程序的计数器。使用这些自定义计数器,你可以在Mapper、Combiner和Reducer类中递增它们。框架会准确汇总各个阶段的所有计数并生成总计。例如,如果你有15个Mapper,并且在Mapper阶段添加了这些计数器,框架会确保将所有不同的Mapper计数器聚合在一起,以便在作业历史服务器中显示最终总数,或者你也可以通过API访问它。


如何实现用户自定义计数器?
以下是实现用户自定义计数器的步骤。
首先,你需要定义一个计数器。推荐使用枚举类型来定义,这是一种类型安全的方法。定义计数器后,你可以从上下文对象中检索该计数器,然后递增其值。这个过程相当直接。
定义计数器
定义计数器的方法是编写一个枚举类。枚举类的名称成为计数器组的名称,而枚举的状态或值则成为单个计数器的名称。
例如,定义一个名为 MyCounter 的枚举,其中包含一个状态 UNIQUE_WORDS。这样,MyCounter 就是组名,UNIQUE_WORDS 就是计数器名。我们的目标是统计作业处理的唯一单词数量(适用于单词计数应用程序)。
public enum MyCounter {
UNIQUE_WORDS
}
检索并递增计数器
为了检索计数器以便递增其值,你可以利用在 map 和 reduce 方法中作为参数传递的上下文对象。有两种方法可以从上下文对象中检索计数器。
方法一:使用字符串名称
这种方法涉及调用上下文对象的 getCounter 方法,并传入组名和计数器名的字符串。这种方法非常动态,你甚至可以即时创建新的计数器(即使没有预先定义枚举)。但这也容易出错,因为拼写错误可能会意外创建新的计数器。
方法二:使用枚举(类型安全)
这种方法同样调用上下文对象的 getCounter 方法,但直接传入枚举实例。例如,context.getCounter(MyCounter.UNIQUE_WORDS)。这种方法是类型安全的,可以避免拼写错误。
一旦你获得了计数器对象,就可以递增或设置其值。你可以调用计数器对象的 increment 方法(例如 increment(1))来增加计数,或者调用 setValue 方法来设置一个特定的长整型数值。
示例:在Reducer中使用计数器
以下是一个在Reducer中实现计数器的示例程序,展示了两种技术。
技术一(使用字符串):
public class WordCountReducerWithCounterTech1 extends Reducer<Text, IntWritable, Text, IntWritable> {
public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
// ... 正常的业务处理逻辑 ...
// 检索并递增计数器
Counter counter = context.getCounter("MyCounter", "UNIQUE_WORDS");
counter.increment(1);
}
}

技术二(使用枚举,类型安全):
public class WordCountReducerWithCounterTech2 extends Reducer<Text, IntWritable, Text, IntWritable> {
public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
// ... 正常的业务处理逻辑 ...
// 检索并递增计数器(类型安全)
Counter counter = context.getCounter(MyCounter.UNIQUE_WORDS);
counter.increment(1);
}
}
作业完成后检索和打印计数器
作业完成后,你可以通过Java API检索计数器,例如将其打印出来或存储到数据库中。使用作业对象的 getCounters 方法可以获取一个包含所有计数器的 Counters 对象,然后遍历并打印它们。

以下是一个示例程序片段,展示了如何在作业完成后检索并打印计数器:
// 提交并等待作业完成
Job wordCountJob = ... // 作业配置
boolean success = wordCountJob.waitForCompletion(true);
if (success) {
// 获取所有计数器
Counters counters = wordCountJob.getCounters();
// 遍历所有计数器组
for (CounterGroup group : counters) {
System.out.println("计数器组: " + group.getDisplayName());
// 遍历组内的每个计数器
for (Counter counter : group) {
System.out.println(" " + counter.getDisplayName() + ": " + counter.getValue());
}
}
}


演示:运行带计数器的作业
在Hadoop服务器虚拟机上,我们可以运行一个集成了计数器的单词计数作业。假设我们使用技术一的Reducer,它会在处理每个唯一单词时递增 MyCounter.UNIQUE_WORDS 计数器。

作业运行后,我们可以在历史服务器Web界面查看结果。在作业的“计数器”部分,我们会找到自定义的计数器组“MyCounter”,其下有一个计数器“UNIQUE_WORDS”,其值表示在输入文件中发现的唯一单词总数(例如22602个)。
总结
在本节课中,我们一起学习了Hadoop MapReduce中的计数器机制。我们了解到计数器是一种用于为MapReduce作业收集统计信息的工具。Hadoop框架本身提供了多个内置的计数器组,用于跟踪作业、文件系统、框架等方面的指标。当这些内置计数器无法满足需求时,我们可以创建用户自定义的计数器。

在访问和递增计数器方面,我们学习了两种主要技术:一种是使用字符串名称的动态方法,另一种是使用枚举的类型安全方法。计数器信息既可以通过作业历史服务器的图形界面查看,也可以通过编程方式获取。总而言之,计数器是检测和收集作业信息的强大工具。
057:Hadoop大数据处理 - P57 🧠
第17讲:MapReduce中级编程总结

在本节课中,我们将对MapReduce中级编程的几个核心概念进行总结。我们将回顾Combiner、Partitioner、压缩以及计数器的作用与实现方式,帮助你巩固对MapReduce框架优化和自定义功能的理解。
Combiner:Map阶段的“迷你Reducer” 🔄
上一节我们介绍了MapReduce的基本流程,本节中我们来看看如何优化Map阶段的输出。Combiner是一种在Map阶段执行的特殊函数。
Combiner通过扩展Reducer类并设置为Job的一部分来启用,因此常被称为“迷你Reducer”。它是一种优化机制,主要优化IO操作。
Combiner的作用体现在以下两个方面:
- 它减少了Map输出的数据量,因为数据在写入本地磁盘前会先在Map端进行局部聚合。
- 它减轻了Sort和Shuffle阶段的负担,因为需要排序和跨网络传输的数据变少了。
从网络IO和磁盘IO的角度看,这提升了效率。同时,由于部分Reducer的逻辑在Map阶段就已执行,这也优化了分布式处理的性能。
Partitioner:控制数据流向 🗂️
理解了如何优化Map输出后,我们接下来看看如何控制这些输出的去向。Partitioner允许你控制Map输出数据由哪个Reducer处理。
编写Partitioner需要扩展Partitioner类并重写getPartition方法。
MapReduce提供了一个默认的Partitioner,它使用哈希码(hash code)将数据分配到不同的Reducer。以下是其逻辑的简化表示:
// 默认Partitioner的核心逻辑示意
int partition = (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
如果你需要自定义数据分发逻辑,可以编写自己的Partitioner,从而将特定的数据发送到指定的Reducer。

压缩:提升存储与传输效率 📦
在HDFS部分我们学习过压缩,现在我们将了解如何在MapReduce作业中应用压缩。压缩可以显著减少数据存储空间和网络传输开销。
MapReduce作业处理压缩文件非常灵活:
- 输入压缩:MapReduce作业可以直接读取压缩格式的文件作为输入,无需额外配置。
- 输出压缩:作业也可以生成压缩格式的输出文件。
- 中间文件压缩:Map输出在写入本地磁盘后,会经过Sort和Shuffle阶段传输到Reducer端。这个中间的Map输出文件同样可以进行压缩,以节省本地磁盘空间和网络带宽。
计数器:收集作业统计信息 📊
最后,我们来学习如何监控作业的运行状态。计数器用于收集作业的统计信息。
MapReduce提供了多种内置计数器。此外,你也可以为应用程序编写用户自定义的计数器。
创建计数器主要有两种方式:
- 使用枚举(Enum),这种方式具有强类型检查的优点。
- 使用简单的字符串作为计数器名称,然后递增其值。
总结 📝
本节课中我们一起学习了MapReduce中级编程的四个关键组件:
- Combiner:在Map端进行预聚合,优化IO和网络传输。
- Partitioner:自定义数据分发逻辑,控制Map输出由哪个Reducer处理。
- 压缩:在输入、输出及中间阶段应用压缩,节省存储和带宽。
- 计数器:通过内置或自定义计数器来收集和监控作业的运行时统计信息。
掌握这些概念,将使你能够编写出更高效、更可控的MapReduce程序。
058:MapReduce高级编程导论 🚀
在本节课中,我们将学习MapReduce高级编程的核心概念与最佳实践。我们将探讨多线程映射器、输入输出格式的内部机制、推测执行、本地作业调试以及一系列开发中的反模式与对应策略。

多线程映射器 🧵
上一节我们介绍了本模块的概览,本节中我们来看看如何编写多线程映射器以提升I/O密集型作业的效率。
在MapReduce中,映射器(Mapper)默认是单线程运行的。然而,对于涉及大量I/O操作(如读取多个小文件)的任务,使用多线程可以显著提升处理速度。通过创建多个线程并行处理输入分片,我们可以更充分地利用计算资源。
以下是实现多线程映射器的核心代码结构:
public class MultiThreadedMapper extends Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
private ExecutorService executor;
private List<Future> futures;
@Override
protected void setup(Context context) {
int numThreads = context.getConfiguration().getInt("mapreduce.mapper.multithreaded.threads", 10);
executor = Executors.newFixedThreadPool(numThreads);
futures = new ArrayList<>();
}
@Override
public void map(KEYIN key, VALUEIN value, Context context) {
Future future = executor.submit(new ProcessingTask(key, value, context));
futures.add(future);
}
@Override
protected void cleanup(Context context) {
for (Future future : futures) {
future.get(); // 等待所有线程完成
}
executor.shutdown();
}
}
通过上述方式,我们可以并发处理多个键值对,但需注意线程安全与上下文共享问题。
输入与输出格式内部机制 ⚙️
了解了多线程映射器后,我们进一步探讨输入格式(InputFormat)与输出格式(OutputFormat)的内部工作原理。
输入格式负责两件事:定义如何分割输入数据(生成InputSplit),以及如何读取分割后的数据(通过RecordReader)。例如,TextInputFormat将文件按行分割,每行作为一个记录。
输出格式则定义了如何写入作业的结果。常见的TextOutputFormat将键值对以制表符分隔写入文件。
以下是输入格式的关键方法:
public abstract class InputFormat<K, V> {
public abstract List<InputSplit> getSplits(JobContext context);
public abstract RecordReader<K, V> createRecordReader(InputSplit split, TaskAttemptContext context);
}
理解这些内部机制有助于我们自定义格式以处理非标准数据源。
推测执行概念 🔍
接下来,我们学习一个在Hadoop MapReduce中默认启用的有趣概念——推测执行(Speculative Execution)。
推测执行旨在解决集群中某些任务节点运行缓慢的问题。当某个任务(Map或Reduce)明显慢于同阶段的其他任务时,Hadoop会在另一个节点上启动相同的任务作为备份。最先完成的任务结果将被采用,另一个任务则被终止。
推测执行可以通过以下配置启用或禁用:
<property>
<name>mapreduce.map.speculative</name>
<value>true</value>
</property>
<property>
<name>mapreduce.reduce.speculative</name>
<value>true</value>
</property>
虽然推测执行能提高作业的整体完成时间,但在资源紧张或任务涉及外部竞争资源(如数据库写入)时,可能需要禁用它以避免重复计算或冲突。
本地作业运行与调试 🐛
为了提升开发效率,我们可以在本地运行MapReduce作业,而无需依赖完整的Hadoop集群(如ResourceManager、NodeManager或HistoryServer)。这对于调试和快速验证逻辑至关重要。
使用Hadoop的本地运行模式,只需在配置中设置:
Configuration conf = new Configuration();
conf.set("mapreduce.framework.name", "local");
conf.set("fs.defaultFS", "file:///");
然后,像在集群上一样提交作业。所有任务将在本地JVM中执行,日志直接输出到控制台,便于排查问题。
MapReduce反模式与最佳实践 📋
最后,我们总结一系列常见的MapReduce反模式及其对应的最佳实践,以帮助编写更高效、健壮的代码。
以下是需要避免的实践及改进建议:
- 避免在映射器或归约器中创建大量临时对象,这会增加GC开销。应重用对象或使用高效的数据结构。
- 不要忽略配置参数调优,如
mapreduce.task.io.sort.mb(排序内存)和mapreduce.reduce.shuffle.parallelcopies(并行复制数),它们对性能影响显著。 - 避免在单个作业中处理极度倾斜的数据。可通过预分区(Partitioning)或使用Combiner来减少数据倾斜的影响。
- 不要在任务中执行阻塞式I/O操作,这会导致任务超时。对于外部服务调用,应考虑异步方式或批量处理。
- 谨慎使用全局静态变量,因为在分布式环境中,它们可能引发不可预知的行为。应通过配置或上下文传递共享数据。
- 避免输出大量小文件,这会给HDFS和后续作业带来压力。可通过合并文件或使用特定输出格式(如
SequenceFileOutputFormat)来优化。 - 不要硬编码路径或配置。应使用配置管理工具或将参数通过命令行传递,以提高作业的灵活性。
- 忽略日志记录和监控会使调试变得困难。确保在关键步骤添加有意义的日志,并利用Hadoop的计数器(Counter)收集指标。
- 避免在归约阶段进行复杂计算,尽量将计算向映射阶段倾斜,以利用并行处理优势。
- 不要忘记设置合理的任务超时时间,特别是对于长时间运行的任务,防止因个别任务挂起导致整个作业停滞。
- 避免在作业中直接依赖外部系统的最新状态(如数据库表),这可能导致结果不一致。应考虑将所需数据快照导入HDFS再进行处理。
遵循这些最佳实践,可以显著提升MapReduce作业的性能、可维护性和可靠性。

本节课中我们一起学习了MapReduce高级编程的多个关键主题:通过多线程映射器提升I/O效率,深入理解了输入输出格式的内部机制,掌握了推测执行的适用场景,学会了在本地调试作业以加速开发周期,并回顾了十一个重要的反模式与最佳实践。掌握这些内容将帮助你设计出更高效、健壮的大数据处理作业。
059:MapReduce中的多线程优化技术 🧵

在本节课中,我们将学习如何在MapReduce框架中利用多线程技术来优化Mapper的性能。我们将探讨多线程适用的场景、其工作原理,并通过一个具体的例子来演示如何实现多线程Mapper。
概述
我们知道MapReduce支持多任务处理,即同一台机器上运行多个进程来完成一个作业,甚至支持多台机器协同工作的架构。但是,多线程呢?本节我们将探讨何时以及如何在MapReduce中利用多线程的优势。


为了理解多线程,我们首先需要了解默认情况下Mapper内部发生了什么。
默认的Mapper处理方式
默认情况下,Mapper是单线程的。每个Mapper在一个JVM中运行,并创建一个Mapper实例。输入分片中的记录会一次一条地传递给Mapper。

让我来解释一下。你之前见过这个图表:假设我们的输入被分解为四个输入分片(Input Split 1, 2, 3, 4)。每个分片对应一个Mapper(Mapper 1, 2, 3, 4)。我们知道这四个Mapper是并行工作的,但在一个Mapper内部,记录是按顺序处理的。这实际上是由一个叫做“记录读取器”的组件完成的。记录读取器负责一次将一条记录传递给Mapper,以便Mapper进行处理并生成输出。

多线程Mapper的理念
Mapper中多线程的理念是利用多线程同时处理多条记录。这通常不是默认做法,但在某些情况下,这种多线程可能非常合适。
当Mapper受限于I/O操作时,多线程Mapper就很有用。例如,如果Mapper的map方法需要获取网页(这比本地I/O有更高的延迟),或者需要调用某种网络服务(如IP地理位置服务)。每当Mapper需要等待某些处理或I/O操作时,多线程Mapper就能发挥作用。
在这种情况下,多线程是有益的,因为当一个线程因网络调用而阻塞时,其他线程可以继续工作。Hadoop MapReduce确实支持多线程Mapper。
Hadoop对多线程的支持
Hadoop框架提供了一个名为MultithreadedMapper的类,它扩展了标准的Mapper类。这个类提供了一个多线程的Mapper实现,它有一个专门的run方法来管理线程。
此外,框架通过确保记录读取器能够获取输入,并在Map任务中的可用线程之间循环分配记录,来支持多线程。
编写多线程Mapper的步骤
以下是编写多线程Mapper的步骤,稍后我们将通过一个词频统计的例子来演示其用途和实现方法。

要编写多线程Mapper,你需要确保编写一个线程安全的Map类。线程安全意味着实例变量需要同步,静态变量也应该同步。

你还需要将Mapper设置为多线程Mapper。在编写单线程Mapper(默认情况)时,你编写自己的Mapper类并将其设置为Mapper类。但在使用多线程Mapper时,你实际设置为Mapper类的是MultithreadedMapper类。那么,你编写的线程安全Map类如何设置呢?
你编写的线程安全Map类将通过调用MultithreadedMapper类的静态方法进行设置,将作业对象和你自己的Mapper类(即线程安全的Mapper类)传递给它。同时,你还需要调用MultithreadedMapper类的另一个静态方法setNumThreads,传入作业对象和你希望MultithreadedMapper处理的线程数量。
当我们通过示例讲解时,这会变得更加清晰。
示例:线程安全的词频统计Mapper
这里有一个名为WordCountThreadsafeMapper的类。我说过你需要编写一个线程安全的Mapper类。我所做的是,取用现有的Mapper类,并将其称为线程安全Mapper。我这样称呼它是因为我将在Mapper中使用多线程。当我说线程安全时,我需要确保如果使用了任何实例变量,我要么将它们转换为局部变量,要么对它们进行同步。我过去在类中有一个名为textWord的实例变量,但我已将其改为局部变量,这样我们就不必担心任何线程安全问题。
我之所以在这里使用多线程,真正的原因是:在map方法中,我实际上在进行一个外部调用(这里我模拟了一个外部调用)。在这个makeExternalCall方法中,我让线程睡眠大约10毫秒,以模拟一个I/O场景。我想说明的观点是:如果你编写的Mapper需要进行外部调用,并且这些调用在I/O上花费时间,那么你的Mapper处理速度将会非常慢。我将向你展示,在没有多线程Mapper的情况下,处理时间要比使用多线程Mapper长得多。
好的,这就是一个线程安全的词频统计Mapper。
示例:多线程主类
现在我有一个名为WordCountMultithreaded的主类,它包含main方法。我想再次向你展示:我有一个配置对象,一个新的作业对象,并完成所有设置。
最重要的是,当我设置Mapper类时,我没有设置我的业务逻辑Mapper类,而是设置了MultithreadedMapper类,这是Hadoop框架内置的一个类。
然后,我使用MultithreadedMapper类的静态方法来实际设置我的Mapper(这将是一个线程安全的Mapper),并设置线程数量。实际上,在这种情况下发生的是:当记录读取器读取输入分片时,它会启动多个线程。然后,它会为每一条记录调用map方法。所有这些不同的线程一次读取一条记录,并以多线程方式调用map方法。事实上,我设置了四个线程。
演示与对比
让我通过演示来对比两种方式的性能。我将运行两个版本的程序:一个是不使用多线程的版本,另一个是使用多线程的版本。
首先,我运行非多线程版本的词频统计程序。Mapper在每次调用map方法时都会等待10毫秒。对于一个需要处理大量记录的任务,这会累积成显著的延迟。使用time命令计时,这个程序运行了大约4分11秒。
接下来,我运行多线程版本的词频统计程序。这次,每个Mapper内部有四个线程同时处理记录。理论上,这应该使处理速度提高大约四倍。实际运行结果显示,该程序耗时大约1分15秒。
正如你所见,当Mapper执行某些外部调用或受I/O限制的活动时,多线程确实大有帮助。多线程是优化Mapper性能的一个极佳方式。
重要注意事项
我想提醒你的唯一注意事项是:没有“多线程Reducer”这个概念。只有Mapper可以被多线程化。

总结
在本节中,我们学习了MapReduce中的多线程技术。我们了解到可以拥有多线程Mapper。Hadoop框架通过提供一个名为MultithreadedMapper的特殊类来支持多线程,该类允许我们在一个Mapper内同时读取和处理多条记录。请记住,没有所谓的多线程Reducer,只有Mapper可以被多线程化。
060:课程 20: Hadoop 输入输出格式内部机制 🔧

在本节课中,我们将学习Hadoop MapReduce作业中输入格式和输出格式的内部工作机制。理解这些概念对于掌握数据如何被读取、分割、处理以及最终写入至关重要。
输入分片概念
上一节我们介绍了MapReduce的基本流程,本节中我们来看看数据输入的第一步:输入分片。
MapReduce作业可以从文件或数据库获取输入。在我们之前的演示和作业中,输入通常来自纯文本文件。输入也可以来自序列文件、Map文件或数据库(如关系型数据库或HBase)。
当处理一个输入文件时,文件由多行记录组成。输入分片是一组逻辑上连续的记录集合。例如,一个包含100万条记录的文件可以被划分为三个分片。每个Mapper实例可以独立处理一个输入分片,从而实现并行处理。
输入分片通过扩展Hadoop内置的InputSplit类实现。通常我们不需要直接处理分片,因为这是输入格式类的职责。
MapReduce API 中的格式类
理解了分片的概念后,我们来看看MapReduce API中负责管理它们的核心类。

以下是MapReduce API中与输入输出相关的关键类结构:
InputFormat:处理作业输入格式的基类。DBInputFormat:处理数据库输入。FileInputFormat:处理文件输入。TextInputFormat:处理文本文件输入。
OutputFormat:处理作业输出格式的基类。DBOutputFormat:输出到数据库。FileOutputFormat:输出到文件。TextOutputFormat:输出到文本文件。

InputFormat和OutputFormat都是抽象类,具体的派生类实现了其中的关键抽象方法。
输入格式详解
现在,让我们深入探讨InputFormat类是如何工作的。
InputFormat是一个高级类,它负责为作业读取数据制定规范。它的主要工作包括:
- 从输入文件或数据库创建输入分片,将工作分解成块。
- 通过提供
RecordReader类的实现,来指定如何读取每个分片。
查看Hadoop API源码,InputFormat抽象类定义了关键方法:
public abstract List<InputSplit> getSplits(JobContext context);
public abstract RecordReader<K,V> createRecordReader(InputSplit split, TaskAttemptContext context);
框架如何使用InputFormat:
- 作业运行时,框架调用具体
InputFormat(如TextInputFormat)的getSplits方法,生成输入分片列表。 - 为每个分片,框架调用
createRecordReader方法生成一个RecordReader。 - 每个
RecordReader被分配给一个独立的Mapper任务。 - Mapper通过其
RecordReader按顺序读取分片中的每一条记录(键值对),并调用map方法进行处理。
Hadoop框架内置了多种InputFormat实现:
TextInputFormat:最常用,默认格式。NLineInputFormat:指定每N行为一个分片。KeyValueTextInputFormat:键值由制表符分隔的文本文件。SequenceFileInputFormat:二进制序列文件。DBInputFormat:关系型数据库。TableInputFormat:HBase数据库。StreamInputFormat:Hadoop流作业。
你可以通过以下代码为作业配置输入格式:
job.setInputFormatClass(XXXInputFormat.class); // XXX可以是Text, NLine, KeyValueText等
文本输入格式的工作方式
由于TextInputFormat是最常用的格式,我们具体看看它如何处理文本文件。
假设有一个文本文件,行之间由换行符\n分隔。TextInputFormat基于HDFS的数据块来创建分片。
分片、记录、键值的确定:
- 分片:通常,一个HDFS数据块(如128MB)对应一个输入分片。
- 记录:一行文本就是一个记录,由换行符
\n界定。 - 键:
LongWritable类型,表示该行在文件中的起始字节偏移量。 - 值:
Text类型,即该行的文本内容。
处理跨块记录:
如果一个记录开始于一个数据块的末尾,并延续到下一个数据块的开头,TextInputFormat的分片机制会妥善处理:
- 第一个分片(包含记录开头)的
RecordReader会读取到下一个分片的开始位置,直到找到记录的结束符\n。 - 第二个分片的
RecordReader则会跳过已由第一个分片处理的部分,从下一个完整的记录开始读取。
所有这些复杂性都由TextInputFormat在内部处理完毕。
输出格式详解
了解了数据如何输入后,我们再来看看数据如何输出。OutputFormat是InputFormat的对应面,负责为作业写出数据制定规范。
OutputFormat的主要职责包括:
- 验证输出规范:例如,检查输出目录是否已存在(如果存在,默认会报错并终止作业)。
- 创建
RecordWriter:负责将Reducer(或Mapper)输出的键值对实际写入文件(即最终的part-r-00000等文件)。 - 创建
OutputCommitter:负责作业任务的设置和清理工作,例如创建输出目录、在作业失败时清理临时文件等。
查看OutputFormat接口,它定义了三个关键方法:
RecordWriter<K, V> getRecordWriter(TaskAttemptContext context);
void checkOutputSpecs(JobContext context);
OutputCommitter getOutputCommitter(TaskAttemptContext context);
Hadoop框架同样内置了多种OutputFormat实现:
TextOutputFormat:最常用,默认格式,输出为文本文件。DBOutputFormat:输出到关系型数据库。TableOutputFormat:输出到HBase。MapFileOutputFormat:输出为Map文件。SequenceFileOutputFormat:输出为序列文件。NullOutputFormat:不产生任何输出。
你可以通过以下代码配置作业的输出格式、键和值的类型:
job.setOutputFormatClass(XXXOutputFormat.class); // XXX可以是Text, DB, Table等
job.setOutputKeyClass(MyKeyClass.class);
job.setOutputValueClass(MyValueClass.class);
文本输出格式的工作方式
最后,我们聚焦于最常用的TextOutputFormat。
TextOutputFormat是OutputFormat的具体实现,用于将作业输出写入文本文件。
- 默认情况下,它使用制表符分隔键和值,格式为:
key\tvalue。 - 你可以通过配置属性
mapred.textoutputformat.separator来更改分隔符。 - 输出路径通过
FileOutputFormat.setOutputPath(Job job, Path outputDir)方法设置。
总结

本节课中,我们一起学习了Hadoop MapReduce作业中输入输出格式的内部机制。
我们了解到:
- 输入格式(
InputFormat)负责将输入数据划分为分片,并为每个分片提供记录读取器(RecordReader),使Mapper能顺序处理记录。 - 文本输入格式基于HDFS块创建分片,并智能处理跨块的记录。
- Hadoop提供了多种输入格式以适应不同数据源,如文本、序列文件、数据库等。
- 输出格式(
OutputFormat)负责验证输出、提供记录写入器(RecordWriter)来写出数据,并通过输出提交器(OutputCommitter)管理输出目录的创建与清理。 - 文本输出格式是最常用的输出方式,默认以制表符分隔键值。
希望本节内容能帮助你从I/O视角更深入地理解Hadoop MapReduce作业的内部工作原理。
061:推测执行策略 🚀



在本节课中,我们将要学习MapReduce框架中的“推测执行”策略。这是一种优化技术,旨在处理分布式计算环境中运行缓慢的任务,从而提升整体作业的执行效率。
概述
推测执行是MapReduce框架为应对任务执行缓慢而设计的一种机制。当一个作业被分解成大量任务并行执行时,个别任务的缓慢会拖慢整个作业的完成速度。推测执行通过启动这些慢任务的“备份”任务来尝试解决这个问题。
什么是推测执行?
为了理解推测执行,我们需要了解分布式计算框架如何处理运行缓慢的任务。
一个作业实际上被分解为许多小任务。这些任务可能是Map任务,也可能是Reduce任务。因此,一个作业的完成速度取决于其中最慢的任务。
考虑到一个作业可能被分解为成百上千个任务,并分布在多台机器上运行,某些任务运行缓慢的原因有很多:
- 硬件问题,例如磁盘或内存故障。
- I/O瓶颈。
- 机器上同时运行了其他进程或其他MapReduce任务,占用了CPU、I/O或内存资源。
- 机器本身的操作系统配置不佳。
框架可以通过在另一台机器上启动相同的任务来解决慢任务问题。在Hadoop HDFS中,文件会被复制到多台机器上。因此,当一个Map任务在处理某个数据块时,完全可以在拥有该数据块副本的另一台机器上启动一个相同的Map任务,然后比较两者的结果。这就是推测执行背后的核心思想。
正式定义:推测执行是指当MapReduce框架发现某些任务运行缓慢时,启动重复(或称“推测性”)任务的机制。

这是一种优化技术,而非功能性改进。需要明确的是,推测执行并非一开始就启动两个竞争任务让它们赛跑。它是在所有任务都已启动后,针对那些进度显著落后的特定任务,才启动其推测性副本。
推测执行何时触发?
推测执行在满足以下所有条件时触发:
- 作业的所有任务都已启动。
- 某个任务已运行了较长时间(通常超过一分钟)。
- 该任务与其他任务相比,进度明显落后。
例如,假设一个作业被分解为三个Map任务(M1, M2, M3),分别在三个主机(H1, H2, H3)上运行。
- M1的处理速度是每秒30条记录。
- M3的处理速度是每秒32条记录。
- M2的处理速度是每秒2条记录。
显然,M2运行缓慢。在这种情况下,Hadoop框架可能会启动另一个任务M4,让它处理与M2相同的数据集(利用HDFS的副本机制)。如果M4能以每秒25或30条记录的速度正常运行,框架就会终止缓慢的M2,并继续使用M4。这就是推测执行的工作方式。
如何配置推测执行?
推测执行可以通过配置开启或关闭。默认情况下,推测执行在配置中是开启的。
以下是通过配置文件 mapred-site.xml 进行控制的示例:
- 将属性
mapreduce.map.speculative设置为false,可以关闭Map任务的推测执行。 - 将属性
mapreduce.reduce.speculative设置为true,可以开启Reduce任务的推测执行。
因此,你可以在配置文件中分别控制Map级别和Reduce级别的推测执行。
请注意:推测执行不能替代编写低效的代码。如果系统或作业性能不佳,你需要深入排查根本原因,而不是简单地依赖开启推测执行。
通过API控制推测执行
你也可以通过编程方式控制推测执行。例如,通过Job对象进行设置:

// 为整个作业(Map和Reduce)设置推测执行
job.setSpeculativeExecution(true);
// 或者分别设置Map和Reduce任务的推测执行
job.setMapSpeculativeExecution(true);
job.setReduceSpeculativeExecution(false);
总结

本节课中,我们一起学习了MapReduce框架如何利用推测执行来处理因环境问题导致的慢任务。推测执行默认是开启的,但可以关闭。许多系统管理员会选择关闭它,以便及时发现系统性能问题,因为作业执行缓慢本身可能就是系统状态不佳的信号。推测执行既可以通过配置文件(mapred-site.xml)进行控制,也可以通过编程API进行设置,并且可以精细地控制仅对Map任务或仅对Reduce任务生效。
062:本地作业运行模式 🖥️

在本节课中,我们将学习如何在 Hadoop 客户端虚拟机中本地运行作业,而不是在 YARN 守护进程的控制下运行。这项技术在大数据程序开发和测试阶段非常有用。

概述
上一节我们介绍了在 YARN 集群上运行作业。本节中,我们来看看如何将 MapReduce 作业配置为在本地模式下运行,这对于开发和调试至关重要。
什么是本地作业运行模式?
可以本地运行一个 MapReduce 作业。这意味着整个 MapReduce 代码的执行都将在客户端进行。包含 main 方法的类,以及运行 main 方法的 JVM,MapReduce 代码也将在同一个虚拟机、同一个上下文中运行。
这项技术也被称为在本地模式或独立模式下运行 Hadoop MapReduce。
在这种情况下,无需运行所有守护进程,尤其是不需要运行 YARN 守护进程。不过,仍然需要运行 HDFS 守护进程。原因是你的 MapReduce 作业需要从 HDFS 读取数据以进行处理,并将结果写回 HDFS。但是,所有的 Mapper 和 Reducer 逻辑都将在客户端虚拟机内部运行。
这项技术极大地帮助了作业的本地开发和测试。
如何配置本地作业运行
有两种技术可以配置作业在本地运行。
以下是配置本地模式的第一种方法:
- 修改配置文件:进入 Hadoop 安装目录下的
etc/hadoop文件夹,找到一个名为mapred-site.xml的文件。在这个文件中,最初有一个配置项mapreduce.framework.name,其值被设置为yarn。你需要将其值从yarn改为local。完成此修改后,你就可以在本地模式下运行 Hadoop 了。
以下是配置本地模式的第二种方法:
- 在代码中覆盖配置:回想一下你使用的
Configuration对象。你可以在你的main方法中,通过设置属性mapreduce.framework.name的值为local来覆盖默认配置。这样做会覆盖mapred-site.xml文件中的任何值,同样可以达到目的。
你可以选择使用第一种或第二种方法。
本地模式的优缺点

上一节我们介绍了如何配置本地模式,本节中我们来分析一下这种模式的优缺点。
优点:
- 简化开发过程,尤其是在集成开发环境中。现在,你可以简单地使用调试器逐步执行代码,观察 MapReduce 程序中业务逻辑的运行情况以进行调试。
缺点:
- 某些功能,特别是一些 MapReduce 功能无法工作。例如,在本地模式下不能设置超过一个 Reducer。
- 存在一个称为分布式缓存的概念,它允许你动态添加 JAR 文件以便在 Mapper 或 Reducer 中运行,还可以传递一些参考文件。这类分布式缓存功能在本地模式下无法工作。
- 由于所有任务都在单台机器上运行,没有并行化发生,因此一些分布式计算相关的错误可能无法被发现。所以,它非常适合测试功能和调试逻辑,但不允许你调试分布式计算功能。
演示:本地运行作业
现在,让我们通过一个演示来看看如何实际操作。
首先,我们以正常模式(在 YARN 守护进程控制下)运行一个 WordCount 程序。我们使用 yarn jar 命令来提交作业。
yarn jar WordCount.jar /input /output
作业成功运行后,我们可以在 ResourceManager 和 JobHistory Server 上看到相应的作业记录(例如 job 38)。
接下来,我们切换到本地模式。我们打开 mapred-site.xml 文件,将 mapreduce.framework.name 的值从 yarn 改为 local。
<property>
<name>mapreduce.framework.name</name>
<value>local</value>
</property>
在再次运行程序之前,我们需要清理之前的输出目录。然后,我们再次运行相同的 yarn jar 命令。
hadoop fs -rm -r /output
yarn jar WordCount.jar /input /output
此时,程序将在本地运行。你会注意到,它没有与 ResourceManager 进行任何通信。所有操作都在客户端虚拟机内完成。检查 ResourceManager 和 JobHistory Server,会发现没有新的作业记录(仍然只有 job 38),这证明了作业确实是本地运行的。
如果你能在 IDE 中运行此程序,你实际上可以对此整个过程进行调试,因为它完全在客户端的 JVM 内工作。

总结

本节课中,我们一起学习了如何在 Hadoop 客户端虚拟机中本地运行作业,而不是在 YARN 守护进程的控制下运行。这项技术在大数据软件开发和测试阶段提供了极大的便利,允许开发者在集成开发环境中高效地进行代码调试和功能验证。
063:MapReduce反模式解析 🚫


在本节课中,我们将学习11种Hadoop MapReduce开发中常见的反模式。对于每一种反模式,我们都会探讨其问题所在以及如何避免它们。理解这些反模式有助于编写更高效、更健壮的大数据处理程序。
使用完整大数据集进行开发
上一节我们介绍了课程概述,本节中我们来看看第一个反模式:在开发阶段使用完整的大数据集。
当开发者被告知需要处理数据时,他们倾向于直接使用生产环境的完整数据集进行开发。这样做的问题是会增加开发运行时间,并且可能效率低下。
以下是使用完整数据集开发的主要问题:
- 开发运行时间长。在开发过程中需要反复编写、编译和运行代码,如果每次运行都需要几分钟,会严重影响效率。
- 不利于测试各种边界情况。
更好的方法是,从大数据集中提取一个具有代表性的小子集用于开发。这不仅能缩短开发运行时间,还能帮助测试数据中可能存在的各种情况。如果你在进行数据科学活动,使用代表性小数据集也能让你对处理完整数据集时可能得到的结果有一个预期。
进行外部网络调用
在编写MapReduce程序的map和reduce函数时,进行外部网络调用(如HTTP调用、TCP调用)来获取额外数据以供处理,是一个糟糕的做法。
换句话说,在你的map函数和reduce函数内部,不要进行网络调用。一个例子是调用地理定位服务来查询IP地址,或者在Mapper中爬取网站。
进行网络调用会使Mapper或Reducer变慢,因为你在进行I/O操作。尽管可以使用多线程,但通常不建议进行网络调用。此外,在共享集群环境中,低效的作业会占用计算资源,影响他人使用。
如果你需要连接不同的数据集,应确保所有数据在作业开始前都已准备就绪,然后使用Map端连接或Reduce端连接等技术。对于爬虫任务,应在进行任何处理之前,先爬取所有所需数据。
忽视数据类型选择
开发者在编写MapReduce程序前,往往不仔细考虑数据类型。例如,开发者倾向于对所有数据都使用字符串类型,或者对所有数据都使用整数类型。
你必须在使用前思考数据类型的选择。这会影响内存使用以及数据的溢出或下溢。你需要考虑数据的大小、该数据类型能容纳的最大值以及数据集将占用多少内存。
之所以强调这一点,是因为你正在处理大数据。在处理大数据时,任何小错误都会被放大,因此在选择数据类型前必须深思熟虑。
不使用Combiner
对于直接的聚合操作(如计数),不使用Combiner是一个坏主意。Hadoop非常擅长计数和各类计算。
本地聚合是Hadoop和分布式计算中的基本算法之一。因此,尽可能考虑使用Combiner。
只使用一个Reducer
Hadoop中默认的Reducer数量是1,而开发者常常忘记这一点。如果你有500个输入文件,它们将生成500个不同的Mapper。即使Mapper可以并行运行,但所有Mapper产生的数据最终都必须发送到Reducer。
因此,你需要考虑为你的应用程序设置多个Reducer。当然,在使用多个Reducer时,需要考虑使用合适的分区算法。
为全局排序只使用一个Reducer
很多时候,开发者希望输出结果是有序的。他们通常的做法是将Reducer数量设置为1,这样会自动产生一个全局排序的输出。
虽然这是一个简单的方法,但只使用一个Reducer会显著降低作业的完成时间。这里的建议是考虑使用自定义分区器,将不同范围的输出键发送到不同的Reducer,并且使用多个Reducer。


通过爬取Hadoop UI界面进行监控
为了满足企业中的监控需求,许多开发者倾向于通过爬取资源管理器或作业历史服务器UI来获取信息。他们编写程序访问网站,下载HTML页面内容,解析后存入数据库或显示在管理控制台。
不要这样做,因为作业历史服务器UI和资源管理器UI可能在下一个版本中发生变化。你应该考虑使用API来获取信息。Hadoop提供了RESTful API来获取作业信息。此外,yarn命令也有application参数,配合-status和application ID可以给出作业状态信息。
每个作业使用数十或数百个计数器
很多时候,Hadoop开发者希望获取作业的统计信息,于是开始大量使用计数器。虽然计数器非常适合跟踪信息,但不应过度使用,因为它们需要进行同步。
如果你有50、60甚至100个Mapper,所有这些Mapper都需要回连到中央应用程序主节点来更新计数器。在大型集群中,大量Mapper并行运行时,频繁更新计数器会使应用程序主节点不堪重负。
你可以使用计数器,但必须有效地使用。具体做法是定期、按块递增计数器值。查看计数器API,你可以递增1,也可以定期递增100或200。这就是“按块递增”的含义,你可能需要结合定期和按块递增两种方式。
运行大量短时Map任务
拥有成千上万个运行时间非常短(例如5到10秒)的Map任务的应用程序是一个坏主意。如果你有10000个文件,每个文件只有2MB或3MB,那么每个文件都会启动一个Mapper。
启动一个Mapper意味着启动一个进程,启动Reducer也是如此。由于启动进程本身较慢,这会拖慢整个作业的处理速度。
你应该考虑使用更大的文件和更大的块大小。你也可以研究YARN的一个较新特性——Uber任务,它可以复用现有的JVM。
仅使用Java进行MapReduce编程

在过去的四周里,我们一直在讨论用Java进行MapReduce编程。Java无疑是一门强大的语言,但有时也显得冗长。
当然,Java运行在Hadoop之上,它在速度控制、二进制数据处理以及与现有Java或MapReduce库协作方面表现出色,因为Hadoop本身是用Java编写的,集成性很好。但除了Java,还有其他技术可用于Hadoop MapReduce编程。
以下是其他可选方案:
- Hadoop Pipes:允许你使用C++ API编写MapReduce程序。
- Hadoop Streaming:允许你使用脚本语言(如Python、Ruby)编写MapReduce程序。
- 高级编程语言:如Pig、Hive和Cascading,这些是更高级的声明式编程语言,可以在进行MapReduce编程时利用。
在接下来的几周里,我们将稍微讨论Pipes和Streaming,并重点介绍Pig和Hive。

疏远运维团队
最后,我想告诉你,当你在企业环境中使用Hadoop时,不要疏远你的运维团队。在这门课程中,我让你们同时担任系统管理员和开发者,但现实中,你可能是一名开发者,而由系统管理员为你提供Hadoop环境。
你需要与系统管理员保持良好的关系,因为他们是为你分配大数据处理配额的人,当Hadoop出现任何问题或你的应用程序在Hadoop环境中运行时遇到困难时,他们也是提供帮助的人。很多事情都可能出错,因此你真的需要与系统管理员,当然还有网络工程师成为朋友,因为系统管理员和网络工程师通常紧密合作。
总结


本节课中,我们一起学习了Hadoop MapReduce开发中常见的11种反模式,包括使用完整数据集开发、进行外部网络调用、忽视数据类型、不使用Combiner、不合理设置Reducer数量、低效的监控方法、滥用计数器、创建大量短任务、局限于Java编程以及忽视与运维团队的合作。理解并避免这些反模式,对于构建高效、可维护的大数据应用至关重要。
064:MapReduce高级编程总结 📚

在本节课中,我们将总结MapReduce高级编程的几个核心概念。这些概念包括多线程Mapper、输入输出格式类的内部机制、推测执行、本地作业运行以及MapReduce反模式。掌握这些内容将帮助你编写更高效、更健壮的大数据处理程序。
多线程Mapper处理IO密集型任务 🧵
上一节我们介绍了MapReduce的基础编程模型,本节中我们来看看如何通过多线程技术优化Mapper的性能。
我们学习了如何在Mapper中使用多线程来高效处理IO密集型任务。如果你编写的Mapper需要进行网络调用,从这一角度看,它是IO密集型的。在这种情况下,你可以使用多线程Mapper。在编写多线程Mapper时,你需要关注线程安全问题。
以下是实现多线程Mapper的一个核心代码结构示例:
public class MultiThreadedMapper extends Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
private ExecutorService executor;
@Override
protected void setup(Context context) {
// 初始化线程池
int numThreads = context.getConfiguration().getInt("mapreduce.mapper.multithreaded.threads", 10);
executor = Executors.newFixedThreadPool(numThreads);
}
@Override
public void map(KEYIN key, VALUEIN value, Context context) throws IOException, InterruptedException {
// 将任务提交给线程池处理
executor.submit(new ProcessingTask(key, value, context));
}
@Override
protected void cleanup(Context context) {
// 关闭线程池
executor.shutdown();
}
}
输入与输出格式类的内部机制 ⚙️
我们深入探讨了输入和输出格式类的内部工作原理。
我们看到,输入格式类负责为每个任务创建数据分片(Splits)和记录读取器(Record Reader)。输出格式类则负责创建记录写入器(Record Writer),用于将数据写出作业,并且每个任务的输出数据和目录管理也由输出格式类负责。
其核心关系可以概括为以下流程:
InputFormat → Splits → Record Reader → Mapper → Reducer → Record Writer → OutputFormat
推测执行机制 ⚡

然后,我们也学习了推测执行的概念。推测执行是指当Hadoop框架发现某个任务由于多种原因(可能是硬件问题,也可能是同一机器上运行的其他进程)运行缓慢时,它会在另一个主机上启动相同的任务。此功能默认是开启的。
我们学习了如何开启或关闭此功能,包括如何为Map阶段或Reduce阶段显式开启它。配置参数如下:
mapreduce.map.speculative: 控制Map任务的推测执行。mapreduce.reduce.speculative: 控制Reduce任务的推测执行。
本地运行作业 🖥️
接着,我们了解了如何在本地运行作业,而不是在资源管理器、节点管理器和历史服务器的控制下运行。这对于测试和调试目的非常有用。
你可以通过设置以下配置,在IDE或单机环境中运行MapReduce作业:
Configuration conf = new Configuration();
conf.set("mapreduce.framework.name", "local");
MapReduce反模式与最佳实践 🚫
最后,我们探讨了MapReduce反模式。我们研究了11种常见的MapReduce反模式及其相应的最佳实践。
以下是部分关键反模式与建议的简要列表:
- 单Reducer:避免将所有数据发送到单个Reducer,这会造成瓶颈。应根据数据量合理设置Reducer数量。
- 数据倾斜:某些键对应的值过多,导致个别Reducer负载过重。应考虑在Mapper端进行Combine操作或自定义分区器。
- 无意义的中间数据:避免在Map和Reduce阶段产生大量不需要的中间数据,这会造成IO和网络开销。
- 过度复杂的键/值对象:使用复杂对象作为键或值会增加序列化开销。应尽量使用原生类型或Hadoop提供的Writable类型。
本节课中我们一起学习了MapReduce高级编程的多个关键主题:利用多线程Mapper提升IO效率、理解输入输出格式的职责、通过推测执行应对慢任务、在本地环境进行作业测试与调试,以及识别并避免常见的MapReduce反模式。掌握这些知识,将有助于你设计出性能更优、容错性更强的大数据处理应用。
065:Hive导论 🐝
概述

在本节课中,我们将学习如何使用Hive进行大数据分析。Hive是一种构建在Hadoop之上的数据仓库工具,它允许我们使用类似SQL的语言(HiveQL)来查询和管理存储在HDFS中的大规模数据集。我们将从Hive的基本概念和架构开始,逐步学习如何安装、配置Hive,并执行数据操作。
Hive概念与架构
上一节我们回顾了Hadoop的核心组件。本节中,我们来看看Hive是什么以及它是如何工作的。
Hive是一个数据仓库基础设施,它提供了数据汇总、查询和分析的功能。Hive将结构化的数据文件映射为一张数据库表,并提供完整的SQL查询功能。Hive的架构主要包括以下组件:
- Hive CLI/Beeline:命令行界面,用于与Hive交互。
- Hive Server:允许远程客户端使用JDBC/ODBC连接并执行查询的服务。
- Metastore:存储Hive的元数据(如表名、列信息、分区信息等)的数据库。
- Driver:接收查询,编译查询,优化执行计划,并执行。
- Execution Engine:默认使用MapReduce作为执行引擎,将HiveQL转换为MapReduce任务在Hadoop集群上运行。
其核心工作流程可以概括为:用户提交HiveQL查询 -> Hive编译器将其转换为一个或多个MapReduce任务 -> Hadoop集群执行这些任务 -> 结果返回给用户。
设置Hive环境
了解了Hive的架构后,我们需要将其部署到Hadoop环境中。以下是设置Hive的基本步骤。
- 下载Hive:从Apache官网下载稳定版本的Hive压缩包。
- 解压与配置:将压缩包解压到指定目录,并配置环境变量(如
HIVE_HOME)。 - 配置Metastore:Hive需要一个数据库(如Derby、MySQL)来存储元数据。需要配置
hive-site.xml文件来指定数据库连接信息。 - 初始化Metastore:运行
schematooinit命令来初始化元数据库。 - 启动Hive:运行
hive命令启动Hive命令行界面。
一个简单的环境变量配置示例(在~/.bashrc或类似文件中):
export HIVE_HOME=/path/to/your/hive
export PATH=$PATH:$HIVE_HOME/bin
一个简单的Hive示例
环境配置完成后,我们可以通过一个简单的例子来快速体验Hive。本节我们将创建一张表,加载数据,并执行查询。
假设我们有一个文本文件sample.txt,内容如下:
1,Alice,Engineering
2,Bob,Marketing
3,Charlie,Sales
在Hive CLI中执行以下操作:
-- 1. 创建表
CREATE TABLE IF NOT EXISTS employees (
id INT,
name STRING,
department STRING
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ',';
-- 2. 将本地文件数据加载到Hive表中
LOAD DATA LOCAL INPATH '/path/to/sample.txt' INTO TABLE employees;
-- 3. 执行查询
SELECT * FROM employees WHERE department = 'Engineering';
这个例子展示了Hive的基本操作:使用类SQL语法定义表结构,从本地文件系统加载数据,以及进行条件查询。
将数据加载到Hadoop的策略
在Hive中操作数据,首先需要将数据导入HDFS。有多种策略可以将数据加载到Hadoop中。
- 使用
LOAD DATA命令:如上例所示,可以直接将本地文件或HDFS中的文件加载到Hive表。 - 使用
INSERT语句:可以从一个Hive查询的结果,将数据插入到另一张表中。 - 使用Sqoop工具:这是一个专用于在Hadoop和关系型数据库之间高效传输批量数据的工具。
- 使用Flume工具:适用于实时收集、聚合和移动大量日志数据到HDFS。
- 直接放置文件到HDFS:使用
hadoop fs -put命令将文件直接上传到HDFS的特定目录,然后创建Hive外部表指向该位置。
选择哪种策略取决于数据源、数据量以及数据到达的频率。
Hive语句与数据类型
要灵活使用Hive,必须熟悉其数据定义语言(DDL)和数据类型。本节我们来看看如何创建数据库、表以及定义列。
Hive支持多种语句,主要分为:
- DDL(数据定义语言):用于创建、修改、删除数据库、表、视图等,如
CREATE,ALTER,DROP。 - DML(数据操作语言):用于向表中加载、插入、导出数据,如
LOAD DATA,INSERT,EXPORT。 - 查询语句:用于检索数据,即
SELECT语句。
以下是创建数据库和表的一个例子,其中展示了常用的基本数据类型:
-- 创建数据库
CREATE DATABASE IF NOT EXISTS company_db;
-- 使用数据库
USE company_db;
-- 创建表,定义列及其数据类型
CREATE TABLE employees_details (
emp_id INT, -- 整数类型
emp_name STRING, -- 字符串类型
salary FLOAT, -- 浮点数类型
join_date DATE, -- 日期类型
is_active BOOLEAN -- 布尔类型
);
Hive还支持复杂数据类型,如ARRAY, MAP, STRUCT,用于处理更嵌套的数据结构。
使用分区管理大数据
当数据量非常庞大时,全表扫描会非常低效。Hive的分区功能通过将数据组织成更易管理的部分来提升查询性能。
分区(Partition)类似于根据表中某一列的值(如日期、地区)将数据存储在不同的子目录中。查询时,如果指定了分区条件,Hive只会扫描相关分区,而非整个数据集。
以下是如何创建和使用分区表:
-- 创建一个按‘部门’分区的表
CREATE TABLE employees_partitioned (
id INT,
name STRING,
salary FLOAT
)
PARTITIONED BY (department STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
-- 向特定分区加载数据
LOAD DATA LOCAL INPATH '/path/to/engineering_data.txt' INTO TABLE employees_partitioned PARTITION (department='Engineering');
LOAD DATA LOCAL INPATH '/path/to/sales_data.txt' INTO TABLE employees_partitioned PARTITION (department='Sales');
-- 查询特定分区的数据,效率更高
SELECT * FROM employees_partitioned WHERE department='Sales';
通过分区,我们可以有效地管理和查询大规模数据集。
使用Join进行高级查询
Hive支持类似SQL的连接(Join)操作,允许你组合多个表中的数据。这对于关系型数据分析至关重要。
Hive支持多种JOIN类型,包括INNER JOIN, LEFT OUTER JOIN, RIGHT OUTER JOIN, FULL OUTER JOIN。其基本语法与SQL一致。
假设我们有两张表:employees和departments。
-- 创建部门表
CREATE TABLE departments (
dept_id INT,
dept_name STRING
);
-- 使用INNER JOIN查询员工及其部门信息
SELECT e.name, e.salary, d.dept_name
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id;
这个查询将employees表和departments表连接起来,返回每个员工的姓名、工资和所属部门名称。掌握Join操作能让你执行复杂的数据关联分析。
总结

本节课中我们一起学习了Hive,这个在Hadoop生态中进行大数据分析的重要工具。我们从Hive的核心概念和架构入手,学习了如何安装和设置Hive环境。通过一个简单的示例,我们实践了创建表、加载数据和执行查询的完整流程。我们还探讨了将数据导入Hadoop的不同策略,深入了解了Hive的DDL语句和丰富的数据类型。为了高效管理海量数据,我们介绍了分区技术。最后,我们学习了如何使用Join操作来关联多张表的数据,完成更复杂的分析任务。掌握这些知识,你将能够利用HiveQL轻松地对Hadoop中的大数据进行查询和分析。
066:Hive 架构与核心概念 🐝

在本节课中,我们将学习 Apache Hive 的概述与架构。我们将了解 Hive 是什么、其诞生的动机、核心概念以及整体架构。
概述
Hive 是一个构建在 Hadoop 之上的数据仓库基础设施。它允许用户使用类似 SQL 的语言(HiveQL)进行查询处理、数据汇总和分析,从而简化了在 HDFS 上进行大数据处理的过程。
MapReduce 的挑战
在过去的四周里,我们一直在学习 MapReduce 编程。但 MapReduce 本身存在一些挑战。
以下是 MapReduce 编程面临的主要挑战:
- 编程难度高:使用 Java 编写 MapReduce 程序很困难,需要开发者具备 Java 编程能力。
- 编程范式不同:MapReduce 的编程范式与传统结构化编程、面向对象编程或面向过程编程不同,需要理解 Map 和 Reduce 这种分布式计算模型。
- 开发周期长:编写 MapReduce 程序耗时较长。开发者需要使用 Hadoop API 编写代码,然后编译并运行。即使编写一个简单的计数程序(如单词计数),也需要数百行代码。
Hive 的起源
上一节我们介绍了 MapReduce 的挑战,本节我们来看看 Facebook 如何应对这些挑战。
Facebook 需要一种方法,让非 Java 程序员(如数据科学家和数据分析师)能够访问 HDFS 中的数据以进行大数据分析。他们的目标用户是那些不懂 Java 编程语言的数据科学家和数据分析师。
为此,Facebook 开发了 Hive。Hive 是一种旨在满足数据分析师需求的语言。其核心理念是创建一种基于 SQL 的语言,因为数据科学家和数据分析师通常对传统数据仓库和 SQL 语言非常熟悉。这样,学习成本更低,使用更便捷,使得集群上的数据更容易访问和处理。如今,Facebook 和其他许多组织都广泛使用 Hive 进行数据分析。
什么是 Hive?
Hive 是一个构建在 Hadoop 之上的数据仓库基础设施。
它允许您进行查询处理(选择记录)、数据汇总(如聚合计算)以及数据分析工作。Hive 支持使用一种类似 SQL 的声明式编程语言(称为 HiveQL,即 Hive 查询语言)来表达查询。Hive 是 Apache 软件基金会管理的一个开源项目。
Hive 核心概念
Hive 借鉴并重新诠释了关系数据库管理系统(RDBMS)中的概念。
以下是 Hive 中的核心概念及其与文件系统的映射关系:
- 数据库:用于保存一组表,解决命名冲突。在 Hive 中,一个数据库映射为 HDFS 中的一个目录。
- 表:一组具有相同模式(列定义)的行。在 Hive 中,一个表映射为数据库目录下的一个子目录。
- 行:包含一组列的单个记录。在 Hive 中,一行数据对应文件中的一行内容。
- 列:为单个值提供值和类型。在 Hive 中,列定义了文件中数据的结构。
- 分区:当表变得非常大时,可以将其分解为多个分区以便于数据管理。在 Hive 中,一个分区通常映射为表目录下的一个子目录。
Hive 的有趣之处在于,数据库、表、行、列和分区等概念都被映射为 HDFS 文件系统中的目录和文件。因此,我们在 RDBMS 中拥有的所有概念,在 Hive 中都以目录、子目录、文件及其内容的形式存在。
Hive 与 RDBMS 的区别
Hive 不是一个 RDBMS。关系数据库管理系统(RDBMS)有许多优势:响应时间快(通常在毫秒级)、能处理数千个并发用户连接、支持修改现有记录以及支持 ACID(原子性、一致性、隔离性、持久性)事务。
请理解,Hive 并没有将 Hadoop 转变为关系数据库管理系统。 当您开始学习 Hive 概念并编写类似 SQL 的查询时,可能会感觉它像一个数据库,但它本质上并不是数据库。虽然有些人正尝试将 Hive 打造成类似 NoSQL 的数据库,但目前 Hive 还不是一个数据库。
Hive 的工作原理
Hive 解释器将 HiveQL 查询转换为 MapReduce 程序。因此,每当您在 Hive 中编写一个 SELECT 语句时,它都会被转换成一个 MapReduce 程序。本质上,Hive 是编写 MapReduce 程序的一个高级抽象。
在极少数情况下,Hive 会绕过 MapReduce 直接从 Hadoop 获取数据。例如,如果您执行 SELECT * FROM table 且没有 WHERE 子句,Hive 可能会直接读取 HDFS 中的数据并输出。
但在几乎所有情况下,它都会将您的 SQL 转换为 MapReduce 程序。即使是一个简单的 Hive 查询也可能需要数秒或更长时间才能产生结果,因为您的 Hive 查询必须被翻译成 MapReduce 程序,然后该程序需要作为一个作业提交到集群执行。
Hive 主要用于查询,但也可以进行“更新”操作,这通常是通过向现有文件追加数据或添加新文件来实现的。我们将在后面看到 Hive 数据库和表如何映射到目录和文件,因此引入数据(即更新数据)实际上就是更新文件。
Hive 与 RDBMS 的对比


让我们从语言、更新能力、事务、延迟、索引和数据规模等方面对比 Hive 和 RDBMS。
以下是详细的对比列表:

- 语言:
- 传统数据分析 (RDBMS):使用 SQL 92 标准。
- 大数据分析 (Hive):使用 SQL 92 的一个子集,并有一些 Hive 特定的扩展。请注意,并非所有 SQL 92 的功能都能在 HiveQL 中实现。
- 更新能力:
- 传统数据分析 (RDBMS):支持 INSERT、UPDATE、DELETE 语句。
- 大数据分析 (Hive):支持
INSERT OVERWRITE(用新数据集覆盖整个表),没有直接的 UPDATE 或 DELETE 能力。更新可以通过添加新文件或替换现有文件来实现。
- 事务:
- 传统数据分析 (RDBMS):支持 ACID 事务。
- 大数据分析 (Hive):不支持事务。
- 延迟:
- 传统数据分析 (RDBMS):亚秒级延迟(毫秒级)。
- 大数据分析 (Hive):可能需要数秒甚至数分钟,取决于 MapReduce 作业的规模。
- 索引:
- 传统数据分析 (RDBMS):可以创建多个索引,对性能至关重要。
- 大数据分析 (Hive):通常没有索引概念,数据总是被扫描(当然是通过 MapReduce 任务并行扫描)。在大数据场景下,全表扫描通常比维护索引更高效。
- 数据规模:
- 传统数据分析 (RDBMS):可以处理 TB 级数据,但在 TB 级以上表现不佳。
- 大数据分析 (Hive):可以处理 PB 级数据。
Hive 架构论文
正如 Google 在 2003 年发表了 GFS 论文,2004 年发表了 MapReduce 论文一样,Facebook 的数据基础设施团队也发表了一篇关于 Hive 架构的论文,名为《Hive: A Warehousing Solution Over a MapReduce Framework》。您可以通过提供的 URL 找到这篇论文。论文的主要作者是 Ashish Thusoo,他也是 Hive 的主要架构师。论文涵盖了数据模型、表、分区、桶的概念、HiveQL 查询语言、如何将 HiveQL 转换为 MapReduce、Hive 架构、元存储等核心内容。这篇论文只有四页,强烈建议您阅读。
Hive 执行流程
如前所述,Hive 将 HiveQL 语句转换为一组 MapReduce 作业,然后在 Hadoop 集群上执行。
假设您有一个 Hadoop 集群。您只需要在客户端机器上安装 Hive,然后使用 HiveQL 创建模式(即表),并执行 SELECT 语句。这些 SELECT 语句被提交给 Hive,Hive 将其转换为 MapReduce 作业。这些作业在 Hadoop 集群上执行,结果返回给客户端。这就是 Hive 的通用架构。

Hive 元存储
Hive 有一个元存储(Metastore),它存储在一组关系数据库管理系统的表中。
默认情况下,Hive 元存储在 Derby 数据库中,但也可以存储在其他数据库(如 MySQL)中,或者存储在 Hadoop 目录(HCatalog)中。
在使用 Hive 之前,您需要创建数据库、表、列定义以及数据位置等信息。所有这些信息都存储在 Hive 的元存储中。
元存储中保存的数据包括:

- 表定义:表名、列名、列数据类型、所属数据库。
- 数据位置:表数据在 HDFS 中的存储路径。
- 行格式:表示表数据的文件行格式(如逗号或制表符分隔)。
- 存储格式:文件的存储格式,这决定了 MapReduce 作业将使用哪种输入/输出格式。因为 Hive 查询被转换为 MapReduce 程序,它需要知道如何读写数据。
元存储在后台为您工作。重要的是要理解它的存在,并且您所有的元数据信息都存储在那里。随着本模块的进行,您将看到如何创建模式,以及这些模式如何存储在本地 Derby 数据库的元存储中。
元存储与文件系统映射
Hive 将数据库定义映射为一个目录。当您执行 CREATE DATABASE MyDB 时,这实际上是在 HDFS 中创建一个目录。
Hive 将表定义映射为一个目录。当您执行 CREATE TABLE blog 或 CREATE TABLE customer 时,这实际上是在数据库模式目录内创建另一个子目录。
存储在元存储中的表定义描述了数据文件的布局。通常,表数据是使用逗号、制表符或其他字符分隔的文本文件。但您也可以使用二进制文件,如果这样做,则需要使用自定义的序列化/反序列化器(SerDe)。因此,您可以使用纯文本文件,也可以使用带有自定义 SerDe 的专用文件。
表定义保存在 Hive 的元存储中,默认情况下存储在当前文件夹下的 Derby 数据库中。实际上,在 Hive 中,一切都是 HDFS 目录和常规文件系统目录。
Hive 执行环境
安装 Hive 后,您可以通过几种方式执行 Hive。
以下是 Hive 的几种执行环境:
- 命令行界面:有一个名为
hive的命令。在命令提示符下输入hive并回车,您将进入 Hive 解释器,可以在其中编写 SELECT 语句。 - Web 界面:可以配置 Hive 的 Web 界面。提供的链接指向了如何创建 Hive Web 界面的文档。
- JDBC 接口:您可以使用 Java 数据库连接(JDBC)接口。换句话说,您可以编写使用 Hive JDBC 驱动程序的 Java 程序,通过 Hive 语法访问 HDFS 中的内容。这也是一项很酷的技术,此处提供了相关链接。
整体架构图
希望此时您对 Hive 架构有了一个很好的了解。

在 Hive 架构中,Hadoop 是其基础。您拥有作为主守护进程的 NameNode、ResourceManager 和 Secondary NameNode。在从节点(Slave Nodes)上,运行着 NodeManager 和 DataNode 守护进程。当然,History Server 也会运行在其中。
当您想使用 Hadoop 时,只需在客户端机器上安装 Hive。当您运行 Hive 命令行时,它首先会查询 Hive 元存储(以确保您使用了正确的列名和类型等),然后将您的查询提交给 Hive 解析器。Hive 解析器将您的 Hive 查询转换为 MapReduce 作业并提交。
因此,您的 Hadoop 集群是服务器端,而您的客户端可以是一个命令行界面(通过 Hive 查询解析器提交作业)、一个 Java JDBC 客户端(同样通过 Hive 查询解析器提交作业),或者是一个 Web 界面。无论您做什么,每当您在 Hive 中编写 SELECT 语句时,它们都会被转换为 MapReduce 作业,并且元存储会被查询以确保引用的列存在。
总结
在本节课中,我们一起学习了 Hive 的概述。我们了解到 Hive 是 Facebook 开发的一种高级编程范式,旨在帮助数据科学家和数据分析师访问和处理 HDFS 中的数据,而无需自己编写 MapReduce 程序。我们还了解到 Hive 借鉴了 RDBMS 系统中的许多概念,并将数据库、表、列等概念转换为 HDFS 中的目录、子目录和文件。我们学习了 Hive 查询语言(HiveQL)实际上是 SQL 92 查询语言的一个子集,并拥有自己的扩展,因此并非所有 SQL 92 的功能都能在 HiveQL 中实现。我们明确了 Hive 不是一个关系数据库管理系统,尽管它看起来像,但它不能执行事务和亚秒级查询。最后,我们了解到 Hive 构建在 Hadoop 之上,每当您编写 Hive 语句时,这些语句会查询元存储,然后将作业提交给 Hadoop 执行,结果返回给客户端。因此,安装 Hive 实际上就是建立了一个提供元存储访问以及将查询转换为 MapReduce 作业的 Hive 编译器和查询解析器的基础设施。
067:Hive 环境配置 🛠️



在本节课中,我们将学习如何下载、安装、配置并运行 Apache Hive。Hive 是一个基于 Hadoop 的数据仓库工具,它允许我们使用类似 SQL 的查询语言(HiveQL)来处理存储在 Hadoop 分布式文件系统(HDFS)中的大数据。
概述

Hive 的安装和配置过程主要分为几个步骤:下载安装包、解压安装、设置环境变量、配置元数据存储以及启动 Hive 命令行界面。我们将使用内置的 Derby 数据库作为元数据存储,以简化配置过程。
下载 Hive
首先,我们需要下载 Hive 的安装包。Hive 是 Apache 的开源项目,可以从其官方网站或镜像站点获取。
以下是下载 Hive 的两种主要方式:
- 从官方网站下载:访问
hive.apache.org,点击 “Downloads” 进入下载页面,然后从镜像站点下载文件。文件通常命名为apache-hive-<版本号>-bin.tar.gz。 - 从 Apache 归档站点下载:访问
archive.apache.org/dist/hive/,该目录包含了 Hive 的各个历史版本,你可以选择并下载所需的版本。
安装 Hive
下载完成后,安装 Hive 的过程非常简单,主要就是解压文件到指定目录。
我们假设 Hadoop 安装在 /usr/local/hadoop 目录下。执行以下命令进行解压安装:
cd /usr/local/hadoop
tar -zxvf ~/Downloads/apache-hive-<版本号>-bin.tar.gz
执行上述命令后,Hive 就被解压安装到了 /usr/local/hadoop/apache-hive-<版本号> 目录下。
现在,让我们来了解一下 Hive 安装目录的结构:
bin/:包含最重要的命令行程序hive,它是启动 Hive 解释器的入口。conf/:存放 Hive 的配置文件。其中hive-default.xml.template是一个包含所有默认配置的模板文件。lib/:包含 Hive 运行所需的核心 JAR 文件及其依赖项。examples/:提供了一些 HiveQL 的示例脚本。hcatalog/和scripts/:分别用于集成 HCatalog 和初始化外部数据库(如 MySQL)作为元存储。在本教程中,我们将使用内置的 Derby 数据库,因此暂时不需要关注这两个目录。
配置环境变量
为了让系统能够方便地找到 Hive 命令,我们需要设置两个环境变量:HIVE_HOME 和 PATH。
通常,我们在用户的 ~/.bashrc 或 ~/.profile 文件中进行设置。添加以下内容:
export HIVE_HOME=/usr/local/hadoop/apache-hive-<版本号>
export PATH=$PATH:$HIVE_HOME/bin
保存文件后,执行 source ~/.bashrc 使配置立即生效。
接下来,我们需要告诉 Hive Hadoop 的安装位置。这通过设置 HADOOP_PREFIX 环境变量来实现:
export HADOOP_PREFIX=/usr/local/hadoop
Hive 会通过这个变量找到 Hadoop 的配置,从而知道如何与 HDFS 和 YARN 资源管理器通信。另一种方式是确保 Hadoop 的 bin 目录已在 PATH 中。
Hive 的目录结构

在 HDFS 中,Hive 有自己默认的存储路径:
- 数据仓库目录:
/user/hive/warehouse。所有在 Hive 中创建的数据库和表,其对应的数据文件都存储在这个目录下。 - 临时目录:
/tmp。Hive 处理过程中的临时文件存放在这里。
对于多用户环境,你可能需要修改这些目录的权限,确保所有 Hive 用户都属于同一个用户组并具有读写权限。但在本单节点示例中,我们使用 hdadmin 用户,因此默认配置即可。
配置 Hive 站点文件
上一节我们介绍了 Hive 的目录结构,本节我们来具体配置 Hive。Hive 的主要配置文件位于 $HIVE_HOME/conf 目录下。
虽然存在一个 hive-default.xml.template 模板文件,但我们通常不直接修改它,而是创建一个 hive-site.xml 文件来覆盖默认配置。对于我们最重要的配置是元数据存储(Metastore)的连接信息。
我们将使用内置的 Derby 数据库作为元存储。在 hive-site.xml 中添加以下配置:
<configuration>
<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:derby:;databaseName=/usr/local/hadoop/metastore_db;create=true</value>
<description>JDBC connect string for a JDBC metastore.</description>
</property>
</configuration>
这个配置指定了 Derby 数据库文件的存储路径为 /usr/local/hadoop/metastore_db。
初始化元数据库
配置文件创建好后,我们需要初始化元数据库的模式(Schema)。Hive 提供了 schematool 命令来完成这项工作。
进入 Hive 的 bin 目录,执行以下命令:
./schematool -initSchema -dbType derby
此命令会根据 hive-site.xml 的配置,在指定路径创建并初始化 Derby 数据库文件。执行成功后,你会在 /usr/local/hadoop/ 目录下看到新生成的 metastore_db 文件夹。
启动与使用 Hive CLI
现在,所有准备工作已经就绪,可以启动 Hive 命令行界面(CLI)了。确保 Hadoop 的所有守护进程(如 NameNode, DataNode, ResourceManager)已经启动。

在终端中直接输入 hive 命令:

hive

如果一切配置正确,你将看到 Hive 的命令行提示符 hive>。在这个界面中,你可以执行 HiveQL 语句,就像在传统的 SQL 解释器中一样。
以下是一些基本操作示例:
-- 显示所有数据库
SHOW DATABASES;
-- 创建一个新数据库
CREATE DATABASE mydb;
-- 使用某个数据库
USE mydb;

-- 创建表
CREATE TABLE users (id INT, name STRING);
当你完成操作后,可以使用以下任意一种方式退出 Hive CLI:
QUIT;
-- 或
EXIT;
-- 或直接按 Ctrl+D
重要提示:由于我们使用的是嵌入式(Embedded)Derby 数据库,它不支持多连接。这意味着在同一时间,只能有一个 Hive CLI 会话连接到元数据库。如果尝试启动第二个会话,将会连接失败。要支持多用户并发访问,需要将元数据库配置为独立的服务,如 MySQL 或 PostgreSQL。
总结
在本节课中,我们一起学习了 Apache Hive 的完整环境配置流程。我们从下载安装包开始,逐步完成了安装解压、设置环境变量、配置 hive-site.xml 文件以指定 Derby 作为元数据存储、使用 schematool 初始化数据库,最后成功启动并验证了 Hive 命令行界面。

现在,你的 Hive 环境已经准备就绪。在接下来的课程中,我们将深入探索如何使用 HiveQL 创建数据库、表,加载数据并执行查询,从而利用 Hadoop 的强大能力来处理大规模数据集。
068:Hive 基础应用示例 🐝


在本节课中,我们将通过一个简单的例子来学习 Hive 的基本操作。我们将了解如何在 Hive 中创建表、加载数据、查询数据以及最终删除表。通过这个过程,你也会理解 Hive 内部的一些工作原理,例如创建表时发生了什么、加载数据时 HDFS 如何变化、查询如何触发 MapReduce 作业,以及删除表时 HDFS 的相应操作。
我们将按照以下四个步骤进行:
- 创建表
- 向表中加载数据
- 查询数据
- 删除表
第一步:创建表 📄
上一节我们介绍了本课程的目标,本节中我们来看看如何创建表。首先,我们需要准备数据。在用户主目录下,有一个名为 examples 的文件夹,其下有一个 hive 子文件夹,里面包含一个名为 user_post.txt 的文件。这个文件包含了用户的博客帖子数据。
我们可以从 Hive 解释器中查看这个文件。进入 Hive 解释器后,可以使用 ! 命令来执行 Unix 命令。例如,输入 !cat examples/hive/user_post.txt; 可以查看文件内容。
这个文件包含三列数据,用逗号分隔:
- 用户 ID
- 博客帖子标题
- 帖子发布时间
了解数据结构后,我们就可以在 Hive 中创建对应的表了。创建表的命令与 MySQL 类似,使用 CREATE TABLE 语句。
以下是创建表的 HiveQL 语句:
CREATE TABLE posts (
user STRING,
post STRING,
time BIGINT
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ‘,’
STORED AS TEXTFILE;
CREATE TABLE posts:创建名为posts的表。(user STRING, post STRING, time BIGINT):定义三个列及其数据类型。ROW FORMAT DELIMITED:指定行格式为分隔符格式。FIELDS TERMINATED BY ‘,’:指定列之间用逗号分隔。STORED AS TEXTFILE:指定数据以文本文件格式存储。
执行创建表命令后,Hive 会在元存储(存储在 Derby 数据库中)记录表结构,同时在 HDFS 的 /user/hive/warehouse/ 目录下创建一个名为 posts 的文件夹。
可以使用 SHOW TABLES; 命令查看所有表,使用 DESCRIBE posts; 命令查看 posts 表的结构。
第二步:向表中加载数据 📥
现在我们已经创建了表,接下来看看如何向表中加载数据。我们使用 LOAD DATA 命令将本地文件的数据加载到 Hive 表中。
加载数据的命令如下:
LOAD DATA LOCAL INPATH ‘examples/hive/user_post.txt’
OVERWRITE INTO TABLE posts;
LOAD DATA LOCAL INPATH:从本地文件系统加载数据。‘examples/hive/user_post.txt’:指定源数据文件的路径。OVERWRITE INTO TABLE posts:将数据覆盖写入到posts表中。
在 Hive 中,表本质上对应 HDFS 中的一个目录,而数据记录就是该目录下的文件。因此,加载数据的过程实际上是将本地文件复制到 HDFS 中对应的表目录下。

执行加载命令后,数据文件 user_post.txt 会被复制到 HDFS 的 /user/hive/warehouse/posts/ 目录中。我们可以通过 Hadoop 命令 hadoop fs -cat /user/hive/warehouse/posts/user_post.txt 来验证数据是否已成功加载。
第三步:查询数据 🔍
数据加载完成后,我们就可以对数据进行查询了。Hive 的查询使用 SQL 语法,但背后会转换成 MapReduce 作业来执行。
首先,我们查询表中的总记录数:
SELECT COUNT(*) FROM posts;
执行此查询时,Hive 会启动一个 MapReduce 作业。你会在输出中看到作业 ID、跟踪 URL 等信息。作业完成后,会返回结果 4,因为我们的数据文件中有四条记录。
我们也可以执行更复杂的查询,例如查找特定用户的记录:
SELECT * FROM posts WHERE user = ‘user2’;
或者,查找在某个时间点之前发布的帖子,并限制返回结果数量:

SELECT * FROM posts WHERE time < 13413431821333839 LIMIT 2;
这些查询都会触发 MapReduce 作业,并在完成后返回结果。这展示了 Hive 如何将高级的 SQL 查询自动转换为分布式的 MapReduce 任务进行处理。
第四步:删除表 🗑️
最后,当我们不再需要某个表时,可以将其删除。删除表使用 DROP TABLE 命令。
删除表的命令非常简单:
DROP TABLE posts;
执行此命令后,会发生两件事:
- Hive 会从元存储(Derby 数据库)中删除该表的元数据信息。
- Hive 会删除 HDFS 中与该表对应的目录,即
/user/hive/warehouse/posts/。
删除后,使用 SHOW TABLES; 命令将看不到 posts 表,使用 Hadoop 命令 hadoop fs -ls /user/hive/warehouse/ 也确认该目录已被删除。
总结 📝
本节课中我们一起学习了 Hive 的四个基本操作。我们创建了一个表,定义了它的结构;将本地文件的数据加载到了表中;使用 SQL 语法查询了数据,并观察到查询被转换成了 MapReduce 作业;最后,我们删除了这个表。
通过这个简单的示例,我们看到了 Hive 如何作为 Hadoop 的数据仓库工具,将结构化的数据文件映射为一张数据库表,并提供简单的 SQL 查询功能。其核心在于:
- 表 对应 HDFS 中的一个目录。
- 加载数据 就是将文件复制到该目录下。
- 查询 会触发 MapReduce 作业。
- 元数据(如表结构)存储在独立的元存储数据库中。

这使得用户无需编写复杂的 MapReduce 程序,就能利用 Hadoop 集群处理大规模数据集。在接下来的课程中,我们将探索更多 Hive 的高级功能。
069:Hive 数据加载策略 🚚

在本节课中,我们将学习如何将数据加载到 Hive 表中。我们将探讨三种主要的数据加载方法,并了解每种方法适用的场景和具体操作。
数据加载到 Hive 表的方法主要取决于两个因素。第一个因素是原始数据的位置:数据文件是位于 HDFS 中,还是位于本地文件系统中。第二个因素是数据最终在 Hive 表中的存放位置。默认情况下,加载的数据会存放在 /user/hive/warehouse/<表名> 目录下。基于这两个考虑,我们主要有三种加载数据的选项。


选项一:从本地文件系统加载数据
首先,我们来看第一种方法:从本地文件系统加载数据。这种方法适用于数据文件最初存储在你的本地机器上。
假设数据文件位于本地文件系统的 ~/examples/hive/user_posts 目录下。如果你想将这个文件加载到名为 posts 的 Hive 表中,可以使用以下语法:
LOAD DATA LOCAL INPATH ‘<input_path>’ INTO TABLE posts;
这里的 LOCAL 关键字表示源文件位于本地文件系统。<input_path> 可以是相对路径或绝对路径。执行此命令后,Hive 会将本地文件复制到 HDFS 的 /user/hive/warehouse/posts 目录下(如果表不属于任何特定数据库模式)。如果表属于某个数据库模式,则路径会变为 /user/hive/warehouse/<schema_name>.db/<table_name>。
选项二:从 HDFS 位置加载数据
上一节我们介绍了从本地加载数据,本节中我们来看看第二种方法:从 HDFS 位置加载数据。这种方法适用于数据已经存在于 HDFS 中的情况。
如果数据文件已经存储在 HDFS 中,例如路径为 /bdp/hive/posts/user-posts,你可以使用以下命令将其加载到 Hive 表:
LOAD DATA INPATH ‘<hdfs_file_path>’ INTO TABLE posts;
请注意,这里没有 LOCAL 关键字,这表明路径指向的是 HDFS 文件。此命令会将 HDFS 中的指定文件复制到 Hive 表的仓库目录中(同样是 /user/hive/warehouse/posts 或相应的模式目录下)。
选项三:使用外部表指向现有数据

前面两种方法都涉及数据的复制操作。现在,我们来看第三种更高效的方法:创建外部表来指向 HDFS 中已存在的数据,而无需进行数据复制。
这种方法的核心是使用 CREATE EXTERNAL TABLE 语法。在创建表时,除了定义表结构,还需要指定数据在 HDFS 中的确切位置。以下是操作步骤:
- 首先,确认数据已存在于 HDFS 的特定目录中,例如
/bdp/hive/posts。 - 在 Hive 中,使用以下命令创建外部表:

CREATE EXTERNAL TABLE posts (
-- 在这里定义你的列和数据类型
...
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ‘,’ -- 根据你的数据格式修改
LOCATION ‘/bdp/hive/posts’;


LOCATION 子句指明了数据所在的 HDFS 目录。创建完成后,你可以直接查询这张表,Hive 会自动读取指定位置的数据。
使用外部表有两大优势:
- 无需数据移动:节省了复制数据的时间和存储开销。
- 数据安全:删除(
DROP)外部表时,只会删除表的元数据,而不会删除 HDFS 上的原始数据文件。
相比之下,使用前两种 LOAD DATA 方式创建的表是内部表(Managed Table),删除内部表会同时删除表和其在 HDFS 上的数据。
为了更直观地理解,以下是一个简单的操作演示流程:
- 登录 HDFS,查看已存在的数据目录和文件。
- 启动 Hive。
- 使用
CREATE EXTERNAL TABLE语句创建指向该目录的表。 - 执行
SELECT * FROM posts;查询,验证可以直接访问数据。 - 执行
DROP TABLE posts;删除表。 - 再次检查 HDFS,确认原始数据文件仍然存在。
这种方法体现了“数据湖”的一个核心思想:先将数据以原始格式组织存放在 HDFS 中,然后通过 Hive 等工具在其上创建元数据层(即外部表),从而实现通过 SQL 灵活查询,而无需改变数据本身的存储位置和格式。
总结
本节课中我们一起学习了三种将数据加载到 Hive 表的方法:
- 从本地文件系统加载:使用
LOAD DATA LOCAL ...,数据从本地复制到 Hive 仓库。 - 从 HDFS 加载:使用
LOAD DATA ...(无 LOCAL),数据在 HDFS 内部进行复制。 - 创建外部表:使用
CREATE EXTERNAL TABLE ... LOCATION,直接指向 HDFS 现有数据,无需复制,且删除表时不删除数据。

理解这些策略有助于你根据数据的初始位置和管理需求,选择最高效、最安全的数据接入方式。
070:Hive语句解析 🗂️


概述
在本节课中,我们将学习Hive中的各种详细语句。主要内容包括如何创建和删除数据库、创建和删除表、修改表结构,以及如何使用SELECT语句和内置函数来查询Hive表。
创建与删除数据库 🗃️
就像在MySQL中可以有模式一样,在Hive中你也可以创建数据库或模式。创建数据库的方法是执行 CREATE DATABASE 命令,后跟数据库名称。
例如,执行 CREATE DATABASE custDB 会为“customer”创建一个数据库。这将在 /user/hive/warehouse 目录下创建一个名为 custDB.db 的文件夹。请注意 .db 的表示法,这就是数据库目录的名称。同时,也会在Derby元存储中创建一个对应名称的条目。
如果你想使用某个数据库,只需执行 USE database_name,这样你就进入了该特定模式或数据库的上下文中。之后,在该数据库内创建的所有表都会位于该数据库名称下的目录中。例如,如果你有一个名为 table1 的表,它将被创建在 custDB.db 目录内。
如果你没有任何数据库,Hive会使用默认数据库的概念。在这种情况下,表将直接创建在 /user/hive/warehouse 目录下。在前面的章节中,我有一些表但没有创建任何数据库,那时表就是直接创建在 /user/hive/warehouse 下的,这就是默认数据库。
你也可以删除一个数据库。执行 DROP DATABASE 命令会从元存储中删除数据库信息,同时也会删除对应的 .db 目录。但此命令只有在数据库为空时才能成功执行。换句话说,你只有在删除了数据库中的所有表之后,才能删除该数据库。这是出于保护目的。
创建与删除表 📊
你可以在Hive中创建表。我使用 CREATE TABLE 语法。我们在上一节已经介绍过 CREATE TABLE 的语法,这里只是想告诉你创建表是可行的。别忘了,你既可以创建内部表,也可以创建外部表。
在创建外部表时,你需要定义目录的位置。如果你已经以某种方式布置好了目录,并且所有文件都以特定方式出现,那么你可能希望使用外部表的方法。外部表的另一个优点是,当你删除表时,它不会删除与该表关联的文件夹。当然,正如我提到的,如果是内部表,删除表会删除其下的所有文件;如果是外部表,则只从元存储中删除信息,目录结构会得以保留。
我个人喜欢使用外部表。我将数据导入到正确的位置,然后简单地使用元存储来创建一个模式,或者在目录结构之上创建一个逻辑表,这样我就可以开始进行查询了。


SELECT语句 🔍
SELECT语句用于从Hive表中查询记录,它与SQL 92标准非常相似,实际上是它的一个子集。
以下是SELECT语句的基本用法:

- 选择特定列:你可以使用
SELECT expression1, expression2, expression3 FROM table_name,其中表达式基本上是列名或列表达式。 - 带WHERE条件:你可以使用
SELECT ... FROM ... WHERE ...来筛选记录。 - 排序与限制:你可以使用
SELECT ... FROM ... WHERE ... ORDER BY ...进行排序。同时,由于在Hive中进行SELECT操作时,你可能要处理数百万甚至数十亿条记录,你可能只想获取前10条或前100条,这时可以使用LIMIT子句。 - 分组:你可以使用
SELECT ... FROM ... GROUP BY ...对结果进行分组。 - 选择所有列:你可以使用
SELECT * FROM table_name选择所有列。如果你还记得,执行SELECT *不会运行MapReduce作业,因为它认为这是一个简单的扫描操作,所以它只是扫描表并输出结果。

ALTER语句 🔧
ALTER语句用于修改表定义,例如添加列、删除列,甚至更改列的类型。你可能需要查阅文档来了解如何修改表。顺便说一下,文档可以在线获取,你可以直接访问Hive网站查看所有语法。
数据类型与模式违规 ⚠️
以下是Hive中可用的不同列数据类型:
- 整数类型:
TINYINT(1字节)、SMALLINT(2字节)、INT(4字节)、BIGINT(8字节)。你可能想用BIGINT来表示时间戳,当然也可以直接使用TIMESTAMP类型。 - 浮点与双精度:
FLOAT、DOUBLE。 - 字符串:
STRING用于表示字符串。 - 布尔类型:
BOOLEAN,取值为TRUE或FALSE。
这里存在一个模式违规的问题:如果你尝试插入不符合预定义模式的数据会发生什么?考虑到在Hive中,当你向Hive表插入记录时,实际上只是将文件复制到一个目录中。而在关系数据库中插入记录时,会进行模式检查。但在Hive这里,没有进行模式检查。因此,完全有可能你定义了一个模式,但文件中的数据记录并没有真正遵循该模式。那么,当你尝试检索不遵循模式的记录时会发生什么?
这里有一个例子:我有一个表,表中的记录不一致。表中有 userID、blogName 和 timestamp 字段。在所有三种情况下,除了一个案例外,其他都正常。在那个异常案例中,时间戳是 2012-01-05,而不是使用某种Unix时间戳,它使用了 YYYY-MM-DD 格式,这是不正确的。
如果你对与此文件关联的 post 表执行 DESCRIBE 命令,你会注意到数据类型是 STRING、STRING 和 BIGINT。timestamp 列应该是 BIGINT 类型,但它不是一个 BIGINT,而是一个日期,所以这不是正确的列值。
但是,如果你在这种情况下执行 SELECT * FROM post 会发生什么?你会注意到它打印出了 userID 和 blogName,但对于 timestamp,它实际上打印出了 NULL。因此,每当模式被违反时,它实际上会在这种情况下打印 NULL,或者更准确地说,将其评估为 NULL。
我想说明的观点是,由于Hive的特性——你有一个模式,但你可以通过简单地复制坏记录来绕过模式并将记录插入表中——当你使用SELECT语句查询时,模式检查才会启动,此时它会将无效列转换为 NULL。
内置函数 🛠️
Hive还拥有多个内置函数。包括数值函数、字符串函数、条件函数、日期函数、聚合函数等。我用了“……”来表示还有很多种函数。
Hive还支持一种机制来提供用户定义函数。如果你不喜欢这里的任何函数,欢迎你用Java编写自己的用户定义函数,并将其作为Hive集合的一部分。
如果你有兴趣获取所有不同函数的完整列表,这里有一个链接,你可以访问它以获取所有函数的列表。
我想向你展示一些函数,以下是一些数值函数的例子:你可以进行四舍五入、取整、正弦、余弦、绝对值、平方根等,可以进行各种计算。这些函数可以应用于列。
还有字符串内置函数:获取字符串长度、连接两个字符串、转换为大写、转换为小写、修剪空格,你甚至可以进行正则表达式替换。
还有日期函数:你可以从Unix时间戳转换,转换为日期,从日期中提取年、月、日等。同样,我用了“……”表示还有更多函数。
然后是聚合函数:例如,求和、平均值、最小值、最大值、标准差、百分位数等,为数据科学活动提供了各种可用的函数。


总结

在本节中,我们学习了各种Hive详细语句,例如创建和删除数据库、创建和删除表、修改表结构。我们还学习了如何使用SELECT语句和内置函数来查询Hive表。
071:Hive 分区管理 🗂️


在本节课中,我们将学习如何在 Hive 中创建、管理以及使用分区。分区是一种优化大数据查询性能的重要技术,它通过将数据表划分为更小的、易于管理的片段来实现。
分区概述
为了提高查询性能,Hive 提供了将数据在表中进行分区的能力。分区列的值实际上将表划分为多个段,在查询时,可以忽略整个段或整个分区。这与关系数据库管理系统中的分区概念非常相似。
需要认识到,分区必须由用户正确创建。在插入数据时,必须指定数据所属的分区。在查询时,Hive 会尽可能自动过滤掉不需要的分区。
理解分区:一个示例场景
理解分区的最佳方式是通过一个场景。假设我们有一个名为 posts 的表,我们希望创建一个带分区的表。
以下是创建表的语句:
CREATE TABLE posts (
user_id STRING,
blog_post STRING,
timestamp STRING
)
PARTITIONED BY (country STRING);
在 CREATE TABLE 语句和列定义之后,是分区部分。这里我们指定按 country(字符串类型)对这个表进行分区。
通常,分区是在列上创建的。虽然我们定义的列中没有名为 country 的列,但在 Hive 中,你可以将分区列 country 视为表的一个实际列。事实上,当你使用 DESCRIBE posts; 命令时,会看到它有 user_id、blog_post、timestamp 和 country 四个列。
如果你输入 SHOW PARTITIONS posts;(这是一个内置命令),会发现它没有显示任何分区。这表明我们知道它是一个分区表,但目前还没有任何分区。
向分区表插入数据
如前所述,用户在使用分区表时必须小心。当向分区表插入记录时,需要明确声明这些记录属于哪个特定分区。
例如,对于一个像 posts 这样的分区表,你不能简单地从本地文件系统加载数据。以下命令会报错:
LOAD DATA LOCAL INPATH '/path/to/data' INTO TABLE posts; -- 错误!
错误信息会提示你需要指定分区。这就是向分区表加载数据时需要特别注意的地方。
正确的方式是,你必须指定分区标识:
LOAD DATA LOCAL INPATH '/path/to/us_posts.txt'
OVERWRITE INTO TABLE posts
PARTITION (country='US');
这条命令表示,该特定文件中的所有记录都属于分区 country='US'。这里,country 是分区列名,'US' 是其值。
同样,我们可以加载另一个分区的数据:
LOAD DATA INPATH '/path/to/au_posts.txt'
OVERWRITE INTO TABLE posts
PARTITION (country='AU');
这样,我们就为 posts 表创建了另一组记录,这次是澳大利亚的博客文章。
现在,当你再次执行 SHOW PARTITIONS posts; 时,会看到有两个分区:country=AU 和 country=US。这正是我们通过分区加载插入的两组数据。
分区与目录的映射
一个有趣的现象是,当创建分区时,这些分区会被映射到目录中。回想一下,我们在介绍 Hive 时提到,Hive 将数据库的概念映射到目录、文件和文件内容。分区同样被映射到目录。
posts 表实际上是默认数据库的一部分,因此它出现在 /user/hive/warehouse/posts 目录下。但请注意,分区目录的名称就是“分区列名=值”的形式。
因此,数据(分区数据)所在的目录实际上是列名 country 等于其特定值。对于 country='US' 分区也是如此。我想强调的重点是:分区本质上就是目录,更具体地说,是你表目录下的子目录。
查询分区表

现在,当你查询分区表时,例如:
SELECT * FROM posts WHERE country='US' AND user_id='user1';
你的查询条件是 country='US'。由于我们知道 country 是一个分区列,这意味着包含所有美国博客文章的整个目录(及其所有文件)将在查询时被考虑。当为此作业运行 MapReduce 时,它不会将其他分区的文件作为 MapReduce 的输入。这极大地提高了查询效率,因为它避免了扫描不相关的数据。
本节总结

在本节课中,我们一起学习了 Hive 分区的核心概念。我们了解了分区如何被创建和管理为表的子文件夹,以及如何利用分区来提升大数据查询的性能。关键点包括:分区通过 PARTITIONED BY 子句创建,数据加载时必须指定目标分区,分区在物理上体现为 HDFS 上的目录结构,并且在查询时 Hive 可以利用分区信息进行高效的数据过滤。
072:Hive中的联结操作实现 🧩

在本节课中,我们将学习如何在Hive中对表进行联结操作。我们将讨论内联结和外联结,并了解其具体语法和结果差异。相比复杂的MapReduce编程,Hive中的联结操作要简单直观得多。
联结操作概述
上一节我们介绍了Hive的基本查询,本节中我们来看看如何将多个表的数据组合在一起。Hive中的联结概念允许你基于某个键将多个表连接起来,这与关系型数据库管理系统中的联结操作非常相似。
如果你还记得在MapReduce中执行联结是多么困难,那么你会发现Hive的联结操作非常简单。Hive支持内联结和外联结。在外联结中,它还支持左外联结、右外联结和全外联结。Hive中的默认联结是内联结。
数据表示例
让我们看一个联结的场景。假设我们有两个表:一个帖子表和一个点赞表。
以下是帖子表的内容,我们通过执行 SELECT * FROM post 来查看结果。在帖子表中,我们有三列:用户ID、博客故事以及博客发布的时间。
SELECT * FROM post;
以下是点赞表的内容,我们通过执行 SELECT * FROM likes 来查看结果。在点赞表中,我们有用户ID、点赞次数以及最后一次点击点赞按钮的时间戳。
SELECT * FROM likes;
我们的目标是通过不同的联结方式,将这两个表的数据组合起来。
内联结
在内联结中,只有当键匹配时,行才会被联结。假设我们有两个表:表一和表二,它们各自拥有键。结果集中将只包含那些键值匹配的行,不匹配的行则不会被包含。
让我们看一个内联结的例子。几秒钟前你看到了帖子表和点赞表,现在我们来将这两个表进行内联结。
联结的语法如下:
SELECT P.*, L.*
FROM post P
INNER JOIN likes L
ON P.user = L.user;
在这个查询中,P.* 表示帖子表的所有列。注意 FROM post P 和 likes L,这里的 P 和 L 是表的别名。这个查询的意思是:我希望基于用户ID来联结帖子表和点赞表。如果 P.user 和 L.user 的值匹配,那么这两条记录就会被联结起来。当然,我会打印出帖子表的所有列和点赞表的所有列。
执行这个查询后,你会注意到在数据场景中,是记录4和记录5,即用户4和用户5在两个表中都有匹配。因此,结果是用户4和用户5的数据,并且它给出了帖子表的所有列和点赞表的所有列。
外联结
外联结有三种类型:左外联结、右外联结和全外联结。让我们先看看左外联结。
左外联结
在左外联结中,无论第一个表(左表)的行在第二个表中是否有匹配,这些行都会被包含在结果中。

以下是左外联结的特点:
- 所有来自左表的记录都会出现在结果中。
- 对于左表中那些在右表没有匹配的行,来自右表的列值将被设置为
NULL。 - 不与另一表联结的行仍然会包含在结果中。
让我们看一个左外联结的例子。我们再次使用帖子表和点赞表。
SELECT P.*, L.*
FROM post P
LEFT OUTER JOIN likes L
ON P.user = L.user;

这里我将帖子表与点赞表基于用户ID进行联结,但这次使用的是左外联结。我打印出帖子表和点赞表的所有列。
你会注意到,它引入了帖子表中的所有记录:用户1、用户2、用户4和用户5。当它在另一个表中找不到匹配的列时,它直接使用 NULL 作为列值。因此,你会看到用户1和用户2没有出现在点赞表中,所以对应的列被设为 NULL。这就是左外联结。
右外联结
右外联结与左外联结相反,无论是否匹配,第二个表(右表)的所有行都会被包含在结果中。
以下是右外联结的特点:
- 所有来自右表的记录都会出现在结果中。
- 对于右表中那些在左表没有匹配的行,来自左表的列值将被设置为
NULL。 - 不与另一表联结的行仍然会包含在结果中。
让我们看一个右外联结的例子。我有帖子表和点赞表。
SELECT P.*, L.*
FROM post P
RIGHT OUTER JOIN likes L
ON P.user = L.user;
我将帖子表与点赞表进行右外联结,联结键是用户ID。在这种情况下,我将引入右表(即点赞表)中的所有记录。所以你会看到用户4、用户5、用户6和用户7出现。但对于那些在左表(即第一个表或帖子表)中没有对应记录的行,其属性或列将被设置为 NULL。请注意,这些列被设置为 NULL,而其他列则正常显示。
全外联结

在全外联结中,来自两个表的所有行都会被包含在结果中。对于不匹配的行,来自另一个表的列将被设置为 NULL。不与另一表联结的行仍然会包含在结果中。
让我们看一个全外联结的例子。这里我们再次使用帖子表和点赞表。
SELECT P.*, L.*
FROM post P
FULL OUTER JOIN likes L
ON P.user = L.user;

它将引入所有表中的所有记录。所以我们将看到用户1、用户2、用户4、用户5、用户6、用户7,所有记录都会出现。在那些没有联结的行中,列仍然会出现,并且会被设置为 NULL,正如你在所有这些情况中所看到的。这就是全外联结。
总结

本节课中我们一起学习了在Hive中跨表执行联结操作是多么容易。请记住,当你在Hive中执行联结时,它们实际上会被转换成一个MapReduce作业。但无论我们需要执行内联结还是其他联结,编写起来都像写一个SELECT语句一样简单。这与我们在Java MapReduce编程中看到的复杂性形成了鲜明对比。
073:Hive模块总结 🎯

概述
在本节课中,我们将总结Hive的核心概念与操作。我们将回顾Hive的架构、数据加载方式、常用语句以及性能优化特性,帮助初学者系统理解Hive在大数据处理中的角色与使用方法。
Hive架构概述 🏗️
上一节我们介绍了Hadoop平台的基础,本节中我们来看看Hive如何在其之上工作。
Hive架构将高级查询语言HiveQL转换为MapReduce程序,并将这些MapReduce程序作为作业提交到Hadoop集群进行处理,最终将结果返回给客户端。因此,Hive实质上是构建在Hadoop平台之上的一个数据仓库工具。
核心转换流程可概括为:
HiveQL → MapReduce程序 → Hadoop作业 → 处理结果 → 返回客户端
Hive环境设置 ⚙️
设置Hive环境非常简单。
以下是基本步骤:
- 下载Hive安装包。
- 解压压缩包(tarball)。
- 通过设置名为
HADOOP_PREFIX的环境变量,指向Hadoop的安装目录。
关键环境变量设置示例:
export HADOOP_PREFIX=/path/to/your/hadoop/installation

数据加载到Hive 📥
将数据加载到Hive中是一个直接的过程。
主要有两种数据加载方式:
- 从本地文件系统加载:将文件复制到HDFS中。
- 从HDFS直接加载:如果数据已存在于HDFS中,可以直接指向HDFS中的数据路径,无需复制到特定文件夹。
Hive语句与功能 📝
Hive提供了一系列类SQL的语句来操作数据和元数据。
以下是主要的Hive语句类型:
- 数据库操作:
CREATE和DROP语句用于创建和删除数据库。 - 表操作:
CREATE、DROP和ALTER TABLE语句用于创建、删除和修改表结构。 - 数据查询:
SELECT语句的多种变体,用于数据查询。虽然其功能不如SQL-92标准全面,但Hive 0.14.0版本提供了足够进行查询操作的子集。 - 函数:支持内置函数,也允许用户自定义函数(UDF)。
- 表分区:支持对表进行分区,主要出于性能优化的考虑。
- 表连接:支持执行内连接(INNER JOIN)和外连接(OUTER JOIN),默认的连接类型是内连接。
总结
本节课中我们一起学习了Hive模块的核心内容。我们了解到Hive是一个将HiveQL转换为MapReduce任务并在Hadoop上运行的架构。其环境配置简单,数据加载灵活。Hive提供了丰富的类SQL语句,包括数据定义、查询、函数以及分区和连接等高级功能,使得在Hadoop上进行数据分析变得更加便捷高效。
074:Pig导论 🐷
在本节课中,我们将学习如何使用一种名为Pig的高级编程语言在Hadoop环境中进行大数据分析。我们将从Pig的概述和架构开始,逐步学习其安装、交互式环境、核心语言特性以及各种操作符的使用。

概述与架构 🏗️
上一节我们深入探讨了Hadoop架构、HDFS编程以及MapReduce的Java API细节。本节中,我们来看看如何利用更高级的编程语言Pig来简化Hadoop上的大数据分析工作。
Pig提供了一个名为Pig Latin的高级数据流语言,它允许用户编写复杂的数据转换,而无需编写底层的MapReduce Java代码。Pig Latin脚本最终会被编译成一系列的MapReduce作业,在Hadoop集群上执行。
Pig的架构主要包括两个组件:
- Pig Latin: 用于描述数据流的数据处理语言。
- 执行引擎: 负责将Pig Latin脚本编译并优化为MapReduce作业,然后在Hadoop上执行。
安装与设置 ⚙️
为了开始使用Pig,我们需要在已有的Hadoop环境中进行安装和配置。我们将使用与安装Hive时相同的Hadoop环境。
以下是安装Pig的主要步骤:
- 从Apache官网下载稳定版本的Pig压缩包。
- 将压缩包解压到合适的目录,例如
/opt/pig。 - 配置环境变量,将Pig的
bin目录添加到系统的PATH中。 - 设置
PIG_HOME环境变量指向Pig的安装目录。 - 配置Pig以使用正确的Hadoop版本(通过
PIG_CLASSPATH环境变量或pig.properties文件)。
完成这些步骤后,Pig就可以与Hadoop协同工作了。

Grunt交互式Shell 🐚
Pig提供了一个名为Grunt的交互式命令行Shell,方便用户快速测试和执行Pig Latin语句。
启动Grunt Shell后,你可以直接输入Pig Latin命令,它们会被立即执行。这对于学习和调试脚本片段非常有用。例如,你可以使用LOAD命令加载数据,使用DUMP命令查看结果。
Pig Latin语言基础 📝
Pig Latin是Pig使用的数据流语言。它的设计目标是让数据转换操作更直观、更易于编写。
Pig Latin脚本由一系列语句组成,每个语句以分号结束。一个基本的Pig Latin脚本通常遵循“加载-转换-存储”的模式。核心概念包括关系(类似于数据库中的表)、字段和数据类型。
一个简单的Pig Latin语句示例如下:
records = LOAD 'input/data.txt' USING PigStorage(',') AS (name:chararray, age:int, city:chararray);
filtered = FILTER records BY age > 18;
STORE filtered INTO 'output/adults';
数据类型与模式 📊
Pig Latin支持多种数据类型,包括基本类型和复杂类型,这为处理结构化、半结构化和嵌套数据提供了灵活性。
以下是Pig支持的主要数据类型:
- 基本类型:
int(整型)、long(长整型)、float(浮点型)、double(双精度浮点型)、chararray(字符串,相当于Java的String)、bytearray(字节数组)、boolean(布尔型)、datetime(日期时间)。 - 复杂类型:
tuple(元组,有序字段集合)、bag(包,元组的无序集合,相当于多行数据)、map(映射,键值对集合)。
模式(Schema)用于为数据定义字段名和类型,它可以在LOAD语句中使用AS子句指定,也可以在后续转换中推断或声明。
核心关系型操作符 ⚙️
Pig Latin提供了一系列关系型操作符,用于对数据进行过滤、分组、排序和聚合等操作。这些操作符是编写数据处理脚本的基础。
以下是Pig Latin中的核心关系型操作符:
LOAD: 从文件系统(如HDFS)加载数据。STORE: 将数据存储到文件系统。FILTER: 基于条件过滤行(元组)。FOREACH ... GENERATE: 对每一行数据进行转换,生成新的列或修改现有列。GROUP: 根据一个或多个键对数据进行分组。COGROUP: 对两个或更多关系进行分组,常用于连接操作的前置步骤。JOIN: 连接两个或多个关系。UNION: 合并两个或多个关系。SPLIT: 根据条件将一个关系拆分成多个关系。ORDER BY: 根据指定字段对关系进行排序。DISTINCT: 去除关系中的重复行。LIMIT: 限制输出的行数。
关系连接操作符 🔗
在数据分析中,经常需要合并来自不同数据集的信息。Pig Latin提供了强大的连接操作符来实现这一目的。
JOIN操作符用于基于共同的键(字段)合并两个或多个关系。Pig支持内连接、外连接等多种连接类型。例如,将用户信息表与订单表通过用户ID进行连接,可以生成包含用户详细信息的订单列表。
调试操作符 🐞
为了帮助开发者更高效地编写和调试Pig Latin脚本,Pig提供了一些专门的调试操作符。
以下是常用的调试操作符:
DUMP: 将关系的内容输出到控制台。这是查看中间结果最直接的方式。DESCRIBE: 显示关系的模式(字段名和类型)。EXPLAIN: 显示Pig Latin语句的执行计划,包括它将如何被转换成MapReduce作业。这对于性能调优非常有帮助。ILLUSTRATE: 使用数据的样本逐步展示操作的执行过程,让你直观地看到每一步转换对数据的影响。
总结 📚
本节课我们一起学习了Apache Pig,这是一个构建在Hadoop之上的高级数据流处理平台。我们从Pig的概述和架构入手,学习了如何安装和设置Pig环境,并体验了Grunt交互式Shell。接着,我们深入探讨了Pig Latin语言的基础、丰富的数据类型与模式系统。然后,我们详细介绍了用于数据转换的核心关系型操作符和用于合并数据集的连接操作符。最后,我们了解了一些用于开发和调试脚本的实用操作符。

通过Pig,你可以用更简洁、更声明式的代码来表达复杂的数据处理逻辑,而无需直接编写冗长的MapReduce Java程序,从而大大提高了大数据分析的开发效率。
075:Pig架构与核心特性 🐷


在本节课中,我们将学习Apache Pig的概述、架构及其核心特性。Pig是一个用于分析大型数据集的高级平台,它简化了在Hadoop上进行数据处理的过程。
Pig概述与架构
上一节我们介绍了MapReduce编程的复杂性。本节中,我们来看看Pig如何解决这些问题。
MapReduce编程,尤其是使用Java进行MapReduce编程,具有挑战性。它要求开发者不仅会编写Java程序,还需要理解Google最初提出的分布式计算范式。编写一个简单的程序(如词频统计)可能需要超过100行代码。
为了解决这个问题,雅虎公司开发了Pig。
什么是Pig?
Pig是一个用于分析大型数据集的平台,其核心是一个用于表达数据分析程序的高级语言。Pig是构建在Hadoop之上的抽象层,它提供了一个专为数据处理设计的高级编程语言——Pig Latin。该语言会将你的Pig脚本转换为MapReduce程序,并在Hadoop集群上执行。
Pig是一个由Apache软件基金会管理的开源项目。
Pig的特性
以下是Pig的一些关键特性:
- 灵活的数据处理:Pig可以在有或没有预定义数据类型和模式(Schema)的情况下工作。与Hive不同,你无需先创建表结构,Pig可以自动加载数据。
- 丰富的数据操作:你可以对数据集进行加载、过滤、排序、分组(Group)、连接(Join)等操作。
- 可扩展性:Pig提供了许多内置函数。如果这些函数不满足需求,你还可以使用Java编写自己的用户定义函数(UDF)。
Pig使用示例
为了更好地理解Pig的数据流处理思想,让我们看一个例子。
假设我们有两个数据集:用户记录和页面访问记录。数据科学家想找出“18至25岁用户访问量最高的五个页面”。
使用Pig Latin,解决思路是一个清晰的数据流:
- 加载用户数据。
- 使用
FILTER操作符按年龄(18-25岁)过滤用户。 - 加载页面访问数据。
- 使用
JOIN操作符将过滤后的用户数据与页面数据连接起来。 - 使用
GROUP操作符按URL分组。 - 使用
COUNT函数统计每个URL的点击量。 - 使用
ORDER操作符按点击量降序排序。 - 使用
LIMIT操作符取前5条结果。
这个过程体现了Pig作为数据流语言的本质:通过一系列操作符逐步转换数据,最终得到结果。
Pig的主要应用场景
Pig主要有两大应用场景:
- ETL(提取、转换、加载):这是Pig的经典用例。由于Pig不需要初始模式,非常适合用于数据清洗、格式转换,以及将数据从一处迁移并加载到另一处。
- 数据研究与探索:当面对原始数据,需要探索其中规律或模式时,Pig Latin语言是数据科学家和分析师的理想工具。它允许你灵活地处理数据,快速获得洞察,从而可能催生新的业务流程。
关于Pig的趣谈
Pig这个名字并非缩写,它源于项目早期研究人员的随意命名,并最终被保留下来。围绕Pig有一个有趣的类比:
- Pigs eat anything(猪什么都吃):类比Pig Latin语言可以处理任何数据,无论其是否有元数据。
- Pigs live anywhere(猪随处可居):Pig的长期目标是成为分布式数据处理的通用语言,而不仅限于Hadoop。
- Pigs are domesticated animals(猪是家养动物):Pig被设计成可由用户高度控制和定制,例如通过编写UDF。
- Pigs fly(猪会飞):意指Pig处理数据非常快速。经过优化,Pig Latin脚本生成的MapReduce作业运行效率已接近手动编写的代码。

Pig的架构组件

现在,我们来深入了解Pig的架构组成。
Pig的核心架构包括以下几个部分:
- Pig Latin语言:基于命令的数据转换语言。
- Pig编译器:负责将Pig Latin脚本转换为一个或多个MapReduce作业。编译器还会尝试优化执行计划。
- 执行环境:支持两种模式——本地模式(Local Mode)和MapReduce模式。
- 交互式Shell(Grunt):类似于Hive Shell,用于交互式地执行Pig命令。
- 批处理模式:可以将Pig脚本写入
.pig文件进行批量执行。 - PigServer:一个Java类,允许在Java程序中通过JDBC-like接口调用Pig。
- PigPen:Eclipse IDE的插件,支持文本和图形化脚本编辑。
Pig的执行模式
Pig可以在两种模式下执行:


- 本地模式(Local Mode):在此模式下,Pig脚本在单个JVM中执行,使用本地文件系统。它不涉及Hadoop集群,非常适合用于开发和调试。
- MapReduce模式:在此模式下,Pig Latin代码被转换为MapReduce作业,并提交到Hadoop集群(完全分布式、伪分布式等)上执行。这是生产环境的标准模式。
在MapReduce模式下,无论你是通过Grunt Shell还是Java程序(PigServer)提交脚本,Pig执行环境都会将其翻译成MapReduce作业,与Resource Manager、Node Manager等Hadoop守护进程通信,并最终将结果返回。
总结

本节课中,我们一起学习了Apache Pig的核心知识。我们了解到Pig是一种高级语言,能够轻松处理Hadoop集群上的数据,而无需编写复杂的MapReduce代码。Pig最初由雅虎为数据科学家开发,现已成为一个被众多公司使用的开源项目。

Pig的核心是Pig Latin语言,用于编写数据处理脚本。这些脚本可以在本地模式(用于开发测试)或MapReduce模式(用于生产)下运行。Pig的设计理念使其特别适合ETL和数据探索任务,其灵活的架构和强大的扩展性为大数据处理提供了便利。
076:🐖 Pig环境配置教程



在本节课中,我们将学习如何下载、安装、配置并运行Apache Pig的命令行界面。Pig是一个用于分析大型数据集的高级平台,它简化了Hadoop MapReduce作业的编写过程。
🗂️ 下载Pig
首先需要下载Pig。Pig是Apache的开源项目,其官方网站是 pig.apache.org。
以下是下载Pig的两种主要方式:
-
通过官方网站下载:
- 访问
pig.apache.org。 - 点击“Downloads”或“Releases”链接,进入下载页面。
- 页面会引导至镜像站点,你可以从其中一个镜像站点下载最新版本的Pig。
- 访问
-
通过Apache存档站点下载:
- 访问Apache存档站点
archive.apache.org/dist/pig。 - 在此页面,你可以下载特定版本的Pig压缩包,例如
pig-0.13.0.tar.gz。版本号会随时间更新。
- 访问Apache存档站点
📦 安装Pig
安装Pig的过程非常简单,只需解压下载的压缩文件即可。
上一节我们介绍了如何下载Pig,本节中我们来看看如何安装。具体步骤如下:
- 打开终端。
- 切换到Hadoop的安装目录:
cd /usr/local/hadoop。 - 执行解压命令。假设压缩包下载在
~/downloads目录下,命令如下:
此命令会将Pig解压到tar -zxvf ~/downloads/pig-0.13.0.tar.gz/usr/local/hadoop/pig-0.13.0目录中。
🔍 探索Pig安装目录
安装完成后,建议浏览Pig的安装目录结构,以了解其组成部分。

以下是Pig安装目录下的主要文件夹及其作用:

bin/:包含名为pig的命令行工具,这是Pig的交互式Shell(Grunt)。conf/:包含配置文件。pig.properties:用于配置Pig的各项属性。默认情况下,所有属性都被注释,你可以根据需要取消注释。log4j.properties:日志配置文件。
docs/:包含Pig的使用文档,对于学习Pig语言、脚本、命令和扩展类非常有帮助。shims/:包含使Pig能与Hadoop协同工作的Java源代码。Pig是一种旨在与多种技术协作的数据流语言,而shims使其能够与Hadoop良好集成。lib/:包含Pig Grunt Shell运行所依赖的JAR文件。scripts/:初始为空。团队工程师可以将编写好的Pig脚本放在此目录,供其他人复用。src/:包含Pig的源代码,可供查阅。tutorial/:包含一些Pig示例脚本和样本数据,有助于更好地理解Pig语言。
⚙️ 配置环境变量
为了让系统能够识别并执行Pig命令,需要设置一些环境变量。
以下是需要配置的环境变量:
PIG_HOME:指向Pig的安装目录。export PIG_HOME=/usr/local/hadoop/pig-0.13.0PATH:将Pig的bin目录添加到系统路径中,以便在终端直接使用pig命令。export PATH=$PATH:$PIG_HOME/bin
🔗 配置Pig与Hadoop通信


Pig脚本在底层需要与Hadoop通信以运行MapReduce作业,因此需要让Pig知道Hadoop的安装位置。
Pig主要通过以下两种方式定位Hadoop:
- 设置
HADOOP_PREFIX环境变量:这是推荐的方式,可以明确指定Hadoop的安装路径。Pig会据此查找Hadoop的配置文件(如etc/hadoop),以确定NameNode和ResourceManager的位置。export HADOOP_PREFIX=/usr/local/hadoop - 确保
hadoop命令在系统路径中:如果hadoop命令可以直接在终端执行,Pig会通过该命令的路径反向推导出Hadoop的主目录。

🖥️ 动手实践演示
现在,让我们通过一个实际的演示来巩固以上步骤。假设我们已登录到Hadoop虚拟机。
- 下载Pig:访问
pig.apache.org,找到最新版本(如0.13.0)的tar.gz文件并下载。 - 安装Pig:
cd /usr/local/hadoop tar -zxvf ~/downloads/pig-0.13.0.tar.gz - 配置环境变量:编辑用户配置文件(如
~/.bash_profile),添加以下行:
然后使配置生效:export PIG_HOME=/usr/local/hadoop/pig-0.13.0 export HADOOP_PREFIX=/usr/local/hadoop export PATH=$PATH:$PIG_HOME/binsource ~/.bash_profile - 验证安装:在终端输入
pig命令。如果成功,将进入Pig的Grunt Shell交互界面。输入quit;可退出。

📝 总结

本节课中我们一起学习了Apache Pig环境的完整配置流程。我们首先从官网下载了Pig安装包,然后将其解压到指定目录。接着,我们探索了Pig安装目录的结构,了解了各个文件夹的作用。之后,我们通过设置 PIG_HOME 和 PATH 环境变量使系统能够识别Pig命令。最后,我们配置了 HADOOP_PREFIX 环境变量,确保了Pig能够正确与Hadoop集群通信。完成这些步骤后,我们成功启动了Pig Grunt Shell,为后续编写和运行Pig脚本做好了准备。
077:Grunt交互式终端 🐖


在本节课中,我们将学习如何使用Grunt交互式终端与Pig进行交互。我们将了解Grunt Shell的两种运行模式,以及如何在Shell中直接执行Pig Latin语句或运行脚本。
Grunt交互式Shell简介
上一节我们介绍了Pig的基本概念,本节中我们来看看其交互式操作环境。Grunt Shell是Pig提供的交互式命令行界面。它主要支持两种运行模式:本地模式(Local Mode)和MapReduce模式(MapReduce Mode)。用户可以在Shell中直接输入Pig Latin语句,也可以执行预先写好的脚本文件。
启动Pig与运行模式
当你通过在终端输入 pig 命令来启动Pig时,就进入了我们所说的交互式Grunt Shell。
启动Pig时,你会注意到它首先尝试本地模式,然后尝试MapReduce模式,并最终默认选择MapReduce作为执行类型。这意味着,默认情况下,你执行的任何Pig脚本或语句都将作为一个MapReduce程序运行。
同时,启动Pig时会创建一个日志文件,所有出现的日志消息都会记录在其中。如果出现异常,你可能需要查看这个日志文件。
Pig实际上可以在本地模式或MapReduce模式下运行。
- 本地模式:你可以通过输入
pig -x local来显式地在本地模式下运行Pig。在此模式下,没有MapReduce程序运行,所有操作都在Pig解释器或Pig客户端上下文中执行。你会注意到它选择的文件系统是file:///,这意味着它没有使用HDFS。本地模式非常适合测试,因为你不会真正调用资源管理器或数据节点。 - MapReduce模式:这是默认模式。输入
pig -x mapreduce即可在此模式下执行Pig。它会尝试连接HDFS(例如hdfs://localhost:9000)。回忆我们安装Pig时,设置了一个名为HADOOP_PREFIX的环境变量,指向Hadoop的安装位置。Pig正是通过这个变量来定位Hadoop并运行在MapReduce模式下的。如果找不到这个变量,尝试运行MapReduce模式会导致运行时错误,Pig解释器会关闭。
Grunt Shell的实用功能
Grunt Shell内置了一些非常实用的功能,能提升操作效率。
以下是Grunt Shell提供的一些便捷编辑和操作特性:
- 命令历史记录:使用上下箭头键可以浏览和执行之前输入过的Pig语句或脚本。
- 命令内导航:使用左右箭头键可以在一个命令内移动光标,方便修正拼写错误。
- Tab键自动补全:在Grunt Shell中,Tab补全功能在两个场景下特别有用:
- 补全关系运算符命令:例如,输入
du然后按Tab,它会建议补全为dump。 - 补全关系名称:当你加载数据并赋值给一个变量(称为关系变量或关系名)后,输入该变量名的开头部分并按Tab,Shell会尝试补全这个关系名。
- 补全关系运算符命令:例如,输入
- 自定义补全:你还可以通过修改
conf文件夹下的pig.properties文件,添加自己的补全词列表。
在Grunt Shell中操作HDFS
在Grunt Shell中操作HDFS文件系统变得异常简单。回想一下,在普通终端中,你需要输入冗长的命令,如 hadoop fs -ls 或 hadoop fs -mkdir。
以下是Grunt Shell中操作HDFS的便捷方式:
- 使用
fs前缀:在Grunt Shell中,你可以直接输入fs后跟命令,例如fs -ls、fs -mkdir。这样就无需每次都输入hadoop fs。 - 内置快捷命令:Grunt Shell还提供了一些非常方便的内置命令,如
cat、mkdir、rm、cd等。你甚至不需要加fs -前缀,直接输入cat 文件名就可以查看HDFS中的文件内容。同样,cp、copyFromLocal、copyToLocal等命令也都可用。
一个简单的Pig示例
让我们看一个在Grunt Shell中运行的简单Pig例子。假设HDFS中有一个文件 /pig/a_or_z.txt,包含4行记录,每行有两列:第一列是字母(字符串),第二列是数字(整数)。
我们执行以下Pig Latin语句:

records = LOAD '/pig/a_or_z.txt' AS (letter:chararray, count:int);
records_less_than_5 = FILTER records BY count < 5;
STORE records_less_than_5 INTO '/out';
- 第一行:将文件加载到名为
records的关系变量中,并定义两个字段letter和count及其类型。 - 第二行:对
records应用FILTER操作,筛选出count小于5的记录,结果存入新变量records_less_than_5。 - 第三行:使用
STORE命令将结果保存到HDFS的/out目录。正是这个命令会触发MapReduce作业的执行。
执行Pig脚本
除了在Shell中逐行输入,你也可以将所有Pig Latin语句写在一个文件中,然后作为脚本执行。
执行脚本的基本命令格式如下:
pig script.pig(使用默认的MapReduce模式)pig -x local script.pig(在本地模式下执行)pig -x mapreduce script.pig(显式指定MapReduce模式)

关于脚本文件的位置,需要注意:
- 如果Pig运行在本地模式,它默认会在本地文件系统寻找脚本。
- 如果Pig运行在MapReduce模式,它默认会在HDFS上寻找脚本。
- 你可以显式指定脚本的来源,无论当前是哪种模式:
- 从HDFS执行:
pig hdfs://namenode:9000/pig/script.pig - 从本地文件系统执行:
pig file:///path/to/local/script.pig
- 从HDFS执行:
操作演示
(以下为演示过程摘要,展示了上述功能在实际终端中的使用)
- 登录服务器,启动Grunt Shell(输入
pig)。 - 演示HDFS命令:使用
fs -ls /查看根目录,使用ls /bdp(快捷方式)查看特定目录。 - 演示Tab补全:输入
rec并按Tab补全为records;输入du并按Tab补全为dump。 - 运行之前的Pig示例:逐行输入LOAD、FILTER、STORE语句。执行STORE时,会触发一个MapReduce作业。通过资源管理器可以查看作业进度。作业完成后,使用
cat /out/part*查看输出结果。 - 演示执行脚本:首先使用
cat查看脚本内容,然后使用exec /pig/script.pig命令执行该脚本。如果输出目录已存在,需要先使用fs -rm -r /out删除它。 - 演示通过命令行直接执行脚本:
pig -x mapreduce hdfs://localhost:9000/pig/script.pig。

总结 🎯

本节课中我们一起学习了Grunt交互式终端。我们了解到Grunt Shell可以在本地模式或MapReduce模式下运行,且默认为MapReduce模式。我们还学习了Shell内置的便捷功能,特别是如何轻松地操作HDFS文件系统。最后,我们掌握了如何在Shell中直接执行Pig Latin语句,以及如何使用 exec 命令或通过命令行来运行Pig脚本。
078:Pig Latin语言基础 🐷

在本节课中,我们将学习Pig Latin语言的基本构建块和结构。我们将重点了解其数据结构、语句、关系与字段的命名规则、注释、大小写敏感性,以及DUMP和STORE语句。

数据结构
首先,我们来了解Pig Latin的基本构建块。Pig Latin的核心数据结构包括Bag、Tuple和Field。
以下是这些概念的详细说明:
- Bag(包):Bag是元组(Tuple)的集合。你可以将其类比为数据库中的一张表。Bag用花括号
{}表示。例如:{ (tuple1), (tuple2) }。 - Tuple(元组):Tuple是一组有序的字段(Field),类似于数据库表中的一行记录。元组用圆括号
()表示。例如:(field1, field2, field3)。 - Field(字段):Field是数据的基本单元,可以是一个整数、浮点数、字符串或布尔值。它类似于数据库表中的一列。
Pig的数据结构与关系型数据库有相似之处,但也有一个关键区别:Bag中的不同Tuple可以包含不同数量的字段,这比要求所有行结构必须相同的传统数据库表更为灵活。
语句与命令
上一节我们介绍了数据结构,本节中我们来看看Pig Latin的语句。Pig Latin是一种数据流语言,每个处理步骤都会产生一个新的数据集,我们称之为“关系”。
关系名用于标识一个数据集,字段名则标识关系中的列。请看以下示例:
input = LOAD ‘input/data.txt’ AS (letter:chararray, age:int);
input = FILTER input BY age > 18;
第一条语句将文件加载到一个名为input的关系中,并定义了两个字段letter和age。第二条语句对input关系进行过滤,并将结果重新赋值给同一个关系名input。
需要注意的是,虽然可以重用关系名,但最佳实践是为每个新的关系使用不同的名称(例如adults),以提高代码的可读性。
关系名与字段名的命名规则
在编写Pig Latin语句时,为关系和字段命名需要遵循特定规则。
以下是命名时必须遵守的规则:

- 名称必须以字母开头。
- 后续字符可以是零个或多个字母、数字或下划线(
_)。 - 所有字符必须是ASCII字符。
这与其他编程语言中变量名的命名规则非常相似。
注释
为了代码的可读性和维护性,在Pig Latin中添加注释是一个好习惯。
Pig Latin支持两种注释风格:
- SQL风格单行注释:使用双连字符
--,其后的内容直到行尾都会被视作注释。 - Java风格多行注释:使用
/*和*/包裹注释内容,注释可以跨越多行。
大小写敏感性
在Pig Latin中,不同元素对大小写的敏感性不同,理解这一点很重要。
以下是关于大小写敏感性的具体规则:
- 关键字不区分大小写:例如
LOAD、USING、AS、GROUP BY、FOREACH、GENERATE等关键字,无论使用大写还是小写都可以。但通常我们约定俗成使用大写形式。 - 关系名区分大小写:例如
A、B、C作为关系名时,A和a是不同的。 - 字段名区分大小写:例如
F1、F2作为字段名时,F1和f1是不同的。 - 函数名区分大小写:例如内置函数
PigStorage()和COUNT(),必须按照正确的大小写形式书写。
DUMP与STORE语句
我们之前学习了如何定义数据处理步骤,但Pig Latin具有“惰性执行”的特性。这意味着,直到遇到DUMP或STORE这类“动作语句”时,真正的MapReduce作业才会被触发和执行。
DUMP命令用于将关系的内容输出到屏幕,通常用于调试和查看小规模样本数据。为了避免输出过多数据,可以结合LIMIT操作符使用。
records = LOAD ‘input/data.txt’ AS (name:chararray, age:int);
limited_records = LIMIT records 5;
DUMP limited_records; -- 只打印前5条记录
STORE命令用于将最终结果持久化存储到HDFS或HBase等系统中,这是处理大规模数据时的标准做法。当执行STORE命令时,Pig会构建一个“有向无环图”(DAG)来优化整个处理流程,生成并提交相应的MapReduce作业。
总结
本节课中,我们一起学习了Pig Latin语言的基础知识。
我们首先了解了其核心数据结构:Bag(元组的集合,类似表)、Tuple(有序字段集,类似行)和Field(数据单元,类似列)。接着,我们学习了Pig Latin的语句和命令,以及关系名和字段名必须遵循的命名规则。我们还知道了如何添加注释,并明确了关键字不区分大小写,而关系名、字段名和函数名区分大小写。最后,我们探讨了DUMP和STORE这两个关键的动作语句,正是它们触发了Pig作业的实际执行,将逻辑计划转化为物理的MapReduce任务。

掌握这些基础知识,是后续编写复杂Pig Latin脚本进行大数据处理的必要前提。
079:Hadoop大数据处理|Big Data Processing Using Hadoop
课程编号:P79
章节标题:Pig数据类型与模式定义 📚

在本节课中,我们将要学习Apache Pig中的数据类型与模式定义。我们将了解Pig支持的各种简单和复杂数据类型,以及如何为数据定义模式。虽然模式在Pig中是可选的,但定义模式有助于更高效、更准确地进行数据处理。
模式概述

上一节我们介绍了Pig的基本概念,本节中我们来看看Pig中的模式定义。模式在Pig中是可选的,但建议使用。模式允许你为字段分配名称,并为每个字段声明类型,从而让你能够通过应用适当的函数来操作数据。

模式类型声明实际上能带来更好的运行时错误检查,因为当你拥有模式类型时,就可以验证某些值和数据。此外,始终将数据作为整数使用比作为字符串使用效率更高。模式通常在加载操作符中定义。你也可以在流中(即文件本身内)定义模式,或者使用FOREACH操作符的AS子句动态定义模式,但最常见的是在加载操作符中定义模式。
简单数据类型
以下是Pig支持的简单数据类型:
- 整型:
int(4字节)和long(8字节)。 - 浮点型:
float(4字节)和double(8字节)。 - 布尔型:
boolean(1位)。 - 字符数组:
chararray,即字符串。 - 字节数组:
bytearray,用于表示二进制大对象(BLOB),如图像数据或其他二进制数据。
复杂数据类型
在简单数据类型的基础上,Pig还支持三种复杂数据类型:
- 元组:类似于记录,用括号
()表示,字段间用逗号分隔。 - 包:元组的集合,用花括号
{}表示,包内的元组用逗号分隔。 - 映射:键值对的组合,用方括号
[]表示,键值对格式为键#值,对之间用逗号分隔。
数据类型示例
让我们通过一个简单的示例来理解如何定义模式。假设有一个名为sfields.dat的文件,内容如下(字段间以制表符分隔):
James Bond 1950 200000.0 true
Inor Gadget 1960 123.0 false
我们可以使用以下Pig Latin语句加载数据并定义模式:
A = LOAD 'sfields.dat' AS (name:chararray, year:int, salary:float, married:boolean);
在这个例子中,我们为四个字段分别定义了名称和类型:name是字符串,year是整数,salary是浮点数,married是布尔值。加载后,关系A中的每条记录都是一个元组。
常量表示
Pig中的常量表示如下:
- 整型常量:例如
19 - 长整型常量:例如
19L - 浮点数/双精度常量:例如
19.5或19.5F、19.5D - 字符数组(字符串)常量:用单引号表示,例如
'hello' - 字节数组常量:例如
0xFF - 布尔常量:
true或false - 元组常量:
(‘John’, 25) - 包常量:
{(‘John’, 25), (‘Jane’, 30)} - 映射常量:
[‘name’#‘John’, ‘age’#25]
内置函数
Pig提供了丰富的内置函数库,可以方便地对数据进行处理。以下是主要的内置函数类别:
- 数学函数:例如
ABS(绝对值)、COS(余弦)、SIN(正弦)、LOG(对数)等。 - 字符串函数:例如
UPPER(转大写)、LOWER(转小写)、SUBSTRING(取子串)、TRIM(去除空格)等。 - 评估与聚合函数:这些函数对集合进行操作,例如
AVG(平均值)、CONCAT(连接)、COUNT(计数)、MAX(最大值)、MIN(最小值)、SUM(求和)等。 - 加载与存储函数:用于从文件系统加载数据或将数据存储回文件系统,例如
PigStorage(处理结构化文本)、BinStorage(二进制格式)、JsonLoader/JsonStorage(JSON格式)等。 - 元组、映射与包函数:例如
TOTUPLE、TOMAP、TOBAG等用于数据类型转换的函数。
所有内置函数的详细列表和用法可以在Apache Pig的官方文档中找到。
用户自定义函数

如果内置函数无法满足你的特定需求,Pig允许你创建用户自定义函数。你需要编写一个实现特定接口(如EvalFunc)的Java类,重写相关方法,将其编译并打包成JAR文件。然后,通过设置环境变量PIG_CLASSPATH指向该JAR文件,并在Pig脚本中使用REGISTER命令注册后,即可像使用内置函数一样使用你的自定义函数。
用户自定义函数与内置函数的主要区别在于:
- 内置函数无需注册,Pig已知其位置。
- 内置函数在使用时无需限定,而UDF需要先注册。
官方文档

Apache Pig提供了非常全面的官方文档,地址是:https://pig.apache.org/docs/。文档中包含了入门指南、Pig Latin语法详解、所有操作符和函数的说明、编写UDF的教程以及性能调优等信息,是学习和解决问题的重要资源。
总结
本节课中我们一起学习了Apache Pig的核心组成部分——数据类型与模式定义。我们了解到Pig支持包括整型、浮点型、字符串在内的简单数据类型,以及元组、包和映射这三种复杂数据类型。虽然模式定义是可选的,但它能提升代码的清晰度、执行效率和错误检查能力。此外,我们还探讨了Pig丰富的内置函数库以及如何通过编写用户自定义函数来扩展其功能。掌握这些基础知识,是编写高效、可靠的Pig Latin脚本来处理大规模数据集的关键。
080:核心关系运算符 🧮

在本节课中,我们将要学习Pig Latin中的核心关系运算符。这些运算符是处理和提取数据的基础工具,能够帮助你从大数据集中筛选、组织和生成所需的结果。

Distinct(去重) 🔄
上一节我们介绍了Pig Latin的基本概念,本节中我们来看看DISTINCT运算符。DISTINCT用于从关系中移除重复的元组。
其语法非常简单:DISTINCT 后跟一个关系名或别名。执行结果是获得原始记录集合中的唯一记录集,并赋值给一个新的关系名。
结果不保留原始内容的顺序,因为它可能经过MapReduce层处理,顺序可能发生变化。DISTINCT不能用于字段子集,必须作用于整个记录,即基于整个记录进行去重。
以下是DISTINCT的一个示例:
A = LOAD 'data' AS (a1:int, a2:int, a3:int);
DUMP A;
-- 输出: (1,2,3), (4,3,3), (1,2,3), (7,2,5), (8,4,3)
X = DISTINCT A;
DUMP X;
-- 输出: (1,2,3), (4,3,3), (7,2,5), (8,4,3)
原始数据中(1,2,3)和(4,3,3)各出现两次,DISTINCT操作后,我们得到了4条唯一的记录。
Filter(过滤) 🎯
FILTER运算符允许你根据某些条件从关系中选择元组。
其语法为:FILTER alias BY expression。这意味着你将筛选出真正关心的记录。
它通常用于选择你想要的数据。以下是FILTER的一个示例:
A = LOAD 'data' AS (a1:int, a2:int, a3:int);
DUMP A;
-- 输出: (1,2,3), (4,3,3), (1,2,3), (7,2,5), (8,4,3)
X = FILTER A BY a3 == 3;
DUMP X;
-- 输出: (1,2,3), (4,3,3), (1,2,3), (8,4,3)
此操作筛选出a3字段等于3的所有记录。
你还可以使用更复杂的表达式进行过滤:
X = FILTER A BY (a1 == 8) OR (NOT (a2 + a3 > a1));
这个表达式会筛选出a1等于8,或者a2与a3之和不大于a1的记录。
Split(拆分) ✂️
SPLIT是另一个核心关系运算符,它将一个关系分割成两个或更多个关系。
其语法允许你根据多个表达式将数据分配到不同的关系中。需要注意的是,根据条件,一个元组可能被分配到多个关系中。
以下是SPLIT的一个示例:
A = LOAD 'data' AS (f1:int, f2:int, f3:int);
DUMP A;
-- 输出: (1,2,3), (4,5,6), (7,8,9)
SPLIT A INTO X IF f1 < 7, Y IF f2 == 5, Z IF (f3 < 6) OR (f3 > 6);
DUMP X; -- 输出: (1,2,3), (4,5,6)
DUMP Y; -- 输出: (4,5,6)
DUMP Z; -- 输出: (1,2,3), (7,8,9)
记录(4,5,6)同时满足了进入X和Y关系的条件。
Order By(排序) 📊
ORDER BY运算符基于一个或多个字段对关系进行排序。
其语法为:ORDER alias BY field [ASC|DESC]。如果多个记录具有相同的排序键,返回的记录顺序是未定义的,并且不能保证在不同运行中保持一致,因为底层使用了MapReduce。
以下是ORDER BY的一个示例:
A = LOAD 'data' AS (a1:int, a2:int, a3:int);
DUMP A;
-- 输出: (1,2,3), (4,3,3), (7,2,5), (8,4,3)
X = ORDER A BY a3 DESC;
DUMP X;
-- 输出: (7,2,5), (1,2,3), (4,3,3), (8,4,3) -- 按a3降序排列
Limit(限制) ⛔
LIMIT运算符限制输出元组的数量。
其语法为:LIMIT alias n。它不保证返回哪n个元组,并且返回的元组在不同运行间可能变化。如果n大于或等于关系中的元组数,则返回所有元组。LIMIT在开发和调试Pig脚本时非常高效,应尽可能使用。
以下是LIMIT的一个示例:
A = LOAD 'data' AS (a1:int, a2:int, a3:int);
X = LIMIT A 3;
DUMP X;
-- 可能输出前3条记录,例如: (1,2,3), (4,3,3), (1,2,3)
Group(分组) 👥
GROUP运算符将一个或多个关系中的数据分组。
其语法为:GROUP alias BY expression。GROUP操作将具有相同组键的元组集合在一起。每次GROUP操作的结果为每个元组生成两个字段:第一个字段名为group,第二个字段采用原始关系的名称,类型为bag(包)。
以下是GROUP的一个示例:
A = LOAD 'student_data' AS (name:chararray, age:int, gpa:float);
DUMP A;
-- 输出: (John, 18, 4.0), (Joe, 18, 3.8), (Jane, 19, 3.9), (Bill, 20, 3.7)
X = GROUP A BY age;
DUMP X;
-- 输出:
-- (18, {(John,18,4.0), (Joe,18,3.8)})
-- (19, {(Jane,19,3.9)})
-- (20, {(Bill,20,3.7)})
所有年龄为18的学生被分组在一起。
For Each(遍历) 🔁
FOREACH运算符基于数据列生成数据转换。
其语法为:FOREACH alias GENERATE expression。它通常用于处理GROUP操作后产生的包(bag),遍历其中的每个元素。
以下是结合GROUP和FOREACH的示例:
A = LOAD 'student_data' AS (name:chararray, age:int, gpa:float);
B = GROUP A BY age;
-- 计算每个年龄组的学生数量
C = FOREACH B GENERATE group, COUNT(A);
DUMP C;
-- 输出: (18,2), (19,1), (20,1)
-- 从包中提取特定字段
D = FOREACH B GENERATE group, A.name;
DUMP D;
-- 输出: (18, {John, Joe}), (19, {Jane}), (20, {Bill})
并行与分区 ⚙️
许多运算符支持自定义分区器和设置Reducer数量,以实现并行处理和分区。

例如,你可以在DISTINCT操作中指定分区方式:
X = DISTINCT A
PARTITION BY org.my.partitioner
PARALLEL 10;
这允许你使用自定义的分区类(如org.my.partitioner)并设置并行度为10,从而在Pig作业中获得更好的分区和并行处理能力。

本节课中我们一起学习了Pig Latin的核心关系运算符,包括DISTINCT、FILTER、SPLIT、ORDER BY、LIMIT、GROUP和FOREACH。这些运算符是构建复杂数据处理流水线的基础,使你能够在大数据环境中高效地操作和转换数据。
081:Pig 中的关系型联结运算符解析 🧩


在本节课中,我们将学习如何在 Apache Pig 中执行关系型联结操作。我们将介绍两种主要的联结运算符:JOIN 和 CROSS,并详细探讨 JOIN 的各种类型,包括内联结和外联结。
关系型联结运算符概述

Pig 提供了两种关系型联结运算符。第一种是 JOIN,它类似于传统关系型数据库和 Hive 中的联结操作,允许你将两个数据集合并。你可以执行内联结或外联结,而外联结又分为左外联结、右外联结和全外联结三种变体。

第二种运算符是 CROSS,它允许你在两个不同的数据集之间执行笛卡尔积,你可以将其理解为矩阵的乘法运算。

Pig 中的 JOIN 操作
Pig 中的 JOIN 操作允许你联结多个数据集。在传统数据库管理系统中,我们称它们为“表”,而在 Pig 中,我们称之为“数据集”。联结可以基于单个或多个键进行,Pig 同时支持这两种概念。
与复杂的 MapReduce 联结相比,Pig 的联结操作非常简单,甚至比 Hive 的联结还要简洁。Pig 支持内联结和外联结,默认的联结类型是内联结。
内联结示例
让我们来看一个联结场景。假设我们有两个数据集:posts(帖子)和 likes(点赞)。posts 数据集包含用户ID、博客名称和发布时间。likes 数据集包含用户ID、点赞数和点赞的最后更新时间。
我们的目标是将这两个数据集联结起来。内联结是指只联结键值匹配的行,不匹配的行不会包含在结果集中。
以下是一个演示内联结的 Pig 脚本 innerjoin.pig:
-- 加载 posts 数据集
posts = LOAD '/pig/blog_data/user_posts.txt' USING PigStorage(',') AS (user:chararray, post:chararray, date:long);
-- 加载 likes 数据集
likes = LOAD '/pig/blog_data/user_likes.txt' USING PigStorage(',') AS (user:chararray, likes:int, date:long);
-- 基于 user 字段执行内联结
user_info = JOIN posts BY user, likes BY user;
-- 输出结果
DUMP user_info;
执行此脚本后,结果将只包含两个数据集中 user 字段匹配的记录(例如用户1、2、4)。结果数据集是这两个数据集字段的并集。
你可以使用 DESCRIBE 操作符来查看数据集的模式。例如,DESCRIBE user_info; 会显示新数据集的模式,其字段名会以原始关系名(如 posts::user)作为前缀。
基于多键的联结
你也可以基于多个键进行联结。在下面的例子中,我们基于 user 和 date 两个字段来联结 posts 和 likes:
user_info_multi = JOIN posts BY (user, date), likes BY (user, date);

这意味着只有当两个数据集中的用户ID和日期都匹配时,记录才会被联结。根据示例数据,可能只有一条记录满足条件。



外联结操作
上一节我们介绍了内联结,本节中我们来看看外联结。Pig 支持三种类型的外联结:左外联结、右外联结和全外联结。

左外联结
在左外联结中,第一个数据集(左侧)的所有行都会被包含在结果中,无论它们在第二个数据集中是否有匹配项。对于没有匹配的行,来自第二个数据集的列将被设置为空(NULL)。
以下是左外联结的示例脚本:
user_info_left = JOIN posts BY user LEFT, likes BY user;
DUMP user_info_left;

执行后,posts 数据集中的所有用户(1, 2, 4, 5)都会出现。用户1、2、4会与 likes 中的匹配记录联结,而用户5没有匹配项,其对应的 likes 字段将为空。

右外联结
右外联结与左外联结相反。第二个数据集(右侧)的所有行都会被包含在结果中,无论它们在第一个数据集中是否有匹配项。对于没有匹配的行,来自第一个数据集的列将被设置为空。

以下是右外联结的示例脚本:
user_info_right = JOIN posts BY user RIGHT, likes BY user;
DUMP user_info_right;

执行后,likes 数据集中的所有用户(1, 2, 3, 4)都会出现。用户1、2、4会与 posts 中的匹配记录联结,而用户3没有匹配项,其对应的 posts 字段将为空。
全外联结
全外联结会包含两个数据集中的所有行。对于匹配的行,两个数据集的数据都会显示;对于不匹配的行,来自另一个数据集的列将被设置为空。
以下是全外联结的示例脚本:

user_info_full = JOIN posts BY user FULL, likes BY user;
DUMP user_info_full;
执行后,结果将包含所有五个用户(1, 2, 3, 4, 5)的记录,并在没有匹配的地方填充空值。


CROSS 操作(笛卡尔积)
除了 JOIN,另一个关系型联结运算符是 CROSS。它计算两个或多个关系的笛卡尔积。这是一个计算开销很大的操作,应谨慎使用。
以下是 CROSS 操作的一个简单示例:
A = LOAD 'data1.txt' AS (a1:int, a2:int, a3:int);
B = LOAD 'data2.txt' AS (b1:int, b2:int);
X = CROSS A, B;
DUMP X;
假设 A 有记录 (1,2,3),B 有记录 (2,4) 和 (8,9),那么 X 将包含 A 中每条记录与 B 中每条记录组合的结果,即 (1,2,3,2,4) 和 (1,2,3,8,9)。
动手演示
现在,让我们在 Pig Grunt shell 中进行实际操作演示。首先,登录到你的 Hadoop 环境并启动 Pig。
- 查看数据文件:
hadoop fs -ls /pig/blog_data/ hadoop fs -cat /pig/blog_data/user_posts.txt hadoop fs -cat /pig/blog_data/user_likes.txt

-
进入 Pig Grunt shell:
pig -
在 Grunt shell 中,你可以逐行输入前面章节的脚本命令来执行内联结、外联结等操作。例如,输入加载数据和执行内联结的命令。
-
你也可以直接在命令行运行写好的 Pig 脚本文件:
pig -x mapreduce hdfs://localhost:9000/pig/scripts/innerjoin.pig pig -x mapreduce hdfs://localhost:9000/pig/scripts/leftouterjoin.pig
通过观察不同联结操作的输出,你可以直观地理解它们之间的区别。
总结
本节课中,我们一起学习了 Apache Pig 中用于联结数据集的两个核心关系型运算符。
JOIN运算符:用于基于一个或多个键合并数据集。它支持:- 内联结:默认类型,只返回键匹配的行。
- 外联结:包括左外联结、右外联结和全外联结,分别用于保留左侧、右侧或两侧数据集的所有行。
CROSS运算符:用于计算两个数据集的笛卡尔积,但由于其计算成本高,需谨慎使用。

Pig 的联结语法简洁高效,通过 DESCRIBE 命令可以清晰地查看联结后新数据集的模式。掌握这些联结操作是进行复杂数据分析的基础。
082:调试运算符应用 🐛


在本节课中,我们将学习Pig Latin中的四个调试运算符:DESCRIBE、DUMP、ILLUSTRATE和SAMPLE。这些工具对于开发和调试Pig脚本至关重要,能帮助你理解数据结构、查看执行流程以及在小数据集上测试程序逻辑。
概述
调试是数据处理流程中不可或缺的一环。Pig Latin提供了几个内置的调试运算符,它们能让你在不运行完整MapReduce作业的情况下,检查数据模式、查看数据样本、理解数据转换步骤。本节将详细介绍这四个运算符的用法和最佳实践。
1. DESCRIBE 运算符
DESCRIBE 运算符用于查看关系(Relation)的模式(Schema)。它返回关系中各个字段的名称和数据类型。
语法:
DESCRIBE relation_name;
例如,加载一个包含角色信息的文件并指定其模式:
roles = LOAD '/pig/fields.txt' AS (name:chararray, year_of_birth:int, salary:float, married:boolean);
执行 DESCRIBE roles; 后,会显示类似以下的模式信息:
roles: {name: chararray, year_of_birth: int, salary: float, married: boolean}
这有助于你确认数据加载是否正确,并了解每个字段的数据类型。有时数据文件可能关联了Avro模式,此时即使加载时不指定模式,DESCRIBE 也能从Avro文件中读取并显示模式信息。
2. DUMP 运算符
DUMP 运算符用于将关系的内容输出到屏幕。它会触发Pig脚本的执行,并运行相应的MapReduce作业来获取结果。
语法:
DUMP relation_name;
例如,执行 DUMP roles; 会将 roles 关系中的所有记录打印到控制台。
重要注意事项:
DUMP主要用于交互式开发和调试。它会立即执行语句,但结果不会持久化保存。- 在生产脚本中应避免使用
DUMP。因为它会强制Pig执行MapReduce作业来输出结果,这会破坏Pig的整体执行计划优化(如多查询执行),可能导致脚本运行变慢。如果你需要持久化输出,应该使用STORE命令将结果保存到HDFS目录中。
3. ILLUSTRATE 运算符
ILLUSTRATE 是一个非常强大的调试工具。它能展示一系列Pig语句的逐步执行过程。该运算符会从输入数据中抽取一个小样本,并让这个样本数据流经整个处理管道,从而让你直观地看到每一步数据转换的结果。
语法:
ILLUSTRATE alias; -- 对单个关系进行图示
ILLUSTRATE -script script_file.pig; -- 对整个脚本文件进行图示
示例:
假设有一个Pig脚本 visits.pig,其功能是根据时间戳将网站访问记录分为“近期访问”和“历史访问”两部分。
visits = LOAD '/pig/visits.txt' AS (user:chararray, site:chararray, ts:int);
recent_visits = FILTER visits BY ts >= 20071201;
historical_visits = FILTER visits BY ts < 20071201;
STORE recent_visits INTO '/pig/out/recent';
STORE historical_visits INTO '/pig/out/historical';
通过执行 ILLUSTRATE -script visits.pig,Pig会展示:
- 从
visits关系中抽取的几条样本数据。 - 经过
FILTER操作后,recent_visits和historical_visits分别包含哪些样本记录。 - 最终数据如何被
STORE到不同目录。
这种方式让你能在处理海量数据前,先用极小数据集验证业务逻辑的正确性。

4. SAMPLE 运算符
SAMPLE 运算符用于从关系中随机抽取一定比例的数据,生成一个新的数据集。这对于从生产数据集中获取小样本进行测试和分析非常有用。
语法:
sampled_relation = SAMPLE relation_name size;
其中,size 是一个介于0和1之间的浮点数,代表抽样比例。
示例:
x = SAMPLE A 0.01;
这行代码会创建关系 x,其中包含关系 A 中大约1%的随机数据。
动手演示
上一节我们介绍了四个调试运算符的概念,本节我们通过一个简单的例子来实际操作一下。

以下是操作步骤的简要说明:
- 使用
DESCRIBE:在Pig Grunt shell中加载一个带模式的文件,然后使用DESCRIBE查看其结构。 - 使用
DUMP:对加载的关系执行DUMP,将数据打印到屏幕。同时演示使用STORE命令将结果持久化到HDFS目录,并与DUMP进行对比。 - 使用
ILLUSTRATE:首先运行一个完整的Pig脚本(例如分割访问记录的脚本),观察其输出。然后,使用ILLUSTRATE -script命令在同样的脚本上运行,观察它如何展示数据流经每个步骤的样本结果。这能清晰展示ILLUSTRATE在调试时的价值。 - 使用
SAMPLE:从一个较大的关系中随机抽取一小部分数据创建新关系,以便进行快速测试。
总结

本节课我们一起学习了Pig Latin中四个核心的调试运算符:
DESCRIBE:用于获取关系的模式信息。DUMP:用于将关系的内容输出到屏幕(仅限调试)。ILLUSTRATE:用于可视化数据在有向无环图(DAG)中的处理流程,是理解复杂数据转换的强大工具。SAMPLE:用于从大数据集中随机抽取样本数据,以便进行快速原型开发和测试。

熟练掌握这些运算符,将能极大地提升你开发和调试Pig数据处理程序的效率。
083:HBase导论 🗄️

在本节课中,我们将学习Hadoop生态系统中的另一个重要子项目——HBase。HBase是一个NoSQL数据库,也被称为Hadoop数据库。它是一个构建在Hadoop之上的分布式、版本化、开源数据库,能够为你的大数据提供实时的读写访问能力。

模块概览 📋
上一节我们介绍了本课程的主题。本节中,我们来看看本模块将涵盖的具体内容。
以下是本模块将要探讨的主题列表:
- 首先,我们将概览NoSQL数据库的版图,你可以将其视为NoSQL入门或鸟瞰图。
- 由于这是一门关于使用Hadoop处理大数据的课程,我们将重点学习Hadoop的NoSQL数据库,即HBase,并概述其架构。
- 接着,我们将在开发环境中学习如何设置HBase。
- 然后,我们将研究HBase的数据模型,它与标准的关系型数据模型有所不同。
- 之后,我们将学习HBase Shell,这是一个交互式命令行工具,允许你执行CRUD操作。
- 最后,我们将了解用于对HBase数据库表执行CRUD操作的Java API。

学习目标 🎯

在了解了课程内容后,本节我们明确一下本模块的学习目标。完成本模块后,你应该能够:
以下是需要达成的具体目标列表:
- 定义NoSQL并对其进行分类。
- 描述HBase的架构及其与Hadoop的关系。
- 在开发环境中安装和设置HBase。
- 为应用程序应用HBase数据模型。
- 对比HBase数据模型与RDBMS系统的异同。
- 学习与HBase交互式Shell进行交互,以便直接在Shell中执行CRUD操作,这类似于MySQL或Oracle的命令行工具。
- 最终,能够使用Java API开发CRUD程序。


本节课中,我们一起学习了HBase的基本概念、课程内容大纲以及明确的学习目标。我们了解到HBase是Hadoop生态中用于实时访问大数据的NoSQL数据库。在接下来的章节中,我们将逐步深入这些主题,从NoSQL概览开始,逐步掌握HBase的核心知识。
084:NoSQL基础理论 🗄️


在本节课中,我们将要学习NoSQL数据库的基础理论。我们将了解NoSQL数据库兴起的背景,对比三种流行的NoSQL数据库,并理解其核心概念与适用场景。
概述:从关系型数据库到NoSQL
为了更好地理解NoSQL数据库的出现,了解1975年至今交互式应用程序的变化至关重要。这些变化主要体现在用户、应用程序和基础设施三个方面。
在1975年,应用程序通常为大约2000名用户构建。例如,AT&T的订单系统。而今天,互联网应用程序的用户起点可能就是2000人,并可能迅速增长到数百万甚至数亿,例如Facebook。
在应用程序方面,过去我们进行业务流程自动化,数据以高度结构化的格式存储在RDBMS中。如今,我们需要处理结构化、半结构化和非结构化数据,因此需要不同的机制来存储、处理和实时检索数据。

在基础设施方面,1970年代数据网络处于起步阶段,而今天我们看到遍布全球的高速数据网络。计算方面,过去是集中式计算,而今天分布式计算成为主流,我们将成百上千台计算机连接起来以解决复杂问题。

为了支持自1975年以来交互式应用程序的变化,应用程序架构已经跟上步伐。现代Web和非Web应用程序被设计为可水平扩展。你只需要在负载均衡器后面添加更多商用服务器即可。系统成本和响应时间呈线性增长。
数据库架构的滞后 😡
与应用程序架构的全面变革形成对比的是,关系型数据库系统在过去40多年里并未发生根本性改变。我们仍然在处理单一、庞大的RDBMS服务器。扩展关系型数据库系统的方式是购买更大的服务器,这导致系统成本急剧增加,并且由于单台服务器的能力有限,应用程序响应时间也会增加。
关系型数据库的扩展方案
人们采取了不同的方案来扩展关系型数据库系统,但这些方案都像是临时补救措施。

以下是几种常见的解决方案:

- 分片:其思想是分割数据,使其分布在多个服务器上。但现实是,当一个分片填满时,重新分片的过程极具破坏性。分片还会失去关系模型的一些优势,并且需要在多台服务器上维护模式,任何模式变更都需要在所有服务器上更新。
- 反规范化:其思想是放宽对严格范式(如第一、第二、第三范式)的要求。但反规范化可能导致数据在数据库中被重复存储,从而失去关系数据模型的一些重要优势。
- 分布式缓存:在数据库前部署分布式缓存技术(如Memcached),以减少对数据库的直接冲击。这种方法加速了数据读取,但对于写入操作,仍然存在“冷缓存”问题,即数据必须先到数据库,再加载到缓存,然后才能返回给客户端。此外,实现分布式缓存可能会增加一个层级,使架构变得更复杂。
仔细审视,许多RDBMS扩展方案都像是临时补救措施。我们真正需要的是一个可水平扩展的解决方案,一个可水平扩展的数据库。就像我们扩展Web服务器和应用服务器一样,我们应该能够通过添加更多商用服务器来扩展数据库服务器。NoSQL数据库正是填补了这一空白。关键在于,扩展数据库应该像添加另一台应用服务器一样简单。
什么是NoSQL?
现在,每个人都在谈论“NoSQL”这个术语。


如果你查看NoSQL市场,除了我们将在本模块讨论的Apache HBase,还有一大批其他NoSQL数据库。访问 nosqldatabase.org 网站,你可以看到完整的NoSQL数据库列表,这个列表可能随时在变化。该网站列出了150多个NoSQL数据库,并对它们进行了简要描述。我强烈建议你有机会时浏览这些不同的NoSQL数据库及其所属类别。
“NoSQL”这个术语看起来像是通过“否定定义法”来定义的,即我们说它不是SQL,或者类似的意思,仿佛它是超越SQL或不同于SQL的东西。那么,NoSQL这个术语到底是什么意思?我想在这里给你一个更清晰的NoSQL数据库图景。
那么,NoSQL到底是什么?NoSQL可以意味着“没有SQL”,也可以意味着“不仅仅是SQL”。我更喜欢使用“不仅仅是SQL”这个说法。NoSQL这个术语实际上可以追溯到Rackspace的一次会议演讲。虽然许多人将NoSQL称为“没有SQL”,但如果你看看像Cassandra这样的NoSQL数据库,它们有一种查询语言叫CQL(Cassandra查询语言)。再看看MongoDB,它们有一种查询语言叫UnQL(非结构化查询语言)。所以,它们似乎都有某种SQL,只是不是我们所说的标准SQL。无论如何,它们都希望内置某种查询语言。因此,称其为“NoSQL”听起来有点奇怪。事实上,我更愿意称之为“无关系”。如果你看看大多数NoSQL数据库,我觉得它们有一个共同点:没有关系,没有连接,可能就是一个大表或一个大的数据模型。所以我觉得“无关系”会是一个更好的术语。
无论如何,我想将NoSQL数据库分为四大类:
以下是NoSQL数据库的四种主要类型:
- 键值对数据库:例如Voldemort、Redis、Amazon Dynamo。当你插入记录时,有一个键和一个值。只要键和值可以被序列化为字节,你可以在其中放入任何内容。
- 列式数据库:例如Apache HBase和Cassandra。它们被称为列式数据库是因为有“列族”的概念。每个列族实际上被放置在文件系统中一个独立的物理文件里,因此修改一个列族不会影响其他列族,这对性能非常有利。Apache HBase和Cassandra都源自Google Bigtable和Amazon SimpleDB。
- 文档型数据库:数据以文档形式存储,如今最常见的文档类型是JSON文档。MongoDB是文档型数据库的典型例子。
- 图数据库:以图的形式存储数据,你可以通过遍历图来获取实体间的各种关系和关联数据。Neo4j是一个非常流行的图数据库。
在这张幻灯片中,我列出了市场上流行的几种关键NoSQL数据库,并将它们分为四大类。如果你访问 nosqldatabase.org,你会看到这些数据库被归入更多不同的类别,但我认为这四类是最重要的。
理解Brewer的CAP定理
从分布式NoSQL数据库的角度理解这一点非常重要。分布式数据库有三个特征:
- 一致性:所有客户端对数据有一致的视图。
- 可用性:所有使用系统的客户端总是能在一定的最大延迟(即一定时间内)内进行读写操作,不能永远等待。
- 分区容错性:允许系统部分故障,除非发生整个网络故障,否则系统不应响应错误。即除非整个数据中心完全宕机,否则系统仍应工作。
这被称为Brewer的CAP定理,其中C代表一致性,A代表可用性,P代表分区容错性。根据Eric Brewer博士的理论,一个分布式系统,特别是分布式数据库,最多只能满足这三个属性中的两个。
如果你看传统数据库,如Oracle、MySQL和SQL Server,它们满足一致性和可用性,但这只发生在单台机器上。你无法将系统拆分到多台机器上,因为你只选择了C和A作为特性。
如果你看一些其他NoSQL数据库,如HBase和Cassandra,它们提供一致性和分区容错性。一致性意味着你读写的是相同的数据,不会出现脏读。分区容错性意味着数据可以分布在多台机器上。但你没有得到完全的可用性。

如果你看另一些NoSQL数据库,如MongoDB和Cassandra,它们声称提供可用性和分区容错性,但无法保证强一致性。

可以看到,有三个区域可能被满足:你可以得到一致性和可用性,或者可用性和分区容错性,或者分区容错性和一致性,但你永远无法同时拥有三者。这就是Eric Brewer博士在其CAP定理中阐述的,后来被一些学生作为研究项目并给出了证明。
还需要注意,Cassandra同时出现在CP和AP区域。这是因为Cassandra具有“可调一致性”,如果你想要可用性和分区容错性,你可以以牺牲一些一致性为代价获得;或者你可以以牺牲可用性为代价获得一致性和分区容错性。
Brewer的CAP定理的关键点是,对于任何共享数据系统,你最多只能拥有这三个属性中的两个。
对比三种流行的NoSQL数据库


让我们具体看看三种NoSQL产品:Apache HBase、Cassandra和MongoDB。我们从它们是什么、起源和编写语言的角度来看。
以下是三种流行NoSQL数据库的基本信息:
- Apache HBase:它是一个分布式(多机)、稀疏的数据库,类似于键值系统。它也是一个列式数据库,因为有列族的概念,每个列族存储在单独的文件中,因此可以独立扩展列族。HBase基于Google Bigtable论文,并利用分布式文件系统HDFS作为底层存储。它使用Java编写。
- Cassandra:它也是一个分布式、稀疏的数据库。它遵循最终一致性或可调一致性的概念。你可以选择获得一致性和分区容错性,或者分区容错性和可用性,并且可以按需调整。Cassandra是Amazon Dynamo项目和Google Bigtable项目的结合体。它最初由Facebook为其收件箱概念创建,后来开源。它完全用Java编写。
- MongoDB:它是一个分布式、可扩展的数据库,自认为是高性能数据库。最重要的是,它是一个文档存储数据库,以文档格式(具体是JSON格式)存储数据。它由DoubleClick(一个非常流行的广告点击平台,后来被Google收购)的创始人创建。这些创始人在将公司卖给Google并在Google工作一段时间后,决定创办自己的公司10gen并推出MongoDB。MongoDB也是开源数据库,使用C++编写。
深入比较:架构、数据模型与特性
现在,让我们从架构、数据模型、客户端访问、ACID能力、安全性、成本、部署、支持和社区等角度来比较这些数据库。
架构对比
上一节我们介绍了三种数据库的基本信息,本节我们来看看它们的架构设计有何不同。
- Apache HBase架构:HBase构建在Hadoop分布式文件系统之上。有一个称为HMaster的组件,负责将区域(分区)分配给Region Server,并检查这些Region Server的健康状况。所有分区逻辑都由HMaster完成,它是HBase系统的大脑,与各个Region Server通信。这些Region Server分布在多台机器上,维护表的部分数据。例如,Region Server 1可能维护表Foo的一部分和表Bar的一部分,并将这些表中的列族数据保存在内存存储中,最终写入HDFS中的各个文件。还有ZooKeeper的概念,因为HMaster是大脑,如果它宕机,整个系统就宕机了。为了实现高可用性,可以启动多个HMaster实例,它们向ZooKeeper(一个协调系统)注册自己。客户端想要插入记录时,首先与ZooKeeper通信,找出HMaster在哪里,然后询问HMaster应该与哪个Region Server通信以进行CRUD操作,最后与该Region Server通信。
- Cassandra架构:Cassandra具有基于环的架构,Cassandra守护进程在多台机器上启动并形成一个环。当客户端想要插入记录时,它连接到其中一个Cassandra守护进程,Cassandra守护进程知道该记录应该插入到哪里,它会与适当的Cassandra守护进程通信并插入记录,然后该记录会在环架构中复制到下一台机器,再下一台机器,从而实现数据的三副本复制。这就是Cassandra获得可用性和分区容错性的方式。在HBase中,你不需要担心三副本复制,因为当数据存储在HDFS中时,它已经作为HDFS复制的一部分被复制了三份。此外,在Cassandra中,你可以拥有多数据中心解决方案。当你在Cassandra中插入一条记录时,它不仅可以插入一个数据中心,还可以传播到第二个数据中心,从而实现跨多个数据中心的读写,这是Cassandra的最大优势之一。而HBase是构建在Hadoop之上的分布式数据库解决方案,但不是多数据中心解决方案。
- MongoDB架构:在MongoDB架构中,有分片,每个分片称为一个副本集。每个副本集有一个主服务器(存放主副本)和至少两个从服务器(实现三副本复制)。你可以根据需要拥有任意多个这样的分片。MongoDB中有MongoDB配置服务器,它与副本集的主服务器通信以确定所有不同分片的位置。当客户端想要进行CRUD操作时,它查询MongoDB配置服务器以确定记录的位置,然后直接与相应副本集的主服务器通信以读写记录。实际上,一旦确定需要与哪个分片或副本集通信,它就与主服务器进行创建、更新和删除操作,但也可以与从服务器进行读取操作。
数据模型对比
了解了架构之后,我们来看看它们是如何组织和存储数据的,即数据模型。
- HBase和Cassandra的数据模型:称为列族数据模型。对于每个表,通常有一个主键(行键),然后有称为列族的东西。你可以拥有任意多的列族,但通常在1到10或15个之间。列族的妙处在于,每个列族可以拥有任意数量的列,事实上你可以有数千、数万甚至数百万列,因此列族内的列是一个非常动态的概念。每个列族为了扩展目的存储在一个单独的文件中。这不是关系数据模型,而是一种列族或列式数据模型。
- MongoDB的数据模型:基于JSON。你只需创建JSON对象并将其插入MongoDB数据库。无论是列族数据模型还是JSON数据模型,都能够动态添加列,因此具有灵活的模式,在NoSQL数据库中更容易演进。
客户端访问、查询与索引
不同的数据模型决定了不同的访问和查询方式。以下是它们在客户端访问方面的特点:
- Apache HBase:可以使用Java API进行CRUD操作,也可以使用Thrift RPC API。还有Hadoop集成,可以在Apache HBase上使用MapReduce。查询记录都是基于API的,因此客户端需要做很多工作来从数据库查询记录。索引仅针对主键。
- Cassandra:支持多种API,如Ruby、Perl、Python、Scala等。支持Hadoop集成。可以基于索引查询,因为有主索引和二级索引的概念。还有CQL(Cassandra查询语言)。支持主索引、二级索引和复合键。
- MongoDB:有原生JavaScript API。也有多种语言的二进制驱动程序(如C++、Python)。对于Java,它提供了一个REST接口(实际上,如今REST在所有三种数据库中都有提供,但可能比原生API慢一些)。还有一种特殊的查询语言,称为UnQL(非结构化查询语言)。支持主键、二级键索引和复合键。
ACID与BASE模型
理解NoSQL数据库时,ACID和BASE的概念很重要。ACID代表原子性、一致性、隔离性、持久性。BASE代表基本可用、软状态、最终一致性。在ACID模型中,你获得完全ACID系统,保证一致性、原子性、隔离性和持久性。而在BASE模型中,你会获得一致性,但它是最终一致性。
像Oracle和MySQL这样的关系型数据库系统是完全ACID兼容的。而像Cassandra这样的系统支持BASE模型,即基本可用、软状态、最终一致性。换句话说,记录最终会一致,但暂时可能有一些延迟,因为它们必须基于时间戳维护一切,它们并不真正更新记录,更新操作实际上是一次插入,它们必须合并和整合这些记录。我想说明的是,Cassandra支持可调一致性模型,可以提供一致性和分区容错性,而HBase支持一致性和分区容错性模型。Cassandra实际上也可以做到AP(可用性和分区容错性)。所有这些特性都是可调的。MongoDB也支持AP模型,提供可用性和分区容错性,实际上它提供的是最终一致性。关键在于,NoSQL数据库实际上放弃了ACID概念,转向了称为BASE的最终一致性,这是由于Eric Brewer博士的CAP定理。
其他考量因素
最后,我们还需要从安全性、成本、部署和支持等实际角度进行考量。

- 安全性:几乎所有NoSQL数据库的安全性都相对较弱。我们知道关系型数据库在数据控制语言方面做得很好,可以控制对数据的访问。这是NoSQL数据库的一个弱点。
- 成本:NoSQL数据库运行在廉价的商用硬件上,因此从前期投资来看,不像传统数据库那样需要从中型数据库开始,然后随着增长购买越来越大的、更昂贵的服务器。在NoSQL数据库中,扩展只是动态添加这些商用服务器。软件方面,几乎所有NoSQL数据库都是开源项目,几乎没有成本。
- 部署、社区与支持:
- Apache HBase:部署在Trend Micro、Facebook和StumbleUpon等公司。社区方面,谷歌搜索“HBase NoSQL”约有75万条结果,有大约5本相关书籍。支持方面,可以从Cloudera、Hortonworks或MapR获得HBase的支持许可。
- Cassandra:部署在Netflix、Facebook和Simple Geo等公司。谷歌搜索“Cassandra NoSQL”约有310万条结果,有2本相关书籍。支持可以从DataStax获得。
- MongoDB:部署在Foursquare、Bitly、Craigslist、SourceForge、Disney等公司。谷歌搜索“MongoDB NoSQL database”约有230万条结果,有14本相关书籍。支持可以从10gen获得。
总结

本节课中,我们一起学习了NoSQL数据库的基础理论。我们了解了NoSQL兴起的背景,对比了关系型数据库与NoSQL在扩展性上的差异。我们深入探讨了Brewer的CAP定理,理解了分布式系统在一致性、可用性和分区容错性之间的权衡。最后,我们详细比较了三种流行的NoSQL数据库——Apache HBase、Cassandra和MongoDB——在架构、数据模型、访问方式、一致性模型以及生态系统等方面的特点。希望通过本课,你能对NoSQL数据库有一个清晰的概览,并能在实际工作中根据需求选择合适的数据库解决方案。在本课程中,由于主题是“使用Hadoop进行大数据处理”,我们将重点学习Apache HBase这一NoSQL数据库。
085:HBase架构与核心特性 🗄️

在本节课中,我们将学习HBase数据库的概述及其架构的关键细节。HBase是一个构建在Hadoop之上的开源NoSQL数据库,专为处理海量数据而设计。
HBase概述
HBase是一个开源的NoSQL数据库。它也被称为Hadoop数据库,因为它精确地构建在Hadoop之上,具体来说是构建在HDFS之上。它当然可以与Hadoop MapReduce协同工作,但HDFS是HBase运行所需的最低要求。
HBase是分布式的,这意味着它可以在多台服务器上运行,也意味着它专为大型表设计,这些表可以包含数十亿行甚至数百万列。HBase支持随机的实时CRUD操作。
回想一下HDFS,在HDFS中你可以存储文件,但它主要用于顺序或流式读取以及MapReduce处理。在HDFS中,你存储数据,然后使用MapReduce进行顺序和流式读取与处理。而使用HBase,你可以获得随机的实时读写操作,因此可以对数据执行实时的CRUD操作。
再次回到HDFS,在HDFS中你只能追加文件,无法真正修改文件中间的内容。HBase是一个支持版本控制的数据库。这是HBase表的一个非常好的特性:当你拥有列时,这些列可以有多个版本的值。你可以有一个版本1的值,几分钟后你可能更改该值,那就是版本2的值。它实际上维护着这些版本,并且你可以指定希望为某个单元格或值保留多少个版本。
HBase运行在商用硬件集群上。因为HBase构建在Hadoop之上,运行在商用硬件集群上是必然的选择。当然,别忘了,在生产环境中,你运行在商用服务器上,可能是戴尔、IBM或惠普的服务器,而不是笔记本电脑或台式机。
HBase是水平可扩展的。当你向HBase插入记录时,它会自动进行分片。如果你有多个HBase Region Server实例,它会确保数据在多个Region Server之间进行分片。因此,分片是自动完成的,无需你进行特殊操作。


HBase是强一致性的。这是ACID属性,但HBase的ACID属性只发生在行级别。你可以在事务性方式下更新一行内的所有列,但无法跨行进行此操作。如果你想跨行更新记录并保持强一致性,那么你可能需要使用ZooKeeper进行分布式事务。
HBase具有自动故障恢复功能,这意味着当一台机器崩溃时,另一台机器会接管该特定数据,因为所有数据都在HBase中。HBase与MapReduce框架存在集成。尽管HBase运行在HDFS上,我们也知道在HDFS之上是YARN,在YARN之上是MapReduce。你实际上可以运行以HBase表作为输入的MapReduce程序,然后输出可以返回到HBase表,也可以写入文件系统。
从编程角度看,访问HBase的接口有Java API,也有REST API。此外,还有HBase命令行Shell,我们稍后会讨论。

HBase基于谷歌的Bigtable论文。以下是谷歌Bigtable论文的链接。就像HDFS基于GFS论文,MapReduce基于谷歌的MapReduce论文一样,HBase项目基于Bigtable论文。
HBase历史
2006年,谷歌发布了Bigtable论文。有一家名为PowerSet的小公司,其开发人员阅读了Bigtable论文并深受启发,因此他们希望启动一个基于Hadoop的新项目,称为HBase。
2007年,HBase成为Hadoop的一个贡献模块。如果你回顾Hadoop的安装,你会注意到实际上有一个contrib文件夹,那里存放着对Hadoop的第三方贡献。Hive和Pig最初都是作为贡献模块启动,后来成为顶级项目。同样,在2007年,HBase作为Hadoop 0.15.0版本发布的一部分,开始作为贡献模块。
2008年,HBase成为Hadoop的一个子项目,因此它位于hadoop.apache.org/hbase下。后来它成为了所谓的顶级项目,因此变成了hbase.apache.org项目。
2009年,HBase 0.19和0.20版本发布,被认为是Hadoop的两个主要稳定版本。
到2010年,他们实际上将版本号改为0.89,以与Hadoop的版本号区分开来。Hadoop的版本号从0.1、0.2、0.3等开始,在某个时间点,HBase开发人员认为这个数字太低了,他们希望快速向HBase 1.0迈进,但当时尚未实现。从2010年开始,到2014年,它即将进入1.0版本。无论如何,他们将版本号移到了0.89,这就是编号从Hadoop转向其自身编号方案的方式。

然后在2011年,HBase 0.90和0.92版本发布,这是相当稳定的版本。2011年至2014年间发生了很多事情。现在我们有了HBase 0.99.x,它很快将成为1.00,最终他们将发布2.0版本。2.0版本将是与Hadoop 2更兼容的版本。1.0版本是一个包含适用于Hadoop 1和Hadoop 2的软件包的版本,而2.0版本将只适用于Hadoop 2,这是HBase目前的计划。
我想提到的关于版本的另一点是,如果你看到一个偶数版本号,如0.98或0.92,那意味着它是一个稳定版本。奇数版本号则是不稳定版本或开发版本。目前我们将使用0.99版本。我们本有机会使用0.98版本,但我决定在本课程中使用0.99版本。
HBase的使用者
以下是一些使用HBase的知名公司名单,这是一个非常有限的列表:Facebook、Adobe、Twitter、Yahoo、Netflix、Meetup、StumbleUpon等。这些公司也在使用许多其他项目,例如Facebook启动了Cassandra项目,有些项目也使用Oracle。因此,所有这些不同的公司都混合使用了多种技术,但这里列出了一些使用HBase的大公司。
何时使用HBase
关于何时使用HBase,有两个众所周知的用例。
- 海量数据或行数:不是数百万行,而是数十亿行、数千亿行甚至数万亿行,这时HBase非常出色。
- 大量的客户端请求:如果你每秒有数百万次请求需要扩展,或者每秒有数百万次更新或插入,这基本上意味着每次用户登录时都有更多数据涌入,你可能希望将其记录到一个辅助系统中,而不是写入HDFS文件。你可能希望将日志事件写入HBase表,以便可以扫描、进行批量处理等。因此,每当有大量数据涌入并希望插入这些记录,或者有大量客户端请求进行读取访问时,你实际上可以使用HBase。
HBase之所以非常适合处理大量客户端请求,是因为它是一个分布式系统。这意味着如果七台机器不够好,那么将硬件翻倍,变成14台机器,现在你已经自动分区了数据库表。因此,它非常适合海量数据或数十亿条记录,也适合大量涌入的请求。
它非常适合单个随机选择。它非常适用于你说“这是键,请给我这条特定记录”的场景。例如,如果你有10亿用户,你不能将10亿用户放在MySQL数据库中。你可以做的是将10亿用户存储在HBase中,你可能拥有100台服务器,然后每次用户登录时,你获取作为键的用户ID,去HBase说“检索这条记录”,它会给你存储在HBase中的用户名和密码哈希,然后将其与用户输入的内容进行匹配。因此,你可以将其用于身份验证目的。这是使用HBase的一个绝佳场景。
它同样适合范围扫描。你也可以扫描一组记录,例如可以说“给我这个键和那个键之间的所有记录”,这也是可能的。
HBase的另一个优点是可变模式。在关系数据库中,你必须定义模式并坚持该特定模式:这是你为这个表定义的17列,仅此而已,并且数据类型也是固定的。而在HBase中,它们有称为列族的概念,列族是固定的,然后在列族内你可以有列,这些列甚至可以动态添加。事实上,当你插入记录时,它们会作为记录的一部分被动态添加。这些列属于一个列族,这些列名可以是动态的,你可以有数百万列,并且可以在其中存储任何你想要的数据,只要它可以序列化为字节,数据类型无关紧要。
何时不使用HBase
HBase不适用于传统的RDBMS系统,在这些系统中你希望进行事务性应用程序操作,需要更新多个不同的表并将其作为单个事务的一部分。使用HBase无法做到这一点,事务只能发生在单行内。
同样,对于关系分析,例如你可能希望进行一系列分组操作,或者进行连接和WHERE子句查询。它不具备这些功能。它主要支持CRUD操作,以及少量可能的扫描操作以获取一组记录。
HBase非常适合存储大量行并进行CRUD操作,但如果你想在这些数据中进行搜索,通常在关系数据库中,你会使用SELECT语句,可能使用通配符甚至进行文本搜索。在HBase中,这些功能效果不佳,因为首先它没有文本处理等功能。在关系数据库中,你可以使用SELECT语句和LIKE操作符,甚至可以进行一些文本搜索,但当涉及到像Solr和Lucene这样的真正搜索引擎时,你无法在HBase中做这些事情。HBase主要是用于记录的CRUD操作。
有一些项目正在混合使用HBase和Lucene。这里有一个由AK Kumar开发的项目,称为HBase-Lucene,它将HBase和Lucene混合在一起:在HBase中插入记录时,它也会在索引中插入一条记录。还有另一个项目叫做Lily项目,它基于Lucene构建的Solr,并将其与HBase混合。这被称为Lily项目。这些都是可用的一些开源项目,它们正在尝试这样做。我认为这是未来两到三年内你会看到很多活动的一个领域。
HBase架构
在HBase中,你有一个由区域组成的HBase表。你可以将区域视为关系数据库中的分区。因此,区域就像存储在一起的一系列行,这就是我所说的分区,或者你可以说是一个分片。它实际上用于扩展:一个分片存储在一台机器上,另一个分片存储在另一台机器上,第三个分片存储在第三台机器上,这允许你扩展系统。
表的这种分片思想是动态完成的,并且是自动为你完成的。因此,当HBase系统发现系统中有许多其他机器时,它会自动将表分割成各种分片,并尝试将这些分片分布到这些不同的机器上。
Region Server是一个服务或守护进程,一次服务于一个或多个区域。区域是什么?它是一个分片,一个分区。因此,Region Server服务于这些分区之一。
Master Server是一个负责管理HBase集群的守护进程。因此,Master Server或HMaster是HBase系统的主节点。HBase将其数据存储到HDFS中。通常在关系数据库中,它将数据存储在文件系统中。在HBase中,它将数据存储在HDFS中。因此,考虑到HDFS具有三副本复制和容错等内置功能,HBase自动继承了所有这些特性,实际上它依赖于HDFS的高可用性和容错特性来维护HBase数据库的持久性。
HBase使用称为ZooKeeper的东西。ZooKeeper是Apache的另一个项目,用于分布式协调。HBase使用ZooKeeper来确保HMaster处于运行状态,并选举其中一个作为领导者。我们将在接下来的一两张幻灯片中讨论这个问题。这些是你应该熟悉的一些高级HBase架构术语。
现在让我们来看看HBase架构。

在最底层,你有Hadoop分布式文件系统。HBase构建在HDFS之上。在HBase中,你有一个特殊的守护进程叫做HMaster守护进程。请注意,我实际上启动了两个HMaster守护进程。你实际上可以有两个、三个、四个、五个,任意多个HMaster守护进程,但一次只能有一个处于活动状态。
还有这些称为Region Server守护进程的守护进程。Region Server守护进程是负责服务一个分片的守护进程,它们负责为表服务一个分片。因此,一个Region Server将负责服务,比如说两个表:我们有一个名为Foo的表和一个名为Bar的表,它基本上存储Foo表的一部分和Bar表的一部分。在内部,它将所有数据存储在HDFS和HLog中。这些都是它使用HFS进行的一些内部操作。还有这些称为CF1和CF2的东西,它们是该特定表的列族。这个MemStore是内存中的映射。我想说明的重点是,Region Server有一个分片的内存映射,其余数据则在数据库中。因此,这是你拥有的内存存储或内存缓存,你可以拥有任意多个Region Server。
ZooKeeper是一个负责告知HMaster在哪里的守护进程。如果你有两个HMaster,它们都会向ZooKeeper注册自己,ZooKeeper基本上会选举其中一个作为主Master。假设这个HMaster被选为主节点,第二个Master或第二个Master被保留为辅助Master,只有当第一个Master失败时,它才会成为主节点。为什么它会成为主节点?因为ZooKeeper在那一刻选举第二个HMaster作为主节点。这就是ZooKeeper的主要工作。
客户端想要进行一些操作,它想要进行一些CRUD操作。首先,客户端必须与HMaster通信。如果它必须与HMaster通信,它需要知道HMaster在哪里。这时客户端实际上首先进行查找:客户端在ZooKeeper中进行查找,询问HMaster在哪里。ZooKeeper说你应该与之通信的HMaster是这个,所以请将信息发送给这个HMaster。于是客户端去HMaster那里说“我正在寻找检索这条记录,这是该特定记录的ID”。HMaster说“哦,关于该特定记录的信息在Region Server 1中”。然后客户端去Region Server 1,基本上检索关于该记录的信息并将其返回给客户端。
我想说明的重点是:客户端总是去HMaster那里找出记录的位置,然后最终去相应的Region Server检索该特定记录。这是因为HMaster完全跟踪不同键范围放置的位置,因此基于传递给它的键,它可以告诉你哪个Region Server拥有该信息。
我还想告诉你,客户端并不总是返回HMaster获取该信息。客户端会不时地去那里。它实际上会获取关于不同区域和不同键范围位置的信息并进行缓存。这样,它就不必总是去HMaster那里。它会不时地与HMaster通信,以确保其缓存信息是最新的,但它并不总是必须去HMaster那里。第一次肯定会去。
此外,当客户端识别出ZooKeeper给出的HMaster是这个HMaster时,那么客户端将总是去这个HMaster,它不必再返回ZooKeeper询问“哦,HMaster在哪里”。客户端后续对HMaster的请求将直接发送给HMaster。如果由于某种原因HMaster死亡,那么客户端将去ZooKeeper询问“HMaster在哪里”。与此同时,ZooKeeper选举了辅助HMaster作为HMaster,然后客户端最终将去辅助HMaster那里。
总而言之,ZooKeeper的主要工作是管理HMaster守护进程:如果有多个HMaster守护进程在运行,确保其中一个被选为领导者。
客户端负责与ZooKeeper通信以找出HMaster的位置,从而获取关于分区的信息。
Region Server是服务于表的一个分片部分的守护进程。Region Server维护多个表,但它们维护多个表的记录部分。请注意,我有Region Server 1、Region Server 2、后面的3和4。这些Region Server可能运行在不同的机器上。因此,在一台机器上运行HMaster,在另一台机器上运行作为辅助HMaster的HMaster,然后在三台机器上运行ZooKeeper。这样就有五台机器了。然后你有Region Server 1、Region Server 2和Region Server 3。这是四个Region Server,也就是四台机器。因此,为了运行HBase系统,你至少需要:这里用于Region Server的4台机器,用于ZooKeeper的3台机器,总共7台,再加上两台HMaster机器,就是9台机器。所以你真的需要9台机器来拥有一个HBase的最低限度系统,然后你可以通过向HBase系统添加越来越多的Region Server机器来不断扩展。
重点是HBase要复杂得多。当然,所有这些都需要安装Hadoop。但HBase的起始机器数量要复杂得多。
对于我们在本课程中将要做的内容,我们将在同一台机器上运行所有守护进程,包括Hadoop守护进程和HBase守护进程,但通常它们是分布式的。
HBase组件详解
这是HBase组件的另一个视图。我们有HMaster,它是HBase的大脑,负责跟踪所有分片的位置。我们有Region Server,它维护表的一个分片,它可能跨多个表拥有多个分片。
还有一个内存中的MemStore,它维护着某种内存映射,几乎就像缓存。其余信息存储在一个名为HFile的文件中,该文件位于HDFS中。还有WAL,它代表预写日志。当你向Region Server插入记录时,它首先将所有内容写入预写日志,然后替换到HFile中并合并到HFile中。因为HFile是存储记录信息的底层存储文件,并且这些信息实际上是以排序方式保存的。当我们学习HBase数据模型和HBase Shell时,会看到这一点。
因为信息在HFile中是排序的,所以信息首先被写入WAL文件(即预写日志文件)以提高速度,然后不时地排序并放入HFile中。


ZooKeeper跟踪NameNode的位置,也跟踪各个Region Server的位置。
数据视角下的HBase
这是从数据角度看HBase的另一个视图。假设我们有三个Region Server:Region Server 1、Region Server 2和Region Server 3。这里是行,更准确地说,是行键。它是一个键值系统,所以A1是键,A2是键,A22是键,这些是我们拥有的所有键。请注意,键总是按排序顺序存储。所有键都按排序顺序存储。
这组键存储在一个分片中,位于Region Server 1中。这组键存储在另一个分片中,位于Region Server 2中。这组键存储在另一个分片中,位于Region Server 3中。这组键存储在一个分片中,位于Region Server 1中。正如你所见,分区或分片正在发生,当然,这些信息由HMaster维护,以了解不同范围的分片放置在哪里。这就是为什么每当客户端进来并说“我想更新这条记录”或“我想删除具有这个特定键的记录”时,它确切地知道哪个Region Server、哪个分片拥有该记录,因此它引导客户端去那个特定的分片执行CRUD操作。
HBase安装细节
我之前在HBase安装方面做了一些粗略的介绍,这里有一张更详细的幻灯片,说明HBase安装如何进行。
你可以有一台机器运行ZooKeeper,另一台机器运行ZooKeeper,第三台机器运行ZooKeeper。顺便说一下,你总是以奇数运行ZooKeeper:1、3、5、7,因为ZooKeeper用于选举目的,你希望确保它们是奇数个。如果三个ZooKeeper中有两个同意应该选举其中一个作为主节点,那么那个就会被选为主节点。如果ZooKeeper的数量是偶数,那么事情就无法正常工作。这就是为什么你保持3、5、7个ZooKeeper守护进程。
如果你有机会,你可能想了解一下ZooKeeper项目,它与大数据无关。ZooKeeper更多是关于分布式计算和分布式事务管理,碰巧HBase使用ZooKeeper来实现其高可用性特性。
在一台运行ZooKeeper的机器上,你可能想运行HBase Master,即HMaster。在一台运行ZooKeeper的守护进程机器上,你可能想运行NameNode。在一台守护进程机器上,你可能想运行Secondary NameNode。

然后你有一
086:HBase环境部署 🛠️



在本节课中,我们将学习如何在开发环境中下载、安装、配置并运行HBase。

概述
HBase是一个运行在Hadoop HDFS之上的分布式、可扩展的NoSQL数据库。为了进行开发和测试,我们将在“伪分布式”模式下安装和配置HBase。这种模式模拟了完全分布式环境,但所有守护进程都运行在同一台主机上,非常适合学习和原型设计。
下载HBase


HBase是Apache的开源项目,其官方网站是 hbase.apache.org。
以下是下载HBase的两种方法:
方法一:通过官方网站下载
- 访问
hbase.apache.org。 - 点击“Downloads”链接,页面会跳转到一个镜像下载页面。
- 在镜像页面下载最新版本的HBase。
方法二:通过Apache存档站点下载
- 访问
archive.apache.org/dist/hbase。 - 页面会列出所有HBase发行版,选择最新的版本进行下载。
HBase的发行版文件命名格式为:hbase-版本号-bin.tar.gz。
安装HBase
安装HBase非常简单,只需解压下载的压缩包即可。
- 进入Hadoop的安装目录(例如
/usr/local/hadoop)。 - 使用以下命令解压HBase压缩包:
这将在当前目录下创建一个名为tar -zxvf /path/to/downloaded/hbase-*.tar.gzhbase-版本号的文件夹。
现在HBase已安装完成。让我们探索一下HBase的安装目录,了解各个文件夹的作用。
bin目录:存放HBase的启动脚本。其中包含start-hbase.sh脚本和用于启动HBase交互式Shell的脚本。conf目录:存放HBase的配置文件。其中最重要的文件是hbase-site.xml,它包含了连接Hadoop HDFS所需的信息,我们需要修改此文件以使HBase连接到Hadoop。docs目录:存放HBase的文档。hbase-webapps目录:存放HMaster管理控制台和RESTful Web服务的模板。通常我们不需要过多关注此目录。lib目录:存放HBase的JAR文件。如果你要编写HBase客户端程序以执行数据操作,需要包含这些JAR文件。logs目录:存放所有HBase守护进程的日志文件。
配置环境变量
安装好HBase后,我们需要设置环境变量,以便系统能够找到HBase的命令。
编辑 ~/.bash_profile 文件(或相应的shell配置文件),添加以下两行:
- 设置
HBASE_HOME环境变量,指向HBase的安装路径:export HBASE_HOME=/usr/local/hadoop/hbase-版本号 - 将HBase的
bin目录添加到PATH环境变量中,这样我们就可以直接执行启动、停止命令以及启动HBase Shell:
保存文件后,执行export PATH=$PATH:$HBASE_HOME/binsource ~/.bash_profile使配置生效。


配置HBase连接HDFS
由于我们要在伪分布式模式下运行HBase,需要确保HDFS正在运行。你可以通过浏览器访问NameNode控制台(默认端口9870)或执行 hadoop fs -ls / 命令来验证HDFS是否可用。

HBase可以在三种模式下运行:

- 本地/独立模式:开箱即用的模式,不使用HDFS,所有守护进程(HMaster、RegionServer、ZooKeeper)都在同一个JVM中运行。仅用于快速测试,不推荐使用。
- 伪分布式模式:开发模式,需要HDFS。它模拟了完全分布式环境,但所有守护进程运行在同一台主机上。这是学习、测试和调试应用程序的理想模式,也是本教程将使用的模式。
- 完全分布式模式:生产模式,HBase守护进程分布在多台机器上运行。
为了让HBase在伪分布式模式下与Hadoop HDFS通信,我们需要修改 $HBASE_HOME/conf/hbase-site.xml 配置文件。
在该文件中,我们需要设置以下三个关键属性:
hbase.rootdir:指定HDFS的位置以及HBase存储其元数据和数据的目录。<property> <name>hbase.rootdir</name> <value>hdfs://localhost:9000/hbase</value> </property>hbase.cluster.distributed:设置为true,以启用伪分布式模式,让各个守护进程独立运行。<property> <name>hbase.cluster.distributed</name> <value>true</value> </property>hbase.unsafe.stream.capability.enforce:对于较新版本的Hadoop(支持擦除编码),需要将此属性设置为false,因为HBase的预写日志(WAL)目前不完全兼容此特性。<property> <name>hbase.unsafe.stream.capability.enforce</name> <value>false</value> </property>
启动与验证HBase

配置完成后,就可以启动HBase了。由于我们已经将HBase的 bin 目录加入了PATH,可以直接执行启动命令:
start-hbase.sh
此命令会依次启动ZooKeeper、HMaster和RegionServer守护进程。

可以通过以下几种方式验证HBase是否成功运行:
- 使用HBase Shell:执行
hbase shell命令进入交互式Shell,然后输入list命令。如果返回表列表(初始为空),则说明HBase运行正常且客户端可以连接。hbase shell list - 检查HDFS目录:执行
hadoop fs -ls /hbase命令。如果HBase运行成功,会在HDFS的/hbase目录下创建一系列用于存储内部信息、数据和元数据的目录。 - 访问Web管理控制台:
- HMaster控制台:默认运行在
http://localhost:16010。在此可以查看HMaster状态和已连接的RegionServer列表。 - RegionServer控制台:每个RegionServer默认运行在
http://localhost:16030。可以从HMaster控制台点击链接直接访问。
- HMaster控制台:默认运行在
停止HBase

停止HBase非常简单,只需执行:
stop-hbase.sh
此命令会按顺序停止RegionServer、HMaster和ZooKeeper守护进程。

总结

本节课中,我们一起学习了如何在开发环境中部署HBase。我们完成了从官网下载HBase发行版、解压安装、配置环境变量、修改核心配置文件 hbase-site.xml 以连接HDFS,并在伪分布式模式下成功启动和验证了HBase服务。我们还了解了HBase的三种运行模式及其适用场景,并掌握了通过Shell和Web控制台验证服务状态的方法。现在,你的HBase开发环境已经准备就绪,可以开始进行数据操作了。
087:HBase数据模型解析 🗂️


在本节课中,我们将学习HBase的数据模型。这是一种灵活的、面向列的、基于键值对和列族的数据模型,与我们熟悉的传统关系型数据库模型有所不同。
概述
HBase数据模型的核心在于其独特的组织方式。数据存储在表中,这一点与关系数据库类似,但其底层存储依赖于HDFS,并且数据以列族的形式进行组织,支持动态列和版本控制。
HBase数据模型详解
上一节我们介绍了HBase的概览,本节中我们来看看其数据模型的具体构成。
表与行
在HBase数据模型中,数据存储在表中,这与关系数据库非常相似。但底层数据实际上存储在HDFS中。表由行组成,每行通过一个唯一的键来引用。这个键是一个字节数组,这意味着任何可序列化为字节的数据类型(如字符串、长整型或自定义类型)都可以作为键。
列与列族
行由列组成,但这里的列被分组到所谓的“列族”中。在一个行内,可以有多个列族,每个列族下又包含多个列。因此,一个数据单元(称为“单元格”)实际上是行、列族和列这三者的组合,值就存储在这个单元格中。
以下是关于列族的关键点:
- 列族是静态的:在创建表时预定义,之后可以修改但相对固定。
- 列是动态的:列名可以在程序运行时动态创建和添加。
- 存储方式:每个列族及其下的所有列和值,都存储在一个单独的HDFS文件中(即HFile)。因此,一个包含三个列族的表,在HDFS中会对应三个文件。
- 数量限制:列族的数量通常较少(个位数到十位数),而一个列族内可以拥有数百万个列。
列族名称必须由可打印字符组成。你可以将列族名视为单元格的一个“标签”。
单元格与版本控制
行实际上由单元格组成。单元格通过 列族:列名 的组合来定位。
HBase单元格是带版本控制的。默认情况下,每个单元格会保留三个版本的值(包括当前值)。这为数据检索增加了时间维度。你可以根据时间戳来获取特定版本的值,或获取某个时间范围内的值。
时间戳可以由区域服务器隐式分配,也可以由客户端在插入数据时显式提供。读取数据时,默认返回最新值,但也可以请求获取旧版本。保留的版本数量可以在列族定义时进行配置。


综上所述,一个HBase单元格的值由以下元素唯一确定:
值 = f(表名, 行键, 列族名, 列名, 时间戳)
本质上,一个HBase表是一个排序的映射(Sorted Map):
- 键是行键。
- 值又是一个映射,其键是带有列族标签的列,值则是带时间戳版本的值列表。
行键排序
当向HBase表插入记录时,所有行会按照行键进行字典序排序(在字节级别从左到右比较)。例如,行键 1, 2, 3, 10, 15 会排序为 1, 10, 15, 2, 3。这类似于关系数据库中基于主键的聚簇索引。
综合示例
为了将上述概念串联起来,请看以下示例表,它包含两行(row1, row2)、两个列族(name, address)以及多个时间戳版本:
| 行键 | 时间戳 | 列族:列名 | 值 |
|---|---|---|---|
| row1 | T5 | name:firstname | Bob |
| row1 | T5 | name:lastname | Smith |
| row1 | T8 | address:number | 10 |
| row1 | T8 | address:street | First St |
| row1 | T10 | address:street | Second St |
| row1 | T15 | address:street | Last St |
| row2 | T10 | name:firstname | Alice |
- 当请求
row1的所有内容时,HBase默认返回每个单元格的最新值:name:firstname= Bobname:lastname= Smithaddress:number= 10address:street= Last St
- 对于
address:street,系统保留了T8、T10、T15三个时间戳的版本(假设版本数设置为3)。如果后续在T31更新为Eight St,那么最旧的T8版本将被删除。 - 你可以请求特定时间戳的值,或某个时间范围内的所有值。

总结

本节课中我们一起学习了HBase的数据模型。我们看到HBase将数据存储在HDFS中,其表包含列族,每个列族存储在独立的HDFS文件里。列族可以包含列,列可以动态添加。列族是静态定义的,而列名可以动态创建。此外,HBase基于时间戳存储值,默认保留三个版本(包括当前值)。我们可以在创建表时配置版本数量,也可以根据时间戳或时间戳范围来查询特定版本的数据。简单地请求一个单元格的值,将返回最新的版本。
088:HBase终端操作 🖥️


在本节课中,我们将学习如何使用HBase的交互式Shell。HBase Shell是一个基于JRuby的命令行工具,它允许我们直接与HBase数据库进行交互,执行创建表、插入数据、查询、更新和删除等操作。通过本节的学习,你将掌握使用HBase Shell进行基本数据操作和管理的方法。
HBase Shell简介
上一节我们介绍了HBase的基本架构,本节中我们来看看如何通过命令行与HBase进行交互。
HBase Shell是一个JRuby交互式Shell,它是一个集成了HBase命令的JRuby IRB(交互式Ruby Shell)。这意味着你可以在其中执行许多HBase命令,例如创建表、向表中插入记录、从表中删除记录、扫描整个表以及执行其他数据操作语言(DML)和管理操作。
要运行HBase Shell,你只需在命令行中输入 hbase shell,前提是你的系统路径中已经包含了HBase的bin目录。执行该命令后,你将进入交互式Ruby Shell环境。
启动与帮助命令
以下是启动HBase Shell并获取帮助信息的基本步骤。
- 在命令行中输入
hbase shell启动交互式Shell。 - 输入
help命令可以获取所有可用命令的列表。 - 输入
help "命令名"(例如help "get")可以获取特定命令的详细使用信息和示例。
请注意,在HBase Shell中,表名和列名必须用引号括起来,可以是单引号或双引号。对于二进制键值,必须使用双引号。你还可以使用Ruby哈希(Hash)来为命令指定参数,其格式为 {key1 => 'value1', key2 => 'value2'}。
HBase Shell支持的命令类型
HBase Shell支持多种类型的命令,方便进行数据库操作和管理。
以下是HBase Shell支持的主要命令类别:
- 通用命令:例如
status和version,用于获取HBase集群的状态和版本信息。 - DDL命令(数据定义语言):用于定义数据结构,例如
create(创建表)、describe(描述表)、disable/enable(禁用/启用表)、drop(删除表)和alter(修改表)。 - DML命令(数据操作语言):用于操作数据,例如
put(插入或更新记录)、delete(删除记录)、get(获取单行记录)、scan(扫描多行记录)和count(统计行数)。 - 集群管理命令:用于HBase数据库管理员(DBA)进行集群管理,例如平衡集群、刷新内容、ZooKeeper相关命令和复制命令等。本课程不深入讨论这些管理命令。
检查集群状态
一旦进入HBase Shell,你可以使用 status 命令来获取HBase服务器的状态信息。
status 命令会返回集群的基本状态,例如活跃的服务器数量。而 status 'detailed' 命令则会提供更详细的状态信息,这些信息与我们在安装HBase时看到的HBase Web管理控制台上的信息类似。
详细状态信息包括HBase版本号、活跃的RegionServer列表、它们服务的区域和表等信息。这对于管理大型集群并希望了解其运行状况非常有用。
完整的DDL与DML操作流程
我们将通过一个完整的例子来演示如何使用HBase Shell命令创建表、填充数据、访问数据、编辑数据、删除记录并最终删除表。
我们将创建一个名为 blog 的表,其模式(Schema)如下:
- 列族(Column Family)
info:包含title、author、date列。 - 列族
content:包含post列。
创建表
使用 create 命令可以创建表。你可以指定列族及其属性,例如版本数(VERSIONS)和生存时间(TTL)。
创建 blog 表的命令如下:
create 'blog', {NAME => 'info', VERSIONS => 3}, {NAME => 'content', VERSIONS => 3}
此命令创建了 blog 表,包含 info 和 content 两个列族,每个列族都保留每个单元格的3个版本,并且数据永久有效(默认TTL)。
插入数据
使用 put 命令可以向表中插入数据。put 命令用于插入单个单元格(Cell)。
插入数据的命令格式为:put ‘表名’, ‘行键’, ‘列族:列名’, ‘值’
例如,插入第一条博客记录的标题:
put 'blog', 'mat001', 'info:title', 'Elephants'
重复此过程,插入 author、date 和 post 等列的值,以及其余四条记录。
统计与查询数据
插入数据后,可以使用 count、get 和 scan 命令来查看数据。
count ‘表名’:统计表中的行数。对于大表,可以指定间隔参数(如INTERVAL => 1000)来显示扫描进度。get ‘表名’, ‘行键’:获取指定行的所有单元格。可以附加参数来指定列、时间戳或版本。scan ‘表名’:扫描整个表或指定范围的行。可以使用STARTROW和STOPROW参数限定范围,STOPROW是排他的。
更新数据
在HBase中,put 命令也用于更新数据。其原理是插入一个具有新时间戳的单元格。HBase会为每个列族保留指定数量的版本(如之前设置的3个)。

例如,更新 Michelle004 的博客日期:
put 'blog', 'Michelle004', 'info:date', '1997-07-07'
执行 get 命令时,默认返回最新版本。可以通过指定 VERSIONS 参数来获取多个历史版本。

删除数据与表
使用 delete 命令可以删除特定的单元格。可以指定精确的时间戳来删除特定版本。
例如,删除 Bob003 的日期信息:
delete 'blog', 'Bob003', 'info:date'
要删除整个表,必须先使用 disable ‘表名’ 命令禁用表,然后才能使用 drop ‘表名’ 命令删除它。使用 list 命令可以查看当前所有的表。
实战演示
现在,让我们通过一个实战演示来巩固以上知识。假设我们已经登录到HBase服务器。
- 启动HBase Shell:在终端输入
hbase shell。 - 查看帮助:输入
help查看所有命令;输入help “list”查看list命令的用法。 - 查看现有表:输入
list。 - 检查状态:输入
status和status ‘detailed’。 - 创建
blog表:使用前面提到的create命令。 - 插入数据:使用一系列
put命令插入示例数据。 - 查询数据:使用
scan ‘blog’查看所有数据;使用get ‘blog’, ‘mat001’查看特定行。 - 删除表:依次执行
disable ‘blog’和drop ‘blog’,最后用list确认表已删除。
通过这个演示,你可以清晰地看到如何使用JRuby交互式Shell与HBase进行完整的交互。
总结


本节课中我们一起学习了如何启动和使用HBase的JRuby交互式Shell。我们涵盖了通过Shell与HBase服务器交互的核心操作,包括创建表、插入记录、删除记录、更新记录以及扫描和获取整个表的数据。掌握这些基本命令是有效使用HBase进行大数据处理的重要一步。
089:使用Java API实现HBase增删改查 📚

在本节课中,我们将学习如何使用HBase的Java原生API来操作和查询HBase表。HBase本身是用Java编写的,因此它提供了功能强大的Java原生API,支持以编程方式对基于行的表进行数据操作。我们将重点介绍如何执行增删改查(CRUD)操作以及扫描操作。
概述
HBase的Java原生API是访问HBase表中记录的最快方式。通过它,我们可以执行所有在HBase Shell中能完成的操作,包括数据定义(DDL)和数据操作(DML)。本节我们将专注于使用Java API进行DML操作。
客户端API使用步骤
以下是使用HBase Java客户端API的一般步骤:
- 创建HBase配置对象。
- 构建HTable对象,它代表要操作的HBase表。
- 执行操作,如扫描(Scan)、插入(Put)、获取(Get)和删除(Delete)。
- 操作完成后,关闭HTable实例以释放资源。
接下来,我们将详细探讨这些步骤中的关键概念和操作。
1. 创建HBase配置对象 🛠️
在使用CRUD API之前,首先需要创建一个HBase配置对象。该对象代表了HBase客户端代码的配置。
代码示例:创建配置对象
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.conf.Configuration;
Configuration config = HBaseConfiguration.create();
HBaseConfiguration.create() 方法会从类路径中的 hbase-default.xml 文件加载配置。因此,你需要确保将HBase的JAR文件(位于 $HBASE_HOME/lib 目录下)以及包含 hbase-site.xml 的配置目录($HBASE_HOME/conf)添加到程序的类路径中。
2. 构建HTable对象 📊
HTable对象是客户端与单个HBase表交互的接口。所有对HBase表的操作都需通过此对象进行。
代码示例:构建HTable对象
import org.apache.hadoop.hbase.client.HTable;
HTable table = new HTable(config, "your_table_name");
创建HTable实例是一个开销较大的操作,因为它需要扫描元数据表并与HMaster通信。建议每个线程创建一个HTable实例并尽可能重用。HTable不是线程安全的,每个线程应持有自己的HTable对象实例。

3. 执行数据操作 🔄
上一节我们介绍了如何建立与HBase表的连接,本节中我们来看看如何使用Java API进行核心的数据操作。

插入(Put)操作
Put操作用于向表中插入或更新单行数据。操作以行为单位,具有原子性。
代码示例:插入数据
import org.apache.hadoop.hbase.client.Put;
import static org.apache.hadoop.hbase.util.Bytes.toBytes;
Put put = new Put(toBytes("row_key_1"));
put.add(toBytes("column_family"), toBytes("column_name"), toBytes("value"));
table.put(put);
以下是插入操作的关键点:
- 行键(Row Key):构造Put对象时必须指定行键,且所有键值都需要转换为字节数组。
- 字节转换:HBase内部以字节形式存储所有数据。可以使用
org.apache.hadoop.hbase.util.Bytes工具类的toBytes()方法进行转换。 - 添加列:通过
put.add()方法指定列族、列限定符和值。 - 提交操作:最后通过
table.put(put)将数据写入表。
获取(Get)操作
Get操作用于从表中检索单行数据。你可以指定条件来缩小返回结果的范围。
代码示例:获取数据
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Result;
Get get = new Get(toBytes("row_key_1"));
// 可选:缩小查询范围
get.addFamily(toBytes("column_family"));
get.addColumn(toBytes("column_family"), toBytes("column_name"));
Result result = table.get(get);
// 处理结果
byte[] value = result.getValue(toBytes("column_family"), toBytes("column_name"));
System.out.println(Bytes.toString(value));
以下是获取数据时可以使用的筛选条件:
- 按列族筛选:
get.addFamily() - 按列筛选:
get.addColumn() - 按时间范围筛选:
get.setTimeRange() - 按版本数筛选:
get.setMaxVersions()
Result 对象包含了查询返回的数据,可以通过 getValue() 等方法提取具体内容。
扫描(Scan)操作
Scan操作用于遍历整个表或表的一部分。
代码示例:扫描表
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.ResultScanner;
Scan scan = new Scan();
ResultScanner scanner = table.getScanner(scan);
for (Result result : scanner) {
// 处理每一行结果
// 例如调用之前定义的 printResult 方法
}
scanner.close();
删除(Delete)操作
Delete操作用于删除表中的行或特定单元格。删除也是以行为单位进行的。

代码示例:删除数据
import org.apache.hadoop.hbase.client.Delete;

// 删除整行
Delete delete = new Delete(toBytes("row_key_1"));
table.delete(delete);
// 删除特定列
Delete specificDelete = new Delete(toBytes("row_key_2"));
specificDelete.deleteColumn(toBytes("column_family"), toBytes("column_name"));
table.delete(specificDelete);
以下是删除操作的方法:
deleteColumn():删除指定列的最新版本。deleteColumns():删除指定列的所有版本。
4. 资源清理 🧹
所有操作执行完毕后,必须关闭HTable对象以释放网络连接和缓冲区等资源。建议使用 try-finally 块或Java 7的 try-with-resources 语句来确保资源被正确关闭。

代码示例:使用try-with-resources
try (HTable table = new HTable(config, "table_name")) {
// 执行各种CRUD操作
Put put = new Put(...);
table.put(put);
// ...
} // 自动关闭table
项目配置与运行 🚀
为了成功编译和运行HBase Java程序,需要进行正确的项目配置。

以下是配置项目类路径的关键步骤:
- 添加HBase库:将
$HBASE_HOME/lib目录下的所有JAR文件添加到项目的构建路径中。 - 添加配置目录:将
$HBASE_HOME/conf目录(包含hbase-site.xml)也添加到类路径中,以便客户端能定位到HBase集群。 - 注意依赖:HBase的JAR通常已包含必要的Hadoop依赖,因此通常无需单独添加Hadoop库。
在集成开发环境(IDE)中配置好库后,即可编译项目生成JAR文件。在命令行中运行时,需要设置包含所有必要JAR和配置目录的 CLASSPATH 环境变量。

总结
在本节课中,我们一起学习了如何使用HBase的Java CRUD API来操作HBase表。我们掌握了四个核心操作对象的使用方法:使用 Put 对象插入或更新记录,使用 Get 对象检索记录,使用 Delete 对象删除记录,以及使用 Scan 对象遍历表。所有操作都通过 HTable 对象执行,该对象由HBase配置对象和表名构造。最后,我们强调了显式关闭HTable对象以释放资源的重要性,并了解了正确配置项目类路径的必要步骤。通过掌握这些知识,你已经能够使用Java程序高效地与HBase进行交互了。
090:HBase模块总结 📚


在本节课中,我们将对HBase模块的核心内容进行总结。我们将回顾HBase的架构、数据模型以及操作方式,帮助你巩固对HBase的理解。
HBase架构总结 🏗️
上一节我们介绍了HBase的基本操作,本节中我们来看看HBase的整体架构特点。
HBase是一个分布式、版本控制的NoSQL数据库。
- 分布式:多台机器可以协同工作,形成一个统一的分布式数据库。
- 版本控制:可以存储同一个值的多个版本。
- NoSQL数据库:它不是关系型数据库。
HBase允许对大数据进行随机、实时的读写访问。这在普通的HDFS中难以实现,尽管HBase实际上是构建在Hadoop HDFS文件系统之上的。
以下是HBase的简要设置步骤:
- 下载HBase的
.tar.gz文件。 - 解压文件。
- 在
hbase-site.xml配置文件中,将其指向Hadoop的HDFS NameNode。

HBase数据模型总结 📊
了解了架构之后,我们来看看HBase独特的数据模型。
HBase的数据模型包含列族和列的概念,这与关系型数据库中固定的列不同。
- 在一个列族内,可以有许多列,这带来了灵活性,它是一种灵活的模式。
- 在列中,你可以存储任何想要的值。
因此,其内部数据结构可以表示为:
行键(Row ID) + 列族(Column Family) + 列(Column) + 时间戳(Timestamp) = 值(Value)
这就是HBase内部数据模型的存储方式。
HBase操作方式总结 🛠️
最后,我们来总结操作HBase的两种主要方式。
HBase提供了一个交互式Shell,你可以使用以下命令:
- DDL语句:创建表。
- DML语句:使用
put和delete进行记录的插入(更新)和删除操作。 - 查询语句:使用
get和scan从表中检索记录。
此外,我们还学习了Java Client API,它允许我们通过编程方式操作HBase:
- 使用
put方法向表中插入或更新记录。 - 使用
get方法从表中检索记录。 - 使用
delete方法删除表中的条目。
总结 ✨

本节课中我们一起学习了HBase的核心内容。我们回顾了HBase作为分布式、版本控制的NoSQL数据库的架构特点,探讨了其基于列族和列的灵活数据模型,并总结了通过交互式Shell和Java API进行数据操作的方法。希望本模块能帮助你建立起对HBase的基本认识。
091:Spark框架导论 🚀
在本节课中,我们将学习Spark框架的基本概念、架构、安装方法以及编程模型。通过本课程,您将能够理解Spark如何工作,它与Hadoop平台的关系,并掌握编写和运行Spark应用程序的基本技能。

Spark概述 🔍
上一节我们介绍了本课程的整体结构,本节中我们来看看Spark的起源、优势、特性以及它与MapReduce的区别。
Spark起源于加州大学伯克利分校的AMPLab实验室,旨在解决MapReduce在处理迭代算法和交互式数据挖掘任务时的性能瓶颈。Spark的主要优势在于其内存计算能力,这显著提高了数据处理速度。
以下是Spark的核心特性:
- 速度:Spark通过内存计算和优化的执行引擎,比Hadoop MapReduce快上百倍。
- 易用性:Spark提供了丰富的API(支持Java、Scala、Python和R),并包含用于SQL查询、流处理、机器学习和图计算的集成库。
- 通用性:Spark提供了一个统一的栈,可以处理批处理、交互式查询、实时流处理、机器学习和图计算等多种任务。
- 运行模式多样:Spark可以运行在本地模式、Apache Mesos、Hadoop YARN上,也可以独立运行。
Spark与MapReduce的主要区别在于数据处理模型。MapReduce将中间结果写入磁盘,而Spark尽可能将数据保留在内存中,从而减少了磁盘I/O开销。其核心数据抽象是弹性分布式数据集(RDD)。
Spark架构 🏗️
了解了Spark的基本概念后,本节我们来探讨Spark的架构及其多种运行方式。
Spark架构的核心是驱动程序(Driver Program)和集群管理器(Cluster Manager)。驱动程序包含应用程序的main函数并定义了RDD及其上的操作。集群管理器负责在集群中分配资源。
Spark支持多种运行模式,以适应不同的环境和需求:
- 本地模式:在单个机器上运行,用于开发和测试。
- 独立模式:使用Spark自带的简单集群管理器。
- Apache Mesos:运行在通用的集群管理平台Mesos之上。
- Hadoop YARN:运行在Hadoop的资源调度框架YARN之上,这是与Hadoop平台集成的主要方式。
本课程将主要关注Spark在Hadoop平台上的集成方式。
安装与运行Spark ⚙️
上一节我们介绍了Spark的架构,本节中我们将实际操作,学习如何安装和设置Spark,使其能够在Hadoop平台上运行。
安装Spark的步骤如下:
- 下载Spark:从Apache Spark官网下载预编译版本。
- 解压文件:将下载的压缩包解压到指定目录。
- 配置环境变量:设置
SPARK_HOME环境变量,并将$SPARK_HOME/bin添加到PATH中。 - 配置Hadoop集成:为了在YARN上运行,需要确保
HADOOP_CONF_DIR或YARN_CONF_DIR环境变量指向Hadoop的配置文件目录。
完成安装后,可以通过运行 spark-shell 命令来启动交互式Shell,验证安装是否成功。
Spark编程模型 💻
在成功安装Spark之后,本节我们来学习如何编写Spark应用程序。您将了解RDD操作、Spark Shell的使用以及如何提交作业。
编写Spark应用程序通常涉及以下步骤:
- 创建SparkContext:这是与Spark集群连接的主要入口点。
val conf = new SparkConf().setAppName("MyApp") val sc = new SparkContext(conf) - 创建RDD:可以从外部数据集(如HDFS文件)或驱动程序中的集合创建RDD。
val data = sc.textFile("hdfs://...") - 对RDD进行转换和行动操作:转换操作(如
map,filter)生成新的RDD,行动操作(如count,collect)触发计算并返回结果。 - 使用Spark Shell:这是一个交互式环境,非常适合快速原型设计和数据探索。
- 提交应用程序:使用
spark-submit脚本将打包好的应用程序提交到集群运行。spark-submit --class MyApp --master yarn myapp.jar
鉴于您已经具备MapReduce、Hive和Pig的编程经验,理解Spark API应该不难。本课程将主要概述API,您可以查阅官方文档来编写具体的应用程序。
Spark实践操作 🛠️
理论结合实践,本节我们将实际与Spark系统交互,演示如何编写和运行一个简单的Spark数据处理脚本。
我们将通过一个例子,使用Spark Shell来统计一个文本文件中各单词出现的频率。这个过程将让您亲身体验RDD的创建、转换和行动操作。
Spark其他组件 📦
Spark不仅仅是一个核心计算引擎。在学习了核心编程之后,本节我们将简要介绍构建在Spark核心之上的其他重要组件,这些组件扩展了Spark的功能。
Spark生态系统包含多个高级组件,为特定领域的计算任务提供了便利的API:

- Spark SQL:用于处理结构化数据的模块,支持使用SQL或DataFrame API进行查询。
- Spark Streaming:用于处理实时流数据的组件。
- MLlib:一个可扩展的机器学习库。
- GraphX:用于图计算和并行图处理的API。
这些组件与Hadoop生态系统中的其他工具(如Hive、Storm、Mahout)存在竞争和互补关系。了解它们有助于您在未来的数据科学工作中选择合适的技术。
总结 📝
本节课中,我们一起学习了Spark框架。我们从Spark的概述和优势开始,了解了其内存计算的特性以及与MapReduce的区别。接着,我们探讨了Spark的架构和多种运行模式,特别是与Hadoop YARN的集成。我们逐步讲解了Spark的安装、配置过程,并介绍了基于RDD的编程模型,包括如何使用Spark Shell和提交作业。通过实践环节,我们巩固了对核心概念的理解。最后,我们概览了Spark SQL、Streaming、MLlib和GraphX等扩展组件,认识到Spark是一个统一、通用的大数据处理平台。

通过本课程的学习,您应该能够在自己的环境中安装和设置Spark,使用Spark Shell进行交互式数据分析,编写基本的数据处理脚本,并向本地或YARN集群提交Spark作业,从而获得宝贵的Spark实战经验。
092:Spark核心概述 🚀

在本节课中,我们将学习Apache Spark是什么,了解其诞生的背景和原因,并对比Spark与Hadoop生态系统。我们还将探讨Spark的关键特性、运行方式、相关研究论文、发行版本以及丰富的社区资源。

Spark是什么?🤔
Apache Spark是一个开源的集群计算平台。这意味着它拥有自己的调度守护进程,包括一个主节点和多个工作节点,可以在多台机器上启动不同的进程和任务。从这个角度看,它类似于Hadoop的YARN。

Spark也是一个数据处理平台,拥有自己的核心引擎——Spark引擎。这使得它成为Hadoop MapReduce引擎的一个竞争者。同时,Spark也可以与Hadoop生态系统互补,运行在Hadoop之上,利用HDFS和YARN。
Spark是一个统一平台,支持多种工作负载。与Hadoop核心项目主要专注于批处理不同,Spark将流处理、机器学习、SQL查询等功能都集成在一个项目中。

Spark项目本身使用Scala语言编写,运行在Java虚拟机之上。它最初由加州大学伯克利分校的AMPLab实验室开发,并于2010年捐赠给Apache软件基金会。

Spark的背景 📜

回顾Hadoop的起源,2004年Google发布的MapReduce论文启发了Yahoo在2006年启动Hadoop项目。大约在2009年,Matei Zaharia在加州大学伯克利分校攻读博士期间创建了Spark。
2010年,Matei Zaharia发表了题为《Spark: Cluster Computing with Working Sets》的论文,这使Spark开始受到关注并逐渐流行起来。2011年,伯克利AMPLab启动了名为BDAS(伯克利数据分析栈)的项目,其中就包含了Spark。
2013年,Matei Zaharia和其他Spark项目的贡献者共同创立了Databricks公司。至此,Spark生态系统正式形成,与Hadoop生态系统既存在竞争,又相互补充。

Spark的官方网站是 spark.apache.org。官网宣称它是一个“闪电般快速”的集群计算平台,比Hadoop MapReduce快10到100倍。


为什么需要Spark?⚡
上一节我们介绍了Spark的背景,本节我们来看看Spark旨在解决Hadoop MapReduce的哪些痛点。

Hadoop MapReduce的局限性:
- 依赖HDFS:MapReduce处理数据时,必须先将数据复制到HDFS中,处理结果也写回HDFS。虽然可以通过其他项目(如Flume、Sqoop)将外部数据导入HDFS,但这增加了复杂性。
- 迭代处理慢:MapReduce的每个步骤都需要将中间结果写入磁盘。对于需要多次迭代的算法(如机器学习),反复的磁盘I/O会严重影响性能。
- 交互操作慢:虽然可以通过Hive或Pig进行交互式查询,但每个查询都可能触发一个或多个MapReduce作业,仍然受限于磁盘读写速度。
- 子项目爆炸:Hadoop生态系统由众多独立的子项目(如Hive、Pig、Mahout、Storm)组成,需要分别安装和集成,增加了部署和维护的复杂度。
- 编程模型局限:MapReduce要求所有问题都抽象为键值对(key-value pair)的处理,编程模型不够直观和灵活。

Spark正是为了克服MapReduce的上述局限性而设计的。

Spark的核心特性 ✨

了解了Spark要解决的问题后,我们来看看Spark提供了哪些关键特性来应对这些挑战。
以下是Spark的核心特性:

- 利用内存加速处理:数据读取和写入的速度,内存(约10 GB/s)远快于SSD(约600 MB/s)和机械硬盘(约100 MB/s)。Spark通过其核心数据结构——弹性分布式数据集(RDD),尽可能将数据保留在内存中进行计算,仅在内存不足时才溢出到本地磁盘,从而极大提升了处理速度。
- 支持迭代处理:Spark可以在内存中保存中间计算结果,供下一次迭代直接使用,避免了反复的磁盘I/O,特别适合机器学习等需要多次迭代的算法。
- 支持交互式处理:由于数据主要驻留在内存中,Spark能够提供真正快速的交互式查询体验,用户可以通过Spark Shell与数据进行实时交互。
- 丰富的操作符:除了Map和Reduce,Spark还支持
count、filter、join、groupBy等多种关系型操作符,以及通过Spark SQL提供的SQL查询接口。 - 多语言统一API:Spark提供了Scala、Java、Python和R语言的统一API,允许用户使用自己熟悉的语言进行开发。
- 实时流处理:通过Spark Streaming模块,Spark可以处理实时流入的数据流。
- 内置机器学习库:MLlib是Spark内置的机器学习库,提供了常见的机器学习算法。
- 图处理:GraphX模块提供了图处理API,支持像PageRank、三角形计数等图算法。
- 集群管理器:Spark自带集群管理器(Standalone模式),可以管理由主节点和工作节点组成的集群。它也支持运行在Apache Mesos或Hadoop YARN之上。
- 统一的大数据框架:Spark将批处理、交互查询、流处理、机器学习和图计算整合在一个统一的平台中,简化了技术栈。
- 统一数据帧API:在Spark 2.0中引入了DataFrame API(在RDD之上构建),它不仅知道数据内容,还知道数据的类型(Schema),为不同语言提供了统一且更高效的数据操作接口。

Hadoop vs. Spark 生态系统对比 🔄
我们已经分别了解了Hadoop和Spark的特性,现在我们来系统地对比一下两者的生态系统。

Hadoop生态系统核心包括:
- HDFS:分布式文件系统。
- YARN:集群资源管理和作业调度框架。
- MapReduce:数据处理引擎。
- 其他子项目:如Hive(SQL)、Pig(脚本)、Mahout(机器学习)、Storm(流处理)等,这些是独立于核心Hadoop的项目,需要额外集成。
Spark生态系统核心包括:
- Spark Core:包含RDD和核心API。
- 内置模块:Spark SQL、Spark Streaming、MLlib、GraphX。这些功能都集成在Spark项目内,下载Spark即包含所有模块。
- 可集成的外部项目:如集群管理器Apache Mesos、分布式文件系统Tachyon等。
- 多语言支持:Scala、Java、Python、R。
关键区别: Hadoop的核心三组件与高级功能(如SQL、机器学习)是分离的不同项目;而Spark将高级功能作为统一项目内的内置模块。Spark既可以独立运行,也可以完美地运行在现有的Hadoop集群之上,使用HDFS存储和YARN进行资源调度。

Spark 运行架构全景图 🗺️
为了更直观地理解Spark如何融入大数据技术栈,我们来看一下Spark从数据源到处理引擎的完整运行架构。
下图展示了Spark生态系统的大致全景:


[数据源]
|
|--- 分布式文件系统 (HDFS, Tachyon, S3...)
|
|--- 分布式数据库 (MongoDB, Cassandra, HBase...)
|
|--- 流数据源 (Kafka, Flume...)
|
v
[Spark Core (运行在集群管理器上)]
|
|--- 集群管理器:Standalone模式 / Apache Mesos / Hadoop YARN
|
v
[Spark 处理模块]
|
|--- Spark SQL (交互查询)
|
|--- Spark Streaming (流处理)
|
|--- MLlib (机器学习)
|
|--- GraphX (图计算)
|
v
[多语言API]
|
|--- Scala / Python / Java / R
|
v
[应用程序]
架构要点:
- 数据源多样性:Spark可以从HDFS、本地文件系统、Amazon S3、以及各种NoSQL和关系型数据库中读取数据,处理后再写回这些系统。
- 集群管理器灵活性:Spark可以运行在自带的Standalone集群模式上,也可以运行在更通用的集群管理器如Apache Mesos或Hadoop YARN上。
- 处理引擎统一性:所有高级处理模块(SQL、Streaming等)都构建在Spark Core之上,共享RDD和内存计算的优势。
- 开发接口友好:为不同开发者提供了Scala、Python、Java、R等多种编程语言接口。

Spark 发行版、社区与资源 📚
了解了Spark的技术架构后,我们来看看在实际应用中如何获取、使用Spark以及从哪里获得支持。
以下是Spark相关的发行版与资源:

-
主要发行版:
- Databricks:由Spark创始人创建的公司,提供商业支持和托管平台。
- Cloudera CDH、Hortonworks HDP、MapR:主流Hadoop发行商都将Spark集成在其平台中。
- Amazon EMR:AWS的云服务,提供Spark作为处理选项。
- 其他如IBM、Oracle等公司也提供集成了Spark的解决方案。
-
Spark版本:
- Spark 1.x:稳定版本,核心是RDD API。本课程使用此版本。
- Spark 2.x:新版本,核心是统一DataFrame/Dataset API,性能更优,并增强了对SQL标准的支持。
-
社区与第三方包:
- spark-packages.org:社区维护的第三方库仓库,包含各种数据源连接器、算法库等。
- 官方文档:访问 spark.apache.org/docs 获取API文档,可根据编程语言(Scala/Python/Java/R)筛选示例。
- Wiki与邮件列表:官方Wiki和用户邮件列表是解决问题的重要渠道。
- Stack Overflow:使用
[apache-spark]标签可以找到大量问答。




-
学习资源:
- 书籍:目前多数书籍基于Spark 1.x(如《Learning Spark》)。Spark 2.x的书籍正在陆续出版。
- 博客:Databricks公司博客是获取深度技术文章的好地方。
- 会议:Spark Summit 是主要的Spark技术会议,每年在旧金山、纽约和欧洲举行。
- 认证:Databricks、Cloudera、IBM等公司提供Spark开发者认证。
-
应用案例:许多知名公司都在使用Spark,如Amazon、eBay、阿里巴巴、腾讯等,用于处理大规模数据分析和机器学习任务。


总结 🎯


本节课中,我们一起学习了Apache Spark的核心概念。
我们了解到Spark是一个快速的、统一的集群计算和数据处理平台,它通过内存计算和弹性分布式数据集(RDD) 解决了Hadoop MapReduce在迭代处理和交互查询上的性能瓶颈。Spark集成了SQL查询、流处理、机器学习和图计算等多种功能,并提供了多语言API。
Spark既可以独立运行,也可以与现有的Hadoop生态系统(HDFS, YARN)无缝集成。它拥有活跃的社区、丰富的第三方库以及由多家厂商提供的商业发行版和支持。
总而言之,Spark以其速度、易用性和统一的栈,成为了现代大数据处理中一个极其重要的工具。
093:Spark架构解析 🚀

在本节课中,我们将学习Spark的核心架构组件、应用运行模式、主节点模式以及其处理模型。我们将深入理解RDD、驱动程序、执行器等核心概念,并了解Spark如何以不同模式运行。


Spark核心架构组件

上一节我们介绍了Spark的概述,本节中我们来看看构成Spark核心架构的具体组件。
Spark的核心架构组件包括:RDD、分区、驱动程序、执行器、任务和核心。这些组件共同协作,实现了Spark的分布式数据处理能力。
RDD:弹性分布式数据集
RDD是Spark最基本的数据抽象,代表一个不可变、可分区、可并行计算的元素集合。其核心思想源于2010年Matei Zaharia等人的论文《Spark: Cluster Computing with Working Sets》。
- 弹性:如果持有某个分区的机器崩溃,Spark可以从数据源重建该分区。
- 分布式:数据被分割并分布在集群的多个节点上。
- 数据集:可以是任何类型的数据集合。

公式/代码描述:RDD[T] 表示一个类型为 T 的RDD。
分区

RDD被分割成多个分区,每个分区是数据集的一个子集。分区是Spark中并行处理的基本单位。
- 从HDFS加载数据时,每个HDFS块通常成为一个RDD分区。
- 分区数量决定了并行度。分区越多,并行处理能力越强,因为每个分区由一个单独的任务处理。
驱动程序

驱动程序是Spark应用的主控程序。它负责执行用户编写的main函数,并创建SparkContext对象。
驱动程序的主要职责包括:
- 与集群管理器通信,申请执行任务所需的资源。
- 将用户程序分解为多个任务。
- 将任务调度到各个执行器上运行。
代码描述:驱动程序是包含 main 方法并创建 SparkContext 的进程。

执行器与任务

执行器是运行在集群工作节点上的工作进程。每个应用都有自己的一组执行器。

- 执行器负责在内存中存储RDD数据。
- 执行器内部运行多个任务。任务是实际执行计算的工作单元,以线程的形式运行在执行器进程中。
- 与Hadoop MapReduce(每个任务运行在独立的JVM中)不同,Spark的任务是执行器进程内的线程,这减少了任务启动的开销。

核心关系:一个执行器包含多个核心(线程),每个核心运行一个任务,每个任务处理一个RDD分区。
Spark核心应用模式
了解了核心组件后,我们来看看Spark应用可以以哪两种模式运行。
Spark支持两种主要的应用执行模式:交互式模式和批处理模式。
以下是两种模式的详细介绍:

-
交互式模式
- 通过Spark Shell实现,用于即席查询和迭代式数据分析。
- 启动后,Shell本身作为驱动程序,并内置了
SparkContext对象,用户可以直接输入命令与Spark交互。 - 支持Scala(
spark-shell)、Python(pyspark)和R(sparkR)语言。
-
批处理模式
- 通过
spark-submit工具提交打包好的应用程序(JAR包或Python脚本)。 - 应用程序通常遵循“读取数据 -> 构建RDD -> 转换处理 -> 输出结果”的流程。
- 这是生产环境中运行Spark作业的标准方式。
- 通过
Spark主节点模式

无论以何种应用模式运行,我们都需要指定集群的主节点模式,它决定了Spark如何管理集群资源。
Spark支持多种主节点模式,主要分为本地模式和分布式集群模式。
以下是各种主节点模式的说明:
-
本地模式
- 通过
--master local[*]指定。 - 所有组件(驱动程序、执行器)都在单个机器的单个JVM进程内运行。
- 适用于开发、测试和原型验证,而非分布式处理。
- 通过
-
独立模式
- 通过
--master spark://HOST:PORT指定。 - 使用Spark内置的独立集群管理器。
- 架构包含一个Spark主节点(类似YARN的ResourceManager)和多个工作节点(Worker)。
- 驱动程序向主节点申请资源,并在工作节点上启动执行器。
- 通过
-
YARN模式
- 通过
--master yarn指定。 - 利用Hadoop YARN作为集群资源管理器。这是企业环境中常见的选择。
- 根据
--deploy-mode参数的不同,分为两种子模式:- 客户端模式:驱动程序运行在提交作业的客户端机器上。客户端必须保持在线直至作业完成。
- 集群模式:驱动程序运行在YARN的Application Master容器中。客户端提交作业后即可断开连接。
- 通过
-
Mesos模式
- 通过
--master mesos://HOST:PORT指定。 - 使用Apache Mesos作为集群资源管理器。
- 通过
Spark处理模型
最后,我们来了解Spark的编程处理模型,这与我们熟悉的MapReduce模型有相似之处,但更加强大。
Spark的操作分为两大类:转换和行动。
- 转换操作:用于从现有RDD创建新的RDD。例如
map()、filter()、groupByKey()。转换是惰性求值的,它们只记录转换关系,并不立即执行计算。 - 行动操作:触发实际的计算,并向驱动程序返回结果或向存储系统写入数据。例如
count()、collect()、saveAsTextFile()、reduce()。
核心区别:只有当一个行动操作被调用时,Spark才会开始计算所有累积的转换操作,这个特性使得Spark可以进行高效的优化。


总结
本节课中我们一起学习了Spark的核心架构。

我们首先介绍了RDD这一核心数据抽象,它是在内存中跨机器分布的弹性数据集。接着,我们了解了驱动程序作为应用的控制中心,以及执行器作为运行任务的工作进程。
然后,我们探讨了Spark的两种应用模式:用于探索的交互式模式和用于生产的批处理模式。在主节点模式部分,我们区分了用于本地测试的本地模式,以及用于分布式计算的独立模式、YARN模式和Mesos模式,并特别说明了YARN模式下客户端与集群部署的区别。
最后,我们学习了Spark的处理模型,理解了惰性求值的转换操作和触发实际计算的行动操作之间的关键差异。

这些概念构成了Spark强大功能的基础,随着后续的实践操作,你对它们的理解会更加清晰和深入。
094:Spark环境配置与运行 🚀
在本节课中,我们将学习如何下载、安装和运行Apache Spark。我们将重点学习两种运行模式:一是将Spark与Hadoop YARN架构结合运行(即Spark on YARN),二是在本地运行Spark。我们将通过详细的步骤,确保初学者能够成功搭建并运行Spark环境。

概述
本节教程的目标是配置一个完整的Spark开发与运行环境。我们将依次完成以下任务:
- 下载并安装Spark。
- 探索Spark的安装目录结构。
- 配置Spark以集成Hadoop环境。
- 下载并安装Scala语言和SBT构建工具。
- 学习如何运行Spark Shell进行交互式操作。
- 学习如何使用Spark Submit提交批处理应用。

下载与安装Spark

Spark是Apache的开源项目。我们可以从其官方网站下载。
以下是下载和安装Spark的步骤:

- 访问下载页面:打开浏览器,访问
spark.apache.org,点击“Download”链接,或直接访问下载存档页面。 - 选择版本:本教程使用Spark 2.4.4版本。请确保下载与你的Hadoop版本兼容的Spark。
- 选择包类型:由于我们已经安装了Hadoop,因此需要选择“Pre-built for Apache Hadoop 2.7 and later”或类似的“Without Hadoop”选项。我们将下载
spark-2.4.4-bin-without-hadoop.tgz。 - 解压安装:将下载的压缩包解压到Hadoop生态系统的统一目录下,例如
/usr/local/hadoop/。
cd /usr/local/hadoop
tar -zxvf ~/Downloads/spark-2.4.4-bin-without-hadoop.tgz

解压后,你将在 /usr/local/hadoop/ 目录下看到一个名为 spark-2.4.4-bin-without-hadoop 的文件夹。

探索Spark目录结构
上一节我们完成了Spark的安装,本节我们来查看其目录结构,了解各个文件夹的作用。

进入Spark安装目录,你会看到以下主要文件夹:
bin/:包含最重要的命令行工具。spark-shell:用于以交互模式启动Spark。spark-submit:用于提交批处理应用程序。spark-sql:用于以SQL模式执行Spark。
conf/:存放配置文件。spark-env.sh.template:环境变量配置模板。spark-defaults.conf:默认配置。slaves:在独立集群模式下,用于指定工作节点。
examples/:包含多种语言的示例代码(Java, Python, R, Scala),是学习的好资源。lib/:Spark的核心库文件。sbin/:包含用于启动Spark守护进程的脚本。注意:由于我们将Spark运行在YARN上,而非独立模式,因此通常不需要使用此目录下的脚本。
了解这些目录有助于后续的配置和问题排查。




配置Spark环境变量
为了让系统识别Spark命令,我们需要设置环境变量。

编辑用户主目录下的 .bash_profile 或 .bashrc 文件,添加以下内容:

export SPARK_HOME=/usr/local/hadoop/spark-2.4.4-bin-without-hadoop
export PATH=$PATH:$SPARK_HOME/bin



SPARK_HOME:指向Spark的安装目录。PATH:将Spark的bin目录加入系统路径,这样你就可以在终端直接运行spark-shell和spark-submit命令。




保存文件后,执行 source ~/.bash_profile 使配置生效。



配置Spark以集成Hadoop
现在,我们需要告诉Spark Hadoop的位置,以便它们协同工作。
以下是配置步骤:
-
进入配置目录:
cd $SPARK_HOME/conf -
创建环境配置文件:复制模板文件并创建实际的配置文件。
cp spark-env.sh.template spark-env.sh -
编辑配置文件:使用文本编辑器(如vi)打开
spark-env.sh文件,在文件末尾添加以下两行:export SPARK_DIST_CLASSPATH=$(hadoop classpath) export HADOOP_CONF_DIR=/usr/local/hadoop/hadoop-3.2.0/etc/hadoop- 第一行:通过执行
hadoop classpath命令,自动获取Hadoop的所有类库路径,并设置为Spark的类路径。这是一种简便的集成方式。 - 第二行:明确指定Hadoop配置文件的目录,确保Spark能读取到Hadoop的核心配置(如
core-site.xml,hdfs-site.xml,yarn-site.xml)。
- 第一行:通过执行
完成这些配置后,Spark就知道了如何连接到现有的Hadoop集群。


下载与安装Scala
Spark本身运行在JVM上,但为了使用Scala语言编写和编译Spark应用,我们需要单独安装Scala。
以下是安装步骤:
- 访问官网下载:访问
scala-lang.org,下载与Spark 2.4.4兼容的Scala版本(本教程使用2.11.12)。 - 解压安装:将下载的Scala压缩包解压到
/usr/local/hadoop/目录。cd /usr/local/hadoop tar -zxvf ~/Downloads/scala-2.11.12.tgz - 配置环境变量:编辑
.bash_profile文件,添加Scala环境变量。
保存并export SCALA_HOME=/usr/local/hadoop/scala-2.11.12 export PATH=$PATH:$SCALA_HOME/binsource配置文件。现在,你可以在命令行使用scalac(Scala编译器)命令了。
下载与安装SBT(Scala构建工具)
为了将Scala源代码打包成可执行的JAR文件,我们需要一个构建工具。SBT是Scala项目常用的构建工具,类似于Java的Maven或Gradle。
以下是安装步骤:
- 访问官网下载:访问
scala-sbt.org,下载SBT(本教程使用1.3.3版本)。 - 解压安装:将下载的SBT压缩包解压到
/usr/local/hadoop/目录。cd /usr/local/hadoop tar -zxvf ~/Downloads/sbt-1.3.3.tgz - 配置环境变量:编辑
.bash_profile文件,添加SBT环境变量。
保存并export SBT_HOME=/usr/local/hadoop/sbt export PATH=$PATH:$SBT_HOME/binsource配置文件。现在,你可以在命令行使用sbt命令来打包项目了(例如,sbt package)。
运行Spark Shell(交互模式)
环境配置完成后,我们可以开始运行Spark了。首先,尝试交互模式。
确保Hadoop的所有守护进程(NameNode, DataNode, ResourceManager等)已经启动。然后,在终端直接输入:

spark-shell
这将启动一个基于Scala的交互式命令行界面。在这个界面中,你可以逐行输入Scala代码来操作Spark,进行数据探索和分析,类似于Hive或Pig的交互模式。
示例:交互式单词计数
在Spark Shell中,你可以执行类似以下的代码片段来对HDFS上的文件进行单词计数:
// 从HDFS读取文件
val inputFileRDD = sc.textFile(“hdfs://localhost:9000/input/input.dat“)
// 将每行文本拆分成单词
val wordsRDD = inputFileRDD.flatMap(line => line.split(“ “))
// 将每个单词映射为 (单词, 1) 的键值对
val pairsRDD = wordsRDD.map(word => (word, 1))
// 按单词聚合,累加计数
val wordCountsRDD = pairsRDD.reduceByKey(_ + _)
// 执行行动操作,将结果保存回HDFS
wordCountsRDD.saveAsTextFile(“hdfs://localhost:9000/output“)
这段代码演示了Spark的核心转换和行动操作。注意,直到 saveAsTextFile 这个“行动”被调用时,计算才会真正执行。
运行Spark Submit(批处理模式)
除了交互模式,我们更常使用 spark-submit 来提交打包好的应用程序JAR包。

以下是使用 spark-submit 的几种常见模式:
- 本地模式:在单个JVM进程中运行,用于测试。
spark-submit --master local --driver-memory 600m /path/to/your-app.jar


- YARN客户端模式:Driver程序运行在提交任务的客户端机器上,但任务执行在YARN集群中。
spark-submit --master yarn --deploy-mode client --num-executors 2 --driver-memory 600m --executor-memory 600m /path/to/your-app.jar


- YARN集群模式:Driver程序也运行在YARN集群的某个容器中,客户端在提交任务后可以断开。
spark-submit --master yarn --deploy-mode cluster --num-executors 2 --driver-memory 600m --executor-memory 600m /path/to/your-app.jar

关键参数说明:
--master:指定运行模式(local,yarn)。--deploy-mode:指定部署模式(client,cluster)。--num-executors:指定执行器数量。--driver-memory,--executor-memory:设置驱动器和执行器的内存。

通过切换这些参数,你可以灵活地在不同环境下运行Spark应用。


总结
在本节课中,我们一起学习了Spark环境的完整配置与运行流程。

我们首先从官网下载并安装了指定版本的Spark,并了解了其目录结构。接着,我们通过配置环境变量和 spark-env.sh 文件,成功将Spark集成到已有的Hadoop YARN环境中。为了支持Scala开发,我们还下载并安装了Scala语言和SBT构建工具。


最后,我们实践了Spark的两种主要运行方式:使用 spark-shell 进行交互式数据操作,以及使用 spark-submit 以本地、YARN客户端、YARN集群等多种模式提交打包好的应用程序。



现在,你已经拥有了一个可以运行Spark on YARN的环境,可以开始进行大数据处理任务的开发与测试了。
095:Spark Core 编程模型 🚀


在本节课中,我们将学习 Spark Core 编程模型的核心概念,包括 Spark 配置与上下文对象、RDD 的创建、转换与行动操作,以及如何构建和运行一个 Spark 应用程序。
Spark 配置对象 ⚙️


Spark 配置对象代表了 Spark 环境的配置。它类似于 Hadoop 的配置对象,在实例化时会加载所有 Spark 的配置文件(如 spark-env.sh 和 spark-defaults.conf)。
该对象内部以键值对的形式存储所有环境变量。你可以通过 set 方法修改配置,或通过 get 方法获取当前值。
以下是两个重要的专用方法:
setAppName:设置应用程序名称,该名称会在资源管理器中显示。setMaster:设置主节点模式,例如local、yarn、mesos或standalone。
核心公式:SparkConf conf = new SparkConf().setAppName("MyApp").setMaster("local[*]")
Spark 配置对象是创建 Spark 上下文对象的基础。
Spark 上下文对象 🔌

Spark 上下文对象代表了与 Spark 集群的连接。它是访问 Spark 所有功能的主要入口点,在 Spark 应用程序中进行的绝大多数操作都需要此对象。
一个关键功能是使用它来创建 RDD。例如,你可以调用 textFile 方法从 HDFS 文件系统读取文本文件并构建 RDD。

重要限制:每个 JVM 中只能有一个活跃的 Spark 上下文对象。如果需要创建新的,必须先调用现有上下文的 stop 方法。
在 Spark Shell 中,配置和上下文对象已预先创建好。但在编写批处理应用程序时,必须显式实例化它们。

创建 RDD 📂
上一节我们介绍了 Spark 上下文对象,本节中我们来看看如何使用它来创建 RDD。
RDD 可以通过两种主要方式创建:

- 从外部数据源加载:这是生产环境中最常见的方式。可以从 HDFS、本地文件系统或数据库(如 MySQL、Cassandra)加载数据。
示例代码(Scala):val logLinesRDD = sc.textFile("hdfs://path/to/loglines.txt")

- 并行化驱动程序中的集合:将驱动程序内存中的集合(如列表、数组)并行化到集群中。这种方式主要用于原型测试。
示例代码(Python):data = ["fish", "cats", "dogs"]; rdd = sc.parallelize(data)
第一种方法适用于处理存储在分布式系统中的海量数据,而第二种方法则便于快速测试和小规模实验。
RDD 转换与行动操作 ⚡
一旦创建了 RDD,就可以对其应用两种类型的操作:转换和行动。

- 转换操作:从一个 RDD 生成一个新的 RDD。转换是惰性的,它们只是定义了计算逻辑,并不会立即执行。
- 示例:
map,filter,flatMap
- 示例:
- 行动操作:触发实际的计算,并返回结果给驱动程序或将结果保存到存储系统。行动操作会强制执行所有累积的转换。
- 示例:
count,collect,saveAsTextFile
- 示例:
工作流程:你通常会对初始 RDD 应用一系列转换(例如 filter -> map),最后调用一个行动操作(如 collect)。只有行动被调用时,Spark 才会构建一个有向无环图(DAG)并执行所有必要的计算。
向 Spark 传递函数 🧩


我们注意到,许多转换和行动操作(如 filter、map)需要将一个函数作为参数传入。以下是不同语言中的传递方式。

在 Java 中传递函数
Spark 提供了函数接口(如 Function, Function2)。你可以通过匿名内部类、具名类或 Java 8 的 Lambda 表达式来实现。
Lambda 表达式示例(Java 8):
JavaRDD<String> lines = ...;
JavaRDD<String> errors = lines.filter(s -> s.contains("error"));
在 Scala 中传递函数
Scala 支持 Lambda 函数,语法更简洁。
Lambda 函数示例(Scala):
val inputFileRDD = sc.textFile("input.txt")
val wordsRDD = inputFileRDD.flatMap(line => line.split(" "))
val wordCountsRDD = wordsRDD.map(word => (word, 1)).reduceByKey(_ + _)
上面的例子演示了经典的“词频统计”逻辑:flatMap 将每行拆分成单词,map 将每个单词映射为 (单词, 1) 的键值对,reduceByKey 将相同单词的计数相加。

Spark 应用程序的生命周期 🔄
一个典型的 Spark 应用程序遵循以下生命周期:
- 创建输入 RDD:从外部数据源加载或并行化集合。
- 定义转换:通过
filter、map等转换操作定义新的 RDD。 - 触发行动:调用
count、collect、save等行动操作来启动计算。 - 输出结果:将行动操作的结果返回给驱动程序或写入外部存储系统。
构建 Scala Spark 应用程序 🛠️
本节我们将学习如何构建一个基于 Scala 的 Spark 应用程序。以下是五个基本步骤:
- 创建项目结构:建立标准的 Scala 项目目录。
- 编写
build.sbt文件:定义项目名称、版本、Scala 版本和 Spark 依赖。 - 编写 Scala 类:创建包含
main方法的应用程序主类。 - 构建 JAR 包:使用 SBT 打包项目。
- 运行应用程序:通过
spark-submit提交 JAR 包执行。
以下是关键部分的示例:
项目目录结构:
wordcount/
├── build.sbt
└── src/
└── main/
└── scala/
└── SimpleApp.scala

build.sbt 文件内容:
name := "Simple Spark App"
version := "1.0"
scalaVersion := "2.10.6"
libraryDependencies += "org.apache.spark" %% "spark-core" % "1.6.3"
Scala 应用程序示例(词频统计):
import org.apache.spark.{SparkConf, SparkContext}

object SimpleApp {
def main(args: Array[String]) {
val conf = new SparkConf().setAppName("WordCount")
val sc = new SparkContext(conf)
val inputFile = "hdfs://.../input.txt"
val inputRDD = sc.textFile(inputFile)
val countsRDD = inputRDD.flatMap(line => line.split(" "))
.map(word => (word, 1))
.reduceByKey(_ + _)
countsRDD.saveAsTextFile("hdfs://.../output")
sc.stop()
}
}

构建命令:在项目根目录执行 sbt package 生成 JAR 文件。
运行 Spark 应用程序 ▶️
构建好 JAR 包后,可以通过 spark-submit 以不同模式运行:
-
本地模式:用于原型测试,所有组件运行在单个 JVM 中。
命令示例:spark-submit --master local[*] --class SimpleApp myapp.jar -
YARN 客户端模式:驱动程序运行在提交任务的客户端机器上,Executor 运行在 YARN 集群中。客户端进程必须保持活跃。
命令示例:spark-submit --master yarn --deploy-mode client --num-executors 2 ... myapp.jar -
YARN 集群模式:驱动程序也运行在 YARN 集群的 Application Master 中。客户端提交任务后即可断开。
命令示例:spark-submit --master yarn --deploy-mode cluster --num-executors 2 ... myapp.jar

选择合适的模式取决于你的测试、调试和生产部署需求。
总结 📚
本节课中我们一起学习了 Spark Core 编程模型的核心内容:
- Spark 配置对象:用于设置 Spark 环境参数。
- Spark 上下文对象:连接集群的主入口,用于创建 RDD。
- 创建 RDD:可通过加载外部数据或并行化内存集合实现。
- 转换与行动:转换是惰性操作,用于定义计算;行动触发实际计算并返回结果。
- 传递函数:在 Java 和 Scala 中如何将函数作为参数传递给 Spark 操作。
- 应用开发生命周期:从创建 RDD 到转换、行动和输出的完整流程。
- 构建与运行:我们学习了如何搭建 Scala Spark 项目,编写
build.sbt,创建应用程序,打包成 JAR,并通过spark-submit以不同模式(本地、YARN 客户端、YARN 集群)运行应用程序。

掌握这些基础知识是进行高效大数据处理和分析的关键。
096:Spark实践操作 🚀



在本节课中,我们将学习如何解决一个具体的数据处理问题,并在此过程中掌握与Spark Shell交互式开发、构建Scala应用程序以及在不同模式下运行Spark作业的核心技能。
概述

本节课程的目标是解决一个经典的数据处理问题:计算学生的平均成绩,并将结果与学生信息合并输出。我们将通过以下步骤达成目标:
- 学习关键的Spark转换与操作方法。
- 使用Spark Shell进行交互式问题求解。
- 将交互式代码整理成一个完整的Scala应用程序。
- 使用SBT工具打包应用,并在本地模式和YARN集群模式下运行。
问题描述
我们有两个存储在HDFS中的输入文件:
student.dat:包含学生信息,字段由管道符|分隔。格式为:学生ID | 名 | 姓 | 年级 | 年龄。score.dat:包含学生成绩,字段由逗号,分隔。格式为:学生ID, 考试日期, 分数。每个学生有多条成绩记录。
任务:为每位学生计算其所有考试的平均分,然后将平均分与学生信息合并,最终输出格式为:学生ID, 名, 姓, 年级, 年龄, 平均分。
解决方案设计
我们将编写一个Scala应用程序来解决此问题。程序的高层逻辑如下:

- 初始化环境:创建Spark配置和上下文,并设置日志级别。
- 处理学生数据:读取
student.dat文件,将每行数据按|分割,并转换为(学生ID, (名, 姓, 年级, 年龄))格式的元组RDD。 - 处理成绩数据:读取
score.dat文件,将每行数据按,分割,提取学生ID和分数,并转换为(学生ID, 分数)格式的元组RDD。 - 计算平均分:对成绩元组RDD按
学生ID进行分组,然后对每个学生的分数列表计算平均值,得到(学生ID, 平均分)格式的RDD。 - 数据连接:将学生信息RDD与平均分RDD通过
学生ID进行连接。 - 格式化输出:从连接后的复杂结构中提取所需字段,并格式化为最终结果。
- 保存结果:将最终RDD保存到HDFS。

以下是核心处理逻辑的代码框架:

// 读取学生数据并转换为元组
val studentRDD = sc.textFile("hdfs://.../student.dat")
val studentTupleRDD = studentRDD.map(line => {
val fields = line.split("\\|")
(fields(0), (fields(1), fields(2), fields(3), fields(4)))
})

// 读取成绩数据并转换为(学生ID, 分数)元组
val scoreRDD = sc.textFile("hdfs://.../score.dat")
val scoreTupleRDD = scoreRDD.map(line => {
val fields = line.split(",")
(fields(0), fields(2).toFloat)
})
// 按学生ID分组并计算平均分
val groupedScoreRDD = scoreTupleRDD.groupByKey()
val avgScoreRDD = groupedScoreRDD.mapValues(scores => {
scores.sum / scores.size
})
// 连接学生信息与平均分
val joinedRDD = studentTupleRDD.join(avgScoreRDD)
// 格式化最终输出
val finalResultRDD = joinedRDD.map(record => {
val studentID = record._1
val studentInfo = record._2._1
val avgScore = record._2._2
s"$studentID, ${studentInfo._1}, ${studentInfo._2}, ${studentInfo._3}, ${studentInfo._4}, $avgScore"
})
// 保存结果
finalResultRDD.saveAsTextFile("hdfs://.../output/grade")


交互式开发与Spark Shell

上一节我们介绍了解决方案的设计思路,本节中我们来看看如何利用Spark Shell进行交互式开发,逐步验证每一步的数据转换。



首先,启动Spark Shell并设置日志级别以减少输出干扰:
spark-shell --master local
sc.setLogLevel("ERROR")



以下是交互式探索的关键步骤列表:
- 加载并查看学生数据:
val studentRDD = sc.textFile("hdfs:///sgrade/student.dat") studentRDD.foreach(println) val studentTupleRDD = studentRDD.map(line => {val f=line.split("\\|"); (f(0), (f(1),f(2),f(3),f(4)))}) studentTupleRDD.foreach(println)

- 加载并处理成绩数据:
val scoreRDD = sc.textFile("hdfs:///sgrade/score.dat") val scoreTupleRDD = scoreRDD.map(line => {val f=line.split(","); (f(0), f(2).toFloat)}) scoreTupleRDD.take(5).foreach(println) // 使用take查看部分数据,避免全部打印



- 分组并计算平均分:
val groupedScoreRDD = scoreTupleRDD.groupByKey() groupedScoreRDD.foreach(println) // 定义平均函数 def avg(values: Iterable[Float]): Double = values.sum / values.size val avgScoreRDD = groupedScoreRDD.mapValues(scores => avg(scores)) avgScoreRDD.foreach(println)


- 连接数据并格式化输出:
val joinedRDD = studentTupleRDD.join(avgScoreRDD) joinedRDD.foreach(println) val finalResultRDD = joinedRDD.map{ case (id, (info, avg)) => s"$id, ${info._1}, ${info._2}, ${info._3}, ${info._4}, $avg" } finalResultRDD.foreach(println)

- 保存结果:
finalResultRDD.saveAsTextFile("hdfs:///sgrade/output")

通过这种交互式方式,我们可以即时看到每个转换步骤的输出,便于理解和调试逻辑。
构建与打包应用程序



交互式验证代码正确后,我们需要将其组织成一个正式的Scala应用程序。我们使用SBT(Simple Build Tool)进行项目管理、依赖解析和打包。




首先,创建项目目录结构,并编写build.sbt文件来定义项目信息和依赖:



name := "grade-average-calculator"
version := "1.0"
scalaVersion := "2.11.12"
libraryDependencies += "org.apache.spark" %% "spark-core" % "2.4.8"


接着,在src/main/scala目录下创建主程序文件GradeApp.scala,将交互式步骤中的核心逻辑整合到main方法中。



最后,在项目根目录下执行打包命令:

sbt package





成功执行后,会在target/scala-2.11/目录下生成一个JAR文件,例如grade-average-calculator_2.11-1.0.jar。这个JAR包包含了我们的应用程序及其所有依赖(除了Spark本身)。


运行Spark应用程序
打包完成后,我们可以在不同的部署模式下运行应用程序。




1. 本地模式 (Local Mode)

在本地模式下,所有组件(Driver, Executor)都运行在单个JVM进程中,适合测试和调试。

spark-submit \
--master local \
--driver-memory 500m \
target/scala-2.11/grade-average-calculator_2.11-1.0.jar

2. YARN客户端模式 (YARN Client Mode)

在YARN客户端模式下,Driver程序运行在提交作业的客户端机器上,而Executor运行在YARN集群的容器中。作业状态和输出在客户端终端可见。

spark-submit \
--master yarn \
--deploy-mode client \
--num-executors 2 \
--driver-memory 500m \
--executor-memory 500m \
target/scala-2.11/grade-average-calculator_2.11-1.0.jar


3. YARN集群模式 (YARN Cluster Mode)

在YARN集群模式下,Driver程序也运行在YARN集群的Application Master容器中。客户端在提交作业后即可断开连接。这是生产环境常用的模式。


spark-submit \
--master yarn \
--deploy-mode cluster \
--num-executors 2 \
--driver-memory 600m \
--executor-memory 600m \
target/scala-2.11/grade-average-calculator_2.11-1.0.jar
注意:在集群模式下运行时,如果遇到内存不足的错误,需要根据集群资源情况调整--driver-memory和--executor-memory参数。可以通过YARN ResourceManager的Web UI或查看容器日志来诊断失败原因。
总结


本节课中我们一起学习了Spark数据处理的全流程。我们从分析一个具体问题(计算学生平均成绩)入手,首先在Spark Shell中进行交互式探索和代码编写,直观地理解了RDD的转换与操作。然后,我们将验证过的逻辑整理成一个结构化的Scala应用程序,并使用SBT工具将其打包成JAR文件。最后,我们演示了如何在本地模式、YARN客户端模式和YARN集群模式下提交和运行这个Spark作业,并了解了不同模式的特点以及调试资源问题的方法。这套“交互开发 -> 打包 -> 多模式部署”的工作流,是使用Spark进行大数据应用开发的坚实基础。
097:Spark附加组件详解 🚀


在本节课中,我们将学习Spark生态系统中的几个重要附加组件。这些组件主要用于数据科学活动,包括Spark SQL、Spark Streaming、GraphX和Spark MLlib。我们将从高层次了解它们的功能和用途,为后续深入学习数据科学课程打下基础。

上一节我们介绍了Spark的核心概念,本节中我们来看看Spark为更高级的数据处理和分析任务提供的扩展功能。

Spark SQL 📊
Spark SQL是一个模块,它在RDD的函数式编程API之上引入了关系型处理功能。其核心思想是让程序员能够使用类似SQL的接口进行数据处理。
这与Hive在Hadoop之上提供的功能非常相似。Spark SQL使用名为DataFrame的API。DataFrame是一个用于Spark SQL处理的新API,它最初构建在RDD之上,但现在已转向主要使用DataFrame。

DataFrame与RDD的主要区别在于,RDD存在数据类型擦除问题,它不理解数据类型和字段名称,你只能通过类似x[1]或_1的索引来引用字段。而DataFrame可以使用命名的字段或列名,这正是你能在基于DataFrame构建的Spark SQL中使用类似SQL语法的原因。
Spark SQL的基础建立在DataFrame之上。DataFrame本质上是一个分布式数据集合,数据被组织成具有名称的列。因为它没有类型擦除,所以能记住数据类型。你可以加载包含字段名和列名的JSON对象或Avro文件,然后在其上使用类似SQL的接口。这几乎就像关系数据库中的一张表,只不过这一切都是在内存中使用DataFrame完成的。
这项技术的灵感来源于Python中的Pandas和R语言中的data.frame。

以下是Spark SQL的一个Java示例:
// 创建SQLContext
SQLContext sqlContext = new SQLContext(sc);
// 从JSON文件读取数据并创建DataFrame
DataFrame df = sqlContext.read().json("path/to/people.json");
// 打印模式(schema)
df.printSchema();
// 将DataFrame注册为临时表
df.registerTempTable("people");
// 执行SQL查询
DataFrame results = sqlContext.sql("SELECT name FROM people WHERE age > 20");

Spark Streaming 🌊
Spark Streaming用于处理实时流入的数据。在大数据应用中,数据通常被批量加载到HDFS或其他存储中,然后由批处理引擎处理。但有时实时处理数据也很有价值。
在Hadoop生态中,有Apache Storm用于流处理。在Spark中,则有Spark Streaming。它扩展了核心API以支持流处理,目标是能够在Spark平台上构建一个完全分布式、容错的实时处理引擎。

但需要记住的关键点是,Spark Streaming本质上仍是微批处理。其思想是:输入数据流持续进入,Spark Streaming将其按特定时间窗口(例如2秒)切分成小批次。每个批次的数据会被收集起来,进行计算,并将结果输出。因此,你是在持续地对多个微批次进行计算。
Spark Streaming的数据可以从多种来源摄取,例如Kafka、Flume、HDFS、S3、Kinesis或Twitter feeds。处理后的结果可以写回HDFS、数据库、仪表盘或其他消息队列。
处理逻辑可以使用我们之前学过的所有转换和行动操作来表达。批处理窗口可以小至500毫秒。你需要确保有足够的硬件资源在窗口时间内完成处理,否则会出现延迟。

以下是Spark Streaming的两个用例:
- 实时页面热度分析:用户点击网站链接产生的页面浏览量被送入Kafka队列,Spark Streaming读取这些数据,实时统计热门页面,帮助分析师了解当前网站上受欢迎的内容。
- 实时异常检测:例如,将智能电表数据与实时天气数据流结合。通过连接这两个实时数据集,可以分析出用电量读数高的原因是否与冬季风暴有关,从而实现实时的数据科学分析。
需要明确的是,Spark Streaming是微批处理,并非像Apache Storm那样的纯实时流处理。Storm可以实现更低的延迟(低于500毫秒)。
以下是一个Python的Spark Streaming示例,它从网络套接字读取数据流:

from pyspark import SparkContext
from pyspark.streaming import StreamingContext
# 创建Spark上下文
sc = SparkContext("yarn", "StreamingExample")
# 创建StreamingContext,批处理间隔为1秒
ssc = StreamingContext(sc, 1)

# 创建指向特定主机和端口的DStream
lines = ssc.socketTextStream("localhost", 9999)
# 对每行数据进行处理(例如单词计数)
counts = lines.flatMap(lambda line: line.split(" ")) \
.map(lambda word: (word, 1)) \
.reduceByKey(lambda a, b: a+b)
counts.pprint()

# 启动流计算
ssc.start()
ssc.awaitTermination()

GraphX 🕸️

GraphX是构建在Spark之上的图处理引擎,类似于Hadoop生态中的Apache Giraph。它通过引入一个新的图抽象API扩展了Spark RDD。

其核心思想是将数据表示为顶点和边。每个顶点代表一个数据实体(例如一个人),每条边代表实体之间的关系(例如合作、指导)。一旦数据以这种形式表示,就可以在其上执行各种图计算算法。
图处理有许多应用场景:
- 社交网络:分析用户之间的关系、共同访问的文档等。
- 网页图与PageRank算法:通过分析网页间的链接关系,计算网页的重要性排名。
- 用户-物品图与推荐系统:通过分析用户对物品的评分关系,构建推荐引擎。
图数据结构是许多机器学习算法的基础。GraphX提供了丰富的API,一旦构建好图数据结构,就可以调用多种算法,如PageRank、三角形计数、连通组件等。
Spark MLlib 🤖

MLlib是Spark生态系统中的机器学习库,类似于Hadoop中的Apache Mahout。它使得大规模机器学习应用变得容易。

MLlib内置了许多经典算法。它最初基于RDD,在Spark 2.0版本中,正在向基于DataFrame的API迁移。

MLlib提供的算法和功能包括:
- 分类算法(如逻辑回归)
- 回归分析
- 决策树
- 推荐算法(协同过滤)
- 聚类算法(如K-means)
- 特征转换与ML管道(构建处理流水线)
- 基础统计功能



总结
本节课中我们一起学习了Spark的四个重要附加组件:Spark SQL、Spark Streaming、GraphX和Spark MLlib。
- Spark SQL 提供了类似SQL的关系型查询接口,基于DataFrame API,便于进行结构化数据分析。
- Spark Streaming 支持对实时数据流进行微批处理,适用于需要近实时响应的场景。
- GraphX 专门用于图结构数据的处理和分析,适用于社交网络、推荐系统等领域。
- Spark MLlib 提供了丰富的可扩展机器学习算法库。


通常,数据工程师更关注数据处理技术本身(如本课程重点),而数据科学家则更倾向于使用Spark SQL、MLlib、GraphX等组件进行数据探索和实验。Spark出色的交互式处理能力使其成为数据科学家进行数据实验的理想平台,在得出洞察后,再由数据工程师将其工程化为生产应用。希望本节能帮助你理解这些工具在Spark生态系统中的角色。
098:Spark 模块总结 🎯
在本节课中,我们将对 Spark 框架的核心内容进行总结。我们将回顾 Spark 的概述、架构、运行模式、环境搭建以及核心编程概念,帮助你巩固对 Spark 的理解。




Spark 概述回顾

上一节我们介绍了 Spark 的基本概念,本节中我们来回顾其核心特性。
Spark 是一个快速集群计算框架,它内置了数据处理引擎。集群计算体现在它拥有自己的调度器,并能与其他调度器协同工作。数据处理则由 Spark 引擎本身完成。

我们了解到,Spark 支持迭代处理和交互式处理。因为它将所有数据保存在内存中,你可以持续地对这些存储在弹性分布式数据集中的数据应用函数。
以下是 Spark 提供的高级功能:
- 高级分析函数:除了
map函数,还提供了其他可在数据上执行的功能。 - 内置 SQL 查询:功能与 Hive 类似。
- 多语言 DSL 接口:支持 Scala、Java、Python 和 R。
- 实时处理:通过 Spark Streaming 实现。
- 图计算与机器学习:提供了相应的库。
市场上有多种 Spark 发行版。经典的大数据 Hadoop 发行版通常将 Spark 作为其引擎之一。此外,还有专注于 Spark 的 Databricks 公司,它与其他公司合作,为它们的大数据平台提供 Spark 引擎。






Spark 架构核心
在 Spark 架构部分,我们学习了几个关键组件。

RDD 是主要组件,即弹性分布式数据集。其核心思想是从某种数据源加载数据,并将数据以分布式集合的形式保存在多台机器上。
Driver 控制程序的流程。
Executor 是任务和核心以线程形式运行的地方。通常,一个应用有多个 Executor,每个 Executor 有多个线程或任务,负责处理 RDD 的一部分。
Spark 应用运行模式
在 Spark 应用模式方面,Spark 可以在多种模式下运行。
Spark 可以运行在交互模式下,允许你通过命令行界面与 Spark 交互。你可以使用基于 Scala 的 Spark Shell、基于 Python 的 PySpark 或基于 R 的 SparkR。
也支持批处理编程,你可以使用 spark-submit 来提交作业。
此外,Spark 可以在不同种类的Master模式下运行。主要分为两类:
- 本地模式:Driver 和 Executor 都在同一台机器上运行。
- 分布式 Master 模式:分为 Standalone、Mesos 和 YARN。
- Standalone 是 Spark 自带的集群计算调度引擎。
- YARN 和 Mesos 是 Spark 可以集成的外部调度引擎。
通常,我们以交互模式或批处理模式运行 Spark,并可以选择在本地或分布式环境下运行。
还存在一种称为部署模式的概念,可以是 client 或 cluster,这决定了 Driver 的运行位置。如果是 client 模式,Driver 在提交客户端运行;如果是 cluster 模式,Driver 则在集群环境中运行。
Spark 环境搭建与运行
在设置和运行 Spark 部分,我们学习了如何在环境中下载和安装 Spark。

我们下载了一个能与现有 Hadoop 部署协同工作的 Spark 版本。因为我们已安装了自己的 Hadoop,所以我们下载了不包含 Hadoop 的 Spark 版本,并将其与现有 Hadoop 集成。

在此过程中,我们配置了 Spark 使其知道 Hadoop 的位置,同时也配置了 Hadoop 使其知道 Spark 的位置。

我们还下载并安装了 Scala 和 SBT,以便构建基于 Scala 的应用程序。在实践演示部分,我们展示了几个如何操作的例子。


Spark 核心编程
在 Spark 核心编程部分,我们学习了几个关键对象和操作流程。
Spark配置 对象代表 Spark 的配置。Spark上下文 代表 Spark 环境。你使用 Spark 配置来创建 Spark 上下文,一旦有了 Spark 上下文,你就可以创建 RDD。
当运行 Spark Shell 时,Spark 配置和 Spark 上下文已经创建好,你只需使用它们。如果是编写应用程序,则需要创建这些配置和上下文对象,然后在其上创建 RDD。
通常,你从存储在 HDFS、常规文件系统或分布式数据库中的数据创建 RDD,然后对其应用 RDD 操作。你可以应用转换操作或行动操作。
你可以应用多个转换操作,但这些操作在行动发生之前并不会真正执行。转换操作(如 filter 和 map 函数)声明数据需要被转换,但由于惰性求值的特性,数据并不会立即被转换,直到一个行动操作发生。行动一旦发生,转换操作会根据一个称为有向无环图的执行计划被执行。
在应用开发流程上,通常是:加载数据 -> 应用转换 -> 应用行动 -> 保存结果。如果需要,你可以在同一数据集上应用多个行动并保存多个结果,这完全取决于你的应用程序目标。
你可以使用 Java、Python 或 Scala 编写应用程序。我们演示了如何使用 Scala 编写,并使用 SBT 和 Scala 编译器来构建你的应用程序。
其他 Spark 组件
我们主要关注了 Spark 架构以及如何使其与 Hadoop 协同运行,但 Spark 之上还构建了许多其他组件,被数据科学家广泛使用。
例如 Spark SQL、Spark Streaming、GraphX 和 Spark MLlib。这些都是构建在 Spark 之上的附加组件,通常随 Spark 发行版一同提供。我们对其中一些组件进行了简要概述。当你进入数据科学的一些高级课程时,可能会使用这些技术来创建更复杂的数据科学应用。
总结
本节课中,我们一起回顾了 Spark 框架的核心内容。我们总结了 Spark 作为一个快速、内存计算框架的概述,其基于 RDD 的核心架构,多样的运行模式(交互式与批处理、本地与分布式),环境搭建与集成的步骤,以及核心的编程模型(转换与行动)。最后,我们还简要了解了构建在 Spark 之上的高级组件。掌握这些基础知识,是进一步深入学习和使用 Spark 进行大数据处理的关键。

浙公网安备 33010602011771号