大数据点点滴滴积少成多---进阶之路

1.   HADOOP 入门 6

1.1 大数据部分的课程介绍 6

1.2 学习建议 6

1.3 就业前景及发展规划 6

1.4 HADOOP 简介 7

1.4.1 前言 7

1.4.2 hadoop 应用场景 7

1.5 hadoop 集群部署安装 7

2. HDFS 9

2.1 hdfs shell 操作 10

2.2 HDFS 的一些概念(概念)和特性 11

2.3 HDFS java 操作 11

2.4 hdfs 的工作机制 12

2.5 namenode 工作机制 14

2.6 datanode 的工作机制 14

2.7 一些补充 15

3. 深入HDFS 源码 16

3.1 hdfs 读数据流程 16

3.2 hdfs 写数据流程 16

3.3 hadoop RPC 框架 16

3.4 hdfs 读数据源码分析 18

3.5 hdfs 写数据源码分析 18

3.6 远程调试跟踪的Hadoop的服务端的代码 18

4. MAPREDUCE 入门 22

4.1 为什么要MAPREDUCE 22

4.2 MAPREDUCE 程序运行演示 22

4.3 MAPREDUCE 编程规范 22

 

4.5 MAPREDUCE 程序提交运行模式及调试方法 24

4.5.1 本地运行模式 24

4.5.2 集群运行模式 25

4.6 MAPREDUCE 中的Combiner 25

4.7 MAPREDUCE 中的序列化 30

4.8 Mapreduce 的排序初步 31

5. Mapreduce 高级特性(一) 31

5.1程序分区编程 31

5.2 Mapreduce 的排序 31

5.2.1部分排序示例,多减少任务自动实现各输出文件有序 32

5.2.3总排序机制 32

5.2.4二级排序机制 34

5.3 shuffle 详解 39

5.4 mr 程序任务数的规划机制 40

5.5 Mapreduce 连接算法 40

5.6 mapreduce 分布式缓存 44

6. Mapreduce 高级特性(二) 45

6.1 Mapreduce 输入格式组件 45

6.1.1  地图任务数量的决定机制引入: 45

6.1.2 InputFormat 的继承体系 46

6.2 MultipleInputs 48

6.3 自定义输入侧格式 49

6.4 Mapreduce 输出格式组件 52

6.4.1 TextOutPutFormat 源码结构解析: 52

6.4.2 MultipleOutputs 52

6.4.3 自定义FileOutPutFormat 52

6.5配置配置对象与Toolrunner 52

6.6 mapreduce 数据压缩 54

6.7 mapreduce 的计数器 56

6.7.1 mapreduce 框架自带计数器: 56

6.7.2 用户自定义计数器: 56

6.8 mapreduce 的日志分析 56

6.9 任务串联 57

7.纱集群 58

7.1产生纱线的原因 58

7.2 Yarn 的架构 58

7.3纱线运行应用程序的流程 59

7.4 MapReduce 程序向提交执行的流程分析 60

7.5申请生命周期 60

7.5 资源请求 60

7.6 任务调度 - 能力调度员/公平调度员 60

7.6.1程序调度概述简单描述 61

7.6.2 Capacity Scheduler 配置 62

7.6.3调度公平程序配置 64

7.7纱线应用程序开发(仅做了解,通常不需要应用开发人员来做) 67

8.动物园管理员 67

8.1 hadoop-sp 问题及HA 解决思路 67

8.2 zookeeper 简介 67

8.3 zookeeper 集群搭建 67

8.4 zookeeper 演示测试 68

8.5 zookeeper-api 应用 71

8.5.1 基本使用 71

8.5.2 demo 增删改查 71

8.6 zookeeper 应用案例(分布式应用HA || 分布式锁) 72

9. Hadoop-HA 81

9.2联邦机制,配置 82

9.3 CDH 介绍,演示 82

10. Hbase 基础 82

10.1 hbase 数据库介绍 82

10.2 hbase 集群结构 84

10.3 hbase 集群搭建 84

10.4 命令行演示 86

10.4.1 基本外壳命令 86

10.4.2 建表高级属性 89

10.5 hbase 代码开发(基本,过滤器查询) 92

10.6 hbase 工作原理 96

10.6.1   物理存储 96

10.6.2   系统架构 98

10.6.3   寻址机制 99

10.6.4区域管理 100

10.6.5 Master 工作机制 101

11. Hbase 高级应用 102

11.1 hbase 应用案例看行键设计 102

11.2 Hbase mapreduce 结合 102

11.2.1 Hbase 中读取数据写入hdfs 102

11.2.2 Hbase 中读取数据写入hdfs 104

11.3 hbase 高级编程 106

11.3.1 协处理器 106

11.3.2 二级索引演示 106

12. Hive 基础 106

12.1 hive 引入 106

12.2 hive 技术架构 106

12.3 hive 原理---- 元数据,数据存储,核心机制 107

12.4 Hive 的安装部署 107

12.5 Hive 使用方式 107

12.6 hql 基本语法 108

12.6.1 基本HQL 语法 108

12.6.2 hql 查询进阶 111

12.7 hive 数据类型 114

13. Hive 高级应用 115

13.1 Hive 常用函数 115

13.2   自定义函数 115

13.3 hive 高级操作 115

13.4 hive 优化 115

13.5 hive 对数据倾斜的优化 115

13.5.1 数据倾斜的原因 115

13.5.2 数据倾斜的解决方案 116

13.5.3 典型的业务场景 117

13.5.4 总结 118

14.   数据采集工具 119

14.1水槽介绍 119

15.风暴基础 137

15.1风暴介绍 137

15.2风暴基本概念 137

15.3 storm 集群搭建 137

15.4 storm 示例编程 137

15.5 kafka 介绍与应用开发 137

16. Storm 高级特性 137

16.1风暴卡夫卡整合性 137

16.2风暴元型态组 138

16.3风暴事务拓扑三叉戟 138

16.3.1 TridentTopology 简介 138

16.3.2   应用举例 138

16.3.3 引言 139

16.3.4 newStream 140

16.3.5每个 141

16.3.6建立 143

16.3.7分组 143

16.4 Storm Topology ack 机制 145

16.5 TridentTopology 调用关系 146

16.6 seeder tuple 的状态机 148

16.4 stormRPC 149

17. 机器学习 149

17.1 机器学习概念介绍 149

17.2 电商推荐- 协同过滤算法 149

17.3 预测算法- 简单线性回归 149

18. Hadoop 综合项目 150

19. Hadoop 综合练习 150

20. Spark 基础之scala 语言 150

21. Spark 基础 151

21.1 spark 框架介绍 151

21.2 spark 集群概念 153

21.3 spark 优势简介 153

21.4 spark 集群搭建 154

21.5 spark 编程基础 154

21.5.1 spark-shell 编程 154

21.5.2 ide 编程 155

22. Spark 原理深入 157

22.1 spark-RDD 原理深度解析 157

22.2 spark 内核源码阅读 157

22.2.1 akka 介绍,演示示例) 157

22.2.2 RDD 158

22.2.3 SparkContext 158

22.3 任务调度流程 165

22.3.1阶段划分 165

22.3.2任务提交 165

22.3.3 DAGScheduler 165

22.3.4 TaskScheduler 165

23. Spark-Streaming 应用 166

23.1 spark-Streaming 概念 166

23.2流动火花原理 166

23.3媒体流应用开发实例 166

23.3.1 hdfs 166

23.3.2水槽 167

23.3.3套接字 168

23.3.4窗口显示操作 168

23.3.5状态有窗口显示操作 169

24. Spark-SQL 应用 170

24.1 sparksql 概念 170

24.2 sparksql 开发示例 171

24.3 MLlib 介绍 173

25. Spark 综合练习 173

 

 

1.   HADOOP入门

1.1大数据部分的课程介绍

大数据技术生态体系:

的Hadoop(HDFS,映射精简,纱线)元老级大数据处理技术框架,擅长离线数据分析

HBase的分布式海量数据库,离线分析和在线业务通吃

Hive sql数据仓库工具,使用方便,功能丰富,基于MR延迟大

Sqoop数据导入导出工具

水槽数据采集框架

风暴实时流式计算框架,流式处理领域头牌框架

Spark基于内存的分布式运算框架,一站式处理all in one,新秀,发展势头迅猛

sparkCore

SparkSQL

SparkStreaming

 

机器学习:

Mahout的基于MapReduce的的机器学习算法库

MLLIB基于火花机器学习算法库

 

1.2学习建议

  • 理解该工框架的功能和适用场景
  • 怎么用(部署使用,编程规范,API)
  • 运行机制
  • 架构原理
  • 源码

 

1.3就业前景及发展规划

BAT ----看运气,其他,一切皆有可能

薪水---- 15000 <=你的底线期望值== 20k

发展路线----

应用开发 - 高级开发人员

平台开发 - 架构级别

|

| ----------------- | --------- ---- ---- ------------- |

|     |                |

架构师数据挖掘模型设计管理

1.4 HADOOP简介

1.4.1前言

先来写一个普通的单词计数程序

观察程序处理的速度和能力

1.4.2 hadoop应用场景

(1)的hadoop的是用于处理(运算分析)海量数据的,且是采用分布式集群的方式;

(2)通俗来说,可以把hadoop的理解为一个编程框架(比例结构,弹簧,休眠/ MyBatis的),有着自己特定的API封装和用户编程规范,用户可借助这些API来实现数据处理逻辑;

(3)从另一个角度,hadoop的又可以理解为一个提供服务的软件(比如数据库服务预言/ MySQL的,索引服务solr的,缓存服务redis的等),用户程序的功能都是通过客户端向的hadoop的集群请求服务来实现;

 

(4)具体来说,hadoop的的两个大的功能:海量数据的存储;海量数据的分析;

(5)的Hadoop的有3大核心组件:

HDFS

---- hadoop分布式文件系统海量数据的存储(集群服务),

MapReduce的的

----运算框架(导jar包写程序),海量数据运算分析(替代品:torm / spark / tez等)

----资源调度管理集群(可以理解为一个分布式的操作系统,集群服务)

 

 

(6)的Hadoop的产生的历史

最早来自于谷歌的三大论文(为什么谷歌会需要这么一种技术)

后来经过doug cutting的山寨,出现了java版本的hdfs mapreduce和hbase

以上三个组件整合起来成为阿帕奇的一个顶级项目的Hadoop

经过演化,hadoop的组件又多出一个纱(mapreduce + yarn + hdfs)

而且,hadoop的的外围产生了越来越多的工具组件,形成一个庞大的的hadoop的生态体系

 

1.5 hadoop集群部署安装

(学习要求:掌握开发测试级别的的hadoop的集群部署运维)

  1. 准备linux服务器(centos 6.4 32位<生产环境的服务器应该采用64位,可以支持更大的内存>)

 

  1. 解压的CentOS的镜像压缩包到某个目录,并用VMware的的打开
  2. 准备操作系统环境(主机名,IP地址配成静态,域名和IP的本地映射主机)
  3. 关闭图形界面的启动修改/ etc / inittab中的启动级别为3

 

 

  1. 准备的Java的环境,安装JDK,配置环境变量等
  • 解压安装包,修改环境变量:JAVA_HOME PATH
  • 配置防火墙(关闭)
  • 为hadoop软件准备一个专门的linux用户(在我们的linux系统镜像中,有用户hadoop,密码:hadoop),为hadoop用户设置sudo权限/ etc / sudoers

 

  1. 安装的hadoop ----(解压,修改配置文件,分发到集群,初始化,启动)

Hadoop的的的目录结构:

滨#可执行文件(Hadoop的的功能操作命令)

等#配置文件

包括    

LIB#本地库文件(数据压缩编解码,本地文件系统操作)

的的libexec    

LICENSE.TXT

NOTICE.txt

的的README.txt

sbin目录#可执行文件(Hadoop的集群进程管理的操作命令)

分享#开发所需要的JAR包及用户帮助文档

 

  1. 修改配置文件(参考现成的配置文件XXX-site.xml中中)

(1)hadoop-env.sh JAVA_HOME = / home / hadoop / app / jdk_7u65

(2)核心-site.xml中    

fs.defaultFS指定的Hadoop所使用的文件系统

hadoop.tmp.dir指定各节点上的hadoop的进程所在的本地工作目录(父目录)

(3)mapred-site.xml mapreduce.framework.name:yarn

(4)yarn-site.xml yarn.resourcemanager.hostname:server01(yarn中的主节点所在主机)

                 yarn.nodemanager.aux服务:mapreduce_shuffle

(5)可选:

如果要让名称节点单独配置一个工作目录,在HDFS-site.xml中:

 <名称> dfs.namenode.name.dir </名称>

 <value> / mnt / driver-meta /,nfs:// </ value>

如果要让数据节点单独配置一个工作目录,在HDFS-site.xml中:

 <名称> dfs.datanode.data.dir </名称>

 <value> / mnt / driver-data-a /,/ mnt / driver-data-b /,/ mnt / driver-data-c / </ value>

如果要让secondary namenode在指定的机器上启动,则配置:

<名称> dfs.namenode.secondary.http地址</名称>

<值> Hadoop的Server02上:50090 </ value>

 

 

(6)真实生产中部署一个中型集群:

有些公司会借助一些自动化的网络拷贝工具加快配置速度

有些公司会采用一些商业发行版(CDH - Cloudera的公司的产品; HORTONWORKS; 微软,IBM,EMC,INTEL)

 

  1. 启动的Hadoop的

首先,格式化nameonde bin / hadoop namenode -format

  1. 手动一台一台地启动

在相应服务器上启动HDFS的相关进程:

  启动namenode进程 - sbin / hadoop-daemon.sh start namenode

启动datanode进程--sbin / hadoop-daemon.sh启动datanode

然后,验证HDFS的服务是否能正常提供:

bin / hdfs dfsadmin -report查看hdfs集群的统计信息

  1. 壳牌脚本批量启动方式:

在任意一台服务器上执行命令:

启动HDFS服务:sbin目录/ start-dfs.sh

启动纱服务:sbin目录/ start-yarn.sh

或者:直接启动hdfs + yarn服务:sbin / start-all.sh

 

6,集群内部的SSH密钥认证登陆机制配置(免密登陆)

配置的机制:在登陆方生成密对,然后将公司复制给目标主机,在目标主机上将这个公司加入授权文件〜/ .ssh / authorized_keys(该文件的权限:600)

 

真实大量配置的时候直接使用SSH工具箱的工具:

1 /在登陆方生成密对,执行命令:ssh-keygen

2 /执行这条指令:

ssh-copy-id hadoop-server03

就可以免密登陆目标主机

 

 

 

2. HDFS

总的设计思想:

分而治之 - 将大文件,大批量文件,分布式存放在大量独立的服务器上,以便于采取分而治之的方式对海量数据进行运算分析;

重点概念:文件切块,副本存放,元数据,位置查询,数据读写流

2.1 hdfs的shell操作

--appendToFile ----追加一个文件到已经存在的文件末尾

例如:hadoop fs -appendToFile ./hello.txt hdfs:// hadoop-server01:9000 / hello.txt

可以简写为:

Hadoop fs -appendToFile ./hello.txt /hello.txt

-cat ---显示文件内容  

例如:hadoop fs -cat /hello.txt

-chgrp

-chmod

-chown

上面三个跟的的Linux中的用法一样

例如:hadoop fs -chmod 666 /hello.txt

-copyFromLocal#从本地文件系统中拷贝文件到HDFS路径去

例如:hadoop fs -copyFromLocal ./jdk.tar.gz / aaa /

-copyToLocal#从HDFS拷贝到本地

例如:hadoop fs -copyToLocal /aaa/jdk.tar.gz

-count#统计一个指定目录下的文件节点数量

例如:hadoop fs -count / aaa /

-cp#从HDFS的一个路径拷贝HDFS的另一个路径

hadoop fs -cp /aaa/jdk.tar.gz /bbb/jdk.tar.gz.2

-createSnapshot

-deleteSnapshot

-renameSnapshot

以上三个用来操作HDFS文件系统目录信息快照

例如:hadoop fs -createSnapshot /

-df#统计文件系统的可用空间信息

-du

例如:hadoop fs -df -h /

例如:hadoop fs -du -s -h / aaa / *

-get#等同于copyToLocal,就是从HDFS下载文件到本地

-getmerge#合并下载多个文件

例如:比如hdfs的目录/ aaa /下有多个文件:log.1,log.2,log.3,...

hadoop fs -getmerge /aaa/log.* ./log.sum

-help#输出这个命令参数手册

-ls#显示目录信息

例如:hadoop fs -ls hdfs:// hadoop-server01:9000 /

这些参数中,所有的HDFS路径都可以简写

例如:hadoop fs -ls /等同于上一条命令的效果

-mkdir#在HDFS上创建目录

例如:hadoop fs -mkdir -p / aaa / bbb / cc / dd

-moveFromLocal#从本地剪切粘贴到HDFS

-moveToLocal#从HDFS剪切粘贴到本地

-mv#在HDFS目录中移动文件

-put#等同于copyFromLocal

-rm#删除文件或文件夹

例如:hadoop fs -rm -r / aaa / bbb /

-rmdir#删除空目录

-setrep#设置HDFS中文件的副本数量

例如:hadoop fs -setrep 3 /aaa/jdk.tar.gz

-stat#显示一个文件或文件夹的元信息

-tail#显示一个文件的末尾

-text#以字符形式打印一个文件的内容

 

2.2 HDFS的一些概念(概念)和特性

首先,它是一个文件系统,有一个统一的命名空间 - 目录树

其次,它是分布式的,由很多服务器联合起来实现功能;

  1. HDFS系统-会给文件客户端的的提供一个统一的抽象艺术艺术目录树,客户端访问HDFS文件时就是通过指定这个抽象目录中的路径来访问
  2. HDFS的中文件都是分块(块)存储的,块的大小可以通过配置参数(dfs.blocksize)来规定,默认大小在hadoop2.x版本中是128M,老版本中是64M
  3. 文件的各个块由谁来进行真实的存储呢?----在分布各个数据节点服务节点上,每而且一个块可以都存储多个副本(副本数量也可以通过参数设置dfs.replication)
  4. Hdfs中有一个重要的角色:namenode,负责维护整个hdfs文件系统的目录树,以及每一个路径(文件)所对应的块块信息(block的id,及所在的datanode服务器)
  5. HDFS的设计英文分类照片中翻译适应一次写入,多次读出的场景,并不支持文件的修改

(HDFS并不适合用来做网盘应用,因为,不便修改,延迟大,网络开销大,成本太高)

 

 

 

 

2.3 HDFS的java操作

可以随机定位读取位置:DFSInputStream.seek()

 

 

 

  1. 搭建开发环境(蚀,HDFS的罐子包----的的hadoop的安装目录的份额下)

<依赖性>

    <的groupId> org.apache.hadoop </的groupId>

   <artifactId的> Hadoop的客户端</ artifactId的>

    <版本> 2.4.1 </版本>

</依赖性>

 

建议在linux下下下进行客户端应用的开发,不会存在兼容性问题。

如果非要在窗口上做客户端应用开发,需要设置以下环境:

  1. 在窗口的某个目录下解压一个的的Hadoop的安装包
  2. 将安装包下的lib中中和斌目录用对应的窗口版本平台编译的本地库替换
  3. 在窗口系统中配置HADOOP_HOME指向你解压的安装包
  4. 在视窗系统的路径变量中加入的的Hadoop的仓目录

 

 

  1. 在Java的的中操作HDFS,首先要获得一个客户端实例

配置conf =新配置()

FileSystem fs = FileSystem.get(conf)

 

而我们的操作目标是HDFS,所以获取到的FS对象应该是DistributedFileSystem的实例;

获得方法是从何处判断具体实例化那种客户端类呢?

----从CONF中的一个参数fs.defaultFS的配置值判断;

如果我们的代码中没有指定并且工程类路径下也没有给定相应的配置,conf中的默认值就来自于hadoop的jar包中的core-default.xml,默认值为:file:///

 

FS所具备的方法:

 

 

 

2.4 hdfs的工作机制

HDFS集群分为两大角色:的的NameNode,DataNode会会

的NameNode会负责管理整个文件系统的元数据

数据管理部负责管理用户的文件数据块

 

 

 

 


2.5 namenode工作机制

NameNode会的职责:

负责客户端请求的响应

元数据的管理(查询,修改)

---- hdfs元数据是怎么存储的?

A,内存中有一份完整的元型态型态数据

B,磁盘有一个“准完整”的元数据镜像

C,当客户端对HDFS中的文件进行新增或者修改操作,的响应记录首先被记入编辑作业这种记录日志中,当客户端操作成功后,相应的元数据会更新到内存中

每隔一段时间,会由secondary namenode将namenode上积累的所有edits和一个最新的fsimage下载到本地,并加载到内存进行merge(这个过程称为检查点)

d,检查点操作的触发条件配置参数:

dfs.namenode.checkpoint.check.period = 60   检查触发条件是否满足的频率,60秒

dfs.namenode.checkpoint.dir =文件:// $ {} hadoop.tmp.dir / DFS / namesecondary

以上两个参数做checkpoint 操作时,secondary namenode 的本地工作目录

dfs.namenode.checkpoint.edits.dir = $ {} dfs.namenode.checkpoint.dir

 

dfs.namenode.checkpoint.max-retries = 3   #最大重试次数

dfs.namenode.checkpoint.period = 3600   #两次检查点之间的时间间隔3600秒

dfs.namenode.checkpoint.txns = 1000000  #两次checkpoint之间最大的操作记录

E,namenode和secondary namenode的工作目录存储结构完全相同,所以,当namenode故障退出需要重新恢复时,可以从secondary namenode的工作目录中将fsimage拷贝到namenode的工作目录,以恢复namenode的元数据

 

楼可以通过HDFS的一个工具来查看编辑中的信息

bin / hdfs oev -i edits -o edits.xml

 

2.6 datanode的工作机制

某个Datanode的工作职责:

存储管理用户的文件块数据

定期向名称节点汇报自身所持有的块信息(通过心跳信息上报)

上传一个文件,观察文件的块具体的物理存放情况

在每一台数据节点机器上的这个目录:

/home/hadoop/app/hadoop-2.4.1/tmp/dfs/data/current/BP-193442119-192.168.2.120-1432457733977/current/finalized

 

 

 

2.7一些补充

  1. HDFS的其他访问方式:

HDFS文件系统可以通过标准的hdfs shell / rest api / java api来操作,还可以利用fuse这种工具将hdfs挂载为一个unix标准文件系统,就可以使用标准的linux文件操作方式来操作hdfs文件系统

HDFS还可以挂载为一个NFS系统

 

FileUtil工具类

FileUtil。副本  文件(C:/test.tar.gz ),文件系统获得。(URI 创建“HDFS:// Hadoop的SERVER01:9000 ),CONF,“Hadoop的” ),  路径(“/ test.tar.gz ),true,conf);

 

  1. HDFS的垃圾桶配置

HDFS存在回收站机制,进入回收站的文件可以保存一段时间,过期后再清除

参数配置:

fs.trash.checkpoint.interval = 0      回收站过期机制检查频率(分钟)

fs.trash.interval = 0      回收站中文件过期的时间限制(分钟)

 

 

  1. 通配符及过滤器选择文件

 

 

 

 

  1. Namenode会会的安全模式

(1)当nameonde发现文件块丢失的数量达到一个配置的门限时,就会进入安全模式,它在这个模式下等待数据管理部向它汇报块信息。

(2)在安全模式下,名称节点可以提供元数据查询的功能,但是不能修改;

可以手动管理的的NameNode的安全模式:

hdfs dfsadmin -safemode <enter | 离开| 得到| 等待]>

 

 

 

 

 

 

 

3.深入HDFS源码

3.1 hdfs读数据流程

1,跟的NameNode的通信查询元数据,找到文件块所在的数据节点服务器

2,挑选一台数据节点(就近原则,然后随机)服务器,请求建立插座流

3,数据管理部开始发送数据(从磁盘里面读取数据放入流,以包为单位来做校验)

如图4所示,客户端以分组为单位接收,现在本地缓存,然后写入目标文件

3.2 hdfs写数据流程

如图1所示,根名称节点通信请求上传文件,名称节点检查目标文件是否已存在,父目录是否存在

2,名称节点返回是否可以上传

3,客户端请求第一个块该传输到哪些数据节点服务器上

4,名称节点返回3个数据节点服务器ABC

5,客户端请求3台DN中的一台甲上传数据(本质上是一个RPC调用,建立管道),A收到请求会继续调用B,然后乙调用C,将真个管道建立完成,逐级返回客户端

6,客户端开始往甲上传第一个块(先从磁盘读取数据放到一个本地内存缓存),以分组为单位,A收到一个数据包就会传给B,B传给℃; 一个每传一个数据包会放入一个应答队列等待应答

如图7所示,当一个块传输完成之后,客户端再次请求名称节点上传第二个块的服务器。

 

3.3 hadoop的RPC框架 ----(业界良心,讲技术)

Hadoop的的中各节点之间存在大量的远程过程调用,Hadoop的的为此封装了一个RPC基础框架

使用方法:

(1)定义一个接口,实例如下:

// RCP通信的两端共同遵守的协议(本质上就是业务实现类的接口)

public interface ClientNameNodeProtocal {

// RPC通信双方一致的版本号

public static final long versionID = 1L;

//业务方法签名

public String getMetaData(String path);

 

}

 

(2)编写接口的业务实现类

/ **

 *业务的具体实现类,应该运行在远端服务器上

 * @author duanhaitao@itcast.cn

 *

 * /

公共类NamNodeNameSystemImpl实现ClientNameNodeProtocal {

 

@覆盖

public String getMetaData(String path){

 

//许多逻辑代码用于在元数据池中查找元数据

返回“{/aa/bb/bian4.mp4;300M; [BLK_1,BLK_2,BLK_3]; 3;

{[BLK_1:DN-A,DN-B,DN-E],[BLK_2:DN-A,DN-B,DN-C],[BLK_3:DN-A,DN-d,DN-E]}} “;

}

}

(3)使用RPC框架API将业务实现发布为RPC服务

/ **

 * RCP服务发布工具

 * @author duanhaitao@itcast.cn

 *

 * /

公共类PublishServiceTool {

 

public static void main(String [] args)抛出HadoopIllegalArgumentException,IOException {

 

//创建一个RPC服务建设者

Builder builder = new RPC.Builder(new Configuration());

//将要发布的服务的信息设置到建设者中

builder.setBindAddress(“spark01”)。setPort(10000).setProtocol(ClientNameNodeProtocal.class).setInstance(new NamNodeNameSystemImpl());

 

//用建设者构建出一个插座服务

Server server = builder.build();

//将服务启动,就可以等待客户端请求

server.start();

}

}

 

(4)客户端通过RPC框架API获取跟RPC服务端通信的插座代理,调用远端服务

公共类客户{

 

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

//首先用RPC框架获得要调用的远端服务的引用(动态代理对象)

ClientNameNodeProtocal namenodeImpl = RPC.getProxy(ClientNameNodeProtocal.class,1L,new InetSocketAddress(“spark01”,10000),new Configuration());

//因为这个动态代理对象实现了业务类的接口,所以可以直接通过这个引用来调用业务类的实现方法(本质上,具体实现在远端,走的是插座通信请求)

String metaData = namenodeImpl.getMetaData(“/ aa / bb / bian4.mp4”);

 

的的System.out.println(元数据);

 

}

}

 

 

3.4 hdfs读数据源码分析

 

 

3.5 hdfs写数据源码分析

 

 

 

 

 

 

 

3.6远程调试跟踪Hadoop的服务端代码

(1)需要在$ HADOOP_HOME的/ etc / hadoop的/ hadoop-env.sh文件的最后添加你想调试的进程

#远程调试的NameNode的

export HADOOP_NAMENODE_OPTS =“ - agentlib:jdwp = transport = dt_socket,address = 8888,server = y,suspend = y”

#远程调试的数据节点

export HADOOP_DATANODE_OPTS =“ - agentlib:jdwp = transport = dt_socket,address = 9888,server = y,suspend = y”

 

 

  1. 在本地的日食中打开的NameNode的或者数据节点类,点击右键,添加远程调试配置,如图:

 

 

(3)添加一个远程调试调试配置

 

 

 

(4)填写远程服务端的调试地址和端口号

 

 

 

(5)接着在名称节点类中添加断点,如图:

 

 

  1. 回到集群服务器上启动HDFS

 

 

  1. 回到日食之前配置的远程调试配置上,点击调试开始调试

 

 

(8)成功进入断点

 

 

 

 

 

 

 

 

 

4. MAPREDUCE入门

MapReduce的的是一个分布式的运算编程框架,核心功能是将用户编写的核心逻辑代码分布式地运行在一个集群的很多服务器上;

学习要求:掌握MR程序编程规范;

   掌握MR程序运行机制

   掌握MR常见需求解决方式

 

 

4.1为什么要的MapReduce

(1)海量数据在单机上处理因为硬件资源限制,无法胜任,因为需要采用分布式集群的方式来处理。

(2)而一旦将单机版程序扩展到集群来分布式运行,将极大地增加程序的复杂度和开发难度

(3)引入的MapReduce的框架后,开发人员可以将绝大部分工作集中在业务逻辑的开发上,而将分布式计算中的复杂性交由框架来处理

 

4.2 MAPREDUCE程序运行演示

的的Hadoop的发布包中内置了一个的Hadoop的MapReduce的的例子-2.4.1.jar,这个罐包中有各种MR示例程序,可以通过以下步骤运行:

启动HDFS,纱

然后在集群中的任意一台服务器上执行,(比如运行单词计数的):

hadoop jar hadoop-mapreduce-example-2.4.1.jar wordcount / wordcount / data / wordcount / out

4.3 MAPREDUCE编程规范

  1. 用户程序会分成三个部分:映射器,减速机,驱动器
  2. 映射器的输入数据是KV对的形式,KV的类型可以设置
  3. 映射器的输出数据是KV对的形式,KV的类型可以设置
  4. 映射中的业务逻辑写在映射方法中
  5. 地图方法是每进来一个KV对调用一次
  6. 减速机的输入数据应该对应映射器的输出数据,也是KV
  7. 减速机的业务逻辑写在减少方法中
  8. 减少方法是对每一个<键,值列表>调用一次
  9. 用户的映射和减速都要继承各自的父类
  10. 整个程序需要一个Drvier来进行提交,提交的是一个描述了各种必要信息的工作对象

 

4.4 wordcount示例编写

(1)定义一个映射器类

//首先要定义四个泛型的类型

// keyin:LongWritable valuein:Text

// keyout:Text valueout:IntWritable

 

公共类WordCountMapper扩展Mapper <LongWritable,Text,Text,IntWritable> {

// map方法的生命周期:框架每传一行数据就被调用一次

// key:这一行的起始点在文件中的偏移量

//值:这一行的内容

@覆盖

protected void map(LongWritable key,Text value,Context context)抛出IOException,InterruptedException {

//拿到一行数据转换为字符串

String line = value.toString();

//将这一行切分出各个单词

String [] words = line.split(“”);

//遍历数组,输出<单词,1>

for(String word:words){

context.write(new Text(word),new IntWritable(1));

}

}

}

 

(2)定义一个减速器类

//生命周期:框架每传递进来一个千伏组,减少方法被调用一次

@覆盖

protected void reduce(Text key,Iterable <IntWritable> values,Context context)抛出IOException,InterruptedException {

 

//定义一个计数器

int count = 0;

//遍历这一组千伏的所有V,累加到计数中

for(IntWritable value:values){

count + = value.get();

}

context.write(key,new IntWritable(count));

}

}

 

 

(3)定义一个主类,用来描述工作并提交工作

公共类WordCountRunner {

//把业务逻辑相关的信息(哪个是映射器,哪个是减速,要处理的数据在哪里,输出的结果放哪里......)描述成一个工作对象

//把这个描述好的作业提交给集群去运行

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

配置conf = new Configuration();

Job wcjob = Job.getInstance(conf);

//指定我这个工作所在的JAR包

// wcjob.setJar(“/ home / hadoop / wordcount.jar”);

wcjob.setJarByClass(WordCountRunner.class);

 

wcjob.setMapperClass(WordCountMapper.class);

wcjob.setReducerClass(WordCountReducer.class);

//设置我们的业务逻辑映射类的输出键和值的数据类型

wcjob.setMapOutputKeyClass(Text.class);

wcjob.setMapOutputValueClass(IntWritable.class);

//设置我们的业务逻辑减速类的输出键和值的数据类型

wcjob.setOutputKeyClass(Text.class);

wcjob.setOutputValueClass(IntWritable.class);

 

//指定要处理的数据所在的位置

FileInputFormat.setInputPaths(wcjob,“hdfs:// hdp-server01:9000 / wordcount / data / big.txt”);

//指定处理完成之后的结果所保存的位置

FileOutputFormat.setOutputPath(wcjob,new Path(“hdfs:// hdp-server01:9000 / wordcount / output /”));

 

//向纱线集群提交这个工作

boolean res = wcjob.waitForCompletion(true);

System.exit(RES?0:1);

}

 

 

4.5 MAPREDUCE程序提交运行模式及调试方法

4.5.1本地运行模式

  1. MapReduce的的程序是被提交给LocalJobRunner在本地运行
  2. 而处理的数据及输出结果可以在本地文件系统,也可以在HDFS上
  3. 怎样实现本地运行:写一个程序,不要带集群的配置文件(本质是你的先生程序的CONF中是否有mapreduce.framework.name =本地以及yarn.resourcemanager.hostname参数)

 

4.5.2集群运行模式

  1. MapReduce的的程序会提交给纱线集群的ResourceManager的中,分发到很多的节点上并发执行
  2. 处理的数据和输出结果应该位于HDFS文件系统
  3. 怎样实现集群运行:

A,将程序打成JAR包,然后在集群的任意一个节点上用的Hadoop的命令启动

$ hadoop jar wordcount.jar cn.itcast.bigdata.mrsimple.WordCountDriver inputpath outputpath

B,直接在Linux的的的蚀中运行主要方法

(项目中要带参数:mapreduce.framework.name =纱以及纱线的两个基本配置)

C,如果要在窗口的日食中提交作业给集群,则要修改YarnRunner类

4.6 MAPREDUCE中的Combiner

  1. 组合是MR程序中映射和减速之外的一种组件
  2. 组合组件的父类就是减速
  3. 合和减速机的区别在于运行的位置:

合是在每一个maptask所在的节点运行

减速是接收全局所有映射器的输出结果;

 

 

4.7 MAPREDUCE中的序列化

(1)的爪哇的序列化是一个重量级序列化框架(串行化),一个对象被序列化后,会附带很多额外的信息(各种校验信息,页眉,继承体系.... ),所以很臃肿,不便于在网络中高效传输;

所以,的hadoop的自己开发了一套序列化机制(可写),精简,高效

  1. 简单代码验证两种序列化机制的差别:

公共类TestSeri {

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

//定义两个ByteArrayOutputStream,用来接收不同序列化机制的序列化结果

ByteArrayOutputStream ba = new ByteArrayOutputStream();

ByteArrayOutputStream ba2 = new ByteArrayOutputStream();

 

//定义两个DataOutputStream类类,用于将普通对象进行JDK标准序列化

DataOutputStream dout = new DataOutputStream(ba);

DataOutputStream dout2 = new DataOutputStream(ba2);

ObjectOutputStream obout = new ObjectOutputStream(dout2);

//定义两个豆,作为序列化的源对象

ItemBeanSer itemBeanSer = new ItemBeanSer(1000L,89.9f);

ItemBean itemBean = new ItemBean(1000L,89.9f);

 

//用于比较字符串类型和文本类型的序列化差别

文本atext = new Text(“a”);

// atext.write(dout);

itemBean.write(DOUT);

 

byte [] byteArray = ba.toByteArray();

 

//比较序列化结果

的的System.out.println(byteArray.length);

for(byte b:byteArray){

 

是System.out.print是(b)中中;

是System.out.print(“:”);

}

 

的System.out.println(“-----------------------”);

 

String astr =“a”;

// dout2.writeUTF(astr);

obout.writeObject(itemBeanSer);

 

byte [] byteArray2 = ba2.toByteArray();

的的System.out.println(byteArray2.length);

for(byte b:byteArray2){

是System.out.print是(b)中中;

是System.out.print(“:”);

}

}

}

 

 

4.8 Mapreduce的排序初步

MR程序在处理数据的过程中会对数据排序,排序的依据是映射器输出的关键

 

 

 

5. Mapreduce高级特性(一)

5.1分区程序编程

Partition就是对地输出的密钥进行分组,不同的组可以指定不同的reduce task处理;

分区功能由分区的实现子类来实现

示例:不同省份流量数据汇总到不同文件中

 

5.2 Mapreduce的排序----重点

MR中的常见排序机制:部分/全部/次级排序

MR中排序的基本要素:

排序是在映射阶段输出之后,降低处理之前

(通过无减少的MR程序示例观察)

只针对重点进行排序

关键要实现WritableComparable接口

简单示例:对流量汇总数据进行倒序排序

 

5.2.1 partital排序示例,多减少任务自动实现各输出文件有序

5.2.3总排序机制

  1. 设置一个reduce task,全局有序,但是并发度太低,单节点负载太大
  2. 设置分区段partitioner,设置相应数量的reduce task,可以实现全局有序,但难以避免数据分布不均匀 - 数据倾斜问题,有些减少任务负载过大,而有些则过小;
  3. 可以通过编写一个职位来统计数据分布规律,获取合适的区段划分,然后用分区段分区来实现排序,但是这样需要另外编写一个工作对整个数据集运算,比较费事
  4. 利用的hadoop的自带的取样器,来对数据集取样并划分区段,然后利用的hadoop的自带的TotalOrderPartitioner分区来实现全局排序

 

示例:

/ **

 *全排序示例

 * @author duanhaitao@itcast.cn

 *

 * /

公共类TotalSort {

 

static class TotalSortMapper扩展Mapper <Text,Text,Text,Text> {

OrderBean bean = new OrderBean();

 

@覆盖

protected void map(文本键,文本值,上下文上下文)抛出IOException,InterruptedException {

 

// String line = value.toString();

// String [] fields = line.split(“\ t”);

// bean.set(fields [0],Double.parseDouble(fields [1]));

context.write(键,值);

}

}

 

static class TotalSortReducer扩展Reducer <Text,Text,Text,Text> {

 

@覆盖

protected void reduce(文本键,Iterable <Text>值,Context上下文)抛出IOException,InterruptedException {

 

for(Text v:values){

context.write(键,V);

}

}

}

 

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

 

配置conf = new Configuration();

Job job = Job.getInstance(conf);

 

job.setJarByClass(TotalSort.class);

 

job.setMapperClass(TotalSortMapper.class);

job.setReducerClass(TotalSortReducer.class);

// job.setOutputKeyClass(OrderBean.class);

// job.setOutputValueClass(NullWritable.class);

 

//用来读取序列源文件的输入组件

job.setInputFormatClass(SequenceFileInputFormat.class);

 

FileInputFormat.setInputPaths(job,new Path(args [0]));

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

 

// job.setPartitionerClass(RangePartitioner.class);

 

//分区的逻辑使用的Hadoop的的自带的全局排序分区组件

job.setPartitionerClass(TotalOrderPartitioner.class);

 

//系统自带的这个抽样器只能针对sequencefile抽样

RandomSampler randomSampler

 = new InputSampler.RandomSampler <Text,Text>(0.1,100,10);

InputSampler.writePartitionFile(工作,randomSampler);

 

//获取抽样器所产生的分区规划描述文件

配置conf2 = job.getConfiguration();

String partitionFile = TotalOrderPartitioner.getPartitionFile(conf2);

 

//把分区描述规划文件分发到每一个任务节点的本地

job.addCacheFile(new URI(partitionFile));

 

//设置若干并发的减少任务

job.setNumReduceTasks(3);

 

job.waitForCompletion(真);

}

}

 

5.2.4二级排序机制

----就是让的MapReduce的帮我们根据价值排序

 

 

考虑一个场景,需要取按键分组的最大价值条目:

通常,洗牌只是对键进行排序

如果需要对值排序,则需要将值放到钥匙中,但是此时,价值就和原来的键形成了一个组合键,从而到达减速时,组合键是一个一个到达减速,想在减速中输出最大值的那一个,不好办,它会一个一个都输出去,除非自己弄一个缓存,将到达的组合键全部缓存起来然后只取第一个

(或者弄一个访问标识?但是同一个减速可能会收到多个键的组合键,无法判断访问标识)

此时就可以用到secondary sort,其思路:

  1. 要有对组合键排序的比较器
  2. 要有分区进行分区负载并行减速计算
  3. 要有一个groupingcomparator来重定义值列表聚合策略 - 这是关键,其原理就是将相同键而不同组合键的数据进行聚合,从而把他们聚合成一组,然后在减速中可以一次收到这一组关键词的组合键,并且,值最大的也就是在这一组中的第一个组合键会被选为迭代器值列表的关键,从而可以直接输出这个组合键,就实现了我们的需求

 

示例:输出每个项目的订单金额最大的记录

(1)定义一个GroupingComparator

/ **

 *用于控制洗牌过程中减少端对KV对的聚合逻辑

 * @author duanhaitao@itcast.cn

 *

 * /

公共类ItemidGroupingComparator扩展WritableComparator {

 

protected ItemidGroupingComparator(){

 

超(OrderBean.class,真);

}

 

 

@覆盖

public int compare(WritableComparable a,WritableComparable b){

OrderBean abean =(OrderBean)a;

OrderBean bbean =(OrderBean)b;

 

//将ITEM_ID相同的豆都视为相同,从而聚合为一组

return abean.getItemid()。compareTo(bbean.getItemid());

}

}

 

(2)定义订单信息豆

/ **

 *订单信息豆,实现的Hadoop的序列化机制

 * @author duanhaitao@itcast.cn

 *

 * /

公共类OrderBean实现WritableComparable <OrderBean> {

私人文本的itemid;

私人DoubleWritable金额;

 

public OrderBean(){

}

public OrderBean(Text itemid,DoubleWritable amount){

集(商品ID,金额);

}

 

public void set(Text itemid,DoubleWritable amount){

 

this.itemid = itemid;

this.amount =金额;

 

}

 

public Text getItemid(){

return itemid;

}

 

public DoubleWritable getAmount(){

退货金额;

}

 

@覆盖

public int compareTo(OrderBean o){

int cmp = this.itemid.compareTo(o.getItemid());

if(cmp == 0){

 

cmp = -this.amount.compareTo(o.getAmount());

}

返回CMP;

}

 

@覆盖

public void write(DataOutput out)抛出IOException {

out.writeUTF(itemid.toString());

out.writeDouble(amount.get());

 

}

 

@覆盖

public void readFields(DataInput in)throws IOException {

String readUTF = in.readUTF();

double readDouble = in.readDouble();

 

this.itemid = new Text(readUTF);

this.amount = new DoubleWritable(readDouble);

}

 

 

@覆盖

public String toString(){

return itemid.toString()+“\ t”+ amount.get();

}

}

 

 

(3)自定义一个分区器,以使相同ID的豆发往相同减少任务

公共类ItemIdPartitioner扩展了Partitioner <OrderBean,NullWritable> {

 

@覆盖

public int getPartition(OrderBean key,NullWritable value,int numPartitions){

//指定ITEM_ID相同的豆发往相同的减速任务

返回(key.getItemid()的hashCode()&Integer.MAX_VALUE的。)%numPartitions;

}

}

 

 

(4)定义了主体先生流程

/ **

 *利用secondarysort机制输出每种项目订单金额最大的记录

 * @author duanhaitao@itcast.cn

 *

 * /

公共类SecondarySort {

 

static class SecondarySortMapper扩展Mapper <LongWritable,Text,OrderBean,NullWritable> {

 

OrderBean bean = new OrderBean();

 

@覆盖

protected void map(LongWritable key,Text value,Context context)抛出IOException,InterruptedException {

 

String line = value.toString();

String [] fields = StringUtils.split(line,“\ t”);

 

bean.set(new Text(fields [0]),new DoubleWritable(Double.parseDouble(fields [1])));

 

context.write(豆,NullWritable.get());

 

}

 

}

 

static class SecondarySortReducer扩展Reducer <OrderBean,NullWritable,OrderBean,NullWritable> {

 

 

//在设置了groupingcomparator以后,这里收到的kv数据就是:<1001 87.6>,null <1001 76.5>,null ....

//此时,减少方法中的参数key就是上述kv组中的第一个kv的密钥:<1001 87.6>

//要输出同一个项目的所有订单中最大金额的那一个,就只要输出这个关键

@覆盖

protected void reduce(OrderBean key,Iterable <NullWritable> values,Context context)抛出IOException,InterruptedException {

context.write(键,NullWritable.get());

}

}

 

 

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

 

配置conf = new Configuration();

Job job = Job.getInstance(conf);

 

job.setJarByClass(SecondarySort.class);

 

job.setMapperClass(SecondarySortMapper.class);

job.setReducerClass(SecondarySortReducer.class);

 

 

job.setOutputKeyClass(OrderBean.class);

job.setOutputValueClass(NullWritable.class);

 

FileInputFormat.setInputPaths(job,new Path(args [0]));

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

//指定洗牌所使用的GroupingComparator类

job.setGroupingComparatorClass(ItemidGroupingComparator.class);

//指定洗牌所使用的分区类

job.setPartitionerClass(ItemIdPartitioner.class);

 

job.setNumReduceTasks(3);

 

job.waitForCompletion(真);

 

}

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5.3 shuffle详解

随机播放缓存流程:

 

---- shuffle是MR处理流程中的一个过程,它的每一个处理步骤是分散在各个地图任务和减少任务节点上完成的,整体来看,分为3个操作:

  1. 分区分区
  2. 排序根据键排序
  3. 合进行局部值的合并

 

整个洗牌的大流程如下:

 

  • map task输出结果到一个内存缓存,并溢出为磁盘文件
  • 组合调用
  • 分区/排序
  • 减少任务拉取地图输出文件中对应的分区数据
  • 降低端归并排序

产生聚合值迭代器来传递给降低方法,并把这组聚合KV(聚合的依据是GroupingComparator)中排序最前的KV的关键传给减少方法的入参密钥

 

5.4 mr程序映射任务数的规划机制

一个inputsplit对应一个地图

而inputsplit切片规划是由InputFormat的具体实现子类来实现,就是调用

InputSplits [] getSplits()方法,这个方法的逻辑可以自定义

在默认情况下,由FileInputFormat来实现,它的核心逻辑:

  1. 规划切片的大小

   long minSize = Math.max(getFormatMinSplitSize(),getMinSplitSize(job));

    long maxSize = getMaxSplitSize(job);

    public static long getMaxSplitSize(JobContext context){

returncontext.getConfiguration()。getLong(SPLIT_MAXSIZE,Long.MAX_VALUE);

      }

 

// mapreduce.input.fileinputformat.split.minsize配置这个值可以让切片大小>块大小  

// mapreduce.input.fileinputformat.split.maxsize配置这个值可以让切片大小<块大小

 

long splitSize = computeSplitSize(blockSize,minSize,maxSize);

//计算切片大小

protected long computeSplitSize(long blockSize,long minSize,long maxSize){

    返回Math.max(minSize,Math.min(maxSize,blockSize));

}

 

  1. 构造切片信息对象,并放入InputSplits []中

splits.add(makeSplit(路径,长度bytesRemaining,splitSize,blkLocations [blkIndex] .getHosts()));

注:FileInputFormat的切片机制是针对一个一个的文件进行,因此,如果文件太小,则整个文件划分为一个切片

如果一个大文件被切成若干个切片后,剩下的长度如果在块大小的1.1倍大小以内,则将剩下的长度全部规划为一个切片

 

 

5.5 Mapreduce的连接算法

(1)减少边连接

示例:

订单数据

商品信息

 

实现机制:

通过将关联的条件作为map输出的密钥,将两表满足加入条件的数据并携带数据所来源的文件信息,发往同一个减少任务,在减少中进行数据的串联

 

 

公共类OrderJoin {

 

static class OrderJoinMapper扩展Mapper <LongWritable,Text,Text,OrderJoinBean> {

 

@覆盖

protected void map(LongWritable key,Text value,Context context)抛出IOException,InterruptedException {

 

//拿到一行数据,并且要分辨出这行数据所属的文件

String line = value.toString();

 

String [] fields = line.split(“\ t”);

 

//拿到itemid

String itemid = fields [0];

 

//获取到这一行所在的文件名(通过inpusplit)

String name =“你拿到的文件名”;

 

//根据文件名,切分出各字段(如果是a,切分出两个字段,如果是b,切分出3个字段)

 

OrderJoinBean bean = new OrderJoinBean();

bean.set(null,null,null,null,null);

context.write(new Text(itemid),bean);

 

}

 

}

 

static class OrderJoinReducer extends Reducer <Text,OrderJoinBean,OrderJoinBean,NullWritable> {

 

@覆盖

protected void reduce(Text key,Iterable <OrderJoinBean> beans,Context context)抛出IOException,InterruptedException {

 

 //拿到的钥匙是某一个商品ID,比如1000

//拿到的咖啡豆是来自于两类文件的豆

// {1000,金额} {1000,金额} {1000,金额} --- {1000,价格,名称}

 

//将来自于b文件的豆里面的字段,跟来自于一个的所有豆进行字段拼接并输出

}

}

}

 

缺点:减少端的处理压力太大,映射节点的运算负载则很低,资源利用率不高

  容易产生数据倾斜

 

注:也可利用二次排序的逻辑来实现降低端加入

 

(2)地图边连接

- 原理阐述

适用于关联表中有小表的情形;

可以将小表分发到所有的地图节点,这样,地图节点就可以在本地对自己所读到的大表数据进行连接并输出最终结果

可以大大提高参与操作的并发度,加快处理速度

 

- 示例:先在映射器类中预先定义好小表,进行加盟

- 引入实际场景中的解决方案:一次加载数据库或者用distributedcache

公共类TestDistributedCache {

static class TestDistributedCacheMapper扩展Mapper <LongWritable,Text,Text,Text> {

FileReader in = null;

BufferedReader reader = null;

HashMap <String,String> b_tab = new HashMap <String,String>();

String localpath = null;

String uirpath = null;

 

//是在地图任务初始化的时候调用一次

@覆盖

protected void setup(Context context)抛出IOException,InterruptedException {

//通过这几句代码可以获取到缓存文件的本地绝对路径,测试验证用

Path [] files = context.getLocalCacheFiles();

localpath = files [0] .toString();

URI [] cacheFiles = context.getCacheFiles();

 

 

//缓存文件的用法 - 直接用本地IO来读取

//这里读的数据是map task所在机器本地工作目录中的一个小文件

in = new FileReader(“b.txt”);

reader = new BufferedReader(in);

String line = null;

而(空!=(线= reader.readLine())){

 

String [] fields = line.split(“,”);

b_tab.put(字段[0],字段[1]);

 

}

IOUtils.closeStream(读取器);

IOUtils.closeStream(IN);

 

}

 

@覆盖

protected void map(LongWritable key,Text value,Context context)抛出IOException,InterruptedException {

 

//这里读的是这个地图任务所负责的那一个切片数据(在hdfs上)

 String [] fields = value.toString()。split(“\ t”);

 

 String a_itemid = fields [0];

 字符串a_amount = fields [1];

 

 String b_name = b_tab.get(a_itemid);

 

 //输出结果1001 98.9 banan

 context.write(new Text(a_itemid),new Text(a_amount +“\ t”+“:”+ localpath +“\ t”+ b_name));

 

}

 

 

}

 

 

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

 

配置conf = new Configuration();

Job job = Job.getInstance(conf);

 

job.setJarByClass(TestDistributedCache.class);

 

job.setMapperClass(TestDistributedCacheMapper.class);

 

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(LongWritable.class);

 

//这里是我们正常的需要处理的数据所在路径

FileInputFormat.setInputPaths(job,new Path(args [0]));

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

 

//不需要减速

job.setNumReduceTasks(0);

//分发一个文件到任务进程的工作目录

job.addCacheFile(new URI(“hdfs:// hadoop-server01:9000 / cachefile / b.txt”));

 

//分发一个归档文件到任务进程的工作目录

// job.addArchiveToClassPath(archive);

 

//分发的jar包到任务节点的类路径下

// job.addFileToClassPath(jarfile);

 

job.waitForCompletion(真);

}

}

 

5.6 mapreduce的分布式缓存

应用场景:map side join

工作原理:

通过mapreduce框架将一个文件(本地/ HDFS)分发到每一个运行时的任务(map task / reduce task)节点上(放到任务进程所在的工作目录)

获取的方式:在我们自己的mapper或者reducer的代码内,直接使用本地文件JAVA ---- API来访问这个文件

 

示例程序:

首先在job对象中进行指定:  

job.addCacheFile(new  URI(“hdfs:// hadoop-server01:9000 / cachefile / b.txt” ));

//分发一个文件到任务进程的工作目录

job.addCacheFile(new URI(“hdfs:// hadoop-server01:9000 / cachefile / b.txt”));

//分发一个归档文件到任务进程的工作目录

//job.addArchiveToClassPath(archive);

//分发的jar包到任务节点的类路径下

//job.addFileToClassPath(jarfile);

 

然后在映射器或者减速中直接使用:

in = new FileReader(“b.txt”);

reader = new BufferedReader(in);

String line = reader.readLine()

 

 

动手练习

 

 

 

 

6. Mapreduce高级特性(二)

6.1 Mapreduce输入格式组件

6.1.1由地图任务数量的决定机制引入:

数据切片与地图任务数的机制

示例观察(多文件,大文件)

源码跟踪

的TextInputFormat源码阅读

isSplitable()判断要处理的数据是否可以做切片

getSplit()规划切片信息(实现在FileInputFormat类中)

---- TextInputformat切片逻辑:对每一个文件单独切片;切片大小默认等于blocksize

但是有两个参数可以调整:

 

 

 

 

 

如果是大量小文件,这种切片逻辑会有重大弊端:切片数量太多,maptask太多

 

 

createRecordReader()构造一个记录读取器

 

具体读取数据的逻辑是实现在LineRecordReader中(按行读取数据,行起始偏移量作为键,行的内容作为价值),比较特别的地方是:

LineRecordReader在读取一个具体的切片时,总是忽略掉第一行(针对的是:非第一切片),总是跨分割多读一行(针对的是:非最末切片)

6.1.2 InputFormat的继承体系

 

 

InputFormat子类介绍:

(1)的TextInputFormat(默认的输入格式类)详解

- 源码结构getsplits()阅读器

- 为何不会出现一行被割断处理的原理

  • 在LineRecordReader中,对拆分的第一行忽略

  public void initialize(InputSplit genericSplit,

                         TaskAttemptContext context)抛出IOException {

    FileSplit split =(FileSplit)genericSplit;

    配置作业= context.getConfiguration();

 

...... ......... ..

 

    //打开文件并寻找拆分的开始

    final FileSystem fs = file.getFileSystem(job);

    fileIn = fs.open(file);

    

    CompressionCodec codec = new CompressionCodecFactory(job).getCodec(file);

    if(null!= codec){

    ...... ...... 

//我们总是将第一条记录抛弃(文件第一个分割除外)

//因为我们总是在nextKeyValue()方法中跨拆分多读了一行(文件最后一个拆除除外)

    if(start!= 0){

      start + = in.readLine(new Text(),0,maxBytesToConsume(start));

    }

    this.pos = start;

  }

 

 

  • 在LineRecordReader中,nextKeyValue()方法总是跨分裂多读一行

public boolean nextKeyValue()throws IOException {

    if(key == null){

      key = new LongWritable();

    }

    key.set(POS);

    if(value == null){

      value = new Text();

    }

    int newSize = 0;

    //使用<=来多读取一行

    while(getFilePosition()<= end || in.needAdditionalRecordAfterSplit()){

      newSize = in.readLine(value,maxLineLength,

          Math.max(maxBytesToConsume(pos),maxLineLength));

      pos + = newSize;

      if(newSize <maxLineLength){

        打破;

...。...。

  }

 

 

  1. CombineTextInputFormat  

它的切片逻辑跟的TextInputFormat完全不同:

CombineTextInputFormat可以将多个小文件划为一个切片

这种机制在处理海量小文件的场景下能提高效率

(小文件处理的机制,最优的是将小文件先合并再处理)

思路

CombineFileInputFormat涉及到三个重要的属性:

mapred.max.split.size:同一节点或同一机架的数据块形成切片时,切片大小的最大值;

mapred.min.split.size.per.node:同一节点的数据块形成切片时,切片大小的最小值;

mapred.min.split.size.per.rack:同一机架的数据块形成切片时,切片大小的最小值。

切片形成过程:

(1)逐个节点(数据块)形成切片;

一个。遍历并累加这个节点上的数据块,如果累加数据块大小大于或等于mapred.max.split.size,则将这些数据块形成一个切片,继承该过程,直到剩余数据块累加大小小于mapred。 max.split.size,则进行下一步;

湾如果剩余数据块累加大小大于或等于mapred.min.split.size.per.node,则将这些剩余数据块形成一个切片,如果剩余数据块累加大小小于mapred.min.split.size.per。节点,则这些数据块留待后续处理。

(2)逐个机架(数据块)形成切片;

一个。遍历并累加这个机架上的数据块(这些数据块即为上一步遗留下来的数据块),如果累加数据块大小大于或等于mapred.max.split.size,则将这些数据块形成一个切片,继承该过程,直到剩余数据块累加大小小于mapred.max.split.size,则进行下一步;

湾如果剩余数据块累加大小大于或等于mapred.min.split.size.per.rack,则将这些剩余数据块形成一个切片,如果剩余数据块累加大小小于mapred.min.split.size.per。机架,则这些数据块留待后续处理。

(3)遍历并累加剩余数据块,如果数据块大小大于或等于mapred.max.split.size,则将这些数据块形成一个切片,继承该过程,直到剩余数据块累加大小小于mapred.max.split .size,则进行下一步;

(4)剩余数据块形成一个切片。

核心实现

//从机架名称映射到它具有的块列表

HashMap <String,List <OneBlockInfo >> rackToBlocks =

new HashMap <String,List <OneBlockInfo >>();

//从块映射到它具有副本的节点

HashMap <OneBlockInfo,String []> blockToNodes =

new HashMap <OneBlockInfo,String []>();

//从节点映射到它包含的块列表

HashMap <String,List <OneBlockInfo >> nodeToBlocks =

new HashMap <String,List <OneBlockInfo >>();

 

 

开始形成切片之前,需要初始化三个重要的映射关系:

rackToBlocks:机架和数据块的对应关系,即某一个机架上有哪些数据块;

blockToNodes:数据块与节点的对应关系,即一块数据块的“拷贝”位于哪些节点;

nodeToBlocks:节点和数据块的对应关系,即某一个节点上有哪些数据块;

初始化过程如下代码所示,其中每一个路径代表的文件被形成一个OneFileInfo对象,映射关系也在形成OneFileInfo的过程中被维护。

//填充所有文件的所有块longlongLength = 0;

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

  files [i] = new OneFileInfo(paths [i],job,

                             rackToBlocks,blockToNodes,nodeToBlocks,rackToNodes);

  totLength + = files [i] .getLength();

}

 

  1. 逐个节点(数据块)形成切片,代码如下:

//保存当前切片所包含的数据块

    ArrayList <OneBlockInfo> validBlocks = new ArrayList <OneBlockInfo>();

    //保存当前切片中的数据块属于哪些节点

    ArrayList <String> nodes = new ArrayList <String>();

    //保存当前切片的大小long curSplitSize = 0;

 

    //处理所有节点并创建本地节点的分割。

    //依据处理每个节点上的数据块for(Iterator <Map.Entry <String,List <OneBlockInfo >>> iter = nodeToBlocks.entrySet()。iterator(); iter.hasNext();){

      Map.Entry <String,List <OneBlockInfo >> one = iter.next();

      nodes.add(one.getKey());

      List <OneBlockInfo> blocksInNode = one.getValue();

 

      //对于每个块,将其复制到validBlocks中。从blockToNodes中删除它,以便在//两个不同的分割中不出现相同的块。

      //依次处理每个数据块,注意blockToNodes变量的作用,它保证了同一数据块不会出现在两个切片中for(OneBlockInfo oneblock:blocksInNode){

        if(blockToNodes.containsKey(oneblock)){

          validBlocks.add(oneblock);

          blockToNodes.remove(oneblock);

          curSplitSize + = oneblock.length; //如果累积的分割大小超过最大值,则创建此分割。

          //如果数据块累积大小大于或等于maxSize,则形成一个切片if(maxSize!= 0 && curSplitSize> = maxSize){

            //创建一个输入拆分并将其添加到拆分数组addCreatedSplit(job,splits,nodes,validBlocks);

            curSplitSize = 0;

            validBlocks.clear();

          }

        }

      }

      //如果剩下任何块,它们的组合大小是

      //大于minSplitNode,然后将它们组合成一个分割。

      //否则将它们添加回未处理的池中。这有可能

      //稍后他们将与同一机架中的其他块组合在一起。

      //如果剩余数据块大小大于或等于minSizeNode,则将这些数据块构成一个切片;

      //如果剩余数据块大小小于minSizeNode,则将这些数据块归还给blockToNodes,交由后期“同一机架”过程处理if(minSizeNode!= 0 && curSplitSize> = minSizeNode){

        //创建一个输入拆分并将其添加到拆分数组addCreatedSplit(job,splits,nodes,validBlocks);

      } else {

        for(OneBlockInfo oneblock:validBlocks){

          blockToNodes.put(oneblock,oneblock.hosts);

        }

      }

      validBlocks.clear();

      nodes.clear();

      curSplitSize = 0;

    }

 

 

(2)逐个机架(数据块)形成切片,代码如下:

//如果机架中的块低于指定的最小大小,请保留它们

    //在'溢出'中。所有机架处理完成后,这些溢出

    //块将组合成分裂。

    // overflowBlocks用于保存“同一机架”过程处理之后剩余的数据块

    ArrayList <OneBlockInfo> overflowBlocks = new ArrayList <OneBlockInfo>();

    ArrayList <String> racks = new ArrayList <String>();

 

    //一遍又一遍地处理所有机架,直到没有其他工作要做。(blockToNodes.size()> 0){

      //在移动到下一个机架之前,为此机架创建一个拆分。

      //为每个人创建一个分割后,回到这个机架

      //剩下的架子

      //一次处理一个机架位置,合并所有可能的块

      //作为一个分割驻留在此机架上。(受最小和最大限制

      //分割大小)。

 

      //遍历所有机架

      //依次处理每个机架for(Iterator <Map.Entry <String,List <OneBlockInfo >>> iter =

           rackToBlocks.entrySet()迭代(); iter.hasNext();){

        Map.Entry <String,List <OneBlockInfo >> one = iter.next();

        racks.add(one.getKey());

        List <OneBlockInfo> blocks = one.getValue();

 

        //对于每个块,将其复制到validBlocks中。从// blockToNodes中删除它,以便同一个块不会出现在//两个不同的splits中.boolean createdSplit = false; //依次处理该机架的每个数据块for(OneBlockInfo oneblock:blocks){

          if(blockToNodes.containsKey(oneblock)){

            validBlocks.add(oneblock);

            blockToNodes.remove(oneblock);

            curSplitSize + = oneblock.length; //如果累积的分割大小超过最大值,则创建此分割。

            //如果数据块累积大小大于或等于maxSize,则形成一个切片if(maxSize!= 0 && curSplitSize> = maxSize){

              //创建一个输入拆分并将其添加到拆分数组addCreatedSplit(job,splits,getHosts(racks),validBlocks);

              createdSplit = true;

              打破;

            }

          }

        }

 

        //如果我们创建了一个分割,那么只需转到下一个rackif(createdSplit){

          curSplitSize = 0;

          validBlocks.clear();

          racks.clear();

          继续;

        }

 

        if(!validBlocks.isEmpty()){

          //如果剩余数据块大小大于或等于minSizeRack,则将这些数据块构成一个切片if(minSizeRack!= 0 && curSplitSize> = minSizeRack){

            //如果指定了最小大小,则创建单个拆分

            //否则,将这些块存储到溢出数据结构addCreatedSplit(job,splits,getHosts(racks),validBlocks);

          } else {

            //这个机架中有几个块还有待处理。

            //将它们保存在“溢出”阻止列表中。这些将在以后合并。

            //如果剩余数据块大小小于minSizeRack,则将这些数据块加入overflowBlocks overflowBlocks.addAll(validBlocks);

          }

        }

        curSplitSize = 0;

        validBlocks.clear();

        racks.clear();

      }

    }

 

(3)遍历并累加剩余数据块,代码如下:

//处理所有溢出块(OneBlockInfo oneblock:overflowBlocks){

 validBlocks.add(oneblock); curSplitSize + = oneblock.length; //这可能会导致重新添加现有的机架位置,

//但它应该是ok.for(int i = 0; i <oneblock.racks.length; i ++){

   racks.add(oneblock.racks [I]); }

//如果累积的分割大小超过最大值,那么

//创建此拆分。

//如果剩余数据块大小大于或等于maxSize,则将这些数据块构成一个切片if(maxSize!= 0 && curSplitSize> = maxSize){

//创建一个输入拆分并将其添加到拆分数组addCreatedSplit(作业,分裂,getHosts(机架),validBlocks); curSplitSize = 0;

   validBlocks.clear();

   racks.clear();

 }

    }

 

 

(4)剩余数据块形成一个切片,代码如下:

//处理任何剩余的块,如果有的话。(!validBlocks.isEmpty()){

      addCreatedSplit(作业,分裂,getHosts(机架),validBlocks);

    }

 

总结

CombineFileInputFormat形成切片过程中考虑数据本地性(同一节点,同一机架),首先处理同一节点的数据块,然后处理同一机架的数据块,最后处理剩余的数据块,可见本地性是逐步减弱的。另外CombineFileInputFormat是抽象的,具体使用时需要自己实现getRecordReader方法。

 

 

 

 

 

 

(3)SequenceFileInputFormat / SequenceFileOutputFormat

sequenceFile是Hadoop的的中非常重要的一种数据格式

sequenceFile文件内部的数据组织形式是:KV对

读入/写出为的hadoop的序列文件

 

示例代码:

 

6.2 MultipleInputs

虽然FileInputFormat可以读取多个目录,但是有些场景下我们要处理的数据可能有不同的来源,或者经历过版本升级而产生格式的差别。比如一些文件是tab分隔,一些文件是逗号分隔,此时就可以使用MultipleInputs,可以为不同的路径指定不同的mapper类来处理;

 

应用示例:假如某数据分析系统需要分析的数据有两类文件格式,一类为普通Text文本,一类为SequenceFile格式文件

实现步骤:

  1. 对应不同格式文件,相应地要编写两个不同逻辑的mapper类

 

static class TextMapperA extends Mapper<LongWritable, Text, Text, LongWritable> {

@Override

protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

 

String line = value.toString();

String[] words = line.split(" ");

for (String w : words) {

context.write(new Text(w), new LongWritable(1));

}

}

}

 

static class SequenceMapperB extends Mapper<Text, LongWritable, Text, LongWritable> {

@Override

protected void map(Text key, LongWritable value, Context context) throws IOException, InterruptedException {

 

context.write(key, value);

 

}

}

 

(2)在job描述中使用MultipleInputs对不同类型数据设置不同的mapper类及inputformat来处理

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

Configuration conf = new Configuration();

Job job = Job.getInstance(conf);

job.setJarByClass(WordCount.class);

// 为不同路径的文件指定不同的mapper类及inputformat类

MultipleInputs.addInputPath(job, new Path("c:/wordcount/textdata"), TextInputFormat.class, TextMapperA.class);

MultipleInputs.addInputPath(job, new Path("c:/wordcount/seqdata"), SequenceFileInputFormat.class, SequenceMapperB.class);

job.setReducerClass(SameReducer.class);

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(LongWritable.class);

FileOutputFormat.setOutputPath(job, new Path("c:/wordcount/multiinouts"));

job.waitForCompletion(true);

}

 

 

 

 

6.3 自定义Inputformat

当框架自带的TextInputformat,SequenceFileInputFormat不能满足需求时,可以自定义InputFormat来读取文件。

 

场景示例:将小文件整个合入大文件SequenceFile(在生产实际中常用,MR擅长处理大文件,而很多生产系统所产生的数据多为大量小文件,如果直接用hadoop来分析处理,效率较低,而通过SequenceFile可以方便地将小文件合并为大文件,从而提高处理效率)

通常做法是:将小文件的文件名作为key,将小文件的内容作为value,写入一个大的SequenceFile中)

 

代码实现:

(1)自定义一个InputFormat

static class WholeFileInputFormat extends FileInputFormat<NullWritable, BytesWritable> {

@Override

         //改写父类逻辑,总是返回false,从而让

protected boolean isSplitable(JobContext context, Path filename) {

return false;

}

 

@Override

public RecordReader<NullWritable, BytesWritable> createRecordReader(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {

              //返回一个自定义的RecordReader用于读取数据

WholeFileRecordReader reader = new WholeFileRecordReader();

reader.initialize(split, context);

return reader;

}

}

  1. 实现自定义的WholeFileRecordReader

class WholeFileRecordReader extends RecordReader<NullWritable, BytesWritable> {

 

private FileSplit fileSplit;

private Configuration conf;

//定义一个bytes缓存,用来存储一个小文件的数据内容

private BytesWritable value = new BytesWritable();

private boolean processed = false;

 

//初始化方法,将传入的文件切片对象和context对象赋值给类成员

@Override

public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {

fileSplit = (FileSplit) split;

conf = context.getConfiguration();

}

 

@Override

//核心逻辑,用于从源数据中读取数据并封装为KEY  / VALUE

public boolean nextKeyValue() throws IOException, InterruptedException {

//当前小文件处理过,则processed为true

if (!processed) {

 

byte[] contents = new byte[(int) fileSplit.getLength()];

Path filePath = fileSplit.getPath();

FileSystem fs = filePath.getFileSystem(conf);

FSDataInputStream in = fs.open(filePath);

IOUtils.readFully(in, contents, 0, contents.length);

value.set(contents, 0, contents.length);

IOUtils.closeStream(in);

processed = true;

return true;

 

}

//如果当前小文件已经处理过,则返回false,以便调用者跳到下一个文件切片的处理

return false;

}

 

@Override

//返回一个key

public NullWritable getCurrentKey() throws IOException, InterruptedException {

// TODO Auto-generated method stub

return NullWritable.get();

}

 

@Override

//返回一个values

public BytesWritable getCurrentValue() throws IOException, InterruptedException {

// TODO Auto-generated method stub

return value;

}

 

@Override

//用于返回进度信息,读完一个小文件即返回1

public float getProgress() throws IOException, InterruptedException {

// TODO Auto-generated method stub

return processed ? 1.0f : 0.0f;

}

 

@Override

public void close() throws IOException {

// TODO Auto-generated method stub

 

}

 

}

 

 

6.4 Mapreduce输出格式组件

6.4.1 TextOutPutFormat源码结构解析:

 

 

 

 

6.4.2 MultipleOutputs

static class CountReducer extends Reducer<Text, LongWritable, Text, LongWritable> {

MultipleOutputs<Text, LongWritable> multipleOutputs = null;

 

//在初始化方法中构造一个multipleOutputs

protected void setup(Context context) throws IOException, InterruptedException {

 

multipleOutputs = new MultipleOutputs<Text, LongWritable>(context);

 

};

 

@Override

protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {

 

long count = 0;

for (LongWritable value : values) {

count += value.get();

}

if (key.toString().startsWith("a")) {

//可以通过条件判断将不同内容写入不同文件

multipleOutputs.write(key, new LongWritable(count), "c:/multi/outputa/a");

} else {

multipleOutputs.write(key, new LongWritable(count), "c:/sb/outputb/b");

}

 

}

 

//一定要对multipleOutputs进行close,否则内容不会真实写入文件

@Override

protected void cleanup(Context context) throws IOException, InterruptedException {

multipleOutputs.close();

}

 

}

 

6.4.3 自定义FileOutPutFormat

public class FlowOutputFormat extends FileOutputFormat<Text, NullWritable> {

 

@Override

public RecordWriter<Text, NullWritable> getRecordWriter(

TaskAttemptContext context) throws IOException,

InterruptedException {

 

FileSystem fs = FileSystem.get(context.getConfiguration());

 

Path enhancelog = new Path("hdfs://weekend01:9000/enhance/enhanced.log");

Path tocrawl = new Path("hdfs://weekend01:9000/enhance/tocrawl.log");

 

//构造两个不同的输出流

FSDataOutputStream enhanceOs = fs.create(enhancelog);

FSDataOutputStream tocrawlOs = fs.create(tocrawl);

 

 

//通过构造函数将两个流传给FlowRecordWriter

return new FlowRecordWriter(enhanceOs, tocrawlOs);

}

 

public static class FlowRecordWriter extends

RecordWriter<Text, NullWritable> {

 

private FSDataOutputStream enhanceOs;

private FSDataOutputStream tocrawlOs;

 

public FlowRecordWriter(FSDataOutputStream enhanceOs,

FSDataOutputStream tocrawlOs) {

 

this.enhanceOs = enhanceOs;

this.tocrawlOs = tocrawlOs;

 

}

 

//具体的写出动作在write方法中完成

@Override

public void write(Text key, NullWritable value) throws IOException,

InterruptedException {

String line = key.toString();

if (line.contains("itisok")) {

enhanceOs.write(line.getBytes());

 

} else {

 

tocrawlOs.write(line.getBytes());

 

}

 

}

 

@Override

public void close(TaskAttemptContext context) throws IOException,

InterruptedException {

if (enhanceOs != null) {

enhanceOs.close();

}

if (tocrawlOs != null) {

tocrawlOs.close();

}

 

}

}

 

}

 

 

 

 

 

6.5 Configuration配置对象与Toolrunner

(1)配置参数的优先级:

集群*-site.xml < src/conf < conf.set()

(2)Toolrunner----可通过提交命令动态设置配置参数或文件

Configuration对象还可以用来分发少量数据到所有任务节点

 

示例:

/**

可以通过运行时加参数来传递参数给conf对象

-D property=value

-conf filename ...

-fs uri  等价于 -D fs.defaultFS=uri

-jt host:port 等价于 -D yarn.resourcemanager.address=host:port

-files file1,file2,

-archives archive1,archive2

-libjars jar1,jar2,...

 *

 * @author duanhaitao@itcast.cn

 *

 */

public class TestToolrunner extends Configured implements Tool {

 

static {

Configuration.addDefaultResource("hdfs-default.xml");

Configuration.addDefaultResource("hdfs-site.xml");

Configuration.addDefaultResource("core-default.xml");

Configuration.addDefaultResource("core-site.xml");

Configuration.addDefaultResource("mapred-default.xml");

Configuration.addDefaultResource("mapred-site.xml");

Configuration.addDefaultResource("yarn-default.xml");

Configuration.addDefaultResource("yarn-site.xml");

}

 

@Override

public int run(String[] args) throws Exception {

Configuration conf = getConf();

TreeMap<String, String> treeMap = new TreeMap<String,String>();

for (Entry<String, String> ent : conf) {

treeMap.put(ent.getKey(), ent.getValue());

 

}

 

 

for (Entry<String, String> ent : treeMap.entrySet()) {

 

System.out.printf("%s=%s\n", ent.getKey(), ent.getValue());

 

}

 

return 0;

}

 

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

ToolRunner.run(new TestToolrunner(), args);

}

 

}

 

 

 

6.6 mapreduce数据压缩

运算密集型的job,少用压缩

IO密集型的job,多用压缩

 

 

通过压缩编码对mapper或者reducer的输出进行压缩,以减少磁盘IO,提供MR程序运行速度(但相应增加了cpu运算负担)

 

(1)MR支持的压缩编码

 

(2)Reducer输出压缩

----设置

mapreduce.output.fileoutputformat.compress=false

mapreduce.output.fileoutputformat.compress.codec=org.apache.hadoop.io.compress.DefaultCodec

mapreduce.output.fileoutputformat.compress.type=RECORD

或在代码中设置

Job job = Job.getInstance(conf);

FileOutputFormat.setCompressOutput(job, true);

FileOutputFormat.setOutputCompressorClass(job, (Class<? extends CompressionCodec>) Class.forName(""));

 

(3)Mapper输出压缩

----设置

mapreduce.map.output.compress=false

mapreduce.map.output.compress.codec=org.apache.hadoop.io.compress.DefaultCodec

 

或者在代码中:

conf.setBoolean(Job.MAP_OUTPUT_COMPRESS, true);

conf.setClass(Job.MAP_OUTPUT_COMPRESS_CODEC,

                   GzipCodec.class, CompressionCodec.class);

 

 

 

(4)压缩文件的读取

Hadoop自带的InputFormat类内置支持压缩文件的读取,比如TextInputformat类,在其initialize方法中:

  public void initialize(InputSplit genericSplit,

                         TaskAttemptContext context) throws IOException {

    FileSplit split = (FileSplit) genericSplit;

    Configuration job = context.getConfiguration();

    this.maxLineLength = job.getInt(MAX_LINE_LENGTH, Integer.MAX_VALUE);

    start = split.getStart();

    end = start + split.getLength();

    final Path file = split.getPath();

 

    // open the file and seek to the start of the split

    final FileSystem fs = file.getFileSystem(job);

    fileIn = fs.open(file);

    //根据文件后缀名创建相应压缩编码的codec

    CompressionCodec codec = new CompressionCodecFactory(job).getCodec(file);

    if (null!=codec) {

      isCompressedInput = true;

      decompressor = CodecPool.getDecompressor(codec);

  //判断是否属于可切片压缩编码类型

      if (codec instanceof SplittableCompressionCodec) {

        final SplitCompressionInputStream cIn =

          ((SplittableCompressionCodec)codec).createInputStream(

            fileIn, decompressor, start, end,

            SplittableCompressionCodec.READ_MODE.BYBLOCK);

 //如果是可切片压缩编码,则创建一个CompressedSplitLineReader读取压缩数据

        in = new CompressedSplitLineReader(cIn, job,

            this.recordDelimiterBytes);

        start = cIn.getAdjustedStart();

        end = cIn.getAdjustedEnd();

        filePosition = cIn;

      } else {

//如果是不可切片压缩编码,则创建一个SplitLineReader读取压缩数据,并将文件输入流转换成解压数据流传递给普通SplitLineReader读取

        in = new SplitLineReader(codec.createInputStream(fileIn,

            decompressor), job, this.recordDelimiterBytes);

        filePosition = fileIn;

      }

    } else {

      fileIn.seek(start);

   //如果不是压缩文件,则创建普通SplitLineReader读取数据

      in = new SplitLineReader(fileIn, job, this.recordDelimiterBytes);

      filePosition = fileIn;

    }

 

6.7 mapreduce的计数器

6.7.1 mapreduce框架自带计数器: 

Task group

Inputformat group

Outputformat group

Framework group

 

6.7.2 用户自定义计数器

  • 枚举方式
  • 动态设置

 

public class MultiOutputs {

//通过枚举形式定义自定义计数器

enum MyCounter{MALFORORMED,NORMAL}

 

static class CommaMapper extends Mapper<LongWritable, Text, Text, LongWritable> {

 

@Override

protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

 

String[] words = value.toString().split(",");

 

for (String word : words) {

context.write(new Text(word), new LongWritable(1));

}

//对枚举定义的自定义计数器加1

context.getCounter(MyCounter.MALFORORMED).increment(1);

//通过动态设置自定义计数器加1

context.getCounter("counterGroupa", "countera").increment(1);

}

 

}

 

 

计数器原理简述(由appmaster维护,是一个全局的)

 

 

6.8 mapreduce的日志分析

日志存放位置

(1)系统服务进程的日志,默认存放在hadoop安装目录下的logs目录中

 

 

 

(2)用户应用输出的日志:

 

 

 

日志配置

 

 

 

6.9 多job串联

借助jobControl类来建立job间顺序和依赖关系;

示例:

      ControlledJob cJob1 = new ControlledJob(job1.getConfiguration());

        ControlledJob cJob2 = new ControlledJob(job2.getConfiguration());

        ControlledJob cJob3 = new ControlledJob(job3.getConfiguration());

       

        // 设置作业依赖关系

        cJob2.addDependingJob(cJob1);

        cJob3.addDependingJob(cJob2);

 

        JobControl jobControl = new JobControl("RecommendationJob");

        jobControl.addJob(cJob1);

        jobControl.addJob(cJob2);

        jobControl.addJob(cJob3);

 

        cJob1.setJob(job1);

        cJob2.setJob(job2);

        cJob3.setJob(job3);

 

        // 新建一个线程来运行已加入JobControl中的作业,开始进程并等待结束

        Thread jobControlThread = new Thread(jobControl);

        jobControlThread.start();

        while (!jobControl.allFinished()) {

            Thread.sleep(500);

        }

        jobControl.stop();

 

        return 0;

 

 

 

动手练习

 

 

 

 

 

附:MR常见算法练习

1 数据去重----(预处理:清洗、过滤、去重)

2012-3-1 a

2012-3-2 b

2012-3-3 c

2012-3-4 d

2012-3-5 a

2012-3-6 b

2012-3-7 c

2012-3-3 c

 

2 数据排序

  1. 用一个reducer
  2. 用多个reducer(自定义partitioner/用inputsampler抽样后生成partitioner,用totalorder)

变形:分组排序、topk(自己写一遍)

 

 

如原始数据:

2

32

654

32

15

756

65223

5956

22

650

92

要求结果:

1    2

2    6

3    15

4    22

5    26

6    32

7    32

8    54

9    92

10    650

11    654

12    756

13    5956

14    65223

 

 

3 求均值

 

原始数据:

1)math:

 

张三    88

李四    99

王五    66

赵六    77

 

2)chinese:

 

张三    78

李四    89

王五    96

赵六    67

 

3)english:

 

张三    80

李四    82

王五    84

赵六    86

 

输出结果:

张三    82

李四    90

王五    82

赵六    76

 

 

4 单表关联

给出child-parent(孩子——父母)表,要求输出grandchild-grandparent(孙子——爷奶)表。

样例输入如下所示。

file:

child parent

Tom Lucy

Tom Jack

Jone Lucy

Jone Jack

Lucy Mary

Lucy Ben

Jack Alice

Jack Jesse

Terry Alice

Terry Jesse

Philip Terry

Philip Alma

Mark Terry

Mark Alma

输出结果:

grandchild grandparent

Tom Alice

Tom Jesse

Jone Alice

Jone Jesse

Tom Mary

Tom Ben

Jone Mary

Jone Ben

Philip Alice

Philip Jesse

Mark Alice

Mark Jesse

 

5 多表关联

Map side join

Reduce side join

 

 

6 日志解析

简单转换(如字段截取,字符串替代等)

外部字典替换

格式转换(如json,xml等格式转换为plain text)

 

 

7 共同好友

原始数据:每个人的好友列表

A:B,C,D,F,E,O

B:A,C,E,K

C:F,A,D,I

D:A,E,F,L

E:B,C,D,M,L

F:A,B,C,D,E,O,M

G:A,C,D,E,F

H:A,C,D,E,O

I:A,O

J:B,O

K:A,C,D

L:D,E,F

M:E,F,G

O:A,H,I,J

……

 

输出结果:每个人和其他各人所拥有的功能好友

A-B C,E,

A-C D,F,

A-D E,F,

A-E B,C,D,

A-F B,C,D,E,O,

A-G C,D,E,F,

A-H C,D,E,O,

A-I O,

A-J B,O,

A-K C,D,

A-L D,E,F,

A-M E,F,

B-C A,

B-D A,E,

……

 

8 其他杂例

去哪儿网笔试题:

去哪儿旅行的APP每天会产生大量的访问日志。用户【uuid-x】的每一次操作记录会产生一条日志记录,假设用户可以通过单程搜索【search-dancheng】,往返搜索【search-wangfan】等多个入口进入报价详情页【detail】选择航班并完成最后的下订单【submit】购票操作。日志格式如下,请编写Map/Reduce程序完成如下需求(伪代码完成即可)

a) 计算20140510这一天去哪儿旅行APP的订单有多少来自单程搜索,有多少来自往返搜索

 

日志示例(仅作示例【片段,每天数据量会非常大】):

20140510 09:17:19 uuid-01 search-dancheng dep=北京&arr=上海&date=20140529&pnvm=0

20140510 09:18:20 uuid-02 search-wangFan dep=北京&arr=上海&sdate=20140529&edate=20140605

20140510 09:18:23 uuid-01 detail dep=北京&arr=上海&date=20140529&fcode=CA1810

20140510 09:20:29 uuid-02 detail dep=北京&arr=上海&date=20140529&fcode=CA1810

20140510 09:21:19 uuid-01 submit dep=北京&arr=上海&date=20140529&fcode=CA1810&price=1280

20140510 09:23:19 uuid-03 search-dancheng dep=北京&arr=广州&date=20140529&pnvm=0

20140510 09:25:19 uuid-04 search-dancheng dep=北京&arr西安&date=20140529&pnvm=0

20140510 09:25:30 uuid-05 search-dancheng dep=北京&arr=天津&date=20140529&pnvm=0

20140510 09:26:29 uuid-04 detail dep=北京&arr=西安&上海&date=20140529&fcode=CA1810

20140510 09:28:19 uuid-06 submit dep=北京&arr=拉萨&date=20140529&fcode=CA1810&price=2260

 

电力公司数据更新日志合并

 

 

 

某公司日志处理需求说明:

根据系统和关键字查询日志,并将关键字所在行以下10行数据输出或保存到hdfs,最终是把这些数据展示到Web页面。

(关键字所在的数据行与它以下10行数据并没有关联关系,日志数据为很乱的原数据。)

 

java应用+shell脚本+spark.jar包

java应用负责用户登录后,输入系统、关键字等参数,提交查询,java调用shell脚本-->submit

结果数据保存到hdfs上。保存的该文件用随机数命名,最后在Web页面读取展示出来。

 

 

样例数据如下:

15-06-10.23:58:02.321 [pool-22-thread-5] INFO  HttpPostMessageSender   -  HttpPostMessageSender resp statusCode: 200 content:success tradeno:2015061010001000070650683

15-06-10.23:58:02.321 [pool-22-thread-5] INFO  HttpPostMessageSender   - HttpPost 是否发送成功  true

15-06-10.23:58:02.321 [pool-22-thread-5] INFO  NotificationServiceImpl  - ****进行入库操作****

15-06-10.23:58:02.324 [pool-22-thread-5] INFO  NotificationServiceImpl  - ****没有此TRADE_NO,新增Notification****tradeNo=2015061010001000070650683

15-06-10.23:58:02.327 [pool-22-thread-5] INFO  NotifyServiceImpl       - ****enter--saveOrUpdateNotity****

15-06-10.23:58:02.330 [pool-22-thread-5] INFO  NotifyServiceImpl       - ****没有此TRADE_NO,新增Notify****tradeNo=2015061010001000070650683

15-06-10.23:58:02.333 [pool-22-thread-5] INFO  ACCESS                  - 2015061010001000070650683,FINISHED_SUCCESS

15-06-10.23:58:04.250 [pool-20-thread-2] INFO  NPPListener             - Received a new message OutTradeNotify{tradeInfo=TradeInfo{outTradeNo='150610263916206067998', tradeNo='2015061010001000070650827', originalTradeNo='null', bizTradeNo='9488771051', tradeType=TRADE_GENERAL, subTradeType=SALE, payMethod=CASHIERGATEMODE, tradeMoney=Money{currency=CNY, amount=10420}, tradeSubject='消费订单', submitter=ThinCustomer{merchantNo='23077370', customerNo='360080000230773708', customerLoginName='null', customerName='null', customerOutName='null'}, seller=ThinCustomer{merchantNo='23077370', customerNo='360080000230773708', customerLoginName='null', customerName='null', customerOutName='null'}, sellerAccountNo='360080000230773708000811', buyer=ThinCustomer{merchantNo='null', customerNo='360000000260175680', customerLoginName='null', customerName='null', customerOutName='null'}, tradeStatus=TRADE_FINISHED, createdDate=Wed Jun 10 23:57:32 CST 2015, deadlineTime=null, tradeFinishedDate='20150610', tradeFinishedTime='235802', payTool=EXPRESS, bankCode='CEB', exchangeDate='null', exchangeRate='null', returnParams='null', oldGWV60AuthCode='null', oldEXV10TerminalNo='null', clearingCurrency=null, clearingMoney=null, tradeExtInfo=TradeExtInfo{notifyStatus='NOT', outMessageId='null', cardSha1='null', signNo='null', returnParams='null', extendParams='null', pageBackUrl='null', serverNotifyUrl='http://gw.jd.com/payment/notify_chinabankReal.action', notifySmsMoible='null', notifyMailAddress='null', innerMessageFormat='XML', apiMessageFormat='EX_V1.0', requestCharset='UTF-8', encryptType='3DES', signType='MD5', requestModule='null', requestVersion='null', remoteIp='109.145.60.24', receivingChannel='JDSC', requestProtocol='HTTP', requestMethod='null', outTradeDate='20150610', outTradeTime='235731', outTradeIp='109.145.60.24', outRefererHosts='null', retryCount=1}, ext=null}}, OutMessageNotify{apiMessageFormat=null, messageFormat=null, notifyCharset='null', signType='null', encryptType='null'}, MessageNotify{responseModule='null', responseCode='null', responseDesc='null'}

15-06-10.23:58:04.253 [pool-20-thread-2] INFO  KeyServiceImpl          - Calling SecurityService to get {} key for merchant {} with codeClass {}23077370KeyTypeEnum{code='3DES', cnName='三DES'}EXPRESS

15-06-10.23:58:04.264 [pool-20-thread-2] INFO  CustomerCenterFacade    - [INVOCATION_LOG_C] 2015-06-10.23:58:04.264;pool-20-thread-2;172.17.92.48:0->172.17.87.47:20996;com.wangyin.customer.api.CustomerCenterFacade:1.1.6.getMerchantCustomerKeys(com.wangyin.customer.common.dto.customer.CustomerParamDTO);***;2015-06-10.23:58:04.253;RESULT:***;11,112,359;

15-06-10.23:58:04.264 [pool-20-thread-2] INFO  KeyServiceImpl          - 获取的3DES 密钥值为20B0984A9B751F0B911A1AEA0738D557AE16548CCE029E2A

15-06-10.23:58:04.264 [pool-20-thread-2] INFO  KeyServiceImpl          - Calling SecurityService to get {} key for merchant {} with codeClass {}23077370KeyTypeEnum{code='SALT', cnName='签名密钥'}EXPRESS

15-06-10.23:58:04.270 [pool-24-thread-4] INFO  NPPListener             - Received a new message OutTradeNotify{tradeInfo=TradeInfo{outTradeNo='22015061023575751670871914', tradeNo='2015061010001000070651406', originalTradeNo='null', bizTradeNo='null', tradeType=TRADE_GENERAL, subTradeType=SALE, payMethod=APIEXPRESSMODE, tradeMoney=Money{currency=CNY, amount=500000}, tradeSubject='消费订单', submitter=ThinCustomer{merchantNo='22843776', customerNo='360080000228437761', customerLoginName='null', customerName='null', customerOutName='null'}, seller=ThinCustomer{merchantNo='22843776', customerNo='360080000228437761', customerLoginName='null', customerName='null', customerOutName='null'}, sellerAccountNo='360080000228437761000811', buyer=null, tradeStatus=TRADE_FINISHED, createdDate=Wed Jun 10 23:57:57 CST 2015, deadlineTime=null, tradeFinishedDate='20150610', tradeFinishedTime='235802', payTool=EXPRESS, bankCode='ICBC', exchangeDate='null', exchangeRate='null', returnParams='22894010', oldGWV60AuthCode='null', oldEXV10TerminalNo='00000002', clearingCurrency=null, clearingMoney=null, tradeExtInfo=TradeExtInfo{notifyStatus='NOT', outMessageId='API.150610.0ddf8c2f7ed94f3e9f741cd44500a866', cardSha1='5D72C7755A82576EE906BAB8314164ABAC513C9C', signNo='201505110010089270009113541', returnParams='22894010', extendParams='null', pageBackUrl='null', serverNotifyUrl='http://jrb-api.d.chinabank.com.cn/notify/quick.htm', notifySmsMoible='null', notifyMailAddress='null', innerMessageFormat='XML', apiMessageFormat='EX_V1.0', requestCharset='UTF-8', encryptType='3DES', signType='MD5', requestModule='null', requestVersion='null', remoteIp='172.17.80.168', receivingChannel='API', requestProtocol='HTTP', requestMethod='POST', outTradeDate='null', outTradeTime='null', outTradeIp='null', outRefererHosts='null', retryCount=1}, ext=null}}, OutMessageNotify{apiMessageFormat=null, messageFormat=null, notifyCharset='null', signType='null', encryptType='null'}, MessageNotify{responseModule='null', responseCode='null', responseDesc='null'}

15-06-10.23:58:04.271 [pool-20-thread-2] INFO  CustomerCenterFacade    - [INVOCATION_LOG_C] 2015-06-10.23:58:04.271;pool-20-thread-2;172.17.92.48:0->172.17.91.104:20996;com.wangyin.customer.api.CustomerCenterFacade:1.1.6.getMerchantCustomerKeys(com.wangyin.customer.common.dto.customer.CustomerParamDTO);***;2015-06-10.23:58:04.264;RESULT:***;6,971,110;

15-06-10.23:58:04.272 [pool-20-thread-2] INFO  KeyServiceImpl          - 获取MD5 TOKEN 密钥的值为1qaz2wsx3edc

15-06-10.23:58:04.273 [pool-20-thread-2] INFO  NPPNotifyProcessorImpl  - ApiMessageFormatEX_V1.0

15-06-10.23:58:04.273 [pool-24-thread-4] INFO  KeyServiceImpl          - Calling SecurityService to get {} key for merchant {} with codeClass {}22843776KeyTypeEnum{code='3DES', cnName='三DES'}EXPRESS

15-06-10.23:58:04.273 [pool-20-thread-2] INFO  NPPNotifyProcessorImpl  - 转化为NotificationDTO的结果为: com.wangyin.npp.notify.facade.dto.NotificationDTO@54b27890

15-06-10.23:58:04.273 [pool-20-thread-2] INFO  NotificationServiceImpl  - 准备入库(可能会入库)的 notification=Notification [TRADE_NO=2015061010001000070650827, SOURCE_NAME=NPP_PAYMENT_COMPLETE, FROM_ADDRESS=EXPRESS, FROM_NAME=23077370, TO_ADDRESS=http://gw.jd.com/payment/notify_chinabankReal.action, CHANNEL=HTTP_POST, SUBJECT=null, CONTENT=resp=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxDSElOQUJBTks%2BCiAgPFZFUlNJT04%2BMS4wLjA8L1ZFUlNJT04%2BCiAgPE1FUkNIQU5UPjIzMDc3MzcwPC9NRVJDSEFOVD4KICA8VEVSTUlOQUw%2BMDAwMDAwMDE8L1RFUk1JTkFMPgogIDxEQVRBPllFbm10T0Zkb0RBK0tHdmhVZmJBZTlKOVZDOC9ONGx1YW5uMlBTRFF0L0VNTUh3eHR6L29tYi9vdlArTjAybnlsTGdhbUhCVDBZYVpBMUxoSC9iV3RndmoxN0JMTDhPTFc3U3laZmMxMU5sczRqSFdGeUR1UHNsb3F4YU51aFdUUFFDTzljMCtrTFpDZkpuZHB6d2sxN3J4dU5mRGVuYmljZ21kWHphSlhQNElQZzFKQ2h1ZGRRNWdTQTQ4UWVPVEE0UUhJYUsyQVFJNTNZQU03RHdQWFBrZkNPMythRUgvMk5oeGJMRmtYMTEvalJWUUI0NDM1K2FtSm1zclE0UFJ5cVVSWmx6eGVJQk5XNU4xZnZjMUE1NXRVa1RmRjNWc1orWjU2WkdydFoyQzdnQ3BWNkxqOUNDUWlzbjhKMEd3Z2JLS0kvdUMyUVNDTHJOMUl3YU8waSsxUUFIVWdPRGRtTFZHUGxhSTBqTS85UWVmY0Q2R0FjaVJua214R

 

7. Yarn集群

7.1 Yarn产生的原因

(1)MapreduceV1中,jobtracker存在瓶颈:

集群上运行的所有mr程序都有jobtracker来调度

SPOF单点故障

职责划分不清晰

 

  1. 将jobtracker的职责划分成两个部分:
  • 资源调度与管理:由统一的资源调度平台(集群)来实现(yarn)
  • 任务监控与管理:

A、每一个application运行时拥有一个自己的任务监控管理进程AppMaster

B、AppMaster的生命周期:application提交给yarn集群之后,yarn负责启动该application的AppMaster,随后任务的执行监控调度等工作都交由AppMaster,待这个application运行完毕后,AppMaster向yarn注销自己。

C、AppMaster的具体实现由application所使用的分布式运算框架自己负责,比如Mapreduce类型的application有MrAppMaster实现类。Spark DAG应用则有SparkOnYarn的SparkContext实现

 

 

 

7.2 Yarn的架构

 

 

(1)ResourceManager ----> master node,可配多个RM实现HA机制,

由两个核心组件构成:

Scheduler ApplicationsManager;

Scheduler:负责资源调度,调度策略可插拔(内置实现 CapacityScheduler / FairScheduler ),不提供对application运行的监控;

ApplicationsManager:负责响应任务提交请求,协商applicationMaster运行的container,重启失败的applicationMaster

 

 

(2)NodeManager ----> slave nodes,每台机器上一个

职责:加载containers,监控各container的资源使用情况,并向Resourcemanager/Scheduler汇报

 

 

(3)ApplicationMaster ----> 特定运算框架自己实现,接口为统一的AppMaster

职责:向Scheduler请求适当的资源,跟踪任务的执行,监控任务执行进度、状态等

 

 

7.3 Yarn运行application的流程

 详细参见《yarn运行application的流程图》

 

 

*Job提交流程详解

流程简述

源码跟踪:注重客户端与resourcemanager之间的交互

 

7.4 MapReduce程序向yarn提交执行的流程分析

Job.waitForCompletion()

创建  yarnrunner

向resourcemanager提交请求,获取application id

Yarnrunner提交job资源

 

7.5 application生命周期

Yarn支持短周期和长周期应用

MR:短周期应用,用户的每一个job为一个application

Spark:短周期应用,但比上一种效率要高,它是将一个工作流(DAG)转化为一个application,这样在job之间可以重用container及中间结果数据可以不用落地

 

Storm:long-running应用,应用为多用户共享,降低了资源调度的前期消耗,从而可以为用户提供低时延响应

 

 

7.5资源请求

资源请求由Container对象描述,支持数据本地性约束,如处理hdfs上的数据,则container优先分配在block所在的datanode,如该datanode资源不满足要求,则优选同机架,还不能满足则随机分配

 

Application可以在其生命周期的任何阶段请求资源,可以在一开始就请求所需的所有资源,也可以在运行过程中动态请求资源;如spark,采用第一种策略;而MR则分两个阶段,map task的资源是在一开始一次性请求,而reduce task的资源则是在运行过程中动态请求;并且,任务失败后,还可以重新请求资源进行重试

 

 

 

7.6 任务调度--capacity scheduler / fair scheduler

由于集群资源有限,当无法满足众多application的资源请求时,yarn需要适当的策略对application的资源请求进行调度;

7.6.1 Scheduler概述

Yarn中实现的调度策略有三种:FIFO/Capacity/Fair Schedulers

 (1)FIFO Scheduler:

将所有application按提交的顺序排队,先进先出

优点---->简单易懂且不用任何配置

缺点---->不适合于shared clusters;大的应用会将集群资源占满从而导致大量应用等待

 

 

  1. Capacity Scheduler

将application划分为多条任务队列,每条队列拥有相应的资源

在队列的内部,资源分配遵循FIFO策略

队列资源支持弹性调整:一个队列的空闲资源可以分配给“饥饿”队列(注意:一旦之前的空闲队列需求增长,因为不支持“先占”,不能强制kill资源container,则需要等待其他队列释放资源;为防止这种状况的出现,可以配置队列最大资源进行限制)

任务队列支持继承结构

 

 

  1. Fair Scheduler

不需要为特定small application保留资源,而是在需要执行时进行动态公平分配;

动态资源分配有一个延后,因为需要等待large job释放一部分资源

Small job资源使用完毕后,large job可以再次获得全部资源

Fair Scheduler也支持在application queue之间进行调度

 

 

  

7.6.2 Capacity Scheduler配置

yarn的Scheduler机制,由yarn-site.xml中的配置参数指定:

<name>yarn.resourcemanager.scheduler.class</name>

默认值为:

<value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.CapacityScheduler </value>

修改为:

<value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.FairScheduler</value>

 

CapacityScheduler的配置文件则位于:

etc/hadoop/capacity-scheduler.xml 

 

capacity-scheduler .xml示例

 

 

 

 

 

 

 

 

 

 

 

 

 

 

如果修改了capacity-scheduler.xml(比如添加了新的queue),只需要执行:

yarn rmadmin -refreshQueues即可生效

 

application中指定所属的queue使用配置参数:

mapreduce.job.queuename

在示例配置中,此处queuename即为prod或dev或science

如果给定的queue name不存在,则在submission阶段报错

如果没有指定queue name,则会被列入default queue

 

 

7.6.3 Fair Scheduler配置

 

Fair Scheduler工作机制

 

 

 

 

 

 

 

启用Fair Scheduler,在yarn-site.xml中

<property> <name>yarn.resourcemanager.scheduler.class</name> <value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler</value></property>

 

配置参数--参考官网:

http://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/FairScheduler.html

一部分位于yarn-site.xml

yarn.scheduler.fair.allocation.file

Path to allocation file. An allocation file is an XML manifest describing queues and their properties, in addition to certain policy defaults. This file must be in the XML format described in the next section. If a relative path is given, the file is searched for on the classpath (which typically includes the Hadoop conf directory). Defaults to fair-scheduler.xml.

yarn.scheduler.fair.user-as-default-queue

Whether to use the username associated with the allocation as the default queue name, in the event that a queue name is not specified. If this is set to “false” or unset, all jobs have a shared default queue, named “default”. Defaults to true. If a queue placement policy is given in the allocations file, this property is ignored.

yarn.scheduler.fair.preemption

Whether to use preemption. Defaults to false.

yarn.scheduler.fair.preemption.cluster-utilization-threshold

The utilization threshold after which preemption kicks in. The utilization is computed as the maximum ratio of usage to capacity among all resources. Defaults to 0.8f.

yarn.scheduler.fair.sizebasedweight

Whether to assign shares to individual apps based on their size, rather than providing an equal share to all apps regardless of size. When set to true, apps are weighted by the natural logarithm of one plus the app’s total requested memory, divided by the natural logarithm of 2. Defaults to false.

yarn.scheduler.fair.assignmultiple

Whether to allow multiple container assignments in one heartbeat. Defaults to false.

yarn.scheduler.fair.max.assign

If assignmultiple is true, the maximum amount of containers that can be assigned in one heartbeat. Defaults to -1, which sets no limit.

yarn.scheduler.fair.locality.threshold.node

For applications that request containers on particular nodes, the number of scheduling opportunities since the last container assignment to wait before accepting a placement on another node. Expressed as a float between 0 and 1, which, as a fraction of the cluster size, is the number of scheduling opportunities to pass up. The default value of -1.0 means don’t pass up any scheduling opportunities.

yarn.scheduler.fair.locality.threshold.rack

For applications that request containers on particular racks, the number of scheduling opportunities since the last container assignment to wait before accepting a placement on another rack. Expressed as a float between 0 and 1, which, as a fraction of the cluster size, is the number of scheduling opportunities to pass up. The default value of -1.0 means don’t pass up any scheduling opportunities.

yarn.scheduler.fair.allow-undeclared-pools

If this is true, new queues can be created at application submission time, whether because they are specified as the application’s queue by the submitter or because they are placed there by the user-as-default-queue property. If this is false, any time an app would be placed in a queue that is not specified in the allocations file, it is placed in the “default” queue instead. Defaults to true. If a queue placement policy is given in the allocations file, this property is ignored.

 

 

 

另外还可以制定一个allocation file来描述application queue

 

 

 

 

7.7 yarn应用程序开发(仅做了解,通常不需要应用开发人员来做)

开发要点:参考文档《Yarn上的application开发规程》

Distributed-shell源码解读

 

8. zookeeper

8.1 hadoop-spof问题及HA解决思路

引入集群协调服务框架的必要性

8.2 zookeeper简介

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务,配置维护和命名服务等。

目前zookeeper被广泛应用于hadoop生态体系中各种框架的分布式协调,我们也可以利用zookeeper来简化分布式应用开发

 

Zookeeper可以实现的分布式协调服务包括:

统一名称服务

配置管理

分布式锁

集群节点状态协调(负载均衡/主从协调)

 

8.3 zookeeper集群搭建

(1)zookeeper集群组件:

同一个zookeeper服务下的server有三种,一种是leader server,另一种是follower server,还有一种叫observer server;

leader特殊之处在于它有决定权,具有Request Processor

(observer server 与follower server的区别就在于不参与leader选举)

 

(2)部署模式:

单节点

分布式(伪分布式)

 

(3)配置文件:

3.1添加一个zoo.cfg配置文件

$ZOOKEEPER/conf

mv zoo_sample.cfg zoo.cfg

 

3.2修改配置文件(zoo.cfg)

dataDir=/itcast/zookeeper-3.4.5/data

 

server.1=itcast05:2888:3888

server.2=itcast06:2888:3888

server.3=itcast07:2888:3888

 

3.3在(dataDir=/itcast/zookeeper-3.4.5/data)创建一个myid文件,里面内容是server.N中的N(server.2里面内容为2)

echo "1" > myid

 

3.4将配置好的zk拷贝到其他节点

scp -r /itcast/zookeeper-3.4.5/ itcast06:/itcast/

scp -r /itcast/zookeeper-3.4.5/ itcast07:/itcast/

 

3.5注意:在其他节点上一定要修改myid的内容

在itcast06应该讲myid的内容改为2 (echo "6" > myid)

在itcast07应该讲myid的内容改为3 (echo "7" > myid)

 

4.启动集群

分别启动zk

./zkServer.sh start

 

 

8.4 zookeeper演示测试

服务启动

bin/zkServer.sh status获取节点角色状态

服务状态详细信息查看(四字命令):四字命令可以获取更多信息

Zookeeper支持一下四字节命令来进行交互,查询状态信息等;可以用telnet/nc

来发送命令,如:

echo ruok | nc server01 2181

echo conf | nc server01 2181

 

 

conf

输出相关服务配置的详细信息。

cons

列出所有连接到服务器的客户端的完全的连接 / 会话的详细信息。包括“接受 / 发送”的包数量、会话 id 、操作延迟、最后的操作执行等等信息。

dump

列出未经处理的会话和临时节点。

envi

输出关于服务环境的详细信息(区别于 conf 命令)。

reqs

列出未经处理的请求

ruok

测试服务是否处于正确状态。如果确实如此,那么服务返回“imok ”,否则不做任何相应。

stat

输出关于性能和连接的客户端的列表。

wchs

列出服务器 watch 的详细信息。

wchc

通过 session 列出服务器 watch 的详细信息,它的输出是一个与watch 相关的会话的列表。

wchp

通过路径列出服务器 watch 的详细信息。它输出一个与 session相关的路径。

 

shell客户端操作

bin/zkCli.sh -server server01 2181

操作命令:

        connect host:port

        get path [watch]

        ls path [watch]

        set path data [version]

        rmr path

        delquota [-n|-b] path

        quit

        printwatches on|off

        create [-s] [-e] path data acl

        stat path [watch]

        close

        ls2 path [watch]

        history

        listquota path

        setAcl path acl

        getAcl path

        sync path

        redo cmdno

        addauth scheme auth

        delete path [version]

        setquota -n|-b val path

 

  • ZooKeeper数据模型和层次命名空间

提供的命名空间与标准的文件系统非常相似。一个名称是由通过斜线分隔开的路径名序列所组成的。ZooKeeper中的每一个节点是都通过路径来识别。如图:

         

 

 

 

  • ZooKeeper中的数据节点:

每一个节点称为znode,通过路径来访问

每一个znode维护着:数据、stat数据结构(ACL、时间戳及版本号)

znode维护的数据主要是用于存储协调的数据,如状态、配置、位置等信息,每个节点存储的数据量很小,KB级别

znode的数据更新后,版本号等控制信息也会更新(增加)

znode还具有原子性操作的特点:写--全部替换,读--全部

znode有永久节点和临时节点之分:临时节点指创建它的session一结束,该节点即被zookeeper删除;

 

  • zk性能:

Zookeeper 的读写速度非常快(基于内存数据库),并且读的速度要比写的速度更快。

顺序一致性:客户端的更新顺序与它们被发送的顺序相一致。

原子性:更新操作要么成功要么失败,没有第三种结果。

单系统镜像:无论客户端连接到哪一个服务器,客户端将看到相同的 ZooKeeper 视图。

可靠性:一旦一个更新操作被应用,那么在客户端再次更新它之前,它的值将不会改变。这个保证将会产生下面两种结果:

1 .如果客户端成功地获得了正确的返回代码,那么说明更新已经成功。如果不能够获得返回代码(由于通信错误、超时等等),那么客户端将不知道更新操作是否生效。

2 .当从故障恢复的时候,任何客户端能够看到的执行成功的更新操作将不会被回滚。

实时性:在特定的一段时间内,客户端看到的系统需要被保证是实时的。在此时间段内,任何系统的改变将被客户端看到,或者被客户端侦测到。

给予这些一致性保证, ZooKeeper 更高级功能的设计与实现将会变得非常容易,例如: leader 选举、队列以及可撤销锁等机制的实现。

 

 

8.5 zookeeper-api应用

8.5.1 基本使用

 org.apache.zookeeper.Zookeeper是客户端入口主类,负责建立与server的会话

它提供了表 1 所示几类主要方法  

功能

描述

create

在本地目录树中创建一个节点

delete

删除一个节点

exists

测试本地是否存在目标节点

get/set data

从目标节点上读取 / 写数据

get/set ACL

获取 / 设置目标节点访问控制列表信息

get children

检索一个子节点上的列表

sync

等待要被传送的数据

   1  ZooKeeper API 描述

 

 

8.5.2 demo增删改查

 

public class SimpleDemo {

// 会话超时时间,设置为与系统默认时间一致

private static final int SESSION_TIMEOUT = 30000;

// 创建 ZooKeeper 实例

ZooKeeper zk;

// 创建 Watcher 实例

Watcher wh = new Watcher() {

public void process(org.apache.zookeeper.WatchedEvent event)

{

System.out.println(event.toString());

}

};

// 初始化 ZooKeeper 实例

private void createZKInstance() throws IOException

{

zk = new ZooKeeper("weekend01:2181", SimpleDemo.SESSION_TIMEOUT, this.wh);

}

private void ZKOperations() throws IOException, InterruptedException, KeeperException

{

System.out.println("/n1. 创建 ZooKeeper 节点 (znode : zoo2, 数据: myData2 ,权限: OPEN_ACL_UNSAFE ,节点类型: Persistent");

zk.create("/zoo2", "myData2".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

System.out.println("/n2. 查看是否创建成功: ");

System.out.println(new String(zk.getData("/zoo2", false, null)));

System.out.println("/n3. 修改节点数据 ");

zk.setData("/zoo2", "shenlan211314".getBytes(), -1);

System.out.println("/n4. 查看是否修改成功: ");

System.out.println(new String(zk.getData("/zoo2", false, null)));

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

zk.delete("/zoo2", -1);

System.out.println("/n6. 查看节点是否被删除: ");

System.out.println(" 节点状态: [" + zk.exists("/zoo2", false) + "]");

}

private void ZKClose() throws InterruptedException

{

zk.close();

}

public static void main(String[] args) throws IOException, InterruptedException, KeeperException {

SimpleDemo dm = new SimpleDemo();

dm.createZKInstance();

dm.ZKOperations();

dm.ZKClose();

}

}

 

 

 

8.6  zookeeper应用案例(分布式应用HA||分布式锁)

(1)实现分布式应用的主节点HA及客户端动态更新主节点状态

A、客户端实现

public class AppClient {

private String groupNode = "sgroup";

private ZooKeeper zk;

private Stat stat = new Stat();

private volatile List<String> serverList;

 

/**

 * 连接zookeeper

 */

public void connectZookeeper() throws Exception {

zk

= new ZooKeeper("localhost:4180,localhost:4181,localhost:4182", 5000, new Watcher() {

public void process(WatchedEvent event) {

// 如果发生了"/sgroup"节点下的子节点变化事件, 更新server列表, 并重新注册监听

if (event.getType() == EventType.NodeChildrenChanged

&& ("/" + groupNode).equals(event.getPath())) {

try {

updateServerList();

} catch (Exception e) {

e.printStackTrace();

}

}

}

});

 

updateServerList();

}

 

/**

 * 更新server列表

 */

private void updateServerList() throws Exception {

List<String> newServerList = new ArrayList<String>();

 

// 获取并监听groupNode的子节点变化

// watch参数为true, 表示监听子节点变化事件.

// 每次都需要重新注册监听, 因为一次注册, 只能监听一次事件, 如果还想继续保持监听, 必须重新注册

List<String> subList = zk.getChildren("/" + groupNode, true);

for (String subNode : subList) {

// 获取每个子节点下关联的server地址

byte[] data = zk.getData("/" + groupNode + "/" + subNode, false, stat);

newServerList.add(new String(data, "utf-8"));

}

 

// 替换server列表

serverList = newServerList;

 

System.out.println("server list updated: " + serverList);

}

 

/**

 * client的工作逻辑写在这个方法中

 * 此处不做任何处理, 只让client sleep

 */

public void handle() throws InterruptedException {

Thread.sleep(Long.MAX_VALUE);

}

 

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

AppClient ac = new AppClient();

ac.connectZookeeper();

 

ac.handle();

}

}

 

 

 

B、服务器端实现

public class AppServer {

private String groupNode = "sgroup";

private String subNode = "sub";

 

/**

 * 连接zookeeper

 * @param address server的地址

 */

public void connectZookeeper(String address) throws Exception {

ZooKeeper zk = new ZooKeeper(

"localhost:4180,localhost:4181,localhost:4182",

5000, new Watcher() {

public void process(WatchedEvent event) {

// 不做处理

}

});

// 在"/sgroup"下创建子节点

// 子节点的类型设置为EPHEMERAL_SEQUENTIAL, 表明这是一个临时节点, 且在子节点的名称后面加上一串数字后缀

// 将server的地址数据关联到新创建的子节点上

String createdPath = zk.create("/" + groupNode + "/" + subNode, address.getBytes("utf-8"),

Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

System.out.println("create: " + createdPath);

}

 

/**

 * server的工作逻辑写在这个方法中

 * 此处不做任何处理, 只让server sleep

 */

public void handle() throws InterruptedException {

Thread.sleep(Long.MAX_VALUE);

}

 

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

// 在参数中指定server的地址

if (args.length == 0) {

System.err.println("The first argument must be server address");

System.exit(1);

}

 

AppServer as = new AppServer();

as.connectZookeeper(args[0]);

as.handle();

}

}

 

(2)分布式共享锁的简单实现

  • 多线程方式测试

public class DistributedClient {

    // 超时时间

    private static final int SESSION_TIMEOUT = 5000;

    // zookeeper server列表

    private String hosts = "localhost:4180,localhost:4181,localhost:4182";

    private String groupNode = "locks";

    private String subNode = "sub";

 

    private ZooKeeper zk;

    // 当前client创建的子节点

    private String thisPath;

    // 当前client等待的子节点

    private String waitPath;

 

    private CountDownLatch latch = new CountDownLatch(1);

 

    /**

     * 连接zookeeper

     */

    public void connectZookeeper() throws Exception {

        zk = new ZooKeeper(hosts, SESSION_TIMEOUT, new Watcher() {

            public void process(WatchedEvent event) {

                try {

                    // 连接建立时, 打开latch, 唤醒wait在该latch上的线程

                    if (event.getState() == KeeperState.SyncConnected) {

                        latch.countDown();

                    }

 

                    // 发生了waitPath的删除事件

                    if (event.getType() == EventType.NodeDeleted && event.getPath().equals(waitPath)) {

                        doSomething();

                    }

                } catch (Exception e) {

                    e.printStackTrace();

                }

            }

        });

 

        // 等待连接建立

        latch.await();

 

        // 创建子节点

        thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE,

                CreateMode.EPHEMERAL_SEQUENTIAL);

 

        // wait一小会, 让结果更清晰一些

        Thread.sleep(10);

 

        // 注意, 没有必要监听"/locks"的子节点的变化情况

        List<String> childrenNodes = zk.getChildren("/" + groupNode, false);

 

        // 列表中只有一个子节点, 那肯定就是thisPath, 说明client获得锁

        if (childrenNodes.size() == 1) {

            doSomething();

        } else {

            String thisNode = thisPath.substring(("/" + groupNode + "/").length());

            // 排序

            Collections.sort(childrenNodes);

            int index = childrenNodes.indexOf(thisNode);

            if (index == -1) {

                // never happened

            } else if (index == 0) {

                // inddx == 0, 说明thisNode在列表中最小, 当前client获得锁

                doSomething();

            } else {

                // 获得排名比thisPath前1位的节点

                this.waitPath = "/" + groupNode + "/" + childrenNodes.get(index - 1);

                // 在waitPath上注册监听器, 当waitPath被删除时, zookeeper会回调监听器的process方法

                zk.getData(waitPath, true, new Stat());

            }

        }

    }

 

    private void doSomething() throws Exception {

        try {

            System.out.println("gain lock: " + thisPath);

            Thread.sleep(2000);

            // do something

        } finally {

            System.out.println("finished: " + thisPath);

            // 将thisPath删除, 监听thisPath的client将获得通知

            // 相当于释放锁

            zk.delete(this.thisPath, -1);

        }

    }

 

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

        for (int i = 0; i < 10; i++) {

            new Thread() {

                public void run() {

                    try {

                        DistributedClient dl = new DistributedClient();

                        dl.connectZookeeper();

                    } catch (Exception e) {

                        e.printStackTrace();

                    }

                }

            }.start();

        }

 

        Thread.sleep(Long.MAX_VALUE);

    }

}

 

  • 分布式多节点方式测试

public class DistributedClientMy {

 

 

// 超时时间

private static final int SESSION_TIMEOUT = 5000;

// zookeeper server列表

private String hosts = "spark01:2181,spark02:2181,spark03:2181";

private String groupNode = "locks";

private String subNode = "sub";

private boolean haveLock = false;

 

private ZooKeeper zk;

// 当前client创建的子节点

private volatile String thisPath;

 

/**

 * 连接zookeeper

 */

public void connectZookeeper() throws Exception {

zk = new ZooKeeper("spark01:2181", SESSION_TIMEOUT, new Watcher() {

public void process(WatchedEvent event) {

try {

 

// 子节点发生变化

if (event.getType() == EventType.NodeChildrenChanged && event.getPath().equals("/" + groupNode)) {

// thisPath是否是列表中的最小节点

List<String> childrenNodes = zk.getChildren("/" + groupNode, true);

String thisNode = thisPath.substring(("/" + groupNode + "/").length());

// 排序

Collections.sort(childrenNodes);

if (childrenNodes.indexOf(thisNode) == 0) {

doSomething();

thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE,

CreateMode.EPHEMERAL_SEQUENTIAL);

}

}

} catch (Exception e) {

e.printStackTrace();

}

}

});

 

// 创建子节点

thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE,

CreateMode.EPHEMERAL_SEQUENTIAL);

 

// wait一小会, 让结果更清晰一些

Thread.sleep(new Random().nextInt(1000));

 

// 监听子节点的变化

List<String> childrenNodes = zk.getChildren("/" + groupNode, true);

 

// 列表中只有一个子节点, 那肯定就是thisPath, 说明client获得锁

if (childrenNodes.size() == 1) {

doSomething();

thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE,

CreateMode.EPHEMERAL_SEQUENTIAL);

}

}

 

/**

 * 共享资源的访问逻辑写在这个方法中

 */

private void doSomething() throws Exception {

try {

System.out.println("gain lock: " + thisPath);

Thread.sleep(2000);

// do something

} finally {

System.out.println("finished: " + thisPath);

// 将thisPath删除, 监听thisPath的client将获得通知

// 相当于释放锁

zk.delete(this.thisPath, -1);

}

}

 

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

DistributedClientMy dl = new DistributedClientMy();

dl.connectZookeeper();

Thread.sleep(Long.MAX_VALUE);

}

 

 

}

 

 

动手练习:

流量运营系统中采集集群的动态端口分配

 

 

 

 

 

9. Hadoop-HA

(1)hadoop-ha集群运作机制介绍

所谓HA,即高可用(7*24小时不中断服务)

实现高可用最关键的是消除单点故障

hadoop-ha严格来说应该分成各个组件的HA机制----hdfs/yarn/hbase

 

(2)HDFS的HA机制

通过双namenode消除单点故障

双namenode协调工作的要点:

A、元数据管理方式需要改变:

内存中各自保存一份元数据

Edits日志只能有一份,只有Active状态的namenode节点可以做写操作

两个namenode都可以读取edits

共享的edits放在一个共享存储中管理(qjournal和NFS两个主流实现)

B、需要一个状态管理功能模块

实现了一个zkfailover,常驻在每一个namenode所在的节点

每一个zkfailover负责监控自己所在的namenode节点,利用zk进行状态标识

当需要进行状态切换时,由zkfailover来负责切换

切换时需要防止brain split现象的发生

  1. hadoop-ha配置文件

core-site.xml

<configuration>

<!-- 指定hdfs的nameservice为ns1 -->

<property>

<name>fs.defaultFS</name>

<value>hdfs://ns1/</value>

</property>

<!-- 指定hadoop临时目录 -->

<property>

<name>hadoop.tmp.dir</name>

<value>/home/hadoop/app/hadoop-2.4.1/tmp</value>

</property>

 

<!-- 指定zookeeper地址 -->

<property>

<name>ha.zookeeper.quorum</name>

<value>weekend05:2181,weekend06:2181,weekend07:2181</value>

</property>

</configuration>

 

 

hdfs-site.xml

configuration>

<!--指定hdfs的nameservice为ns1,需要和core-site.xml中的保持一致 -->

<property>

<name>dfs.nameservices</name>

<value>ns1</value>

</property>

<!-- ns1下面有两个NameNode,分别是nn1,nn2 -->

<property>

<name>dfs.ha.namenodes.ns1</name>

<value>nn1,nn2</value>

</property>

<!-- nn1的RPC通信地址 -->

<property>

<name>dfs.namenode.rpc-address.ns1.nn1</name>

<value>weekend01:9000</value>

</property>

<!-- nn1的http通信地址 -->

<property>

<name>dfs.namenode.http-address.ns1.nn1</name>

<value>weekend01:50070</value>

</property>

<!-- nn2的RPC通信地址 -->

<property>

<name>dfs.namenode.rpc-address.ns1.nn2</name>

<value>weekend02:9000</value>

</property>

<!-- nn2的http通信地址 -->

<property>

<name>dfs.namenode.http-address.ns1.nn2</name>

<value>weekend02:50070</value>

</property>

<!-- 指定NameNode的元数据在JournalNode上的存放位置 -->

<property>

<name>dfs.namenode.shared.edits.dir</name>

<value>qjournal://weekend05:8485;weekend06:8485;weekend07:8485/ns1</value>

</property>

<!-- 指定JournalNode在本地磁盘存放数据的位置 -->

<property>

<name>dfs.journalnode.edits.dir</name>

<value>/home/hadoop/app/hadoop-2.4.1/journaldata</value>

</property>

<!-- 开启NameNode失败自动切换 -->

<property>

<name>dfs.ha.automatic-failover.enabled</name>

<value>true</value>

</property>

<!-- 配置失败自动切换实现方式 -->

<property>

<name>dfs.client.failover.proxy.provider.ns1</name>

<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>

</property>

<!-- 配置隔离机制方法,多个机制用换行分割,即每个机制暂用一行-->

<property>

<name>dfs.ha.fencing.methods</name>

<value>

sshfence

shell(/bin/true)

</value>

</property>

<!-- 使用sshfence隔离机制时需要ssh免登陆 -->

<property>

<name>dfs.ha.fencing.ssh.private-key-files</name>

<value>/home/hadoop/.ssh/id_rsa</value>

</property>

<!-- 配置sshfence隔离机制超时时间 -->

<property>

<name>dfs.ha.fencing.ssh.connect-timeout</name>

<value>30000</value>

</property>

/configuration>

 

 

(4)集群搭建、测试

一些运维知识补充:

Datanode动态上下线

Namenode状态切换管理

数据块的balance

 

  1. HA下hdfs-api变化

客户端需要nameservice的配置信息

其他不变

 

9.2 Federation机制、配置

扩大namenode容量

 

 

9.3 CDH介绍,演示

动手练习(ha集群搭建及测试)

 

10. Hbase基础

10.1 hbase数据库介绍

hbase是bigtable的开源山寨版本。是建立的hdfs之上,提供高可靠性、高性能、列存储、可伸缩、实时读写的数据库系统。

它介于nosql和RDBMS之间,仅能通过主键(row key)和主键的range来检索数据,仅支持单行事务(可通过hive支持来实现多表join等复杂操作)。主要用来存储非结构化和半结构化的松散数据。

与hadoop一样,Hbase目标主要依靠横向扩展,通过不断增加廉价的商用服务器,来增加计算和存储能力。

 

hbase表结构

HBase中的表一般有这样的特点:

1 大:一个表可以有上亿行,上百万列

2 面向列:面向列(族)的存储和权限控制,列(族)独立检索。

3 稀疏:对于为空(null)的列,并不占用存储空间,因此,表可以设计的非常稀疏。

二、 逻辑视图

 

HBase以表的形式存储数据。表有行和列组成。列划分为若干个列族(row family)

 

Row Key

与nosql数据库们一样,row key是用来检索记录的主键。访问hbase table中的行,只有三种方式:

1 通过单个row key访问

2 通过row key的range

3 全表扫描

Row key行键 (Row key)可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),在hbase内部,row key保存为字节数组。

存储时,数据按照Row key的字典序(byte order)排序存储。设计key时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)

注意:

字典序对int排序的结果是

1,10,100,11,12,13,14,15,16,17,18,19,2,20,21,…,9,91,92,93,94,95,96,97,98,99。要保持整形的自然序,行键必须用0作左填充。

行的一次读写是原子操作 (不论一次读写多少列)。这个设计决策能够使用户很容易的理解程序在对同一个行进行并发更新操作时的行为。

 

列族

hbase表中的每个列,都归属与某个列族。列族是表的chema的一部分(而列不是),必须在使用表之前定义。列名都以列族作为前缀。例如courses:history , courses:math 都属于 courses 这个列族。

访问控制、磁盘和内存的使用统计都是在列族层面进行的。实际应用中,列族上的控制权限能 帮助我们管理不同类型的应用:我们允许一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据(甚至可能因 为隐私的原因不能浏览所有数据)。

 

时间戳

HBase中通过row和columns确定的为一个存贮单元称为cell。每个 cell都保存着同一份数据的多个版本。版本通过时间戳来索引。时间戳的类型是 64位整型。时间戳可以由hbase(在数据写入时自动 )赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个 cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。

为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,hbase提供了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段时间内的版本(比如最近七天)。用户可以针对每个列族进行设置。

 

Cell

由{row key, column( =<family> + <label>), version} 唯一确定的单元。cell中的数据是没有类型的,全部是字节码形式存贮。

 

 

10.2 hbase集群结构

 

 

 

 

 

10.3 hbase集群搭建

----先部署一个zookeeper集群

(1)上传hbase安装包

(2)解压

(3)配置hbase集群,要修改3个文件

注意:要把hadoop的hdfs-site.xml和core-site.xml 放到hbase/conf下

 

(3.1)修改hbase-env.sh

export JAVA_HOME=/usr/java/jdk1.7.0_55

//告诉hbase使用外部的zk

export HBASE_MANAGES_ZK=false

 

(3.2)修改 hbase-site.xml

<configuration>

<!-- 指定hbase在HDFS上存储的路径 -->

        <property>

                <name>hbase.rootdir</name>

                <value>hdfs://ns1/hbase</value>

        </property>

<!-- 指定hbase是分布式的 -->

        <property>

                <name>hbase.cluster.distributed</name>

                <value>true</value>

        </property>

<!-- 指定zk的地址,多个用“,”分割 -->

        <property>

                <name>hbase.zookeeper.quorum</name>

                <value>weekend05:2181,weekend06:2181,weekend07:2181</value>

        </property>

</configuration>

 

(3.3)修改 regionservers

weekend03

weekend04

weekend05

weekend06

 

(3.4) 拷贝hbase到其他节点

scp -r /weekend/hbase-0.96.2-hadoop2/ weekend02:/weekend/

scp -r /weekend/hbase-0.96.2-hadoop2/ weekend03:/weekend/

scp -r /weekend/hbase-0.96.2-hadoop2/ weekend04:/weekend/

scp -r /weekend/hbase-0.96.2-hadoop2/ weekend05:/weekend/

scp -r /weekend/hbase-0.96.2-hadoop2/ weekend06:/weekend/

(4) 将配置好的HBase拷贝到每一个节点并同步时间。

 

(5) 启动所有的hbase进程

首先启动zk集群

./zkServer.sh start

启动hbase集群

start-dfs.sh

启动hbase,在主节点上运行:

start-hbase.sh

(6) 通过浏览器访问hbase管理页面

192.168.1.201:60010

(7) 为保证集群的可靠性,要启动多个HMaster

hbase-daemon.sh start master

 

 

10.4 命令行演示

10.4.1 基本shell命令

进入hbase命令行

./hbase shell

 

显示hbase中的表

list

 

创建user表,包含info、data两个列族

create 'user', 'info1', 'data1'

create 'user', {NAME => 'info', VERSIONS => '3'}

 

向user表中插入信息,row key为rk0001,列族info中添加name列标示符,值为zhangsan

put 'user', 'rk0001', 'info:name', 'zhangsan'

 

向user表中插入信息,row key为rk0001,列族info中添加gender列标示符,值为female

put 'user', 'rk0001', 'info:gender', 'female'

 

向user表中插入信息,row key为rk0001,列族info中添加age列标示符,值为20

put 'user', 'rk0001', 'info:age', 20

 

向user表中插入信息,row key为rk0001,列族data中添加pic列标示符,值为picture

put 'user', 'rk0001', 'data:pic', 'picture'

 

获取user表中row key为rk0001的所有信息

get 'user', 'rk0001'

 

获取user表中row key为rk0001,info列族的所有信息

get 'user', 'rk0001', 'info'

 

获取user表中row key为rk0001,info列族的name、age列标示符的信息

get 'user', 'rk0001', 'info:name', 'info:age'

 

获取user表中row key为rk0001,info、data列族的信息

get 'user', 'rk0001', 'info', 'data'

get 'user', 'rk0001', {COLUMN => ['info', 'data']}

 

get 'user', 'rk0001', {COLUMN => ['info:name', 'data:pic']}

 

获取user表中row key为rk0001,列族为info,版本号最新5个的信息

get 'user', 'rk0001', {COLUMN => 'info', VERSIONS => 2}

get 'user', 'rk0001', {COLUMN => 'info:name', VERSIONS => 5}

get 'user', 'rk0001', {COLUMN => 'info:name', VERSIONS => 5, TIMERANGE => [1392368783980, 1392380169184]}

 

获取user表中row key为rk0001,cell的值为zhangsan的信息

get 'people', 'rk0001', {FILTER => "ValueFilter(=, 'binary:图片')"}

 

获取user表中row key为rk0001,列标示符中含有a的信息

get 'people', 'rk0001', {FILTER => "(QualifierFilter(=,'substring:a'))"}

 

put 'user', 'rk0002', 'info:name', 'fanbingbing'

put 'user', 'rk0002', 'info:gender', 'female'

put 'user', 'rk0002', 'info:nationality', '中国'

get 'user', 'rk0002', {FILTER => "ValueFilter(=, 'binary:中国')"}

 

 

查询user表中的所有信息

scan 'user'

 

查询user表中列族为info的信息

scan 'user', {COLUMNS => 'info'}

scan 'user', {COLUMNS => 'info', RAW => true, VERSIONS => 5}

scan 'persion', {COLUMNS => 'info', RAW => true, VERSIONS => 3}

查询user表中列族为info和data的信息

scan 'user', {COLUMNS => ['info', 'data']}

scan 'user', {COLUMNS => ['info:name', 'data:pic']}

 

 

查询user表中列族为info、列标示符为name的信息

scan 'user', {COLUMNS => 'info:name'}

 

查询user表中列族为info、列标示符为name的信息,并且版本最新的5个

scan 'user', {COLUMNS => 'info:name', VERSIONS => 5}

 

查询user表中列族为info和data且列标示符中含有a字符的信息

scan 'user', {COLUMNS => ['info', 'data'], FILTER => "(QualifierFilter(=,'substring:a'))"}

 

查询user表中列族为info,rk范围是[rk0001, rk0003)的数据

scan 'people', {COLUMNS => 'info', STARTROW => 'rk0001', ENDROW => 'rk0003'}

 

查询user表中row key以rk字符开头的

scan 'user',{FILTER=>"PrefixFilter('rk')"}

 

查询user表中指定范围的数据

scan 'user', {TIMERANGE => [1392368783980, 1392380169184]}

 

删除数据

删除user表row key为rk0001,列标示符为info:name的数据

delete 'people', 'rk0001', 'info:name'

删除user表row key为rk0001,列标示符为info:name,timestamp为1392383705316的数据

delete 'user', 'rk0001', 'info:name', 1392383705316

 

 

清空user表中的数据

truncate 'people'

 

 

修改表结构

首先停用user表(新版本不用)

disable 'user'

 

添加两个列族f1和f2

alter 'people', NAME => 'f1'

alter 'user', NAME => 'f2'

启用表

enable 'user'

 

 

###disable 'user'(新版本不用)

删除一个列族:

alter 'user', NAME => 'f1', METHOD => 'delete' 或 alter 'user', 'delete' => 'f1'

 

添加列族f1同时删除列族f2

alter 'user', {NAME => 'f1'}, {NAME => 'f2', METHOD => 'delete'}

 

将user表的f1列族版本号改为5

alter 'people', NAME => 'info', VERSIONS => 5

启用表

enable 'user'

 

 

删除表

disable 'user'

drop 'user'

 

 

get 'person', 'rk0001', {FILTER => "ValueFilter(=, 'binary:中国')"}

get 'person', 'rk0001', {FILTER => "(QualifierFilter(=,'substring:a'))"}

scan 'person', {COLUMNS => 'info:name'}

scan 'person', {COLUMNS => ['info', 'data'], FILTER => "(QualifierFilter(=,'substring:a'))"}

scan 'person', {COLUMNS => 'info', STARTROW => 'rk0001', ENDROW => 'rk0003'}

 

scan 'person', {COLUMNS => 'info', STARTROW => '20140201', ENDROW => '20140301'}

scan 'person', {COLUMNS => 'info:name', TIMERANGE => [1395978233636, 1395987769587]}

delete 'person', 'rk0001', 'info:name'

 

alter 'person', NAME => 'ffff'

alter 'person', NAME => 'info', VERSIONS => 10

 

 

get 'user', 'rk0002', {COLUMN => ['info:name', 'data:pic']}

 

10.4.2 建表高级属性

下面几个shell 命令在后续的hbase 操作中可以起到很到的作用,且主要体现在建表的过程中,看下面几个create 属性

1、BLOOMFILTER  默认是NONE 是否使用布隆过虑 使用何种方式

     布隆过滤可以每列族单独启用。使用 HColumnDescriptor.setBloomFilterType(NONE | ROW | ROWCOL) 对列族单独启用布隆。 Default = NONE 没有布隆过滤。对 ROW,行键的哈希在每次插入行时将被添加到布隆。对 ROWCOL,行键 + 列族 + 列族修饰的哈希将在每次插入行时添加到布隆

   使用方法: create 'table',{BLOOMFILTER =>'ROW'}

   启用布隆过滤可以节省必须读磁盘过程,可以有助于改进读取延迟

2、VERSIONS 默认是3 这个参数的意思是数据保留三个 版本,如果我们认为我们的数据没有这么大的必要保留这么多,随时都在更新,而老版本的数据对我们毫无价值,那将此参数设为1 能节约2/3的空间

     使用方法: create 'table',{VERSIONS=>'2'}

3、COMPRESSION 默认值是NONE 即不使用压缩

     这个参数意思是该列族是否采用压缩,采用什么压缩算法

     使用方法: create 'table',{NAME=>'info',COMPRESSION=>'SNAPPY'}

     我建议采用SNAPPY压缩算法,个压缩算法的比较网上比较多,我从网上摘抄一个表格作为参考,具体的snappy 的安装后续会以单独章节进行描述。

     这个表是Google几年前发布的一组测试数据,实际测试Snappy 和下表所列相差无几。

    HBase中,在Snappy发布之前(Google 2011年对外发布Snappy),采用的LZO算法,目标是达到尽可能快的压缩和解压速度,同时减少对CPU的消耗;

    在Snappy发布之后,建议采用Snappy算法(参考《HBase: The Definitive Guide》),具体可以根据实际情况对LZO和Snappy做过更详细的对比测试后再做选择。

                    

Algorithm

% remaining

Encoding

Decoding

GZIP

13.4%

21 MB/s

118 MB/s

LZO

20.5%

135 MB/s

410 MB/s

Zippy/Snappy

22.2%

172 MB/s

409 MB/s

 

  

 

 

 

如果建表之初没有 压缩,后来想要加入压缩算法,怎么办 hbase 有另外的一个命令alter

4、alter

     使用方法:

     如 修改压缩算法      

      disable 'table'

      alter 'table',{NAME=>'info',COMPRESSION=>'snappy'}

      enable 'table'

     删除列族

     disable 'table'

     alter 'table',{NAME=>'info',METHOD=>'delete'}

     enable 'table'

     但是这样修改之后发现表数据还是那么大,并没有发生多大变化。怎么办

     major_compact 'table' 命令之后 才会做实际的操作。

 

5、TTL 默认是 2147483647 即:Integer.MAX_VALUE 值 大概是68年

     这个参数是说明该列族数据的 存活时间 也就是数据的生命周期 单位是s 默写文章写的单位是ms 是错误的。

     这个参数可以根据 具体的需求 对数据设定 存活时间,超过存过时间的数据将在表中不在显示,待下次major compact的时候再彻底删除数据

     为什么在下次major compact的时候删除数据,后面会具体介绍到。

     注意的是TTL设定之后 MIN_VERSIONS=>'0' 这样设置之后,TTL时间戳过期后,将全部彻底删除该family 下所有的数据,如果MIN_VERSIONS 不等于0 那将保留最新

     的MIN_VERSIONS个版本的数据,其它的全部删除,比如MIN_VERSIONS=>'1' 届时将保留一个最新版本的数据,其它版本的数据将不再保存。

6、describe 'table' 这个命令查看了create table 的各项参数 或者是默认值。

7、disable_all 'toplist.*' disable_all 支持正则表达式,并列出当前匹配的表的如下:

      toplist_a_total_1001                                                                                                                                                 

      toplist_a_total_1002                                                                                                                                                

      toplist_a_total_1008                                                                                                                                                

      toplist_a_total_1009                                                                                                                                                

      toplist_a_total_1019                                                                                                                                                

      toplist_a_total_1035

     ...

     Disable the above 25 tables (y/n)? 并给出确认提示

8、drop_all 这个命令和disable_all的使用方式是一样的

9、hbase 表预分区 也就是手动分区

     默认情况下,在创建HBase表的时候会自动创建一个region分区,当导入数据的时候,所有的HBase客户端都向这一个region写数据,直到这个region足够大了才进行切分。一种可以加快批量写入速度的方法是通过预先创建一些空的regions,这样当数据写入HBase时,会按照region分区情况,在集群内做数据的负载均衡。

     使用方法:create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}

     也可以使用 api的方式

     hbase org.apache.hadoop.hbase.util.RegionSplitter test_table HexStringSplit -c 10 -f info  

     参数很容易看懂 test_table  是表名 HexStringSplit 是split 方式 -c 是分10个region -f 是family

     这样就可以将表预先分为10个区,减少数据达到storefile 大小的时候自动分区的时间消耗,并且还有以一个优势,就是合理设计rowkey 能让各个region 的并发请求 平均分配(趋于均匀) 使IO 效率达到最高,但是预分区需要将filesize 设置一个较大的值,设置哪个参数呢 hbase.hregion.max.filesize 这个值默认是10G 也就是说单个region 默认大小是10G

     这个值发生从0.90 到0.92到0.94.3 从 256M--1G--10G 这个根据自己的需求将这个值修改。

     但是如果MapReduce Input类型为TableInputFormat 使用hbase作为输入的时候,就要注意了,每个region一个map,如果数据小于10G 那只会启用一个map 造成很大的资源浪费,这时候可以考虑适当调小 该参数的值,或者采用预分配region 的方式,并将hbase.hregion.max.filesize 设为一个相对比较大的值,不容易达到的值比如1000G,检测如果达到这个值,再手动分配region。

 

 前面说到了 compact 为什么设置了TTL 超过存活时间的数据 就消失了,是如何消失的呢?是删除了吗?通过哪些参数删除的。

后面将要说到 hbase compact

 

 

10.5 hbase代码开发(基本,过滤器查询

10.5.1  基本增删改查java实现

public class HbaseDemo {

 

private Configuration conf = null;

 

@Before

public void init(){

conf = HBaseConfiguration.create();

conf.set("hbase.zookeeper.quorum", "weekend05,weekend06,weekend07");

}

 

@Test

public void testDrop() throws Exception{

HBaseAdmin admin = new HBaseAdmin(conf);

admin.disableTable("account");

admin.deleteTable("account");

admin.close();

}

 

@Test

public void testPut() throws Exception{

HTable table = new HTable(conf, "person_info");

Put p = new Put(Bytes.toBytes("person_rk_bj_zhang_000002"));

p.add("base_info".getBytes(), "name".getBytes(), "zhangwuji".getBytes());

table.put(p);

table.close();

}

 

 

@Test

public void testDel() throws Exception{

HTable table = new HTable(conf, "user");

Delete del = new Delete(Bytes.toBytes("rk0001"));

del.deleteColumn(Bytes.toBytes("data"), Bytes.toBytes("pic"));

table.delete(del);

table.close();

}

 

@Test

public void testGet() throws Exception{

HTable table = new HTable(conf, "person_info");

Get get = new Get(Bytes.toBytes("person_rk_bj_zhang_000001"));

get.setMaxVersions(5);

Result result = table.get(get);

 

List<Cell> cells = result.listCells();

 

for(Cell c:cells){

}

 

//result.getValue(family, qualifier);  可以从result中直接取出一个特定的value

 

//遍历出result中所有的键值对

List<KeyValue> kvs = result.list();

//kv  ---> f1:title:superise....      f1:author:zhangsan    f1:content:asdfasldgkjsldg

for(KeyValue kv : kvs){

String family = new String(kv.getFamily());

System.out.println(family);

String qualifier = new String(kv.getQualifier());

System.out.println(qualifier);

System.out.println(new String(kv.getValue()));

 

}

table.close();

}

 

 

(2)过滤器查询

/**

 * 多种过滤条件的使用方法

 * @throws Exception

 */

@Test

public void testScan() throws Exception{

HTable table = new HTable(conf, "person_info".getBytes());

Scan scan = new Scan(Bytes.toBytes("person_rk_bj_zhang_000001"), Bytes.toBytes("person_rk_bj_zhang_000002"));

 

//前缀过滤器----针对行键

Filter filter = new PrefixFilter(Bytes.toBytes("rk"));

 

//行过滤器  ---针对行键

ByteArrayComparable rowComparator = new BinaryComparator(Bytes.toBytes("person_rk_bj_zhang_000001"));

RowFilter rf = new RowFilter(CompareOp.LESS_OR_EQUAL, rowComparator);

 

/**

         * 假设rowkey格式为:创建日期_发布日期_ID_TITLE

         * 目标:查找  发布日期  为  2014-12-21  的数据

         * sc.textFile("path").flatMap(line=>line.split("\t")).map(x=>(x,1)).reduceByKey(_+_).map((_(2),_(1))).sortByKey().map((_(2),_(1))).saveAsTextFile("")

         *

         *

         */

        rf = new RowFilter(CompareOp.EQUAL , new SubstringComparator("_2014-12-21_"));

 

 

//单值过滤器 1 完整匹配字节数组

new SingleColumnValueFilter("base_info".getBytes(), "name".getBytes(), CompareOp.EQUAL, "zhangsan".getBytes());

//单值过滤器2 匹配正则表达式

ByteArrayComparable comparator = new RegexStringComparator("zhang.");

new SingleColumnValueFilter("info".getBytes(), "NAME".getBytes(), CompareOp.EQUAL, comparator);

 

//单值过滤器3 匹配是否包含子串,大小写不敏感

comparator = new SubstringComparator("wu");

new SingleColumnValueFilter("info".getBytes(), "NAME".getBytes(), CompareOp.EQUAL, comparator);

 

//键值对元数据过滤-----family过滤----字节数组完整匹配

        FamilyFilter ff = new FamilyFilter(

                CompareOp.EQUAL ,

                new BinaryComparator(Bytes.toBytes("base_info"))   //表中不存在inf列族,过滤结果为空

                );

        //键值对元数据过滤-----family过滤----字节数组前缀匹配

        ff = new FamilyFilter(

                CompareOp.EQUAL ,

                new BinaryPrefixComparator(Bytes.toBytes("inf"))   //表中存在以inf打头的列族info,过滤结果为该列族所有行

                );

        

       //键值对元数据过滤-----qualifier过滤----字节数组完整匹配

        

        filter = new QualifierFilter(

                CompareOp.EQUAL ,

                new BinaryComparator(Bytes.toBytes("na"))   //表中不存在na列,过滤结果为空

                );

        filter = new QualifierFilter(

                CompareOp.EQUAL ,

                new BinaryPrefixComparator(Bytes.toBytes("na"))   //表中存在以na打头的列name,过滤结果为所有行的该列数据

         );

 

        //基于列名(即Qualifier)前缀过滤数据的ColumnPrefixFilter

        filter = new ColumnPrefixFilter("na".getBytes());

        

        //基于列名(即Qualifier)多个前缀过滤数据的MultipleColumnPrefixFilter

        byte[][] prefixes = new byte[][] {Bytes.toBytes("na"), Bytes.toBytes("me")};

        filter = new MultipleColumnPrefixFilter(prefixes);

 

        //为查询设置过滤条件

        scan.setFilter(filter);

        

        

        

scan.addFamily(Bytes.toBytes("base_info"));

//一行

// Result result = table.get(get);

//多行的数据

ResultScanner scanner = table.getScanner(scan);

for(Result r : scanner){

/**

for(KeyValue kv : r.list()){

String family = new String(kv.getFamily());

System.out.println(family);

String qualifier = new String(kv.getQualifier());

System.out.println(qualifier);

System.out.println(new String(kv.getValue()));

}

*/

//直接从result中取到某个特定的value

byte[] value = r.getValue(Bytes.toBytes("base_info"), Bytes.toBytes("name"));

System.out.println(new String(value));

}

table.close();

}

 

 

10.6 hbase工作原理

10.6.1  物理存储

 

1 已经提到过,Table中的所有行都按照row key的字典序排列。

2 Table 在行的方向上分割为多个Hregion。

 

3 region按大小分割的,每个表一开始只有一个region,随着数据不断插入表,region不断增大,当增大到一个阀值的时候,Hregion就会等分会两个新的Hregion。当table中的行不断增多,就会有越来越多的Hregion。

 

4 Hregion是Hbase中分布式存储和负载均衡的最小单元。最小单元就表示不同的Hregion可以分布在不同的HRegion server上。但一个Hregion是不会拆分到多个server上的。

 

5 HRegion虽然是分布式存储的最小单元,但并不是存储的最小单元。

事实上,HRegion由一个或者多个Store组成,每个store保存一个columns family。

每个Strore又由一个memStore和0至多个StoreFile组成。如图:

StoreFile以HFile格式保存在HDFS上。

 

HFile的格式为:

 

Trailer部分的格式:

 

HFile分为六个部分:

Data Block 段–保存表中的数据,这部分可以被压缩

Meta Block 段 (可选的)–保存用户自定义的kv对,可以被压缩。

File Info 段–Hfile的元信息,不被压缩,用户也可以在这一部分添加自己的元信息。

Data Block Index 段–Data Block的索引。每条索引的key是被索引的block的第一条记录的key。

Meta Block Index段 (可选的)–Meta Block的索引。

Trailer–这一段是定长的。保存了每一段的偏移量,读取一个HFile时,会首先 读取Trailer,Trailer保存了每个段的起始位置(段的Magic Number用来做安全check),然后,DataBlock Index会被读取到内存中,这样,当检索某个key时,不需要扫描整个HFile,而只需从内存中找到key所在的block,通过一次磁盘io将整个 block读取到内存中,再找到需要的key。DataBlock Index采用LRU机制淘汰。

HFile的Data Block,Meta Block通常采用压缩方式存储,压缩之后可以大大减少网络IO和磁盘IO,随之而来的开销当然是需要花费cpu进行压缩和解压缩。

目标Hfile的压缩支持两种方式:Gzip,Lzo。

 

 

Memstore与storefile

一个region由多个store组成,每个store包含一个列族的所有数据

Store包括位于把内存的memstore和位于硬盘的storefile

写操作先写入memstore,当memstore中的数据量达到某个阈值,Hregionserver启动flashcache进程写入storefile,每次写入形成单独一个storefile

当storefile大小超过一定阈值后,会把当前的region分割成两个,并由Hmaster分配奥相应的region服务器,实现负载均衡

客户端检索数据时,先在memstore找,找不到再找storefile

 

 

HLog(WAL log)

WAL 意为Write ahead log(http://en.wikipedia.org/wiki/Write-ahead_logging),类似mysql中的binlog,用来 做灾难恢复只用,Hlog记录数据的所有变更,一旦数据修改,就可以从log中进行恢复。

每个Region Server维护一个Hlog,而不是每个Region一个。这样不同region(来自不同table)的日志会混在一起,这样做的目的是不断追加单个 文件相对于同时写多个文件而言,可以减少磁盘寻址次数,因此可以提高对table的写性能。带来的麻烦是,如果一台region server下线,为了恢复其上的region,需要将region server上的log进行拆分,然后分发到其它region server上进行恢复。

HLog文件就是一个普通的Hadoop Sequence File,Sequence File 的Key是HLogKey对象,HLogKey中记录了写入数据的归属信息,除了table和region名字外,同时还包括 sequence number和timestamp,timestamp是”写入时间”,sequence number的起始值为0,或者是最近一次存入文件系统中sequence number。HLog Sequece File的Value是HBase的KeyValue对象,即对应HFile中的KeyValue,可参见上文描述。

 

 

10.6.2  系统架构

Client

1 包含访问hbase的接口,client维护着一些cache来加快对hbase的访问,比如regione的位置信息。

 

Zookeeper

1 保证任何时候,集群中只有一个master

2 存贮所有Region的寻址入口。

3 实时监控Region Server的状态,将Region server的上线和下线信息实时通知给Master

4 存储Hbase的schema,包括有哪些table,每个table有哪些column family

 

Master

1 为Region server分配region

2 负责region server的负载均衡

3 发现失效的region server并重新分配其上的region

4 GFS上的垃圾文件回收

5 处理schema更新请求

 

Region Server

1 Region server维护Master分配给它的region,处理对这些region的IO请求

2 Region server负责切分在运行过程中变得过大的region

可以看到,client访问hbase上数据的过程并不需要master参与(寻址访问zookeeper和region server,数据读写访问regione server),master仅仅维护者table和region的元数据信息,负载很低。

 

10.6.3  寻址机制

 

系统如何找到某个row key (或者某个 row key range)所在的region

bigtable 使用三层类似B+树的结构来保存region位置。

第一层是保存zookeeper里面的文件,它持有root region的位置。

第二层root region是.META.表的第一个region其中保存了.META.z表其它region的位置。通过root region,我们就可以访问.META.表的数据。

.META.是第三层,它是一个特殊的表,保存了hbase中所有数据表的region 位置信息。

 

说明:

1 root region永远不会被split,保证了最需要三次跳转,就能定位到任意region 。

2.META.表每行保存一个region的位置信息,row key 采用表名+表的最后一样编码而成。

3 为了加快访问,.META.表的全部region都保存在内存中。

假设,.META.表的一行在内存中大约占用1KB。并且每个region限制为128MB。

那么上面的三层结构可以保存的region数目为:

(128MB/1KB) * (128MB/1KB) = = 2(34)个region

4 client会将查询过的位置信息保存缓存起来,缓存不会主动失效,因此如果client上的缓存全部失效,则需要进行6次网络来回,才能定位到正确的region(其中三次用来发现缓存失效,另外三次用来获取位置信息)。

 

10.6.4 读写过程

读写过程

上文提到,hbase使用MemStore和StoreFile存储对表的更新。

数据在更新时首先写入Log(WAL log)和内存(MemStore)中,MemStore中的数据是排序的,当MemStore累计到一定阈值时,就会创建一个新的MemStore,并 且将老的MemStore添加到flush队列,由单独的线程flush到磁盘上,成为一个StoreFile。于此同时,系统会在zookeeper中 记录一个redo point,表示这个时刻之前的变更已经持久化了。(minor compact)

当系统出现意外时,可能导致内存(MemStore)中的数据丢失,此时使用Log(WAL log)来恢复checkpoint之后的数据。

前面提到过StoreFile是只读的,一旦创建后就不可以再修改。因此Hbase的更 新其实是不断追加的操作。当一个Store中的StoreFile达到一定的阈值后,就会进行一次合并(major compact),将对同一个key的修改合并到一起,形成一个大的StoreFile,当StoreFile的大小达到一定阈值后,又会对 StoreFile进行split,等分为两个StoreFile。

由于对表的更新是不断追加的,处理读请求时,需要访问Store中全部的 StoreFile和MemStore,将他们的按照row key进行合并,由于StoreFile和MemStore都是经过排序的,并且StoreFile带有内存中索引,合并的过程还是比较快。

写请求处理过程

 

1 client向region server提交写请求

2 region server找到目标region

3 region检查数据是否与schema一致

4 如果客户端没有指定版本,则获取当前系统时间作为数据版本

5 将更新写入WAL log

6 将更新写入Memstore

7 判断Memstore的是否需要flush为Store文件。

 

10.6.4  Region管理

(1) region分配

任何时刻,一个region只能分配给一个region server。master记录了当前有哪些可用的region server。以及当前哪些region分配给了哪些region server,哪些region还没有分配。当存在未分配的region,并且有一个region server上有可用空间时,master就给这个region server发送一个装载请求,把region分配给这个region server。region server得到请求后,就开始对此region提供服务。

 

(2) region server上线

master使用zookeeper来跟踪region server状态。当某个region server启动时,会首先在zookeeper上的server目录下建立代表自己的文件,并获得该文件的独占锁。由于master订阅了server 目录上的变更消息,当server目录下的文件出现新增或删除操作时,master可以得到来自zookeeper的实时通知。因此一旦region server上线,master能马上得到消息。

 

  1. region server下线

当region server下线时,它和zookeeper的会话断开,zookeeper而自动释放代表这台server的文件上的独占锁。而master不断轮询 server目录下文件的锁状态。如果master发现某个region server丢失了它自己的独占锁,(或者master连续几次和region server通信都无法成功),master就是尝试去获取代表这个region server的读写锁,一旦获取成功,就可以确定:

1 region server和zookeeper之间的网络断开了。

2 region server挂了。

的其中一种情况发生了,无论哪种情况,region server都无法继续为它的region提供服务了,此时master会删除server目录下代表这台region server的文件,并将这台region server的region分配给其它还活着的同志。

如果网络短暂出现问题导致region server丢失了它的锁,那么region server重新连接到zookeeper之后,只要代表它的文件还在,它就会不断尝试获取这个文件上的锁,一旦获取到了,就可以继续提供服务。

 

 

10.6.5  Master工作机制

master上线

master启动进行以下步骤:

1 从zookeeper上获取唯一一个代码master的锁,用来阻止其它master成为master。

2 扫描zookeeper上的server目录,获得当前可用的region server列表。

3 和2中的每个region server通信,获得当前已分配的region和region server的对应关系。

4 扫描.META.region的集合,计算得到当前还未分配的region,将他们放入待分配region列表。

 

master下线

由于master只维护表和region的元数据,而不参与表数据IO的过 程,master下线仅导致所有元数据的修改被冻结(无法创建删除表,无法修改表的schema,无法进行region的负载均衡,无法处理region 上下线,无法进行region的合并,唯一例外的是region的split可以正常进行,因为只有region server参与),表的数据读写还可以正常进行。因此master下线短时间内对整个hbase集群没有影响。从上线过程可以看到,master保存的 信息全是可以冗余信息(都可以从系统其它地方收集到或者计算出来),因此,一般hbase集群中总是有一个master在提供服务,还有一个以上 的’master’在等待时机抢占它的位置。

 

 

 

动手练习(增删改查)

 

11.  Hbase高级应用

11.1 hbase应用案例看行键设计

表结构设计

列族高级配置--数据块/缓存、布隆过滤器、生存时间、压缩

Hbase行键设计

 

11.2 Hbase和mapreduce结合

11.2.1 从Hbase中读取数据写入hdfs

/**

public abstract class TableMapper<KEYOUT, VALUEOUT>

extends Mapper<ImmutableBytesWritable, Result, KEYOUT, VALUEOUT> {

}

 * @author duanhaitao@itcast.cn

 *

 */

public class HbaseReader {

 

public static String flow_fields_import = "flow_fields_import";

static class HdfsSinkMapper extends TableMapper<Text, NullWritable>{

 

@Override

protected void map(ImmutableBytesWritable key, Result value, Context context) throws IOException, InterruptedException {

 

byte[] bytes = key.copyBytes();

String phone = new String(bytes);

byte[] urlbytes = value.getValue("f1".getBytes(), "url".getBytes());

String url = new String(urlbytes);

context.write(new Text(phone + "\t" + url), NullWritable.get());

 

}

 

}

 

static class HdfsSinkReducer extends Reducer<Text, NullWritable, Text, NullWritable>{

 

@Override

protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {

 

context.write(key, NullWritable.get());

}

}

 

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

Configuration conf = HBaseConfiguration.create();

conf.set("hbase.zookeeper.quorum", "spark01");

 

Job job = Job.getInstance(conf);

 

job.setJarByClass(HbaseReader.class);

 

// job.setMapperClass(HdfsSinkMapper.class);

Scan scan = new Scan();

TableMapReduceUtil.initTableMapperJob(flow_fields_import, scan, HdfsSinkMapper.class, Text.class, NullWritable.class, job);

job.setReducerClass(HdfsSinkReducer.class);

 

FileOutputFormat.setOutputPath(job, new Path("c:/hbasetest/output"));

 

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(NullWritable.class);

 

job.waitForCompletion(true);

 

 

}

 

}

 

 

 

 

 

 

11.2.2 从Hbase中读取数据写入hdfs

/**

public abstract class TableReducer<KEYIN, VALUEIN, KEYOUT>

extends Reducer<KEYIN, VALUEIN, KEYOUT, Writable> {

}

 * @author duanhaitao@itcast.cn

 *

 */

public class HbaseSinker {

 

public static String flow_fields_import = "flow_fields_import";

static class HbaseSinkMrMapper extends Mapper<LongWritable, Text, FlowBean, NullWritable>{

@Override

protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

 

String line = value.toString();

String[] fields = line.split("\t");

String phone = fields[0];

String url = fields[1];

 

FlowBean bean = new FlowBean(phone,url);

 

context.write(bean, NullWritable.get());

}

}

 

static class HbaseSinkMrReducer extends TableReducer<FlowBean, NullWritable, ImmutableBytesWritable>{

 

@Override

protected void reduce(FlowBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {

 

Put put = new Put(key.getPhone().getBytes());

put.add("f1".getBytes(), "url".getBytes(), key.getUrl().getBytes());

 

context.write(new ImmutableBytesWritable(key.getPhone().getBytes()), put);

 

}

 

}

 

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

Configuration conf = HBaseConfiguration.create();

conf.set("hbase.zookeeper.quorum", "spark01");

 

HBaseAdmin hBaseAdmin = new HBaseAdmin(conf);

 

boolean tableExists = hBaseAdmin.tableExists(flow_fields_import);

if(tableExists){

hBaseAdmin.disableTable(flow_fields_import);

hBaseAdmin.deleteTable(flow_fields_import);

}

HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(flow_fields_import));

HColumnDescriptor hColumnDescriptor = new HColumnDescriptor ("f1".getBytes());

desc.addFamily(hColumnDescriptor);

 

hBaseAdmin.createTable(desc);

 

 

Job job = Job.getInstance(conf);

 

job.setJarByClass(HbaseSinker.class);

 

job.setMapperClass(HbaseSinkMrMapper.class);

TableMapReduceUtil.initTableReducerJob(flow_fields_import, HbaseSinkMrReducer.class, job);

 

FileInputFormat.setInputPaths(job, new Path("c:/hbasetest/data"));

 

job.setMapOutputKeyClass(FlowBean.class);

job.setMapOutputValueClass(NullWritable.class);

 

job.setOutputKeyClass(ImmutableBytesWritable.class);

job.setOutputValueClass(Mutation.class);

 

job.waitForCompletion(true);

 

 

}

 

}

 

11.3 hbase高级编程

11.3.1 协处理器

coprocessor

endpoint)

 

11.3.2 二级索引demo

 

12.  Hive基础

12.1 hive引入

12.2 hive技术架构

12.3 hive原理----元数据、数据存储、核心机制

12.4 Hive的安装部署

----准备安装包

----默认配置和mysql meta store配置

 

<configuration>

<property>

<name>javax.jdo.option.ConnectionURL</name>

<value>jdbc:mysql://weekend01:3306/hive?createDatabaseIfNotExist=true</value>

</property>

<property>

<name>javax.jdo.option.ConnectionDriverName</name>

<value>com.mysql.jdbc.Driver</value>

</property>

<property>

<name>javax.jdo.option.ConnectionUserName</name>

<value>root</value>

</property>

<property>

<name>javax.jdo.option.ConnectionPassword</name>

<value>root</value>

</property>

</configuration>

 

 

12.5 Hive使用方式

----shell/hiveserver/beeline/jdbc/hive -e -S

 Hiveserver2

Beeline客户端

JDBC配置

12.6 hql基本语法

12.6.1 基本hql语法

set hive.cli.print.header=true;

 

CREATE TABLE page_view(viewTime INT, userid BIGINT,

     page_url STRING, referrer_url STRING,

     ip STRING COMMENT 'IP Address of the User')

 COMMENT 'This is the page view table'

 PARTITIONED BY(dt STRING, country STRING)

 ROW FORMAT DELIMITED

   FIELDS TERMINATED BY '\001'

STORED AS SEQUENCEFILE;

 

//sequencefile

create table tab_ip_seq(id int,name string,ip string,country string)

    row format delimited

    fields terminated by ','

    stored as sequencefile;

    

//使用select语句来批量插入数据

insert overwrite table tab_ip_seq select * from tab_ext;

 

 

//create & load

create table tab_ip(id int,name string,ip string,country string)

    row format delimited

    fields terminated by ','

    stored as textfile;

    

//从本地导入数据到hive的表中(实质就是将文件上传到hdfs中hive管理目录下)

load data local inpath '/home/hadoop/ip.txt' into table tab_ext;

 

//从hdfs上导入数据到hive表中(实质就是将文件从原始目录移动到hive管理的目录下)

load data inpath 'hdfs://ns1/aa/bb/data.log' into table tab_user;

 

 

//external外部表

CREATE EXTERNAL TABLE tab_ip_ext(id int, name string,

     ip STRING,

     country STRING)

 ROW FORMAT DELIMITED FIELDS TERMINATED BY ','

 STORED AS TEXTFILE

 LOCATION '/external/user';

 

 

// CTAS  根据select语句建表结构

CREATE TABLE tab_ip_ctas

   AS

SELECT id new_id, name new_name, ip new_ip,country new_country

FROM tab_ip_ext

SORT BY new_id;

 

 

//CLUSTER <--相对高级一点,你可以放在有精力的时候才去学习>

create table tab_ip_cluster(id int,name string,ip string,country string)

clustered by(id) into 3 buckets;

 

load data local inpath '/home/hadoop/ip.txt' overwrite into table tab_ip_cluster;

set hive.enforce.bucketing=true;

insert into table tab_ip_cluster select * from tab_ip;

 

select * from tab_ip_cluster tablesample(bucket 2 out of 3 on id);

 

//PARTITION  分区表

create table tab_ip_part(id int,name string,ip string,country string)

    partitioned by (year string)

    row format delimited fields terminated by ',';

    

load data local inpath '/home/hadoop/data.log' overwrite into table tab_ip_part

     partition(year='1990');

    

    

load data local inpath '/home/hadoop/data2.log' overwrite into table tab_ip_part

     partition(year='2000');

 

select * from tab_ip_part;

 

select * from tab_ip_part  where part_flag='part2';

select count(*) from tab_ip_part  where part_flag='part2';

 

 

alter table tab_ip change id id_alter string;

ALTER TABLE tab_cts ADD PARTITION (partCol = 'dt') location '/external/hive/dt';

 

show partitions tab_ip_part;

 

//insert from select   通过select语句批量插入数据到别的表

create table tab_ip_like like tab_ip;

insert overwrite table tab_ip_like

    select * from tab_ip;

   

//write to hdfs  将结果写入到hdfs的文件中

insert overwrite local directory '/home/hadoop/hivetemp/test.txt' select * from tab_ip_part where part_flag='part1';    

insert overwrite directory '/hiveout.txt' select * from tab_ip_part where part_flag='part1';

 

//cli shell  通过shell执行hive的hql语句

hive -S -e 'select country,count(*) from tab_ext' > /home/hadoop/hivetemp/e.txt  

 

select * from tab_ext sort by id desc limit 5;

 

select a.ip,b.book from tab_ext a join tab_ip_book b on(a.name=b.name);

 

 

//array

create table tab_array(a array<int>,b array<string>)

row format delimited

fields terminated by '\t'

collection items terminated by ',';

 

select a[0] from tab_array;

select * from tab_array where array_contains(b,'word');

insert into table tab_array select array(0),array(name,ip) from tab_ext t;

 

//map

create table tab_map(name string,info map<string,string>)

row format delimited

fields terminated by '\t'

collection items terminated by ','

map keys terminated by ':';

 

load data local inpath '/home/hadoop/hivetemp/tab_map.txt' overwrite into table tab_map;

insert into table tab_map select name,map('name',name,'ip',ip) from tab_ext;

 

//struct

create table tab_struct(name string,info struct<age:int,tel:string,addr:string>)

row format delimited

fields terminated by '\t'

collection items terminated by ','

 

load data local inpath '/home/hadoop/hivetemp/tab_st.txt' overwrite into table tab_struct;

insert into table tab_struct select name,named_struct('age',id,'tel',name,'addr',country) from tab_ext;

 

 

 

//UDF

select if(id=1,first,no-first),name from tab_ext;

 

hive>add jar /home/hadoop/myudf.jar;

hive>CREATE TEMPORARY FUNCTION fanyi AS 'cn.itcast.hive.Fanyi';

select id,name,ip,fanyi(country) from tab_ip_ext;

 

 

 

12.6.2  hql查询进阶

MapReduce脚本

连接(join)

内连接(inner join)

外连接(outer join)

半连接(semi join)

Map连接(map join)

子查询(sub query)

视图(view)

通过Hive提供的order by子句可以让最终的输出结果整体有序。但是因为Hive是基于Hadoop之上的,要生成这种整体有序的结果,就必须强迫Hadoop只利用一个Reduce来完成处理。这种方式的副作用就是回降低效率。

如果你不需要最终结果整体有序,你就可以使用sort by子句来进行排序。这种排序操作只保证每个Reduce的输出是有序的。如果你希望某些特定行被同一个Reduce处理,则你可以使用distribute子句来完成。比如:

表student(classNo,stuNo,score)数据如下:

C01  N0101      82

C01  N0102      59

C02  N0201      81

C01  N0103      65

C03  N0302      92

C02  N0202      82

C02  N0203      79

C03  N0301      56

C03  N0306      72

 

我们希望按照成绩由低到高输出每个班级的成绩信息。执行以下语句:

Select classNo,stuNo,score from student distribute byclassNo sort by score;

输出结果为:

C02  N0203      79

C02  N0201      81

C02  N0202      82

C03  N0301      56

C03  N0306      72

C03  N0302      92

C01  N0102      59

C01  N0103      65

C01  N0101      82

我们可以看到每一个班级里所有的学生成绩是有序的。因为同一个classNo的记录会被分发到一个单独的reduce处理,而同时sort by保证了每一个reduce的输出是有序的。

注意:

为了测试上例中的distribute by的效果,你应该首先设置足够多的reduce。比如上例中有3个不同的classNo,则我们需要设置reduce个数至少为3或更多。如果设置的reduce个数少于3,将会导致多个不同的classNo被分发到同一个reduce,从而不能产生你所期望的输出。设置命令如下:

set mapred.reduce.tasks = 3;

MapReduce脚本

 

如果我们需要在查询语句中调用外部脚本,比如Python,则我们可以使用transform,map,reduce等子句。

比如,我们希望过滤掉所有不及格的学生记录,只输出及格学生的成绩信息。

新建一个Python脚本文件score_pass.py,内容如下:

#! /usr/bin/env python

import sys

for line in sys.stdin:

         (classNo,stuNo,score)= line.strip().split('\t')  

         ifint(score) >= 60:

                   print"%s\t%s\t%s" %(classNo,stuNo,score)

执行以下语句

add file /home/user/score_pass.py;

select transform(classNo,stuNo,score) using'score_pass.py' as classNo,stuNo,score from student;

输出结果为:

C01  N0101      82

C02  N0201      81

C01  N0103      65

C03  N0302      92

C02  N0202      82

C02  N0203      79

C03  N0306      72

注意:

1) 以上Python脚本中,分隔符只能是制表符(\t)。同样输出的分隔符也必须为制表符。这个是有hive自身决定的,不能更改,不要尝试使用其他分隔符,否则会报错。同时需要调用strip函数,以去除掉行尾的换行符。(或者直接使用不带参数的line.split()代替。

2) 使用脚本前,先使用add file语句注册脚本文件,以便hive将其分发到Hadoop集群。

3) Transfom传递数据到Python脚本,as语句指定输出的列。

连接(join)

 

直接编程使用Hadoop的MapReduce是一件比较费时的事情。Hive则大大简化了这个操作。

内连接(inner join)

 

和SQL的内连相似。执行以下语句查询每个学生的编号和教师名:

Select a.stuNo,b.teacherName from student a join teacherb on a.classNo = b.classNo;

输出结果如下:

N0203      Sun

N0202      Sun

N0201      Sun

N0306      Wang

N0301      Wang

N0302      Wang

N0103      Zhang

N0102      Zhang

N0101      Zhang

注意:

数据文件内容请参照上一篇文章。

不要使用select xx from aa,bb where aa.f=bb.f这样的语法,hive不支持这种写法。

如果需要查看hive的执行计划,你可以在语句前加上explain,比如:

explain Select a.stuNo,b.teacherName from student a jointeacher b on a.classNo = b.classNo;

外连接(outer join)

 

和传统SQL类似,Hive提供了left outer join,right outer join,full out join。

半连接(semi join)

 

Hive不提供in子查询。此时你可以用leftsemi join实现同样的功能。

执行以下语句:

Select * from teacher left semi join student onstudent.classNo = teacher.classNo;

输出结果如下:

C02  Sun

C03  Wang

C01  Zhang

可以看出,C04 Dong没有出现在查询结果中,因为C04在表student中不存在。

注意:

右表(student)中的字段只能出现在on子句中,不能出现在其他地方,比如不能出现在select子句中。

Map连接(map join)

 

当一个表非常小,足以直接装载到内存中去时,可以使用map连接以提高效率,比如:

Select /*+mapjoin(teacher) */ a.stuNo,b.teacherNamefrom student a join teacher b on a.classNo = b.classNo;

以上红色标记部分采用了C的注释风格。

当连接时用到不等值判断时,也比较适合Map连接。具体原因需要深入了解Hive和MapReduce的工作原理。

子查询(sub query)

 

运行以下语句将返回所有班级平均分的最高记录。

Select max(avgScore) as maScore

from

(Select classNo,avg(score) as avgScore from student group byclassNo) a;

输出结果:

80.66666666666667

以上语句中红色部分为一个子查询,且别名为a。返回的子查询结果和一个表类似,可以被继续查询。

视图(view)

 

和传统数据库中的视图类似,Hive的视图只是一个定义,视图数据并不会存储到文件系统中。同样,视图是只读的。

运行以下两个命令:

Create view avg_score as

Select classNo,avg(score) as avgScore from student groupby classNo;

Select max(avgScore) as maScore

From avg_score;

可以看到输出结果和上例中的结果是一样的。

 

12.7 hive数据类型

---基本类型

---复合类型

 

 

 

动手练习(hql查询及udf编写)

 

13.  Hive高级应用

13.1  Hive常用函数

----参见youdao笔记《hive 优化总结》函数部分

----造数据做例子

13.2  自定义函数

自定义函数的实现步骤

hive自定义函数UDF示例

hive自定义函数UDAF示例

 

13.3  hive高级操作

分区表/桶表应用,skew,map-join

行列转换

13.4  hive优化

hive优化思想

Explain的使用

经典案例(distinct count)

 

13.5  hive对数据倾斜的优化

13.5.1 数据倾斜的原因

1.1操作:

关键词 情形 后果

Join 其中一个表较小,

但是key集中 分发到某一个或几个Reduce上的数据远高于平均值

大表与大表,但是分桶的判断字段0值或空值过多 这些空值都由一个reduce处理,灰常慢

group by group by 维度过小,

某值的数量过多 处理某值的reduce灰常耗时

Count Distinct 某特殊值过多 处理此特殊值的reduce耗时

1.2原因:

1)、key分布不均匀

2)、业务数据本身的特性

3)、建表时考虑不周

4)、某些SQL语句本身就有数据倾斜

 

1.3表现:

任务进度长时间维持在99%(或100%),查看任务监控页面,发现只有少量(1个或几个)reduce子任务未完成。因为其处理的数据量和其他reduce差异过大。

单一reduce的记录数与平均记录数差异过大,通常可能达到3倍甚至更多。 最长时长远大于平均时长。

 

13.5.2 数据倾斜的解决方案

2.1参数调节:

hive.map.aggr=true

Map 端部分聚合,相当于Combiner

hive.groupby.skewindata=true

有数据倾斜的时候进行负载均衡,当选项设定为 true,生成的查询计划会有两个 MR Job。第一个 MR Job 中,Map 的输出结果集合会随机分布到 Reduce 中,每个 Reduce 做部分聚合操作,并输出结果,这样处理的结果是相同的 Group By Key 有可能被分发到不同的 Reduce 中,从而达到负载均衡的目的;第二个 MR Job 再根据预处理的数据结果按照 Group By Key 分布到 Reduce 中(这个过程可以保证相同的 Group By Key 被分布到同一个 Reduce 中),最后完成最终的聚合操作。

 

2.2 SQL语句调节:

如何Join:

关于驱动表的选取,选用join key分布最均匀的表作为驱动表

做好列裁剪和filter操作,以达到两表做join的时候,数据量相对变小的效果。

大小表Join:

使用map join让小的维度表(1000条以下的记录条数) 先进内存。在map端完成reduce.

大表Join大表:

把空值的key变成一个字符串加上随机数,把倾斜的数据分到不同的reduce上,由于null值关联不上,处理后并不影响最终结果。

count distinct大量相同特殊值

count distinct时,将值为空的情况单独处理,如果是计算count distinct,可以不用处理,直接过滤,在最后结果中加1。如果还有其他计算,需要进行group by,可以先将值为空的记录单独处理,再和其他计算结果进行union。

group by维度过小:

采用sum() group by的方式来替换count(distinct)完成计算。

特殊情况特殊处理:

在业务逻辑优化效果的不大情况下,有些时候是可以将倾斜的数据单独拿出来处理。最后union回去。

 

13.5.3 典型的业务场景

3.1空值产生的数据倾斜

场景:如日志中,常会有信息丢失的问题,比如日志中的 user_id,如果取其中的 user_id 和 用户表中的user_id 关联,会碰到数据倾斜的问题。

解决方法1: user_id为空的不参与关联(红色字体为修改后)

 

select * from log a  join users b  on a.user_id is not null  and a.user_id = b.user_idunion allselect * from log a  where a.user_id is null;

 

 

解决方法2 :赋与空值分新的key值

select *  from log a  left outer join users b  on case when a.user_id is null then concat(‘hive’,rand() ) else a.user_id end = b.user_id;

 

结论:方法2比方法1效率更好,不但io少了,而且作业数也少了。解决方法1中 log读取两次,jobs是2。解决方法2 job数是1 。这个优化适合无效 id (比如 -99 , ’’, null 等) 产生的倾斜问题。把空值的 key 变成一个字符串加上随机数,就能把倾斜的数据分到不同的reduce上 ,解决数据倾斜问题。

 

3.2不同数据类型关联产生数据倾斜

场景:用户表中user_id字段为int,log表中user_id字段既有string类型也有int类型。当按照user_id进行两个表的Join操作时,默认的Hash操作会按int型的id来进行分配,这样会导致所有string类型id的记录都分配到一个Reducer中。

解决方法:把数字类型转换成字符串类型

select * from users a  left outer join logs b  on a.usr_id = cast(b.user_id as string)

 

3.3小表不小不大,怎么用 map join 解决倾斜问题

使用 map join 解决小表(记录数少)关联大表的数据倾斜问题,这个方法使用的频率非常高,但如果小表很大,大到map join会出现bug或异常,这时就需要特别的处理。 以下例子:

select * from log a  left outer join users b  on a.user_id = b.user_id;

 

users 表有 600w+ 的记录,把 users 分发到所有的 map 上也是个不小的开销,而且 map join 不支持这么大的小表。如果用普通的 join,又会碰到数据倾斜的问题。

解决方法:

 

select /*+mapjoin(x)*/* from log a  left outer join (    select  /*+mapjoin(c)*/d.*      from ( select distinct user_id from log ) c      join users d      on c.user_id = d.user_id    ) x  on a.user_id = b.user_id;

 

假如,log里user_id有上百万个,这就又回到原来map join问题。所幸,每日的会员uv不会太多,有交易的会员不会太多,有点击的会员不会太多,有佣金的会员不会太多等等。所以这个方法能解决很多场景下的数据倾斜问题。

13.5.4 总结

使map的输出数据更均匀的分布到reduce中去,是我们的最终目标。由于Hash算法的局限性,按key Hash会或多或少的造成数据倾斜。大量经验表明数据倾斜的原因是人为的建表疏忽或业务逻辑可以规避的。在此给出较为通用的步骤:

1、采样log表,哪些user_id比较倾斜,得到一个结果表tmp1。由于对计算框架来说,所有的数据过来,他都是不知道数据分布情况的,所以采样是并不可少的。

2、数据的分布符合社会学统计规则,贫富不均。倾斜的key不会太多,就像一个社会的富人不多,奇特的人不多一样。所以tmp1记录数会很少。把tmp1和users做map join生成tmp2,把tmp2读到distribute file cache。这是一个map过程。

3、map读入users和log,假如记录来自log,则检查user_id是否在tmp2里,如果是,输出到本地文件a,否则生成<user_id,value>的key,value对,假如记录来自member,生成<user_id,value>的key,value对,进入reduce阶段。

4、最终把a文件,把Stage3 reduce阶段输出的文件合并起写到hdfs。

 

如果确认业务需要这样倾斜的逻辑,考虑以下的优化方案:

1、对于join,在判断小表不大于1G的情况下,使用map join

2、对于group by或distinct,设定 hive.groupby.skewindata=true

3、尽量使用上述的SQL语句调节进行优化

 

动手练习(spark中订单数据统计模型的sql编写)

14.  数据采集工具

14.1 flume介绍

  一、什么是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。

 

        flume的特点:

flume是一个分布式、可靠、和高可用的海量日志采集、聚合和传输的系统。支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume提供对数据进行简单处理,并写到各种数据接受方(比如文本、HDFS、Hbase等)的能力 。

flume的数据流由事件(Event)贯穿始终。事件是Flume的基本数据单位,它携带日志数据(字节数组形式)并且携带有头信息,这些Event由Agent外部的Source生成,当Source捕获事件后会进行特定的格式化,然后Source会把事件推入(单个或多个)Channel中。你可以把Channel看作是一个缓冲区,它将保存事件直到Sink处理完该事件。Sink负责持久化日志或者把事件推向另一个Source。

 

        flume的可靠性

  当节点出现故障时,日志能够被传送到其他节点上而不会丢失。Flume提供了三种级别的可靠性保障,从强到弱依次分别为:end-to-end(收到数据agent首先将event写到磁盘上,当数据传送成功后,再删除;如果数据发送失败,可以重新发送。),Store on failure(这也是scribe采用的策略,当数据接收方crash时,将数据写到本地,待恢复后,继续发送),Besteffort(数据发送到接收方后,不会进行确认)。

 

        flume的可恢复性:

  还是靠Channel。推荐使用FileChannel,事件持久化在本地文件系统里(性能较差)。

 

flume的一些核心概念:

Agent        使用JVM 运行Flume。每台机器运行一个agent,但是可以在一个agent中包含多个sources和sinks。

Client        生产数据,运行在一个独立的线程。

Source        从Client收集数据,传递给Channel。

Sink        从Channel收集数据,运行在一个独立线程。

Channel        连接 sources 和 sinks ,这个有点像一个队列。

Events        可以是日志记录、 avro 对象等。

 

 

Flume以agent为最小的独立运行单位。一个agent就是一个JVM。单agent由Source、Sink和Channel三大组件构成,如下图:

 

值得注意的是,Flume提供了大量内置的Source、Channel和Sink类型。不同类型的Source,Channel和Sink可以自由组合。组合方式基于用户设置的配置文件,非常灵活。比如:Channel可以把事件暂存在内存里,也可以持久化到本地硬盘上。Sink可以把日志写入HDFS, HBase,甚至是另外一个Source等等。Flume支持用户建立多级流,也就是说,多个agent可以协同工作,并且支持Fan-in、Fan-out、Contextual Routing、Backup Routes,这也正是NB之处。如下图所示:

 

 

  二、flume的官方网站在哪里?

http://flume.apache.org/

 

  三、在哪里下载?

http://www.apache.org/dyn/closer.cgi/flume/1.5.0/apache-flume-1.5.0-bin.tar.gz

 

  四、如何安装?

1)将下载的flume包,解压到/home/hadoop目录中,你就已经完成了50%:)简单吧

2)修改 flume-env.sh 配置文件,主要是JAVA_HOME变量设置

root@m1:/home/hadoop/flume-1.5.0-bin# cp conf/flume-env.sh.template conf/flume-env.sh

root@m1:/home/hadoop/flume-1.5.0-bin# vi conf/flume-env.sh

# Licensed to the Apache Software Foundation (ASF) under one

# or more contributor license agreements.  See the NOTICE file

# distributed with this work for additional information

# regarding copyright ownership.  The ASF licenses this file

# to you under the Apache License, Version 2.0 (the

# "License"); you may not use this file except in compliance

# with the License.  You may obtain a copy of the License at

#

#     http://www.apache.org/licenses/LICENSE-2.0

#

# Unless required by applicable law or agreed to in writing, software

# distributed under the License is distributed on an "AS IS" BASIS,

# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

# See the License for the specific language governing permissions and

# limitations under the License.

 

# If this file is placed at FLUME_CONF_DIR/flume-env.sh, it will be sourced

# during Flume startup.

 

# Enviroment variables can be set here.

 

JAVA_HOME=/usr/lib/jvm/java-7-oracle

 

# Give Flume more memory and pre-allocate, enable remote monitoring via JMX

#JAVA_OPTS="-Xms100m -Xmx200m -Dcom.sun.management.jmxremote"

 

# Note that the Flume conf directory is always included in the classpath.

#FLUME_CLASSPATH=""

   3)验证是否安装成功

 

root@m1:/home/hadoop# /home/hadoop/flume-1.5.0-bin/bin/flume-ng version

Flume 1.5.0

Source code repository: https://git-wip-us.apache.org/repos/asf/flume.git

Revision: 8633220df808c4cd0c13d1cf0320454a94f1ea97

Compiled by hshreedharan on Wed May  7 14:49:18 PDT 2014

From source with checksum a01fe726e4380ba0c9f7a7d222db961f

root@m1:/home/hadoop#

 

五、flume的案例

1)案例1:Avro

Avro可以发送一个给定的文件给Flume,Avro 源使用AVRO RPC机制。

    1. 创建agent配置文件

root@m1:/home/hadoop#vi /home/hadoop/flume-1.5.0-bin/conf/avro.conf

 

a1.sources = r1

a1.sinks = k1

a1.channels = c1

 

# Describe/configure the source

a1.sources.r1.type = avro

a1.sources.r1.channels = c1

a1.sources.r1.bind = 0.0.0.0

a1.sources.r1.port = 4141

 

# Describe the sink

a1.sinks.k1.type = logger

 

# Use a channel which buffers events in memory

a1.channels.c1.type = memory

a1.channels.c1.capacity = 1000

a1.channels.c1.transactionCapacity = 100

 

# Bind the source and sink to the channel

a1.sources.r1.channels = c1

a1.sinks.k1.channel = c1

b) 启动flume agent a1

root@m1:/home/hadoop# /home/hadoop/flume-1.5.0-bin/bin/flume-ng agent -c . -f /home/hadoop/flume-1.5.0-bin/conf/avro.conf -n a1 -Dflume.root.logger=INFO,console

 

c)创建指定文件

root@m1:/home/hadoop# echo "hello world" > /home/hadoop/flume-1.5.0-bin/log.00

 

d)使用avro-client发送文件

root@m1:/home/hadoop# /home/hadoop/flume-1.5.0-bin/bin/flume-ng avro-client -c . -H m1 -p 4141 -F /

 

f)在m1的控制台,可以看到以下信息,注意最后一行:

root@m1:/home/hadoop/flume-1.5.0-bin/conf# /home/hadoop/flume-1.5.0-bin/bin/flume-ng agent -c . -f /home/hadoop/flume-1.5.0-bin/conf/avro.conf -n a1 -Dflume.root.logger=INFO,console

Info: Sourcing environment configuration script /home/hadoop/flume-1.5.0-bin/conf/flume-env.sh

Info: Including Hadoop libraries found via (/home/hadoop/hadoop-2.2.0/bin/hadoop) for HDFS access

Info: Excluding /home/hadoop/hadoop-2.2.0/share/hadoop/common/lib/slf4j-api-1.7.5.jar from classpath

Info: Excluding /home/hadoop/hadoop-2.2.0/share/hadoop/common/lib/slf4j-log4j12-1.7.5.jar from classpath

...

2014-08-10 10:43:25,112 (New I/O  worker #1) [INFO - org.apache.avro.ipc.NettyServer$NettyServerAvroHandler.handleUpstream(NettyServer.java:171)] [id: 0x92464c4f, /192.168.1.50:59850 :> /192.168.1.50:4141] UNBOUND

2014-08-10 10:43:25,112 (New I/O  worker #1) [INFO - org.apache.avro.ipc.NettyServer$NettyServerAvroHandler.handleUpstream(NettyServer.java:171)] [id: 0x92464c4f, /192.168.1.50:59850 :> /192.168.1.50:4141] CLOSED

2014-08-10 10:43:25,112 (New I/O  worker #1) [INFO - org.apache.avro.ipc.NettyServer$NettyServerAvroHandler.channelClosed(NettyServer.java:209)] Connection to /192.168.1.50:59850 disconnected.

2014-08-10 10:43:26,718 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 68 65 6C 6C 6F 20 77 6F 72 6C 64                hello world }

 

2)案例2:Spool

Spool监测配置的目录下新增的文件,并将文件中的数据读取出来。需要注意两点:

1) 拷贝到spool目录下的文件不可以再打开编辑。

2) spool目录下不可包含相应的子目录

 

 

a)创建agent配置文件

root@m1:/home/hadoop# vi /home/hadoop/flume-1.5.0-bin/conf/spool.conf

 

a1.sources = r1

a1.sinks = k1

a1.channels = c1

 

# Describe/configure the source

a1.sources.r1.type = spooldir

a1.sources.r1.channels = c1

a1.sources.r1.spoolDir = /home/hadoop/flume-1.5.0-bin/logs

a1.sources.r1.fileHeader = true

 

# Describe the sink

a1.sinks.k1.type = logger

 

# Use a channel which buffers events in memory

a1.channels.c1.type = memory

a1.channels.c1.capacity = 1000

a1.channels.c1.transactionCapacity = 100

 

# Bind the source and sink to the channel

a1.sources.r1.channels = c1

a1.sinks.k1.channel = c1

b)启动flume agent a1

root@m1:/home/hadoop# /home/hadoop/flume-1.5.0-bin/bin/flume-ng agent -c . -f /home/hadoop/flume-1.5.0-bin/conf/spool.conf -n a1 -Dflume.root.logger=INFO,console

c)追加文件到/home/hadoop/flume-1.5.0-bin/logs目录

 

root@m1:/home/hadoop# echo "spool test1" > /home/hadoop/flume-1.5.0-bin/logs/spool_text.log

d)在m1的控制台,可以看到以下相关信息:

14/08/10 11:37:13 INFO source.SpoolDirectorySource: Spooling Directory Source runner has shutdown.

14/08/10 11:37:13 INFO source.SpoolDirectorySource: Spooling Directory Source runner has shutdown.

14/08/10 11:37:14 INFO avro.ReliableSpoolingFileEventReader: Preparing to move file /home/hadoop/flume-1.5.0-bin/logs/spool_text.log to /home/hadoop/flume-1.5.0-bin/logs/spool_text.log.COMPLETED

14/08/10 11:37:14 INFO source.SpoolDirectorySource: Spooling Directory Source runner has shutdown.

14/08/10 11:37:14 INFO source.SpoolDirectorySource: Spooling Directory Source runner has shutdown.

14/08/10 11:37:14 INFO sink.LoggerSink: Event: { headers:{file=/home/hadoop/flume-1.5.0-bin/logs/spool_text.log} body: 73 70 6F 6F 6C 20 74 65 73 74 31                spool test1 }

14/08/10 11:37:15 INFO source.SpoolDirectorySource: Spooling Directory Source runner has shutdown.

14/08/10 11:37:15 INFO source.SpoolDirectorySource: Spooling Directory Source runner has shutdown.

14/08/10 11:37:16 INFO source.SpoolDirectorySource: Spooling Directory Source runner has shutdown.

14/08/10 11:37:16 INFO source.SpoolDirectorySource: Spooling Directory Source runner has shutdown.

14/08/10 11:37:17 INFO source.SpoolDirectorySource: Spooling Directory Source runner has shutdown.

 

3)案例3:Exec

EXEC执行一个给定的命令获得输出的源,如果要使用tail命令,必选使得file足够大才能看到输出内容

 

a)创建agent配置文件

root@m1:/home/hadoop# vi /home/hadoop/flume-1.5.0-bin/conf/exec_tail.conf

 

a1.sources = r1

a1.sinks = k1

a1.channels = c1

 

# Describe/configure the source

a1.sources.r1.type = exec

a1.sources.r1.channels = c1

a1.sources.r1.command = tail -F /home/hadoop/flume-1.5.0-bin/log_exec_tail

 

# Describe the sink

a1.sinks.k1.type = logger

 

# Use a channel which buffers events in memory

a1.channels.c1.type = memory

a1.channels.c1.capacity = 1000

a1.channels.c1.transactionCapacity = 100

 

# Bind the source and sink to the channel

a1.sources.r1.channels = c1

a1.sinks.k1.channel = c1

 

b)启动flume agent a1

root@m1:/home/hadoop# /home/hadoop/flume-1.5.0-bin/bin/flume-ng agent -c . -f /home/hadoop/flume-1.5.0-bin/conf/exec_tail.conf -n a1 -Dflume.root.logger=INFO,console

c)生成足够多的内容在文件里

root@m1:/home/hadoop# for i in {1..100};do echo "exec tail$i" >> /home/hadoop/flume-1.5.0-bin/log_

 

e)在m1的控制台,可以看到以下信息:

2014-08-10 10:59:25,513 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 65 78 65 63 20 74 61 69 6C 20 74 65 73 74       exec tail test }

2014-08-10 10:59:34,535 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 65 78 65 63 20 74 61 69 6C 20 74 65 73 74       exec tail test }

2014-08-10 11:01:40,557 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 65 78 65 63 20 74 61 69 6C 31                   exec tail1 }

2014-08-10 11:01:41,180 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 65 78 65 63 20 74 61 69 6C 32                   exec tail2 }

2014-08-10 11:01:41,180 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 65 78 65 63 20 74 61 69 6C 33                   exec tail3 }

2014-08-10 11:01:41,181 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 65 78 65 63 20 74 61 69 6C 34                   exec tail4 }

2014-08-10 11:01:41,181 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 65 78 65 63 20 74 61 69 6C 35                   exec tail5 }

2014-08-10 11:01:41,181 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 65 78 65 63 20 74 61 69 6C 36                   exec tail6 }

....

....

....

2014-08-10 11:01:51,550 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 65 78 65 63 20 74 61 69 6C 39 36                exec tail96 }

2014-08-10 11:01:51,550 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 65 78 65 63 20 74 61 69 6C 39 37                exec tail97 }

2014-08-10 11:01:51,551 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 65 78 65 63 20 74 61 69 6C 39 38                exec tail98 }

2014-08-10 11:01:51,551 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 65 78 65 63 20 74 61 69 6C 39 39                exec tail99 }

2014-08-10 11:01:51,551 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 65 78 65 63 20 74 61 69 6C 31 30 30             exec tail100 }

 

4)案例4:Syslogtcp

Syslogtcp监听TCP的端口做为数据源

 

a)创建agent配置文件

 

root@m1:/home/hadoop# vi /home/hadoop/flume-1.5.0-bin/conf/syslog_tcp.conf

 

a1.sources = r1

a1.sinks = k1

a1.channels = c1

 

# Describe/configure the source

a1.sources.r1.type = syslogtcp

a1.sources.r1.port = 5140

a1.sources.r1.host = localhost

a1.sources.r1.channels = c1

 

# Describe the sink

a1.sinks.k1.type = logger

 

# Use a channel which buffers events in memory

a1.channels.c1.type = memory

a1.channels.c1.capacity = 1000

a1.channels.c1.transactionCapacity = 100

 

# Bind the source and sink to the channel

a1.sources.r1.channels = c1

a1.sinks.k1.channel = c1

 

b)启动flume agent a1

root@m1:/home/hadoop# /home/hadoop/flume-1.5.0-bin/bin/flume-ng agent -c . -f /home/hadoop/flume-1.5.0-bin/conf/syslog_tcp.conf -n a1 -Dflume.root.logger=INFO,console

c)测试产生syslog

root@m1:/home/hadoop# echo "hello idoall.org syslog" | nc localhost 5140

 

d)在m1的控制台,可以看到以下信息

14/08/10 11:41:45 INFO node.PollingPropertiesFileConfigurationProvider: Reloading configuration file:/home/hadoop/flume-1.5.0-bin/conf/syslog_tcp.conf

14/08/10 11:41:45 INFO conf.FlumeConfiguration: Added sinks: k1 Agent: a1

14/08/10 11:41:45 INFO conf.FlumeConfiguration: Processing:k1

14/08/10 11:41:45 INFO conf.FlumeConfiguration: Processing:k1

14/08/10 11:41:45 INFO conf.FlumeConfiguration: Post-validation flume configuration contains configuration for agents: [a1]

14/08/10 11:41:45 INFO node.AbstractConfigurationProvider: Creating channels

14/08/10 11:41:45 INFO channel.DefaultChannelFactory: Creating instance of channel c1 type memory

14/08/10 11:41:45 INFO node.AbstractConfigurationProvider: Created channel c1

14/08/10 11:41:45 INFO source.DefaultSourceFactory: Creating instance of source r1, type syslogtcp

14/08/10 11:41:45 INFO sink.DefaultSinkFactory: Creating instance of sink: k1, type: logger

14/08/10 11:41:45 INFO node.AbstractConfigurationProvider: Channel c1 connected to [r1, k1]

14/08/10 11:41:45 INFO node.Application: Starting new configuration:{ sourceRunners:{r1=EventDrivenSourceRunner: { source:org.apache.flume.source.SyslogTcpSource{name:r1,state:IDLE} }} sinkRunners:{k1=SinkRunner: { policy:org.apache.flume.sink.DefaultSinkProcessor@6538b14 counterGroup:{ name:null counters:{} } }} channels:{c1=org.apache.flume.channel.MemoryChannel{name: c1}} }

14/08/10 11:41:45 INFO node.Application: Starting Channel c1

14/08/10 11:41:45 INFO instrumentation.MonitoredCounterGroup: Monitored counter group for type: CHANNEL, name: c1: Successfully registered new MBean.

14/08/10 11:41:45 INFO instrumentation.MonitoredCounterGroup: Component type: CHANNEL, name: c1 started

14/08/10 11:41:45 INFO node.Application: Starting Sink k1

14/08/10 11:41:45 INFO node.Application: Starting Source r1

14/08/10 11:41:45 INFO source.SyslogTcpSource: Syslog TCP Source starting...

14/08/10 11:42:15 WARN source.SyslogUtils: Event created from Invalid Syslog data.

14/08/10 11:42:15 INFO sink.LoggerSink: Event: { headers:{Severity=0, flume.syslog.status=Invalid, Facility=0} body: 68 65 6C 6C 6F 20 69 64 6F 61 6C 6C 2E 6F 72 67 hello idoall.org }

 

5)案例5:JSONHandler

a)创建agent配置文件

root@m1:/home/hadoop# vi /home/hadoop/flume-1.5.0-bin/conf/post_json.conf

 

a1.sources = r1

a1.sinks = k1

a1.channels = c1

 

# Describe/configure the source

a1.sources.r1.type = org.apache.flume.source.http.HTTPSource

a1.sources.r1.port = 8888

a1.sources.r1.channels = c1

 

# Describe the sink

a1.sinks.k1.type = logger

 

# Use a channel which buffers events in memory

a1.channels.c1.type = memory

a1.channels.c1.capacity = 1000

a1.channels.c1.transactionCapacity = 100

 

# Bind the source and sink to the channel

a1.sources.r1.channels = c1

a1.sinks.k1.channel = c1

b)启动flume agent a1

root@m1:/home/hadoop# /home/hadoop/flume-1.5.0-bin/bin/flume-ng agent -c . -f /home/hadoop/flume-1.5.0-bin/conf/post_json.conf -n a1 -Dflume.root.logger=INFO,console

 

c)生成JSON 格式的POST request

 

root@m1:/home/hadoop# curl -X POST -d '[{ "headers" :{"a" : "a1","b" : "b1"},"body" : "idoall.org_body"}]' http://localhost:8888

 

d)在m1的控制台,可以看到以下信息:

14/08/10 11:49:59 INFO node.Application: Starting Channel c1

14/08/10 11:49:59 INFO instrumentation.MonitoredCounterGroup: Monitored counter group for type: CHANNEL, name: c1: Successfully registered new MBean.

14/08/10 11:49:59 INFO instrumentation.MonitoredCounterGroup: Component type: CHANNEL, name: c1 started

14/08/10 11:49:59 INFO node.Application: Starting Sink k1

14/08/10 11:49:59 INFO node.Application: Starting Source r1

14/08/10 11:49:59 INFO mortbay.log: Logging to org.slf4j.impl.Log4jLoggerAdapter(org.mortbay.log) via org.mortbay.log.Slf4jLog

14/08/10 11:49:59 INFO mortbay.log: jetty-6.1.26

14/08/10 11:50:00 INFO mortbay.log: Started SelectChannelConnector@0.0.0.0:8888

14/08/10 11:50:00 INFO instrumentation.MonitoredCounterGroup: Monitored counter group for type: SOURCE, name: r1: Successfully registered new MBean.

14/08/10 11:50:00 INFO instrumentation.MonitoredCounterGroup: Component type: SOURCE, name: r1 started

14/08/10 12:14:32 INFO sink.LoggerSink: Event: { headers:{b=b1, a=a1} body: 69 64 6F 61 6C 6C 2E 6F 72 67 5F 62 6F 64 79    idoall.org_body }

 

6)案例6:Hadoop sink

a)创建agent配置文件

 

root@m1:/home/hadoop# vi /home/hadoop/flume-1.5.0-bin/conf/hdfs_sink.conf

 

a1.sources = r1

a1.sinks = k1

a1.channels = c1

 

# Describe/configure the source

a1.sources.r1.type = syslogtcp

a1.sources.r1.port = 5140

a1.sources.r1.host = localhost

a1.sources.r1.channels = c1

 

# Describe the sink

a1.sinks.k1.type = hdfs

a1.sinks.k1.channel = c1

a1.sinks.k1.hdfs.path = hdfs://m1:9000/user/flume/syslogtcp

a1.sinks.k1.hdfs.filePrefix = Syslog

a1.sinks.k1.hdfs.round = true

a1.sinks.k1.hdfs.roundValue = 10

a1.sinks.k1.hdfs.roundUnit = minute

 

# Use a channel which buffers events in memory

a1.channels.c1.type = memory

a1.channels.c1.capacity = 1000

a1.channels.c1.transactionCapacity = 100

 

# Bind the source and sink to the channel

a1.sources.r1.channels = c1

a1.sinks.k1.channel = c1

 

b)启动flume agent a1

root@m1:/home/hadoop# /home/hadoop/flume-1.5.0-bin/bin/flume-ng agent -c . -f /home/hadoop/flume-1.5.0-bin/conf/hdfs_sink.conf -n a1 -Dflume.root.logger=INFO,console

 

 

c)测试产生syslog

root@m1:/home/hadoop# echo "hello idoall flume -> hadoop testing one" | nc localhost 5140

 

d)在m1的控制台,可以看到以下信息:

14/08/10 12:20:39 INFO instrumentation.MonitoredCounterGroup: Monitored counter group for type: CHANNEL, name: c1: Successfully registered new MBean.

14/08/10 12:20:39 INFO instrumentation.MonitoredCounterGroup: Component type: CHANNEL, name: c1 started

14/08/10 12:20:39 INFO node.Application: Starting Sink k1

14/08/10 12:20:39 INFO node.Application: Starting Source r1

14/08/10 12:20:39 INFO instrumentation.MonitoredCounterGroup: Monitored counter group for type: SINK, name: k1: Successfully registered new MBean.

14/08/10 12:20:39 INFO instrumentation.MonitoredCounterGroup: Component type: SINK, name: k1 started

14/08/10 12:20:39 INFO source.SyslogTcpSource: Syslog TCP Source starting...

14/08/10 12:21:46 WARN source.SyslogUtils: Event created from Invalid Syslog data.

14/08/10 12:21:49 INFO hdfs.HDFSSequenceFile: writeFormat = Writable, UseRawLocalFileSystem = false

14/08/10 12:21:49 INFO hdfs.BucketWriter: Creating hdfs://m1:9000/user/flume/syslogtcp//Syslog.1407644509504.tmp

14/08/10 12:22:20 INFO hdfs.BucketWriter: Closing hdfs://m1:9000/user/flume/syslogtcp//Syslog.1407644509504.tmp

14/08/10 12:22:20 INFO hdfs.BucketWriter: Close tries incremented

14/08/10 12:22:20 INFO hdfs.BucketWriter: Renaming hdfs://m1:9000/user/flume/syslogtcp/Syslog.1407644509504.tmp to hdfs://m1:9000/user/flume/syslogtcp/Syslog.1407644509504

14/08/10 12:22:20 INFO hdfs.HDFSEventSink: Writer callback called.

 

e)在m1上再打开一个窗口,去hadoop上检查文件是否生成

root@m1:/home/hadoop# /home/hadoop/hadoop-2.2.0/bin/hadoop fs -ls /user/flume/syslogtcp

Found 1 items

-rw-r--r--   3 root supergroup        155 2014-08-10 12:22 /user/flume/syslogtcp/Syslog.1407644509504

root@m1:/home/hadoop# /home/hadoop/hadoop-2.2.0/bin/hadoop fs -cat /user/flume/syslogtcp/Syslog.1407644509504

SEQ!org.apache.hadoop.io.LongWritable"org.apache.hadoop.io.BytesWritable^;>Gv$hello idoall flume -> hadoop testing one

 

7)案例7:File Roll Sink

a)创建agent配置文件

root@m1:/home/hadoop# vi /home/hadoop/flume-1.5.0-bin/conf/file_roll.conf

 

a1.sources = r1

a1.sinks = k1

a1.channels = c1

 

# Describe/configure the source

a1.sources.r1.type = syslogtcp

a1.sources.r1.port = 5555

a1.sources.r1.host = localhost

a1.sources.r1.channels = c1

 

# Describe the sink

a1.sinks.k1.type = file_roll

a1.sinks.k1.sink.directory = /home/hadoop/flume-1.5.0-bin/logs

 

# Use a channel which buffers events in memory

a1.channels.c1.type = memory

a1.channels.c1.capacity = 1000

a1.channels.c1.transactionCapacity = 100

 

# Bind the source and sink to the channel

a1.sources.r1.channels = c1

a1.sinks.k1.channel = c1

b)启动flume agent a1

root@m1:/home/hadoop# /home/hadoop/flume-1.5.0-bin/bin/flume-ng agent -c . -f /home/hadoop/flume-1.5.0-bin/conf/file_roll.conf -n a1 -Dflume.root.logger=INFO,console

c)测试产生log

 

root@m1:/home/hadoop# echo "hello idoall.org syslog" | nc localhost 5555

root@m1:/home/hadoop# echo "hello idoall.org syslog 2" | nc localhost 5555

d)查看/home/hadoop/flume-1.5.0-bin/logs下是否生成文件,默认每30秒生成一个新文件

 

root@m1:/home/hadoop# ll /home/hadoop/flume-1.5.0-bin/logs

总用量 272

drwxr-xr-x 3 root root   4096 Aug 10 12:50 ./

drwxr-xr-x 9 root root   4096 Aug 10 10:59 ../

-rw-r--r-- 1 root root     50 Aug 10 12:49 1407646164782-1

-rw-r--r-- 1 root root      0 Aug 10 12:49 1407646164782-2

-rw-r--r-- 1 root root      0 Aug 10 12:50 1407646164782-3

root@m1:/home/hadoop# cat /home/hadoop/flume-1.5.0-bin/logs/1407646164782-1 /home/hadoop/flume-1.5.0-bin/logs/1407646164782-2

hello idoall.org syslog

hello idoall.org syslog 2

8)案例8:Replicating Channel Selector

Flume支持Fan out流从一个源到多个通道。有两种模式的Fan out,分别是复制和复用。在复制的情况下,流的事件被发送到所有的配置通道。在复用的情况下,事件被发送到可用的渠道中的一个子集。Fan out流需要指定源和Fan out通道的规则。

 

    这次我们需要用到m1,m2两台机器

 

a)在m1创建replicating_Channel_Selector配置文件

 

root@m1:/home/hadoop# vi /home/hadoop/flume-1.5.0-bin/conf/replicating_Channel_Selector.conf

 

a1.sources = r1

a1.sinks = k1 k2

a1.channels = c1 c2

 

# Describe/configure the source

a1.sources.r1.type = syslogtcp

a1.sources.r1.port = 5140

a1.sources.r1.host = localhost

a1.sources.r1.channels = c1 c2

a1.sources.r1.selector.type = replicating

 

# Describe the sink

a1.sinks.k1.type = avro

a1.sinks.k1.channel = c1

a1.sinks.k1.hostname = m1

a1.sinks.k1.port = 5555

 

a1.sinks.k2.type = avro

a1.sinks.k2.channel = c2

a1.sinks.k2.hostname = m2

a1.sinks.k2.port = 5555

 

# Use a channel which buffers events in memory

a1.channels.c1.type = memory

a1.channels.c1.capacity = 1000

a1.channels.c1.transactionCapacity = 100

 

a1.channels.c2.type = memory

a1.channels.c2.capacity = 1000

a1.channels.c2.transactionCapacity = 100

b)在m1创建replicating_Channel_Selector_avro配置文件

 

root@m1:/home/hadoop# vi /home/hadoop/flume-1.5.0-bin/conf/replicating_Channel_Selector_avro.conf

 

a1.sources = r1

a1.sinks = k1

a1.channels = c1

 

# Describe/configure the source

a1.sources.r1.type = avro

a1.sources.r1.channels = c1

a1.sources.r1.bind = 0.0.0.0

a1.sources.r1.port = 5555

 

# Describe the sink

a1.sinks.k1.type = logger

 

# Use a channel which buffers events in memory

a1.channels.c1.type = memory

a1.channels.c1.capacity = 1000

a1.channels.c1.transactionCapacity = 100

 

# Bind the source and sink to the channel

a1.sources.r1.channels = c1

a1.sinks.k1.channel = c1

 

c)在m1上将2个配置文件复制到m2上一份

root@m1:/home/hadoop/flume-1.5.0-bin# scp -r /home/hadoop/flume-1.5.0-bin/conf/replicating_Channel_Selector.conf root@m2:/home/hadoop/flume-1.5.0-bin/conf/replicating_Channel_Selector.conf

root@m1:/home/hadoop/flume-1.5.0-bin# scp -r /home/hadoop/flume-1.5.0-bin/conf/replicating_Channel_Selector_avro.conf root@m2:/home/hadoop/flume-1.5.0-bin/conf/replicating_Channel_Selector_avro.conf

 

d)打开4个窗口,在m1和m2上同时启动两个flume agent

root@m1:/home/hadoop# /home/hadoop/flume-1.5.0-bin/bin/flume-ng agent -c . -f /home/hadoop/flume-1.5.0-bin/conf/replicating_Channel_Selector_avro.conf -n a1 -Dflume.root.logger=INFO,console

root@m1:/home/hadoop# /home/hadoop/flume-1.5.0-bin/bin/flume-ng agent -c . -f /home/hadoop/flume-1.5.0-bin/conf/replicating_Channel_Selector.conf -n a1 -Dflume.root.logger=INFO,console

 

 

e)然后在m1或m2的任意一台机器上,测试产生syslog

root@m1:/home/hadoop# echo "hello idoall.org syslog" | nc localhost 5140

 

f)在m1和m2的sink窗口,分别可以看到以下信息,这说明信息得到了同步:

14/08/10 14:08:18 INFO ipc.NettyServer: Connection to /192.168.1.51:46844 disconnected.

14/08/10 14:08:52 INFO ipc.NettyServer: [id: 0x90f8fe1f, /192.168.1.50:35873 => /192.168.1.50:5555] OPEN

14/08/10 14:08:52 INFO ipc.NettyServer: [id: 0x90f8fe1f, /192.168.1.50:35873 => /192.168.1.50:5555] BOUND: /192.168.1.50:5555

14/08/10 14:08:52 INFO ipc.NettyServer: [id: 0x90f8fe1f, /192.168.1.50:35873 => /192.168.1.50:5555] CONNECTED: /192.168.1.50:35873

14/08/10 14:08:59 INFO ipc.NettyServer: [id: 0xd6318635, /192.168.1.51:46858 => /192.168.1.50:5555] OPEN

14/08/10 14:08:59 INFO ipc.NettyServer: [id: 0xd6318635, /192.168.1.51:46858 => /192.168.1.50:5555] BOUND: /192.168.1.50:5555

14/08/10 14:08:59 INFO ipc.NettyServer: [id: 0xd6318635, /192.168.1.51:46858 => /192.168.1.50:5555] CONNECTED: /192.168.1.51:46858

14/08/10 14:09:20 INFO sink.LoggerSink: Event: { headers:{Severity=0, flume.syslog.status=Invalid, Facility=0} body: 68 65 6C 6C 6F 20 69 64 6F 61 6C 6C 2E 6F 72 67 hello idoall.org }

 

 

 

 

 

Flume作用

Flume工作机制

Flume架构、组件

flume常用配置

Spooldir source

Exec source

Netcat source

Avro source

 

Hdfs sink

Log console sink

 

flume扩展编程

 

sqoop介绍

sqoop常用配置

Sqoop导入数据到hive

Sqoop导入数据到hbase

Sqoop导入数据到hdfs

动手练习(flume采集数据到hdfs,再导入hive)

 

15.  Storm基础

15.1 storm介绍

15.2 storm基本概念

15.3 storm集群搭建

15.4 storm示例编程

15.5 kafka介绍与应用开发

 

动手练习

 

16.  Storm高级特性

16.1 Storm与kafka整合

 

16.2 Storm的tuple树

 

 

16.3 storm事务topo(Trident)

16.3.1 TridentTopology简介

TridentTopology是storm提供的高级调用接口,屏蔽了很多spout和bolt的细节,开发人员根据其提供的api就能用非常简短的程序写出许多很有实用价值的

应用。

构成TridentTopology的几个重要概念

• stream

• batch

• TridentState

1. Operations that apply locally to each partition and cause no

network transfer

2. Repartitioning operations that repartition a stream but otherwise

don't change the contents (involves network transfer)

3. Aggregation operations that do network transfer as part of the

operation

4. Operations on grouped streams

5. Merges and join

TridentTopology通过transactional spout与transactional state相结

合,能够做到tuple“只被处理一次,不多也不少”。也就是做到事务性处

理exactly-once,要么成功,要么失败。

而一般的storm topology是无法保证eactly-once的处理的,它们要么是at-

least-once(至少被处理一次,有可能被处理多次);要么是at-most-once(最

多被处理一次,这样就存在遗漏的可能).

TridentTopology在设计中借鉴和保留了目前已经过期的transactional

topology的设计思想。

16.3.2  应用举例

百闻不如一见,举一个例子

TridentTopology topology = new TridentTopology ();

TridentState wordCounts =

topology . newStream (" spout1 ", spout )

. each (new Fields (" sentence "), new Split () , new Fields (" word "))

. groupBy ( new Fields (" word "))

. persistentAggregate ( new MemoryMapState . Factory () , new Count () ,

new Fields (" count "))

. parallelismHint (6) ;

 

上述代码的含义解释如下

• 从spout中读取数据

• 将句子分隔成各个独立的句子

• 将单词分组

• 将统计单词个数,将其存入内存MemoryMap

• 并行数为6

 

 

 

16.3.3 引言

TridentTopology是storm提供的高层使用接口,常见的一些SQL中的操作在

tridenttopology提供的api中都有类似的影射。

从TridentTopology到vanilla topology(普通的topology)由三个层次组

成:

1. 面向最终用户的概念stream, operation

2. 利用planner将tridenttopology转换成vanilla topology

3. 执行vanilla topology

从TridentTopology到基本的Topology有三层,下图是一个全局视图。

 

以wordcount为例,使用TridentTopology上层接口来实现的源码如下所示:

 

 

TridentTopology topology = new TridentTopology ();

topology . newStream (" spout1 ", spout ). parallelismHint (16) . each ( new

Fields (" sentence "),

new Split () , new Fields (" word ")). groupBy ( new Fields (" word ")).

persistentAggregate ( new MemoryMapState . Factory () ,

new Count () , new Fields (" count ")). parallelismHint (16) ;

return topology . build ();

 

上述代码的newStream一行,分两大部分,一是使用newStream来创建一个

stream对象,然后针对该Stream进行各种操作,each/shuffle/persistentAg-

gregate等就是各种operation.

用户在使用TridentTopology的时候,只需要熟悉Stream和TridentTopology

中的API函数即可。

从用户层面来看TridentTopology,有两个重要的概念一是Stream,另一个是

作用于Stream上的各种Operation。在实现层面来看,无论是stream,还是后续

的operation都会转变成为各个Node,这些Node之间的关系通过重要的数据结构

图来维护。具体到TridentTopology,实现图的各种操作的组件是jgrapht。

说到图,两个基本的概念会闪现出来,一是结点,二是描述结点之间关系的

边。要想很好的理解TridentTopology就需要紧盯图中结点和边的变化。

TridentTopology在转换成为普通的StormTopology时,需要将原始的图分成

各个group,每个group将运行于一个独立的bolt中。TridentTopology又是如何

知道哪些node应该在同一个group,哪些应该处在另一个group中的呢;如何来确

定每个group的并发度(parallismHint)的呢。这些问题的解决都与jgrapht分不

开。

在TridentTopology中向图中添加结点的api有三种:

1. addNode

2. addSourcedNode

3. addSourcedStateNode

其中addNode在创建stream是使用,addSourcedStateNode在partitionPer-

sist时使用到,其它的operation使用到的是addSourcedNode.

addNode与其它两个方法的一个重要区别还在于,addNode是不需要添加边

(Edge),而其它两个API需要往图中添加edge,以确定该node的源是哪个。

 

TridentTopoloy

public TridentTopology () {

_graph = new DefaultDirectedGraph ( new ErrorEdgeFactory ());

_gen = new UniqueIdGen ();

}

在TridentTopology的构造函数中,创建了DAG(有向无环图)。利用这个_graph

来作为容器以存储后续过程中创建的各个node及它们之间的关系。

 

 

16.3.4  newStream

newStream会为DAG(有向无环图)中创建源结点,其调用关系如下所示。

newStream

addNode

registerNode

protected void registerNode ( Node n) {

_graph . addVertex (n);

if(n. stateInfo != null ) {

String id = n. stateInfo .id;

if (! _colocate . containsKey (id)) {

_colocate . put (id , new ArrayList ());

}

_colocate . get (id). add (n);

}

}

 

 

16.3.5  each

作用于stream上的Operation有很多,以each为例来看新的operation是如何转

换成为node添加到_graph中的。

// Stream . java

public Stream each ( Fields inputFields , Function function , Fields

functionFields ) {

projectionValidation ( inputFields );

return _topology . addSourcedNode (this ,

new ProcessorNode ( _topology . getUniqueStreamId () ,

_name ,

TridentUtils . fieldsConcat ( getOutputFields () ,

functionFields ),

functionFields ,

new EachProcessor ( inputFields , function )));

}

 

调用关系描述如下

Stream::each

TridentTopology::addSourcedNode

TridentTopology::registerSourcedNode

registerSourcedNode的实现如下

protected void registerSourcedNode (List < Stream > sources , Node newNode

) {

registerNode ( newNode );

int streamIndex = 0;

for ( Stream s: sources ) {

_graph . addEdge (s._node , newNode , new IndexedEdge (s._node ,

newNode , streamIndex ));

streamIndex ++;

}

}

 

注意此处添加edge是,是有索引的,这样可以区别处理的先后顺序。

在Stream中含有成员变量_node,表示stream最近停泊的node,有了该变量添

加edge才成为了可能。

partitionPersist

public TridentState partitionPersist ( StateSpec stateSpec , Fields

inputFields , StateUpdater updater , Fields functionFields ) {

projectionValidation ( inputFields );

String id = _topology . getUniqueStateId ();

ProcessorNode n = new ProcessorNode ( _topology . getUniqueStreamId () ,

_name ,

functionFields ,

functionFields ,

new PartitionPersistProcessor (id , inputFields , updater ))

;

n. committer = true ;

n. stateInfo = new NodeStateInfo (id , stateSpec );

return _topology . addSourcedStateNode (this , n);

}

 

调用关系

Stream::partitionPersist

TridentTopology::addSourcedStateNode

TridentTopology::registerSourcedNode

与addNode及addSourcedNode不同的是,addSourcedStateNode返回的是Tri-

dentState而非Stream。

既然谈到了TridentState就不得不谈到其另一面Stream::stateQuery,

public Stream stateQuery ( TridentState state , Fields inputFields ,

QueryFunction function , Fields functionFields ) {

projectionValidation ( inputFields );

String stateId = state . _node . stateInfo .id;

Node n = new ProcessorNode ( _topology . getUniqueStreamId () ,

_name ,

TridentUtils . fieldsConcat ( getOutputFields () ,

functionFields ),

functionFields ,

new StateQueryProcessor ( stateId , inputFields ,

function ));

_topology . _colocate . get ( stateId ). add (n);

return _topology . addSourcedNode (this , n);

}

 

从此处可以看出stateQueryNode最起码有两个inputStream,一是从TridentState

而来表示状态已经改变,另一个是处于drpcStream这个方面的上一跳结点。

 

 

作用于stream上的Operation有很多,以each为例来看新的operation是如何转

换成为node添加到_graph中的。

 

 

16.3.6  build

TridentTopology::build是将TridentTopology转变为StormTopology的过程,

这一过程中最重要的一环就是将_graph中含有的node进行分组。

 

16.3.7  grouping

算法逻辑概述

1. 将boltNodes中的每个boltNode作为一个group加入全部加入initial-

Groups

2. 以graph和initialGroups作为入参创建GraphGrouper

3. 分组的过程其实就是进行合并的过程,详见GraphGrouper::mergeFully()

• 如果从当前group1的输出目的地都是属于group2,则将group1,group2

合并

• 如果当前group1的所有输入源都是来自于group2,则将group1,

group2合并

• 将需要合并的group1,group2作为入参创建新的group,同时将group1,group2

从已有的集合出移除

 

public void mergeFully () {

boolean somethingHappened = true ;

while ( somethingHappened ) {

somethingHappened = false ;

for ( Group g: currGroups ) {

Collection <Group > outgoingGroups = outgoingGroups (g);

if( outgoingGroups . size () ==1) {

Group out = outgoingGroups . iterator (). next ();

if(out != null ) {

merge (g, out );

somethingHappened = true ;

break ;

}

}

Collection <Group > incomingGroups = incomingGroups (g);

if( incomingGroups . size () ==1) {

Group in = incomingGroups . iterator (). next ();

if(in != null ) {

merge (g, in);

somethingHappened = true ;

break ;

}

}

}

}

}

 

 

 

GraphGrouper::merge()

private void merge ( Group g1 , Group g2) {

Group newGroup = new Group (g1 , g2);

currGroups . remove (g1);

currGroups . remove (g2);

53currGroups . add ( newGroup );

for ( Node n: newGroup . nodes ) {

groupIndex . put (n, newGroup );

}

}

Listing 10: merge

在group之间添加partitionNode

// add identity partitions between groups

for ( IndexedEdge <Node > e: new HashSet < IndexedEdge >( graph . edgeSet ())) {

if (!( e. source instanceof PartitionNode ) && !(e. target instanceof

PartitionNode )) {

Group g1 = grouper . nodeGroup (e. source );

Group g2 = grouper . nodeGroup (e. target );

// g1 being null means the source is a spout node

if(g1 == null && !(e. source instanceof SpoutNode ))

throw new RuntimeException (" Planner␣exception :␣Null␣source

␣group␣must␣indicate␣a␣spout␣node␣at␣this␣phase␣of␣

planning ");

if(g1 == null || !g1. equals (g2)) {

graph . removeEdge (e);

PartitionNode pNode = makeIdentityPartition (e. source );

graph . addVertex ( pNode );

graph . addEdge (e. source , pNode , new IndexedEdge (e. source ,

pNode , 0));

graph . addEdge (pNode , e. target , new IndexedEdge (pNode , e.

target , e. index ));

}

}

}

Listing 11: add PartitionNode

_graph中所有的node在变换过后,变成两组元素,一是spoutNodes,另一个

是合并后的mergedGroup.

spoutNodes中的每个元素作为spout添加到TridentTopologyBuilder的_spouts

数组中,mergedGroup中的每个group添加到TridentTopologyBuilder的_bolt数

组中。在TridentTopologyBuilder::build()中最主要的事情是为每个_spouts

和_bolts数组中的成员添加grouping关系。

 

 

 

 

16.4 Storm Topology的ack机制

在进行TridentTopology的可靠性分析之前,我们先回顾一下在storm topology

中的ack机制。ack bolt是在提交到storm cluster中,由系统自动产生的,一

般来说一个topology只有一个ack bolt(当然可以通过配置参数指定多个)。

当bolt处理并下发完tuple给下一跳的bolt时,会发送一个ack给ack bolt。

ack bolt通过简单的异或原理(即同一个数与自己异或结果为零)来判定从spout

发出的某一个bolt是否已经被完全处理完毕。如果结果为真,ack bolt发送消

息给spout,spout中的ack函数被调用并执行。如果超时,则发送fail消息给

spout,spout中的fail函数被调用并执行,spout中的ack和fail的处理逻辑由

用户自行填写。

54如在github上的kerstel spout就能做到只有当某一个tuple被成功处理之

后,它才会从缓存中移除,否则继续放入到处理队列再次进行处理。

下图是对上述文字所描述过程的形像展示

 

 

 

 

16.5  TridentTopology调用关系

上图是TridentTopology在转换成为vanilla topology在运行过程中的示意

图,要点如下

1. 一个tridenttopoloy会至少引入一个MasterBatchCoordinator,这个MBC

就类似于storm topology中的spout

2. newStream时使用的入参spout会裂变成两个bolt,一是TridentSpoutCo-

ordinator,另一个是TridentSpoutExecutor

3. 针对stream的各种操作则被分散到各个Bolt中,它们的执行上下文是

TridentBoltExecutor

可以看出使用TridentTopology Api进行操作时,所有的东西其实都运行在

bolt context中,而真正的spout是在调用TridentTopologyBuilder.buildTopology()

的时候被添加的。

MasterBatchCoordinator使用batch_stream发送一个类似于seeder tuple的

东西给tridentspoutcoordinator,tridentspoutcoordinator将该信号继续下

发给TridentSpoutExecutor, TridentSpout是如何一步步被调用到的呢。 调用

关系如下所示

TridentBoltExecutor::execute

  TridentSpoutExecutor::execute

  BatchSpoutExecutor::execute

  ITridentSpout::emitBatch

56emitBatch是产生真正需要被处理的tuple的,这些tuple会被各个Operation

所在的bolt所接收。它们的调用顺序是

TridentBoltExecutor::execute

  SubtopologyBolt::execute

  InitialReceiver::receive

  TridentProcessor::execute

9.4.1 处理结束的判断依据

在TridentSpout中是如何判断所有的tuple都已经被处理的呢。

1. 在每跳中认为自己处理完毕的时候,它都会告诉下一跳,即下游,我给

你发送了多少tuple,如果下游将上游发送过来的确认消息与自身确实已

经处理的消息比对一致的话,则认为处理都完成,于是发送ack.

2. 问题的关键变成每一个bolt是如何判断自己已经处理完毕的呢,请看步

骤3

3. 总有一个bolt是没有上游的,即TridentSpoutExecutor,它只会收到启

动指令,但不接收真正的业务数据,于是它会告诉下一跳,我发了多少

tuple给你。

在MasterBatchCoordinator中定义了三种不同的stream,这三种stream分别

1. BATCH_STREAM

2. COMMIT_STREAM

3. SUCCESS_STREAM

这些stream分别在什么时候被使用呢,下图给出一个大概的时序

 

针对上图的简要说明

1. masterbatchcoordinator通过batch_stream发送seeder tuple给tridentspout-

coordinator

2. tridentspoutcoordinator给tridentspoutexecutor继续传递该指令

3. TridentSpoutExecutor在收到启动指令后,调用ITridentSpout接口的实

现类进行emitBatch

4. TridentSpoutExecutor在发送完一批batch后,finishBatch被调用,通

过emitDirect会给下一跳通过coord_stream发送trackedinfo,即我已经

发送了多少消息给你

5. TridentSpoutExecutor紧接着还会给ack bolt发送ack消息,ack bolt将

其传达到MasterBatchCoordinator

6. MasterBatchCoordinator在收到第一个ack后,将状态置为processed

7. 当MasterBatchCoordinator再次收到ack后,会将状态转为committing,

同时通过commit_stream发送tuple给TridentSpoutExecutor

8. 收到commit_stream上传来的tuple后,TridentSpoutExecutor会调用ITri-

dentSpout中的emmitter, emmitter::commit()被执行,TridentSpoutEx-

ecutor会再次ack收到tuple

9. MasterBatchCoordinator在收到这个tuple之后,会认为针对某一个seeder

tuple的处理已经完全实现,于是通过SUCCESS_STREM告知TridentSpout-

Coordinator,所有的活都已经都完成了,收工。

 

10. 收到Success_stream上传来的信号后,ITridentSpout中的内嵌子类Em-

mit和Coordinator中相应的success方法会被调用执行。

16.6  seeder tuple的状态机

在MasterBatchCoordinator中,针对每一个seeder tuple,其状态机如下图所

示。注意这些状态是会被保存到zookeeper server中的,使用的api定义在

TransactionalState中。

 

通过上面的分析可以看出,TridentTopology实现了一个比较好的框架,但

真正要做到exactly-once的处理,还需要用户自己去实现ITridentSpout中的两

个重要内嵌类,Emmitter和Coordinator。

具体如何实现该接口,可以查看storm-core/src/jvm/storm/trident/test-

ing目录下的FixedBatchSpout.java和FeederCommitterBatchSpout.java

 

 

16.4 stormRPC

 

 

 

 

动手练习

17. 机器学习

17.1 机器学习概念介绍

Mahout 是一个机器学习 Java 类库的集合,用于完成各种各样的任务,如分类、评价性的聚类和模式挖掘等。

当前存在很多比较好的框架,它们对用户友好并且配置了更多的算法来完成这些任务。

比如,R 社区比从前更加庞大,而在 Java 世界里,可用的 RapidMiner 和 Weka 库已经存在了好多年。

为什么我们要用 Mahout 来代替前面提到的那些框架呢 ? 真正原因在于前面提到的那些框架并不是为大规模数据集设计的。当我们提到大规模数据集中所谓的数据集时,无论是哪种形式,它的记录都是上亿级别的。

 

 

17.2 电商推荐--协同过滤算法

基于物品的推荐

基于用户的推荐

17.3 预测算法--简单线性回归

 

17.4 Mahout机器学习算法库

Mahout简述

Mahout推荐算法实现示例

Mahout线性回归算法实现示例

 

 

动手练习

 

18.  Hadoop综合项目

hadoop综合项目:

--用户行为轨迹分析

背景,流程,架构,技术,模块实战

 

--手机位置实时查询

Storm与redis和结合应用

 

--上网流量详单查询

 

动手练习

19.  Hadoop综合练习

hadoop综合练习--某app网站运营指标分析:

----新增用户(注册,app下载)

----在线时长统计

----活跃用户统计

 

……

动手练习(样例数据分析)

 

 

 

 

 

 

 

20.  Spark基础之scala语言

scala语言基础

变量

函数

模式匹配

隐式转换

集合操作

动手练习

 

21.  Spark基础

21.1 spark框架介绍

1、什么是spark

让数据分析更快的一个框架

BDAS 伯克利数据分析栈

mesos  yarn

hdfs

spark  hadoopMR storm  mpi

spark streaming  graphx  mlbase sparksql  hive

blinkDB

 

spark--一站式处理,搞定所有

 

stream processing      ad-hoc queries   batch  processing

One stack to rule them all!

 

 

3、spark的API

支持3种语言的api

scala

python

java

 

 

5、其实。。。一切都已rdd为基础

弹性分布式数据集

resilient distributed dataset

--a list of partitions

--a function for computing each split

--a list of dependencies on other rdds

--optionally, a partitioner for key-value rdds

(e.g. to say that the rdd is hash-partitioned)

--optionally,a list of preferred locations to compute each split on

(e.g. block locations for an hdfs file)

 

 

6、spark runtime

driver

worker  

流程示意

分布式文件系统 --加载数据集--  transformation  --action触发执行-- action

rdd可以从集合直接转换而来,也可以由先现存的任何hadoop inputformat而来,亦或者hbase等等

 

7、first demo

lines = sc.testFile("")

errors = lines.filter(_.startsWith(""))     //transformation

errors.persist()   //缓存rdd

mysql_err = errors.filter(_.contains("")).count    //action

http_err = errors.filter(_.contains("http")).count

 

 

8、缓存策略

object StorageLevel {

  val NONE = new StorageLevel(false, false, false, false)

  val DISK_ONLY = new StorageLevel(true, false, false, false)

  val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)

  val MEMORY_ONLY = new StorageLevel(false, true, false, true)

  val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)

  val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)

  val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)

  val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)

  val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)

  val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)

  val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)

  val OFF_HEAP = new StorageLevel(false, false, true, false)

 

 

9、transformatition & action

 

--Transformation

map(f:T => U) : RDD[T] => RDD[U]

qfilter(f:T => Bool): RDD[T] => RDD[T]

flatMap

sample

groupByKey

reduceByKey

union

join

cogroup

crossProduct()

mapValues

sort(c:Comparator[K])

partitionBy(p:Partitioner[K])

 

--Action

count()

collect()

reduce(f:(T,T) => T)

lookup(k:K)

save(path:String)

 

 

 

 

 

 

 

21.2 spark集群概念

4、运行spark的模式

local

standalone

mesos

yarn

 

21.3 spark优势简介

回顾hadoop

shuffle机制图

hadoop慢的原因? 磁盘读写?每次迭代的数据共享?

 

hdfs read  --iter.1--> hdfs write -- hdfs read -->iter.2--> hdfs write --> ...

 

对比之下spark快在哪里

迭代之间的数据共享使用内存

DAG  -- 类似于编译原理,让编译器看到的更多,它能做的优化更多

 

21.4 spark集群搭建

1、首先安装scala

2、解压spark-bin安装包,要跟hadoop版本匹配

3、修改spark-env.sh文件

export JAVA_HOME=/home/hadoop/app/jdk1.7.0_65

#指定standalone模式下master所在的主机

export SPARK_MASTER_IP=weekend01

#指定每一个worker上的可用虚拟core数

export SPARK_WORKER_CORES=2

#指定每一个worker上的可用内存

export SPARK_WORKER_MEMORY=1g

#设置hadoop集群的配置文件所在目录

export HADOOP_CONF_DIR=/home/hadoop/app/hadooplk/etc/hadoop

4、修改slaves文件指定worker所在的机器

weekend01

weekend02

weekend03

5、拷贝整个安装目录到各节点

21.5 spark编程基础

(sparkcontext/rdd/map/flatMap等)

21.5.1 spark-shell编程

1、map & filter & collect

scala> val rdd = sc.parallel(List(1,2,3,4,5,6))

scala> val mapRdd = rdd.map(2*_)

scala> mapRdd.collect

scala> val filterRdd = mapRdd.filter(_>5)

 

函数式编程风格,一次搞定

scala> val filterRdd = sc.parallel(List(1,2,3,4,5,6)).map(2*_).filter(_>5).collect

 

2、filterMap & reduceByKey & groupByKey

准备一个wordcount文件

scala> val rdd = sc.textFile("/home/spark/testdata/wc.data")

scala> rdd.cache

scala> rdd.count

scala> val wordcount = rdd.flatMap(_.split(' ')).map((_,1)).reduceByKey(_+_).collect

scala> wordcount.saveAsTextFile("/home/spark/testdata/result.data")

如果要排序

scala> wordcount.sort

如果用groupByKey,就是相同key聚合一下

val wordcount = rdd.flatMap(_.split(' ')).map((_,1))..groupByKey

如果要根据单词次数排序,调换一下kv

val wordcount = rdd.flatMap(_.split(' ')).map((_,1)).reduceByKey(_+_).map(x => (x._2,x._1)).sortByKey(false).map((x._1,x._2)).saveAsTextFile("")

 

 

 

3/ join & lookup

val rdd1 = sc.parallelize(List(('a',1),('a',2),('b',3),('b',4)))

val rdd2 = sc.parallelize(List(('a',5),('a',6),('b',7),('b',8)))

rdd1 join rdd2

 

rdd1.lookup('a')  ## res16:Seq[Int] = WrappedArray(1,2)

 

 

 

4、LineAge

 

5、容错

 

6、dependency

 

21.5.2 ide编程

(1) wordcount

class WordcountDemo {

 

}

/**

 * 要提交到集群运行,需要进行一下步骤操作:

 * 1、代码中应该在sparkconf中指定masterurl

 * 2、再将程序打成jar包

 * 3、将jar包上传到服务器

 * 4、用spark/bin下的spark-submit脚本进行提交

 * bin/spark-submit  --class cn.itcast.bigdata.spark.WordcountDemo \

 * --master spark://weekend01:7077  \

 * --deploy-mode cluster

 * <jar-path>

 * <main-method-params>

 *

 */

object WordcountDemo {

  def main(args: Array[String]): Unit = {

    val conf = new SparkConf

    conf.setMaster("spark://weekend01:7077").setAppName("wordcount")

 

    val sc = new SparkContext(conf)

    val data = "hdfs://weekend01:9000/wordcount/srcdata/"

    val result = "hdfs://weekend01:9000/wordcount/output/"

    

    sc.textFile(data).flatMap { x => x.split(" ") }.map { x => (x,1) }.reduceByKey(_+_)

    .map(x=>(x._2,x._1)).sortByKey().map(x=>(x._2,x._1)).saveAsTextFile(result)

    

    sc.stop()

  }

}

 

(2) sogou搜索日志关键词分析

class SogouQ {

 

}

object SogouQ {

  

  def main(args: Array[String]): Unit = {

    

    val sb = new StringBuilder

    val masterurl = "spark://weekend01:7077"

    val Array(input,wcoutput,ffoutput) = args

    val conf = new SparkConf

    conf.setMaster(masterurl).setAppName("sogouq")

    val sc = new SparkContext(conf)

    val file = sc.textFile(input)

    file.cache()

    

    val count = file.count()

    println("文件总共有" + count + "行")

    

    val wcrdd = file.map { x => x.split("\t") }.filter { x => x(2).contains("汶川地震") }.map { x => x.mkString("\t") }

    

    val count_wc = wcrdd.count

    println("搜索关键字中包含汶川地震的行数为: " + count_wc)

    wcrdd.saveAsTextFile(wcoutput)

    

    val ffrdd = file.map { x => x.split("\t") }.filter { x => x(3).equals("1 1") }.map { x => x.mkString("\t") }

    val count_11 = ffrdd.count

    println("结果排序和用户点击都为第一位的记录条数为: " + count_11)

    ffrdd.saveAsTextFile(ffoutput)

    sc.stop()

  }

}

 

 

 

 

 

 

动手练习(wordcount,字段解析)

 

 

22.  Spark原理深入

22.1 spark-RDD原理深度解析

(RDD,DAG,stage,dependency)

22.2 spark内核源码阅读

22.2.1 akka介绍,demo示例)

 

22.2.2 RDD类  

22.2.3 SparkContext类

Spark技术内幕之任务调度:从SparkContext开始

SparkContext是开发Spark应用的入口,它负责和整个集群的交互,包括创建RDD,accumulators and broadcast variables。理解Spark的架构,需要从这个入口开始。下图是官网的架构图。

 

DriverProgram就是用户提交的程序,这里边定义了SparkContext的实例。SparkContext定义在core/src/main/scala/org/apache/spark/SparkContext.scala。

Spark默认的构造函数接受org.apache.spark.SparkConf, 通过这个参数我们可以自定义本次提交的参数,这个参数会覆盖系统的默认配置。

先上一张与SparkContext相关的类图:

 

下面是SparkContext非常重要的数据成员的定义:

1.// Create and start the scheduler  

2.private[spark] var taskScheduler = SparkContext.createTaskScheduler(this, master)  

3.private val heartbeatReceiver = env.actorSystem.actorOf(  

4.  Props(new HeartbeatReceiver(taskScheduler)), "HeartbeatReceiver")  

5.@volatile private[spark] var dagScheduler: DAGScheduler = _  

6.try {  

7.  dagScheduler = new DAGScheduler(this)  

8.} catch {  

9.  case e: Exception => throw  

10.    new SparkException("DAGScheduler cannot be initialized due to %s".format(e.getMessage))  

11.}  

12.  

13.// start TaskScheduler after taskScheduler sets DAGScheduler reference in DAGScheduler's  

14.// constructor  

15.taskScheduler.start()  

 

通过createTaskScheduler,我们可以获得不同资源管理类型或者部署类型的调度器。看一下现在支持的部署方法:

1./** Creates a task scheduler based on a given master URL. Extracted for testing. */  

2. private def createTaskScheduler(sc: SparkContext, master: String): TaskScheduler = {  

3.   // Regular expression used for local[N] and local[*] master formats  

4.   val LOCAL_N_REGEX = """local

([0−9]+|\*)

5.""".r  

6.   // Regular expression for local[N, maxRetries], used in tests with failing tasks  

7.   val LOCAL_N_FAILURES_REGEX = """local

([0−9]+|\*)\s∗,\s∗([0−9]+)

8.""".r  

9.   // Regular expression for simulating a Spark cluster of [N, cores, memory] locally  

10.   val LOCAL_CLUSTER_REGEX = """local-cluster\[\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*]""".r  

11.   // Regular expression for connecting to Spark deploy clusters  

12.   val SPARK_REGEX = """spark://(.*)""".r  

13.   // Regular expression for connection to Mesos cluster by mesos:// or zk:// url  

14.   val MESOS_REGEX = """(mesos|zk)://.*""".r  

15.   // Regular expression for connection to Simr cluster  

16.   val SIMR_REGEX = """simr://(.*)""".r  

17.  

18.   // When running locally, don't try to re-execute tasks on failure.  

19.   val MAX_LOCAL_TASK_FAILURES = 1  

20.  

21.   master match {  

22.     case "local" =>  

23.       val scheduler = new TaskSchedulerImpl(sc, MAX_LOCAL_TASK_FAILURES, isLocal = true)  

24.       val backend = new LocalBackend(scheduler, 1)  

25.       scheduler.initialize(backend)  

26.       scheduler  

27.  

28.     case LOCAL_N_REGEX(threads) =>  

29.       def localCpuCount = Runtime.getRuntime.availableProcessors()  

30.       // local[*] estimates the number of cores on the machine; local[N] uses exactly N threads.  

31.       val threadCount = if (threads == "*") localCpuCount else threads.toInt  

32.       val scheduler = new TaskSchedulerImpl(sc, MAX_LOCAL_TASK_FAILURES, isLocal = true)  

33.       val backend = new LocalBackend(scheduler, threadCount)  

34.       scheduler.initialize(backend)  

35.       scheduler  

36.  

37.     case LOCAL_N_FAILURES_REGEX(threads, maxFailures) =>  

38.       def localCpuCount = Runtime.getRuntime.availableProcessors()  

39.       // local[*, M] means the number of cores on the computer with M failures  

40.       // local[N, M] means exactly N threads with M failures  

41.       val threadCount = if (threads == "*") localCpuCount else threads.toInt  

42.       val scheduler = new TaskSchedulerImpl(sc, maxFailures.toInt, isLocal = true)  

43.       val backend = new LocalBackend(scheduler, threadCount)  

44.       scheduler.initialize(backend)  

45.       scheduler  

46.  

47.     case SPARK_REGEX(sparkUrl) =>  

48.       val scheduler = new TaskSchedulerImpl(sc)  

49.       val masterUrls = sparkUrl.split(",").map("spark://" + _)  

50.       val backend = new SparkDeploySchedulerBackend(scheduler, sc, masterUrls)  

51.       scheduler.initialize(backend)  

52.       scheduler  

53.  

54.     case LOCAL_CLUSTER_REGEX(numSlaves, coresPerSlave, memoryPerSlave) =>  

55.       // Check to make sure memory requested <= memoryPerSlave. Otherwise Spark will just hang.  

56.       val memoryPerSlaveInt = memoryPerSlave.toInt  

57.       if (sc.executorMemory > memoryPerSlaveInt) {  

58.         throw new SparkException(  

59.           "Asked to launch cluster with %d MB RAM / worker but requested %d MB/worker".format(  

60.             memoryPerSlaveInt, sc.executorMemory))  

61.       }  

62.  

63.       val scheduler = new TaskSchedulerImpl(sc)  

64.       val localCluster = new LocalSparkCluster(  

65.         numSlaves.toInt, coresPerSlave.toInt, memoryPerSlaveInt)  

66.       val masterUrls = localCluster.start()  

67.       val backend = new SparkDeploySchedulerBackend(scheduler, sc, masterUrls)  

68.       scheduler.initialize(backend)  

69.       backend.shutdownCallback = (backend: SparkDeploySchedulerBackend) => {  

70.         localCluster.stop()  

71.       }  

72.       scheduler  

73.  

74.     case "yarn-standalone" | "yarn-cluster" =>  

75.       if (master == "yarn-standalone") {  

76.         logWarning(  

77.           "\"yarn-standalone\" is deprecated as of Spark 1.0. Use \"yarn-cluster\" instead.")  

78.       }  

79.       val scheduler = try {  

80.         val clazz = Class.forName("org.apache.spark.scheduler.cluster.YarnClusterScheduler")  

81.         val cons = clazz.getConstructor(classOf[SparkContext])  

82.         cons.newInstance(sc).asInstanceOf[TaskSchedulerImpl]  

83.       } catch {  

84.         // TODO: Enumerate the exact reasons why it can fail  

85.         // But irrespective of it, it means we cannot proceed !  

86.         case e: Exception => {  

87.           throw new SparkException("YARN mode not available ?", e)  

88.         }  

89.       }  

90.       val backend = try {  

91.         val clazz =  

92.           Class.forName("org.apache.spark.scheduler.cluster.YarnClusterSchedulerBackend")  

93.         val cons = clazz.getConstructor(classOf[TaskSchedulerImpl], classOf[SparkContext])  

94.         cons.newInstance(scheduler, sc).asInstanceOf[CoarseGrainedSchedulerBackend]  

95.       } catch {  

96.         case e: Exception => {  

97.           throw new SparkException("YARN mode not available ?", e)  

98.         }  

99.       }  

100.       scheduler.initialize(backend)  

101.       scheduler  

102.  

103.     case "yarn-client" =>  

104.       val scheduler = try {  

105.         val clazz =  

106.           Class.forName("org.apache.spark.scheduler.cluster.YarnClientClusterScheduler")  

107.         val cons = clazz.getConstructor(classOf[SparkContext])  

108.         cons.newInstance(sc).asInstanceOf[TaskSchedulerImpl]  

109.  

110.       } catch {  

111.         case e: Exception => {  

112.           throw new SparkException("YARN mode not available ?", e)  

113.         }  

114.       }  

115.  

116.       val backend = try {  

117.         val clazz =  

118.           Class.forName("org.apache.spark.scheduler.cluster.YarnClientSchedulerBackend")  

119.         val cons = clazz.getConstructor(classOf[TaskSchedulerImpl], classOf[SparkContext])  

120.         cons.newInstance(scheduler, sc).asInstanceOf[CoarseGrainedSchedulerBackend]  

121.       } catch {  

122.         case e: Exception => {  

123.           throw new SparkException("YARN mode not available ?", e)  

124.         }  

125.       }  

126.  

127.       scheduler.initialize(backend)  

128.       scheduler  

129.  

130.     case mesosUrl @ MESOS_REGEX(_) =>  

131.       MesosNativeLibrary.load()  

132.       val scheduler = new TaskSchedulerImpl(sc)  

133.       val coarseGrained = sc.conf.getBoolean("spark.mesos.coarse", false)  

134.       val url = mesosUrl.stripPrefix("mesos://") // strip scheme from raw Mesos URLs  

135.       val backend = if (coarseGrained) {  

136.         new CoarseMesosSchedulerBackend(scheduler, sc, url)  

137.       } else {  

138.         new MesosSchedulerBackend(scheduler, sc, url)  

139.       }  

140.       scheduler.initialize(backend)  

141.       scheduler  

142.  

143.     case SIMR_REGEX(simrUrl) =>  

144.       val scheduler = new TaskSchedulerImpl(sc)  

145.       val backend = new SimrSchedulerBackend(scheduler, sc, simrUrl)  

146.       scheduler.initialize(backend)  

147.       scheduler  

148.  

149.     case _ =>  

150.       throw new SparkException("Could not parse Master URL: '" + master + "'")  

151.   }  

152. }  

 

主要的逻辑从line 20开始。主要通过传入的Master URL来生成Scheduler 和 Scheduler backend。对于常见的Standalone的部署方式,我们看一下是生成的Scheduler 和 Scheduler backend:

1.case SPARK_REGEX(sparkUrl) =>  

2.  val scheduler = new TaskSchedulerImpl(sc)  

3.  val masterUrls = sparkUrl.split(",").map("spark://" + _)  

4.  val backend = new SparkDeploySchedulerBackend(scheduler, sc, masterUrls)  

5.  scheduler.initialize(backend)  

6.  scheduler  

 

org.apache.spark.scheduler.TaskSchedulerImpl通过一个SchedulerBackend管理了所有的cluster的调度;它主要实现了通用的逻辑。对于系统刚启动时,需要理解两个接口,一个是initialize,一个是start。这个也是在SparkContext初始化时调用的:

1.def initialize(backend: SchedulerBackend) {  

2.  this.backend = backend  

3.  // temporarily set rootPool name to empty  

4.  rootPool = new Pool("", schedulingMode, 0, 0)  

5.  schedulableBuilder = {  

6.    schedulingMode match {  

7.      case SchedulingMode.FIFO =>  

8.        new FIFOSchedulableBuilder(rootPool)  

9.      case SchedulingMode.FAIR =>  

10.        new FairSchedulableBuilder(rootPool, conf)  

11.    }  

12.  }  

13.  schedulableBuilder.buildPools()  

14.}  

 

由此可见,初始化主要是SchedulerBackend的初始化,它主要时通过集群的配置来获得调度模式,现在支持的调度模式是FIFO和公平调度,默认的是FIFO。

1.// default scheduler is FIFO  

2.  private val schedulingModeConf = conf.get("spark.scheduler.mode", "FIFO")  

3.  val schedulingMode: SchedulingMode = try {  

4.    SchedulingMode.withName(schedulingModeConf.toUpperCase)  

5.  } catch {  

6.    case e: java.util.NoSuchElementException =>  

7.      throw new SparkException(s"Unrecognized spark.scheduler.mode: $schedulingModeConf")  

8.  }  

 

start的实现如下:

1.override def start() {  

2.  backend.start()  

3.  

4.  if (!isLocal && conf.getBoolean("spark.speculation", false)) {  

5.    logInfo("Starting speculative execution thread")  

6.    import sc.env.actorSystem.dispatcher  

7.    sc.env.actorSystem.scheduler.schedule(SPECULATION_INTERVAL milliseconds,  

8.          SPECULATION_INTERVAL milliseconds) {  

9.      Utils.tryOrExit { checkSpeculatableTasks() }  

10.    }  

11.  }  

12.}  

 

主要是backend的启动。对于非本地模式,并且设置了spark.speculation为true,那么对于指定时间未返回的task将会启动另外的task来执行。其实对于一般的应用,这个的确可能会减少任务的执行时间,但是也浪费了集群的计算资源。因此对于离线应用来说,这个设置是不推荐的。

 

org.apache.spark.scheduler.cluster.SparkDeploySchedulerBackend是Standalone模式的SchedulerBackend。它的定义如下:

1.private[spark] class SparkDeploySchedulerBackend(  

2.    scheduler: TaskSchedulerImpl,  

3.    sc: SparkContext,  

4.    masters: Array[String])  

5.  extends CoarseGrainedSchedulerBackend(scheduler, sc.env.actorSystem)  

6.  with AppClientListener  

7.  with Logging {  

 

看一下它的start:

1.override def start() {  

2.   super.start()  

3.  

4.   // The endpoint for executors to talk to us  

5.   val driverUrl = "akka.tcp://%s@%s:%s/user/%s".format(  

6.     SparkEnv.driverActorSystemName,  

7.     conf.get("spark.driver.host"),  

8.     conf.get("spark.driver.port"),  

9.     CoarseGrainedSchedulerBackend.ACTOR_NAME)  

10.   val args = Seq(driverUrl, "{{EXECUTOR_ID}}", "{{HOSTNAME}}", "{{CORES}}", "{{WORKER_URL}}")  

11.   val extraJavaOpts = sc.conf.getOption("spark.executor.extraJavaOptions")  

12.     .map(Utils.splitCommandString).getOrElse(Seq.empty)  

13.   val classPathEntries = sc.conf.getOption("spark.executor.extraClassPath").toSeq.flatMap { cp =>  

14.     cp.split(java.io.File.pathSeparator)  

15.   }  

16.   val libraryPathEntries =  

17.     sc.conf.getOption("spark.executor.extraLibraryPath").toSeq.flatMap { cp =>  

18.       cp.split(java.io.File.pathSeparator)  

19.     }  

20.  

21.   // Start executors with a few necessary configs for registering with the scheduler  

22.   val sparkJavaOpts = Utils.sparkJavaOpts(conf, SparkConf.isExecutorStartupConf)  

23.   val javaOpts = sparkJavaOpts ++ extraJavaOpts  

24.   val command = Command("org.apache.spark.executor.CoarseGrainedExecutorBackend",  

25.     args, sc.executorEnvs, classPathEntries, libraryPathEntries, javaOpts)  

26.   val appDesc = new ApplicationDescription(sc.appName, maxCores, sc.executorMemory, command,  

27.     sc.ui.appUIAddress, sc.eventLogger.map(_.logDir))  

28.  

29.   client = new AppClient(sc.env.actorSystem, masters, appDesc, this, conf)  

30.   client.start()  

31.  

32.   waitForRegistration()  

33. }  

 

接下来,我们将对TaskScheduler,SchedulerBackend和DAG Scheduler进行详解,来逐步揭开他们的神秘面纱。

22.3 任务调度流程

22.3.1 stage划分

22.3.2 task提交   

22.3.3 DAGScheduler    

22.3.4 TaskScheduler

动手练习(源码debug跟踪)

23.  Spark-Streaming应用

23.1 spark-Streaming概念

23.2 spark-streaming原理

23.3 streaming应用开发实例

--数据源:hdfsDir flume  avro等

--操作类型:map/flatMap/filter/window/stateful等

23.3.1 hdfs源

class HdfsWordcountStreaming {

 

}

object HdfsWordcountStreaming {

  def main(args: Array[String]): Unit = {

    if (args.length < 1) {

      System.err.println("Usage: HdfsWordcountStreaming <hdfs-path>")

      System.exit(1)

    }

    

    LogLevelSetter.setLevel

    

    val conf = new SparkConf

    conf.setMaster("local[2]").setAppName("hdfswordcount")

    val ssc = new StreamingContext(conf,Seconds(2))

    

    val fileDstream = ssc.textFileStream(args(0))

    val wordsDstream = fileDstream.flatMap { x => x.split(" ") }.map { x => (x,1) }

    val wordcountsDstream = wordsDstream.reduceByKey(_+_)

    

    wordcountsDstream.print()

    

    ssc.start()

    ssc.awaitTermination()

  }

}

23.3.2 flume源

object FlumeEventCount {

  def main(args: Array[String]) {

    if (args.length < 2) {

      System.err.println(

        "Usage: FlumeEventCount <host> <port>")

      System.exit(1)

    }

 

//    StreamingExamples.setStreamingLogLevels()

    LogLevelSetter.setLevel

    //    val Array(host, IntParam(port)) = args

    val Array(host, port) = args

    //    val host = args(0)

    //    val port = args(1).toInt

 

    val batchInterval = Milliseconds(2000)

 

    // Create the context and set the batch size

    val sparkConf = new SparkConf().setMaster("local[2]").setAppName("flumeevent")

 

    val ssc = new StreamingContext(sparkConf, batchInterval)

 

    // Create a flume stream

    val stream = FlumeUtils.createStream(ssc, host, port.toInt, StorageLevel.MEMORY_ONLY_SER_2)

    //    stream.map { x => x.event.getBody().asCharBuffer().get(0)}.print()

    // Print out the count of events received from this server in each batch

    stream.count().map(cnt => "Received " + cnt + " flume events.").print()

    val words = stream.flatMap { x => new String(x.event.getBody().array()).split(" ") }

//    words.print()

    val kvrdd = words.map { x => (x,1) }.reduceByKey(_+_);

    kvrdd.print()

    //    mapstream.saveAsTextFiles("/home/hadoop/ssres/nothing")

 

    ssc.start()

    ssc.awaitTermination()

  }

}

 

23.3.3 socket源

object SocketWordcount {

 

  def main(args: Array[String]): Unit = {

 

    //将log日志级别设置为warn

    LogLevelSetter.setLevel

    

    val conf = new SparkConf()

    conf.setMaster("local[2]").setAppName("socketwordcount").setSparkHome("D:/app-install-packages/spark-1.3.0-bin-hadoop2.4")

 

    //创建一个sparkStreaming的context

    val ssc = new StreamingContext(conf, Seconds(2))

 

    val stDStream = ssc.socketTextStream("localhost", 8888, StorageLevel.MEMORY_AND_DISK)

 

    //切分单词,注意,此处对dtream的操作其实是针对该dstream中一批rdd来操作

    val words = stDStream.flatMap { x => x.split(" ") }.map { x => (x, 1) }

    val wordcounts = words.reduceByKey(_ + _)

    wordcounts.print()

 

    ssc.start()

    ssc.awaitTermination()

 

  }

 

}

 

23.3.4 window操作

class WindowOperationStreaming {

 

}

 

object WindowOperationStreaming {

 

  def main(args: Array[String]): Unit = {

 

    LogLevelSetter.setLevel

    

    val conf = new SparkConf

    conf.setMaster("local[2]").setAppName("windowreduce")

    val ssc = new StreamingContext(conf, Seconds(2))

    ssc.checkpoint(".")

    

    val socketDs = ssc.socketTextStream("localhost", 9999)

    val wordsDs = socketDs.flatMap { x => x.split(" ") }.map { x => (x, 1) }

    val wordcountsDs = wordsDs.reduceByKeyAndWindow(_ + _, _ - _, Seconds(6), Seconds(4))

    val temp = wordcountsDs.map(x=>(x._2,x._1))

    val sortedDs = temp.transform(_.sortByKey(false))

    val resultDs = sortedDs.map(x=>(x._2,x._1))

    

    resultDs.print()

    ssc.start()

    ssc.awaitTermination()

 

  }

 

}

23.3.5 stateful window操作

class StatefulWordcountStreaming {

 

}

 

object StatefulWordcountStreaming {

  

  /**

   * String :   就是我们的单词,也就是key

   * Seq[Int]: 是我们这一batch rdd中的关联到上面那个key的所有values

   * Option[Int]: 是上面那个key所关联的旧的状态值

   * 我们这个程序的逻辑就是将这一批次的values累加,然后再累加到旧的状态值上,就得到当前时间点为止,这个key的最新状态值

   */

  val updateFunc = (iter:Iterator[(String, Seq[Int], Option[Int])] ) => {

    

    iter.flatMap(it=> Some(it._2.sum + it._3.getOrElse(0)).map { x => (it._1, x) })

    

  }

  def main(args: Array[String]): Unit = {

    

    LogLevelSetter.setLevel

    

    val conf = new SparkConf

    conf.setMaster("local[2]").setAppName("statewc")

    val ssc = new StreamingContext(conf,Seconds(2))

    

    ssc.checkpoint(".")

    

    val socketDs = ssc.socketTextStream("localhost", 9999, StorageLevel.MEMORY_AND_DISK)

    

    //构造一个起始状态数据集

    val initialRDD = ssc.sparkContext.parallelize(List(("A",100),("B",200),("C",300),("D",400)))

    

    val wordsDs = socketDs.flatMap { x => x.split(" ") }.map { x => (x,1) }

    val stateDs = wordsDs.updateStateByKey(updateFunc, new HashPartitioner(ssc.sparkContext.defaultParallelism), true,initialRDD)

    

    stateDs.print()

    

    ssc.start()

    ssc.awaitTermination()

  }

}

 

 

动手练习

  

24.  Spark-SQL应用

24.1 sparksql概念

1 Spark SQL是什么----概述

Spark SQL 是一个用来处理结构化数据的spark组件。它提供了一个叫做DataFrames的可编程抽象数据模型,并且可被视为一个分布式的SQL查询引擎。

 

2 Spark SQL的基础数据模型-----DataFrames

DataFrame是由命名列所组织起来的一个分布式数据集合。你可以把它看成是一个关系型数据库的表。

DataFrame可以通过多种来源创建:结构化数据文件,hive的表,外部数据库,或者RDDs

 

3 Spark SQL如何使用

首先,利用sqlContext从外部数据源加载数据为DataFrame

然后,利用DataFrame上丰富的api进行查询、转换

最后,将结果进行展现或存储为各种外部数据形式

如图所示:

 

 

24.2 sparksql开发示例

加载数据

sqlContext支持从各种各样的数据源中创建DataFrame,内置支持的数据源有parquetFile,jsonFile,外部数据库,hive表,RDD等,另外,hbase等数据源的支持也在社区不断涌现

# 从Hive中的users表构造DataFrame

users = sqlContext.table("users")

 

# 加载S3上的JSON文件

logs = sqlContext.load("s3n://path/to/data.json", "json")

 

# 加载HDFS上的Parquet文件

clicks = sqlContext.load("hdfs://path/to/data.parquet", "parquet")

 

# 通过JDBC访问MySQL

comments = sqlContext.jdbc("jdbc:mysql://localhost/comments", "user")

 

# 将普通RDD转变为DataFrame

rdd = sparkContext.textFile("article.txt") \

                  .flatMap(_.split(" ")) \

                  .map((_, 1)) \

                  .reduceByKey(_+_) \

wordCounts = sqlContext.createDataFrame(rdd, ["word", "count"])

 

# 将本地数据容器转变为DataFrame

data = [("Alice", 21), ("Bob", 24)]

people = sqlContext.createDataFrame(data, ["name", "age"])

 

# 将Pandas DataFrame转变为Spark DataFrame(Python API特有功能)

sparkDF = sqlContext.createDataFrame(pandasDF)

 

 

 

使用DataFrame

和R、Pandas类似,Spark DataFrame也提供了一整套用于操纵数据的DSL。这些DSL在语义上与SQL关系查询非常相近(这也是Spark SQL能够为DataFrame提供无缝支持的重要原因之一)。以下是一组用户数据分析示例:

# 创建一个只包含年龄小于21岁用户的DataFrame

young = users.filter(users.age < 21)

 

# 也可以使用Pandas风格的语法

young = users[users.age < 21]

 

# 将所有人的年龄加1

young.select(young.name, young.age + 1)

 

# 统计年轻用户中各性别人数

young.groupBy("gender").count()

 

# 将所有年轻用户与另一个名为logs的DataFrame联接起来

young.join(logs, logs.userId == users.userId, "left_outer")

 

除DSL以外,我们当然也可以使用熟悉的SQL来处理DataFrame:

young.registerTempTable("young")

sqlContext.sql("SELECT count(*) FROM young")

 

 

保存结果

对数据的分析完成之后,可以将结果保存在多种形式的外部存储中

# 追加至HDFS上的Parquet文件

young.save(path="hdfs://path/to/data.parquet", source="parquet", mode="append")

 

# 覆写S3上的JSON文件

young.save(path="s3n://path/to/data.json", source="json",mode="append")

 

# 保存为Hive的内部表

young.saveAsTable(tableName="young", source="parquet" mode="overwrite")

 

# 转换为Pandas DataFrame(Python API特有功能)

pandasDF = young.toPandas()

 

# 以表格形式打印输出

young.show()

 

 

24.3 MLlib介绍

动手练习

 

25.  Spark综合练习

spark重写mr练习题

广播变量等的应用

重写sca日志增强

spark高级编程接口(自定义hadoopFile加载等)

动手练习

 

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值