Hadoop

Hadoop大数据生态系统
实验教程

第1章
欢迎来到大数据的世界

1.1 拥抱大数据
当今的社会,是一个信息大爆炸的社会,社会在高速发展,科技发达,信息流通,人们之间的交流越来越密切,生活也越来越方便,大量的数据在这个高科技时代快速产生。大数据到底有多大?一组名为“互联网上一天”的数据告诉我们,一天之中,互联网产生的全部内容可以刻满1.68亿张DVD;发出的邮件有2940亿封之多(相当于美国两年的纸质信件数量);发出的社区帖子达200万个(相当于《时代》杂志770年的文字量);卖出的手机为37.8万台,高于全球每天出生的婴儿数量37.1万……。
随着移动互联网、移动终端和数据传感器的出现,数据正以超出想象的速度快速增长。近几年,数据量已经从TB级别跃升到PB乃至ZB级别。
伙伴产业研究院(PAISI)研究统计,2017 年全年数据总量将超过 15.2ZB,同比增长 35.7%。到 2018 年全球数据总量达19.4ZB。未来几年全球数据的增长速度在每年 25% 以上,以此推算,到 2020 年,全球的数据总量将达到 30ZB,2020 年接近50ZB的数据量。

IBM的研究称,整个人类文明所获得的全部数据中,有90%是过去两年内产生的。而到了2020年,全世界所产生的数据规模将达到今天的44倍。每一天,全世界会上传超过5亿张图片,每分钟就有20小时时长的视频被分享。然而,即使是人们每天创造的全部信息——包括语音通话、电子邮件和信息在内的各种通信,以及上传的全部图片、视频与音乐,其信息量也无法匹及每一天所创造出的关于人们自身的数字信息量。
1.1.1 为什么要学习大数据?
我们已经有了海量的数据,并且这些数据在快速的增长。但这并没有什么用,就好像一堆破烂中的一粒金子。每个企业要想在这个时代取得竞争优势,就必须从这些数据中获取有价值的信息,因此我们还必须想办法去存储并分析这些数据。
从以下成功案例中不难看出大数据的神奇魅力。
 2014年谷歌的云计算平台成功预测了世界杯16强比赛每场比赛的胜利者。谷歌使用了来自 Opta Sports(-家体育数据提供商)的数据,评估了每个职业足球联盟过去多个赛季的情况,以及世界杯小组赛期间的统计数据,根据对球员比赛前、比赛中表现的分析,谷歌预测了这些球员在随后比赛中将会有什么样的表现。
 沃尔玛(零售连锁超市)—啤酒与尿不湿。沃尔玛超市管理人员分析销售数据时发现了一个难于理解的现象:啤酒和尿不湿经常出现在同一个购物篮中。于是将啤酒和尿不湿两个看上去没有关系的商品摆放在一起进行销售,并获得了很好的销售收益>2014年推出的百度高考预测押中了全国18套作文考题中的12套。
 对于体育爱好者,追踪电视播放的最新运动赛事几乎是一件不可能的事情,因为有超过上百个赛事在8000多个电视频道播出。而现在市面上开发了一个可追踪所有运动赛事的应用程序RUWT,它已经可以在iOS和Android设备,以及在Web浏览器上使用,它不断地分析运动数据流来让球迷知道他们应该转换成哪个台看到想看的节目,在电视的哪个频道上找到,并让他们在比赛中进行投票。对于谷歌电视和TiVo用户来说,实际上 RUWT就是让他们改变频道调到一个比赛中。该程序能基于赛事的紧张激烈程度对比赛进行评分排名,用户可通过该应用程序找到值得收看的频道和赛事。

大数据已经在很多领域中得到广泛的应用如推荐引擎、情感分析、风险建模、欺诈检测、营销活动分析、客户流失分析、社交图谱分析、用户体验分析、网络监控.产品设计等。作为技术人员,大数据为我们带来了广阔的职业发展空间,能够将我们现有的IT项目进行扩展、升级。

目前大数据比较热门的就业方向有以下几种。
 大数据运维工程师:负责公司大数据平台的部署、管理、优化、监控报警,保障平台服务7*24稳定可靠高效运行。
 大数据系统研发工程师:负责开发大数据分析处理系统。
 大数据应用开发工程师:负责在大数据分析处理系统上开发大数据处理的应用。
 大数据可视化工程师:负责将数据以图像等可视化的形式展现给用户。
 大数据分析师:负责发现数据的价值,设计数据分析的算法。
Hadoop,Spark
1.1.2 什么是大数据
随着大数据时代的到来,“大数据”已经成为互联网信息技术行业的流行词汇。大数据的定义为,指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。
大数据有6个特征,我们称之为“6V”:数据量大(Volume)、数据类型繁多(Variety)、处理速度快(Velocity)、价值密度低(Value)、真实性(Veracity) 和临近性(Vicinity)。
1数据量大
人类进入信息社会以后,数据以自然方式增长,其产生不以人的意志为转移。从1986年开始到2010年的20多年时间里,全球数据的数量增长了100倍,今后的数据量增长速度将更快,我们正生活在一个“数据爆炸”的时代。今天,世界上只有25%的设备是联网的,大约80%的上网设备是计算机和手机,而在不远的将来,将有更多的用户成为网民,汽车、电视、家用电器生产机器等各种设备也将接入互联网。随着Wb2.0和移动互联网的快速发展,人们已经可以随时随地、随心所欲发布包括博客、微博、微信等在内的各种信息。以后,随着物联网的推广和普及,各种传感器和摄像头将遍布我们工作和生活的各个角落,这些设备每时每刻都在自动产生大量数据。
综上所述,人类社会正经历第二次“数据爆炸”(如果把印刷在纸上的文字和图形也看作数据的话,那么人类历史上第一次“数据爆炸”发生在造纸术和印刷术发明的时期)。各种数据产生速度之快,产生数量之大,已经远远超出人类可以控制的范围,“数据爆炸”成为大数据时代的鲜明特征。根据著名咨询机构IDC( Internet Data Center)做出的估测,人类社会产生的数据一直都在以每年50%的速度增长,也就是说,每两年就增加一倍,这被称为“大数据摩尔定律”。这意味着,人类在最近两年产生的数据量相当于之前产生的全部数据量之和预计到2020年,全球将总共拥有35ZB(见表1-1)的数据量,与2010年相比,数据量将增长到近30倍。
单位 换算单位
Byte (字节) 1Byte=8bit
KB(千字节) 1KB = 1024B
MB(兆字节) 1MB = 1024KB
GB(吉字节) 1GB = 1024MB
TB(太字节) 1TB = 1024GB
PB(拍字节) 1PB = 1024TB
EB(艾字节) 1EB = 1024PB
ZB(泽字节) 1ZB = 1024EB
2 数据类型繁多
大数据的数据来源众多,科学研究、企业应用和Web应用等都在源源不断地生成新的数据。生物大数据、交通大数据、医疗大数据、电信大数据、电力大数据、金融大数据等都呈现出“井喷式”增长,所涉及的数量十分巨大,已经从TB级别跃升到PB级别。大数据的数据类型丰富,包括结构化数据和非结构化数据,其中,前者占10%左右,主要是指存储在关系数据库中的数据;后者占90%左右,种类繁多,主要包括邮件、音频、视频、微信、微博、位置信息、链接信息、手机呼叫信息、网络日志等。
如此类型繁多的异构数据,对数据处理和分析技术提出了新的挑战,也带来了新的机遇。传统数据主要存储在关系数据库中,但是,在类似web2.0等应用领域中,越来越多的数据开始被存储在非关系型数据库( Not Only SQL, NoSQL)中,这就必然要求在集成的过程中进行数据转换,而这种转换的过程是非常复杂和难以管理的。传统的联机分析处理(On-Line Analytical Processing,OLAP)和商务智能工具大都面向结构化数据,而在大数据时代,用户友好的、支持非结构化数据分析的商业软件也将迎来广阔的市场空间。
3 处理速度快
大数据时代的数据产生速度非常迅速。在Web2.0应用领域,在1min内,新浪可以产生2万条微博, Twitter可以产生10万条推文,苹果可以下载47万次应用,淘宝可以卖出6万件商品,人人网可以发生30万次访问,百度可以产生90万次搜索查询, Facebook可以产生600万次浏览量。大名鼎鼎的大型强子对撞机(LHC,大约每秒产生6亿次的碰撞,每秒生成约700MB的数据,有成千上万台计算机分析这些碰撞
大数据时代的很多应用都需要基于快速生成的数据给出实时分析结果,用于指导生产和生活实践。因此,数据处理和分析的速度通常要达到秒级响应,这一点和传统的数据挖掘技术有着本质的不同,后者通常不要求给出实时分析结果。
为了实现快速分析海量数据的目的,新兴的大数据分析技术通常采用集群处理和独特的内部设计。以谷歌公司的 Dreme为例,它是一种可扩展的、交互式的实时查询系统,用于只读嵌套数据的分析,通过结合多级树状执行过程和列式数据结构,它能做到几秒内完成对万亿张表的聚合查询,系统可以扩展到成千上万的CPU上,满足谷歌上万用户操作PB级数据的需求,并且可以在2~3s内完成PB级别数据的查询。
4 价值密度低
大数据虽然看起来很美,但是价值密度却远远低于传统关系数据库中已经有的那些数据。在大数据时代,很多有价值的信息都是分散在海量数据中的。以小区监控视频为例,如果没有意外事件发生,连续不断产生的数据都是没有任何价值的,当发生偷盗等意外情况时,也只有记录了事件过程的那一小段视频是有价值的。但是,为了能够获得发生偷盗等意外情况时的那一段宝贵的视频,我们不得不投入大量资金购买监控设备、网络设备、存储设备,耗费大量的电能和存储空间,来保存摄像头连续不断传来的监控数据。
如果这个实例还不够典型的话,那么我们可以想象另一个更大的场景。假设一个电子商务网站希望通过微博数据进行有针对性的营销,为了实现这个目的,就必须构建一个能存储和分析新浪微博数据的大数据平台,使之能够根据用户微博内容进行有针对性的商品需求趋势预测。愿景很美好,但是现实代价很大,可能需要耗费几百万元构建整个大数据团队和平台,而最终带来的企业销售利润增加额可能会比投入低许多,从这点来说,大数据的价值密度是较低的。
5 真实性
数据来自不同的源头,而有些来源的数据(比如京东上的评论和博客上的跟帖)其本身的可信度是需要考虑的。
6 临近性
邻近性和大数据的存储相关,处理数据的程序和服务器需要就近获取资源,不然就会造成大量的浪费和效率的降低。
1.2 大数据对社会发展的影响
大数据将对社会发展产生深远的影响,具体表现在以下几个方面:大数据决策成为一种新的决策方式,大数据应用促进信息技术与各行业的深度融合,大数据开发推动新技术和新应用的不断涌现。
1.大数据决策成为一种新的决策方式
根据数据制定决策,并非大数据时代所特有。从20世纪90年代开始,数据仓库和商务智能工具就开始大量用于企业决策。但是,数据仓库以关系数据库为基础无论是数据类型还是数据量方面都存在较大的限制。现在,大数据决策可以面向类型繁多的、非结构化的海量数据进行决策分析,已经成为受到追捧的全新决策方式。比如,政府部门可以把大数据技术融入“奥情分析”,通过对论坛、微博、微信、社区等多种来源数据进行综合分析,弄清或测验信息中本质性的事实和趋势,揭示信息中含有的隐性情报内容,对事物发展做出情报预测协助实现政府决策,有效应对各种突发事件。
2.大数据应用促进信息技术与各行业的深度融合
有专家指出,大数据将会在未来10年改变几乎每一个行业的业务功能。互联网、银行、保险、交通、材料、能源、服务等行业领域,不断累积的大数据将加速推进这些行业与信息技术的深度融合,开拓行业发展的新方向。比如,大数据可以帮助快递公司选择运费成本最低的最佳行车路径.协助投资者洗择收益最大化的股票投资组合,辅助零售商有效定位目标客户群体,帮助互联网公司实现广告精准投放,还可以让电力公司做好配送电计划确保电网安全等。
3.大数据开发推动新技术和新应用的不断涌现
大数据的应用需求是大数据新技术开发的源泉。在各种应用需求的强烈驱动下,各种突破性的大数据技术将被不断提出并得到广泛应用,数据的能量也将不断得到释放。在不远的将来,原来那些依靠人类自身判断力的领域应用,将逐渐被各种基于大数据的应用所取代。比如,今天的汽车保险公司,只能凭借少量的车主信息,对客户进行简单类别划分,并根据客户的汽车出险次数给予相应的保费优惠方案,客户选择哪家保险公司都没有太大差别。随着车联网的出现,“汽车大数据”将会深刻改变汽车保险业的商业模式,如果某家商业保险公司能够获取客户车辆的相关细节信息,并利用事先构建的数学模型对客户等级进行更加细致的判定,给予更加个性化的“一对—”优惠方案,那么毫无疑问,这家保险公司将具备明显的市场竞争优势,获得更多客户的青睐。
1.3 大数据、云计算、物联网之间的关系
物联网,云计算,大数据是近两年科技、产业界的热门话题。分别什么意思?之间又有什么关系呢?下面写了一点浅显认识和总结。

物联网
简单理解:物物相连的互联网,即物联网。物联网在国际上又称为传感网,这是继计算机、互联网与移动通信网之后的又一次信息产业浪潮。世界上的万事万物,小到手表、钥匙,大到汽车、楼房,只要嵌入一个微型感应芯片,把它变得智能化,这个物体就可以“自动开口说话”。再借助无线网络技术,人们就可以和物体“对话”,物体和物体之间也能“交流”,这就是物联网。随着信息技术的发展,物联网行业应用版图不断增长。
现在的物联网产业以应用层、支撑层、感知层、平台层以及传输层这五个层次构成。

云计算
云计算是一种按使用量付费的模式,这种模式提供可用的、便捷的、按需的网络访问,进入可配置的计算资源共享池(资源包括网络、服务器、存储、应用软件、服务),这些资源能够快速提供,只需投入很少的管理工作,或与服务商进行很少的交互。
云,是指作为接受服务的对象,不论何时何地,都能享受云计算提供的服务。云计算提供了最可靠、最安全的数据存储中心,用户不用再担心数据丢失、病毒入侵等麻烦。云端平台数据可以共享,可以在任意地点对其进行操作,同时对多个对象组成的网络进行控制和协调,云端各种数据能同时被多个用户使用。简单来说,云计算就是提供基于互联网的软硬件服务。

经典应用案例:苹果icloud
苹果icloud不仅是一个云端硬盘,它可让你轻松访问你所有苹果设备上的一切内容,并自动同步所有设备中的文件、图片、音乐、日程表、邮件、联系人目录,更贴心的是,在你修改文件后还能自动将修改同步到所有苹果设备并对旧文件备份。你可以选择免费的5G存储空间,也可以每年花费24.99美元购买iTunes Match服务,这样一来,你可以通过任何苹果设备收听存放在苹果云服务器中的音乐。
物联网和云计算的关系
云计算相当于人的大脑,是物联网的神经中枢。云计算是基于互联网的相关服务的增加、使用和交付模式,通常涉及通过互联网来提供动态易扩展且经常是虚拟化的资源。
大数据与云计算的关系
大数据与云计算的关系就像一枚硬币的正反面一样密不可分。大数据必然无法用单台的计算机进行处理,必须采用分布式架构。它的特色在于对海量数据进行分布式数据挖掘。但它必须依托云计算的分布式处理、分布式数据库和云存储、虚拟化技术。
大数据、云计算和物联网的关系
物联网对应了互联网的感觉和运动神经系统。云计算是互联网的核心硬件层和核心软件层的集合,也是互联网中枢神经系统萌芽。大数据代表了互联网的信息层(数据海洋),是互联网智慧和意识产生的基础。包括物联网,传统互联网,移动互联网在源源不断的向互联网大数据层汇聚数据和接受数据。云计算与物联网则推动了大数据的发展。
1.4 大数据处理流程
大数据并非单一的数据或技术,而是数据和大数据技术的综合体。大数据技术主要包括数据采集、数据存储、数据清洗或预处理、数据处理和分析四个环节。

  1. 收集:原始数据种类多样,格式、位置、存储、时效性等迥异。数据收集从异构数据源中取出或利用日志采集工具把实时采集的数据收集并转换成相应的格式方便处理。
  2. 存储:收集好的数据需要根据成本、格式、查询、业务逻辑等需求,存放在合适的存储中,方便进一步的分析。
  3. 数据清洗/预处理:原始数据需要变形与增强之后才适合分析,比如网页日志中把IP地址替换成省市、传感器数据的纠错、用户行为统计等。
  4. 分析:通过整理好的海量数据处理和分析,对分析结果进行可视化呈现,帮助人们更好的理解数据,分析数据和进行企业决策。分析分为离线数据分析和实时查询分析两种,离线处理就是每天定时处理,最后输出结果或进行展示。实时查询分析是将产生的数据实时进行处理。

1.5 大数据的学习方法
归功于大数据处理系统的发展,以 Hadoop为代表的解决方案和工具逐渐成熟,进行大数据应用开发的门槛正在逐渐降低。本书就是学习如何使用Hadoop完成大数据解决方案。学习Hadoop,仅仅需要了解一些 Linux的操作,以及Java编程基础知识就足够进行大数据课程的学习。对于Linux要了解常用的 shell命令和操作,如安装软件、解压包、配置环境变量等对于Java基础要了解面向对象的概念、集合类, Eclipse的使用等。
针对初学者来说,Hadoop运行环境的安装是个门槛,很大程度上是由于大家对于Linux系统相对不熟悉而实际上Hadoop的安装并不复杂。在配置运行环境时可先简单了解配置步骤与方法按教材所示步骤逐步完成,待后面讲解每个模块时再回顾各配置属性的具体含义。
首先要明确一点,Hadoop解决了大数据的存储和数据处理作业的并行运算这两个问题。幸运的是,这两个核心模块对开发人员都是透明的,也就是说我们不用去关心它是如何进行底层的分布式存储与计算,仅需要我们能够使用 Hadoop提供的HDFS文件管理命令和 MapReduce并行计算的API即可让 Hadoop为我们处理大数据
1.6 大数据的就业方向
大数据作为一种趋势,已经吸引了越来越多的重视,目前,上至国家部委、下至普通公司,已纷纷开展大数据平台的建设和应用,大数据人员的需求在极速扩大,具体的岗位有以下几个。
一是大数据平台架构与研发:从事底层的大数据平合研发,这类从业者难度最高,适合于前沿技术机构,要不断发现与改进目前大数据技术的缺陷,对于形成稳定版本的大数据平台,要面向业界进行推广。这类岗位整体数量不多,但方向会比较专注。这个岗位不是我们核心岗位。
二是大数据平台应用开发:从事大数据平台应用技术的开发工作,满足大量企事业单位使用大数据平台的需求,这类岗位会比较充足,需要不断学习各类大数据平台,并应用到开发项目中。
三是大数据平台集成与运维:从事大数据平台的集成与运维工作。对于大量的企事业单位的大数据部署与常规应用来说,需要有专职的集成人员进行集成安装与调试,需要定期运维人员进行运维与提供技术保障,这类岗位也会比较充足,但需要熟练掌握大数据平台的使用,针对问题能够及时解决。
四是大数据平台数据分析与应用:从事数据分析、预测与应用工作,借助于大数据平台,分析各类业务数据,并服务于业务,这类岗位跟业务休戚相关,一些对数据高度重视的机构,如精确广告营销、大数据安全分析等单位,对此会有充足的人才需求。
五是大数据技术培训与推广:从事大数据技术教育与培训工作。这类工作机构会随着大数据人才需求热度的提高而不断增加岗位人数,与此同时,随着推广程度的延伸,也促生更多的机构,更多的人才加入大数据领域。
六是大数据可视化开发:从事大数据可视化开发,借助大数据平台,可视化工具将各类数据转化成图表的形式展示。需要熟练地使用sql,这类岗位会比较充足。
七是ETL开发:从事数据处理,构建数据仓库重要一环。主要技术发展方向侧重与数据库、或大批量数据处理方向,今后可以向数据库开发工程师、数据库架构师、数据分析师等方面发展。
二三四五岗位是我们的核心岗位。

本章小结:

第2章
Hadoop简介

2.1结构化、半结构化、非结构化数据的理解
记得在课上,老师说,结构化数据就是我们关系数据库里的表,剩下的都是半结构化和非结构化数据,好比XML文档就是半结构化数据,WORD文档就是非结构化数据,大数据就是半结构化和非结构化数据。心中一直有一个疑问?难道大数据不应该包含结构化数据吗?实在学习数据库这门课时,就对这几个概念有所混淆,所幸今天在书中发现了比较清晰的解释,记录下来,方便以后参考。
1.结构化数据
定义:业界是指关系模型数据,即以关系数据库表形式管理的数据
简析:虽然专业角度上看,结构化就是关系模型的说法并不准确,但针对目前业内现状,还是定义为关系模型最为妥善,因为它准确的代表了我们传统上最熟悉的企业业务数据。
2.半结构化数据
定义:非关系模型的、有基本固定结构模式的数据,例如日志文件、XML文档、JSON文档、Email等。
3.非结构化数据
定义:没有固定模式的数据,如WORD、PDF、PPT、EXCEL,各种格式的图片、视频等。
简析:区分半结构化与非结构化的意义在于,对两者的处理方法是不同的,非结构化数据大多采用内容管理方法,而半结构化数据基本没有有效的管理方法。
总结
 结构化、半结构化、非结构化其实是按照数据格式分类。
 严格讲,结构化与半结构化数据都是有基本固定结构模式的数据
 半结构与非结构化数据与目前流行的大数据之间只是有领域重叠的关系,本质讲两者并无必然联系。
 业界有将大数据认同为半结构/非结构化数据,是因为大数据技术最初是在半结构化数据领域发挥作用,其本质是将数据处理技术与数据格式混淆,是不正确的。
大数据包括结构化、半结构化和非结构化数据,非结构化数据越来越成为数据的主要部分。据IDC的调查报告显示:企业中80%的数据都是非结构化数据。
2.2 Hadoop概述
我们以前学习了数据库知识,知道数据库主要保存和处理结构化数据。而Hadoop除了可以处理结构化数据,还可以处理非结构化和半结构化数据。事实上,Hadoop已经成为存储、处理和分析大数据的标准平台。当人们说要搭建大数据平台时,很多时候默认就是搭建Hadoop平台。
Hadoop可以存储以下类型的内容。
 结构化数据
 半结构化的数据,比如日志文档
 完全没有结构的内容,比如文本文件
 二进制数据,比如音频、视频等
Hadoop系统有以下特点。
 可靠性高
 可扩展性好
 性价比高
 灵活
本节简要介绍Hadoop的起源、发展历史、特征、应用现状和版本演变。
2.2.1 Hadoop简介
Hadoop是Apache软件基金会旗下的一个开源分布式计算平台,为用户提供了系统底层细节透明的分布式基础架构。所谓分布式架构可以看做将海量的数据保存在成千上万台普通的电脑上,并且数据进行了冗余备份保存。就像找多头牛一起拉货比培养一头更强壮的牛容易。同理,对于单机无法解决的问题,综合利用多个普通的机器比打造一台超级计算机更加可行,这就是Hadoop的设计思想。Hadoop是基于Java语言开发的,具有很好的跨平台特性,并且可以署在廉价的计算机集群中。
Hadoop的核心有两个,分别是分布式文件系统(Hadoop Distributed File Systen,HDFS)和 MapReduce。HDFS是针对谷歌文件系统( Google File System,GFS)的开源实现,是面向普通硬件环境的分布式文件系统,具有较高的读写速度、很好的容错性和可伸缩性,支持大规模数据的分布式存储,其冗余数据存储的方式很好地保证了数据的安全性。MapReduce是针对谷歌 MapReduce的开源实现,允许用户在不了解分布式系统底层细节的情况下开发并行应用程序,采用MapReduce来整合分布式文件系统上的数据,可保证分析和处理数据的高效性。借助于Hadoop程序员可以轻松地编写分布式并行程序,将其运行于廉价计算机集群上,完成海量数据的存储与计算。
Hadoop的目标是从单一的服务器扩展到成千上万的机器,将ZB级的数据保存在集群中的电脑中,每台电脑提供本地计算和存储,并且将存储的数据备份在多个节点,由此提升集群的可用性,而不是通过硬件提升。当一台机器宕机时,其他节点依然可以提供备份数据和计算服务。
Hadoop被公认为行业大数据标准开源软件,在分布式环境下提供了海量数据的处理能力。几乎所有主流厂商都围绕 Hadoop提供开发工具、开源软件、商业化工具和技术服务,如谷歌、雅虎、微软、思科、淘宝等都支持 Hadoop。
2.2.2 Hadoop的发展简史
Hadoop这个名字郎朗上口,至于为什么要取这样一个名字,其实并没有深奥的道理,只是追求名称简单、容易发音和记忆而已。很显然,小孩子是这方面的高手,大名鼎鼎的“Google”就是由小孩子给取名的。Hadoop同样如此,它是一个小孩子取的名字,意思是“一头吃饱了的棕黄色大象”,如图2.1所示。Hadoop后来的很多子项目和模块的命名方式都沿用了这种风格。如猪(Pig)、蜂巢(Hive)、动物园管理员(zookeeper)。

Hadoop最初是由Apache Lucene项目的创始人Doug Cutting开发的文本搜索库。Hadoop源自2002年的Apache Nutch项目----一个开源的网络搜索引擎并且也是 Lucene项目的一部分。在2002年的时候,Nutch项目遇到了棘手的难题,该搜索引擎框架无法扩展到拥有数十亿网页的网络。而就在一年以后的2003年,谷歌公司发布了分布式文件系统GFS方面的论文,可以解决大规模数据存储的问题。于是在2004年,Nutch项目也模仿GFS开发了自己的分布式文件系统(Nutch Distributed FileSystem,NDFS),也就是HDFS的前身。
2004年,谷歌公司又发表了另一篇具有深远影响的论文,阐述了 MapReduce分布式编程思想。2005年,Nutch开源实现了谷歌的 MapReduce。到了2006年2月,Nutch中的NDFS和 MapReduce开始独立出来,成为 Lucene项目的一个子项目,称为 Hadoop,同时 Doug Cutting加盟雅虎。2008年1月, Hadoop正式成为 Apache顶级项目,Hadoop也逐渐开始被雅虎之外的其他公司使用。2008年4月,Hadoop打破世界纪录,成为最快排序1TB数据的系统,它采用一个由910个节点构成的集群进行运算,排序时间只用了209s。在2009年5月, Hadoop更是把1TB数据排序时间缩短到62s。 Hadoop从此声名大噪,迅速发展成为大数据时代最具影响力的开源分布式开发平台并成为事实上的大数据处理标准。

2.2.3 Hadoop的特征
Hadoop是一个能够对大量数据进行分布式处理的软件框架,并且是以一种可靠、高效、可伸缩的方式进行处理的,它具有以下几个方面的特性。
 高可靠性。采用冗余数据存储方式,即使一个副本发生故障,其他副本也可以保证正常对外提供服务。
 高效性。作为并行分布式计算平台,Hadoop采用分布式存储和分布式处理两大核心技术,能够高效地处理PB级数据。
 高可扩展性。 Hadoop的设计目标是可以高效稳定地运行在廉价的计算机集群上,可以扩展到数以千计的计算机节点上。
 高容错性。采用冗余数据存储方式,自动保存数据的多个副本,并且能够自动将失败的任务进行重新分配。
 成本低。 Hadoop采用廉价的计算机集群,成本比较低,普通用户也很容易用自己的PC搭建 Hadoop运行环境。
 运行在 Linux平台上。Hadoop是基于Java语言开发的,可以较好地运行在 Linux平台上。
 支持多种编程语言。 Hadoop上的应用程序也可以使用其他语言编写,如C++。
2.2.4 Hadoop的版本
Hadoop的版本比较混乱,总的来说,Hadoop分为两代,如表2.1所示。
Apache Hadoop 大版本 说明
Hadoop 3.0 3.x.x
第二代Hadoop 2.0 2.x.x 下一代Hadoop由0.23.x演化而来
0.23.x 下一代Hadoop
第一代Hadoop 1.0 1.0.x 稳定版,由0.20.x演化而来
0.22.x 非稳定版
0.21.x 非稳定版
0.20.x 经典版本,最后演化成1.0.x
Apache Hadoop版本分为两代,第一代Hadoop称为Hadoop 1.0,包含0.20.x,0.21.x,0.22.x,最后0.22.x演化为稳定版1.0.x。Hadoop2.0指的是0.23.x,2.x.x。在本课程中,我们将选择Hadoop2.6.5。
第二代Hadoop一个重大的变化是重构了MapReduce。Hadoop的下载地址为http://hadoop.apache.org/release.html。
2.3 Hadoop体系结构
Hadoop源自Google在2003到2004年公布的关于GFS(Google File System),MapReduce和BigTable三篇论文,创始人是Doug Cutting,Hadoop现在是Apache基金会顶级项目。
2.3.1 Hadoop的核心
前面提过HDFS和 MapReduce是Hadoop的两大核心。通过HDFS来实现对分布存储的底层支持达到高速并行读写与大容量的存储扩展。通过 MapReduce实现对分布式并行任务处理程序支持,保证高速分析处理数据。HDFS在 MapReduce任务处理过程中提供了对文件操作和存储的支持MapReduce在HDFS的基础上实现了任务的分发、跟踪,、执行等工作,并收集结果,二者相互作用完成了Hadoop分布式集群的主要任务。
2.3.2 Hadoop子项目
经过多年的发展,Hadoop生态系统不断完善和成熟,随着整个Hadoop生态圈已发展成为包含很多子项目的集合。除了HDFS和MapReduce两个核心内容之外还包括Hive、HBase、Yarn、ZooKeeper等。下面是Hadoop的项目结构如图2.2所示。下面分别对它们进行简单介绍。

  1. HDFS:Hadoop分布式文件系统,是Hadoop生态系统的核心和基石。是谷歌文件系统(Google File System,GFS)的开源实现。HDFS的设计思路是将ZB级超大数据保存在廉价的大型服务器集群上,数据可以同时备份多地。并且把硬件故障作为一种常态来考虑,能够保证在部分硬件发生故障时仍然能够保证文件系统的整体可用性和可靠性。
  2. MapReduce/Yarn:并行编程模型。Yarn是第二代的MapReduce框架。MapReduce允许用户在不了解分布式系统底层细节的情况下开发并行应用程序,并将其运行于计算机集群上,完成海量数据的处理。通俗地说,MapReduce的核心思想就是“分而治之”,它把需要处理的数据集切分为若干独立的数据块,分发到各个分节点来共同并行完成;最后将所有的结果合并成最终的结果。
  3. Hive:Hive是一个基于Hadoop的数据仓库工具,提供类似SQL语言的查询方式对Hadoop中的数据进行查询,这种类似SQL的语言称为Hive QL。Hive QL语言底层转换为MapReduce任务进行处理,而不必开发专门的MapReduce应用。处理具有一定结构的文件。
  4. HBase:全称为Hadoop Database,Hadoop的分布式,面向列的数据库,来源于Google的BigTable论文,HBase的底层一般采用HDFS进行数据存储,主要用于随机访问、实时读写的大数据。HBase与传统的数据库的一个重要的区别是一个采用列的方式存储,一个采用行的方式存储。
  5. ZooKeeper:是一个为分布式应用所设计的协调服务,主要是为用户提供同步、配置管理、分组和命名等服务,减轻分布式应用程序所承担的协调服务。我们说过,Hadoop生态系统的子项目喜欢用动物的名称命名,zookeeper的中文意思是动物园管理员,就是用来管理Hadoop的各个子项目的。
  6. Sqoop:Sqoop是SQL-to-Hadoop的缩写,主要用来在Hadoop和关系型数据库之间交换数据。通过Sqoop可以方便地将数据从MySQL、Oracle、PostgreSQL等关系型数据库中导入Hadoop,或者将数据从Hadoop导出到关系数据库。Sqoop主要通过JDBC完成和关系型数据库进行交互,理论上支持JDBC的关系型数据库都可以和Sqoop交互。
  7. Flume:日志采集系统。
  8. Kafka:Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。
  9. Pig:一个对大型数据集进行分析、评估的平台,主要作用类似于数据库里的存储过程。
  10. Oozie:可以将多个MapReduce作业组合到一个逻辑工作单元中,进行工作计划的安排,类似于工作流管理引擎。

2.4 安装Hadoop的预备内容
学习本章内容之前,必须先安装下面的两个软件
 VMware Workstation
 CentOS6.5
这两个软件在Linux应用一书中已经讲过,本书不再赘述。在Linux安装Hadoop之前,需要先安装两个必要的程序:

  1. JDK1.7或更高版本
  2. SSH(安全外壳协议),主要用于主机间的免密码登录。Hadoop需要通过SSH来启动Slave列表(Hadoop组件集群中分为Master和Slave两个角色,Master是管理者,Slave是被管理者,是存储数据的主机列表,通常由Master启动Slave。)中各台主机的守护进程,这里推荐OpenSSH。一般默认CentOS已经安装了OpenSSH,所以只需进行SSH配置即可。
    SSH是一种网络协议,用于计算机之间的加密登录。如果一个用户从本地计算机,使用SSH协议登录另一台远程计算机,我们就可以认为,这种登录是安全的,即使被中途截获,密码也不会泄露。最早的时候,互联网通信都是明文通信,一旦被截获,内容就暴露无疑。1995年,芬兰学者Tatu Ylonen设计了SSH协议,将登录信息全部加密,成为互联网安全的一个基本解决方案,迅速在全世界获得推广,目前已经成为Linux系统的标准配置。
    SSH只是一种协议,存在多种实现,既有商业实现,也有开源实现。本文针对的实现是OpenSSH,它是自由软件,应用非常广泛。
    本节讲解安装Hadoop的预备内容,包括以下内容:
  3. 静态IP的设置
  4. 修改主机名和域名测试
  5. JDK的安装和配置
  6. SSH免密码配置
    下一章讲解Hadoop的具体安装步骤。
    2.4.1 静态IP的设置
    (1)在Linux系统命令终端,执行命令
    cd /etc/sysconfig/network-scripts
    切换到该目录并查看该目录下的文件ifcfg-eth0,如图所示。

(2) 在Linx系统命令终端,执行命令
vim ifcfg-eth0,并修改文件的内容。
按“键入编辑内容编译完成后按Esc键退出编译状态,之后执行命令wq,保存并退出。 IPADDR、 NETMASK、 GATEWAY、DNS1的值可以根据自己的本机进行修改,如下所示。
DEVICE=“eth0” #设备名字
BOOTPROTO=“static” #静态ip
HWADDR=“00:0C:29:ED:83:F7” #mac地址
IPV6INIT=“yes”
NM_CONTROLLED=“yes”
ONBOOT=“yes” #开启自启动
TYPE=“Ethernet” #网络类型
UUID=“28354862-67a7-4a5b-9f9a-54561401f614”
IPADDR=192.168.140.128 #IP地址
NETMASK=255.255.255.0 #子网掩码
GATEWAY=192.168.140.2 #网关
DNS1=192.168.140.2 # dns

如何设置IPADDR、NETWASK、GATEWAY、DNS1,使用下面的方法:
单击VMWare WorkStation编辑菜单下的虚拟网络编辑器菜单,打开虚拟网络编辑器窗体。选择VMnet8,如下图所示。

单击NAT设置按钮显示下图。

点击DHCP设置按钮显示下图。

注意:
 IPADDR是自定义的地址,但是必须和192.168.140.x匹配,x自定义
 NETMASK在刚才的图片中显示
 GATEWAY在刚才的图片中显示,DNS1和GATEWAY相同
(3)配置iP地址完毕之后,在命令终端的任意目录下,执行命令 ifconfig,查看配置效果,如图所示。

注意:执行ifconfig前必须保证网络畅通,如下图所示。

(4)在命令终端的任意目录下重启服务,执行命令
[root@hadoop network-scripts]# reboot
(5) ping ip地址看是否安装成功,如图所示。

注意:执行ping前必须保证网络畅通,按ctrl+z退出。
2.4.2修改主机名和域名映射
(1)启动命令终端,在任何目录下执行命令cd /etc/sysconfig,切换到该目录并查看目录下的文件,可以发现存在文件 network,如图所示。

(2)在/etc/sysconfig目录下找到文件 network,然后执行命令 vim network,按“i”进入编辑内容,编译完成后按Esc退出编译状态,之后执行命令wq保存并退出,如下图所示修改了主机名,主机名是我们刚才安装Linux时的主机名(hadoop是我们刚才安装时设置的主机名)。

(3)修改主机名和IP地址具有映射关系,执行命令vim /etc/hosts,按“i”进入编辑内容,编译完成后按Esc退出编译状态,之后执行命令wq保存并退出,如图所示。

前面是设置的静态IP地址,后面的是主机名。
一旦修改过主机名和域名映射,重启Linux会显示下图。

2.4.3 JDK的安装和配置
(1)启动 Linux命令终端,创建目录mkdir /usr/java,执行命令cd/usr/java,切换到该目录下
[root@hadoop ~]# mkdir/usr/java
[root@hadoop ~]# cd /usr/java
(2)把JDK文件jdk-8u181-linux-x64.tar.gz上传到该目录下
(3)然后对/usr/java目录下的JDK压缩文件jdk-8u181-linux-x64.tar.gz,执行命令
对jdk-8u181-linux-x64.tar.gz进行解压
[root@hadoop java]# tar -xzvf jdk-8u181-linux-x64.tar.gz
(4)解压之后,执行命令ll,可以看到该目录下多了一个解压后的JDK文件,如图2-43所示。

(5)完成上一步之后,可以执行cd jdk1.8.0_181,进入JDK安装目录

(6)确定解压无误之后,此时需要配置JDK环境变量,执行命令 vim /etc/profile 单击”i“进入编辑内容,编译完成后按Esc退出编译状态,之后执行命令wq保存并退出。如图

(7)编辑完后进行配置文件刷新,执行命令 source /etc/profile,刷新配置,配置的信息才会生效,如图所示。
[root@hadoop Desktop]# source /etc/profile
(8)完成以上步骤之后,需要测试环境变量是否配置成功,只需要在任何目录下执行
[root@hadoop Desktop]# java -version
如图,出现下图情况就是配置成功。

2.4.4 SSH免密码配置
SSH是 Secure Shell的缩写,由IETF的网络工作小组制定。SSH是建立在应用层和传输层上的安全协议,专为远程登录会话和其他网络服务提供安全性的协议。
Hadoop集群的节点之间的通信应该设置为不用密码交互,否则节点之间每次通信都需要输入密码,会给集群运行带来很大麻烦。使用SSH公钥登录可以解决这个问题,省略掉节点通信需要输入密码的步骤。
先在本机上实现SSH免密码登录实质上是使用一对加密字符串,一个称为公钥,另一个称为私钥。公钥对任何人都可见,而私钥仅对拥有者可见。
(1)在Linux系统的终端的任何目录下通过切换cd ~/.ssh,进入到.ssh目录下。
[root@hadoop Desktop]# cd ~/.ssh
~表示当前用户的home目录,通过cd ~可以进入到你的home目录。.开头的文件表示隐藏文件,这里.ssh就是隐藏目录文件。
(2)在Linux系统命令框的.ssh目录下,执行ssh-keygen命令。
[root@hadoop .ssh]# ssh-keygen -t rsa

(连续按三次回车)执行完上面命令后,会生成两个id_rsa(私钥)、id_rsa.pub(公钥)两个文件,如图所示。

(3)授权SSH免密码
[root@hadoop .ssh]# ssh-copy-id hadoop
相当于该主机给自己设置免密码登录,hadoop是刚才设置的主机名。根据提示输入yes并输入访问主机所需要的密码。

(4)执行ssh Hadoop命令,发现不需要密码就能使用ssh命令连接Hadoop主机了。
[root@hadoop .ssh]# ssh hadoop
如图所示,注意hadoop是刚才设置的主机名。

注意一旦执行ssh命令后,需要使用exit命令注销ssh连接。
如果没有执行步骤(3),会显示下面的信息,如下图所示。

2.4.5 xshell连接Linux
(1)新建会话
单击”文件”菜单下的”新建…”,打开”新建会话属性”对话框,输入名称和主机。
名称可以任意起,主机是2.3.1中设置的静态IP地址。

(2)用户身份验证
选择用户身份验证,输入用户名和密码,单击确定。

(3)连接
选择创建好的会话,单击连接。

第3章
Hadoop实验:Hadoop环境的搭建

3.1 实验目的
1.了解如何搭建单机版和伪分布式集群
2.熟悉Linux的基本命令
3.2 实验要求
1.搭建Hadoop集群
2.通过SSH工具登录集群服务器
注意:本章搭建Hadoop集群时有很多没有讲过的知识,后面章节我们会完整讲解。

3.3 Hadoop环境搭建
本节讲解Hadoop的环境搭建,Hadoop有三种运行模式。
 单机运行模式(standalone或local mode):单机模式是Hadoop的默认模式。Hadoop会完全运行在本地。因为不需要与其他节点交互,单机模式不使用HDFS,也不加载任何Hadoop的守护进程。由于在本机模式下测试和调试MapReduce程序较为方便,因此,这种模式适宜用在开发阶段。

 伪分布式模式:在本地机器上模拟一个小规模的集群,但是集群中只有一个节点,Hadoop守护进程运行在这个节点上。换句话说,可以配置一台机器的Hadoop集群,伪分布式是完全分布式的一个特例。

 完全分布式模式:Hadoop运行在一个集群上,集群中有多个节点(每个节点可以是一个物理机器或VMWare WorkStation上的一个Linux虚拟机),每个节点都有各自的Hadoop守护进程。

注意:守护进程(Daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。
分布式模式要启动守护进程。即使用分布式hadoop时,要先启动一些准备程序进程,然后才能执行分布式的命令。而本地模式不需要启动这些守护进程。
伪分布式安装是指在一台机器上模拟一个小的集群,但是这个集群中只有一个节点。需要说明的是,在一台机器上也是可以实现完全分布式安装(而不是伪分布式),只需要在一台机器上安装多个Linux虚拟机,每个虚拟机成为一个节点,这时就可以实现Hadoop的完全分布式安装。本节只介绍伪分布式安装,后面当我们学习HDFS、MapReduce等相关概念后,再讲解单机下的完全分布式安装。

Hadoop的组件配置,均可以利用XML文件进行配置,有以下的5个配置文件,具体配置作用见表3.1。这些文件都存储在hadoop安装目录下的etc/hadoop子目录中。
配置文件名 配置文件功能
etc/hadoop/hadoop-env.sh 配置JAVA_HOME
core-site.xml 配置通用属性
hdfs-site.xml 配置HDFS的属性
mapred-site.xml 配置MapReduce属性
yarn-site.xml 配置YARN属性
下面我们介绍两种Hadoop运行方式:分别是单机运行安装和伪分布式安装Hadoop。
3.3.1 Hadoop单机运行安装

  1. 启动 Linux命令终端
    [root@hadoop Desktop]# mkdir /usr/hadoop
    [root@hadoop Desktop]# cd /usr/hadoop,
    切换到该目录下,把Hadoop文件上传到该目录下

  2. 然后对/usr/hadoop目录下的Hadoop压缩文件hadoop-2.6.5.tar.gz
    [root@hadoop Desktop]# tar -zxvf hadoop-2.6.5.tar.gz -C /usr/hadoop
    -C是指解压压缩包到指定位置

  3. 切换到$HADOOP_NAME/etc/hadoop 目录下并查看该目录下的包,如图
    [root@hadoop Desktop]# ls

  4. 在$HADOOP_NAME/etc/hadoop目录下
    [root@hadoop Desktop]# vim hadoop-env.sh
    按“i”键进入编辑内容,在文件中添加如下内容:
    export JAVA_HOME=/usr/java /jdk1.8.0_181
    编写完成后,按ESC退出编辑状态,之后执行命令wq!保存并退出

注意:$HADOOP_NAME目录是/usr/hadoop/hadoop-2.6.5
因为Hadoop是使用Java语言开发的,所以这里在Hadoop的环境文件中增加了JAVA_HOME。

  1. 在$HADOOP_NAME/etc/hadoop目录下执行命令vim yarn-site.xml,并修改配置文件yarn-site.xml ,内容如下



    yarn.nodemanager.aux-services
    mapreduce_shuffle


    上面的参数yarn.nodemanager.aux-services,是NodeManager上运行的附属服务。需配置成mapreduce_shuffle,才可运行MapReduce程序。

  2. 开启Hadoop进程,切换到$HADOOP_NAME/sbin目录,执行命令。
    [root@hadoop sbin]# start-all.sh

    [root@hadoop sbin]# ./start-all.sh

  3. 用jps查看进程。
    jps是JDK提供的一个显示当前所有java进程pid的命令,简单实用,非常适合在linux/unix平台上简单察看当前java进程的一些简单情况。如下所示。

如果显示ResourceManager和NodeManager进程,表示MapReduce/Yarn启动了。
8. 关闭Hadoop进程,切换到$HADOOP_NAME/sbin目录,执行命令。
[root@hadoop Desktop]# stop-all.sh

[root@hadoop Desktop]# ./stop-all.sh
9. 在浏览器的地址栏输入http://192.168.140.128:8088/cluster(ResourceManager运行在主节点master上,可以Web控制台查看状态),如下图所示。

注意:地址栏输入的网址”192.168.140.128”是第二章设置的静态IP地址。
3.3.2 Hadoop伪分布式安装
注意:Hadoop单机运行安装和Hadoop伪分布式安装很多步骤是相同的,我们可以创建一个新的虚拟机操作一下步骤。

  1. 启动 Linux命令终端
    [root@hadoop Desktop]# mkdir /usr/hadoop
    [root@hadoop Desktop]# cd /usr/hadoop,
    切换到该目录下,把Hadoop文件上传到该目录下

  2. 然后对/usr/hadoop目录下的Hadoop压缩文件hadoop-2.6.5.tar.gz
    [root@hadoop hadoop]# tar -zxvf hadoop-2.6.5.tar.gz -C /usr/hadoop
    -C是指解压压缩包到指定位置
    [root@hadoop hadoop]# ls
    hadoop-2.6.5 hadoop-2.6.5.tar.gz
    解压缩后的目录为hadoop-2.6.5,这个目录是$HADOOP_NAME。

  3. 切换到$HADOOP_NAME/etc/hadoop 目录下并查看该目录下的包。
    [root@hadoop hadoop]# ls

  4. 在$HADOOP_NAME/etc/hadoop目录下
    [root@hadoop hadoop]# vim hadoop-env.sh
    按“i”键进入编辑内容,在文件中修改如下内容。

The java implementation to use.

export JAVA_HOME=${JAVA_HOME}
修改为
export JAVA_HOME=/usr/java/jdk1.8.0_181
修改完成后,按ESC退出编辑状态,之后执行命令wq保存并退出。

  1. 在$HADOOP_NAME/etc/hadoop目录下执行命令vim core-site.xml,并修改配置文件core-site.xml ,内容如下

    这是一个描述集群中NameNode结点的URI(包括协议、主机名称、端口号),集群里面的每一台机器都需要知道NameNode的地址。DataNode结点会先在NameNode上注册,这样它们的数据才可以被使用。
    –>

    fs.default.name
    hdfs://hadoop:9000

    注意:Hadoop2.x后fs.defaultFS已经替换了fs.default.name,这里我还是加上了。
    –>

    fs.defaultFS
    hdfs://hadoop:9000



    hadoop.tmp.dir
    /hadoop/tmp


    注意:
     红色的hadoop是当前的主机名,一定要根据当前主机名进行相应修改。
     /hadoop/tmp目录会自动产生。

  2. 在$HADOOP_NAME/etc/hadoop目录下执行命令vim hdfs-site.xml,并修改配置文件hdfs-site.xml ,内容如下



    dfs.replication
    1



    dfs.name.dir
    /hadoop/name



    dfs.data.dir
    /hadoop/data


    注意:
     /hadoop/name和/hadoop/data目录会自动产生。

  3. 在$HADOOP_NAME/etc/hadoop目录下查看是否有配置文件mapred-site.xml。目录下默认情况下没有该文件,可通过执行命令,修改一个文件的命名
    mv mapred-site.xml.template mapred-site.xml
    然后执行命令,并修改配置文件mapred-site.xml
    vi mapred-site.xml
    内容如下:



    mapreduce.framework.name
    yarn

  4. 在$HADOOP_NAME/etc/hadoop目录下执行命令vim yarn-site.xml,并修改配置文件yarn-site.xml ,内容如下



    yarn.resourcemanager.hostname
    hadoop



    yarn.nodemanager.aux-services
    mapreduce_shuffle


    注意:
     yarn.resourcemanager.hostname的value值是当前主机名。

  5. 执行命令vi /etc/profile,把Hadoop的安装目录配置到环境变量中,如图

  6. 然后让配置文件生效,执行命令
    [root@hadoop Desktop]# source /etc/profile

  7. 格式化namenode。在任意目录下(配置Hadoop环境变量的情况下)执行命令
    [root@hadoop Desktop]# hdfs namenode -format
    或者
    [root@hadoop Desktop]# hadoop namenode -format
    实现格式化。
    注意:这个命令在Hadoop创建使用一次,以后不用。如果以前已经格式化过了。需要格式化的时候需要先删除Hadoop的临时缓存目录也就是我们在core-site.xml里面配置的hadoop.tmp.dir。

  8. 启动Hadoop进程,首先启动HDFS系统,在$HADOOP_NAME/sbin目录下
    [root@hadoop sbin]# ./start-dfs.sh
    然后用jps查看进程,如图

  9. 启动YARN,在$HADOOP_NAME/sbin目录下,执行命令
    [root@hadoop sbin]# ./start-yarn.sh
    然后用jps查看进程,如图

注意:执行hadoop代码时,有时会产生下面的异常:
18/12/18 06:56:44 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform… using builtin-java classes where applicable
需要修改$HADOOP_HOME/etc/hadoop/log4j.properties文件,添加以下内容。
log4j.logger.org.apache.hadoop.util.NativeCodeLoader=ERROR

  1. 测试HDFS和YARN,首先在浏览器地址栏中输入http://192.168.140.128:50070 下面显示HDFS管理界面,如下图所示。

在浏览器的地址栏输入http://192.168.140.128:8088
下面显示MapReduce的管理页面,如下图所示。

注意:上面的IP地址必须是第二章2.4.1设置的本地IP。
14. 关闭HDFS系统,在$HADOOP_NAME/sbin目录下,执行命令
[root@hadoop sbin]# ./stop-dfs.sh

  1. 关闭YARN系统,在$HADOOP_NAME/sbin目录下,执行命令
    [root@hadoop sbin]# ./stop-yarn.sh

第4章
分布式文件系统HDFS

4.1 Hadoop概述
大数据时代必须解决海量数据的高效存储问题。为此,谷歌开发了分布式文件系统(Google File System,GFS),通过网络实现文件在多台机器上的分布式存储,较好的满足了大规模数据存储的需求。Hadoop分布式文件系统(Hadoop Distributed File System,HDFS)是针对GFS的开源实现,它是Hadoop两大核心组成部分之一,提供了在廉价服务器集群中进行大规模分布式文件存储的能力,HDFS具有很好的容错能力,并且兼容廉价的硬件设备,因此可以以较低的成本利用现有机器实现大流量和大数据量的读写。
4.1.1 计算机集群结构
计算机集群简称集群,是一种分布式计算机系统, 它通过一组松散集成的计算机软件或硬件连接起来高度紧密地协作完成计算工作。在某种意义上,他们可以被看作是一台计算机。
节点可能代表的是一台计算机这样的物理节点,在有数据传入和输出的点都也可以叫节点。
分布式文件系统把文件分布存储到多个计算机节点上,成千上万的计算机节点构成计算机集群。与之前使用大量的高性能处理器和专用高级硬件的昂贵大型机不同的是,目前的分布式文件系统所使用的计算机集群都是由普通硬件构成,这大大降低了硬件上的开销。
计算机集群的基本架构如图所示。集群中的计算机节点存放在机架(Rack)上,每个机架上可以存放8~64个节点,同一机架上的不同节点之间通过网络互连,多个不同机架之间采用交换机互练。

分布式文件系统由块组成,如HDFS的块大小有64MB,如果一个文件小于一个数据块大小,它并不占用整个数据块的存储空间。
分布式文件系统在物理结构上是由计算机集群中的多个节点构成的,如图4-2所示。这点分为两类:一类叫“主节点”(Master Node),或者也被称为“名称节点”(NameNode);另类叫“从节点”(Slave Node),或者也被称为“数据节点”(DataNode)。名称节点负责文件和目录的创建、删除和重命名等,同时管理着数据节点和文件块的映射关系,因此客户端只有访问名称节点才能找到请求的文件块所在的位置,进而到相应位置读取所需文件块。数据节点负责数据的存储和读取,在存储时,由名称节点分配存储位置,然后由客户端把数据直接写入相应数据节点;在读取时,客户端从名称节点获得数据节点和文件块的映射关系,然后就可以到相应位置访问文件块。数据节点也要根据名称节点的命令创建、删除数据块和冗余复制。

计算机集群中的节点可能发生故障,因此为了保证数据的完整性,分布式文件系统通常采用多副本存储。文件块会被复制为多个副本,存储在不同的节点上,而且存储同一文件块的不同副本的各个节点会分布在不同的机架上,这样,在单个节点出现故障时,就可以快速调用副本重启单个节点上的计算过程,而不用重启整个计算过程,整个机架出现故障时也不会丢失所有文件块。文件块的大小和副本个数通常可以由用户指定。
分布式文件系统是针对大规模数据存储而设计的,主要用于处理大规模文件,如TB级文件。处理过小的文件不仅无法充分发挥其优势,而且会严重影响到系统的扩展和性能。

4.1.2 HDFS简介
我们现在开始讲Hadoop的核心组件:HDFS。为什么我们不说Hadoop呢?因为Hadoop由很多组件组成。它是一个大的概念,我们必须通过讲解Hadoop的组件来理解Hadoop。
目前得到广泛应用的分布式文件系统主要包括GFS和HDFS等,Hadoop就是使用的HDFS,它是Google GFS的开源实现。
下面是HDFS的优势:
 存储超大文件,文件大小通常都是上百MB、TB、PB级别。
 标准流式访问,基于“一次写入,多次读取”的构建思路,即只支持文件的追加写,不支持随机访问,这是最高效的访问模式。流式方式就是按照顺序来,一条线,找一次就够了。所以适合一次写,多次读的数据。
 运行在廉价的商用机器集群上, Hadoop并不需要昂贵且高可靠的硬件。
 兼容廉价的硬件设备。在成百上千的廉价服务器上存储数据,常会出现节点。
 强大的平台兼容性。HDFS是由java编写的,支持JVM的机器都可以运行HDFS。
HDFS特殊的设计,在实现上述优良特性的同时,也使得自身具有一些应用局限性,主要包括以下几个方面:
 不适合低延迟数据访问。HDFS主要是面向大规模数据批量处理而设计的,采用流式数据读取,具有很高的数据吞吐率,但是,这也意味着较高的延迟。因此,HDFS不适合用在需要较低延迟(如数十毫秒)的应用场合。对于低延时要求的应用程序而言, HBase是一个更好的选择。
 无法高效存储大量小文件。小文件是指文件大小小于一个块(64MB)的文件,HDFS无法高效存储和处理大量小文件,过多小文件会给系统扩展性和性能带来诸多问题。首先,HDFS采用名称节点(Name Node)来管理文件系统的元数据,这些元数据被保存在内存中,从而使客户端可以快速获取文件实际存储位置。通常,每个文件、目录和块大约占150字节,如果有1000万个文件,每个文件对应一个块,那么,名称节点至少要消耗3GB的内存来保存这些元数据信息。很显然这时元数据检索的效率就比较低了,需要花费较多的时间找到一个文件的实际存储位置。而且如果继续扩展到数十亿个文件时,名称节点保存元数据所需要的内存空间就会大大增加,以现有的硬件水平,是无法在内存中保存如此大量的元数据的。其次,用 MapReduce处理大量小文件时,会产生过多的Map任务,线程管理开销会大大增加,因此处理大量小文件的速度远远低于处理同等大小的大文件的速度。再次,访问大量小文件的速度远远低于访问几个大文件的速度,因为访问大量小文件,需要不断从一个数据节点跳到另一个数据节点,严重影响性能。
 不支持多用户写入及任意修改文件。HDFS只允许一个文件有一个写入者,不允许多个用户对同一个文件执行写操作,而且只允许对文件执行追加操作,不能执行随机写操作。
HDFS的体系结构如图所示。

下面我们针对此图中各部分分别介绍。
4.1.3 基本概念
1.块
HDFS中的文件被分成块进行存储,它是文件的最小逻辑单元,默认块大小为64MB,使用文件块的好处是:
 文件的所有块并不需要存储在同一个磁盘上,可以利用集群上的任意一个磁盘进行存储。
 对分布式系统来说,由于块的大小是固定的,因此计算单个磁盘能存储多少个块就相对容易,可简化存储管理。
 在数据冗余备份时,将每个块复制到少数几台独立的机器上(默认为三台).可以确保在块磁盘或机器发生故障后数据不会丢失。如果发现一个块不可用,系统会从其他地方读取个副本,这个过程对用户是透明的。
使用fsck命令可以显示块信息:
[root@hadoop sbin]# hdfs fsck / -files -blocks
Connecting to namenode via http://hadoop:50070
FSCK started by root (auth:SIMPLE) from /192.168.142.139 for path / at Tue Dec 18 00:44:00 PST 2018
/


/hadoop1
Status: HEALTHY
Total size: 0 B
Total dirs: 2
Total files: 0
Total symlinks: 0
Total blocks (validated): 0
Minimally replicated blocks: 0
Over-replicated blocks: 0
Under-replicated blocks: 0
Mis-replicated blocks: 0
Default replication factor: 1
Average block replication: 0.0
Corrupt blocks: 0
Missing replicas: 0
Number of data-nodes: 1
Number of racks: 1
FSCK ended at Tue Dec 18 00:44:00 PST 2018 in 25 milliseconds
fsck命令将列出文件系统中根目录”/”下各个文件由哪些块构成。fsck命令只是从NameNode获取信息,并不与任何DataNode交互,因此并不真正获取数据。

注意:执行hadoop代码时,有时会产生下面的异常:
18/12/18 06:56:44 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform… using builtin-java classes where applicable
需要修改$HADOOP_HOME/etc/hadoop/log4j.properties文件,添加以下内容。
log4j.logger.org.apache.hadoop.util.NativeCodeLoader=ERROR
2.NameNode和DataNode节点
NameNode和DataNode属于HDFS集群的两类节点。
NameNode负责管理文件系统的命名空间,属于管理者角色。它维护文件系统树内所有文件和目录,记录每个文件在各个DataNode上的位置和副本信息,并协调客户端对文件的访问。名称节点(Name Node)保存了两个核心的数据结构(见图4-3),即FsImage和 EditLog。FsImage用于维护文件系统树以及文件树中所有的文件和文件夹的元数据。操作日志文件 EditLog中记录了所有针对文件的创建、删除、重命名等操作。名称节点记录了每个文件中各个块所在的数据节点的位置信息,但是并不持久化存储这些信息,而是在系统每次启动时扫描所有数据节点重构得到这些信息。
名称节点在启动时,会将FsImage的内容加载到内存当中,然后执行 EditLog文件中的多项操作,使得内存中的元数据保持最新。这个操作完成以后,就会创建一个新的FsImage文件和一个空的EditLog文件。名称节点启动成功并进入正常运行状态以后,HDFS中的更新操作都会被写入到EditLog,而不是直接写入FsImage,这是因为对于分布式文件系统而言,FsImage文件通常都很庞大(一般都是GB级别以上),如果所有的更新操作都直接往FsImage文件中添加,那么系统就会变得非常缓慢。相对而言, EditLog通常都要远远小于FsImage,更新操作写人到EditLog是非常高效的。名称节点在启动的过程中处于“安全模式”,只能对外提供读操作无法提供写操作。启动过程结束后,系统就会退出安全模式,进入正常运行状态,对外提供读写操作。

DataNode根据需要存储、检索数据块,并定期向NameNode发送所存储的块的列表,属于工作者角色。它负责所在物理节点的存储管理,按照一次写入、多次读取的原则。存储文件由数据块组成,典型的块大小是64MB,尽量将各数据块分布到各个不同的DataNode节点上。DataNode负责处理文件系统客户端的文件读写请求,并在NameNode的统一调度下进行数据块的创建、删除和复制工作。如果在NameNode上的数据损坏,HDFS中所有的文件都不能被访问,由此可见NameNode节点的重要性。为了保证NameNode的高可用性,Hadoop对NameNode进行了补充,即第二命名节点(Secondary NameNode)。
3.Secondary NameNode节点
系统中会同步运行一个Secondary NameNode,也称为二级NameNode。相当于NameNode的快照,能够周期性的备份NameNode,记录NameNode的元数据等,也可以用来恢复NameNode,但Secondary NameNode中的备份会滞后于NameNode,所以会带来一定的数据损失。为了防止宕机,通常将NameNode和Secondary NameNode设置为不同的主机。使用hdfs-site.xml中配置的dfs.namenode.secondary.http-address属性值可以通过浏览器查看Secondary NameNode的运行状态。

我们可以使用jps命令时发现SecondaryNameNode,说明Secondary NameNode节点已经启动。
4.1.4 Master/Slave架构
一个HDFS集群由一个NameNode和多个DataNode组成,属于典型的 Master/ Slave模式。其读写流程如下:
 数据读流程:由客户端向 NameNode请求访问某个文件, NameNode返回该文件所在位置即在哪个 DataNode上,然后由客户端从该 DataNode读取数据。
 数据写流程:由客户端向 NameNode发出文件写请求, NameNode告诉客户该向哪个 DataNode写入该文件,然后由客户将文件写入该 DataNode节点,随后该DataNode将该文件自动复制到其他 DataNode节点上,默认三份备份。
1.节点添加
可扩展性是HDFS的一个重要特性,向HDFS集群中添加节点很容易实现。添加一个新的 DataNode点的步骤如下。
(1)对新节点进行系统配置,包括 hostname、 hosts文件、JK环境、防火墙等。
(2)在新加节点上安装好Hadoop,要和 NameNode使用相同的配置,可以直接从 NameNode复制。
(3)在 NameNode上修改 HADOOP HON/etc/ hadoop/ Slaves文件,加入新加节点主机名。
(4)运行启动命令:
[root@hadoop sbin]# ./start-all.sh
2.负载均衡
HDFS的数据在各个 DataNode中的分布可能很不均匀,尤其是在 DataNode节点出现故障或新增DataNode节点时。
使用如下命令可重新平衡 DataNode上的数据块的分布:
[root@hadoop sbin]# ./start-balancer.sh
3.安全机制
Master/Save架构通过 NameNode来统一调度,没有 NameNode文件系统将无法使用。 Hadoop采用两种机制来确保 NameNode的安全。
 第一种是将 NameNode上存储的元数据文件转移到其他文件系统中。
 第二种就是使用 Secondary NameNode同步备份。

4.2 Hadoop守护进程介绍
3.3.2执行后可以看到下面的Hadoop守护进程列表。

守护进程名 描述
NameNode Hadoop中的主服务器,管理文件系统名称空间和对集群中存储的文件的访问。
SecondaryNameNode 它不是 namenode 的冗余守护进程,而是提供周期检查点和清理任务。
出于对可扩展性和容错性等考虑,我们一般将SecondaryNameNode运行在一台非NameNode的机器上。
NodeManager 功能比较专一,就是负责Container状态的维护,并向 RM 保持心跳。
ResourceManager ResourceManager负责集群中所有资源的统一管理和分配,它接受来自各个节点(NodeManager)的资源汇报信息,并把这些信息按照一定的策略分配给各个应用程序(即ApplicationMaster)。
DataNode 每个存储数据的节点运行一个 datanode 守护进程。

4.3 HDFS的WEB界面
通过http:// NameNodeIP:50070可以访问HDFS的Web界面,该界面提供了 NameNode基本信息与所有 DataNode信息,如图4.5和图4.6所示。在后续章节中,关于“NameNodeIP”,在不作特别说明的情况下,其表示 NameNode节点的或 hostname,请自行替换。

NameNode基本信息

DataNode基本信息

浏览文件系统
4.4 Hadoop FS Shell命令
调用Hadoop的文件系统Shell(FileSystem Shell)的命令格式如下:
语法:hadoop fs
其中 hadoop命令位于$HADOOP_HOME/bin目录下,“ts”为其参数,表示 FS Shell。是ts的子命令,格式类似于 Linux Shell命令,并且功能也类似,如
hadoop fs -ls file:///home/hauser
该命令使用了fs子命令-ls,最后一个参数表示本地系统文件路径。运行命令后会列出该目录下所有文件。
可以看出 Hadoop fs子命令-ls与 Linux中的ls命令的作用及语法均相似。但应该注意到在HDFS中访问本地系统文件加上了前缀file://,那么是否可以省略呢?
Hadoop的文件系统可以支持多种文件系统的访问,如Local(本地文件系统)和HDFS。访问时均使用URL路径作为参数,如”file://path”和”hdfs://NameNodeIP:NameNodePort/path”,分别表示访问本地文件系统和HDFS。
在Hadoop配置(core-site.xml属性fs. defaultFS)中指定的文件的系统格式为:

fs.defaultFS
hdfs://hadoop:9000

所以可以省略hdfs:/NameNodeIP:NameNodePort
hadoop fs -ls /hadoop2
1.创建目录:mkdir
hadoop fs -mkdir
[root@hadoop sbin]# hadoop fs -mkdir /user
[root@hadoop sbin]# hadoop fs -mkdir /user/resources
#同时创建多个目录
[root@hadoop sbin]# hadoop fs -mkdir /user/resources/img /user/resources/info

2.列表文件:ls
[root@hadoop sbin]# hadoop fs -ls /user/resources
Found 3 items
-rw-r–r-- 1 root supergroup 41 2018-12-18 05:47 /user/resources/aaa.txt
drwxr-xr-x - root supergroup 0 2018-12-18 05:32 /user/resources/img
drwxr-xr-x - root supergroup 0 2018-12-18 05:47 /user/resources/info
列出hdfs文件系统/user/resources目录下的1个文件2个目录。

3.查看文件:cat
[root@hadoop sbin]# hadoop fs -cat /user/resources/aaa.txt
hello world
this is a simple text file.

4.转移文件

  1. put命令
    hadoop fs -put …
    从本地文件中复制单个或多个文件到HDFS,也支持从标准输入中读取输入写入到目标文件系统。其中localsrc只能是本地文件,dst只能是HDFS文件,且不受fs.defaultFS属性影响。
    [root@hadoop sbin]# hadoop fs -put /usr/aaa.txt /user/resources
    #将多个文件复制到HDFS目录”/user/resources”
    [root@hadoop sbin]# hadoop fs -put /usr/aaa.txt /usr/bbb.txt /user/resources

    2)get命令
    hadoop fs –get
    复制HDFS中单个或多个文件到本地文件系统,是put命令的逆操作。其中localdst只能是本地文件,src只能是HDFS文件,且不受fs.defaultFS属性影响。
    [root@hadoop sbin]# hadoop fs -get /user/resources/aaa.txt H O M E / f i l e 1 将 H D F S 文 件 / u s e r / r e s o u r c e s / a a a . t x t 复 制 到 HOME/file1 将HDFS文件/user/resources/aaa.txt复制到 HOME/file1HDFS/user/resources/aaa.txtHOME/file1目录中,$HOME为Linux系统环境变量,代表用户根目录,如用户zhangsan用户的根目录为/home/zhangsan。

  2. mv命令
    hadoop fs –mv URI [URI…]
    将文件从源路径移动到目标路径,允许多个源路径,目标路径必须是一个目录。不允许在不同的文件系统间移动文件,也就是说所有路径都必须是同一文件系统URL格式。
    [root@hadoop sbin]# hadoop fs -mv /input2/file1. txt /input2/file2.txt /user/hadoop/dir1
    将HDFS上的file1.txt、file2.txt移动到dir1中。
    4)cp命令
    hadoop fs –cp URI [URI…]
    将文件从源路径复制到目标路径,允许多个源路径,目标路径必须是一个目录。不允许在不同的文件系统间复制文件。
    [root@hadoop sbin]# hadoop fs -cp /input2/file1. txt /input2/file2.txt /user/hadoop/dir1
    将HDFS上的file1.txt、file2.txt复制到dir1中。

5.删除文件:rm、rmr

  1. rm命令
    hadoop fs –rm URI [URI…]
    删除指定的文件,只删除非空目录和文件。
    [root@hadoop Desktop]# hadoop fs –rm /input2/file1.txt
  2. rmr命令
    hadoop fs –rmr URI [URI…]
    rm的递归版本,整个文件夹及子文件夹将全部删除。
    #递归删除/input2文件夹
    [root@hadoop Desktop]# hadoop fs –rmr /input2
  1. 管理命令:test、du、expunge
  1. test命令
    hadoop fs -test -[选项] URI
    选项:
    -e 检查文件是否存在。如果存在则返回0。
    -z 检查文件是否是0字节。如果是则返回0。
    -d 如果路径是个目录,则返回1,否则返回0。
  2. du命令
    hadoop fs -du URI [URI…]
    显示目录中所有文件的大小。
    [root@hadoop sbin]# hadoop fs -du /user
    121 /user/resources
    [root@hadoop sbin]# hadoop fs -du /user/resources/aaa.txt
    41 /user/resources/aaa.txt
    如果是目录就显示目录中所有文件的大小,如果是文件就显示文件的大小。
  3. expunge命令
    清空回收站
    以上只是介绍了常见的几个Hadoop Shell命令,更多命令可以自行参考Hadoop帮助文档,在线文档为http://hadoop.apache.org/docs/。

第5章
HDFS实验:HDFS文件操作

5.1实验目的

  1. 会在 Linux环境下编写读写HDFS文件的代码;
  2. 会使用jar命令打包代码;
  3. 会在 linux上运行HDFS读写程序;
  4. 会在 Windows上安装 Eclipse Hadoop插件;
  5. 会在 Eclipse环境编写读写HDFS文件的代码;
  6. 会使用 Eclipse打包代码。
    5.2实验要求
    要求实验结束时,每位学生均已搭建HDFS开发环境;编写了HDFS写、读代码;在 linux上执行了该写、读程序。搭建HDFS开发环境,编程实现读写HDFS,了解HDFS读写文件的调用流程,理解HDFS读写文件的原理。
    5.3实验步骤
    5.3.1在windows下安装Java
    JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。
    本书后续章节的Hadoop开发时基于Java语言的,所以需要在Windows下安装JDK,具体安装步骤如下。
    (1) 双击jdk-7u79-windows-x64.exe,如图所示,单击“下一步”按钮

(2) 安装完JDK时,系统会弹出安装JRE的提示窗口,这里根据自己的需要更改JRE的安装目录。需要注意的是,JDK和JRE的安装目录最好在同一个文件夹下,比如都在: c:\Program files\java\下,如图所示,单击“下一步”按钮,进行JRE的安装。

(3)安装完JDK时,单击“关闭”按钮即可完成JDK的安装。

(4)配置环境变量。
①右键单击“计算机”,选择“属性”命令,在出现的系统设置窗口中选择“高级系统设置”选项,进入到“系统属性”对话框,单击“环境变量”按钮,出现“环境变量对话框”,如图所示。

②新建JAVA_HOME变量,变量值输入JDK安装路径⑥,如图所示。

③新建 CLASSPATE变量,变量值输入“%JAVA_HOME%lib\dt.jar;.;%JAVA_HOME%\lib\tools.jar”,如图所示。

④找到Path变量,在变量值输入“%JAVA_HOME%\bin”(注意最后面有个“;”号),如图所示。

⑤测试环境变量是否配置成功。打开“开始”菜单,在搜索框里输入“cmd”(命令提示符),弹出一个页面,输入“java -version”,出现所示的信息。

⑥将hadoop-2.6.5文件夹中复制到d:\hadoop目录下,新建 HADOOP_HOME变量,变量值输入安装路径,如图所示

⑦找到Path变量,在变量值输入“%HADOOP_HOME%\bin”(注意最后面有个“;”号),如图所示。

⑧打开DOS命令窗口,输入命令
C:\Users\Administrator.4WECRBYQ638LSVK>java -version
java version “1.8.0_161”
Java™ SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot™ 64-Bit Server VM (build 25.161-b12, mixed mode)

5.3.2 Linux启动Hadoop进程
(1)查看服务状态。在命令终端,执行命令jps,查看进程状态(查看 hadoop服务否已经启动,如未启动,启动服务)

(2)启动 Hadoop进程。启动Hadoop服务可以通过一次性启动Hadoop所有进程,执行命令 start-all. sh可以启动Hadoop所有进程。相当于执行命令start-hdfs.sh &&start-yarn.sh,如图3-55所示。

(3)在浏览器上输入“192.168.11.128:50070”,点击“Utilities”->“Browse the file system”查看

5.3.3 Eclipse上安装Hadoop插件
在D盘下创建Hadoop目录,将Hadoop-2.6.5复制到Hadoop目录下。
(1)复制hadoop-eclipse-plugin-2.5.2.jar 到 eclipse安装目录/plugins/ 下,把hadoop.dll放到c:/windows/system32下,把winutils.exe放到Hadoop的安装目录如D:\Hadoop\hadoop-2.6.5\bin\下替换原有的winutils.exe文件。

(2)重启eclipse,配置hadoop installation directory。 如果安装插件成功,打开Window–>Preferences,你会发现Hadoop Map/Reduce选项,在这个选项里你需要配置Hadoop installation directory。配置完成后退出。

(3)配置Map/Reduce Locations。
在Window–>Show View中打开Map/Reduce Locations。

(4)修改yarn-site.xml(Linux下的还是windows下的? 我都加了)

yarn-resourcemanager.scheduler.address
192.168.142.139:8030

core-site.xml

fs.defaultFS
hdfs://hadoop:9000

(5)如下图,点击右下角,在弹出的对话框中你需要配置Location name,如设置名称为Hadoop,还要配置Map/Reduce Master和DFS Master。这里面的Host、Port分别为你在yarn-site.xml、core-site.xml中配置的地址及端口,User name是虚拟机的名字,如hadoop。

(6)配置完后点击Finish退出。点击DFS Locations–>Hadoop如果能显示文件夹,说明配置正确,如果显示"拒绝连接",请检查你的配置或防火墙是否关闭。

注意:这时一定要关闭linux防火墙,如果不加的话会连接不上。
注意:要想执行上面的创建目录代码,必须关闭Linux的防火墙,执行命令:
[root@hadoop Desktop]# service iptables stop
如果需要永久关闭防火墙,执行命令:
[root@hadoop Desktop]# chkconfig iptables off
运行完成后查看防火墙关闭状态
[root@hadoop Desktop]# service iptables status
iptables: Firewall is not running.
注意:Linux防火墙一旦关闭,在Windows上可以访问Hadoop。

(7)上传模拟数据文件夹。
通过Hadoop的命令在HDFS上创建目录/hdfs,命令如下
[root@hdoop Desktop]# hdfs dfs –mkdir /hdfs
然后我们在eclipse上单击“DFS Locations”右键选择“reconnect”下,然后发现文件夹变成(4)了,如图所示。

5.3.4 eclipse连接虚拟机
(1)打开 Eclipse开发工具,单击File选择“New”→“Java project”,新建名称为“ Hadoop的Java项目,单击右键“ hadoop项目,选择“New”→“ Package”,如图所示。

(2)输入包名称“ com.hdfs”,单击“Finish”按钮,如图所示。

(3)新建java类,选中包名并单击右键,选择“New”→“ Class”,如图所示。

(4)在name项输入“HdfsTest " 类名称,单击" Finish”按钮完成,如图所示。

(5)在创建的项目目录下创建文件夹lib,通过选中文件夹“src”并点击右键,选择“New”→“SourceFolder”,在“Folder name”项输入lib,然后点击“finish”,如图所示。

(6)拷贝第五章实验资料下Hadoop jar文件夹下的所有 hadoop的 jar包到lib文件夹下。

(7)选中lib下的所有jar包,单击右键,然后选择”Add to Build Path”,即可把所有jar包添加到path环境中。

(8)把org包放到src下。

(9)编写程序。
package com.hadoopdemo1;

import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.junit.Before;
import org.junit.Test;

public class HdfsTest {

// 获取HADOOP FileSystem对象
private FileSystem fs = null;

/*
 * 初始化环境变量
 */
@Before
public void init() throws Exception {
	/*
	 * new URI(“hdfs://192.168.11.128:9000”) 连接HDFS new Configuration()
	 * 使用HADOOP默认配置 “root” 登录用户
	 */
	fs = FileSystem.get(

new URI(“hdfs://192.168.142.139:9000”), new Configuration(), “root”);
}

/*
 * 创建目录
 */
@Test
public void testMkdir() throws Exception {
	boolean flag = fs.mkdirs(new Path("/javaApi/"));
	System.out.println(flag ? "创建成功" : "创建失败");
}

}

(10)在项目栏目最右侧,选中要运行的方法“testMkdir”,单击右键,弹出菜单,选择“Run As Junit”→“Test”,等待执行结果,如图所示。
(11)显示运行结果,如图所示。

(11) 在浏览器上输入“192.168.142.139:50070”,查看是不是多了一个文件夹。
注意:要想执行上面的创建目录代码,必须关闭Linux的防火墙,执行命令:
[root@hadoop Desktop]# service iptables stop
如果需要永久关闭防火墙,执行命令:
[root@hadoop Desktop]# chkconfig iptables off
运行完成后查看防火墙关闭状态
[root@hadoop Desktop]# service iptables status
iptables: Firewall is not running.
关闭防火墙后,在windows下访问Hadoop HDFS,如图。

5.3.5读写文件
1.写文件
package com.hadoopdemo1;

import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.junit.Test;

public class HdfsWrite {
// 获取HADOOP FileSystem对象
private FileSystem fs = null;

/**
 * 写文件
 * 
 * new URI("hdfs://192.168.142.139:9000") 连接HDFS new Configuration()
 * 使用HADOOP默认配置 "root" 登录用户 FSDataOutputStream数据输出流
 * 
 * */
@Test
public void WriteFile() throws Exception {
	FileSystem hdfs = FileSystem.get(

new URI(“hdfs://192.168.142.139:9000”), new Configuration(), “root”);
Path dfs = new Path("/hdfs/ss.txt");
FSDataOutputStream out = hdfs.create(dfs);
out.writeUTF(“cccc”);
}

}

2.读文件
package com.hadoopdemo1;

import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.junit.Test;

public class HdfsRead {
// 获取HADOOP FileSystem对象
private FileSystem fs = null;

/**
 * 读文件
 * */
@Test
public void ReadFile() throws Exception {
	Configuration conf = new Configuration();
	FileSystem hdfs = FileSystem.get(

new URI(“hdfs://192.168.142.139:9000”), conf, “root”);
Path dfs = new Path("/hdfs/ss.txt");
FSDataInputStream input = hdfs.open(dfs);
System.out.print(“myfile:” + input.readUTF());
input.close();
}

}
5.3.6 创建文件
package com.hadoopdemo1;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.junit.Test;

public class HdfsCreateFile {

// 获取HADOOP FileSystem对象
private FileSystem fs = null;

/**
 * 创建文件
 */
@Test
public void CreateFile() throws Exception {
	Configuration conf = new Configuration();
	FileSystem hdfs = FileSystem.get(

new URI(“hdfs://192.168.142.139:9000”), conf, “root”);
Path dfs = new Path("/hdfs/new.txt");
FSDataOutputStream outputStream = hdfs.create(dfs);
InputStream in = new ByteArrayInputStream(“Hello!Hadoop HDFS”.getBytes(“UTF-8”));
IOUtils.copyBytes(in, outputStream, 4096, true);
}

}

5.3.7 复制文件
1.本地文件复制到HDFS
如果要将文件从本地系统复制到HDFS中,可使用copyFromLocalFile()方法。
public void copyFromLocalFile(Path srcPath,Path dstPath)
此方法第一个参数要求必须是本地文件,第二个必须是HDFS文件。
public class CopyFromLocalFile {

/*
 * 复制本地文件到HDFS
 */
@Test
public void testCopyFromLocalFile() throws Exception {
	Configuration conf = new Configuration();
	FileSystem hdfs = FileSystem.get(

new URI(“hdfs://192.168.142.139:9000”), conf, “root”);
Path srcFile = new Path(“D:\ebook_mysql.sql”);
Path dstFile = new Path("/hdfs/ebook.txt");
hdfs.copyFromLocalFile(srcFile, dstFile);
}

}

2.HDFS文件复制到本地
如果要将文件从HDFS复制到本地系统中,可使用copyToLocalFile()方法。
public void copyToLocalFile(Path dstPath, Path srcPath)
public class CopyToLocalFile {

/*
 * 复制HDFS到本地文件
 */
@Test
public void testCopyFromLocalFile() throws Exception {
	Configuration conf = new Configuration();
	FileSystem hdfs = FileSystem.get(

new URI(“hdfs://192.168.142.139:9000”), conf, “root”);
Path dstFile = new Path(“D:\ebook_mysql2.sql”);
Path srcFile = new Path("/hdfs/ebook.txt");
hdfs.copyToLocalFile(srcFile, dstFile);
}

}
5.3.8 删除文件
public boolean delete(Path f,boolean recursive);
参数2 recursive表示是否进行递归删除。如果f是一个文件或空目录,那么recursive的值就会被忽略。
public class HdfsDeleteFile {

// 获取HADOOP FileSystem对象
private FileSystem fs = null;

/**
 * 删除文件
 */
@Test
public void DeleteFile() throws Exception {
	Configuration conf = new Configuration();
	FileSystem hdfs = FileSystem.get(

new URI(“hdfs://192.168.142.139:9000”), conf, “root”);
Path dfs = new Path("/hdfs/new.txt");
hdfs.delete(dfs,true);
}

}
5.3.9 文件查询
FileStatus封装了文件的元数据,通过这个对象可以获得文件路径,文件大小等信息。
public class HdfsFileStatus {
// 获取HADOOP FileSystem对象
private FileSystem fs = null;

/**
 * 文件信息
 * */
@Test
public void ReadFile() throws Exception {
	Configuration conf = new Configuration();
	FileSystem hdfs = FileSystem.get(new URI("hdfs://192.168.142.139:9000"), conf, "root");
	Path dfs = new Path("/hdfs/ss.txt");
	FileStatus stat = hdfs.getFileStatus(dfs);
	System.out.println("文件路径:"+stat.getPath());
	System.out.println("文件大小:"+stat.getLen());
	System.out.println("副本数量:"+stat.getReplication());
	System.out.println("用户:"+stat.getOwner());
	System.out.println("权限:"+stat.getPermission().toString());
}

}

如果我们希望列出HDFS目录下的文件,可以使用listStatus()方法列出目录内容。
public class HdfsListFileStatus {
// 获取HADOOP FileSystem对象
private FileSystem fs = null;

/**
 * 列出文件信息
 * */
@Test
public void ReadFile() throws Exception {
	Configuration conf = new Configuration();
	FileSystem hdfs = FileSystem.get(

new URI(“hdfs://192.168.142.139:9000”), conf, “root”);
Path dfs = new Path("/hdfs");
FileStatus[] states = hdfs.listStatus(dfs);
for(FileStatus stat:states){
System.out.println(stat.getPath());
}
}

}
5.3.10重命名文件
public class HdfsRenameFile {

// 获取HADOOP FileSystem对象
private FileSystem fs = null;

/**
 * 删除文件
 */
@Test
public void DeleteFile() throws Exception {
	Configuration conf = new Configuration();
	FileSystem hdfs = FileSystem.get(

new URI(“hdfs://192.168.142.139:9000”), conf, “root”);
Path src = new Path("/hdfs/ss.txt");
if(hdfs.exists(src)){
Path dst = new Path("/hdfs/new2.txt");
hdfs.rename(src, dst);
}
else{
System.out.println(“文件不存在!!!”);
}
}

}

第6章
复习1

第7章
MapReduce

大数据时代除了需要解决大规模数据的高效存储问题,还需要解决大规模数据的高效处理问题。分布式并行编程可以大幅度提高程序性能,实现高效的批量数据处理。MapReduce是一种并行编程模型,用于大规模数据集(1TB)的并行计算。
本章介绍MapReduce模型,阐述其具体工作流程,并以单词统计为实例介绍MapReduce程序设计方法。
7.1 MapReduce介绍
7.1.1 分布式并行编程
在过去的很长一段时间里,CPU的性能都会遵循“摩尔定律”,大约每隔18个月性能提升一倍。这意味着不需要对程序做任何改变,仅仅通过使用更高级的CPU,程序就可以享受免费的性能提升。但是,大规模集成电路的制作工艺已经达到一个极限,从2005年开始摩尔定律逐渐失效,就不能再把希望过多地寄托在性能更高的CPU身上。于是,人们开始钟情于分布式并行编程来提高程序的性能。分布式程序运行在大规模计算机集群上,集群中包括大量廉价服务器,可以并行执行大规模数据处理任务,从而获得海量的计算能力。
分布式并行编程与传统的程序开发方式有很大的区别。传统的程序都是以单指令、单数据流方式顺序执行,虽然这种方式比较符合人类的思维习惯,但是这种程序的性能受到单台机器性能限制,可扩展性较差。分布式并行程序可以运行在由大量计算机构成的集群上,从而可以充用集群的并行处理能力,同时通过向集群中增加新的计算节点,就可以很容易地实现集群算力的扩充。
谷歌公司最先提出了分布式并行编程模型 MapReduce,Hadoop MapReduce是它的开源实现。谷歌的Mapreduce运行在分布式文件系统GFS上。与谷歌类似,Hadoop MapReduce运行在分布式文件系统HDFS上。
7.1.2 MapReduce概念
为什么使用 MapReduce?对于大量数据的计算,通常采用的处理方法就是并行计算。并行计算的含义就是要求能够将大型而复杂的计算问题分解为各个子任务,并分配到多个计算资源下同时进行计算,其显著特点是耗时小于单个计算资源下的计算。对多数开发人员来说,并行计算还是个陌生、复杂的东西尤其是涉及分布式的问题,将会更加棘手。MapReduce就是一种实现并行计算的编程模型,它向用户提供接口,屏蔽了并行计算特别是分布式处理的诸多细节,让那些没有多少并行计算经验的开发人员也可以很方便地开发并行应用。
MapReduce由两个概念合并而来,map(映射)和reduce(归约)。map负责把任务分解为多个任务,reduce负责把分解后多任务的处理结果进行汇总。
我们已经知道,Hadoop的 MapReduce框架源自Google的MapReduce论文。现在 MapReduce被广泛地应用于日志分标海量数据排序、在海量数据中查找特定模式等场景中。
Hadoop中的并行应用程序的开发是基于MapReduce编程模型的,基于它可以将任务分发到由上千台商用机器组成的集群上,实现 Hadoop的并行任务处理功能。前面提过,HDFS和 MapReduce二者相互作用,共同完成了 Hadoop分布式集群的主要任务。
在MapReduce中,一个存储在分布式文件系统中的大规模数据集会被切分成许多独立的小数据块,这些小数据块可以被多个Map任务并行处理。MapReduce框架会为每个Map任务输入一个数据子集,Map任务生成的结果会继续作为Reduce任务的输入,最终由Reduce 任务输出最后结果,并写入分布式文件系统。特别需要注意的是,适合用 MapReduce来处理的数据集需要满足一个前提条件:待处理的数据集可以分解成许多小的数据集,而且每一个小数据集都可以完全并行地进行处理。
MapReduce由两个阶段组成。
map():任务分解。
reduce():结果汇总。

MapReduce设计的一个理念就是“计算向数据靠拢”,而不是“数据向计算靠拢”,因为移动数据需要大量的网络传输开销,尤其是在大规模数据环境下,这种开销尤为惊人,所以,移动计算要比移动数据更加经济。本着这个理念,在一个集群中,只要有可能,MapReduce框架就会将Map程序就近地在HDFS数据所在的节点运行,即将计算节点和存储节点放在一起运行,从而减少了节点间的数据移动开销。
Hadoop框架是用Java实现的,但是 MapReduce应用程序则不一定要用Java来写。
7.1.3 MapReduce架构设计
与HDFS架构设计相似,在 Hadoop中,用于执行 MapReduce作业的机器也有两个角色:
 JobTracker:是一个Master服务(主服务),用于作业(Job)的管理和调度工作,一个 Hadoop集群中只有一台JobTracker,一般情况应该把它部署在单独的机器上。 JobTracker负责创建、调度作业中的每个子任务(Map Task或 Reduce Task)运行于TaskTracker上,并监控它们,如果发现有失败的任务就重新运行它。
 TaskTracker:是运行于多个节点上的Slave服务,用于执行任务。TaskTracker需要运行在HDFS的 DataNode上
基于Job Tracker和TaskTracker的运行架构为MapReduce V1,在第二代MapReduce V2中,V1架构已被YARN替代,关于YARN我们稍后会讲解。从学习的难易程度来看,应该先了解 MapReduce V1。不论是V1还是V2,都不会影响我们编写MapReduce程序,好比同样的一个Web应用,运行在 Tomcat与Jetty下的效果是相同的。由此可见,实际上运行 MapReduce作业的过程对开发人员是透明的。
7.1.4 MapReduce编程模型
我们现在举一个大数据人都知道的例子,用来形象地介绍MapReduce方法。我们现在有一本1000页的书,在这本书打印完毕后,老板想知道这本书一共使用了哪些字,每个字被使用了多少次。
首先,把数字书稿交给M小姐处理。她的任务蛮单调、简单,但是工作量很大,就是一字一字地读下去,每读到一个字,就将其记录到一个中间文件上。这个中间文件一般具体采用这么一个格式:(阅读到的字,1)。例如,Map小姐读到一个字“中”,就在文件中记录下(中,1)。接下来,她又读到一个字“国“,就又记录下来一笔(国,1)。其实很简单,前面表示阅读到的一个具体的字,后面的1表示这个字出现了一次。或者科班一点地说,前面是一个键,表示是哪一个;后边是一个值,表示相应的我们关心的数值。键与值构成一个键值对,共同表示我们感兴趣的一条记录。于是有一天, M小姐努力地完成了工作,提交了一个很枯燥的表达字的使用的键值对的数据文件,里面罗列了这本书里面字的每一次出现。键值对里面,用字本身来作为键,用1这个值来表示出现1次这个值。图7.2是这个数据键值表的一个片段,表达了“中国中学生”这些字的出现。
接下来,由R小姐接续处理M小姐提交的键值表。同样,她的工作也是蛮单调、简单,但是工作量也很大。她把文件里面出现的字及其这个字出现的总次数都记录下来。例如,上图的键值对会产出左图的结果。表示“中”这个字出现了两次。国这个字出现了一次,等等。 如此形成的文档,就是所期望的结果了。
下面我们介绍一下MapReduce程序的运行方式。
当编写完成MapReduce程序时,要配置为一个MapReduce作业(Job)。这里的“作业”可以理解为:为了进行一次分布式计算任务而编写 MapReduce程序后,将该程序提交到 MapReduce执行框架中并执行的全过程。
我们的作业是获得每个单词出现的次数,这就是一个Job。当客户端提交Job到JobTracker后,MapReduce数据流如图7.3所示。

由图7.3中可以看出,待处理数据从输入到最后输出经过五个阶段:

  1. input(输入):由JobTracker创建该Job,并根据Job的输入计算输入分片(Input Split)。这里要求待处理的数据集必须可以分解成许多小的数据集,且每个小的数据集都可以完全并行地进行处理。输入目录中文件的数量决定了分片的数量,如果单个文件超过HDFs默认块大小(64MB),则将按块大小进行分割。

  2. split(分片):作业调度器获取Job的输入分片信息,对输入分片中的记录按照一定规则解析成键值对。“键”(key)是每行的起始位置,以字节为单位,“值”(vaue)是本行文本内容,最后每个分片创建一个 MapTask并分配到某个 TaskTracker。

  3. map(映射):TaskTracker开始执行MapTask,处理输入的每个键值对。如何处理取决于在该阶段的程序代码,处理完成后产生新的键值对,保存在本地。

  4. shuffle(混洗) :将MapTask的输出转换为ReduceTask的输入的过程。从图7.3中可以看出该过程会在各 TaskTracker Node之间进行数据交换,按照key进行分组。

  5. reduce:读取Shuffling阶段的输出,开始执行ReduceTask,处理输入的每个键值对。同样如何处理取决于该阶段的程序代码。最后输出最终结果。
    在Hadoop中每个MapReduce计算任务都会被初始化为一个Job。其中主要有两个处理阶段:map阶段和 reduce阶段,两个阶段均以键值对作为输入,然后产生同样为形式的输出。两个阶段分别对应map()和 reduce()方法,这便是开发人员需要实现的两个最重要的阶段和方法,而其他阶段大多可由系统自动处理。
     每个map()方法负责计算一个输入分片并输出计算结果,由org.apache.hadoop. mapreduce.Mapper类提供。每个 MapTask都会创建一个Mapper实例。
     每个reduce()方法负责将多个map()的处理结果进行汇总,由org.apache.hadoop. mapreduce.Reducer类提供。每个 ReduceTask都会创建一个 Reducer实例。
    由以上分析可以看出一个MapReduce编程模型所做的工作是利用一个输入的键值对集合来产生一个输出的键值对集合。
    再从总体上来看,一个MapReduce作业在MapReduce框架中的工作原理如图7.4所示。

    从图3.2中可看出,一个 MapReduce作业的完整运行过程包括10个步骤:

  6. 编写 MapReduce程序,包括Mapper处理、 Reducer处理以及为执行这些处理而定义的作业,首先将所有这些程序打包后运行作业。

  7. 获取作业D

  8. 复制作业资源。

  9. 提交作业资源。

  10. 初始化作业。

  11. 获取输入分片。

  12. 心跳通信。TaskTracker通过运行一个简单的循环来定期发送“心跳”给JobTracker,表明Task Tracker是否还存活,同时也充当两者之间的消息通道。

  13. 获取作业资源。

  14. 分配任务。

  15. 运行任务 MapTask或 ReduceTask,最后输出 MapReduce任务处理结果。
    除了第一步由开发人员编码实现,其他步骤全部由 Hadoop MapReduce框架自动执行。换句话说编写一个 MapReduce程序,以下三个基本部分是我们需要重点关注的:
     MapTask程序:Mapper的实现。
     ReduceTask程序:Reducer的实现。
     Job相关配置。
    7.2伪分布式下运行内置的WordCount
    下面我们通过运行Hadoop自身封装的WordCount,明白mapreduce的运行原理。WordCount被誉为Hadoop中的HelloWorld。
    1.了解Hadoop官方的示例程序包
    在集群服务器的本地目录“$HADOOP_HOME/share/hadoop/mapreduce”中可以发现示例程序包hadoop-mapreduce-example-2.6.5.jar。这个程序包封装了一些常用的测试模板,内容如表所示。
    模板名称 内容
    multifilewc 统计多个文件中单词的数量
    pi 应用 quasI- Monte Carlo算法来估算圆周率π的值
    randomtextwriter 在每个数据节点随机生成一个10GB的文本文件
    wordcount 对输入文件中的单词进行频数统计
    wordmean 计算输入文件中单词的平均长度
    wordmedian 计算输入文件中单词长度的中位数
    wordstandarddeviation 计算输入文件中单词长度的标准差

2.提交MapReduce任务给集群运行
提交MapReduce任务,通常使用Hadoop jar命令。他的基本用法格式如下。
[root@hadoop Desktop]# hadoop jar [mainclass] args
下面结合实际任务,对它的各项参数依次说明
hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.6.5.jar
wordcount /input/test.txt /output
参考代码中的内容,针对 hadoop jar命令的常用参数做以下解释说明。

  1. H A D O O P H O M E : 指 主 机 中 设 置 的 环 境 变 量 ( 参 考 / e t c / p r o f i l e 文 件 内 容 ) 。 此 处 的 HADOOP_HOME:指主机中设置的环境变量(参考 /etc/profile文件内容)。此处的 HADOOPHOME/etc/profileHADOOP_HOME就是本地目录/usr/hadoop/hadoop-2.6.5
  2. hadoop-mapreduce-examples-2.6.5.jar: Hadoop官方提供的示例程序包,其中包括词频统计模块(wordcount)。
  3. wordcount:程序包中的主类名称。
  4. /input/test.txt:HDFS上的输入文件名称。
  5. /output:HDFS上的输出文件目录。注意这个目录不用创建,程序自己创建。

3.运行Wordcount

  1. 开启hadoop进程执行命令start-all.sh
    [root@hadoop ~]# start-all.sh

  2. 创建Hadoop HDFS目录,执行命令hadoop fs -mkdir
    [root@hadoop ~]# hadoop fs -mkdir /input
    [root@hadoop ~]# vim test.txt

  3. 在linux系统的/usr文件夹下创建一个文件test.txt ,执行命令vim test.txt。按“i”键进入编辑内容,在文件里输入内容,按ESC退出编辑状态,之后输入wq!保存并退出,如下图。

  4. 将linux系统/usr文件夹下的test.txt文件复制到Hadoop HDFS系统中/input目录下。
    [root@hadoop ~]# hadoop fs -put /usr/test.txt /input

  1. 执行hadoop-mapreduce -examples-2.6.5.jar中的WordCount功能包。
    到$HADOOP_HOME/share/hadoop/mapreduce目录下,执行命令。
    [root@hadoop ~]cd /usr/hadoop/hadoop-2.6.5/share/hadoop/mapreduce
    [root@Hadoop mapreduce] hadoop jar hadoop-mapreduce-examples-2.6.5.jar wordcount
    /input/test.txt /output
    注意:hdfs下的/output目录必须不存在,否则将无法执行。
    1 INFO client.RMProxy: Connecting to ResourceManager at hadoop/192.168.86.200:8032
    org.apache.hadoop.mapred.FileAlreadyExistsException: Output directory hdfs://hadoop:9000/output2 already exists
    查看执行结果,会发现任务执行完成后,显示以下信息。
    18/12/24 14:08:34 INFO client.RMProxy: Connecting to ResourceManager at hadoop/192.168.142.139:8032
    18/12/24 14:08:35 INFO input.FileInputFormat: Total input paths to process : 1
    18/12/24 14:08:35 INFO mapreduce.JobSubmitter: number of splits:1
    18/12/24 14:08:36 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1545640521748_0002
    18/12/24 14:08:38 INFO impl.YarnClientImpl: Submitted application application_1545640521748_0002
    18/12/24 14:08:38 INFO mapreduce.Job: The url to track the job: http://hadoop:8088/proxy/application_1545640521748_0002/
    18/12/24 14:08:38 INFO mapreduce.Job: Running job: job_1545640521748_0002
    18/12/24 14:08:56 INFO mapreduce.Job: Job job_1545640521748_0002 running in uber mode : false
    18/12/24 14:08:56 INFO mapreduce.Job: map 0% reduce 0%
    18/12/24 14:09:12 INFO mapreduce.Job: map 100% reduce 0%
    18/12/24 14:09:25 INFO mapreduce.Job: map 100% reduce 100%
    18/12/24 14:09:26 INFO mapreduce.Job: Job job_1545640521748_0002 completed successfully
    18/12/24 14:09:27 INFO mapreduce.Job: Counters: 49
    File System Counters
    FILE: Number of bytes read=80
    FILE: Number of bytes written=214897
    FILE: Number of read operations=0
    FILE: Number of large read operations=0
    FILE: Number of write operations=0
    HDFS: Number of bytes read=157
    HDFS: Number of bytes written=50
    HDFS: Number of read operations=6
    HDFS: Number of large read operations=0
    HDFS: Number of write operations=2
    Job Counters
    Launched map tasks=1
    Launched reduce tasks=1
    Data-local map tasks=1
    Total time spent by all maps in occupied slots (ms)=13473
    Total time spent by all reduces in occupied slots (ms)=9366
    Total time spent by all map tasks (ms)=13473
    Total time spent by all reduce tasks (ms)=9366
    Total vcore-milliseconds taken by all map tasks=13473
    Total vcore-milliseconds taken by all reduce tasks=9366
    Total megabyte-milliseconds taken by all map tasks=13796352
    Total megabyte-milliseconds taken by all reduce tasks=9590784
    Map-Reduce Framework
    Map input records=4
    Map output records=12
    Map output bytes=107
    Map output materialized bytes=80
    Input split bytes=98
    Combine input records=12
    Combine output records=6
    Reduce input groups=6
    Reduce shuffle bytes=80
    Reduce input records=6
    Reduce output records=6
    Spilled Records=12
    Shuffled Maps =1
    Failed Shuffles=0
    Merged Map outputs=1
    GC time elapsed (ms)=148
    CPU time spent (ms)=1680
    Physical memory (bytes) snapshot=305659904
    Virtual memory (bytes) snapshot=1683111936
    Total committed heap usage (bytes)=136122368
    Shuffle Errors
    BAD_ID=0
    CONNECTION=0
    IO_ERROR=0
    WRONG_LENGTH=0
    WRONG_MAP=0
    WRONG_REDUCE=0
    File Input Format Counters
    Bytes Read=59
    File Output Format Counters
    Bytes Written=50
    下面是hadoop jar执行MapReduce任务时的日志输出,其中的一些关键信息有助于检查执行的过程与状态。
    (1)job_1545469247951_0002:表示此项任务的ID,通常也被称为作业号。
    (2)18/12/22 17:55:14 INFO mapreduce.Job: map 0% reduce 0%:表示将开始Map操作。
    (3)18/12/22 17:55:29 INFO mapreduce.Job: map 100% reduce 0%:表示Map操件成功。
    (4)18/12/22 17:55:41 INFO mapreduce.Job: map 100% reduce 100%:表示Reduce操件成功。
    (5)18/12/22 17:55:44 INFO mapreduce.Job: Job job_1545469247951_0002 completed successfully:表示此作业成功完成。
    (6)Map input records=4:表示输入的记录共有4条。
    (7)Reduce output records=6:表示输出的结果共有6条。
    查看执行结果,会发现任务执行完成后,在输出目录/output/中有两个新文件生成:一个是_SUCCESS,这是一个标识文件,表示这个任务执行完成;另一个是part-r-00000任务执行完成后产生的结果文件。

5.将Hadoop HDFS文件系统中的part-r-00000文件复制到Linux系统中。
[root@hadoop Desktop]# hadoop fs -get /output/part-r-00000 /output/part-r-00000
显示出的part-r-00000内容如图所示

图中有两列数据,第一列是单词,第二列是统计单词的个数。
通过本机web浏览器,在资源管理器监控界面(http://192.168.142.139:8088/cluster/apps)中打开,可以看到执行的任务,执行任务的时间,任务的状态。

7.3 MapReduce应用开发
要编写MapReduce程序,首先要了解Hadoop的基本数据类型和MapReduce的输入/输出格式。
7.3.1 Hadoop数据类型
Hadoop基本数据类型如下:
 IntWritable:整型数
 LongWritable:长整型数
 FloatWritable:浮点数
 DoubleWritable:双字节数
 BooleanWritable:布尔型
 ByteWritable:单字节,对应byte类型
 BytesWritable:字节数组,对应byte[]
Hadoop常用的其他数据类型如下:
 Text:使用UTF8格式存储的文本,对String类型的重写
 ObjectWritable:是一种对多类型的封装,可以用于Java的基本类型,如String等,如同Java的Object类
 NullWritable:是一个点位符,序列化长度为零,当中的key或value为空时使用
 ArrayWritable:针对数组的数据类型
 MapWritable:对java.util.Map的实现
Hadoop数据类型都实现了Writable接口,并且与Java类型之间可相互转化。

  1. Java类型转Hadoop类型
    // 方法1:
    IntWritable num= new IntWritable(1);
    Text sss = new Text(“hello world!”);

// 方法2
IntWritable num= new IntWritable();
num.set(2);
Text sss = new Text();
sss.set(“hello world!”);

  1. Hadoop类型转Java类型
    对于Text类型
    Text sss = new Text();
    sss.set(“hello world!”);
    sss.toString();
    对于除了Text的类型
    IntWritable aaa = new IntWritable(123);
    aaa.get();

既然Hadoop基本数据类型与Java基本类型之间是互通的,为什么不直接使用Java基本数据型,而是重新定义一套数据类型呢?
在Hadoop中,序列化处于核心地位。无论是存储文件还是在计算中传输数据,都需要执行序列化的过程。序列化与反序列化的速度、序列化后的数据大小都会影响数据传输的速度,以致影响计算的效率。正是因为如此,Hadoop并没有采用Java提供的序列化机制,而是重新写了一套,由此所有的Hadoop基础数据类型都实现了org.apache.hadoop.io.Writable接口。
Writable接口只定义了两个方法:
 void write(java.io.DataOutput out); // 序列化输出数据流
 void readFields(java.io.DataInput input); // 序列化输入数据流

下面我们使用Hadoop基本数据类型的使用,代码如下。
import org.apache.hadoop.io.ArrayWritable;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.MapWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;

public class HadoopDataType {

// String
public static void testString(){
	String s = "Hello Jiang Tao";
	System.out.println(s.length());
	System.out.println(s.indexOf("a"));
	System.out.println(s);
}

// Text
public static void testText(){
	Text t = new Text("Hello Jiang Tao");
	System.out.println(t.getLength());
	System.out.println(t.find("a"));
	System.out.println(t.toString());
}

// ArrayWritable
public static void testArrayWritable(){
	// 整形数组
	ArrayWritable arr = new ArrayWritable(IntWritable.class);
	// 整形
	IntWritable i = new IntWritable(2);
	IntWritable j = new IntWritable(3);
	IntWritable k = new IntWritable(4);
	// 将多个整形放入整形数组
	arr.set(new IntWritable[]{i,j,k});
	System.out.println(String.format("i=%d,j=%d,k=%d", 
		((IntWritable)arr.get()[0]).get(),
		((IntWritable)arr.get()[1]).get(),
		((IntWritable)arr.get()[2]).get()
	));
}

// MapWritable
public static void testMapWritable(){
	// map
	MapWritable map = new MapWritable();
	// 字符串
	Text k1 = new Text("name");
	Text v1 = new Text("jason");
	Text k2 = new Text("password");
	// 填充map
	map.put(k1, v1);
	map.put(k2, NullWritable.get());
	System.out.println(map.get(k1).toString());
	System.out.println(map.get(k2).toString());
}

public static void main(String[] args) {
	testString();
	testText();
	testArrayWritable();
	testMapWritable();	
}

}
以上代码创建的项目是Java Project,需要使用的jar文件有。
 hadoop-common-2.6.5.jar
 commons-collections-3.2.2.jar
 commons-logging-1.1.3.jar
 guava-11.0.2.jar
Hadoop的数据类型均定义在hadoop-common-2.6.5.jar,而该jar文件又依赖其他3个jar文件。

第8章
MapReduce实验:单词计数

8.1 实验目的
基于 Mapreduce思想,编写WordCount程序
8.2实验要求
8.2.1理解 MapReduce编程思想
8.2.2会编写WordCount
8.2.3会执行该程序
8.3实验原理
MapReduce是一种计算模型,简单地说就是将大批量的工作(数据)分解执行,然后再将结果合并成最终结果,这样做的好处是可以在任务被分解后,可以通过大量机器进行并行计算,减少整个操作的时间。
适用范围:数据量大,但是数据种类少可以放入内存。
基本原理及要点:将数据交给不同的机器去处理,数据划分,结果归约。
理解 MapReduce和YARN:在新版 Hadoop中,YARN作为一个资源管理调度框架,是 Hadoop下 MapReduce程序运行的生存环境,其实 MapReduce除了可以运行YARN框架下,也可以运行在诸如 Mesons,Corona之类的调度框架上,使用不同的调度框架,需要针对 Hadoop做不同的适配。
一个完成的 MapReduce程序在YARN中执行过程如下:

 ResourceManager: 全局资源管理和任务调度
 NodeManager: 单个节点的资源管理和监控
 ApplicationMaster: 单个作业的资源管理和任务监控
 Container: 资源申请的单位和任务运行的容器

  1. ResourceManager Job Client向 ResourceManager提交一个Job;
  2. ResourceManager找到一个供App Master运行的Container,然后启动它;
  3. App Master启动起来后向 ResourceManager注册;
  4. ResourcManager Job Client向 ResourceManager获取到 App Master相关的信息,然后直接与App Master进行通信
  5. App Master运行起来后能够做什么依赖于应用本身。有可能是在所处的容器中简单的运行一个应用,并将结果返回返回给客户端;或是向ResourceManager请求更多的Container获得App Master,以运行一个分布式应用。

MapReduce运行步骤如下:

  1. 客户端要编写好mapreduce程序,配置好mapreduce的作业也就是job,接下来就是提交job了,提交job是提交到JobTracker上的,这个时候JobTracker就会构建这个job,具体就是分配一个新的job任务的ID值。
  2. 做检查操作,这个检查就是确定输出目录是否存在,如果存在那么job就不能正常运行下去(注意:运行前提是输出目录是否存在),JobTracker会抛出错误给客户端。接下来检查输入目录是否存在,如果不存在同样抛出错误,如果存在JobTracker会根据输入计算输入分片(Input Split),如果分片计算不出来也会抛出错误。这些都做好了JobTracker就会配置Job需要的资源了。
    注意:JobTracker是一个master服务,软件启动之后JobTracker接收Job,负责调度Job的每一个子任务task运行于TaskTracker上,并监控它们,如果发现有失败的task就重新运行它。一般情况应该把JobTracker部署在单独的机器上。
    JobTracker 对应于 NameNode,TaskTracker 对应于 DataNode
  3. 分配好资源后,JobTracker就会初始化作业,初始化主要做的是将Job放入一个内部的队列(queue),让配置好的作业调度器能调度到这个作业,作业调度器会初始化这个Job,初始化就是创建一个正在运行的job对象(封装任务和记录信息),以便JobTracker跟踪job的状态和进程。
  4. 初始化完毕后,作业调度器会获取输入分片信息(input split),每个分片创建一个map任务。接下来就是任务分配了,这个时候tasktracker会运行一个简单的循环机制定期发送心跳给jobtracker,心跳间隔是5秒,程序员可以配置这个时间,心跳就是jobtracker和tasktracker沟通的桥梁,
  5. 通过心跳,jobtracker可以监控tasktracker是否存活,也可以获取tasktracker处理的状态和问题,同时tasktracker也可以通过心跳里的返回值获取jobtracker给它的操作指令。任务分配好后就是执行任务了。在任务执行时候jobtracker可以通过心跳机制监控tasktracker的状态和进度,同时也能计算出整个job的状态和进度,而tasktracker也可以本地监控自己的状态和进度。
  6. 当jobtracker获得了最后一个完成指定任务的tasktracker操作成功的通知时候,jobtracker会把整个job状态置为成功,然后当客户端查询job运行状态时候(注意:这个是异步操作),客户端会查到job完成的通知的。如果job中途失败,mapreduce也会有相应机制处理,一般而言如果不是程序员程序本身有bug,mapreduce错误处理机制都能保证提交的job能正常完成。

8.4 实验步骤
8.4.1创建项目
打开eclipse,新建一个工程。“file” ->“New” ->“other”,选择“Map/Reduce Project”,输入工程名(自定义)

新建三个class文件(WordCountMapper,WordCountReduce,WordCountJob)
(1)选择src,单击右键,选择“New”→“ Package”,输入包名,点击“Finish”,如图所示

(2)选择包名“wordcount”,点击右键,选择“New”→“Class”,输入类名,点击“Finish”,如图所示

8.4.2 编写程序
1.输入格式
数据输入格式(InputFormat)用于描述MapReduce作业的数据输入规范。MapReduce框架依靠输入格式完成输入规范检查、对数据文件进行输入分块,以及提供从输入分块中将数据记录逐一读出,并转换为map阶段的输入键值对等功能。Hadoop提供了丰富的内置数据输入格式,其中常用的包括文本输入格式(TextInputFormat),键值对输入格式(KeyValue TextInputFormat),它们都是 FileInputFormat的子类。
(1). TextInputFormat是默认的数据输入格式,可将文本文件分块并逐行读入,最后转化为Mapper能够处理的形式的键值对。每读入一行,所产生的key为当前行在整个文本文件中的字节偏移位置,而value就是该行的内容。
例如:某分片输入数据如下
hello hadoop
hello jiangtao
hello is me
通过TextInputFormat可得到数据集:{<0,hello hadoop>,<12,hello jiangtao>,<26,hello is me>}.其中第一行在整个文本文件中的字节偏移位置为0,第二行从12(第一行为0~11) 开始,得到的keyvale的输入类型分别是 LongWritable和Text。
(2).KeyValueTextInputFormat可将一个按照形式逐行存放的文本文件逐行读出
自动解析生成相应的key和value。
例如,上面的分片输入数据通过 KeyValueTextInputFormat输入可得到数据集{},得到的key和value的输入类型分别是Text和Text。
对于每种数据输入格式,都必须依靠记录读取器(RecordReader)才能将一行数据记录分解为具体的键值对,并传送给map过程作为键值对输入参数。上面两个输入格式的别RecordReader分别是:
TextInputFormat的记录读取器:LineRecordReader类
KeyValueTextInputFormat的记录读取器:KeyValueLineRecordReader类
2.输出格式
数据输出格式(OutputFormat)提供将文件写在本地磁盘或HDFS上的功能。如果不指定输出格式,默认的实例为 TextOutputFormat,继承自 FileOutputFormat类。
TextOutputFormat以键值对的方式把数据写入一个文本文件,默认情况下key和value的类型分别是 LongWritable和Text。之后的学习中,在配置 MapReduce作业时,大家应该注意到这点。
每个运行的ReduceTask会把结果输出到指定文件目录下,生成文件名类似part-r-形式,其中""由已启动的 ReduceTask的数量决定,代表了与其关联的某个ReduceTask的分区ID。
同样,指定了输出格式之后,还必须指定数据输出目录,可通过FileOutputFormat. setOutputPath()设置。
MapReduce读写文本文件步骤:
由MapReduce的编程模型的分析可以看出,一个MapReduce程序的开发需要以下三步操作。

  1. 编写Mapper实现类用于执行MapTask
  2. 编写Reducer实现用于执行ReduceTask
  3. 创建Job,主要是为Job指定Mapper和Reducer,还包括作业配置、作业提交
    下面通过介绍MapReduce API来看如何编写它们。
  1. Mapper类
    一个Mapper应该继承于Hadoop提供的Mapper类(org.apache.hadoop.mapreduce.Mapper),并重写map()方法。Mapper类是一个泛型类型,它有四个形参类型,其类定义关键代码如下:
    public class Mapper{
    protected void map(KEYIN key, VALUEIN value, Context context){
    context.write( (KEYOUT) key, (VALUEOUT) value );
    }
    //省略其他方法定义
    }
     KEYIN:表示输入键类型
     VALUEIN:表示输入值类型
     KEYOUT:表示输出键类型
     VALUEOUT:表示输出值类型
    mapper类实现:
    package wordcount;
    import java.io.IOException;
    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.io.LongWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Mapper;
    /*
  • KEYIN:输入kv数据对中key的数据类型

  • VALUEIN:输入kv数据对中value的数据类型

  • KEYOUT:输出kv数据对中key的数据类型

  • VALUEOUT:输出kv数据对中value的数据类型
    /
    public class WordCountMapper extends Mapper{
    /

    • map方法是提供给map task进程来调用的,map task进程是每读取一行文本来调用一次我们自定义的map方法
    • map task在调用map方法时,传递的参数:
    • 一行的起始偏移量LongWritable作为key
    •  一行的文本内容Text作为value
      

    */
    @Override
    protected void map(LongWritable key, Text value,Context context) throws IOException, InterruptedException {
    //拿到一行文本内容,转换成String 类型
    String line = value.toString();
    //将这行文本切分成单词
    String[] words=line.split(" ");

     //输出<单词,1>
     for(String word:words){
     	context.write(new Text(word), new IntWritable(1));
     }
    

    }
    }
    2.Reducer类
    同样地,一个Reducer类应该继承于Hadoop提供的Reducer类(org.apache.hadoop.mapreduce.Reducer),并重写reduce()方法。Reducer类也是一个泛型类型,同样有四个形参类型用于指定输入和输出类型。其类定义的关键代码如下:
    public class Reducer{
    protected void reduce(KEYIN key,Iterable values,Context context){
    for(VALUEIN value:values){
    context.write( (KEYOUT)key,(VALUEOUT)value);
    }
    }
    // 省略其他方法定义
    }
    四个形参类型分别指定了reduce()方法的输入键、输入值、输出键、输出值的类型。如下例所示,WordCountReducer类指定了 reduce()方法的输入键类型为Text,输入值类型为 IntWritable,输出键类型为Text,输出值类型为 IntWritable。需要特别注意的是 reduce()方法的输入类型必须匹配 Mapper中map()方法的输出类型。
    reducer类实现:
    package wordcount;
    import java.io.IOException;
    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Reducer;

/*

  • KEYIN:对应mapper阶段输出的key类型

  • VALUEIN:对应mapper阶段输出的value类型

  • KEYOUT:reduce处理完之后输出的结果kv对中key的类型

  • VALUEOUT:reduce处理完之后输出的结果kv对中value的类型
    /
    public class WordCountReducer extends Reducer{
    @Override
    /

    • reduce方法提供给reduce task进程来调用
    • reduce task会将shuffle阶段分发过来的大量kv数据对进行聚合,聚合的机制是相同key的kv对聚合为一组
    • 然后reduce task对每一组聚合kv调用一次我们自定义的reduce方法
    • 比如:
    • hello组会调用一次reduce方法进行处理,tom组也会调用一次reduce方法进行处理
    • 调用时传递的参数:
    •  key:一组kv中的key
      
    •  values:一组kv中所有value的迭代器
      

    */
    protected void reduce(Text key, Iterablevalues,Context context) throws IOException, InterruptedException {
    //定义一个计数器
    int count = 0;
    //通过value这个迭代器,遍历这一组kv中所有的value,进行累加
    for(IntWritable value:values){
    count+=value.get();
    }

     //输出这个单词的统计结果
     context.write(key, new IntWritable(count));
    

    }
    }

3.Job类
通过前面的知识我们知道,一个Job提交到JobTracker后会顺序经过 Input、split、map、shuffle这些阶段,并且Job一旦提交后,会进入完全地自动化执行过程。在这个过程中,用户除监控作业的执行情况和强制中止作业之外,不能进行其他任何干预。因此在Job提交之前,用户将所有应该配置的参数配置完毕。
Job对象指定作业执行规范,可以用它来控制整个作业的运行。创建、配置作业的主要步骤如下:
(1) 创建作业和基本配置。
Job job = new Job(conf,“word count”); //创建作业
//或者
Job job = Job.getInstance (conf, “word count” ); //创建作业

job.setJarByClass(WordCountJob.class); //指定jar文件
job.setNumReduceTasks(2); //指定ReduceTask数量
需要特别注意的是语句“job.setJarByClass(WordCountJob.class)”,表示查找包含WordCountJob.class的jar文件。从该jar文件中应能够找到定义的 Mapper和 Reducer类,实际上执行“hadoop jar xxx. jar”命令时首先从“xxx.jar”文件中查找,如果找不到则在JVM(java虚拟机)的 classpath中继续查找.找到后该jar文件会被上传到集群缓存中并重命名为job.jar,仍未找到则不会上传,到此作业提交已结束。作业被 JobTracker调度后,TaskTracker节点在执行 MapReduce任务时会在job.jar中搜索自定义的Mapper和 Reducer类,如果未找到则抛出 ClassNotFoundException的异常。
经过上面的分析,可以得出结论,最终要保证TaskTracker在执行MapTask或ReduceTask时能够找到到我们所定义的 Mapper和 Reducer类。可以通过两种策略指定它们的位置:
 通过setJarByClass(Class clazz)方法,适用于 Mapper、Reducer类与Job定义在同一jar文件中时,这是推荐用法。当它们不在同一jar文件中,但如果 Mapper, Reducer类所在jar文件位于当前JVM的 classpath中,也可用此方法
 通过setJar(String jarFile)方法,jarFile为绝对路径,适用于 MapPer, Reducer与Job定义在不同jar文件中且不在当前JVM的 classpath中
(2) 设置 Mapper和 Reducer类型
job. setMapperclass(WordCountMapper.class); // 指定Mapper
job. setReducerclass(WordCountReducer.class); // 指定Reducer
(3) 输入/输出文件格式。
在下面的示例中,我们没有设置输入/输出文件格式,前面提到 Hadoop默认为 TextInputFormat和TextOutputFormat。
在 TextInputFormat中,每个输入分片都会单独作为Mapper的输入,之后每行数据都会生成一条记录并表示为形式。如果需要更改输入格式,可以通过Job对象的 setInputFormatClass()方法来设置,而输出格式使用setOutputFormatClass()方法设置。
(4) 输出类型。
job. setOutputKeyClass(Text.class); //指定输出key的类型
job. setOutputValueClass(IntWritable.class); //指定输出value的类型
上面两行代码同时指定了Mapper和 Reducer的key,value输出类型,若 Mapper和 Reducer的key、value出型不相同,便需要单独指定Map的输出类型。比如 Mapper输出,而Reducer输出时,需使用如下设置:
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
具体在使用时必须对照Mapper和Reducer的实际形参类型来设置。如果不指定输出类型,那么TextOutputFormat会以输出。需要注意的是,这里只提到输出类型,因为对于输入类型已经由输入格式(InputFormatClass)决定。
(5) 输入/输出数据的路径。
FileInputFormat. addInputPath(job, new Path(“xxx”)); //输入数据文件
FileOutputFormat.setOutputPath(job,new Path(“xxx”)); //输出目录
至此,作业已满足运行的基本条件,使用job.waitForCompletion(true)将作业提交到 MapReduce执行框架中使其开始自动运行。其返回布尔值表示是否执行成功,方法参数"true"表示将运行状态进度更新反馈给客户端,否则仅等待作业结束。
job提交客户端实现:
package wordcount;
import java.io.IOException;
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.output.FileOutputFormat;

public class WordCountJob {

public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
	//实例化配置
	Configuration conf = new Configuration();
	//创建作业
	Job wordCountJob = Job.getInstance(conf);
	//重要:指定本job所在的jar包
	wordCountJob.setJarByClass(WordCountJob.class);
	
	//设置wordCountJob所用的mapper逻辑类为哪个类
	wordCountJob.setMapperClass(WordCountMapper.class);
	//设置wordCountJob所用的reducer逻辑类为哪个类
	wordCountJob.setReducerClass(WordCountReducer.class);
	
	//设置map阶段输出的kv数据类型
	wordCountJob.setMapOutputKeyClass(Text.class);
	wordCountJob.setMapOutputValueClass(IntWritable.class);
	
	//设置最终输出的kv数据类型
	wordCountJob.setOutputKeyClass(Text.class);
	wordCountJob.setOutputValueClass(IntWritable.class);
	
	//设置要处理的文本数据所存放的路径
	//FileInputFormat.setInputPaths(wordCountJob, new Path("D:\\hadoopprjs\\wordcount\\test.txt"));
	FileInputFormat.setInputPaths(wordCountJob, new Path("hdfs://192.168.11.128:9000/wordcount/test.txt"));
	//FileOutputFormat.setOutputPath(wordCountJob,new Path("D:\\hadoopprjs\\wordcount\\output"));
	FileOutputFormat.setOutputPath(wordCountJob,new Path("hdfs://192.168.11.128:9000/wordcount/output/"));		
	//提交job给hadoop集群
	wordCountJob.waitForCompletion(true);
}

}

[root@hadoop mapreduce]# hadoop fs -mkdir /wordcount
8.4.3创建文本文件

[root@hadoop mapreduce]# hadoop fs -put /usr/test.txt /wordcount/test.txt
8.4.4本地测试
(1)先创建本地路径“d:/wordount”,如图所示

(2)把8.4.3的文本放到“d:/wordcount”下

(3)在类WordCountJob中把“FileInputFormat.setInputPaths(wordCountJob, “hdfs://192.168.11.128:9000/wordcount/test.txt”)”的路径改成本地的,如““D:\wordcount\test.txt””。把“FileOutputFormat.setOutputPath(wordCountJob,new Path(“hdfs://192.168.11.128:9000/wordcount/output/”));”的路径改成本地的,如“D:\wordcount\output”
(4)在类WordCountJob中运行工程,如图所示

(5)控制台没有报错,在“D:/wordcount/output”下查看,有4个文件,说明本地运行成功

(6)查看part-r-00000内容,是不是要求的,如图所示

注意:

  1. 程序自动生成目录/output目录,必须保证/output目录不存在。
  2. 当执行hdfs访问方式时,产生如下错误
    org.apache.hadoop.security.AccessControlException: Permission denied: user=fd, access=WRITE, inode="/input/fd.txt":root:supergroup:drwxr-xr-x
    需要在Linux的$HADOOP_HOME/etc/hadoop目录下的 hdfs-site.xml中添加如下参数:

    dfs.permissions
    false

    如果是 true,则打开前文所述的权限系统。如果是 false,权限检查就是关闭的,但是其他的行为没有改变。所以必须设置dfs.permissions属性为false。这个配置参数的改变并不改变文件或目录的模式、所有者和组等信息。
  3. 修改hdfs-site.xml文件后,需要重启Hadoop。
  4. 执行程序时为了显示更多信息,需要将log4j.properties文件复制到src目录下。
    8.4.5项目生成jar包
    (1)选择项目名“wordcount”,点击右键,选择“Export”,如图所示

(2)选择“java” -->“Jar File”,如图所示

(3)点击“Browse”,选择要导出的路径,在文件名里输入名字,然后点击“保存”,如图所示

(4)再点击两次“next”,如图所示

(5)“Main class”是指哪个类有main方法,点击“Browse”,出来一个“Select Main Class”框,双击点击“WordCountJob”,然后点击“Finish”,如图所示

将jar包上传到linux上的/usr目录下。
8.4.6启动hadoop集群
执行命令start-all.sh
[root@hadoop ~]start-all.sh
8.4.7将文本文件上传到HDFS
(1)先把文本从本地上传到linux(/input)上
(2)在hdfs上创建/wordcount文件夹,执行命令hdfs dfs -mkdir /wordcount
(3)再从linux上传到hdfs上,执行命令
hdfs dfs -put /input/test.txt hdfs://192.168.11.128:9000/wordcount
8.4.8运行jar文件
在Linux的/usr目录下,执行命令。
hadoop jar wordcount.jar wordcount.WordCountJob

8.4.9查看结果
[root@hadoop2 sbin]# hadoop dfs -ls /wordcount/output

[root@hadoop2 sbin]# hadoop dfs -cat /wordcount/output/part-r-00000

第9章
MapReduce实验:二次排序

9.1实验目的
基于wordcount的思想,编写SecondarySort程序。
9.2实验要求
理解 MapReduce编程思想
编写MapReduce的二次排序程序
9.3实验原理
MapReduce框架对处理结果的输出会根据key值进行默认的排序,这个默认排序可以满足一部分需求,但是也是十分有限的,在我们实际的需求当中,往往有要对reduce输出结果进行二次排序的需求。对于二次排序的实现,网络上已经有很多人分享过了,但是对二次排序的实现原理及整个MapReduce框架的处理流程的分析还是有非常大的出入,而且部分分析是没有经过验证的。本文将通过一个实际的MapReduce二次排序的例子,讲述二次排序的实现和其MapReduce的整个处理流程,并且通过结果和Map、Reduce端的日志来验证描述的处理流程的正确性。
运行效果如下:
1.输入数据

  1. sort1 1
  2. sort2 3
  3. sort2 88
  4. sort2 54
  5. sort1 2
  6. sort6 22
  7. sort6 888
  8. sort6 58
  9. 目标输出
  10. sort1 1,2
  11. sort2 3,54,88
  12. sort6 22,58,888
  13. 具体解决思路
    (1). Map端处理
    根据上面的需求,我们有一个非常明确的目标就是要对第一列相同的记录,并且对合并后的数字进行排序。我们都知道MapReduce框架不管是默认排序或者是自定义排序都只是对key值进行排序,现在的情况是这些数据不是key值,怎么办?其实我们可以将原始数据的key值和其对应的数据组合成一个新的key值,然后新的key值对应的value还是原始数据中的value。那么我们就可以将原始数据的map输出变成类似下面的数据结构:
  14. {[sort1,1],1}
  15. {[sort2,3],3}
  16. {[sort2,88],88}
  17. {[sort2,54],54}
  18. {[sort1,2],2}
  19. {[sort6,22],22}
  20. {[sort6,888],888}
  21. {[sort6,58],58}
    那么我们只需要对[]里面的新key值进行排序就OK了,然后我们需要自定义一个分区处理器,因为我的目标不是想将新key相同的记录传到一个reduce中,而是想将新key中第一个字段相同的记录放到同一个reduce中进行分组合并,所以我们需要根据新key值的第一个字段来自定义一个分区处理器。通过分区操作后,得到的数据流如下:
  22. Partition1:{[sort1,1],1}、{[sort1,2],2}
  23. Partition2:{[sort2,3],3}、{[sort2,88],88}、{[sort2,54],54}
  24. Partition3:{[sort6,22],22}、{[sort6,888],888}、{[sort6,58],58}
    分区操作完成之后,我调用自己的自定义排序器对新的key值进行排序。
  25. {[sort1,1],1}
  26. {[sort1,2],2}
  27. {[sort2,3],3}
  28. {[sort2,54],54}
  29. {[sort2,88],88}
  30. {[sort6,22],22}
  31. {[sort6,58],58}
  32. {[sort6,888],888}
    (2). Reduce端处理
    经过Shuffle处理之后,数据传输到Reducer端了。在Reducer端按照组合键的第一个字段进行分组,并且每处理完一次分组之后就会调用一次reduce函数来对这个分组进行处理和输出。最终各个分组的数据结果变成类似下面的数据结构:
  33. sort1 1,2
  34. sort2 3,54,88
  35. sort6 22,58,888

9.4实验步骤
9.4.1创建项目
(1)打开eclipse,新建一个工程。“file” ->“New” ->“other”,select a wizard中选择“Map/Reduce Project”,输入工程名(自定义)

9.4.2编写程序
(1)选择src,单击右键,选择“New”→“ Package”,输入包名,点击“Finish”,如图所示

(2)选择包名“MySort”,点击右键,选择“New”→“Class”,输入类名,点击“Finish”,如图所示

自定义Bean实现:
package MySort;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.WritableComparable;
/*

  • name money
  • 自定义key一般实现writableComparable为了实现Shuffle过程的分区,排序,合并
    */
    public class SortBean implements WritableComparable{
    private String name;
    private int age;

public SortBean() {
}
public SortBean(String name, int age) {
super();
this.name = name;
this.age = age;
}
public void set (String name,int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public void write(DataOutput out) throws IOException {
out.writeUTF(name);
out.writeInt(age);
}
@Override
public void readFields(DataInput in) throws IOException {
this.name=in.readUTF();
this.age=in.readInt();
}
@Override
public int compareTo(SortBean o) {
//比较第一个字段即name字段
int comp = this.name.compareTo(o.getName());
if(0 != comp){
return comp;
}
//比较第二个字段即age字段
return Integer.valueOf(o.getAge()).compareTo(Integer.valueOf(this.age));
}
Map端实现:
package MySort;

import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;/*

  • KEYIN:输入kv数据对中key的数据类型
  • VALUEIN:输入kv数据对中value的数据类型
  • KEYOUT:输出kv数据对中key的数据类型
  • VALUEOUT:输出kv数据对中value的数据类型
    */
    public class SecondaryMapper extends Mapper<
    LongWritable,Text, SortBean, IntWritable> {
    private SortBean mapOutputKey = new SortBean();
    private IntWritable mapOutputValue = new IntWritable();
    @Override
    protected void map(LongWritable key, Text value, Context context)
    throws IOException, InterruptedException {
    //切分每行数据转化成字符串数组
    String[] strs = value.toString().split("\t");
    //设置输出的对象
    mapOutputKey.set(strs[0], Integer.valueOf(strs[1]));
    mapOutputValue.set(Integer.valueOf(strs[1]));
    context.write(mapOutputKey, mapOutputValue);
    }
    }

Reduce端实现:
package MySort;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
/*

  • KEYIN:对应mapper阶段输出的key类型
  • VALUEIN:对应mapper阶段输出的value类型
  • KEYOUT:reduce处理完之后输出的结果kv对中key的类型
  • VALUEOUT:reduce处理完之后输出的结果kv对中value的类型
    */
    public class SecondaryReducer extends Reducer{

private Text outputKey = new Text();
@Override
protected void reduce(SortBean key, Iterablevalues,
Context context) throws IOException, InterruptedException {
//value通过遍历values获取
for(IntWritable value :values){
outputKey.set(key.getName());
context.write(outputKey, value);
}

}

}

job提交客户端实现:
package MySort;

import java.io.IOException;
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.output.FileOutputFormat;

/* 自定义排序驱动程序
*/
public class SecondaryJob {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
Job job=Job.getInstance(conf);
//重要:指定本job所在的jar包
job.setJarByClass(SortBean.class);

		//设置MySortJob所用的mapper逻辑类为哪个类
		job.setMapperClass(SecondaryMapper.class);
		//设置wordCountJob所用的reducer逻辑类为哪个类
		job.setReducerClass(SecondaryReducer.class);
		//设置wordCountJob所用的bean逻辑类为哪个类
		job.setOutputKeyClass(SortBean.class);
		//设置map阶段输出的kv数据类型
		job.setMapOutputKeyClass(SortBean.class);
		job.setMapOutputValueClass(IntWritable.class);

		//设置最终输出的kv数据类型
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(IntWritable.class);
	
		//设置要处理的文本数据所存放的路径
		FileInputFormat.setInputPaths(job,new Path("hdfs:// 192.168.11.128:9000/sort/sort.txt"));
        FileOutputFormat.setOutputPath(job,new Path("hdfs:// 192.168.11.128:9000/sort/output/"));

		//提交job给hadoop集群
		job.waitForCompletion(true);
    }

}

(3).将log4j.properties文件复制到src目录下,这样可以显示更多的错误信息。
9.4.3创建文本文件
注意:输入完名字后需要敲TAB键再输入年龄。

9.4.4 本地测试
(1)先创建本地路径“d:/sort”,如图所示

(2)把9.4.4的文本放到“d:/sort”下

(3)在类WordCountJob中把“FileInputFormat.setInputPaths(job,new path(“hdfs://192.168.11.128:9000/sort/sort.txt”)”的路径改成本地的,如““D:\sort\sort.txt””。把“FileOutputFormat.setOutputPath(job,new Path(“hdfs://192.168.11.128:9000/sort/output/”));”的路径改成本地的,如“D:\sort\output”。
(4)在类WordCountJob中运行工程,如图所示

(5)控制台没有报错,在“D:/sort/output”下查看,有4个文件,说明本地运行成功

(6)查看part-r-00000内容,是不是要求的,如图所示

9.4.5项目生成jar包
(1)选择项目名“MySort”,点击右键,选择“Export”,如图所示

(2)选择“java”文件夹 -->“Java File”,如图所示

(3)点击“Browse”,选择要导出的路径,在文件名里输入名字,然后点击“保存”,如图所示

(4)再点击两次“next”,如图所示

(5)“Main class”是指哪个类有main方法,点击“Browse”,出来一个“Select Main Class”框,双击点击“MySortJob”,然后点击“Finish”,如图所示

将jar包上传到linux上的/usr目录下。
9.4.6启动hadoop集群
执行命令start-all.sh
[root@hadoop ~]# start-all.sh
9.4.7将文本数据上传到hdfs上
(1)把文本文件sort.txt从本地上传到linux(/sortinput)上
(2)在hdfs上创建/sort文件夹,执行命令
[root@hadoop ~]# hadoop fs -mkdir /sort
(3)再从linux上传到hdfs上,执行命令
[root@hadoop ~]# hadoop fs -put /usr/sort.txt /sort
9.4.8运行jar文件
执行hadoop命令:
语法:hadoop jar Xxx.jar package.MainClass
hadoop jar MySort.jar MySort.Secondaryob

9.4.9查看结果
[root@hadoop2 sbin]# hadoop dfs -ls /wordcount/output
[root@hadoop2 sbin]# hadoop dfs -cat /wordcount/output/part-r-00000

第10章
MapReduce实验:Join操作

10.1实验的目的
基于MapReduce思想,编写两文件join操作的程序
10.2实验的要求
 理解 MapReduce编程思想
 编写MapReduce的Join程序
 学会Java main方法的args参数赋值
10.3实现的原理
10.3.1. 概述
在传统数据库(如:MYSQL)中,JOIN操作是非常常见且非常耗时的。而在HADOOP中进行JOIN操作,同样常见且耗时,由于Hadoop的独特设计思想,当进行JOIN操作时,有一些特殊的技巧。
本文首先介绍了Hadoop上通常的JOIN实现方法,然后给出了几种针对不同输入数据集的优化方法。
10.3.2. 常见的join方法介绍
假设要进行join的数据分别来自File1和File2.
10.3.2.1 reduce side join
reduce side join是一种最简单的join方式,其主要思想如下:
在map阶段,map函数同时读取两个文件File1和File2,为了区分两种来源的key/value数据对,对每条数据打一个标签(tag),比如:tag=0表示来自文件File1,tag=2表示来自文件File2。即:map阶段的主要任务是对不同文件中的数据打标签。
在reduce阶段,reduce函数获取key相同的来自File1和File2文件的value list,然后对于同一个key,对File1和File2中的数据进行join(笛卡尔乘积)。即:reduce阶段进行实际的连接操作。

下面我们来举一个reduce side join的例子。
我们现在有两个文件File1和File2,分别保存商品信息和订单信息(因为我们已经学习了MySQL数据库,所以我们对照数据库的join操作实现reduce side join),我们假设两个文件中分别存放两个表格。
File1(商品表)
商品编号 商品名称 价格 商品所属分类编号
1 小米音箱 300 2
2 华为P20 2900 2
3 华为MateBook 5600 3
File2(订单表)
订单编号 商品编号 数量
1001 1 5
1001 2 4
1002 2 5
1002 3 7
上面两个File最终通过MapReduce形成下面的文件。
订单编号 商品编号 商品名称 数量
1001 1 小米音箱 5
1001 2 华为P20 4
1002 2 华为P20 5
1002 3 华为MateBook 7
达到上面的效果,通过Map和Reduce两个步骤实现,过程如下:

  1. Map操作
    在File1前的每条记录前加flag0,File2前的每条记录前加flag1
    File1(商品表)
    标识 商品编号 商品名称 价格 商品所属分类编号
    Flag0 1 小米音箱 300 2
    Flag0 2 华为P20 2900 2
    Flag0 3 华为MateBook 5600 3
    File2(订单表)
    标识 订单编号 商品编号 数量
    Flag1 1001 1 5
    Flag1 1001 2 4
    Flag1 1002 2 5
    Flag1 1002 3 7

  2. Reduce操作
    将两个文件的每条记录做笛卡尔积,如果flag0商品编号等于flag1的商品编号,说明这个订单记录就是买的这个商品,就将这两条记录放到同一个List集合中。如上面表3所示的表格(这个表格相当于集合,每一条记录相当于集合中的一个元素),最后将这个集合进行输出。
    10.3.2.2 map side join
    之所以存在reduce side join,是因为在map阶段不能获取所有需要的join字段,即:同一个key对应的字段可能位于不同map中。Reduce side join是非常低效的,因为shuffle阶段要进行大量的数据传输。
    Map side join是针对以下场景进行的优化:两个待连接表中,有一个表非常大,而另一个表非常小,以至于小表可以直接存放到内存中。这样,我们可以将小表复制多份,让每个map task内存中存在一份(比如存放到hash table中),然后只扫描大表:对于大表中的每一条记录key/value,在hash table中查找是否有相同的key的记录,如果有,则连接后输出即可。
    为了支持文件的复制,Hadoop提供了一个类DistributedCache,使用该类的方法如下:
    (1)用户使用静态方法DistributedCache.addCacheFile()指定要复制的文件,它的参数是文件的URI(如果是HDFS上的文件,可以这样:hdfs://namenode:9000/home/XXX/file,其中9000是自己配置的NameNode端口号)。JobTracker在作业启动之前会获取这个URI列表,并将相应的文件拷贝到各个TaskTracker的本地磁盘上。(2)用户使用DistributedCache.getLocalCacheFiles()方法获取文件目录,并使用标准的文件读写API读取相应的文件。
    10.3.2.3 SemiJoin
    SemiJoin,也叫半连接,是从分布式数据库中借鉴过来的方法。它的产生动机是:对于reduce side join,跨机器的数据传输量非常大,这成了join操作的一个瓶颈,如果能够在map端过滤掉不会参加join操作的数据,则可以大大节省网络IO。
    实现方法很简单:选取一个小表,假设是File1,将其参与join的key抽取出来,保存到文件File3中,File3文件一般很小,可以放到内存中。在map阶段,使用DistributedCache将File3复制到各个TaskTracker上,然后将File2中不在File3中的key对应的记录过滤掉,剩下的reduce阶段的工作与reduce side join相同。
    10.3.2.4 reduce side join + BloomFilter
    在某些情况下,SemiJoin抽取出来的小表的key集合在内存中仍然存放不下,这时候可以使用BloomFiler以节省空间。
    BloomFilter最常见的作用是:判断某个元素是否在一个集合里面。它最重要的两个方法是:add() 和contains()。最大的特点是不会存在false negative,即:如果contains()返回false,则该元素一定不在集合中,但会存在一定的true negative,即:如果contains()返回true,则该元素可能在集合中。
    因而可将小表中的key保存到BloomFilter中,在map阶段过滤大表,可能有一些不在小表中的记录没有过滤掉(但是在小表中的记录一定不会过滤掉),这没关系,只不过增加了少量的网络IO而已。
    10.4实现的思路
    (1)定义bean
    把SQL执行结果中的各列封装成一个bean对象,实现序列化。
    bean中还要有一个另外的属性flag,用来标识此对象的数据是订单还是商品。
    (2)map处理
    map会处理两个文件中的数据,根据文件名可以知道当前这条数据是订单还是商品。
    对每条数据创建一个bean对象,设置对应的属性,并标识flag(0代表order,1代表product)
    以join的关联项“productid”为key,bean为value进行输出。
    (3)reduce处理
    reduce方法接收到pid相同的一组bean对象。
    遍历bean对象集合,如果bean是订单数据,就放入一个新的订单集合中,如果是商品数据,就保存到一个商品bean中。然后遍历那个新的订单集合,使用商品bean的数据对每个订单bean进行信息补全。
    这样就得到了完整的订单及其商品信息。
    10.5 Java main方法的args参数赋值
    (1)点击Run->Run Configuration,出现下面的窗口,如图所示

(2)切换到(x)=Arguments窗口,输入参数,如图所示

(3)点击Run运行即可。10.6实验步骤
10.6实验步骤
(1)打开eclipse,新建一个工程。“file” ->“New” ->“other”,select a wizard中选择“Map/Reduce Project”,输入工程名(自定义)

10.6.1创建四个class文件
(1)选择src,单击右键,选择“New”→“ Package”,输入包名,点击“Finish”,如图所示

(2)选择包名“MyJoin”,点击右键,选择“New”→“Class”,输入类名,点击“Finish”,如图所示

10.6.2编写程序
自定义Bean实现:
package MyJoin;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import org.apache.hadoop.io.Writable;

public class InfoBean implements Writable{

private String oid;//订单id
private String date;//订单日期
private String pid;//商品id
private int amount;//商品数量
private String pname;//商品名称
private int category_id;//商品类别
private int price;//商品价格

//0:订单信息 1:商品信息
private int flag;
//序列化方法
publicvoid write(DataOutput out) throws IOException {
out.writeUTF(oid);
out.writeUTF(date);
out.writeUTF(pid);
out.writeInt(amount);
out.writeUTF(pname);
out.writeInt(category_id);
out.writeInt(price);
out.writeInt(flag);
}
//反序列化方法
public void readFields(DataInput in) throws IOException {
this.oid=in.readUTF();
this.date=in.readUTF();
this.pid=in.readUTF();
this.amount=in.readInt();
this.pname=in.readUTF();
this.category_id=in.readInt();
this.price=in.readInt();
this.flag=in.readInt();
}
//初始化属性
public void setInfoBean(String oid, String date, String pid, int amount, String pname, int category_id, int price,
int flag) {
this.oid = oid;
this.date = date;
this.pid = pid;
this.amount = amount;
this.pname = pname;
this.category_id = category_id;
this.price = price;
this.flag = flag;
}
//重写toString方法,以便文件中展示
@Override
public String toString() {
return “oid=” + oid + “, date=” + date + “, pid=” + pid + “, amount=” + amount + “, pname=” + pname
+ “, category_id=” + category_id + “, price=” + price ;
}
//序列化必须有无参构造方法
public InfoBean() {
}
public String getOid() {
return oid;
}
public void setOid(String oid) {
this.oid = oid;
}
public String getDate() {
return date;
}
publicvoid setDate(String date) {
this.date = date;
}
public String getPid() {
return pid;
}
publicvoid setPid(String pid) {
this.pid = pid;
}
publicint getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public int getCategory_id() {
return category_id;
}
public void setCategory_id(int category_id) {
this.category_id = category_id;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getFlag() {
return flag;
}
public void setFlag(int flag) {
this.flag = flag;
}

}
Map端实现:
package MyJoin;

import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;

public class MapReduceJoinMapper extends Mapper{
InfoBean bean = new InfoBean();
Text text = new Text();
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String line = value.toString();
//获取切片
FileSplit inputSplit = (FileSplit)context.getInputSplit();
//获取文件名,根据文件名进行不同的处理
//订单文件名order.txt,商品文件名product.txt
String name = inputSplit.getPath().getName();
String pid = “”;
if(name.startsWith(“order”)){
String[] fields = line.split("\t");
pid = fields[2];
//订单文件
bean.setInfoBean(fields[0], fields[1], pid,
Integer.parseInt(fields[3]), “”, 0, 0, 0);
}else {
String[] fields = line.split("\t");
pid = fields[0];
//商品文件
bean.setInfoBean("", “”, pid,
0, fields[1], Integer.parseInt(fields[2]), Integer.parseInt(fields[3]), 1);
}
text.set(pid);
context.write(text, bean);

    }

}

Reduce端实现:
package MyJoin;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

public class MapReduceJoinReducer extends Reducer{
//传来的一组信息中只会有一个商品bean可能有多个订单bean
@Override
protected void reduce(Text key, Iterable beans,
Context context) throws IOException, InterruptedException {
//用来存储唯一的商品bean信息
InfoBean ProductBean = new InfoBean();
//用来存储多个订单信息的集合
ListorderList = new ArrayList();
for (InfoBean bean : beans) {
//判断是订单还是商品信息
int flag = bean.getFlag();
if(flag == 1){
//商品信息,只能是一个
try {
BeanUtils.copyProperties(ProductBean, bean);
} catch (Exception e) {
e.printStackTrace();
}
}else {
//订单信息,可能是多个
InfoBean orderBean = new InfoBean();
try {
BeanUtils.copyProperties(orderBean, bean);
orderList.add(orderBean);
} catch (Exception e) {
e.printStackTrace();
}
}
}
for (InfoBean orderBean : orderList) {
//多个订单信息中设置好对应的商品信息
orderBean.setPname(ProductBean.getPname());
orderBean.setCategory_id(ProductBean.getCategory_id());
orderBean.setPrice(ProductBean.getPrice());
context.write(orderBean, NullWritable.get());
}
}
}
job提交客户端实现:
package MyJoin;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
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.output.FileOutputFormat;

public class MapReduceJob {
public static void main(String[] args) throws Exception{
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);

job.setJarByClass(MapReduceJoinMapper.class);
//指定本业务job要使用的mapper,reducer业务类

job.setMapperClass(MapReduceJoinMapper.class);
job.setReducerClass(MapReduceJoinReducer.class);
//虽然指定了泛型,以防框架使用第三方的类型
//指定mapper输出数据的kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(InfoBean.class);

//指定最终输出的数据的kv类型
job.setOutputKeyClass(InfoBean.class);
job.setOutputValueClass(NullWritable.class);

//指定job输入原始文件所在位置
FileInputFormat.addInputPath(job, new Path(args[0]));
FileInputFormat.addInputPath(job, new Path(args[1]));
//指定job输入原始文件所在位置
FileOutputFormat.setOutputPath(job,new Path(args[2]));
//将job中配置的相关参数以及job所用的java类所在的jar包,提交给yarn去运行
boolean b = job.waitForCompletion(true);
System.exit(b?0:1);
}
}

10.6.3创建文本数据
创建文本文件order.txt和product.txt文件,用来保存订单信息和商品信息。
product.txt每一行分别存储一个商品信息,产品信息有商品编号、商品名字、商品价格、商品所属分类。
order.txt每一行分别存储一个订单信息,订单信息有订单编号、订单日期、商品编号、商品数量。
注意:每一列都用Tab键隔开

控制台查看日志,在工程上加入log4j.properties.
10.6.4本地测试
(1)先创建本地路径“D:\myjoin\input”,如图所示
(2)把10.6.3的文本放到“D:\myjoin\input”下

(3)main方法传参,参照10.5
(4)运行工程,如图所示

(5)控制台没有报错,在“D:/myjoin/output”下查看,有4个文件,说明本地运行成功

(6)查看part-r-00000内容,是不是要求的,如图所示

10.6.5将项目打成jar包
(1)选择项目名“MyJoin”,点击右键,选择“Export”,如图所示

(2)选择“java” -->“Java File”,如图所示

(3)点击“Browse”,选择要导出的路径,在文件名里输入名字,然后点击“保存”,如图所示

(4)再点击两次“next”,如图所示

(5)“Main class”是指哪个类有main方法,点击“Browse”,出来一个“Select Main Class”框,双击点击“MapReduceJob”,然后点击“Finish”,如图所示

10.6.6启动hadoop集群
执行命令start-all.sh
[root@hadoop ~]start-all.sh
10.6.7将jar包上传到linux上
10.6.8将文本数据上传到hdfs上
(1)先把文本从本地上传到linux(/joininput)上
(2)在hdfs上创建/join文件夹,执行命令hdfs dfs –mkdir hdfs://192.168.11.128:9000/join
(3)再从linux上传到hdfs上,执行命令hdfs dfs –put /join/order.txt hdfs://192.168.11.128:9000/join&&hdfs dfs -put /join/produce.txt hdfs://192.168.11.128:9000/join

10.6.9运行jar文件
执行命令hadoop jar MyJoin.jarhdfs://192.168.11.128:9000/join/order.txthdfs://192.168.11.128:9000/join/produce.txthdfs://192.168.11.128:9000/join/output

10.6.10查看运行结果

10.6.11查看part-r-00000

第11章
MapReduce实验:分布式缓存

11.1实验目的
理解序列化与反序列化;
熟悉Job类;
学会使用Job类进行文件传递;
理解分布式缓存处理思想
11.2实验要求
理解MapReduce编程思想
编写MapReduce的分布式缓存
11.3实验原理
11.3.1概念
在执行MapReduce时,可能Mapper之间需要共享一些信息,如果信息量不大,可以将其从HDFS加载到内存中,这就是Hadoop分布式缓存机制。假定现有一个大为10GB的大表big.txt和大小为1MB的小表,我们可以使用“扫描大表、加载小表”。
11.3.2使用方式
Mapper类主要有4个方法:
setup()、map()、cleanup()、run()。
setup 在所有map方法运行之前执行,setup执行且仅执行一次,主要用来为map方法做一些准备工作,所以很多初始化的工作尽量放在这里做。
map方法则一般承担主要的处理工作
cleanup 在所有map方法运行之后执行,做收尾工作如关闭文件或者执行map()后的K-V分发等。
run()方法调用其它三个方法,顺序是:setup()–>map()–>cleanup()。
public class OGMJoinMapper extends Mapper {
@Override
protected void setup(Mapper.Context context)
throws IOException, InterruptedException {
// 初始化代码
}
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
// 通常的处理工作
}
}

11.4实验步骤
11.4.1创建工程
(1)打开eclipse,新建一个工程。“file” ->“New” ->“other”,select a wizard中选择“Map/Reduce Project”,输入工程名(自定义)

11.4.2创建类
创建两个类,分别是Job类OGMJoinJob和Mapper类OGMJoinMapper。
(1)选择src,单击右键,选择“New”→“ Package”,输入包名,点击“Finish”,如图所示

(2)选择包名“MyCache”,点击右键,选择“New”→“Class”,输入类名,点击“Finish”,如图所示。

11.4.3编写程序
Map端实现:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
/**

  • 为了防止reducer端发生处理数据倾斜

  • 则采用在map端进行数据join
    */
    public class OGMJoinMapper extends Mapper {
    //设定一个map 保存从本task节点的goods.txt读取出的信息
    HashMap goodsMap = new HashMap();

    //setup方法在mapper执行时,最先只执行一次 map方法会执行很多次
    @Override
    protected void setup(Mapper.Context context)
    throws IOException, InterruptedException {
    // TODO Auto-generated method stub
    super.setup(context);
    //从本task工作节点读取produce.txt的商品信息放入缓存map
    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(“product.txt”)));
    String line = null;
    while ((line=br.readLine())!=null) {
    //开始对produce.txt中每一行数据进行拆分
    String[] linearr = line.split("\t");
    //商品编号
    String gid = linearr[0];
    //商品名称商品价格
    String gname = linearr[1];
    String price = linearr[2];
    String number= linearr[3];
    //拼接商品名称和商品价格
    String result = gname+"\t"+price+"\t"+number;
    //把此商品信息放入map缓存
    goodsMap.put(gid, result);
    }
    }
    @Override
    protected void map(LongWritable key, Text value, Context context)
    throws IOException, InterruptedException {
    //1:从order.txt读取每一行进行处理
    String line = value.toString();
    String[] linearr = line.split("\t");
    //2:从每一行中解析出商品编号
    String gid = linearr[2];
    //使用商品编号从mr的缓存文件中获取对应的商品名称和价格
    String result = goodsMap.get(gid);
    //3:把订单id 下单时间商品编号商品名称价格直接拼接即可
    String oid = linearr[0];//订单id
    String odate = linearr[1];//下单时间
    String k = oid+"\t"+odate+"\t"+gid+"\t"+result;

     //把k放入context
     context.write(new Text(k), NullWritable.get());
    

    }
    }

job提交客户端实现:
public class OGMJoinJob {

public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, URISyntaxException {
	Configuration conf = new Configuration();
	Job job = Job.getInstance(conf);
	// job.addFileToClassPath(new  			 		Path("d:\\bigandsmalltable\\product.txt"));
	job.addFileToClassPath(new Path("hdfs://192.168.86.200:9000/bigandsmalltable/product.txt"));
	job.setJarByClass(OGMJoinJob.class);
	job.setMapperClass(OGMJoinMapper.class);
	job.setMapOutputKeyClass(Text.class);
	job.setMapOutputValueClass(NullWritable.class);
	job.setOutputKeyClass(Text.class);
	job.setOutputValueClass(NullWritable.class);
	//设置不需要再使用reducer  因为mapper的结果就是最终结果
	job.setNumReduceTasks(0);
	// FileInputFormat.setInputPaths(job, new 		Path("d:\\bigandsmalltable\\order.txt"));
	FileInputFormat.setInputPaths(job, new Path("hdfs://192.168.86.200:9000/bigandsmalltable/order.txt"));
	// FileOutputFormat.setOutputPath(job, new 	Path("d:\\bigandsmalltable\\output"));
	FileOutputFormat.setOutputPath(job, new 	Path("hdfs://192.168.86.200:9000/bigandsmalltable/output"));
	Boolean flag = job.waitForCompletion(true);
	System.exit(flag?0:1);
}

}
11.4.4创建文本数据
为降低操作难度,此处用少量数据代表大文件order.txt,更少量数据代表小文件product.txt。
注意:每一列都用Tab键隔开

在d盘下创建bigandsmalltable目录,将order.txt和product.txt复制到这个目录下,执行OGMJoinJob类的main函数,将会创建output目录,效果如下图。

运行结果见part-m-00000。

11.4.5将项目打成jar包
(1)选择项目“MyCache”,点击右键,选择“Export”,如图所示

(2)选择“java” -->“Java File”,如图所示

(3)点击“Browse”,选择要导出的路径,在文件名里输入名字,然后点击“保存”,如图所示

(4)再点击两次“next”,如图所示

(5)“Main class”是指哪个类有main方法,点击“Browse”,出来一个“Select Main Class”框,双击点击“MapReduceJob”,然后点击“Finish”,如图所示

11.4.6启动hadoop集群
执行命令start-all.sh
[root@hadoop ~]start-all.sh

将jar包上传到linux系统的/usr目录下。

10.4.7将文本文件上传到hdfs上
(1)先把order.txt和product.txt文本文件从本地上传到linux(/usr)上
(2)在hdfs上创建/cache文件夹,执行命令
[root@hadoop sbin]# hadoop fs -mkdir /bigandsmalltable
(3)再将order.txt和product.txt文件从linux上传到hdfs上,执行命令
[root@hadoop sbin]# hadoop fs -put /usr/order.txt /bigandsmalltable
[root@hadoop sbin]# hadoop fs -put /usr/product.txt /bigandsmalltable

11.4.8运行jar文件
执行命令
[root@hadoop usr]# hadoop jar bigandsmalltable.jar bigandsmalltable.OGMJoinJob
11.4.9运行结果

查看part-r-00000

第12章
Hadoop实验:Hadoop完全分布式集群搭建

在第三章我们讲解了Hadoop的环境搭建,Hadoop有三种运行模式。我们已经讲解了单机运行安装和伪分布式安装Hadoop。还没有讲解完全分布式安装Hadoop,本章就开始讲解这种安装方式。
12.1介绍
Hadoop完全分市式集群是典型的主从架构(master-slave),一般需要使用多台服务器来组建。我们准备3台服务器(关闭防火墙、静态IP、主机名称)。如果没有这样的环境,可以在一台电脑上安装VMWare Workstation。在VM上安装三台Linux,分别是1个主节点,2个从节点,如下图所示。
节点类型 IP地址 主机名
NameNode 192.168.86.150 master
DataNode 192.168.86.160 slave1
DataNode 192.168.86.170 slave2
注意:这3个节点的IP地址在实际搭建时会有所不同。
12.2集群搭建步骤
12.2.1在VMware Workstation上创建3台虚拟机
(1)找到桌面的虚拟机图标,双击后,启动VMware界面,选择“创建新的虚拟机”,如下图,即会弹出向导。

(2)点击下一步,初学者使用典型配置。

(3)点击浏览,找到我们给的iso镜像文件。

(4)选择镜像文件。

(5)打开虚拟机向导。

(6)填写用户信息。

备注:写上全名,用户名,密码(用户名是用来登录Linux系统的,配合密码可以完成登录。全名只是对用户名的一个备注说明。)

(7)设置虚拟机名称和位置。

(8)设置虚拟机磁盘容量,磁盘容量默认即可 20G已够用。

(9)安装完成。

(10)启动虚拟机。
虚拟机已开始启动

等待几分钟

启动完成

12.2.2配置网络
(1)在Linux系统命令终端,执行命令cd /etc/sysconfig/network-scripts,切换到该目录并查看该目录下的文件ifcfg-eth0,如图所示。

(2) 在Linx系统命令终端,执行命令 vim ifcfg-eth0,并修改文件的内容,按“键入编辑内容编译完成后按Esc键退出编译状态,之后执行命令wq,保存并退出。IPADDR、 NETMASK、 GATEWAY、DNS1的值可以根据自己的本机进行修改,如下所示。
DEVICE=“eth0” #设备名字
BOOTPROTO=“static” #静态ip
HWADDR=“00:0C:29:ED:83:F7” #mac地址
IPV6INIT=“yes”
NM_CONTROLLED=“yes”
ONBOOT=“yes” #开启自启动
TYPE=“Ethernet” #网络类型
UUID=“28354862-67a7-4a5b-9f9a-54561401f614”
IPADDR=192.168.11.10 #IP地址
NETMASK=255.255.255.0 #子网掩码
GATEWAY=192.168.11.2 #网关
DNS1=192.168.11.2 # dns

单击编辑菜单下的虚拟网络编辑器菜单,打开虚拟网络编辑器窗体,如下图所示。

(3)配置iP地址完毕之后,在命令终端的任意目录下,执行命令 ifconfig,查看配置效果,如图所示。

(4)在命令终端的任意目录下重启服务,执行命令reboot。
(5) ping ip地址看是否安装成功,如图所示。

(6)剩下两台都按步骤配置。
12.2.3修改主机名和域名映射
(1)启动命令终端,在任何目录下执行命令cd/ etc/sysconfig,切换到该目录并查看目录下的文件,可以发现存在文件 network,如图所示。

(2)在/etc/sysconfig目录下找到文件 network,然后执行命令 vim network,按“i”进入编辑内容,编译完成后按Esc退出编译状态,之后执行命令wq保存并退出,后面两台也都这样,如下图所示。

(3)修改主机名和iP地址具有映射关系,执行命令vim/ etc/hosts,按“i”进入编辑内容,编译完成后按Esc退出编译状态,之后执行命令wq保存并退出,把三台的ip和主机名都编辑,如图所示。

(4)scp命令传送文件
scp /etc/hosts root@slave1:/etc/hosts
scp /etc/hosts root@slave2:/etc/hosts
把修改好的发送给slave1,再用相同的方法,发送给slave2。

下面我们详细介绍一下scp命令,scp是secure copy的缩写,是用于Linux之间复制文件和目录的。scp是Linux系统下基于ssh登陆进行安全的远程文件拷贝命令。
scp数据传输可以使用ssh1或ssh2。scp命令可以使用IPv4寻址或IPv6寻址。
 复制文件语法:
  scp /源文件完整路径 远程用户名@IP地址: /目标文件完整路径
或者
scp /源文件完整路径 远程用户名@机器名: /目标文件完整路径
scp /home/space/music/1.mp3 root@slave1:/home/root/others/music
scp /home/space/music/1.mp3 [email protected]:/home/root/others/music/001.mp3
 复制目录语法:
scp -r /源目录完整路径 远程用户名@IP地址: /目标目录所在路径
或者
scp -r /源目录完整路径 远程用户名@机器名: /目标目录所在路径
scp -r /home/space/music/ root@slave3:/home/root/others/
scp -r /home/space/music/ [email protected]:/home/root/others/
 从远程复制到本地
从远程复制到本地,只要将从本地复制到远程的命令的后2个参数调换顺序即可。
scp root@slave3:/home/root/others/music/1.mp3 /home/space/music
参数:
-r 递归复制整个目录。
-v 和大多数linux命令中的-v意思一样,用来显示进度。可以用来查看连接、认证、或是配置错误。
-C 允许压缩。(将-C标志传递给ssh,从而打开压缩功能)。
-1 强制scp命令使用协议ssh1。
-2 强制scp命令使用协议ssh2。
-4 强行使用IPV4地址。
-6 强行使用IPV6地址。
-q 不显示传输进度条。
12.2.4在linux下安装Java
(1)启动 Linux命令终端,分别在三台虚拟机上创建目录,执行命令mkdir /usr/java,切换到该目录下执行命令cd/usr/java,
[root@hadoop ~] mkdir/usr/java
[root@hadoop ~]cd /usr/java
(2)把JDK文件jdk-8u181-linux-x64.tar.gz上传到该目录下
(3)然后对/usr/java目录下的JDK压缩文件jdk-8u181-linux-x64.tar.gz,执行命令
对jdk-8u181-linux-x64.tar.gz进行解压
[root@hadoop java]#tar -xzvf jdk-8u181-linux-x64.tar.gz
(4)解压之后,执行命令 Il,可以看到该目录下多了一个解压后的Jdk文件,如图2-43所示。

(5)把jdk文件上传到其他两台,通过命令上传到其他两台虚拟机上,指定命令
scp –r /usr/java root@主机名:/usr
[root@master ~]scp –r /usr/java root@slave1:/usr
[root@master ~] scp –r /usr/java root@slave2:/usr
(6)然后到slave1和slave2的/usr目录下看,是否有java这个目录
(7)完成上一步之后,可以执行cd jdk.1.7.0_80,进入JDK安装目录

(8)确定解压无误之后,此时需要配置JDK环境变量,执行命令 vim /etc/profile单击”i“进入编辑内容,编译完成后按Esc退出编译状态,之后执行命令wq保存并退出。如图

(9)编辑完后进行配置文件刷新,执行命令 source /etc/profile,刷新配置,配置的信息才会生效,如图所示。
[root@hadoop jdk1.7.0_80]# source /etc/profile
(10)完成以上步骤之后,需要测试环境变量是否配置成功,只需要在任何目录下执行Java –version ,如图,出现下图情况就是配置成功。

12.2.5关闭防火墙
关闭Linux防火墙有以下3个步骤:
1.查看防火墙状态
service iptables status
2. 关闭防火墙
service iptables stop
3. 永久性关闭防火墙
chkconfig iptables off
12.2.6 SSH免密
(1)在Linux系统的终端的任何目录下通过切换cd ~/.ssh,进入到.ssh目录下,如图

~表示当前用户的home目录,通过cd ~可以进入到你的home目录。.开头的文件表示隐藏文件,这里.ssh就是隐藏目录文件。
(2)在Linux系统命令框的.ssh目录下
[root@root .ssh]# ssh-keygen -t rsa

(连续按四次回车)执行完上面命令后,会生成两个id_rsa(私钥)、id_rsa.pub(公钥)两个文件,如图所示

(3)授权SSH免密码
[root@master .ssh]# ssh-copy-id master
[root@master .ssh]# ssh-copy-id slave1
[root@master .ssh]# ssh-copy-id slave2
给当前主机和其他两台都设置免密码登录,这样三台可以互通。(根据提示输入yes并输入访问主机所需要的密码。)

(4)在master主机上执行下面的3条命令。
[root@master .ssh]# ssh master
[root@master .ssh]# ssh slave1
[root@slave1 ~]# exit
[root@master .ssh]# ssh slave2
发现不需要密码就能连接任意一台虚拟机,如图所示

注意:当执行ssh slave1命令后,就以SSH免密方式登录到slave1。必须使用exit命令退出登录slave1,再尝试执行ssh slave2。
12.2.7配置时间同步服务
NTP是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源做同步化,提供高精准度的时间校正。Hadoop集群对时间要求很高,主节点与各从节点的时间都必须要同步。配置时间同步服务主要是为了进行集群间的时间同步。Hadoop集群配置时间同步服务的步骤如下。
(1)安装NTP服务。
在各节点执行命令 yum install -y ntp即可。若是最终出现了“Complete”信息,就说明安装NTP服务成功。
(2)设置 master节点为NTP服务主节点,那么其配置如下。
使用命令“ vim /etc/ntp.conf”来打开/etc/ntp.conf文件,注释掉以 server开头的行,并添加代码所示的内容。
restrict 192.168.0.0 mask 255.255.255.0 nomodify notrap
server 127.127.1.0
fudge 127.127.1.0 stratum 10

(3)分别在slave1,slave2中配置NTP,同样修改/etc/ntp.conf文件,注释掉server开头的行,并添加下面代码所示的内容。
server master

(4)永久性关闭防火墙,主节点和从节点都要关闭。执行命令
service iptables stop
chkconfig iptables off

(5)启动NTP服务
①在 master节点执行命令“ service ntpd start& chkconfig ntpd on”,如下图所示,说明NTP服务启动成功。

②在slave1、slave2上同步时间。执行命令
ntpdate master
如下图所示。

③在 slave1、slave2上分别执行“ service ntpd start& chkconfig ntpd on”,即永久启动NTP服务,如下图所示。

④分别在master、slave1、slave2上分别输入date,看时间是否一致。
12.2.8 hadoop分布式的安装
master主机上
(1)创建目录mkdir /usr/hadoop,执行命令cd/usr/hadoop,切换到该目录下,把Hadoop文件上传到该目录下
(2)然后对/usr/hadoop目录下的Hadoop压缩文件hadoop-2.6.5.tar.gz,执行命令
tar -zxvf hadoop-2.6.5.tar.gz -C /usr/hadoop
-C是指解压压缩包到指定位置
(3)修改配置文件
切换到$HADOOP_NAME/etc/hadoop 目录下并查看该目录下的包,如图

下面我们需要修改以下的7个文件。
文件名 文件路径
hadoop-env.sh $HADOOP_NAME/etc/hadoop
core-site.xml $HADOOP_NAME/etc/hadoop
hdfs-site.xml $HADOOP_NAME/etc/hadoop
mapred-site.xml $HADOOP_NAME/etc/hadoop
yarn-site.xml $HADOOP_NAME/etc/hadoop
yarn-env.sh $HADOOP_NAME/etc/hadoop
profile /etc/ profile
slaves $HADOOP_NAME/etc/hadoop

(4)在 H A D O O P N A M E / e t c / h a d o o p 目 录 下 执 行 命 令 v i h a d o o p − e n v . s h 按 “ i ” 键 进 入 编 辑 内 容 , 在 文 件 中 添 加 如 下 内 容 : e x p o r t J A V A H O M E = / u s r / j a v a / j d k 1.7. 0 8 0 e x p o r t H A D O O P C O M M O N L I B N A T I V E D I R = HADOOP_NAME/etc/hadoop目录下执行命令 vi hadoop-env.sh 按“i”键进入编辑内容,在文件中添加如下内容: export JAVA_HOME=/usr/java/jdk1.7.0_80 export HADOOP_COMMON_LIB_NATIVE_DIR= HADOOPNAME/etc/hadoopvihadoopenv.shiexportJAVAHOME=/usr/java/jdk1.7.080exportHADOOPCOMMONLIBNATIVEDIR={HADOOP_PREFIX}/lib/native
export HADOOP_OPTS="-Djava.library.path=$HADOOP_PREFIX/lib"
编译完成后,按ESC退出编辑状态,之后执行命令wq保存并退出

(5)在 H A D O O P N A M E / e t c / h a d o o p 目 录 下 执 行 命 令 v i c o r e − s i t e . x m l 并 修 改 配 置 文 件 c o r e − s i t e . x m l , 内 容 如 下 : < c o n f i g u r a t i o n > < ! — 指 定 H D F S 的 ( N a m e n o d e ) 的 缺 省 路 径 地 址 : m a s t e r 是 计 算 机 名 , 也 可 以 是 i p 地 址 − − > < p r o p e r t y > < n a m e > f s . d e f a u l t F S < / n a m e > < v a l u e > h d f s : / / m a s t e r : 9000 < / v a l u e > < / p r o p e r t y > < ! — 指 定 H a d o o p 运 行 时 产 生 文 件 的 存 储 目 录 , 需 要 创 建 / u s r / h a d o o p / t m p 目 录 − − > < p r o p e r t y > < n a m e > h a d o o p . t m p . d i r < / n a m e > < v a l u e > / u s r / h a d o o p / t m p < / v a l u e > < / p r o p e r t y > < / c o n f i g u r a t i o n > ( 6 ) HADOOP_NAME/etc/hadoop目录下执行命令 vi core-site.xml 并修改配置文件core-site.xml ,内容如下: fs.defaultFS hdfs://master:9000 hadoop.tmp.dir /usr/hadoop/tmp (6) HADOOPNAME/etc/hadoopvicoresite.xmlcoresite.xml,:<configuration><!HDFSNamenodemasterip><property><name>fs.defaultFS</name><value>hdfs://master:9000</value></property><!Hadoop,/usr/hadoop/tmp><property><name>hadoop.tmp.dir</name><value>/usr/hadoop/tmp</value></property></configuration>6HADOOP_NAME/etc/hadoop目录下执行命令vi hdfs-site.xml,并修改配置文件hdfs-site.xml ,内容如下



dfs.replication
3



dfs.namenode.name.dir
/home/hadoop/dfs/name



dfs.datanode.data.dir
/home/hadoop/dfs/data

	

dfs.namenode.secondary.http-address
master:50090

(7)在$HADOOP_NAME/etc/hadoop目录下查看是否有配置文件mapred-site.xml。目录下默认情况下没有该文件,可通过执行命令
mv mapred-site.xml.template mapred-site.xml,修改一个文件的命名。
然后执行命令
vi mapred-site.xml
并修改配置文件mapred-site.xml,内容如下:



mapreduce.framework.name
yarn


	mapreduce.jobhistory.address
	master:10020



 
	mapreduce.jobhistory.webapp.address
	master:19888

(8)在$HADOOP_NAME/etc/hadoop目录下执行命令vi yarn-site.xml,并修改配置文件yarn-site.xml ,内容如下 yarn.resourcemanager.hostname master
	
	
		yarn.nodemanager.aux-services
		mapreduce_shuffle
	

(9)在 H A D O O P N A M E / e t c / h a d o o p 目 录 下 执 行 命 令 v i y a r n − e n v . s h 修 改 配 置 文 件 y a r n − e n v . s h , 增 加 如 下 内 容 e x p o r t H A D O O P C O M M O N L I B N A T I V E D I R = HADOOP_NAME/etc/hadoop目录下执行命令 vi yarn-env.sh 修改配置文件yarn-env.sh ,增加如下内容 export HADOOP_COMMON_LIB_NATIVE_DIR= HADOOPNAME/etc/hadoopviyarnenv.shyarnenv.sh,exportHADOOPCOMMONLIBNATIVEDIR={HADOOP_PREFIX}/lib/native
export HADOOP_OPTS="-Djava.library.path=$HADOOP_PREFIX/lib"

(10)执行命令vi /etc/profile,把Hadoop的安装目录配置到环境变量中
#hadoop
HADOOP_HOME=/usr/hadoop/hadoop-2.6.5
export PATH= H A D O O P H O M E / b i n : HADOOP_HOME/bin: HADOOPHOME/bin:HADOOP_HOME/sbin:$PATH

(11)然后让配置文件生效,执行命令
source /etc/profile
(12)在 H A D O O P N A M E / e t c / h a d o o p 目 录 下 执 行 命 令 v i s l a v e s , 并 修 改 配 置 文 件 s l a v e s , 内 容 如 下 。 s l a v e 1 s l a v e 212.2.9 分 发 h a d o o p 在 m a s t e r 上 执 行 命 令 , 将 配 置 好 的 h a d o o p 分 发 到 两 个 从 节 点 s l a v e 1 , s l a v e 2 上 。 s c p − r / u s r / h a d o o p / h a d o o p − 2.6.5 r o o t @ s l a v e 1 : / u s r / h a d o o p / s c p − r / u s r / h a d o o p / h a d o o p − 2.6.5 r o o t @ s l a v e 2 : / u s r / h a d o o p / 注 : 在 s l a v e 1 和 s l a v e 2 上 提 前 先 创 建 好 / u s r / h a d o o p 目 录 在 从 节 点 s l a v e 1 , s l a v e 2 上 修 改 / e t c / p r o f i l e 12.2.10 格 式 化 N a m e N o d e m a s t e r 主 机 是 N a m e N o d e , 必 须 格 式 化 之 后 才 能 使 用 , 格 式 化 命 令 只 需 要 执 行 一 次 。 在 任 意 目 录 下 , 执 行 命 令 h d f s n a m e n o d e − f o r m a t 或 者 h a d o o p n a m e n o d e − f o r m a t 注 意 : 以 上 命 令 必 须 在 配 置 H a d o o p 环 境 变 量 的 情 况 下 才 能 执 行 。 所 以 必 须 修 改 / e t c / p r o f i l e 文 件 并 且 执 行 下 面 的 命 令 启 动 修 改 。 s o u r c e / e t c / p r o f i l e 12.2.11 启 动 h a d o o p 集 群 ( 1 ) 首 先 启 动 H D F S 系 统 , m a s t e r 上 HADOOP_NAME/etc/hadoop目录下执行命令vi slaves,并修改配置文件slaves,内容如下。 slave1 slave2 12.2.9分发hadoop 在master上执行命令,将配置好的hadoop分发到两个从节点slave1,slave2上。 scp -r /usr/hadoop/hadoop-2.6.5 root@slave1:/usr/hadoop/ scp -r /usr/hadoop/hadoop-2.6.5 root@slave2:/usr/hadoop/ 注:在slave1和slave2上提前先创建好/usr/hadoop目录 在从节点slave1,slave2上修改/etc/profile 12.2.10格式化NameNode master主机是NameNode,必须格式化之后才能使用,格式化命令只需要执行一次。在任意目录下,执行命令 hdfs namenode -format 或者 hadoop namenode -format 注意:以上命令必须在配置Hadoop环境变量的情况下才能执行。所以必须修改/etc/profile文件并且执行下面的命令启动修改。 source /etc/profile 12.2.11启动hadoop集群 (1)首先启动HDFS系统,master上 HADOOPNAME/etc/hadoopvislaves,slaves,slave1slave212.2.9hadoopmasterhadoopslave1,slave2scpr/usr/hadoop/hadoop2.6.5root@slave1:/usr/hadoop/scpr/usr/hadoop/hadoop2.6.5root@slave2:/usr/hadoop/slave1slave2/usr/hadoopslave1,slave2/etc/profile12.2.10NameNodemasterNameNode使,hdfsnamenodeformathadoopnamenodeformatHadoop/etc/profilesource/etc/profile12.2.11hadoop1HDFSmasterHADOOP_HOME/sbin目录下执行命令start-dfs.sh(注意slave1和slave2不需要执行命令),然后在master、slave1、slave2主机上用jps查看进程,如图所示。
master:

slave1:

slave2:

如上图可以看出master主机是NameNode同时还是SecondNameNode。slave1和slave2主机是DataNode。
注意:在slave1和slave2上修改/etc/profile,执行12.2.11中的步骤10和步骤11。
(2)首先启动yarn系统,master上执行命令start-yarn.sh(注意slave1和slave2不需要执行命令),然后在master、slave1、slave2主机上用jps查看进程,如图所示。
master:

slave1:

slave2:

如上图可以看出master主机上有ResourceManager。slave1和slave2主机上有NodeManager。

12.2.12 web页面查看

  1. 查看HDFS管理界面(http://192.168.86.150:50070),如图所示。

  2. 查看MapReduce管理页面(http://192.168.86.150:8088),如图所示。

第13章
Zookeeper概述

13.1 Zookeeper概述
Zookeeper是一个开放源码的分布式应用程序协调服务,是 Google的Chubby一个开源的实现,是 Hadoop和 HBASE的重要组件。主要解决分布式应用一致性问题。
13.1.1 分布式应用
分布式应用可以在给定时间(同时)在网络中的多个系统上运行,通过协调它们以快速有效的方式完成特定任务。通常来说,对于复杂而耗时的任务,非分布式应用(运行在单个系统中)需要几个小时才能完成,而分布式应用通过使用所有系统涉及的计算能力可以在几分钟内完成。
通过将分布式应用配置为在更多系统上运行,可以进一步减少完成任务的时间。分布式应用正在运行的一组系统称为集群,而在集群中运行的每台机器被称为节点。
分布式应用有两部分, Server(服务器) 和 Client(客户端) 应用程序。服务器应用程序实际上是分布式的,并具有通用接口,以便客户端可以连接到集群中的任何服务器并获得相同的结果。 客户端应用程序是与分布式应用进行交互的工具。

分布式应用的优点
• 可靠性 - 单个或几个系统的故障不会使整个系统出现故障。
• 可扩展性 - 可以在需要时增加性能,通过添加更多机器,在应用程序配置中进行微小的更改,而不会有停机时间。
• 透明性 - 隐藏系统的复杂性,并将其显示为单个实体/应用程序。
分布式应用的挑战
• 竞争条件 - 两个或多个机器尝试执行特定任务,实际上只需在任意给定时间由单个机器完成。例如,共享资源只能在任意给定时间由单个机器修改。
• 死锁 - 两个或多个操作等待彼此无限期完成。
• 不一致 - 数据的部分失败。
分布式应用程序提供了很多好处,但它们也抛出了一些复杂和难以解决的挑战。ZooKeeper框架提供了一个完整的机制来克服所有的挑战。
13.1.2 Zookeeper简介
Zookeeper是一个能够高效开发和维护分布式的开放源码的应用协调服务。是Google的 Chubby一个开源的实现,是 Hadoop和 HBASE的重要组件。Zookeeper是一个为分布式应用提供一致性服务的软件,提供的功能包括维护配置信息、名 字服务、分布式同步、组服务等。ZooKeeper框架最初是在“Yahoo!"上构建的,用于以简单而稳健的方式访问他们的应用程序。 后来,Apache ZooKeeper成为Hadoop,HBase和其他分布式框架使用的有组织服务的标准。
首先我们对上一个段落做一个解释。

  1. Zookeeper是一个开放源代码的软件。
  2. Zookeeper是一个管理“分布式应用程序”的软件。什么是分布式应用程序服务?我们知道,Hadoop中的组件,如hdfs、MapReduce/yarn、hbase、double、kafka都是分布式服务。如MapReduce就是一个分布式服务,MapReduce会将所做的工作分发给Hadoop集群中的多台服务器共同实现。如何对分布式服务做协调,管理这些运行在不同电脑上的任务?就需要一个对分布式应用程序做协调的服务,这就是Zookeeper的工作。
  3. Zookeeper可以实现对分布式应用程序做一致性服务,什么是一致性服务?比如我们对A服务器上的一个数据进行了修改,这个数据同时在D服务器和M服务器有两个备份,这时就要对D服务器和M服务器有两个备份都进行修改,这就是一致性服务。Zookeeper就可以实现这么一个一致性服务。
  4. Zookeeper实现的强一致性服务。一致性服务分为3类,分别是:
     强一致性:a发生变化,b立刻就发生变化
     弱一致性:a发生变化,b过一会会发生变化
     最终一致性:a发生变化,b最终也会发生变化
    Zookeeper可以实现立刻的数据一致性,即强一致性。
    大家知道,Hadoop生态系统中的组件,都喜欢起动物的名称。如Hadoop、Hive、Pig等。而Zookeeper中文意思是动物园管理员,就是管理Hadoop生态系统。
  5. ZooKeeper的好处
    以下是使用ZooKeeper的好处:
    • 简单的分布式协调过程
    • 同步 - 服务器进程之间的相互排斥和协作。此过程有助于Apache HBase进行配置管理。
    • 有序的消息
    • 序列化 - 根据特定规则对数据进行编码。确保应用程序运行一致。这种方法可以在MapReduce中用来协调队列以执行运行的线程。
    • 可靠性
    • 原子性 - 数据转移完全成功或完全失败,但没有事务是部分的。
    13.1.3 Zookeeper的选举机制
    看看下面的图表。它描述了ZooKeeper的“客户端-服务器架构”。

配置多个实例共同构成一个Zookeeper集群对外提供服务以达到水平扩展的目的,集群中的每一台电脑都称为服务器(Server),每个服务器上的数据是相同的,每一个服务器均可以对外提供读和写的服务,这点和redis是相同的,即对客户端来讲每个服务器都是平等的。zookeeper集群一般需要奇数台服务器,为什么是奇数台服务器?因为我们需要通过选举机制选出领导者(leader),所以必须是奇数台服务器。
Zookeeper提供了三种选举机制:
 LeaderElection
 AuthFastLeaderElection
 FastLeaderElection
默认的算法是FastLeaderElection,所以这篇主要分析它的选举机制。

客户端(client)是请求发起方。服务器分为不同的角色,有领导者(leader),也有学习者(learner)。角色的不同是在选举中产生的,下面是选举的流程。
目前有5台服务器,每台服务器均没有数据,它们的编号分别是A,B,C,D,E按编号依次启动,它们的选择举过程如下:
• 服务器A启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器A的状态一直属于Looking(选举状态)。
• 服务器B启动,给自己投票,同时与之前启动的服务器A交换结果,由于服务器B的编号大所以服务器B胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是Looking(选举状态)。
• 服务器C启动,给自己投票,同时与之前启动的服务器A,B交换信息,由于服务器C的编号最大所以服务器C胜出,此时投票数正好大于半数,所以服务器C成为领导者(Leader),服务器A,B成为小弟。
• 服务器D启动,给自己投票,同时与之前启动的服务器A,B,C交换信息,尽管服务器D的编号大,但之前服务器C已经胜出,所以服务器D只能成为小弟。
• 服务器E启动,后面的逻辑同服务器E成为小弟。
这里的小弟就是学习者(learner)。学习者(learner)分为两类,能够参与投票的就是跟随者(follower),否则就是观察者(observer)。
服务器有以下状态。
• LOOKING:竞选状态。
• FOLLOWING:随从状态,同步leader状态,参与投票。
• OBSERVING:观察状态,同步leader状态,不参与投票。
• LEADING:领导者状态。
下面是选举的简易流程图。

以下是选举状态图
描述Leader选择过程中的状态变化,这是假设全部实例中均没有数据,假设服务器启动顺序分别为:A,B,C。

角色 描述
服务器(Server) 领导者(Leader) 服务器节点,负责进行投票的发起和决议,更新系统状态。
学习者(Learner) 跟随者(Follower) 服务器节点,用于接收客户端请求并向客户端返回结果,在选举过程中能参与投票。
观察者(Observer) 当集群节点数目逐渐增大为了支持更多的客户端,需要增加更多Server,然而Server增多,投票阶段延迟增大,影响性能。为了权衡伸缩性和高吞吐率,引入Observer。
服务器节点,可以接收客户端连接,将写请求转发给Leader节点。但Observer不能参与投票,只同步Leader的状态。Observer的目的是为了扩展系统,提高读取速度。
客户端(Client) 请求发起方
13.1.4 Zookeeper的读写机制
Zookeeper是分布式应用程序的协调程序。分布式应用程序运行在集群上,客户端对一台服务器的请求完成后修改了数据并将数据同步到其他备份,并且需要将结果告知集群中所有电脑,这个由分布式应用程序自身实现吗?可以,但是也可以由另一个协调程序完成这个功能,Zookeeper就是这么一个协调程序。下面我们介绍以下Zookeeper写流程。下面我们见下图。
客户端首先和一个Server或者Observer(可以认为是一个Server的代理)通信,发起写请求,然后Server将写请求转发给Leader,Leader再将写请求转发给其他Server,Server在接收到写请求后写入数据并回应Leader,Leader在接收到大多数写成功回应后,认为数据写成功,回应Client。

Zookeeper读取由特定连接的Server在内部执行,因此不需要与集群进行交互。
13.1.5 Zookeeper的数据模型
Zookeeper的数据保存在一个类似于文件系统的一个树形结构中,每个数据节点只能携带少量的数据。为什么只能携带少量的数据呢?因为Zookeeper用于进行协调服务的,所以不需要携带大量数据。
每个数据节点(树中的每一个分支节点或者叶子节点)称之为znode。每一个znode节点既是目录又是文件(是文件的含义是它可以带少量数据,是目录的含义是它有可能还有子目录),这和我们普通看到的文件系统不一样。

 每个目录在zookeeper中叫做znode,并且其有一个唯一的路径标识,如/services/myservice/servers/stuidname1
 znode有两种类型,短暂的(ephemeral: 断开连接自己删除)和持久的(ersistent: 断开连接不删除);
 znode可以包含数据和子znode(ephemeral类型的节点不能有子znode);
 znode中的数据可以有多个版本,比如某一个znode下存有多个数据版本,那么查询这个路径下的数据需带上版本;
创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护 。
如存一个/stu/name值mike,会对路径上加序列化,如/name000001
再存一个/stu/name值jack,会对路径上加序列化, 如/name000002
上面的znode就有两个版本
 客户端应用可以在znode上设置监视器(Watcher)。
 znode不支持部分读写,而是一次性完整读写
 znode的类型在创建时确定并且之后不能再修改;
 ephemeral znode的客户端会话结束时,zookeeper会将该ephemeral znode删除,ephemeral znode不可以有子节点;
 persistent znode不依赖于客户端会话,只有当客户端明确要删除该persistent znode时才会被删除;
Zookeeper以节点的方式存储一些关键信息,默认情况下,所有应用都可以读写任何节点,在复杂的应用中,这不太安全,Zookeeper通过ACL(Access Controll List)机制来解决访问权限问题。
总体来说,Zookeeper的节点有5种ACL(Access Controller List)权限:
 CREATE 允许创建Child Nodes
 READ 允许获取ZNode的数据,以及该节点的孩子列表
 WRITE 可以修改ZNode的数据
 DELETE 可以删除一个孩子节点
 ADMIN 可以设置权限
这5种权限简写为crwda(即:每个单词的首字符缩写)。注意这5种权限中,delete是指对子节点的删除权限,其它4种权限指对自身节点的操作权限。

13.1.6 Zookeeper的监听机制
Zookeeper的节点是可以被监控,目录中存储数据的修改、子节点目录的变化,都可以触发事件并通知监听的客户端,这是 Zookeeper重要的特性。通过此特性可以实现的功能个监听事件是一个有配置的集中管理、集群管理、分布式锁等。监听机制官方说明为次性的监听器,当被设置了监听的数据发生变化时,服务器就会将这个改变发送给负责设置 Watch的客户端。
ZooKeeper中的Watch是只能触发一次。也就是说,如果客户端在指定的ZNode设置了Watch,如果该ZNode数据发生变更,ZooKeeper会发送一个变更通知给客户端,同时触发设置的Watch事件。如果ZNode数据又发生了变更,客户端在收到第一次通知后没有重新设置该ZNode的Watch,则ZooKeeper就不会发送一个变更通知给客户端。
Zookeeper的特点是
 一次性触发
 事件触发后发给客户端。
 数据被设置 Watch

第14章
Zookeeper实验:集群建设

本章参考《Hadoop大数据开发案例教程与项目实战》第7章
14.1实验目的
掌握 ZooKeeper集群安装部署,加深对 ZooKeeper相关概念的理解,熟练
Zookeeper的一些常用Shell命令。
14.2实验要求
部署三个节点的 ZooKeeper集群,通过 ZooKeeper客户端连接 ZooKeeper集群,并
用Shell命令练习创建目录,查询目录等。
14.3实验原理
ZooKeeper分布式服务框架是 Apache Hadoop的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,例如,统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。
ZooKeeper是以 Fast Paxos算法为基础的。
ZooKeeper集群的初始化过程:集群中所有机器以投票的方式(少数服从多数)选取某一台机器作为 leader(领导者),其余机器作为 follower(追随者)。如果集群中只有1台机器,那么就这台机器就是 leader,没有 follower。
ZooKeeper集群与客户端的交互:客户端可以在任意情况下、 ZooKeeper集群中任意一台机器上进行读操作;但是写操作必须得到 leader的同意后才能执行。
ZooKeeper选取 leader的核心算法思想:如果某服务器获得N/2+1票,则该服务器成为 leader,其中N为集群中机器数量。为了避免岀现两台服务器获得相同票数(N/2),应该确保N为奇数,因此构建 ZooKeeper集群最少需要3台机器。
14.4 Zookeeper集群搭建
14.4.1 Zookeeper安装模式
ZooKeeper的安装模式分为三种,分别为:单机模式、伪分布式集群模式、分布式集群模式。
单机模式:Zookeeper只运行在一台服务器上,适合测试环境。
伪分布式集群模式:就是在一台服务器上运行多个Zookeeper 实例。
分布式集群模式:一个集群有多个Linux虚拟机组成,Zookeeper运行于一个集群上,适合生产环境。
本章介绍的是分布式集群模式。
本章可以在12章的基础上进行实验。
在一台电脑上安装VMWare Workstation。在VM上安装三台Linux,分别是1个主节点,2个从节点,如下图所示。
节点类型 IP地址 主机名
NameNode 192.168.86.150 master
DataNode 192.168.86.160 slave1
DataNode 192.168.86.170 slave2
注意:这3个节点的IP地址在实际搭建时会有所不同。

14.4.2搭建步骤
(1)在master上启动 Linux命令终端,创建目录mkdir /usr/zookeeper,执行命令cd /usr/zookeeper,切换到该目录下,把zookeeper文件上传到该目录下,如图所示。

(2)Zookeeper压缩文件zookeeper -3.4.12.tar.gz进行解压缩。
tar -zxvf zookeeper-3.4.12.tar.gz -C /usr/zookeeper
-C是指解压压缩包到指定位置

(3)在/usr/zookeeper/zookeeper-3.4.12目录下新建data,logs两个文件夹。
mkdir /usr/zookeeper/zookeeper-3.4.12/data
mkdir /usr/zookeeper/zookeeper-3.4.12/logs

(4)进入zookeeper -3.4.12/conf目录,把zoo_sample.cfg文件复制一份名字改成zoo.cfg。
复制命令如下
cp zoo_sample.cfg zoo.cfg

(5)修改zoo.cfg文件,需要修改一下几个地方:
dataDir=/usr/zookeeper/zookeeper-3.4.12/data
dataLogDir=/usr/zookeeper/zookeeper-3.4.12/logs
同时在文件末尾添加:
server.1=192.168.86.150:2888:3888
server.2=192.168.86.160:2888:3888
server.3=192.168.86.170:2888:3888

注意:三个server,后面创建的文件myid里面写的X就是server.X=ip:2888:3888 中ip所对应的X
server.X=A:B:C
X-代表服务器编号
A-代表ip
B和C-代表端口,这个端口用来系统之间通信

(7)在slave1和slave2上新建/usr/zookeeper文件夹,将配置好的zookeeper复制到另外两台服务器上。
scp -r /usr/zookeeper/zookeeper-3.4.12 root@slave1:/usr/zookeeper/
scp -r /usr/zookeeper/zookeeper-3.4.12 root@slave2:/usr/zookeeper/

(8)分别在三台服务器的/usr/zookeeper/zookeeper-3.4.12/data/目录下新建myid文件,内容分别为1, 2, 3,
创建方式如下,在192.168.86.150服务器上执行如下命令,另外两台服务器以此类推。
echo “1” > myid
(9)把每台虚拟机上的zookeeper安装目录配置到环境变量中,执行命令vi /etc/profile,如图。

(10)然后让配置文件生效,执行命令
source /etc/profile。
(11)启动服务,在每台虚拟机上执行zookeeper启动命令zkServer.sh(注意,zkServer.sh文件位于$ZOOKEEPER_HOME/bin)。然后用jps查看,如图所示。
执行命令zkServer.sh start

master:

slave1:

slave2:

Zookeeper集群启动的入口类是QuorumPeerMain来加载配置启动QuorumPeer线程。首先我们来看下QuorumPeer, 谷歌翻译quorum是法定人数,定额的意思, peer是对等的意思,那么QuorumPeer中quorum代表的意思就是每个zookeeper集群启动的时候集群中zookeeper服务数量就已经确定了,在每个zookeeper的配置文件中配置集群中的所有机器

注意启动集群中节点时必须按myid的顺序执行启动

(12)启动完成后,查看服务状态。
执行命令zkServer.sh status
master:

slave1:

slave2:

(13)关闭Zookeeper集群。
执行命令zkServer.sh stop

本章介绍了Zookeeper集群的搭建,作为Hadoop大数据生态圈的一部分,Zookeeper实际上是可以在没有安装Hadoop时单独安装的,当然本章安装Zookeeper时是在第十二章实验的3台Linux虚拟机上安装的,但是Hadoop和Zookeeper没有关联。
14.5 Zookeeper客户端命令
14.5.1介绍
Zookeeper命令行工具类似于 Linux的Shell环境,不过功能肯定不及 Shell 啦,但是使用它我们可以简单地对 Zookeeper进行访间、数据创建、数据修改等操作。
zkCLi是Zookeeper安装之后自带的客户端命令行工具,运行zkCli.sh连接到本机端口2181,连接命令为
zkCli.sh -server 127.0.0.1:2181
若想连接到集群或者其他主机,可用以下命令
zkCli.sh -server host:post [host:post,host:post…]
连接成功后,系统会输出 Zookeeper的相关环境以及配置信息。
客户端命令的常见操作如下。
 显示根目录下文件:ls/。使用ls命令来查看当前zookeeper中所包含的内容。
 显示根目录下文件:ls2/。查看当前节点数据并能看到更新次数等数据。
 创建文件,并设置初始内容。create /zk “zkdata”。在当前目录下创建一个新的znode节点“zk”以及与它关联的字符串。
 获取文件内容: get /zk。获取zk所包含的信息。
 修改文件内容: set /zk “zknewdata”。 对zk所关联的字符串进行设置。
 删除文件:delete /zk。删除节点zk。
 history:打印出最近执行的十个命令。
 退出客户端:quit。
 帮助命令:help。

14.5.2操作步骤
(1)启动Zookeeper集群,执行命令zkServer.sh start,如图所示。

(2)查看集群状态,如图所示。

(3)在Zookeeper启动下,master上执行命令,命令目录是$ZOOKEEPER_HOME/bin。
./zkCli.sh -timeout 5000 -r -server master:2181
或者修改主机名为ip地址。
bin/zkCli.sh -timeout 5000 -r -server 192.168.86.150:2181
连接到ZooKeeper服务器,如图所示。

bin/zkCli.sh -timeout 5000 -r -server slave1:2181

bin/zkCli.sh -timeout 5000 -r -server slave2:2181
(4)在Zookeeper客户端执行命令ls /,查看子节点个数,如图所示。
(5)在Zookeeper客户端执行命令create /nodel test,创建节点nodel,数据是test。如图所示。

(6)获取节点数据以及节点其他信息,执行命令:
get /nodel
修改已存在的节点数据,执行命令
set /nodel tt

(8)执行命令stat /,查看当前节点概况(不显示子节点),如图所示。

(9)ZooKeeper做为分布式架构中的重要中间件,通常会在上面以节点的方式存储一些关键信息,默认情况下,所有应用都可以读写任何节点,在复杂的应用中,这不太安全。ZooKeeper通过ACL(Access Controll List)机制来解决访问权限问题。
总体来说,ZooKeeper的节点有5种操作权限:
CREATE、READ、WRITE、DELETE、ADMIN 也就是 增、删、改、查、管理权限,这5种权限简写为crwda(即:每个单词的首字符缩写)
注:这5种权限中,delete是指对子节点的删除权限,其它4种权限指对自身节点的操作权限。
身份的认证有4种方式:
 world:默认方式,相当于全世界都能访问
 auth:代表已经认证通过的用户(cli中可以通过addauth digest user:pwd 来添加当前上下文中的授权用户)
 digest:即用户名:密码这种方式认证,这也是业务系统中最常用的
 ip:使用IP地址认证
Cli命令行下可以这样查看某个节点的权限。

通过getAcl命令可以发现,刚创建的节点,默认是 world,anyone的认证方式,具有cdrwa所有权限。
实现下面的案例,步骤如下:
 创建zookeeper节点zkjiangtao,数据是zkjiangtao-data
[zk: 192.168.86.150:2181(CONNECTED) 9] create /zkjiangtao zkjiangtao-data
Created /zkjiangtao
 创建zookeeper认证用户jiangtao,密码为12345
[zk: 192.168.86.150:2181(CONNECTED) 10] addauth digest jiangtao:12345
 设置zookeeper节点zkjiangtao的ACL控制用户是认证用户jiangtao,拥有r权限
[zk: 192.168.86.150:2181(CONNECTED) 12] setAcl /zkjiangtao auth:jiangtao:12345:r
cZxid = 0x500000012
ctime = Thu Mar 07 01:07:15 PST 2019
mZxid = 0x500000012
mtime = Thu Mar 07 01:07:15 PST 2019
pZxid = 0x500000012
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 15
numChildren = 0
 查看zookeeper节点zkjiangtao的ACL控制用户信息
[zk: 192.168.86.150:2181(CONNECTED) 13] getAcl /zkjiangtao
'digest,'jiangtao:ASfK8BOXi0GRywlk+I3llVQw9lY=
: r

第15章
Zookeeper实验:ACL和进程协作

15.1实验目的
掌握java代码如何连接Zookeeper集群及通过代码对Zookeeper集群下的节点进行增删改查。
掌握Java代码读写ZooKeeper集群目录下的数据,以及线程间的协作。
15.2实验要求
实验1:实现节点的增删改查
实验2:使用Java实现两个线程,一个向ZooKeeper中某一个目录写入数据,另一个监听此目录,若目录下数据有更新则将目录中数据读取并显示出来。
15.3 Zookeeper API操作
Zookeeper API共包含5个包,分别为:
 org.apache.zookeeper
 org.apache.zookeeper.data
 org.apache.zookeeper.server
 org.apache.zookeeper.server.quorum
 org.apache.zookeeper.server.upgrade
其中org.apache.zookeeper包含了Zookeeper类,它是我们编程时最常见的类文件。
Zookeeper类是 Zookeeper客户端库的主要类文件,如果要使用 Zookeeper服务,应用程序首先必须创建一个 Zookeeper实例,这时就需要使用此类。一旦客户端和 Zookeeper服务建立起连接,Zookeeper系统将会分配给此连接会话一个ID值,并且客户端将会周期地向服务器发送心跳来维持会话的连接。只要连接有效,客户端就可以调用 Zookeeper API来做相应的处理。
它主要提供了以下几个常用的方法:创建节点、删除节点、获取节点数据、获取节点下的子节点列表、修改节点数据。
String create(String path,byte[] data,List acl,Create Mode createMode) 创建一个给定的目录节点path,并给它设置数据,CreateMode标识有四种形式的目录节点,分别是PERSISTENT:持久化目录节点。这个节点存储的数据不会丢失;PERSISTENT_SEQUENTIAL:顺序自动编号的目录节点。这种目录节点会根据当前已经存在的节点数自动加1,然后返回给客户端已经成功创建的目录节点名;EPHEMERAL:临时目录节点,一旦创建这个节点的客户端与服务端端口也就是session超时,这种节点会被自动删除;EPHEMERAL_SEQUENTIAL:临时自动编号节点
Stat exists(String path,boolean watch) 判断某个path是否存在,并设置是否监控这个目录节点,这里的watch是在创建Zookeeper示例时指定的watcher,exists方法还有一个重载方法,可以指定特定的watcher
Stat exists(String path,Watcher watcher) 重载方法,这里给某个目录节点设置特定的watcher,watcher在Zookeeper是一个核心功能,watcher可以监控目录节点的数据变化以及子目录的变化,一旦这些状态发生变化,服务器就会通知所有设置在这个目录节点上的watcher,从而每个客户端都很快知道他所关注的目录节点的状态发生变化,而做出相应的变化
void delete(String path,int version) 删除path对应的目录节点,version为-1可以匹配任何节点,也就删除了这个目录节点所有数据
ListgetChildren(String path,boolean watch) 获取指定path下所有子目录节点
Stat setData(String path,byte[] data,int version) 给path设置数据,可以指定这个数据的版本号,如果version为-1就可以匹配任何版本
byte[] getData(String path,byte[] data,Stat stat) 获取这个path对应的目录节点存储的数据。

15.4实验步骤
15.4.1建立项目
(1)打开 Eclipse开发工具,单击File选择“New”→“ Java project”,新建名称为“zookeeper”的Java项目,单击右键“zookeeper项目,选择“New”→“ Package”,如图所示"。

(2)输入包名称“ cn.dzqc.zk",单击“Finish”按钮,如图所示。

(3)新建java类,选中包名并单击右键,选择“New”→“Class”,如图所示。

(4)在name项输入“ZookeeperDemo"类名称,单击" Finish”按钮完成,如图所示。

(5)在创建的项目目录下创建文件夹lib,通过选中文件夹“src”并点击右键,选择“New”→“SourceFolder”,在“Folder name”项输入lib,然后点击“finish”,如图所示。

(6)在lib文件夹下的添加7个jar包。分别是zookeeper-3.4.12.jar,audience-annotations-0.5.0.jar,jline-0.9.94.jar,netty-3.10.6.Final.jar,log4j-1.2.17.jar,slf4j-api-1.7.25.jar,slf4j-log4j12-1.7.25.jar。zookeeper-3.4.12.jar文件在zookeeper-3.4.12.tar.gz解压缩的根目录下,其余6个jar文件在lib文件夹下。
(7)选中lib下的所有jar包,单击右键,然后选择“ Add to Build Path”即可把所有jar包添加到path环境中。
15.4.2编写代码
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;

public class ZookeeperDemo {
public static void main(String[] args) throws Exception{
//创建连接,指定Zookeeper集群节点,连接超时时间,服务是否监控
ZooKeeper zk = new ZooKeeper(“192.168.86.150:2181,192.168.86.160:2181,191.169.86.170:2181”, 3000, null);
System.out.println("=创建节点===");
// zk.exists() 判断节点是否存在
if(zk.exists("/test", false) == null) {
//Ids.OPEN_ACL_UNSAFE 标识节点开启接入权限
//CreateMode.PERSISTENT 持久化目录节点
zk.create("/test", “znode1”.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
System.out.println(“查看节点是否安装成功==”);
System.out.println(new String(zk.getData("/test", false, null)));

    System.out.println("=========修改节点的数据==========");  

zk.setData("/test", “zNode2”.getBytes(), -1);
System.out.println(“查看修改的节点是否成功=”);
System.out.println(new String(zk.getData("/test", false, null)));

    System.out.println("=======删除节点==========");  

zk.delete("/test", -1);
System.out.println(“查看节点是否被删除==”);
System.out.println(“节点状态:” + zk.exists("/test", false));
zk.close();
}
}
15.4.3启动集群
(1)启动服务,在每台虚拟机上执行启动命令,用jps查看,如图所示。
执行命令zkServer.sh start

master:

slave1:

slave2:

(2)启动完成后,查看服务状态。
执行命令zkServer.sh status
master:

slave1:

slave2:

15.4.4运行项目

第16章
Hadoop实验:Hadoop高可用集群

16.1 Hadoop高可用(HA)的原理
Hadoop2.0之前,在HDFS集群中NameNode存在单点故障(single point of failure,缩写SPOF。是指系统中一点失效,就会让整个系统无法运作的部件,换句话说,单点故障即会整体故障)。对于只用一个NameNode的集群,若NameNode机器出现故障,则整个集群无法使用,直到NameNode重新启动。
Hadoop2.0的高可用简称为HA(High Available)。高可用机制有两个NameNode,一个是active NameNode,状态是active。另外一个是standby NameNode,状态是standby。两者的状态是可以切换的,但不能同时两个都是active状态,最多只有1个是active状态。只有active NameNode提供对外的服务,standby NameNode是不对外服务的。当active NameNode宕机后,也可以把standby NameNode切换成active状态,成为active NameNode。切换工作可以进行人工切换和自动切换。人工切换是通过执行Hadoop管理的命令来改变NameNode的状态,从standby到active,或者从active到standby。自动切换则在active NameNode宕机的时候,standby NameNode自动切换成active状态,取代原来的active NameNode成为新的active NameNode,Hadoop继续正常工作。
16.2 Hadoop与Zookeeper的关系
主节点(active NameNode)和备份节点(standby NameNode)的自动切换需要配置Zookeeper。active NameNode和standby NameNode把他们的状态实时记录到Zookeeper中,Zookeeper监视他们的状态变化。当Zookeeper发现active NameNode宕机后,会自动把standby NameNode切换成active NameNode。
16.3Hadoop高可用的节点分布
IP地址/主机名 NameNode DataNode ResourceManager NodeManager Zookeeper JournalNode zkfc
192.168.11.10/master √ √ √ √ √ √ √
192.168.11.20/slave1 √ √ √ √ √ √ √
192.168.11.30/slave2 √ √ √ √
注意:√表示含有此进程,3台IP地址分别是192.168.11.X。X分别是10、20、30。
master和slave1都是NameNode,其中一个是active NameNode,另一个是standby NameNode。master,slave1,slave2同时都是DataNode。在Hadoop集群搭建中,一台虚拟机既可以是NameNode,同时也可以是DataNode。
16.3.1 JournalNode
Hadoop高可用集群中有两个NameNode,两个NameNode为了数据同步,会通过一组称作JournalNodes的独立进程进行相互通信,同时实现共享editLog。当active状态的NameNode的命名空间有任何修改时,会告知大部分的JournalNodes进程。standby状态的NameNode有能力读取JournalNodes中的变更信息,并且一直监控editlog的变化,把变化应用于自己的命名空间。standby可以确保在集群出错时,命名空间状态已经完全同步了。如下图所示。

16.3.1 zkfc
zkfc全称ZooKeeperFailoverController, zkfc是要和NameNode一一对应的,两个NameNode就要部署两个zkfc。它负责监控NameNode的状态,并及时的把状态信息写入Zookeeper。它通过一个独立线程周期性的调用NameNode上的一个特定接口来获取NameNode的健康状态。zkfc也有选择谁作为Active NameNode的权利,因为最多只有两个节点,目前选择策略还比较简单(先到先得,轮换)。
16.4Hadoop高可用集群搭建
16.4.1创建虚拟机
在VMware Workstation上创建3台虚拟机,步骤如下:
(1)找到桌面的虚拟机图标,双击后,启动VMware界面,选择“创建新的虚拟机”,如下图,即会弹出向导。

(2)点击下一步,初学者使用典型配置。

(3)选择iso镜像文件。

(4)选择镜像文件。

(5)打开虚拟机向导。

(6)填写用户信息。

备注:写上全名,用户名,密码(用户名是用来登录Linux系统的,配合密码可以完成登录。全名只是对用户名的一个备注说明。)

(7)设置虚拟机名称和位置。

(8)设置虚拟机磁盘容量,磁盘容量默认即可 20G已够用。

(9)安装完成。

(10)启动虚拟机。
虚拟机已开始启动

等待几分钟

16.4.2配置网络
(1)在master Linux虚拟机命令终端,执行命令cd /etc/sysconfig/network-scripts,切换到该目录并查看该目录下的文件ifcfg-eth0,如图所示。

(2) 在Linx系统命令终端,执行命令 vim ifcfg-eth0,并修改文件的内容,按“键入编辑内容编译完成后按Esc键退出编译状态,之后执行命令wq,保存并退出。IPADDR、 NETMASK、 GATEWAY、DNS1的值可以根据自己的本机进行修改,如下所示。
DEVICE=“eth0” #设备名字
BOOTPROTO=“static” #静态ip
HWADDR=“00:0C:29:ED:83:F7” #mac地址
IPV6INIT=“yes”
NM_CONTROLLED=“yes”
ONBOOT=“yes” #开启自启动
TYPE=“Ethernet” #网络类型
UUID=“28354862-67a7-4a5b-9f9a-54561401f614”
IPADDR=192.168.11.10 #IP地址
NETMASK=255.255.255.0 #子网掩码
GATEWAY=192.168.11.2 #网关
DNS1=192.168.11.2 # dns

单击编辑菜单下的虚拟网络编辑器菜单,打开虚拟网络编辑器窗体,如下图所示。

(3)配置iP地址完毕之后,在命令终端的任意目录下,执行命令 ifconfig,查看配置效果,如图所示。

(4)在命令终端的任意目录下重启服务,执行命令reboot。
(5) ping ip地址看是否安装成功,如图所示。

(6)剩下两台都按步骤配置。
16.4.3修改主机名和域名映射
(1)启动命令终端,在任何目录下执行命令cd /etc/sysconfig,切换到该目录并查看目录下的文件,可以发现存在文件 network,如图所示。

(2)在/etc/sysconfig目录下找到文件 network,然后执行命令 vim network,按“i”进入编辑内容,编译完成后按Esc退出编译状态,之后执行命令wq保存并退出,后面两台也都这样,如下图所示。

(3)修改主机名和iP地址具有映射关系,执行命令vim /etc/hosts,按“i”进入编辑内容,编译完成后按Esc退出编译状态,之后执行命令wq保存并退出,把三台的ip和主机名都编辑,如图所示。

(4)编辑好,通过指定命令scp -r /etc/hosts root@slave1:/etc/hosts ,把修改好的发送给slave1,再用相同的方法,发送给slave2。

16.4.4在linux下安装Java
(1)启动 Linux命令终端,分别在三台虚拟机上创建目录,执行命令mkdir /usr/java,切换到该目录下执行命令cd/usr/java,
[root@hadoop ~] mkdir /usr/java
[root@hadoop ~]cd /usr/java
(2)把JDK文件jdk-8u181-linux-x64.tar.gz上传到该目录下
(3)然后对/usr/java目录下的JDK压缩文件jdk-8u181-linux-x64.tar.gz,执行命令
对jdk-8u181-linux-x64.tar.gz进行解压
[root@hadoop java]#tar -xzvf jdk-8u181-linux-x64.tar.gz
(4)解压之后,执行命令 Il,可以看到该目录下多了一个解压后的Jdk文件,如图2-43所示。

(5)把jdk文件上传到其他两台,通过命令上传到其他两台虚拟机上,指定命令scp –r /usr/java root@主机名:/usr
[root@master ~] scp -r /usr/java root@slave1:/usr
[root@master ~] scp -r /usr/java root@slave2:/usr
(6)然后到slave1和slave2的/usr目录下看,是否有java这个目录
(7)完成上一步之后,可以执行cd jdk.1.7.0_80,进入JDK安装目录

(8)确定解压无误之后,此时需要配置JDK环境变量,执行命令 vim /etc/profile单击”i“进入编辑内容,编译完成后按Esc退出编译状态,之后执行命令wq保存并退出。如图

(9)编辑完后进行配置文件刷新,执行命令 source /etc/profile,刷新配置,配置的信息才会生效,如图所示。
[root@hadoop jdk1.7.0_80]# source /etc/profile
(10)完成以上步骤之后,需要测试环境变量是否配置成功,只需要在任何目录下执行Java –version ,如图,出现下图情况就是配置成功。

16.4.5关闭防火墙
service iptables stop//关闭防火墙
chkconfig iptables off //永久性关闭防火墙

16.4.6 SSH免密
(1)在Linux系统的终端的任何目录下通过切换cd ~/.ssh,进入到.ssh目录下,如图

~表示当前用户的home目录,通过cd ~可以进入到你的home目录。.开头的文件表示隐藏文件,这里.ssh就是隐藏目录文件。
(2)在Linux系统命令框的.ssh目录下
[root@root .ssh]# ssh-keygen -t rsa

(连续按四次回车)执行完上面命令后,会生成两个id_rsa(私钥)、id_rsa.pub(公钥)两个文件,如图所示

(3)实现SSH免密码
[root@hadoop .ssh]# ssh-copy-id master
[root@hadoop .ssh]# ssh-copy-id slave1
[root@hadoop .ssh]# ssh-copy-id slave2
给主机和其他两台都设置免密码登录,这样三台可以互通。(根据提示输入yes并输入访问主机所需要的密码。)

(4)执行ssh master&ssh slave1&ssh slave2命令,发现不需要密码就能连接任意一台虚拟机,如图所示。


16.4.7配置时间同步服务
(1)安装NTP服务。
在各节点执行命令 yum install y ntp即可。若是最终出现了“Complete”信息,就说明安装NTP服务成功。
(2)设置master节点为NTP服务主节点,那么其配置如下。
使用命令“vim /etc/ntp.conf”来打开/etc/ntp.conf文件,注释掉以 server开头的行,并添加代码所示的内容。
restrict 192.168.0.0 mask 255.255.255.0 nomodify notrap
server 127.127.1.0
fudge 127.127.1.0 stratum 10

(3)分别在slave1,slave2中配置NTP,同样修改/etc/ntp.conf文件,注释掉server开头的行,并添加下面代码所示的内容。
server master

(4)执行命令“service iptables stop& chkconfig iptables off",永久性关闭防火墙,主节点和从节点都要关闭(上面的步骤中已经关闭了防火墙)。
(5)启动NTP服务
①在 master节点执行命令“ service ntpd start& chkconfig ntpd on”,如下图所示,说明NTP服务启动成功。

②在slave1、 slave2上执行命令“ ntpdate master”,即可同步时间,如下图所示。

③在 slave1、slave2上分别执行“ service ntpd start & chkconfig ntpd on”,即永久启动NTP服务,如下图所示。

④在master、slave1、slave2上分别输入date,看时间是否一致。

16.4.8 Zookeeper的分布式集群安装
(1)master虚拟机上启动 Linux命令终端,创建目录mkdir /usr/zookeeper,执行命令cd/usr/zookeeper,切换到该目录下,把zookeeper文件上传到该目录下,如图所示。

(2)然后对/usr/zookeeper目录下的zookeeper压缩文件zookeeper -3.4.12.tar.gz,执行命令 tar -zxvf zookeeper-3.4.12.tar.gz -C /usr/zookeeper -C是指解压压缩包到指定位置
(3)在/usr/zookeeper/zookeeper-3.4.12目录下新建data,logs两个文件夹。执行命令
mkdir /usr/zookeeper/zookeeper-3.4.12/data
mkdir /usr/zookeeper/zookeeper-3.4.12/logs
(4)进入zookeeper-3.4.12/conf目录,把zoo_sample.cfg文件复制一份名字改成zoo.cfg。
cp zoo_sample.cfg zoo.cfg
(5)修改zoo.cfg文件,需要修改一下几个地方:
dataDir=/usr/zookeeper/zookeeper-3.4.12/data
dataLogDir=/usr/zookeeper/zookeeper-3.4.12/logs
同时在文件末尾添加:
server.1=192.168.11.10:2888:3888
server.2=192.168.11.20:2888:3888
server.3=192.168.11.30:2888:3888
注意:这里的IP地址是三台Linux虚拟机的IP地址,需要自行修改。
(7)将配置好的zookeeper复制到另外两台服务器上。
scp -r /usr/zookeeper/zookeeper-3.4.12 root@slave1:/usr/zookeeper/
scp -r /usr/zookeeper/zookeeper-3.4.12 root@slave2:/usr/zookeeper/
(8)分别在三台服务器的/usr/zookeeper/zookeeper-3.4.12/data/目录下新建myid文件,内容分别为1, 2, 3,例如:在192.168.11.10服务器上执行如下命令,另外两台服务器以此类推。
echo “1” > myid
(9)把每台虚拟机上的zookeeper安装目录配置到环境变量中,执行命令vi /etc/profile,如图。

(10)然后让配置文件生效,执行命令 source /etc/profile。
执行完这一步后,将系统重新启动。
(11)在每台虚拟机上启动Zookeeper服务,执行启动命令,用jps查看,如图所示。
执行命令zkServer.sh start

master:

slave1:

slave2:

(12)启动完成后,查看服务状态。
执行命令zkServer.sh status
master:

slave1:

slave2:

(13)关闭Zookeeper集群。
3台Linux虚拟机上执行命令zkServer.sh stop
本节讲解了3台Linux虚拟机上安装了Zookeeper,Zookeeper可以单独安装,下面一节我们讲解如何进行Hadoop分布式集群安装,并在集群安装中配置Zookeeper。

16.4.9 Hadoop分布式集群安装
(1)解压hadoop到master虚拟机的指定目录(/usr/hadoop)
(2)创建目录mkdir /usr/hadoop,执行命令cd/usr/hadoop,切换到该目录下,把Hadoop文件上传到该目录下
(3)然后对/usr/hadoop目录下的Hadoop压缩文件hadoop-2.6.5.tar.gz,执行命令 tar -zxvf hadoop-2.6.5.tar.gz -C /usr/hadoop -C是指解压压缩包到指定位置
(4)修改配置文件
切换到$HADOOP_NAME/etc/hadoop 目录下并查看该目录下的包,如图所示。

下面我们需要修改以下的8个文件。
文件名 文件路径
hadoop-env.sh $HADOOP_NAME/etc/hadoop
core-site.xml $HADOOP_NAME/etc/hadoop
hdfs-site.xml $HADOOP_NAME/etc/hadoop
mapred-site.xml $HADOOP_NAME/etc/hadoop
yarn-site.xml $HADOOP_NAME/etc/hadoop
yarn-env.sh $HADOOP_NAME/etc/hadoop
profile /etc/profile
slaves H A D O O P N A M E / e t c / h a d o o p ( 5 ) 在 HADOOP_NAME/etc/hadoop (5)在 HADOOPNAME/etc/hadoop5HADOOP_NAME/etc/hadoop目录下执行命令vi hadoop-env.sh,按“i”键进入编辑内容,在文件中添加如下内容:
export JAVA_HOME=/usr/java/jdk1.7.0_80
export HADOOP_COMMON_LIB_NATIVE_DIR= H A D O O P P R E F I X / l i b / n a t i v e e x p o r t H A D O O P O P T S = " − D j a v a . l i b r a r y . p a t h = {HADOOP_PREFIX}/lib/native export HADOOP_OPTS="-Djava.library.path= HADOOPPREFIX/lib/nativeexportHADOOPOPTS="Djava.library.path=HADOOP_PREFIX/lib"
编译完成后,按ESE退出编辑状态,之后执行命令wq保存并退出

(6)在$HADOOP_NAME/etc/hadoop目录下执行命令vi core-site.xml,并修改配置文件core-site.xml ,内容如下

fs.defaultFS hdfs://ns1 hadoop.tmp.dir /usr/hadoop/tmp ha.zookeeper.quorum master:2181,slave1:2181,slave2:2181 (7)$HADOOP_NAME/etc/hadoop目录下执行命令vi hdfs-site.xml,并修改配置文件hdfs-site.xml ,内容如下 dfs.replication 3 dfs.nameservices ns1 dfs.ha.namenodes.ns1 rm1,rm2 dfs.namenode.rpc-address.ns1.rm1 master:9000 dfs.namenode.rpc-address.ns1.rm2 slave1:9000 dfs.namenode.http-address.ns1.rm1 master:50070 dfs.namenode.http-address.ns1.rm2 slave1:50070 dfs.namenode.shared.edits.dir qjournal://master:8485;slave1:8485;slave2:8485/ns1 dfs.journalnode.edits.dir /usr/hadoop/hadoop-2.6.5/tmp/data/dfs/journalnode dfs.ha.automatic-failover.enabled true dfs.client.failover.proxy.provider.ns1 org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider dfs.ha.fencing.methods sshfence dfs.ha.fencing.ssh.private-key-files /root/.ssh/id_rsa dfs.namenode.name.dir file:///usr/hadoop/hdfs/name dfs.datanode.data.dir file:///usr/hadoop/hdfs/data dfs.webhdfs.enabled true 注意:不要能混httpfs-site.xml和hdfs-site.xml (8)在$HADOOP_NAME/etc/hadoop目录下查看是否有配置文件mapred-site.xml。目录下默认情况下没有该文件,可通过执行命令mv mapred-site.xml.template mapred-site.xml,修改一个文件的命名,然后执行命令vi mapred-site.xml,并修改配置文件mapred-site.xml,内容如下:        mapreduce.framework.name     yarn    (9)在$HADOOP_NAME/etc/hadoop目录下执行命令vi yarn-site.xml,并修改配置文件yarn-site.xml ,内容如下           yarn.nodemanager.aux-services     mapreduce_shuffle          yarn.resourcemanager.webapp.address master:8088 yarn.resourcemanager.webapp.address slave1:8088 yarn.resourcemanager.address master:8032 yarn.resourcemanager.address slave1:8032 yarn.resourcemanager.resource-tracker.address master:8031 yarn.resourcemanager.resource-tracker.address slave1:8031 yarn.resourcemanager.scheduler.address master:8030 yarn.resourcemanager.scheduler.address slave1:8030 yarn.resourcemanager.ha.rm-ids rm1,rm2 yarn.resourcemanager.hostname.rm1 master yarn.resourcemanager.hostname.rm2 slave1 yarn.resourcemanager.zk-address master:2181,slave1:2181,slave2:2181 yarn.resourcemanager.ha.automatic-failover.enabled true yarn.resourcemanager.recovery.enabled true (10)在$HADOOP_NAME/etc/hadoop目录下执行命令vi yarn-env.sh,并修改配置文件yarn-env.sh ,内容如下 export HADOOP_COMMON_LIB_NATIVE_DIR=${HADOOP_PREFIX}/lib/native export HADOOP_OPTS="-Djava.library.path=$HADOOP_PREFIX/lib" (11)执行命令vi /etc/profile,把Hadoop的安装目录配置到环境变量中,如图

(12)然后让配置文件生效,执行命令 source /etc/profile
(13)在$HADOOP_NAME/etc/hadoop目录下执行命令vi slaves,并修改配置文件slaves,内容如下。
master
slave1
slave2
16.4.10将hadoop分发到slave1、slave2上。
在Linux虚拟机slave1和slave2上创建目录/usr/hadoop,并将master上配置好的Hadoop分发到slave1和slave2,执行下面的命令。
scp -r /usr/hadoop/hadoop-2.6.5 root@slave1:/usr/hadoop/ls
scp -r /usr/hadoop/hadoop-2.6.5 root@slave2:/usr/hadoop/

16.4.11集群启动
在执行集群启动步骤前必须保证网络通畅。
(1)在master、slave1、slave2 三个节点执行命令启动zookeeper,并查看运行的程序。
zkServer.sh start
jps
注意:执行上面命令时必须保证网络通畅。
(2)在master、slave1、slave2 三个节点执行命令验证zookeeper集群状态。
zkServer.sh status
master:

slave1:

slave2:

下面是3个节点启动的进程。
IP地址/主机名 NameNode DataNode ResourceManager NodeManager Zookeeper JournalNode zkfc
master √
slave1 √
slave2 √

(3)在master上启动JournalNode集群
执行$HADOOP_HOME/sbin目录下hadoop-daemons.sh。
[root@master hadoop]# hadoop-daemons.sh start journalnode

执行上面的命令,会将执行信息写入相应的日志文件,然后执行jps命令,可以查看到3台Linux虚拟机都启动了JournalNode的进程。

下面是3个节点启动的进程。
IP地址/主机名 NameNode DataNode ResourceManager NodeManager Zookeeper JournalNode zkfc
master √ √
slave1 √ √
slave2 √ √
(4)在master上格式化zkfc
[root@master hadoop]# hdfs zkfc -formatZK
(5)在master上格式化hdfs
[root@master hadoop]# hadoop namenode -format
(6)在master上启动namenode
执行KaTeX parse error: Expected 'EOF', got '#' at position 58: …@master hadoop]#̲ hadoop-daemon.…HADOOP_HOME/sbin目录下hadoop-daemon.sh
slave1启动namenode
[root@ slave1 hadoop]# hadoop-daemon.sh start namenode
下面是3个节点启动的进程。
IP地址/主机名 NameNode DataNode ResourceManager NodeManager Zookeeper JournalNode zkfc
master √ √ √
slave1 √ √ √
slave2 √ √

(8)在master 上启动所有节点的datanode
[root@ master hadoop]# hadoop-daemons.sh start datanode
hadoop-daemons.sh文件在$HADOOP_HOME/sbin目录下
注意:

  1. hadoop-daemons.sh和hadoop-daemon.sh的区别
    hadoop-daemon.sh:用于启动当前节点的进程
    例如hadoop-daemon.sh start namenode 用于启动当前的名称节点
    hadoop-daemons.sh:用于启动所有节点的进程
    例如:hadoop-daemons.sh start datanode 用于启动所有节点的数据节点
    2.如果出现DataNode无法启动时,需要看对应的log文件,如下面显示的内容。

如果出现下面的错误,
2017-04-15 21:21:15,952 FATAL org.apache.hadoop.hdfs.server.datanode.DataNode: Initialization failed for Block pool (Datanode Uuid unassigned) service to ip:port. Exiting.
java.io.IOException: All specified directories are failed to load.
原因是clusterID不一致,保证3台Linux虚拟机的/usr/hadoop/hdfs/name/current/VERSION和/usr/hadoop/hdfs/data/current/VERSION中clusterID完全匹配。
下面是3个节点启动的进程。
IP地址/主机名 NameNode DataNode ResourceManager NodeManager Zookeeper JournalNode zkfc
master √ √ √ √
slave1 √ √ √ √
slave2 √ √ √

(9)在master上启动yarn
执行$HADOOP_HOME/sbin目录下start-yarn.sh
[root@master hadoop]# start-yarn.sh
(10)在slave1上启动yarn
[root@master hadoop]# start-yarn.sh
注意:执行这个命令时有一个提示信息,具体内容如下。
master: nodemanager running as process 3515. Stop it first.
slave1: nodemanager running as process 3515. Stop it first.
slave2: nodemanager running as process 3515. Stop it first.
这个没有问题,可以继续操作。
下面是3个节点启动的进程。
IP地址/主机名 NameNode DataNode ResourceManager NodeManager Zookeeper JournalNode zkfc
master √ √ √ √ √ √
slave1 √ √ √ √ √ √
slave2 √ √ √ √

(11)在master上启动zkfc
[root@masterhadoop]# hadoop-daemons.sh start zkfc
(12)全部启动后分别在master、slave1、slave2上执行jps 可以看到下面这些进程

如果集群启动时出现异常,并且确定不是配置时出现的问题,可以将3台Linux虚拟机中的以下目录删除,实现集群启动信息的初始化,再重新进行集群启动。具体步骤如下:
 $HADOOP_NAME/logs下的所有目录和文件
 /usr/hadoop/tmp下的所有目录和文件
 /usr/hadoop/hdfs/name和/usr/hadoop/hdfs/data下的所有目录和文件
重启3台Linux后重新进行集群启动,注意网络必须通畅。
(12)web页面查看
http:// 192.168.11.10:50070 (HDFS管理界面),如图

http:// 192.168.11.20:50070 (HDFS管理界面),如图

注意:master和slave1两台电脑分别是active和standby模式启动。
http:// 192.168.11.20:8088(MR管理页面),如图

(13)测试HA高可用性
①在slave1(active)节点关闭namenode,按照高可用性Hadoop集群规则,master(standby)节点会自动成为active节点。
[root@ slave1 hadoop]# hadoop-daemon.sh stop namenode

②在slave1节点开启namenode,由于master(standby)节点已经是active节点,所以再次启动后的启动状态还是active。
[root@ slave1hadoop]# hadoop-daemon.sh start namenode

本章结束后实现了Hadoop的分布式集群搭建,并能够通过Zookeeper实现对Hadoop集群的监控,并且实现了高可用性。

16.5 Hadoop集群启动
以上操作完成后集群启动成功,大家可能会觉得启动Hadoop高可用集群步骤太多。其实第一次启动以后每次执行启动集群只需要在两步:
 3个节点都启动Zookeeper
 master执行命令stop-all.sh
 master执行命令start-all.sh

注意:
2. 如果出现某个节点上resourcemanager没有启动,可以重新停止resourcemanager后再启动。
yarn-daemon.sh stop resourcemanager
yarn-daemon.sh start resourcemanager
第17章
Hadoop数据仓库Hive

17.1 初识Hive
Hive是 Apache基金会下面的开源框架,是基于Hadoop的数据仓库工具,它可以把结构化的数据文件映射为一张数据仓库表,并提供简单的SQL(Structured Query Language)查询功能,后台将SQL语句转换为 MapReduce 任务来运行。
Hive和 HBase是两种基于 Hadoop的不同技术——Hive是一种类SOL的引擎,并且运行 MapReduce任务, HBase是一种在 Hadoop之上的 NoSQL的 Key/value数据库。
Hive在英文中的意思是蜂箱、蜂巢、储备、积累的意思,正与数据仓库容纳海量有价值的数据的含义相仿。图17.1是Hive的形象化标志,看起来像一只飞起的蜜蜂,但有大象的头,表明与 Hadoop是一家,是基于 Hadoop的数据仓库。

对于用惯了传统数据库(比如MySQL、 Oracle)的人来说,由于对传统的SQL非常了解,在开始使用 HiveSQL的时候,上手会比较快,因为它们与基础的SQL结构是类似的;当然,HiveSQL为分布式计算做了一些优化。
由于Hive查询使用的是Hadoop的MapReduce作业,所以大部分查询过程都是一个批量操作的过程,因此与MapReduce一样,会是高延迟的,所以Hive不适合那些低延迟的应用。同时,Hive也不支持对已有数据的修改和追加。
17.1.1 Hive数据类型
Hive具有支持的数据类型,主要分为两种:基本数据类型和复杂数据类型,复杂数据类型也可以称为集合数据类型。
(1)基本数据类型
类型 描述 实例
tinyint 1字节int 10
smallint 2字节int 10
int 4字节int 10
bigint 8字节int 10
boolean 布尔型 true ,false
float 单精度浮点型 1.4
double 双精度浮点型 1.5
string 字符串 “hello world”
(2)复杂数据类型
类型 描述 实例
Array 一组有序字段,字段的类型必须相同 Array(1,3,4)
Map 一组无序键值对,键的类型是原子的,值的类型可以是任意类型。同一个Map的键必须是同一个类型,值必须是同一个类型。 Map(‘a’,1,‘b’,2)
Struct 一组命名的字段。字段的类型可以不同。 Struct(‘jiangtao’,‘男’,35)
下面我们看看Hive使用复杂数据类型的实例,建表:
Create table complex(col1 Array,
col2 Map,
col3 Struct);

17.1.2 Hive Metastore(元数据存储)
Metastore是Hive元数据的集中存放地。Metastore默认使用内嵌的Derby数据库作为存储引擎。Derby引擎的缺点:一次只能打开一个会话,使用MySQL作为外置存储引擎,多用户同时访问。
Hive没有专门的数据存储格式,也没有为数据建立索引,用户可以非常自由地组织Hive中的表,只需要在创建表时告诉Hive数据中的列分隔符和行分隔符,Hive就可以解析数据。
其次,Hive表逻辑上由存储的数据和描述表格形式的相关元数据组成。Hive中数据都存储在HDFS中,元数据放在关系型数据库中。
17.1.3 Hive和传统数据库的区别
 查询语言不同:hive是hql语言,mysql是sql语句;
 数据存储位置不同:hive是把数据存储在hdfs上,而mysql数据是存储在自己的系统中;
 数据格式:hive数据格式可以用户自定义,mysql有自己的系统定义格式;
 数据更新:hive不支持数据更新,只可以读,不可以写,而sql支持数据更新;
 索引:hive没有索引,因此查询数据的时候是通过mapreduce很暴力的把数据都查询一遍,也造成了hive查询数据速度很慢的原因,而mysql有索引;
 延迟性:hive延迟性高,原因就是上边一点所说的,而mysql延迟性低;
 数据规模:hive存储的数据量超级大,而mysql只是存储一些少量的业务数据;
 底层执行原理:hive底层是用的mapreduce,而mysql是excutor执行器;
17.1.4 Hive系统架构
Hive的系统架构组成主要分为四个部分:
 用户接口部分:Hive的用户接口层,CLI即Shell命令行,CLI最常用。接口层包括CLI、JDBC/ODBC、WebUI。
 存放Hive元数据的元数据库:Hive将元数据存储在数据库中,连接到这些数据库(derby,mysql)的模式分三种:单用户模式、多用户模式、远程服务器模式。
元数据包括Database、表名、表的列及类型、存储空间、分区、表数据所在目录等。
 解析器:包括编译器、优化器、执行器。完成HQL查询语句的词法分析、语法分析、编译、优化及查询计划的生成。生成的查询计划存储在HDFS中,并由MapReduce调用执行。
 Hadoop:Hive的数据存储在HDFS中,针对大部分的HQL查询请求,Hive内部自动转换为MapReduce任务执行。

17.2 Hive的数据模型
Hive中包含以下数据模型:内部表(Table)、外部表(External Table)、分区表(Partition)、桶表(Bucket)。
17.2.1 内部表
与数据库中的Table在概念上类似,每一个Table在Hive中都有一个相应的目录存储数据。例如,一个表test,它在HDFS中的路径为/warehouse/test。warehouse是在hive-site.xml中由${hive.metastore.warehouse.dir}指定的数据仓库的目录,所有的Table数据(不包括 External Table)都保存在这个目录中。删除表时,元数据与数据都会被删除。
17.2.2 外部表
外部表和内部表在元数据的组织上是相同的,而实际数据的存储则有较大的差异,内部表的创建过程和数据加载过程可以在同一个语句中完成,在加载数据的过程中,实际数据会被移动到数据仓库目录中;之后对数据的访问将会直接在数据仓库目录中完成。删除表时,表中的数据和元数据将会被同时删除。外部表只有一个过程,加载数据和创建表同时完成,并不会移动到数据仓库目录中,只是与外部数据建立一个链接。当删除一个外部表时,仅删除该链接。
17.2.3 分区表
Hive把表组织成分区,分区可以加快分片的查询速度。以分区的常用情况为例。考虑日志文件,其中每条记录包含一个时间戳。如果我们根据日期进行分区,那么同一天的记录就会被存放到同一个分区中。这样对于限定某个条件的插叙,效率会非常高,因为它只需要扫描查询范围的文件。
17.2.4 桶表
桶表是对数据进行哈希取值,然后放到不同文件中存储。
17.3 Hive总结

  1. 什么是Hive?
    Hive可以访问我们的数据仓库。一般来说,我们也可以基于传统方式(Oracle或者MySQL数据库)来搭建这个数据仓库,这个时候数据仓库中的数据是保存在Oracle或者MySQL的数据库当中的。Hive跟传统方式是不一样的,Hive是建立在Hadoop HDFS基础之上的数据仓库基础框架。也就是说Hive这个数据仓库中的数据是保存在HDFS上。Hive定义了简单的类似SQL查询语言,称为HQL。Hive允许熟悉MapReduce开发者的开发自定义的mapper和reducer来处理內建的mapper和reducer无法完成的复杂的分析工作。Hive是SQL解析引擎,它将SQL语句转移成MapReduce Job,然后在Hadoop上执行。把执行的结果最终反映给用户。Hive的表其实就是HDFS的目录,Hive的数据其实就是HDFS的文件。

  2. Hive的体系结构之元数据
    Hive将元数据(meta data)存储在数据库中(metastore),支持mysql、 derby(默认)数据库。Hive中的元数据包括表的名字,表的列和分区以及属性,表的属性(是否为外部表等),表的数据所在目录等。

  3. Hive的体系结构之HQL的执行过程
    一条HQL语句如何在Hive的数据仓库当中中进行查询的?由解释器、编译器、优化器来共同完成HQL查询语句从词法解析、语法解析、编译、优化以及查询计划(Plan)的生成。生成的查询计划存储在HDFS中,并在随后转成MapReduce调用执行。

第18章
Hive实验:Hive环境搭建

18.1实验目的
1.理解hive存在的原因;
2.理解hive的工作原理;
3.理解hive的体系架构;
4.学会如何进行三种模式部署;
18.2实验要求
1.完成hive的三种模式部署;
2.待hive环境搭建好,能够启动并执行一般命令。
18.3实验条件
第十六章安装高可用性Hadoop。
18.4实验原理
Hive是Hadoop大数据生态圈中的数据仓库,其提供以表格的方式来组织与管理HDFS上的数据、以类SQL的方式来操作表格的数据,Hive的设计目的是能够以类SQL的方式存储在HDFS上的大规模数据集,不必开发专门的MapReduce应用。
Hive本质上相当于一个MapReduce和HDFS的翻译终端,用户提交Hive脚本后,Hive运行时环境会将这些脚本翻译成MapReduce和HDFS操作并向集群提交这些操作。
当用户向hive提交其编写的HiveQL后,首先,Hive运行时环境会将这些脚本翻译成MapReduce和HDFS操作;其次,Hive运行时环境使用Hadoop命令行接口向Hadoop集群提交这些MapReduce和HDFS操作;最后,Hadoop集群逐步执行这些MapReduce和HDFS操作,整个过程可概括如下。
(1)用户编写HiveQL并向Hive运行时环境提交该HiveQL。
(2)Hive运行时环境将该HiveQL翻译成MapReduce和HDFS操作。
(3)Hive运行时环境调用Hadoop命令行接口或程序接口,向Hadoop集群提交翻译后的HiveQL。
(4)Hadoop集群执行HiveQL翻译后的MapReduce-APP或HDFS-APP。
18.5 Hive安装模式
Hive一般在工作站上运行。它把SQL查询转换为一系列在Hadoop集群上运行的作业。Hive部署时按metastore存储位置的不同,其部署模式分为内嵌模式、本地模式和远程模式三种。
 内嵌模式
使用内嵌的derby数据库存储元数据,允许一个会话链接,尝试多个会话链接时会报错。
 本地模式
使用MySQL等其他数据库存储元数据。
 远程模式
将metadata作为一个单独的服务进行启动。各种客户端通过beeline来连接,Beeline是Hive新的命令行客户端工具。
18.6 Hive的内嵌模式安装
(1)master上启动 Linux命令终端,切换到/usr/buildinhive,把hive文件上传到该目录下。
(2)然后对/usr/buildinhive目录下的hive压缩文件apache-hive-1.2.2-bin.tar.gz,执行命令
tar -zxvf apache-hive-1.2.2-bin.tar.gz -C /usr/buildinhive -C是指解压压缩包到指定位置。
(3)切换到/usr/buildinhive/conf目录下,复制hive-env.sh.template文件为hive-env.sh,修改hive-env.sh。执行下面的命令:
cp hive-env.sh.template hive-env.sh
vim hive-env.sh
(4)在hive-env.sh中添加以下内容:
HADOOP_HOME=/usr/hadoop/hadoop-2.6.5
(5)启动Zookeeper
zkServer.sh start
(6)启动hadoop
start-all.sh
5,6两步在高可用Hadoop集群搭建中已经完成。
(7)启动hive
在$HIVE_HOME/bin下启动

说明启动成功
注意:执行上面的命令时会产生下面的错误
Logging initialized using configuration in jar:file:/usr/buildinhive/apache-hive-1.2.2-bin/lib/hive-common-1.2.2.jar!/hive-log4j.properties [ERROR] Terminal initialization failed; falling back to unsupported
java.lang.IncompatibleClassChangeError: Found class jline.Terminal, but interface was expected
这个原因是hive依赖于hadoop,在hive的 H I V E H O M E / l i b / j l i n e − 2.12. j a r 和 h a d o o p 的 HIVE_HOME/lib/jline-2.12.jar和hadoop的 HIVEHOME/lib/jline2.12.jarhadoopHADOOP_HOME/share/hadoop/yarn/lib/jline-0.9.94.jar产生jar包冲突,将jline-0.9.94.jar替换为jline-2.12.jar。

(8)Hive基本命令
进入hive环境后,使用“show tables”后,结果如下图所示,则表示配置成功。

启动Hive后查看运行的进程。

18.7 Hive的本地模式安装
18.7.1 Hive的解压
(1)启动 Linux命令终端,创建目录mkdir /usr/hive,执行命令cd /usr/hive,切换到该目录下,把hive文件上传到该目录下,如图所示。
(2)然后对/usr/hive目录下的hive压缩文件apache-hive-1.2.2-bin.tar.gz,执行命令
tar -zxvf apache-hive-1.2.2-bin.tar.gz -C /usr/hive
-C是指解压压缩包到指定位置。
(3)配置到环境变量,把Hadoop的安装目录配置到/etc/profile中,执行命令vi /etc/profile,如图

18.7.2配置Mysql
(1)卸载CentOS自带的MySQL,然后安装mysql
查找所有预安装的mysql:
rpm -qa | grep mysql

卸载所有预安装的mysql:
rpm -e mysql-libs-5.1.71-1.el6.x86_64 --nodeps
安装mysql:
yum -y install mysql-server
(2)开启mysql
service mysqld start
(3)修改mysql的密码
cd /usr/bin
初始化mysql:./mysql_secure_installation
出现Enter current password for root (enter for none): 因为初始时root是没有密码的, 所以直接回车(enter)。
注:安装完mysql-server 可以运行mysql_secure_installation,进行做一些常规化安全设置。
(4)设置MySQL中root用户的密码(应与下面Hive配置一致,下面设置为123456)
Set root password? [Y/n] Y
New password:
Re-enter new password:
Password updated successfully!
Reloading privilege tables… …
Success!
(5)删除匿名用户,选择Y
Remove anonymous users? [Y/n] Y
… Success!
(6)是否不允许用户远程连接,选择N
Disallow root login remotely? [Y/n] N
… Success!
(7)删除test数据库,选择Y
Remove test database and access to it? [Y/n] Y
Dropping test database…
… Success!
Removing privileges on test database…
… Success!
(8)是否重新加载权限表,选择Y
Reload privilege tables now? [Y/n] Y
… Success!
(9)登陆mysql
mysql -uroot -p123456
(10)赋权限给root用户,使其可以远程登录数据库(windows 客户端可以连接到MySQL数据库)
GRANT ALL PRIVILEGES ON . TO ‘root’@’%’ IDENTIFIED BY ‘123456’ WITH GRANT OPTION;
FLUSH PRIVILEGES;
18.7.3配置hive
(1)编辑conf/hive-env.sh文件
将hive-env.sh.template文件复制为hive-env.sh,编辑hive-env.sh文件
JAVA_HOME=/usr/java/jdk1.7.0_80
HADOOP_HOME=/usr/hadoop/hadoop-2.6.5/
HIVE_HOME=/usr/hive/apache-hive-1.2.2-bin
export HIVE_CONF_DIR= H I V E H O M E / c o n f e x p o r t C L A S S P A T H = HIVE_HOME/conf export CLASSPATH= HIVEHOME/confexportCLASSPATH=CLASSPATH: J A V A H O M E / l i b : JAVA_HOME/lib: JAVAHOME/lib:HADOOP_HOME/lib:$HIVE_HOME/lib
export HADOOP_OPTS="-Dorg.xerial.snappy.tempdir=/tmp -Dorg.xerial.snappy.lib.name=libsnappyjava.jnilib $HADOOP_OPTS"
(2)编辑hive-site.xml文件
将hive-default.xml.template文件复制为hive-site.xml,编辑hive-site.xml文件


javax.jdo.option.ConnectionURL
jdbc:mysql://master:3306/hive?createDatabaseIfNotExist=true
JDBC connect string for a JDBC metastore


javax.jdo.option.ConnectionDriverName
com.mysql.jdbc.Driver
Driver class name for a JDBC metastore


javax.jdo.option.ConnectionUserName
root
username to use against metastore database


javax.jdo.option.ConnectionPassword
123456
password to use against metastore database


属性名 类型 描述
javax.jdo.option.ConnectionURL URI JDBC URI
javax.jdo.option.ConnectionDriverName 字符串 JDBC驱动类
javax.jdo.option.ConnectionUserName 字符串 JDBC用户名
javax.jdo.option.ConnectionPassword 字符串 JDBC密码

18.7.4拷贝JDBC包
将mysql数据库的JDBC jar文件mysql-connector-java-5.1.47.jar上传到 H I V E H O M E / l i b 目 录 下 。 18.7.5 拷 贝 j l i n e 扩 展 包 将 HIVE_HOME/lib目录下。 18.7.5拷贝jline扩展包 将 HIVEHOME/lib18.7.5jlineHIVE_HOME/lib目录下jline-2.12.jar包拷贝到 H A D O O P H O M E / s h a r e / h a d o o p / y a r n / l i b 目 录 下 , 并 删 除 HADOOP_HOME/share/hadoop/yarn/lib目录下,并删除 HADOOPHOME/share/hadoop/yarn/lib,HADOOP_HOME/share/hadoop/yarn/lib目录下旧版本的jline包
18.7.6启动Zookeeper集群
每台虚拟机上执行zkServer.sh start
18.7.7启动hadoop集群
虚拟机master上执行
stop-all.sh
start-all.sh
18.7.8启动hive
执行conf/hive

说明启动成功
18.7.9使用Navicat连接MySQL数据库
(1)先双击“navicat.exe”,出现下面对话框,点击“Next”。

(2)选择“I accept the agreement”,然后点击“Next”。

(3)点击“Browse”,选择你要保存的位置,然后点击“Next”。

(4)点击“Next”。

(5)再点击“Next”。

(6)点击“Install”,之后点击“finish”。

(7)打开桌面上的mysql软件,点击“Connection”->“mysql”。

(8)在“Host Name/IP Address”,填上虚拟机的ip号(如192.168.86.150),在“Password”上填你赋予root权限的密码,然后点击“Test Connect”,看是否连接成功。如果测试成功,就点击“OK”。然后双击点击hive,连接成功。
注意:密码跟配置一样

在linux上的mysql新建一个表,看Windows上是否有
(9)输入命令mysql -uroot -p123456,进入mysql界面

(10)创建hive数据库,并在mysql数据库中创建一个shop的表

(11)打开Windows下的mysql,看是否数据库mysql下有个shop的表

18.8 Hive的远程模式
(1)首先执行hiveserver2命令:
hiveserver2 start
启动后命令行会一直监听不退出,我们可以看到它监听了10000端口。

(2)新开一个命令行窗口,执行beeline命令,进入之后再输入连接master,命令:!connect jdbc:hive2://master:10000/:
(用户名与密码跟配置一样)

第19章
Hive实验:Hive Shell操作

19.1实验目的
1.创建Hive的表;
2.显示Hive中的所有表;
3.显示所有表的选项。
19.2实验要求
1.学会操作创建、删除数据库等功能;
2.学会操作新建、显示、修改和删除表等功能;
3.学会操作创建内部表、外部表、分区和分桶等功能;
4.学会操作4种数据导入方式。
19.3实验原理
Hive没有专门的数据存储格式,也没有为数据创建索引,用户可以非常自由地组织Hive中的表,只需要创建表的时候告诉Hive数据中的列分隔符和行分割符,Hive就可以解析数据。
Hive中所有的数据都存储在HDFS中,Hive中包含以下数据模型:数据库(database),表(table),分区(Partition),桶(Bucket)。
Hive中Table和数据库中Table在概念上是类似的,每一个Table在Hive中都有一个相应的目录存储数据。例如,一个表pvs,它在HDFS中的路径为:/wh/pvs,其中,wh是在conf/hive-site.xml中由${hive.metastore.warehouse.dir}指定的数据仓库的目录,所有的Table数据(不包含External Table)都保存在这个目录中。
Hive Shell是运行在Hadoop环境之上,是实现和Hive进行交互的命令行接口,当执行HiveQL(Hive SQL)命令时,Hive Shell把HiveQL查询语句转换成MapReduce作业进行处理并返回结果,这样简化了数据查询的繁琐性。Hive是使用关系型数据库的组织形式存储HDFS上的存储信息的秒数信息,这些描述信息称为元数据,以Metastore形式存储
19.4实验步骤

  1. 启动zookeeper集群
    [root@master ~]zkServer.sh start
    [root@slave1 ~]zkServer.sh start
    [root@slave2 ~]zkServer.sh start
  2. 启动hadoop集群
    [root@master ~]stop-all.sh
    [root@master ~]start-all.sh
    以上两步参考16.5。
  3. 启动MySQL和hive
    [root@master ~] service mysqld start
    [root@master ~] mysql -uroot -p123456
    [root@master ~]hive
    19.4.1数据库操作
    创建数据库
    创建数据库是用来创建数据库在Hive中语句。在Hive数据库是一个命名空间或表的集合。此语法声明如下:
    CREATE DATABASE [IF NOT EXISTS]
    在这里,IF NOT EXISTS是一个可选子句,通知用户已经存在相同名称的数据库。下面的查询执行创建一个名为userdb数据库:
    hive> CREATE DATABASE IF NOT EXISTS userdb;

    hive> CREATE DATABASE userdb;
    创建完数据库之后,查看userdb数据库是否创建成功
    hive>show databases;
    查询指定的一些数据库,可以使用Like或者正则表达式查看数据库
    hive> show databases like ‘f*’;
    查询已经存在的数据库详细信息
    hive> describe database financials;
    OK
    financials hdfs://ns1/user/hive/warehouse/financials.db root USER
    Time taken: 0.417 seconds, Fetched: 1 row(s)
    可以看出,数据库对应文件为hdfs://ns1/user/hive/warehouse/financials.db。
    使用已经存在的数据库,需要使用use关键字说明之后。
    hive>use userdb;
    hive> show tables;
    删除数据库
    DROP DATABASE是删除所有的表并删除数据库的语句。它的语法如下:
    DROP DATABASE [IF EXISTS] database_name [CASCADE] ;
    下面的语句用于删除数据库。假设要删除的数据库名称为userdb。
    hive> DROP DATABASE IF EXISTS userdb;
    删除一个数据库,默认情况下,hive不允许删除含有表的数据库,要先将数据库中的表清空才能drop,否则会报错。加入CASCADE关键字,可以强制删除一个数据库。
    hive> DROP DATABASE IF EXISTS userdb CASCADE;
    19.4.2表
    创建表
    Create Table是用于Hive中创建表的语句。默认情况下,新建表的存储格式均为Text类型,字段间默认分割符为键盘上的Tab键。语法如下:
    hive> CREATE TABLE [IF NOT EXISTS] table_name

    (字段名 数据类型 , 字段名 数据类型 , …)
    比如我们要创建一个emp表。
    hive> create table emp(
    > name string,
    > salary float,
    > mobile array,
    > deductions map,
    > address structstreet:string,city:string,zip:int);
    显示所有表和em开头的表。
    hive>show tables;
    hive>show tables ‘em*’;
    显示employee表的信息
    hive> describe extended employee;
    OK
    name string
    salary float
    mobile array
    deductions map
    address structstreet:string,city:string,zip:int
    显示employee表name字段的信息
    hive> describe extended employee.name;
    OK
    name string from deserializer
    修改表结构
    在Hive里面,修改表结构的操作与传统数据库类似。
    修改表名
    ALTER TABLE name RENAME TO new_name
    hive> alter table employee rename to emp;
    修改表增加列
    ALTER TABLE name ADD COLUMNS (col_spec, col_spec …)
    hive> alter table emp add columns (birth string);
    修改表替换列
    ALTER TABLE name replace columns(col_spec)
    hive> alter table emp replace columns ( birth string);
    emp表只有birth字段。
    删除表
    Hive中删除表的操作与传统数据库类似,执行后,内部表关联的HDFS上的数据会一并被删除:
    DROP TABLE [IF EXISTS] TABLE_name;
    hive> drop table employee;
    OK
    19.4.3数据导入
    Hive的几种常见的数据导入有四种:
     从本地文件系统中导入数据到Hive表
     从HDFS上导入数据到Hive表
     从别的表中查询出相应的数据并导入到Hive表中
     在创建表的时候通过从别的表中查询出相应的记录并插入到所创建的表中
    一、从本地文件系统中导入数据到Hive表
    load data local inpath ‘datapath’ into table table_name;
    第一步:Linux虚拟机的本地文件系统/usr目录下创建emp.txt文件。

第二步:hive中创建文件
hive> create table emp
> (id int,
> name string,
> age int,
> tel string)
> ROW FORMAT DELIMITED
> FIELDS TERMINATED BY ‘\t’
> STORED AS TEXTFILE;
 ROW FORMAT DELIMITED:使用自带的序列化和反序列化功能。
 FIELDS TERMINATED BY ‘\t’:来源的文本数据的字段间隔符
 STORED AS TEXTFILE:数据压缩。
第三步:本地文件数据导入hive表
hive> load data local inpath ‘/usr/emp.txt’ into table emp;
第四步:HQL查询hive表数据
hive> select * from emp;
二、从HDFS上导入数据到Hive表
load data inpath ‘HDFSPath’ into table table_name;
第一步:本地文件复制到HDFS系统上
[root@master Desktop]# hadoop fs -put /usr/emp.txt /user
第二步:创建emp1表
hive> create table if not exists emp1 like emp;
第三步:HDFS系统文件数据导入hive表
hive> load data inpath ‘/user/emp.txt’ into table emp1;
第四步:HQL查询hive表数据
hive> select * from emp1;
三、通过查询语句向表中插入数据;
hive> insert overwrite table emp3 select id,name,age from emp1;
四、在创建表的时候通过从别的表中查询出相应的记录并插入到所创建的表中。
hive> create table emp2 as select id,name,age from emp1;
五、导出数据到本地
hive> insert overwrite local directory ‘/usr/employee’ select * from emp;
六、导出数据到HDFS
hive> insert overwrite directory ‘/user/employee’ select * from emp;

第20章
Hive实验:Hive 分区

20.1实验目的
掌握Hive分区的用法,加深对Hive分区概念的理解,了解Hive表在HDFS的存储目录结构。
20.2实验要求
创建一个Hive分区表;根据数据年份创建year=2015这个分区;将2015年的数据导入到year=2015的分区;在Hive界面用条件year=2015查询2015年的数据。
20.3实验原理
在Hive Select查询中一般会扫描整个表内容,会消耗很多时间做没必要的工作。有时候只需要扫描表中关心的一部分数据,因此建表时引入了分区(partition) 概念。分区表指的是在创建表时指定的partition的分区空间。如果需要创建有分区的表,需要在create表的时候调用可选参数partitioned by,详见表创建的语法结构。
 一个表可以拥有一个或者多个分区,每个分区以文件夹的形式单独存在表文件夹的目录下。
 表和列名不区分大小写。
 分区是以字段的形式在表结构中存在,通过describe table命令可以查看到字段存在,但是该字段不存放实际的数据内容,仅仅是分区的表示。
Hive可以对数据按照某列或者某些列进行分区管理,分区一般是按列进行分区,可以是多个列,语法如下:
CREATE [EXTERNAL] TABLE [IF NOT EXISTS][db_name] table_name

(col_name data_type ,col_name data_type,…)
partitioned by(col_name data_type,…)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ‘\t’
LINES TERMINATED BY ‘\n’
20.4实验步骤
20.4.1启动Zookeeper集群
[root@master~]zkServer.sh start
20.4.2启动Hadoop集群
[root@master ~]start-all.sh
20.4.3启动 Hive
[root@master ~] service mysqld start
[root@master ~]hive
20.4.4在linux上创建数据
[root@master ~]vi employee.txt

注意:数据之间用tab键隔开。
20.4.5通过HQL语句进行试验
进入客户端后,查看Hive数据库,并选择default数据库:

在命令端创建Hive分区表

查看新建的表:

给emp_partition表创建12个分区:

针对每个月的数据有一个分区。
查看emp_partition的表结构:

向hiremonth=n分区导入本地数据(n指的是每个月份,从1到12):

上面的步骤执行完成后,hive会为分区表创建一个目录,每个分区形成一个子目录,每个子目录下有各自的数据文件,如图所示。

根据条件查询hiremonth=1的数据:

第21章
HBase

21.1 HBase简介
HBase是一个基于HDFS的面向列的分布式数据库,源于Google的 《BigTable:一个结构化数据的分布式存储系统》论文。前面提过,HDFS是基于流式数据访问,对于低时间延迟的数据访问并不适合在HDFS上运行。如果需要实时地随机流问超大规模数据集,使用HBase是更好的选择。使用HBase技术可以在廉价PC Server上搭建大规模结构化存储集群,并利用Hadoop的HDFS作为其文件存储系统,利用Hadoop MapReduce来处理HBase中的海量数据,利用Zookeeper作为协同服务。
HBase是数据库,但并不是传统的关系型数据库,如Oracle、SOL Server、MySql等,这些数据库本身并不是为了可扩展的分布式处理而设计。HBase不支持关系型数据库的SOL,并且不使用以行存储的关系型结构存储数据,而是以键值对的方式按列存储,由此我们认为它是非关系型数据库NoSQL(Not Only SQL)中的一个重要代表。 NoSQL的概念在2009年被提出,目前并没有明确的范围和定义,主要特点是通常用于大规模数据的存储、没有预定义的模式(如表结构)、表和表之间没有复杂的关系。总体上可将 NoSOL数据库分为基于列存储的类型、基于文档存储的类型、基于键值对存储的类型和基于图形数据存储的类型四类。
通常人们将HBase归为基于列存储类型这一类,在NoSQL领域, HBase本身不是最优秀的,但与Hadoop的整合,给它带来了更广阔的发展空间。HBase本质上只有插入操作,更新和删除都是使插入方式完成,这是由底层HDFS流式访问特性(一次写入、多次读取)决定的。因此在更新时总是插入一个带时间戳的新行,而删除时插入一个带有删除标记的新行。每次的插入都有一个时间戳标记,每次都是一个新的版本,HBase会保留一定数量的版本,这个值是可以设定的。如果在查询时提供时间戳则返回距离该时间最近的版本,否则返回离现在最近的版本。
HBase的优势:
 成熟:社区成熟,支持工具丰富。
 高效:适应高并发写入。
 分布式:基于HDFS,易扩展。
HBase的特点如下:
 一个大表,亿行万列。
 面向列:面向列族存储和权限访问,列和列族都是独立索引。
 对于为null的列是不占用存储空间,所以表的设计很稀疏。
 数据类型单一,都是字符串类型。
 同一行可以有截然不同的列。
 介于关系型和非关系型数据库之间,实现非结构化和半结构化的数据存储。

21.1.1 HBase体系结构
HBase的服务器体系结构同样是Master/Slaves的主从服务器结构,它由一个 HMaster服务器和多个HRegionServer服务器构成,而这所有服务器都是通过Zookeeper来进行协调并处理各服务器运行期间可能遇到的错误。HMaster负责管理所有的HRegionServer,各HRegionServer负责存储许多HRegion,每个HRegion是对HBase逻辑表的分块。如图4.1所示,图中给出了HBase集群中的所有成员。

下面我们介绍上图的主要实体。

  1. HRegion
    HBase使用表(Table)存储数据集,表由行和列组成,这与关系型数据库类似。但是在HBase中,当表的大小超过设定值时,HBase会自动将表划分为不同的区域(Region),每个区域称为 HRegion。它是 HBase集群上分布式存储和负载均衡的最小单位,在这点上表和 HRegion类似于HDFS中文件与文件块的概念。一个 HRegion中保存一个表中一段连续的数据,通过表名和主键范围(开始主键~结束主键)来区分每个HRegion。一开始,一个表只有一个HRegion,随着HRegion开始变大,直到超出设定的大小值,便会在某行的边界上把表分成两个大小基本相同的HRegion,称为HRegion分裂。如图所示。

每个HRegion由多个HStore组成,每个HStore对应表中一个列族(Column Family)的存储,
列族在后面还有详细介绍。HStore由两部分组成:MemStore和StoreFile,用户写入的数据首先放入MemStore,当MemStore满了以后再刷入(flush)StoreFile。StoreFile是HBase中的最小存储单元,底层最终由HFile实现,而HFile是键值对数据的存储格式,实质是HDFS的二进制格式文件。
HBase中不能直接更新和删除数据,所有的数据均通过追加的方式进行更新。当 StoreFile的数量超过设定的阈值时,将触发合并操作,将多个StoreFile合并为一个StoreFile,此时进行数据的更新和删除。
2. HRegionServer
HRegionServer负责响应用户I/O请求,向HDFS中读写数据,一台机器上只运行一个 HRegionServer。HRegionServer包含两部分HLog部分和 HRegion部分。
其中,HLog用于存储数据日志,实质是HDFS的SequenceFile。到达HRegion的写操作首先被追加到日志中,然后才被加入内存中的MemStore。HLog文件主要用于故障恢复。例如,某台HRegionServer发生故障,那么它所维护的 HRegion会被重新分配到新的机器上,新的 HRegionServer在加载HRegion的时候可以通过HLog对数据进行恢复。
HRegion部分由多个HRegion组成,每个HRegion对应表中的一个分块,并且每个HRegion只会被一个 HRegionServer管理。
3. HMaster
每台HRegionServer都会和HMaster服务器通信,HMaster的主要任务就是告诉每台 HRegionServer要维护哪些 HRegion。
在 HBase中可以启动多个HMaster,通过 Zookeeper的 Master选举机制来保证系统中总有一个HMaster在运行。HMaster的具体功能包括:
 管理用户对表的增、删、改、查操作。
 管理HRegionServer的负载均衡,调整HRegion的分布。
 在HRegion分裂后,负责新HRegion的分配。
 在 HRegionServer停机后,负责失效 HRegionServer上HRegion的迁移。
4. ZooKeeper
ZooKeeper存储的是 HBase中的ROOT表和META表的位置,这是 HBase中两张特殊的表,称为根数据表(ROOT)和元数据表(META)。META表记录普通用户表的 HRegion标识符信息,每个HRegion的标识符为"表名+开始主键+唯一ID"。随着用户表的HRegion的分裂,META表的信息也会增长,并且可能还会被分割为几个HRegion,此时可以用一个ROOT表来保存META的HRegion信息,而ROOT表是不能被分割的,也就是ROOT表只有一个HRegion。那么客户端(Client)在访问用户数据前需要首先访问Zookeeper,然后访问ROOT表,接着访问META表。最后才能找到用户数据的位置进行访问,如图所示。

此外ZooKeeper还负责监控各个机器的状态,之前各机器需要在 Zookeeper中注册一个实例。当某台HRegionServer发生故障时,Zookeeper通知HMaster进行 HRegion迁移:若HMaster发生故障,ZooKeeper负责恢复HMaster,并且保证同时有且只有一台HMaster运行。
21.1.2 HBase数据模型

  1. 数据模型
    在HBase中,数据以表的方式存储。具体数据模型中涉及的术语解释如下:
    表(Table):是一个稀疏表(不存储值为NULL的数据)。表的索引是行关键字、列关键字和时间戳。
    行关键字(Row Key):行的主键,唯一标识一行数据,也称行键。表中的行根据行键进行字典排序,所有对表的访问都要通过表的行键,在创建表时,行键不用也不能预先定义而在对表数据进行操作时必须指定行键,行键在添加数据时首次被确定。
    列族(Column Family):表中的列被分为“列族”。同一个列族的所有成员具有相同的列族前缀。例如,"course:math"和"course:art"都是列族"course"的成员。一个表的列族必须在创建表时预先定义,列族名称不能包含ASCII控制字符(ASCII码在0-31间外加127)和冒号(:)。
    列关键字(Column Key):也称列、列键。格式为::,其中family是列族名,用于表示列族前缀;qualifier是列族修饰符,表示列族中的一个成员。列族成员可以在随后使用时按需加入,也就是只要列族预先存在,我们随时可以把列族成员添加到列族中去。列族修饰符可以是任意字节。
    存储单元格(Cell):在HBase中,值是作为一个单元保存在系统中的,要定位一个单元需要使用“行键+列键+时间戳”三个要素。
    时间戳(Timestamp):插入单元格时的时间戳,默认作为单元格的版本号。
    下面结合HBase的概念视图再来体会这些术语。
    2.概念视图
    在关系型数据库中只能通过表的主键或唯一字段定位到某一条数据,如使用典型的关系表的结构来描述学生成绩表scores,如下表所示,其中字段有姓名(name)、班级(class)、数学成绩(math)、英语成绩(english),主键为name。
    Name Class Math English
    Jiangtao 2 80 90
    Sunlei 1 87 82
    由于主键唯一标识了一行记录,所以我们很容易按姓名查询到某位同学的所有成绩。但是请思考如下问题。
  2. 如果现在新加一门课程,能够在不修改表结构的情况下保存新的课程成绩吗?
  3. 如果某同学数学课程参加了补考,那么两次的考试成绩都能够保存下来吗?
  4. 如果某同学只考试了一门课程而其他课程都没有成绩,是否可以只保存有成绩的课程从而节省存储空间呢?
    对于问题1和2,在不修改表结构的情况下是不能够实现的,即使通过修改表结构实现也不能保证后续的需求不会再发生变化。而在问题3中,按表结构的字段类型定义条记录的某个字段无论是否为NULL都会占用存储空间,那么我们不能通过有选择地保存数据来节省存储空间。
    很明显,上面的需求在实际运用中经常出现,而 HBase则可以完美地解决这些问题。我们将 scores表转为在 HBase中的概念视图,如下表所示。
    行键(name) 时间戳 列族(class) 列族(course)
    列关键字 值 列关键字 值(单元格)
    Jiangtao T6 course:math 80
    T5 course:english 90
    T4 class: 2
    Sunlei T3 course:math 87
    T2 course:english 82
    T1 class: 1
    表中的列包含了行键、时间截和两个列族(class, course),行包含了"Jiangtao"和"Sunlei"两行数据。每次对表操作都必须指定行键和列键,每次操作增加一条数据,每条数据对应一个时间戳,从上往下按倒序排列,该时间截自动生成,不必用户管理。每次只针对一个列键操作,例如,在T3时刻,用户指定"Sunlei"的"math"成绩为"87",类似操作:先找到行键"Sunlei",然后指定列键进行赋值:“course:math=87”,其中"course:math"为列键,"course"为列族名,“math"为列族修饰符,最后将"87"作为列键值赋给列键,T3时刻的时间自动插入到时间戳这列中。
    又如在T4时刻,用户指定"Jiangtao"的"class"为"2”。按照前面分析类似操作如下:先找到行键Jiangtao,然后指定列键并赋值:class:=2,请注意这里没有给出列键的列族修饰符,即列族修饰符为空字符串。这样是允许的,因为前面提过列族修饰符可以是任意字符。
    现在在 HBase中回答前面的三个问题:
  5. 如果现在为学生Jiangtao新增语文成绩.那么指定行键:“Jiangtao”,列键:“cource:chinese”,以及列键值(语文成绩)即可。
  6. 如果学生Jiangtao参加了数学补考,那么指定行键:“Jiangtao”,列键:“cource.math”,以及列键值(补考成绩)即可。
  7. 前面提过HBase是基于稀疏存储设计,在概念视图中发现存在很多空白项,这些空白项并不会被实际存储,总之是有数据就存储无数据则忽略,通过表的物理视图可以更好地体会这一点。
    3.物理视图
    通过概念视图,有助于我们从逻辑上理解 HBase的数据结构,但在实际存储时,是按照列族来存储的,一个新的列键可以随时加入已存在的列族中,这也是列族必须在创建表时预先定义的原因。上表中的概念视图对应的物理视图如下表所示。
    行键(name) 时间戳 列族(class)
    列关键字 值
    Jiangtao T4 class: 2
    Sunlei T1 class: 1

行键(name) 时间戳 列族(class)
列关键字 值
Jiangtao T6 course:math 80
T5 course:english 90
Sunlei T3 course:math 87
T2 course:english 82
HBase就是这样一个基于列模式的映射数据库,它只能表示简单的键-值的映射关系。与关系型数据库相比,它有如下特点:
数据类型: HBase只有简单的字符串类型,它只保存字符串。而关系型数据库有丰富的类型选择和存储方式。
数据操作:HBase只有简单的插入、查询、删除、清空等操作,表和表之间是分离的,没有复杂的表和表之间的关系,因此不能、也没有必要实现表和表之间的关联操作。而关系型数据库有多种连接操作。
存储模式: HBase是基于列存储的,每个列族都由几个文件保存,不同列族的文件是分离的关系型数据库是基于表格结构和行模式存储的。
数据维护: HBase的更新操作实际上是插入了新的数据,它的旧版本依然会保留,而不是关系型数据库的替换修改。
可伸缩性: HBase这类分布式数据库就是为了这个目的而开发的,所以它能够轻松地增加或减少硬件数量,并且对错误的兼容性比较高。而关系型数据库通常需要增加中间层才能实现类似的功能。

第22章
HBase实验:HBase集群搭建

22.1实验目的
1.掌握HBase基础简介及体系架构;
2.掌握HBase集群安装部署及HBase Shell一些常用命令的使用;
3.了解HBase和HDFS及Zookeeper之间的关系。
22.2实验要求
1.部署一个主节点,三个子节点的HBase集群,并引用外部Zookeeper。
22.3实验条件
 Zookeeper集群建设,参照第十四章;
 Hadoop高可用集群,参照第十六章。
具体步骤见16.5。
22.4实验原理
简介:HBase是基于Hadoop的开源分布式数据库,它以Google的BigTable为原型,设计并实现了具有高可用性、高性能、列存储、可伸缩、实时读写的分布式数据库系统,它是基于列而不是基于行的模式,适合存储非结构化数据。
体系结构:HBase是一个分布式的数据库,使用Zookeeper管理集群,使用HDFS作为底层存储,它由HMaster和HRegionServer组成,遵从主从服务器架构。HBase将逻辑上的表划分成多个数据块即HRegion,存储在HRegionServer中。HMaster负责管理所有的HRegionServer,它本身并不存储任何数据,而只是存储数据到HRegionServer的映射关系(元数据)。HBase的基本架构如下图所示。

22.5 HBase环境搭建
22.5.1 HBase运行模式
HBase有三种运行模式:单机模式、伪分布式模式和分布式模式。
 单机模式是默认模式。在单机模式下,HBase不使用HDFS,而是使用本地文件系统,是在同一个JVM中运行所有HBase守护进程和本地ZooKeeper。通过ZooKeeper端口,客户端可以和HBase进行通信。
 伪分布式模式是在一台主机上运行所有进程的模式。
 分布式模式是进程运行在物理服务集群上。
本章重点是分布式模式。
22.5.2 HBase各个节点的分布
节点类型 IP地址 主机名
HMaster 192.168.11.10 master
HRegionServer 192.168.11.20 slave1
HRegionServer 192.168.11.30 slave2
22.5.3 HBase的配置文件
安装分布式HBase需要以下配置文件。
配置文件名 配置文件功能
hbase-env.sh hbase的环境变量
hbase-site.xml hbase的配置信息
regionservers 所有region服务的主机名
22.5.4实验步骤
(1)启动master虚拟机命令终端,创建目录mkdir /usr/hbase,执行命令cd /usr/hbase,切换到该目录下,把hbase文件hbase-1.4.9-bin.tar.gz上传到该目录下。
(2)然后对/usr/hbase目录下的hbase压缩文件hbase-1.4.9-bin.tar.gz进行解压缩。
tar -zxvf hbase-1.4.9-bin.tar.gz
(3)修改配置文件,切换到/usr/hbase/hbase-1.4.9/conf/hbase-env.sh文件,设置如下:
#java安装路径
export JAVA_HOME=/usr/java/jdk1.8.0_181
#不使用HBase自带的Zookeeper
export HBASE_MANAGES_ZK=false
(4)切换到/usr/hbase/hbase-1.4.9/conf/hbase-site.xml文件,设置如下:


hbase.rootdir
hdfs://master:9000/hbase
配置HRegionServer的数据库存储目录


hbase.cluster.distributed
true
配置HBase为完全分布式


hbase.master
master:60000
配置HMaster的地址


hbase.zookeeper.quorum
master,slave1,slave2
配置ZooKeeper集群服务器的位置


hbase.tmp.dir
/usr/hbase/tmp



(5)切换到/usr/hbase/hbase-1.4.9/conf/regionservers文件,设置如下:
slave1
slave2
注意:regionservers文件不包含master,因为master已经在hbase-site.xml中被指定为HMaster服务器,通常不会将HMaster和HRegionServer服务器运行在一个节点上。
(6)配置完成后,将master虚拟机上的hbase目录传送到集群的slave1,slave2节点
scp -r /usr/hbase root@slave1:/usr/
scp -r /usr/hbase root@slave2:/usr/

(7)配置master、slave1、slave2虚拟机环境变量,执行命令vi /etc/profile,如图所示。

(8)然后让配置文件生效,在master、slave1、slave2虚拟机上执行命令source /etc/profile
(9)启动zookeeper集群
zkServer.sh start
(10)启动hadoop集群
stop-all.sh
start-all.sh
如果出现某个节点上resourcemanager没有启动,可以重新停止resourcemanager后再启动。
yarn-daemon.sh stop resourcemanager
yarn-daemon.sh start resourcemanager
(11)如果master虚拟机是standby,slave1是active的话,必须将master状态修改为active。
如下图所示。如果master虚拟机是active,slave1是standby的话,本步骤不用执行。

在master上执行下面两个步骤:
步骤1:将master状态由standby修改为active。
hdfs haadmin -transitionToActive rm1 -forcemanual
步骤2:重新启动ZooKeeper。
master和slave1虚拟机停止zkfc。
hadoop-daemon.sh stop zkfc
再在master虚拟机上重新执行
hadoop-daemons.sh start zkfc
(12)启动master虚拟机上的hbase,执行/usr/hbase/hbase-1.4.9/conf/start-hbase.sh
stop-hbase.sh
start-hbase.sh
启动信息如下图所示。

注意:执行命令后会启动master的HMaster,master、slave1、slave2虚拟机的regionserver。

(13)hbase启动成功后,进入shell界面,用shell命令简单操作hbase数据库验证hbase成功安装,验证结果如图所示。

(14)查看hbase状态

安装成功后,可以通过访问HBase Web页面(http://192.168.11.10:16010)来查看HBase集群的一些基本情况,如图所示。

注意打开HBase Web页面(http://192.168.11.10:16010)时如果出现下面的页面,说明HMaster没有启动完成,需要稍等一下。

第23章
HBase实验:HBase命令操作

23.1实验目的
1.学会Hbase命令操作
23.2 hbase shell命令的介绍
Hbase shell命令 说明
alter 修改表的列族
count 统计表中行的数量,一个行键为一行
create 创建表
describe 显示表相关的详细信息
delete 删除指定对象的值(可以为表,行,列对应的值,另外也可以指定时间戳的值)
deleteall 删除指定行的所有元素值
disable 使表无效
drop 删除表
enable 使表有效
exists 测试表是否存在
exit 退出hbase shell
get 获取行或单元(cell)的值
incr 增加指定表,行或列的值
list 列出hbase中存在的所有表
put 向指定的表单元添加值
tools 列出hbase所支持的工具
scan 通过对表的扫描来获取对应的值
status 返回hbase集群的状态信息
shutdown 关闭集群(关闭后必须重新启动hbase)
trancate 重新创建指定表
version 返回hbase版本信息
注意:shutdown与exit之间的不同
shutdown表示关闭hbase服务,必须重新启动hbase才可以恢复。
exit只是退出hbase shell,退出之后完全可以重新进入。
23.3实验步骤
23.3.1启动zookeeper集群
zkServer.sh start
23.3.2启动hadoop集群
start-all.sh
23.3.3启动hbase
stop-hbase.sh
start-hbase.sh
23.3.4进入hbase shell
(1)查看表:list

HBase中有一个表t1。
(2)创建表:create
语法:
create ‘表名称’, ‘列名称1’, ‘列名称2’, ‘列名称3’…, ‘列名称N’
其中表名称、列名称必须用单引号括起来并用逗号分隔。
下面我们创建scores表,"name"作为行键,有class和course两个列族。行键name在创建表时可以不用预先指定这一列。"时间戳"这一列由HBase自动生成。

另外,创建表时,可以指定列名称和起始版本。
create ‘表名称’ , { NAME => <列名称> , VERSIONS =><起始版本> }
例如:创建scores表,有class和course两个列族,且起始版本均为2。

创建表时需要注意以下两点:
 NAME和VERSIONS必须写成大写。
 表名、字段名必须使用单引号括起。
 指定列族信息时必须使用“=>”符号。
(3)查看表的结构:describe
语法:describe ‘表名称’
例如:查看表scores的结构

describe命令指定scores表是ENABLED状态,class和course两个列族的属性,这些属性在创建表时可以设置,但是通常我们使用默认值即可。
(4)禁用表:disable
语法:disable ‘表名称’
在前面的describe命令中我们可以发现,HBase表分两种状态:DISABLED和ENABLED,表示表是否可用。
禁用scores表,并且验证表是否被禁用,验证使用scan命令。

(5)启用表:enable
语法:enable


启用t1表,并且验证表是否被禁用,验证使用scan命令。

(6)修改表结构:alter
alter命令可以为表增加或修改列族。
注意修改表结构必须先disable
语法:alter ‘表名称’,参数名=>参数值,…
其中列族名参数NAME必须提供,如果已存在则修改,否则增加一个列族。下面示例将scores表的列组"coures"的"VERSIONS"参数修改为10。

同时修改或增加多个列族时以逗号分开,并且每个列族用"{}"括起来。

设定表scores只读

删除scores表中的列族’class’

(7)删除表:drop
被删除的表首先状态必须是disable,然后才能删除表。
删除表scores。

(8)添加数据:put
语法:put ‘表名称’,‘行键’,‘列键’,‘值’

(9)扫描表:scan
scan用于进行全表单元扫描。
scan ‘表名称’,{COLUMNS=>[‘列族名1’,‘列族名2’…],参数名=>参数值}
大括号内的内容为扫描条件,如不指定则查询所有数据。

以上scan了两行数据,相同行键的所有单元视为一行。如果对有些列族不关心,便可指定查询部分列族。

可不可以指定键列来扫描呢?也行,语法如下:
scan ‘表名称’,{COLUMN=>[‘列键1’,‘列键2’…],参数名=>参数值…}
将COLUMNS替换成COLUMN,表示当前扫描的目标是列键,注意区分大小写。如下代码表示扫描所有行的列键为"course:math"的单元,并使用LIMIT参数限制为输出一行。

(10)获取数据:get
get用于获取行的所有单元或者某个指定的单元。
get ‘表名称’,‘行键’,{COLUMNS=>[‘列族名1’,‘列族名2’…],参数名=>参数值}
get ‘表名称’,‘行键’,{COLUMN=>[‘列键1’,‘列键2’…],参数名=>参数值}
与scan相比多了一个参数即行键。scan查找的目标是全表的某个列族、列键,而get查找的目标是某行的某个列族、列键。
①查找行键为"jiangtao"的所有单元

从输出结果看,不指定列族或列键,会输出行键的所有列键单元。
②精确查找行键为"jiangtao",列键为"course:math"的单元

注意:
get ‘scores’,‘jiangtao’,{COLUMNS=>‘course’} 等价于 get ‘scores’,‘jiangtao’,‘course’
get ‘scores’,‘jiangtao’,{COLUMN=>‘course:math’} 等价于get ‘scores’,‘jiangtao’,‘course:math’
get ‘scores’,‘jiangtao’,{COLUMNS=>[‘course’,‘class’]} 等价于get ‘scores’,‘jiangtao’,‘course’,‘class’
get ‘scores’,‘jiangtao’,{COLUMN=>[‘course:math’,‘class’]}
等价于get ‘scores’,‘jiangtao’, ‘course:math’,‘class’
(11)删除数据
delete ‘表名称’, ‘行键’, ‘列键’
deleteall ‘表名称’, ‘行键’
delete只能删除一个单元,而deleteall为删除一行。
删除scores表中,行键为"jiangtao",列键为"course:math"的单元。

删除scores表中,行键为"jiangtao"。

删除表中的所有数据
语法: truncate ‘表名称’
truncate命令的具体过程是先禁用表,再删除表,最后创建表。
删除scores表的所有数据

第24章
HBase实验:HBase Java API

24.1实验目的
1.掌握HBase数据模型
2.掌握如何使用java代码获得HBase连接,并熟练java对HBase数据库的基本操作,进一步加深对HBase表概念的理解。
24.2实验要求
通过java代码实现与HBase数据库连接,然后用Java API创建HBase表,向创建的表中写数据,最后将表中数据读取出来并展示。
24.3实验原理
逻辑模型:HBase以表的形式存储数据,每个表由行和列组成,每个列属于一个特定的列族(Column Family)。表中的行和列确定的存储单元称为一个元素(Cell),每个元素保存了同一份数据的多个份数据的多个版本,有时间戳(Time Stamp)来标识。行健是数据行在表中的唯一标识,并作为检索记录的主键。在HBase中访问表中的行只有三种方式:通过单个行健访问。给定行健的范围扫描、全表扫描。行键可以是任意字符串,默认按字段顺序存储。表中的列定义为::(<列族>:<限定符>),通过列族和限定符两部分可以唯一指定一个数据的存储列。元素由行健、列(<列族>:<限定符>)和时间戳唯一确定,元素中的数据以字节码的形式存储,没有类型之分。
物理模型:HBase是根据列存储的稀疏行/列矩阵,其物理模型实际上就是把概念模型中的一个行进行分割,并按照列族存储。
24.4 HBase数据存储管理API
java类 HBase数据模型
HBaseConfiguration 数据库(DataBase)
Connection
Admin
Table 表(Table)
TableName
HTableDescriptor
HColumnDescriptor 列族(Column Family)
Put 添加数据(put命令)
Get 获取命令(get命令)
ResultScanner、 Scan 扫描表(scan命令)
Result 存储Get或Scan操作后获取的表的单行值
(1)org.apche.hadoop.hbase.HBaseConfiguration
通过org.apche.hadoop.hbase.HBaseConfiguration类可以对HBase进行配置,对HBase的任何操作都需要首先创建HBaseConfiguration类的实例。HBaseConfiguration继承自org.apche.hadoop.conf.Configuration,意味着对于HBase的配置参数的管理类似于HDFS和MapReduce。使用静态方法create()返回的是一个能够访问HBase配置信息的Configuration实例。
示例:
static Configuration conf = HBaseConfiguration.create();
该语句会从classpath中查找hbase-site.xml文件,发现不存在则使用默认配置文件hbase-default.xml。
(2)org.apache.hadoop.hbase.client.Connection
通过org.apache.hadoop.hbase.client.Connection接口表示到HBase的连接。该接口可以让HBase客户端连接到ZooKeeper和个别的HRegionServer,通过ConnectionFactory实例化,其生命周期由用户管理,操作完成后应使用close()关闭连接以释放资源。
Connection类的实例能够查找到HMaster,能够定位HRegion在集群中的位置、缓存、在它们的位置发生变化后进行重新校准,Table和Admin实例需要从该连接实例中获取数据。
// 创建HBase数据库连接
Connection conn = ConnectionFactory.createConnection(conf);
// Admin用于管理HBase数据库的表信息
Admin admin = conn.getAdmin();
// 获取scores表的实例,TableName是对表的封装
Table table = conn.getTable(TableName.valueOf(“scores”));
// 关闭连接
conn.close();
(3)org.apache.hadoop.hbase.client.Admin
org.apache.hadoop.hbase.client.Admin是为管理HBase而提供的接口。它提供的方法包括:创建表,删除表,列出表项,使表有效或无效,以及添加或删除表列族成员等。使用Admin接口可以实现的HBase Shell命令包括create、list、drop、enable、disable、alter。
返回值 函数 描述
void addColumn(String tableName,HColumnDescriptor column) 向一个已经存在的表添加列
void createTable(HTableDescriptor desc) 创建一个新表
void deleteTable(byte[] tableName) 删除一个已经存在的表
void enableTable(byte[] tableName) 使表处于有效状态
void disableTable(byte[] tableName) 使表处于无效状态
HTableDescriptor[] listTables() 列出所有表
void modifyTable(byte[] tableName,HTableDescriptor htd) 修改一个已经存在的表
boolean tableExists(String tableName) 检查表是否存在

package com.hbase;

import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Table;

public class HBaseTest {
// 读取HBase配置文件
static Configuration conf = HBaseConfiguration.create();

public static void main(String[] args) throws IOException {
	// 创建HBase数据库连接
	Connection conn = ConnectionFactory.createConnection(conf);
	// Admin用于管理HBase数据库的表信息
	Admin admin = conn.getAdmin();
	System.out.println("表列表....");
	for(HTableDescriptor table:admin.listTables()){
		System.out.println(table.getNameAsString());
	}
	// 关闭连接
	conn.close();
}

}
(4)org.apache.hadoop.hbase.HTableDescriptor、org.apache.hadoop.hbase.TableName
HTableDescriptor包含了表的名字及其对应的列族。TableName类是对表名进行封装的POJO对象,表名完整形式为

:
,如果没有指定表命名空间(table namespace),会使用HBase默认命名空间default。
create(“scores”,“class”,“course”)等价于create(“default:scores”,“class”,“course”)
下面是HTableDescriptor类的方法。
返回值 函数 描述
void addFamily(HColumnDescriptor hcd) 添加一个列族
HColumnDescriptor removeFamily(byte[] column) 移除一个列族
String getNameAsString() 获取表的名字
用法示例:
TableName tn = TableName.valueOf(tableName);
HTableDescriptor htd = new HTableDescriptor(tn);
htd.addFamily(new HColumnDescriptor(“列族名”));
在上述例子中,通过一个HTableDescriptor实例,为HTableDescriptor添加一个列族。
(5)org.apache.hadoop.hbase.client.HTable
以上的HBase API还不能对表进行数据存储操作,我们需要使用HTable类,HTable类实现了Table接口。Table接口用于和HBase中的表进行通信,代表了该表的实例,使用Connection的getTable(TableName tableName)方法获取该接口的实例,可用于获取、添加、删除、扫描HBase表中的数据。
下面是HTable类的方法。
返回值 函数 描述
void put(Put put) 向表添加值,put表示添加操作
Result get(Get get) 获取单元值,get表示获取操作
void delete(Delete delete) 删除指定的单元/行,delete表示删除操作
ResultScanner getScanner(Scan) 获取当前表的给定列族的scanner实例,ResultScanner代表结果列表
getScanner(byte[] family,byte[] qualifier)
getScanner(byte[] family)
boolean exists(Get get) 测试Get实例所指定的值是否存在于Table中
HTableDescriptor getTableDescriptor() 获取表的HTableDescriptor实例
TableName getName() 获取表名
用法示例:
HTable table =new HTable(conf,Byte.toBytes(tablename));
ResultScanner scanner =table.getScanner(family);
(6) org.apache.hadoop.hbase.HColumnDescriptor
HTableDescriptor是对表结构的描述。而HColumnDescriptor维护着关于列族的信息,例如列族名称、版本号,压缩设置等。它通常在创建表或者为表添加列族的时候使用。列族被创建后不能直接修改,只能通过删除然后重新创建的方式。列族被删除的时候,列族里面的数据也会同时被删除。
返回值 函数 描述
byte[] getName() 获取列族的名字
byte[] getValue(byte[] key) 获取对应的属性的值
void setValue(String key,String value) 设置对应属性的值
用法示例:
HTableDescriptor htd = new HTableDescriptor(tablename);
HColumnDescriptor col = new HColumnDescriptor(“content”);
col.setValue(HConstants.VERSIONS,“5”);
htd.addFamily(col);
此列添加了一个content的列族
(7)org.apache.hadoop.hbase.client.Put
Put类表示为指定行键添加列键和值的操作。HBase Shell put命令的四个参数分别为表名、行键、列键、值。其中表名由Table实例携带,剩下三个参数需要在Put类中获取,其中行键由构造函数指定,列键和值由具体方法指定。
Put类的构造方法如下:
Put(byte[] row):对指定行创建一个Put实例,row为行键的字节码,通常使用org.apache.hadoop.hbase.util.Bytes类的toBytes(String rowKey)方法获取。
Put(byte[] row,long ts):创建Put实例并手动指定时间戳。
返回值 函数 描述
Put addColumn(byte[] family,byte[] qualifier,byte[] value) 将指定的列和对应的值添加到Put示例中
Put addColumn(byte[] family,byte[] qualifier,
long ts,byte[] value) 将指定的列和对应的值及时间戳添加到Put实例中
List get(byte[] family,byte[] qualifier) 返回Put实例中与指定的列键 family:qualifier匹配的项,Cell表示单元格的实例
boolean has(byte[] family,byte[] qualifier) 检查Put实例是否包含指定的列键 family:qualifier
用法示例:
HTable table=new HTable(conf,Bytes.toBytes(tablename));
Put p=new Put(brow);//获取行创建一个Put操作
p.add(family,qualifier,value);
table.put§;
(8)org.apache.hadoop.hbase.client.Get
Get类对应HBaseShell的get命令,用来获取单个行的相关信息。同样行键由构造函数指定:
Get(byte[] row)
返回值 函数 描述
Get addColumn(byte[] family,byte[] qualifier) 获取指定列族和列修饰符对应的列
Get addFamily(byte[] family) 通过指定的列族获取其对应的所有的列
Get setTimeRange(long minStamp,long maxStamp) 指定列的版本号区间
用法示例:
HTable table=new HTable(conf,Bytes.toBytes(tablename));
Get g=new Get(Bytes.toBytes(row));
(9)org.apache.hadoop.hbase.client.Result
Result表示一行数据(一个行键表示一行数据),每个Result中都可以获得行键。
(10)org.apache.hadoop.hbase.client.Scan、ResultScanner
在HBase Shell中scan命令既可以按表扫描,也可按列族、列键扫描。对应的API为Scan类,表示一次扫描操作,默认是全表扫描。Scan操作的扫描结果被ResultScanner接收,可以看成是多个Result的集合,意味着ResultScanner存放了多行数据。
24.5实验步骤

  1. 启动Zookeeper集群
  2. 启动Hadoop集群
  3. 启动HBase集群
    实验步骤
    1.建立项目
    (1)打开 Eclipse开发工具,单击File选择"New"→" Java project"。
    (2)输入包名称"cn.dzqc.hbase"。
    (3)新建java类,输入"HBaseDemo"类名称,单击" Finish"按钮完成。
    (4)在创建的项目目录下创建文件夹libs,通过选中文件夹"src"并点击右键,选择"New"→"SourceFolder",在"Folder name"项输入lib,然后点击"finish"。
    (5)在lib文件下的添加如下jar包。

(6)选中libs下的所有jar包,单击右键,然后选择"Add to Build Path",即可把所有jar包添加到path环境中。
(7)把linux上$HBASE_HOME/conf/hbase-site.xml,放在src下,内容如下。


hbase.rootdir
hdfs://master:9000/hbase


hbase.cluster.distributed
true


hbase.zookeeper.quorum
master,slave1,slave2


hbase.tmp.dir
/usr/hbase/tmp


客户端会读取该配置文件,但是只用到" hbase.zookeeper.quorum"属性,也就是客户端通过该属性直接连接ZooKeeper集群,然后由ZooKeeper来协调各节点的访问。
(8)修改hosts文件。打开C:\Windows\System32\drivers\etc\hosts,把127.0.0.1这行注释,把集群的ip和主机名写上。

2.编写代码
实例1:
package cn.dzqc.hbase;

import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.util.Bytes;

public class HBaseTest2 {

// 读取HBase配置文件
static Configuration conf = HBaseConfiguration.create();

// 列出所有表
public static void list() throws IOException {
	// 创建HBase数据库连接
	Connection conn = ConnectionFactory.createConnection(conf);
	// Admin用于管理HBase数据库的表信息
	Admin admin = conn.getAdmin();
	System.out.println("-----------表列表------------");
	for (HTableDescriptor table : admin.listTables()) {
		System.out.println(table.getNameAsString());
	}
	// 关闭连接
	conn.close();
	System.out.println("---------------------------");
}

// 创建表
public static void create(String tableName, String... familyNames) throws IOException {
	// 创建HBase数据库连接
	Connection conn = ConnectionFactory.createConnection(conf);
	// Admin用于管理HBase数据库的表信息
	Admin admin = conn.getAdmin();
	TableName tn = TableName.valueOf(tableName);
	// 判断表是否存在,如果存在表就先删除表
	if (admin.tableExists(tn)) {
		// 使表无效后删除
		admin.disableTable(tn);
		admin.deleteTable(tn);
	}

	HTableDescriptor htd = new HTableDescriptor(tn);
	for (String name : familyNames) {
		htd.addFamily(new HColumnDescriptor(name));
	}
	admin.createTable(htd);
	// 关闭连接
	conn.close();
	System.out.println("HBase表创建完成");
}

// 修改表-增加列族
public static void addColumnFamily(String tableName, String... familyNames) throws IOException {
	// 创建HBase数据库连接
	Connection conn = ConnectionFactory.createConnection(conf);
	// Admin用于管理HBase数据库的表信息
	Admin admin = conn.getAdmin();
	TableName tn = TableName.valueOf(tableName);
	
	HTableDescriptor htd = admin.getTableDescriptor(tn);
	
	for (String name : familyNames) {
		htd.addFamily(new HColumnDescriptor(name));
	}
	admin.modifyTable(tn,htd);
	// 关闭连接
	conn.close();
	System.out.println("HBase表修改完成,增加列族");
}

// 修改表-删除列族
public static void removeColumnFamily(String tableName, String... familyNames) throws IOException {
	// 创建HBase数据库连接
	Connection conn = ConnectionFactory.createConnection(conf);
	// Admin用于管理HBase数据库的表信息
	Admin admin = conn.getAdmin();
	TableName tn = TableName.valueOf(tableName);
	HTableDescriptor htd = admin.getTableDescriptor(tn);
	for (String name : familyNames) {
		htd.removeFamily(Bytes.toBytes(name));
	}
	admin.modifyTable(tn,htd);
	// 关闭连接
	conn.close();
	System.out.println("HBase表修改完成,删除列族");
}

public static void describe(String tableName) throws IOException{
	// 创建HBase数据库连接
	Connection conn = ConnectionFactory.createConnection(conf);
	// Admin用于管理HBase数据库的表信息
	Admin admin = conn.getAdmin();
	TableName tn = TableName.valueOf(tableName);
	HTableDescriptor htd = admin.getTableDescriptor(tn);
	System.out.println(tableName+" 表结构:");
	for(HColumnDescriptor hcd:htd.getColumnFamilies()){
		System.out.println(hcd.getNameAsString());
	}
	conn.close();
}
public static void main(String[] args) throws IOException {
	list();
	create("stu5","stuid","stuname","class","birthday");
	describe("stu5");
	addColumnFamily("stu5", "teachername","grade");
	describe("stu5");
	removeColumnFamily("stu5", "grade");
	describe("stu5");
}

}

实例2:
package cn.dzqc.hbase;

import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.util.Bytes;

public class HBaseTest3 {
static Configuration conf = HBaseConfiguration.create();
/**
* 插入数据
* /
static void put(String tableName) throws IOException{
Connection conn = ConnectionFactory.createConnection(conf);
Table tb = conn.getTable(TableName.valueOf(tableName));
Put put = new Put(Bytes.toBytes(“mike”));
put.addColumn(Bytes.toBytes(“class”), Bytes.toBytes(""), Bytes.toBytes(“2”));
put.addColumn(Bytes.toBytes(“course”), Bytes.toBytes(“english”), Bytes.toBytes(“86”));
put.addColumn(Bytes.toBytes(“course”), Bytes.toBytes(“math”), Bytes.toBytes(“82”));
tb.put(put);
conn.close();
}
/
*
* 按行键获取
* /
static void get(String tableName,String rowKey) throws IOException{
Connection conn = ConnectionFactory.createConnection(conf);
Table tb = conn.getTable(TableName.valueOf(tableName));
Get g = new Get(Bytes.toBytes(rowKey));
Result result = tb.get(g);
System.out.println(Bytes.toString(result.value())+"\t"+result);
conn.close();
}
/
*
* 按行键、列族获取
* /
static void get(String tableName,String rowKey,String family) throws IOException{
Connection conn = ConnectionFactory.createConnection(conf);
Table tb = conn.getTable(TableName.valueOf(tableName));
Get g = new Get(Bytes.toBytes(rowKey));
g.addFamily(Bytes.toBytes(family));
Result result = tb.get(g);
System.out.println(Bytes.toString(result.value())+"\t"+result);
conn.close();
}
/
*
* 按行键、列族、列修饰符获取
* */
static void get(String tableName,String rowKey,String family,String qualifier) throws IOException{
Connection conn = ConnectionFactory.createConnection(conf);
Table tb = conn.getTable(TableName.valueOf(tableName));
Get g = new Get(Bytes.toBytes(rowKey));
g.addColumn(Bytes.toBytes(family), Bytes.toBytes(qualifier));
Result result = tb.get(g);
System.out.println(Bytes.toString(result.value())+"\t"+result);
conn.close();
}
public static void main(String[] args) throws IOException {
put(“scores”);
get(“scores”,“mike”);
get(“scores”,“mike”,“course”);
get(“scores”,“mike”,“course”,“math”);
}
}

第25章
复习2

第26章
Flume实验:Flume安装配置

Hadoop的构建宗旨是处理大型数据集。通常,我们假设这些数据已经存储在HDFS中或者能够随时批量复制到HDFS。然而,有许多系统并不符合此假设。这些系统能产生出我们想要通过Hadoop来汇总、存储和分析的数据流。与这些系统打交道,Apache Flume(http://flume.apache.org/)再合适不过。
Flume是Cloudera提供的高可用、高可靠、分布式的海量数据收集系统,从多种源数据系统采集、聚集和移动大量的数据并集中存储。当前Flume有两个版本, Flume 0.9X版本的统称Flume-OG,Flume 1.x版本的统称Flume-NG,主要用于解决数据收集问题。本章通过对Flume简介、架构、常见命令、编程接口的讲解,让学生深刻理解和运用 Flume系统。
26.1 Flume概述
Flume作为Cloudera开发的实时日志收集系统,在企业中得到广泛的应用。Flume初始的发行版本目前被统称为Flume OG(original generation),属于 Cloudera。但随着Flume功能的扩展,Flume OG代码工程臃肿、核心组件设计不合理、核心配置不标准等缺点暴露出来,尤其是在Flume OG的最后一个发行版本0.94.0中,日志传输不稳定的现象尤为严重。为了解决这些问题,2011年10月22号, Cloudera完成了Flume-728,对 Flume进行了里程碑式的改动,重构核心组件、核心配置以及代码架构,重构后的版本统称为Flume NG(next generation)。改动的另一原因是将Flume纳入 Apache旗下, Cloudera Flume改名为 Apache Flume。
26.1.1 Flume核心概念
Flume系统要求如下:
 Java运行环境1.7以上
 保证有足够的内存用于配置使用的sources、channels、sinks
 保证被agent使用的目录具有读写权限
Flume采用了分层架构,其中涉及很多概念,例如Agent、Client、Source、Sink、Channel、Events等。如下表所示。
组件 功能
Agent 使用JVM运行Flume,每台机器运行一个agent,但是可以在一个agent中包含多个sources和sinks
Client 生产数据,运行在一个独立的线程
Source 从Client收集数据,传递给Channel
Sink 从Channel收集数据,运行在一个独立线程
Channel 连接sources和sinks,这个有点像一个队列
Events 可以是日志记录、AVRO对象等
26.1.2 Flume架构
Flume架构整体是Source→Channel→Sink的三层架构。
Source:完成对日志数据的搜集,分成Transition和Event送入到Channel之中。
Channel:主要提供一个队列的功能,对Source中提供的数据进行简单的缓存。
Sink:取出Channel中的数据,进入相应的存储文件系统、数据库,或者提交到远程服务器。
Flume以Agent为最小的独立运行单位。一个Agent就是JVM。Flume分布式系统中最核心的角色是Agent,flume采集系统就是由一个个Agent所连接起来形成。单Agent由Source、Sink和Channel三大组件构成。每一个Agent相当于一个数据传递员(Source 到 Channel 到 Sink之间传递数据的形式是Event事件,Event事件是一个数据流单元。)。如图所示。

Flume的数据流由事件(Event)贯穿始终。事件是 Flume的基本数据单位,它携带日志数据(字节数组形式)并且携带有头信息,这些 Event由 Agent外部的 Source,比如上图中的Web服务器生成。Source可以看做数据采集源,用于跟数据源对接,以获取数据。当 Source捕获事件后会进行特定的格式化,然后 Source会把事件推入(单个或多个) Channel中。可以把 Channel看作是一个缓冲区,它将保存事件直到Sink处理完成。Sink负责持久化日志或者把事件推向另一个 Source。
Flume提供了大量内置的Source、Channel和Sink类型。不同类型的Source、Channel和Sink可以自由组合。组合方式基于用户设置的配置文件,非常灵活。比如Channel可以把事件暂存在内存里,也可以持久化到本地硬盘上。Sink可以把日志写入HDFS、 Hbase,甚至是另外一个Source等。
Flume也可以支持用户建立多级流,也就是说,多个Agent可以协同工作,并且支持Fan-in、Fan-out、Contextual Routing、Backup Routes。如下图所示。

Flume的工作流程是把数据从数据源(source)收集过来,再将收集到的数据送到指定的目的地(sink)。为了保证输送的过程一定成功,在送到目的地(sink)之前,会先缓存数据(Channel),待数据真正到达目的地(sink)后,flume再删除自己缓存的数据。这种机制保证了数据传输的可靠与安全。
 Event本身是一个字节数组,可携带headers信息,是事务的基本单位,如果是文本文件,通常为一行记录,flume处理日志时流动的是event
 source:专门从网络收集数据。 可处理类型有avro thrift exec jms spooling directory netcat sequence generator syslog http legacy 甚至自定义。
 channel:存放的临时数据,缓存。可存放地点有内存(memory),jdbc,磁盘(file)
 sink:将数据发往目的地的组件。 目的地类型有HDFS,logger(控制台),avro,thrift,ipc,flie,HBase,solr,自定义
26.2 Flume操作命令
flume-ng命令
help 显示帮助文本
agent 运行一个Flume代理
avro-client 运行一个Avro的Flume客户端
version 显示Flume的版本信息
–conf,-c 使用目录下的配置
–classpath,-c 添加classpath
–dryrun,-d 并没有开始Flume,只是打印命令
-Dproperty=value 设置一个Java系统属性的值
-Xproperty=value 设置一个Java -x选项
–name,-n 这个Agent的名称
–conf-file,-f 指定一个配置文件(如果有-z可以缺失)
–zkConnString,-z 指定使用的zookeeper的链接(如果有-f可以缺失)
–zkBasePath,-p 指定agent config在Zookeeper Base Path
–no-reload-conf 如果改变不重新加载配置文件
–help,-h 显示帮助文本
–rpcProps,-P 远程客户端与服务器链接参数的属性文件
–headerFile,-R 每个新的一行数据都会有的头信息key/value

26.3 Flume安装和配置
flume可以只用安装到master、slave1、slave2三台虚拟机上任何一台上,专门接收外部数据,再保存到Hadoop的HDFS或HBase中。本实验把flume安装到slave2上,原因是现在slave2上运行的进程较少。
(1)启动slave2上的Linux命令终端,创建目录mkdir /usr/flume,切换到该目录下,执行命令cd /usr/flume,把flume文件上传到该目录下。
(2)然后对/usr/flume目录下的flume压缩文件apache-flume-1.6.0-bin.tar.gz,执行命令
tar -zxvf apache-flume-1.6.0-bin.tar.gz -C /usr/flume
-C是指解压压缩包到指定位置。
(3)配置到环境变量里,把flume的安装目录配置到/etc/profile中,执行命令
vi /etc/profile

(4)然后让配置文件生效,执行命令
source /etc/profile。
(5)切换到/usr/flume/apache-flume-1.6.0-bin/conf/目录下查看是否有配置文件flume-env.sh,目录下默认的情况下没有该文件,可修改文件的命名。执行命令
mv flume-env-sh.template flume-env.sh
然后修改flume-env.sh执行命令
vi flume-env.sh

(6)安装完毕后,切换到$FLUME_HOME/bin目录下,查看Flume版本,运行如下命令

查看Flume命令的参数,运行如下命令

(7)创建agent配置文件(注释内容不能出现在配置文件里)
在$FLUME_HOME/conf下创建agent配置文件flume.conf,进入flume的配置文件flume.conf 里,执行命令
vi /usr/flume/apache-flume-1.6.0-bin/conf/flume.conf。
下面是flume.conf配置文件。
#命名Agent a1的组件
a1.sources = r1 #命名Agent的sources为r1
a1.channels = c1 #命名Agent的channels 为c1
a1.sinks = k1 #命名Agent的sinks为k1

描述/配置Source

a1.sources.r1.type = avro #指定r1的类型为AVRO
a1.sources.r1.bind = 0.0.0.0 #将Source与IP地址绑定(这里指本机)
a1.sources.r1.port = 4141 #指定通讯端口为4141

描述Sink

a1.sinks.k1.type = logger #指定k1的类型为Logger(不产生实体文件,只在控制台显示)

描述内存Channel

a1.channels.c1.type = memory #指定Channel的类型为Memory
a1.channels.c1.capacity = 1000 #设置Channel的最大存储event数量为1000
a1.channels.c1.transactionCapacity = 100 #每次最大可以source中拿到或者送到sink中的event数量也是100

#将source、sink分别与Channel c1绑定
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
(8)启动agent
如果要启动一个Agent,首先需要进入到Flume的安装目录bin下,通过执行脚本命令flume-ng,此时需要指定Agent的名称、配置目录和配置文件、日志目标和级别。命令格式如下:
./flume-ng agent --conf-file /usr/flume/apache-flume-1.6.0-bin/conf/flume.conf --name a1 -Dflume.root.logger=INFO,console
–conf-file 指定一个配置文件
–name 指定agent名字。注意要跟配置文件agent的名字一致。
-Dflume.root.logger=INFO,console 日志的级别是INFO,打印在控制台。
(9)查看控制台

如上图所示,说明Agent启动成功(Agent的名字为a1,Source的名字为r1,类型为Avro)。上面的命令不能关闭,关闭后Agent将会停止。

第27章
Flume实验:Flume采集数据至HDFS

27.1实验目的
1.掌握一个Agent中Source、Sink、Channel组件之间的关系。
2.加深对Flume结构和概念的理解。
3.掌握Flume的编码方法及启动任务方法。
27.2实验要求
1.在一台机器上部署flume
2.实时收集本地Hadoop日志的最新信息然后将收集到的日志信息以一分钟一个文件的形式写入HDFS目录中。
27.3实验原理
Flume的核心就是一个agent,这个agent对外有两个进行交互的地方,一个是接受数据的输入——source,一个是数据的输出sink,sink负责将数据发送到外部指定的目的地。source接收到数据之后,将数据发送给channel,channel作为一个数据缓冲区会临时存放这些数据,随后sink会将channel中的数据发送到指定的地方—例如HDFS等,注意:只有在sink将channel中的数据成功发送出去之后,channel才会将临时数据进行删除,这种机制保证了数据传输的可靠性与安全性。
27.4实验步骤
注意本实验前必须先执行26.3的前6步。
(1)开启Zookeeper集群
分别在master,slave1,slave2(另开一个终端)上开启Zookeeper,执行命令zkServer.sh start
(2)开启Hadoop集群
在master上开启hadoop集群,执行命令start-all.sh
(3)在$FLUME_HOME/conf目录下创建文件,执行命令vi a2.conf,并向文件内写入内容,内容如下(注释内容不能出现在文件里)
#命名Agent a2的组件
a2.sources=r1 #命名Agent的sources为r1
a2.channels=c1 #命名Agent的sinks为k1
a2.sinks=k1 #命名Agent的channels 为c1

#描述/配置Source
a2.sources.r1.type=exec #指定r1的类型为exec
a2.sources.r1.command=tail -F /usr/a2.log #设置监控文件,这个文件需手动生成

#配置内存Channel
a2.channels.c1.type=memory #指定Channel的类型为Memory
a2.channels.c1.capacity=1000 #设置Channel的最大存储event数量为1000
a2.channels.c1.transactionCapacity=100 #每次最大可以source中拿到或者送到sink中的event数量也是100

#配置Sink
a2.sinks.k1.type=hdfs #指定Sink的类型为hdfs
a2.sinks.k1.hdfs.path=hdfs://192.168.11.10:9000/flume/a2.flume #设置hdfs的路径
a2.sinks.k1.hdfs.fileType=DataStream #设定hdfs的文件类型
a2.sinks.k1.hdfs.rollSize = 134217728 #HDFS上的文件达到128M时生成一个文件
a2.sinks.k1.hdfs.rollInterval = 60 #HDFS上的文件达到60秒生成一个文件

#为Channel绑定Source和Sink
a2.sources.r1.channels=c1
a2.sinks.k1.channel=c1

(4)启动slave2上的Linux命令终端,在/usr目录下创建文件a2.log,执行命令
echo ‘this is first line’>a2.log。
(5)启动flume,切换到$FLUME_HOME/bin目录下,执行Flume命令
./flume-ng agent --conf-file /usr/flume/apache-flume-1.6.0-bin/conf/a2.conf --name a2 -Dflume.root.logger=INFO,console
–conf 使用目录下的配置
–conf-file 指定一个配置文件
–name 指定agent名字(要和配置文件的参数保持一致,a1.sources=r1两个a1保持一致)
-Dflume.root.logger=INFO,console 日志的级别是INFO,打印在控制台
查看控制台,如图所示

注意:如果出现22.5.4的步骤11问题,需要解决。必须保证192.168.11.10:9000是active状态。下面是出错时信息。

如果成功显示下面的信息。

(6)向a.log文件里追加内容
切换到/usr目录下,执行命令echo ‘this is second line’>>a.log,查看控制台

我们在登录http://192.168.11.10:50070/explorer.html#flume,查看hdfs是否多一个文件。

第28章
Sqoop实验:使用Sqoop将HIVE数据导出到MySQL

28.1实验目的
1.了解sqoop两种导数据方式
2.掌握sqoop的安装部署
3.掌握sqoop如何把hive数据导出到mysql中
28.2实验原理
28.2.1概述
Sqoop(发音:skup)是一款开源的工具,主要用于在Hadoop(Hive)与传统的数据库(mysql、postgresql…)间进行数据的传递,可以将一个关系型数据库(例如 : MySQL ,Oracle ,Postgres等)中的数据导进到Hadoop的HDFS中,也可以将HDFS的数据导进到关系型数据库中。
Sqoop项目开始于2009年,最早是作为Hadoop的一个第三方模块存在,后来为了让使用者能够快速部署,也为了让开发人员能够更快速的迭代开发,Sqoop独立成为一个Apache项目。
绝大部分企业所使用的sqoop的版本都是 sqoop1。sqoop-1.4.6 或者 sqoop-1.4.7 都是 sqoop1。sqoop-1.99.4是 sqoop2。
本章使用的是sqoop-1.4.7。文件是sqoop-1.4.7.bin_hadoop-2.6.0.tar.gz。
28.2.2 Sqoop概述
下图描述了Sqoop的工作流程。

sqoop 是 apache 旗下一款“Hadoop 和关系数据库服务器之间传送数据”的工具。核心的功能有两个:导入数据、导出数据
导入数据:MySQL,Oracle 导入数据到 Hadoop 的 HDFS、HIVE、HBASE 等数据存储系统
导出数据:从 Hadoop 的文件系统中导出数据到关系数据库 mysql 等 Sqoop 的本质还是一个命令行工具,和 HDFS,Hive 相比,并没有什么高深的理论。
将来sqoop在使用的时候有可能会跟那些系统或者组件打交道?HDFS, MapReduce, YARN, ZooKeeper, Hive, HBase, MySQL。注意sqoop就是一个工具, 只需要在一个节点上进行安装即可。
28.2.3 sqoop 命令
create-hive-table 将表导进hive中
export 将hdfs目录导出到数据库中
import 将数据库的表导进hdfs中
import-all-tables 将数据库中的所有表都倒进hdfs中
list-tables 列出数据库中的所有表
list-databases 列出服务器中的所有数据库
version 版本信息
28.2.4安装sqoop
(1)启动master上的Linux命令终端,创建目录mkdir /usr/sqoop,切换到该目录下,执行命令cd /usr/sqoop,把sqoop文件sqoop-1.4.7.bin_hadoop-2.6.0.tar.gz上传到该目录下。
(2)然后对/usr/sqoop目录下的sqoop压缩文件sqoop-1.4.7.bin_hadoop-2.6.0.tar.gz,执行命令
tar -zxvf sqoop-1.4.7.bin_hadoop-2.6.0.tar.gz -C /usr/sqoop
-C是指解压压缩包到指定位置。
(3)修改配置文件sqoop-env.sh
在sqoop/conf目录下有一个文件sqoop-env-template.sh,把它复制为sqoop-env.sh并修改,执行命令:
cp sqoop-env-template.sh sqoop-env.sh
然后编辑sqoop-env.sh,执行命令:
vi sqoop-env.sh
具体内容如下:

先去掉注释,然后把hadoop、hbase、hive、Zookeeper的安装路径填上。
(4)配置sqoop到环境变量里,把sqoop的安装目录配置到/etc/profile中,执行命令
vi /etc/profile
修改profile文件如下

(5)然后让配置文件生效,执行命令source /etc/profile。
(6)拷贝JDBC包
将JDBC包mysql-connector-java-5.1.47.jar放入 S Q O O P H O M E / l i b 目 录 下 ( 7 ) 拷 贝 h a d o o p 包 将 h a d o o p 包 h a d o o p − c o m m o n − 2.6.5. j a r 放 入 SQOOP_HOME/lib目录下 (7)拷贝hadoop包 将hadoop包hadoop-common-2.6.5.jar放入 SQOOPHOME/lib7hadoophadoophadoopcommon2.6.5.jarSQOOP_HOME/lib目录下
(8)修改$SQOOP_HOME/bin/configure-sqoop
注释HCatalog,Accumulo检查(因为你没有使用HCatalog,Accumulo等HADOOP上的组件),如果没有注释掉就会产生警告信息。

(9)测试与mysql的连接
①开启mysql
在终端上输入命令:service mysqld start
②测试是否连通
在$SQOOP_HOME/bin下输入命令:
sqoop list-databases --connect jdbc:mysql://192.168.11.10:3306/ --username root --password 123456
注意username和password是第十六章安装mysql设置的用户名和密码。192.168.11.10是安装mysql的Linux虚拟机IP地址。list-databases列出所有数据库。

28.2.5 hive数据导出到mysql
(1)master虚拟机上/usr目录下创建user.txt,内容如下(分别是id,name,age):
1,Jed,15
2,Tom,16
3,Tony,13
4,Mary,17
5,Henny,23
6,Jack,29
(2)在HDFS上新建/user/sqoop目录,并把user.txt文件上传到hdfs上
[root@hadoop sbin]# hadoop fs -mkdir /user/sqoop
[root@hadoop sbin]# hadoop fs -put /usr/user.txt /user/sqoop
(3)在hive建立表users
①启动Zookeeper集群
在master,slave1,slave2上各执行命令:zkServer.sh start
②启动hadoop集群
在master上执行start-all.sh
在slave1启动start-yarn.sh (slave1没出现resourcemanager)
③启动mysql
在master上执行service mysqld start
④启动hive
在master上执行hive,执行$HIVE_HOME/conf/hive

⑤hive中创建表
hive>create table users

(id int,name string,age int)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ‘,’
STORED AS TEXTFILE;
(4)上传文件中的数据到hive的users中
hive>load data local inpath ‘/usr/user.txt’ into table default.users;
注:default是hive默认数据库的名字
(5)查看hive数据的存储地

注意:hive本身是不存储数据的,事实上它的数据本身还是放在hdfs上的。/user/hive/warehouse/就是hive默认存放数据的地方。
(4)查看users表中的数据
hive>select * from users;
(5)在mysql中创建表
①进入mysql
执行命令:mysql -u root -p 通过提示,输入密码,进入mysql 。
②创建表
mysql>use hive
mysql> create table users(
-> id int not null,
-> name varchar(20) not null,
-> age int not null,
-> primary key (id));
(6)把hive的数据导出到mysql中,执行命令
[root@master Desktop]# sqoop export
–connect jdbc:mysql://192.168.11.10:3306/hive \ //mysql的hive数据库
–username root
–password 123456
–table users
–export-dir hdfs://master:9000/user/hive/warehouse/users \ //master必须是active
–input-fields-terminated-by ‘,’; //数据间按’,’连接
注意:每行的""前必须有空格,具体效果见下图。

(7)查看mysql数据,看是否将hive中的数据通过sqoop导出到mysql中。

第29章
Kafka实验:Kafka发布订阅系统

29.1实验目的
1.掌握Kafka的安装部署
2.掌握Kafka的Topic创建及如何生成消息和消费信息
3.掌握Kafka和Zookeeper之间的关系
4.了解kafka如何保存数据及加深对Kafka相关概念的理解
29.2实验要求
在三台机器上(以master,slave1,slave2为例),分别部署一个Broker,创建一个Topic,启动模拟的生产者和消费者脚本,在生产者端向Topic里写数据,在消费者端观察取到的数据。
29.3 Kafka
Kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写。Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。Kafka最初是为了解决LinkedIn内部数据管道问题应用而生的。
下面是Kafka的发展历史:
 2010年底,Kafka作为开源项目在GitHub上发布。
 2011年7月,成为Apache的孵化器项目。
 2012年10月,Kafka从孵化器项目毕业,成为Apache顶级项目。
 2014年秋,Kafka原创团队离开LinkedIn,创办Confluent。
 最新版本:2.1.0
 目前Kafka由LinkedIn、Confluent和开源社区共同开发和维护。
下面是Kafka的应用场景。上层是数据源,数据来自于App日志、数据库、第三方推送的数据、后端服务器产生的server log。这些数据需要往下游传递,中间都需要接一个Kafka,利用Kafka作为消息的缓冲。下层是数据的使用方,实现指标分析、活动监控、日志搜索、安全分析。

 活动追踪:跟踪大型电商网站用户与前端应用程序发生交互。如网站的PV (PV是页面点击量,一个访问者在24小时(0点到24点)内到底看了你网站几个页面。PV之于网站,就像收视率之于电视,从某种程度上已成为投资者衡量商业网站表现的最重要尺度。)和UV(UV是访问某个站点或点击某条新闻的不同IP地址的人数) 分析。
 传递消息:系统间异步的信息交互。如营销活动中,用户系统注册后将信息发布到Kafka,销售系统从Kafka订阅信息后发送券码福利。
 数据库:将数据库的更新发布到Kafka上。如交易发生后修改数据库,数据库更新后发布到Kafka上传送给消费者。
 日志收集:收集系统及应用程序的度量指标和日志。如应用监控和告警。
你可以把Kafka看成大型的分布式的,基于发布/订阅的消息缓冲区。
 集群模式:动态增减节点,伸缩性
 多个生产者:多个程序可以同时推送数据
 多个消费者:多个程序可以同时订阅数据
 消息持久化:当数据被消费完后,还会进行基于磁盘的数据存储,当某个条件达到后,再进行数据的销毁
Kafka通过横向扩展生产者、消费者和broker(每台安装Kafka的机器),Kafka可以轻松处理巨大的消息流、具有很好的性能和吞吐量。
29.3.1 Kafka名词解释

名称 解释

1 Broker(代理) 消息中间件处理节点,一个Kafka节点就是一个broker,一个或多个broker可以组成一个Kafka集群。
2 Topic(主题) Kafka根据Topic对消息进行逻辑归类,发布到Kafka集群的每条消息都需要一个Topic。就像是mysql的数据分到不同的表。
3 Partition(分区) 物理上的概念,topic物理上的分组。一个topic可以分为多个Partition,每个Partition内部是有序的。
4 Replication (副本) 副本,每个partition有多个副本。存储在不同的broker上,保证消息的高可用。
5 Segment(片段) Partition物理上有多个segment组成,每个segment是一个文件,每个segment存着message消息
6 Message(消息) 消息,是基本的通讯单位,由一个key,一个value和时间戳构成。如图mysql里的一条记录。
7 Producer(生产者) 消息生产者,向Broker发送消息的客户端
8 Consumer(消费者) 消息消费者,从Broker读取消息的客户端
9 ConsumerGroup
(消费者群组) 每个Consumer属于一个特定的Consumer Group,一个消息可以发送到多个不同的Consumer Group,但是一个Consumer Group中只能有一个Consumer能够消费该消息
29.3.2 Kafka生产者
kafka的生产者向broker写入数据。Kafka消息生产者的每条消息都是一个ProducerRecord,里面包含必选数据Topic和Value,如主题A。可选数据为Key和Partition。消息经过序列化器和分区器后传给Kafka Broker。传送失败后,可以进行重试,如果不能重试后就抛出异常。如果成功后返回元数据。

29.3.2 Kafka消费者
Kafka的消费者从broker读取数据。主题T1一共有4个分区,分别是分区0到分区3。有两个消费者群组可以消费主题T1。一个主题的一个分区只能被一个组中的一个消费者消费。所以消费者群组1中的4个消费者消费主题T1的4个分区,如果消费者群组1增加一个消费者5,消费者5不会消费任何分区。如果消费者4宕机,分区3数据将由另外3个消费者中一个进行消费,这种情况称为再均衡,分区的所有权将从一个消费者转移到另一个消费者。主题可以被一个组反复消费,只要消息没有被删除。

29.3.3 Kafka使用场景

  1. Messaging
    对于一些常规的消息系统,Kafka是个不错的选择; partitions/replication和容错,可以使Kafka具有良好的扩展性和性能优势。不过到目前为止,我们应该很清楚认识到,成并没有提供JMS中的“事务性”、“消息传输担保(消息确认机制)”、“消息分组”等企业级特性;Kafka只能使用作为“常规”的消息系统,在一定程度上,尚未确保消发送与接收绝对可靠(比如,消息重发,消息发送丢失等)。
  2. Website activity tracking
    Kafka可以作为“网站活性跟踪”的最佳工具:可以将网页/用户操作等信息发送到Kafka中。并实时监控,或者离线统计分析等。
  3. Log Aggregation
    Kafka的特性决定它非常适合作为“日志收集中心”; application可以将操作日志“批量”“异步”的发送到Kafka集群中,而不是保存在本地或者DB中; Kafka可以批量提交消息/压缩消息等,这对 Producer端而言,几乎感觉不到性能的开支。此时Consumer端可以使 Hadoop等其他系统化的存储和分析系统。

29.4实验步骤
29.4.1安装Kafka集群
安装Kafka集群前,需要安装ZooKeeper集群。
IP地址/主机名 角色
192.168.11.10/master Broker,id为1。
192.168.11.20/slave1 Broker,id为2。消息生产者Producer。
192.168.11.30/slave2 Broker,id为3。消息消费者Consumer。

(1)在master启动 Linux命令终端,切换到/usr/kafka,把kafka文件上传kafka_2.11-1.1.1.tgz到该目录下。
(2)然后对/usr/kafka目录下的kafka压缩文件 ,执行命令
tar -zxvf kafka_2.11-1.1.1.tgz -C /usr/kafka
-C是指解压压缩包到指定位置。
(3)把kafka分发到slave1、slave2上
在slave1、slave2创建/usr/kafka
执行命令:scp -r kafka_2.11-1.1.1 root@slave1:/usr/kafka
scp -r kafka_2.11-1.1.1 root@slave2:/usr/kafka
(4)修改配置文件,在/usr/kafka/kafka_2.11-1.1.1/config下,修改server.properties,修改内容如下。
master配置
#broker.id
broker.id=1
#broker.port
port=9092
#host.name
host.name=master
#本地日志文件位置
log.dirs=/usr/kafka/logs
#zookeeper地址
zookeeper.connect=master:2181,slave1:2181,slave2:2181

slave1配置
#broker.id
broker.id=2
#broker.port
port=9092
#host.name
host.name=slave1
#本地日志文件位置
log.dirs=/usr/kafka/logs
#zookeeper地址
zookeeper.connect=master:2181,slave1:2181,slave2:2181

slave2配置
#broker.id
broker.id=3
#broker.port
port=9092
#host.name
host.name=slave2
#本地日志文件位置
log.dirs=/usr/kafka/logs
#zookeeper地址
zookeeper.connect=master:2181,slave1:2181,slave2:2181
(5)启动Zookeeper集群
三台机器都运行zkServer.sh start
(6)进入/usr/kafka/kafka_2.11-1.1.1/bin目录,先分别给三台机器bin目录下所有文件赋予执行的权限,执行命令:
chmod -R +x /usr/kafka/kafka_2.11-1.1.1/bin/
 -R : 对目前目录下的所有文件与子目录进行相同的权限变更
 + 表示增加权限
 x 表示可执行
然后三台Linux虚拟机分别进入$KAFKA_HOME/bin目录下,执行启动各自的kafka服务。执行命令:
nohup ./kafka-server-start.sh …/config/server.properties &

注意:nohup Command [ Arg … ] [ & ]
表示把进程放到后台运行,进程号为3107。使用jobs查看当前后台运行的进程。

(7)查看进程,发现三台机器进程都有kafka这个进程,说明启动成功。

(8)在master上创建Topic,这个Topic有2个分区,每个分区有2个副本。
执行命令:
./kafka-topics.sh --create --zookeeper master:2181,slave1:2181,slave2:2181 --replication-factor 2 --partitions 2 --topic test

注意:
 --replication-factor 每个partition的副本数
 --partitions 一个Topic的分区数
 --topic 主题名字
(9)在slave1的KaTeX parse error: Unexpected character: '' at position 137: …topic test 注意: ̲ --broker-list …KAFKA_HOME/bin目录下启动消费者(即Consumer端)
执行命令:
./kafka-console-consumer.sh --zookeeper master:2181,slave1:2181,slave2:2181 --topic test --from-beginning
注意:
 --zookeeper表示zookeeper集群节点列表
 --from-beginning 开始消费
(11)在producer端输入任意信息,然后观察Consumer端接收到的数据,如图所示:
producer端,输入信息Hello Kafka发布到Kafka:

Consumer端,订阅刚才发布到Kafka上的信息:

第30章
项目:在线旅游系统分析系统

30.1项目的介绍
如今在线预订酒店已经成为用户的首选,其方便性及透明度均比过去电话预订提升了很多,而用户在线预订会选择OTA(Online Travel Agent),即在线旅行社,代表有携程、艺龙、去哪。这些OTA在给用户带来方便的同时,也产生了很多用户访问量,并积累了很多用户数量。为了更好地提高酒店房间的入住率,间接提高自身收入,OTA会分析数据的价值和它们存在的意义,那么选择一个大数据分析平台,看起来很有必要。
设计一款OTA大数据分析处理平台,对整个OA行业提供了所需要的解决方案,能够解决类似各大OTA平台数量和交易金额纷纷大幅增长的问题。以携程和去哪为例,面对如此大的数据交易和自身数据的价值,开发一个合适的数据分析平台采集OTA数据、分析OTA数据、展示OTA数据很有必要。因为通过这样的大数据分析平台,才能了解客户深人客户,才能更好地为客户服务
离线分析平台方案主要分四层:存储平台层、数据日志数据采集层。
存储平台层负责提供数据存储、数据仓库的功能,包括 Hadoop、Hive等。
数据采集层通过 Flume把样本数据采集至HDFS中。
数据分析层分为 MapReduce数据清洗、Hive数据分析、Sqoop2数据迁移。
30.2功能需求
统计每家酒店未来一个月内每天的预定量
所有酒店的预定区域分布
30.3软件开发关键技术
 Hadoop作为分布式计算平台。
 Flume作为数据采集系统
 Hive作为数据分析系统
 Sqoop作为HDFS和 MySQL数据库之间的数据迁移和转换。

30.4项目流程

30.5项目的具体步骤
IP地址/主机名 角色
192.168.11.10/master ZooKeeper,Hadoop(active),HBase,Hive,Sqoop,MySQL
192.168.11.20/slave1 ZooKeeper,Hadoop(standby),HBase
192.168.11.30/slave2 ZooKeeper ,Hadoop ,Flume,HBase

30.5.1数据文件
首先我们看一看旅行社数据文件hotel.txt。文件内容格式如下,每个字段值分别为酒店id、酒店名字、入住日期、入住人数、入住城市。
1,如家,2013-11-1,2,上海
2,七天,2013-11-21,1,北京
3,如家,2013-11-11,3,上海
4,汉庭,2013-11-11,3,上海
5,四季,2013-11-2,1,北京
6,四季,2013-11-2,3,上海
7,七天,2013-11-3,2,北京
8,七天,2013-11-3,2,北京
9,七天,2013-11-3,3,北京
10,七天,2013-11-3,4,北京

30.5.2采集数据

  1. 创建监控文件
    启动slave2上的Linux命令终端,在/usr目录下创建的hotel.txt,执行命令
    touch hotel.txt
    注意:
     master、slave1、slave2三台Linux虚拟机中只有slave2中安装了flume,具体安装步骤见26.3。
     hotel.txt中现在没有数据,当flume启动时再往hotel.txt中添加数据。
  2. 创建Agent配置文件
    (1)开启Zookeeper集群
    分别在master,slave1,slave2(另开一个终端)上开启Zookeeper,执行命令zkServer.sh start
    (2)开启Hadoop集群
    在master上开启hadoop集群,执行命令start-all.sh
    (3)在$FLUME_HOME/conf下创建配置文件hotel.conf,进入flume的配置文件hotel.conf,执行命令vi /usr/flume/apache-flume-1.6.0-bin/conf/hotel.conf。
    a2.sources=r1
    a2.channels=c1
    a2.sinks=k1

a2.sources.r1.type=exec
a2.sources.r1.command=tail -F /usr/hotel.txt

a2.channels.c1.type=memory
a2.channels.c1.capacity=1000
a2.channels.c1.transactionCapacity=100

a2.sinks.k1.type=hdfs a2.sinks.k1.hdfs.path=hdfs://192.168.11.10:9000/flume/hotel.flume a2.sinks.k1.hdfs.fileType=DataStream
a2.sinks.k1.hdfs.rollSize = 134217728
a2.sinks.k1.hdfs.rollInterval = 60

a2.sources.r1.channels=c1
a2.sinks.k1.channel=c1
(4)启动flume,切换到,切换到$FLUME_HOME/bin目录下,执行Flume命令:
./flume-ng agent --conf-file /usr/flume/apache-flume-1.6.0-bin/conf/hotel.conf --name a2 -Dflume.root.logger=INFO,console
注意:如果出现下面的问题,说明192.168.11.10:9000状态是standby。必须将192.168.11.10:9000修改为active状态。下面是flume-ng出错时信息。

具体步骤见22.5.4的步骤11。
下面是flume-ng执行成功时信息。

(5)把实际数据保存到/usr/hotel.txt下,在hdfs上可以看到flume生成的数据文件hotel.flume。

注意:
 复制数据时必须保证光标在最后一行数据的末尾。
 我们将数据保存到/usr/hotel.txt文件中,flume会自动将文件保存到HDFS中/flume/hotel.flume中,具体参数见$FLUME_HOME/conf/hotel.conf。
30.5.3数据分析
数据分析的第一步是数据清洗(Data cleaning)。数据清洗是对对数据进行重新审查和校验的过程,目的在于删除重复信息、纠正存在的错误,并提供数据一致性。
数据清洗从名字上也看的出就是把“脏”的“洗掉”,指发现并纠正数据文件中可识别的错误的最后一道程序,包括检查数据一致性,处理无效值和缺失值等。因为数据仓库中的数据是面向某一主题的数据的集合,这些数据从多个业务系统中抽取而来而且包含历史数据,这样就避免不了有的数据是错误数据、有的数据相互之间有冲突,这些错误的或有冲突的数据显然是我们不想要的,称为“脏数据”。我们要按照一定的规则把“脏数据”“洗掉”,这就是数据清洗。而数据清洗的任务是过滤那些不符合要求的数据,将过滤的结果交给业务主管部门,确认是否过滤掉还是由业务单位修正之后再进行抽取。不符合要求的数据主要是有不完整的数据、错误的数据、重复的数据三大类。数据清洗是与问卷审核不同,录入后的数据清理一般是由计算机而不是人工完成 。
ETL编程
ETL,是英文Extract-Transform-Load的缩写,用来描述将数据从来源端经过萃取(extract)、转置(transform)、加载(load)至目的端的过程。
(1)在eclipse上创建map/reduce工程,打开eclipse,新建一个工程。“file” ->“New” ->“other”,select a wizard中选择“Map/Reduce Project”,输入工程名etl(自定义)。

(2)单击工程,右键点击src,选择source folder,然后创建resource。

(1)创建hadoop_ch30_ETL项目之后,编写代码
map类:
package etl;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

public class EtlMapper extends Mapper{
@Override
protected void map(LongWritable key, Text value,Context context)
throws IOException, InterruptedException {
//拿到一行文本内容,转换成String 类型
String line = value.toString();
//将这行文本切分成单词
String[] words=line.split(",");
String hotelid=words[0];
String hotelname=words[1];
String indate=words[2];
String ordernum=words[3];
String provincename=words[4];
String hotel=hotelid+","+hotelname+","+indate+","+
ordernum+","+provincename;
context.write(new Text(hotel),NullWritable.get());
}
}

job类:
package etl;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
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.output.FileOutputFormat;

public class EtlJob {
public static void main(String[] args) throws Exception{
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
job.setJarByClass(EtlMapper.class);
//指定本业务job要使用的mapper,reducer业务类

		job.setMapperClass(EtlMapper.class);
		//job.setReducerClass(MapReduceJoinReducer.class);
		//虽然指定了泛型,以防框架使用第三方的类型
		//指定mapper输出数据的kv类型
		job.setMapOutputKeyClass(Text.class);
		job.setMapOutputValueClass(NullWritable.class);

		//指定最终输出的数据的kv类型
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(NullWritable.class);

		//指定job输入原始文件所在位置
        FileInputFormat.addInputPath(job, new Path(args[0]));
		//指定job输入原始文件所在位置
        FileOutputFormat.setOutputPath(job,new Path(args[1]));
		//将job中配置的相关参数以及job所用的java类所在的jar包,提交给yarn去运行
	   boolean b = job.waitForCompletion(true);
        System.exit(b?0:1);
    }

}
(2)将项目打成jar包(项目打包过程见9.4.5)

(3)将jar包上传到linux虚拟机master上的/root目录下。
(4)linux虚拟机master上运行jar文件。
执行命令:
语法:hadoop jar Xxx.jar package.MainClass 数据文件 生成目录
hadoop jar /root/etl.jar hdfs://192.168.11.10:9000/hotel.txt hdfs://192.168.11.10:9000/output
(5)执行完之后,查看hdfs上有一个output文件夹,进入文件夹发现出现下面两个文件

(6)查看part-r-00000文件,在master上执行hdfs dfs -cat /output/part-r-00000

30.5.4业务分析
(1)启动master虚拟机上的MySQL,执行命令
service mysqld start
master虚拟机上在$HIVE-HOME/bin目录下执行命令hive进入 Hive Shell环境,执行创建表命令,如下所示:
[root@master conf]# hive
Logging initialized using configuration in jar:file:/usr/hive/apache-hive-1.2.2-bin/lib/hive-common-1.2.2.jar!/hive-log4j.properties
hive> create database ota;
hive> use ota;
hive>create external table hotelorder(
orderid int,
hotelname string,
indate string,
ordernum int,
provincename string)
row format delimited
fields terminated by ‘,’ ;
(2)从HDFS上导入数据到Hive表
hive> load data inpath ‘/output/part-r-00000’ into table ota.hotelorder;
(3)然后查看hotelorder表

(4)创建分析结果表,在hive的安装目录bin下执行命令./hive进入hive shell环境,执行创建表命令,如下所示。
创建通过日期的预订数量表
hive>create external table order_num_bydate(
hotelname string,
indate string,
ordernum int)
row format delimited
fields terminated by ‘,’;
创建通过省份的预订数量
hive>create external table order_num_byprovince(
provincename string,
ordernum int)
row format delimited
fields terminated by ‘,’;
上面我们创建了ota数据库,并在ota库中创建了两章表,HIVE会自动在HDFS中创建两级目录,如下图所示。

(3)Hive计算
未来一个月内每天的预订量如下。
hive>insert overwrite table order_num_bydate
select hotelname, indate,count() as num from hotelorder where indate >= ‘2013-11-1’ and indate <= ‘2013-11-31’ group by hotelname, indate order by indate;
全国酒店预订量分布如下。
hive>insert overwrite table order_num_byprovince
select provincename,count(
) as num from hotelorder group by provincename;
执行完添加语句后,查看hive中order_num_bydate和order_num_byprovince表数据。

30.5.5 使用Sqoop数据导出
本节使用Sqoop将Hive中的表数据导出到MySQL。

  1. 使用master虚拟机进入mysql
    执行命令:
    [root@master /]# mysql -uroot -p123456
  2. MySQL创建表
    mysql> create database hotel;
    mysql> use hotel;
    mysql>CREATE TABLE order_num_bydate (
    hotelname varchar(20) NOT NULL,
    indate varchar(20) NOT NULL,
    ordernum int(11) NOT NULL
    );
    mysql>CREATE TABLE order_num_byprovince (
    provincename varchar(20) NOT NULL,
    ordernum int(11) NOT NULL
    );
  3. 使用Sqoop将hive表数据导出到mysql的hotel数据库中,分别导出order_num_bydate和order_num_byprovince两个表数据
    下面是导出order_num_bydate表
    [root@master Desktop]# sqoop export
    –connect “jdbc:mysql://192.168.86.150:3306/hotel?useUnicode=true&characterEncoding=utf-8”
    –username root
    –password 123456
    –table order_num_bydate
    –export-dir hdfs://master:9000/user/hive/warehouse/ota.db/order_num_bydate/000000_0
    –input-fields-terminated-by ‘,’;
    下面是导出order_num_byprovince表
    [root@master Desktop]# sqoop export
    –connect “jdbc:mysql://192.168.11.10:3306/hotel?useUnicode=true&characterEncoding=utf-8”
    –username root
    –password 123456
    –table order_num_byprovince
    –export-dir hdfs://master:9000/user/hive/warehouse/ota.db/order_num_bydate/000000_0
    –input-fields-terminated-by ‘,’;

出现问题:乱码
①sqoop导出命令中要在connect后面加上useUnicode=true&characterEncoding=utf-8
②修改mysql中/etc/my.cnf文件

③重启mysql服务 service mysqld restart

(4)检查Mysql导出结果
mysql>select * from order_num_bydate;

mysql>select * from order_num_byprovince;

如何使得standby状态的master变成active。
在master上执行下面两个步骤:
步骤1:将master状态由standby修改为active。
hdfs haadmin -transitionToActive rm1 -forcemanual
步骤2:重新启动ZooKeeper。
master和slave1虚拟机停止zkfc。
hadoop-daemon.sh stop zkfc
再在master虚拟机上重新执行
hadoop-daemons.sh start zkfc

你可能感兴趣的:(Hadoop)