优化阅读格式

This commit is contained in:
heibaiying
2019-07-31 17:18:07 +08:00
parent ceb868fe13
commit ca7c99802b
91 changed files with 4059 additions and 4058 deletions

View File

@ -14,24 +14,24 @@
## 一、简介
Azkaban主要通过界面上传配置文件来进行任务的调度。它有两个重要的概念
Azkaban 主要通过界面上传配置文件来进行任务的调度。它有两个重要的概念:
- **Job** 你需要执行的调度任务;
- **Flow**一个获取多个Job及它们之间的依赖关系所组成的图表叫做Flow。
- **Flow**:一个获取多个 Job 及它们之间的依赖关系所组成的图表叫做 Flow。
目前 Azkaban 3.x 同时支持 Flow 1.0 和 Flow 2.0,本文主要讲解 Flow 1.0的使用下一篇文章会讲解Flow 2.0的使用。
目前 Azkaban 3.x 同时支持 Flow 1.0 和 Flow 2.0,本文主要讲解 Flow 1.0 的使用,下一篇文章会讲解 Flow 2.0 的使用。
## 二、基本任务调度
### 2.1 新建项目
在Azkaban主界面可以创建对应的项目
Azkaban 主界面可以创建对应的项目:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-create-project.png"/> </div>
### 2.2 任务配置
新建任务配置文件`Hello-Azkaban.job`,内容如下。这里的任务很简单,就是输出一句`'Hello Azkaban!'`
新建任务配置文件 `Hello-Azkaban.job`,内容如下。这里的任务很简单,就是输出一句 `'Hello Azkaban!'`
```shell
#command.job
@ -41,27 +41,27 @@ command=echo 'Hello Azkaban!'
### 2.3 打包上传
`Hello-Azkaban.job `打包为`zip`压缩文件:
`Hello-Azkaban.job ` 打包为 `zip` 压缩文件:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-zip.png"/> </div>
通过Web UI 界面上传:
通过 Web UI 界面上传:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-upload.png"/> </div>
上传成功后可以看到对应的Flows
上传成功后可以看到对应的 Flows
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-flows.png"/> </div>
### 2.4 执行任务
点击页面上的`Execute Flow`执行任务:
点击页面上的 `Execute Flow` 执行任务:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-execute.png"/> </div>
### 2.5 执行结果
点击`detail`可以查看到任务的执行日志:
点击 `detail` 可以查看到任务的执行日志:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-successed.png"/> </div>
@ -71,7 +71,7 @@ command=echo 'Hello Azkaban!'
### 3.1 依赖配置
这里假设我们有五个任务TaskA——TaskE,D 任务需要在ABC任务执行完成后才能执行而 E 任务则需要在 D 任务执行完成后才能执行,这种情况下需要使用`dependencies`属性定义其依赖关系。各任务配置如下:
这里假设我们有五个任务TaskA——TaskE,D 任务需要在 ABC 任务执行完成后才能执行,而 E 任务则需要在 D 任务执行完成后才能执行,这种情况下需要使用 `dependencies` 属性定义其依赖关系。各任务配置如下:
**Task-A.job** :
@ -112,13 +112,13 @@ dependencies=Task-D
### 3.2 压缩上传
压缩后进行上传这里需要注意的是一个Project只能接收一个压缩包这里我还沿用上面的Project默认后面的压缩包会覆盖前面的压缩包
压缩后进行上传,这里需要注意的是一个 Project 只能接收一个压缩包,这里我还沿用上面的 Project默认后面的压缩包会覆盖前面的压缩包
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-task-abcde-zip.png"/> </div>
### 3.3 依赖关系
多个任务存在依赖时默认采用最后一个任务的文件名作为Flow的名称其依赖关系如图
多个任务存在依赖时,默认采用最后一个任务的文件名作为 Flow 的名称,其依赖关系如图:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-dependencies.png"/> </div>
@ -126,11 +126,11 @@ dependencies=Task-D
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-task-abcde.png"/> </div>
从这个案例可以看出Flow1.0无法通过一个job文件来完成多个任务的配置但是Flow 2.0 就很好的解决了这个问题。
从这个案例可以看出Flow1.0 无法通过一个 job 文件来完成多个任务的配置,但是 Flow 2.0 就很好的解决了这个问题。
## 四、调度HDFS作业
步骤与上面的步骤一致这里以查看HDFS上的文件列表为例。命令建议采用完整路径配置文件如下
步骤与上面的步骤一致,这里以查看 HDFS 上的文件列表为例。命令建议采用完整路径,配置文件如下:
```shell
type=command
@ -143,7 +143,7 @@ command=/usr/app/hadoop-2.6.0-cdh5.15.2/bin/hadoop fs -ls /
## 五、调度MR作业
MR作业配置
MR 作业配置:
```shell
type=command
@ -163,7 +163,7 @@ type=command
command=/usr/app/hive-1.1.0-cdh5.15.2/bin/hive -f 'test.sql'
```
其中`test.sql`内容如下,创建一张雇员表,然后查看其结构:
其中 `test.sql` 内容如下,创建一张雇员表,然后查看其结构:
```sql
CREATE DATABASE IF NOT EXISTS hive;
@ -179,11 +179,11 @@ sal double,
comm double,
deptno int
) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';
-- 查看emp表的信息
-- 查看 emp 表的信息
desc emp;
```
打包的时候将`job`文件与`sql`文件一并进行打包:
打包的时候将 `job` 文件与 `sql` 文件一并进行打包:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-hive.png"/> </div>
@ -193,11 +193,11 @@ desc emp;
## 七、在线修改作业配置
在测试时我们可能需要频繁修改配置如果每次修改都要重新打包上传这会比较麻烦。所以Azkaban支持配置的在线修改点击需要修改的Flow就可以进入详情页面
在测试时,我们可能需要频繁修改配置,如果每次修改都要重新打包上传,这会比较麻烦。所以 Azkaban 支持配置的在线修改,点击需要修改的 Flow就可以进入详情页面
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-project-edit.png"/> </div>
在详情页面点击`Eidt`按钮可以进入编辑页面:
在详情页面点击 `Eidt` 按钮可以进入编辑页面:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-edit.png"/> </div>
@ -207,7 +207,7 @@ desc emp;
## 附:可能出现的问题
如果出现以下异常多半是因为执行主机内存不足Azkaban要求执行主机的可用内存必须大于3G才能执行任务
如果出现以下异常多半是因为执行主机内存不足Azkaban 要求执行主机的可用内存必须大于 3G 才能执行任务:
```shell
Cannot request memory (Xms 0 kb, Xmx 0 kb) from system for job
@ -215,7 +215,7 @@ Cannot request memory (Xms 0 kb, Xmx 0 kb) from system for job
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-memory.png"/> </div>
如果你的执行主机没办法增大内存,那么可以通过修改`plugins/jobtypes/`目录下的`commonprivate.properties` 文件来关闭内存检查,配置如下:
如果你的执行主机没办法增大内存,那么可以通过修改 `plugins/jobtypes/` 目录下的 `commonprivate.properties` 文件来关闭内存检查,配置如下:
```shell
memCheck.enabled=false

View File

@ -13,20 +13,20 @@
### 1.1 Flow 2.0 的产生
Azkaban 目前同时支持 Flow 1.0 和 Flow2.0 但是官方文档上更推荐使用Flow 2.0因为Flow 1.0会在将来的版本被移除。Flow 2.0的主要设计思想是提供1.0所没有的流级定义。用户可以将属于给定流的所有`job / properties`文件合并到单个流定义文件中其内容采用YAML语法进行定义同时还支持在流中再定义流称为为嵌入流或子流。
Azkaban 目前同时支持 Flow 1.0 和 Flow2.0 ,但是官方文档上更推荐使用 Flow 2.0,因为 Flow 1.0 会在将来的版本被移除。Flow 2.0 的主要设计思想是提供 1.0 所没有的流级定义。用户可以将属于给定流的所有 `job / properties` 文件合并到单个流定义文件中,其内容采用 YAML 语法进行定义,同时还支持在流中再定义流,称为为嵌入流或子流。
### 1.2 基本结构
项目zip将包含多个流YAML文件一个项目YAML文件以及可选库和源代码。Flow YAML文件的基本结构如下
项目 zip 将包含多个流 YAML 文件,一个项目 YAML 文件以及可选库和源代码。Flow YAML 文件的基本结构如下:
+ 每个Flow都在单个YAML文件中定义
+ 每个 Flow 都在单个 YAML 文件中定义;
+ 流文件以流名称命名,如:`my-flow-name.flow`
+ 包含DAG中的所有节点
+ 包含 DAG 中的所有节点;
+ 每个节点可以是作业或流程;
+ 每个节点 可以拥有 name, type, config, dependsOn 和 nodes sections等属性
+ 通过列出dependsOn列表中的父节点来指定节点依赖性
+ 每个节点 可以拥有 name, type, config, dependsOn 和 nodes sections 等属性;
+ 通过列出 dependsOn 列表中的父节点来指定节点依赖性;
+ 包含与流相关的其他配置;
+ 当前properties文件中流的所有常见属性都将迁移到每个流YAML文件中的config部分。
+ 当前 properties 文件中流的所有常见属性都将迁移到每个流 YAML 文件中的 config 部分。
官方提供了一个比较完善的配置样例,如下:
@ -112,7 +112,7 @@ nodes:
## 二、YAML语法
想要使用 Flow 2.0 进行工作流的配置首先需要了解YAML 。YAML 是一种简洁的非标记语言有着严格的格式要求的如果你的格式配置失败上传到Azkaban的时候就会抛出解析异常。
想要使用 Flow 2.0 进行工作流的配置,首先需要了解 YAML 。YAML 是一种简洁的非标记语言,有着严格的格式要求的,如果你的格式配置失败,上传到 Azkaban 的时候就会抛出解析异常。
### 2.1 基本规则
@ -121,7 +121,7 @@ nodes:
3. 缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级;
4. 使用#表示注释
5. 字符串默认不用加单双引号,但单引号和双引号都可以使用,双引号表示不需要对特殊字符进行转义;
6. YAML中提供了多种常量结构包括整数浮点数字符串NULL日期布尔时间。
6. YAML 中提供了多种常量结构包括整数浮点数字符串NULL日期布尔时间。
### 2.2 对象的写法
@ -159,20 +159,20 @@ key:
支持单引号和双引号,但双引号不会对特殊字符进行转义:
```yaml
s1: '内容\n字符串'
s2: "内容\n字符串"
s1: '内容\n 字符串'
s2: "内容\n 字符串"
转换后:
{ s1: '内容\\n字符串', s2: '内容\n字符串' }
{ s1: '内容\\n 字符串', s2: '内容\n 字符串' }
```
### 2.6 特殊符号
一个YAML文件中可以包括多个文档使用`---`进行分割。
一个 YAML 文件中可以包括多个文档,使用 `---` 进行分割。
### 2.7 配置引用
Flow 2.0 建议将公共参数定义在`config`下,并通过`${}`进行引用。
Flow 2.0 建议将公共参数定义在 `config` 下,并通过 `${}` 进行引用。
@ -180,7 +180,7 @@ Flow 2.0 建议将公共参数定义在`config`下,并通过`${}`进行引用
### 3.1 任务配置
新建`flow`配置文件:
新建 `flow` 配置文件:
```yaml
nodes:
@ -190,7 +190,7 @@ nodes:
command: echo "Hello Azkaban Flow 2.0."
```
在当前的版本中Azkaban同时支持 Flow 1.0 和 Flow 2.0如果你希望以2.0的方式运行,则需要新建一个`project`文件指明是使用的是Flow 2.0
在当前的版本中Azkaban 同时支持 Flow 1.0 和 Flow 2.0,如果你希望以 2.0 的方式运行,则需要新建一个 `project` 文件,指明是使用的是 Flow 2.0
```shell
azkaban-flow-version: 2.0
@ -204,13 +204,13 @@ azkaban-flow-version: 2.0
### 3.3 执行结果
由于在1.0 版本中已经介绍过Web UI的使用这里就不再赘述。对于1.02.0版本,只有配置方式有所不同,其他上传执行的方式都是相同的。执行结果如下:
由于在 1.0 版本中已经介绍过 Web UI 的使用,这里就不再赘述。对于 1.02.0 版本,只有配置方式有所不同,其他上传执行的方式都是相同的。执行结果如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-simle-result.png"/> </div>
## 四、多任务调度
和1.0给出的案例一样这里假设我们有五个任务jobA——jobE, D 任务需要在ABC任务执行完成后才能执行而 E 任务则需要在 D 任务执行完成后才能执行相关配置文件应如下。可以看到在1.0中我们需要分别定义五个配置文件而在2.0中我们只需要一个配置文件即可完成配置。
1.0 给出的案例一样这里假设我们有五个任务jobA——jobE, D 任务需要在 ABC 任务执行完成后才能执行,而 E 任务则需要在 D 任务执行完成后才能执行,相关配置文件应如下。可以看到在 1.0 中我们需要分别定义五个配置文件,而在 2.0 中我们只需要一个配置文件即可完成配置。
```yaml
nodes:
@ -250,7 +250,7 @@ nodes:
## 五、内嵌流
Flow2.0 支持在一个Flow中定义另一个Flow称为内嵌流或者子流。这里给出一个内嵌流的示例`Flow`配置如下:
Flow2.0 支持在一个 Flow 中定义另一个 Flow称为内嵌流或者子流。这里给出一个内嵌流的示例 `Flow` 配置如下:
```yaml
nodes:
@ -279,7 +279,7 @@ nodes:
command: echo "This is job A"
```
内嵌流的DAG图如下
内嵌流的 DAG 图如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-embeded-flow.png"/> </div>

View File

@ -5,32 +5,32 @@
#### 1.1 背景
一个完整的大数据分析系统,必然由很多任务单元(如数据收集、数据清洗、数据存储、数据分析等)组成,所有的任务单元及其之间的依赖关系组成了复杂的工作流。复杂的工作流管理涉及到很多问题:
一个完整的大数据分析系统,必然由很多任务单元 (如数据收集、数据清洗、数据存储、数据分析等) 组成,所有的任务单元及其之间的依赖关系组成了复杂的工作流。复杂的工作流管理涉及到很多问题:
- 如何定时调度某个任务?
- 如何在某个任务执行完成后再去执行另一个任务?
- 如何在任务失败时候发出预警?
- ......
面对这些问题工作流调度系统应运而生。Azkaban就是其中之一。
面对这些问题工作流调度系统应运而生。Azkaban 就是其中之一。
#### 1.2 功能
Azkaban产生于LinkedIn并经过多年生产环境的检验它具备以下功能
Azkaban 产生于 LinkedIn并经过多年生产环境的检验它具备以下功能
- 兼容任何版本的Hadoop
- 易于使用的Web UI
- 可以使用简单的Web页面进行工作流上传
- 兼容任何版本的 Hadoop
- 易于使用的 Web UI
- 可以使用简单的 Web 页面进行工作流上传
- 支持按项目进行独立管理
- 定时任务调度
- 模块化和可插入
- 身份验证和授权
- 跟踪用户操作
- 支持失败和成功的电子邮件提醒
- SLA警报和自动查杀失败任务
- SLA 警报和自动查杀失败任务
- 重试失败的任务
Azkaban的设计理念是在保证功能实现的基础上兼顾易用性其页面风格清晰明朗下面是其WEB UI界面
Azkaban 的设计理念是在保证功能实现的基础上兼顾易用性,其页面风格清晰明朗,下面是其 WEB UI 界面:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-web.png"/> </div>
@ -40,26 +40,26 @@ Azkaban 和 Oozie 都是目前使用最为广泛的工作流调度程序,其
#### 功能对比
- 两者均可以调度Linux命令、MapReduce、Spark、Pig、Java、Hive等工作流任务
- 两者均可以调度 Linux 命令、MapReduce、Spark、Pig、Java、Hive 等工作流任务;
- 两者均可以定时执行工作流任务。
#### 工作流定义
- Azkaban使用Properties(Flow 1.0)YAML(Flow 2.0)文件定义工作流;
- Oozie使用Hadoop流程定义语言hadoop process defination languageHPDL来描述工作流HPDL是一种XML流程定义语言。
- Azkaban 使用 Properties(Flow 1.0)YAML(Flow 2.0) 文件定义工作流;
- Oozie 使用 Hadoop 流程定义语言hadoop process defination languageHPDL来描述工作流HPDL 是一种 XML 流程定义语言。
#### 资源管理
- Azkaban有较严格的权限控制如用户对工作流进行读/写/执行等操作;
- Oozie暂无严格的权限控制。
- Azkaban 有较严格的权限控制,如用户对工作流进行读/写/执行等操作;
- Oozie 暂无严格的权限控制。
#### 运行模式
+ Azkaban 3.x 提供了两种运行模式:
+ **solo server model(单服务模式)** 元数据默认存放在内置的H2数据库可以修改为MySQL该模式中`webServer`(管理服务器)和 `executorServer`(执行服务器)运行在同一个进程中,进程名是`AzkabanSingleServer`。该模式适用于小规模工作流的调度。
+ **multiple-executor(分布式多服务模式)** 存放元数据的数据库为MySQLMySQL应采用主从模式进行备份和容错。这种模式下`webServer``executorServer`在不同进程中运行,彼此之间互不影响,适合用于生产环境。
+ **solo server model(单服务模式)** :元数据默认存放在内置的 H2 数据库(可以修改为 MySQL该模式中 `webServer`(管理服务器) `executorServer`(执行服务器) 运行在同一个进程中,进程名是 `AzkabanSingleServer`。该模式适用于小规模工作流的调度。
+ **multiple-executor(分布式多服务模式)** :存放元数据的数据库为 MySQLMySQL 应采用主从模式进行备份和容错。这种模式下 `webServer``executorServer` 在不同进程中运行,彼此之间互不影响,适合用于生产环境。
+ Oozie使用TomcatWeb容器来展示Web页面默认使用derby存储工作流的元数据由于derby过于轻量实际使用中通常用MySQL代替。
+ Oozie 使用 TomcatWeb 容器来展示 Web 页面,默认使用 derby 存储工作流的元数据,由于 derby 过于轻量,实际使用中通常用 MySQL 代替。
@ -67,10 +67,10 @@ Azkaban 和 Oozie 都是目前使用最为广泛的工作流调度程序,其
## 三、总结
如果你的工作流不是特别复杂推荐使用轻量级的Azkaban主要有以下原因
如果你的工作流不是特别复杂,推荐使用轻量级的 Azkaban主要有以下原因
+ **安装方面**Azkaban 3.0 之前都是提供安装包的直接解压部署即可。Azkaban 3.0 之后的版本需要编译这个编译是基于gradle的自动化程度比较高
+ **安装方面**Azkaban 3.0 之前都是提供安装包的直接解压部署即可。Azkaban 3.0 之后的版本需要编译,这个编译是基于 gradle 的,自动化程度比较高;
+ **页面设计**:所有任务的依赖关系、执行结果、执行日志都可以从界面上直观查看到;
+ **配置方面**Azkaban Flow 1.0 基于Properties文件来定义工作流这个时候的限制可能会多一点。但是在Flow 2.0 就支持了YARM。YARM语法更加灵活简单著名的微服务框架Spring Boot就采用的YAML代替了繁重的XML。
+ **配置方面**Azkaban Flow 1.0 基于 Properties 文件来定义工作流,这个时候的限制可能会多一点。但是在 Flow 2.0 就支持了 YARM。YARM 语法更加灵活简单,著名的微服务框架 Spring Boot 就采用的 YAML 代替了繁重的 XML。

View File

@ -15,7 +15,7 @@
先说一下,为什么要使用 Flume + Kafka
以实时流处理项目为例由于采集的数据量可能存在峰值和峰谷假设是一个电商项目那么峰值通常出现在秒杀时这时如果直接将Flume聚合后的数据输入到Storm等分布式计算框架中可能就会超过集群的处理能力这时采用Kafka就可以起到削峰的作用。Kafka天生为大数据场景而设计具有高吞吐的特性能很好地抗住峰值数据的冲击。
以实时流处理项目为例,由于采集的数据量可能存在峰值和峰谷,假设是一个电商项目,那么峰值通常出现在秒杀时,这时如果直接将 Flume 聚合后的数据输入到 Storm 等分布式计算框架中,可能就会超过集群的处理能力,这时采用 Kafka 就可以起到削峰的作用。Kafka 天生为大数据场景而设计,具有高吞吐的特性,能很好地抗住峰值数据的冲击。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/flume-kafka.png"/> </div>
@ -23,11 +23,11 @@
## 二、整合流程
Flume发送数据到Kafka上主要是通过`KafkaSink`来实现的,主要步骤如下:
Flume 发送数据到 Kafka 上主要是通过 `KafkaSink` 来实现的,主要步骤如下:
### 1. 启动Zookeeper和Kafka
这里启动一个单节点的Kafka作为测试
这里启动一个单节点的 Kafka 作为测试:
```shell
# 启动Zookeeper
@ -39,7 +39,7 @@ bin/kafka-server-start.sh config/server.properties
### 2. 创建主题
创建一个主题`flume-kafka`之后Flume收集到的数据都会发到这个主题上
创建一个主题 `flume-kafka`,之后 Flume 收集到的数据都会发到这个主题上:
```shell
# 创建主题
@ -56,7 +56,7 @@ bin/kafka-topics.sh --zookeeper hadoop001:2181 --list
### 3. 启动kafka消费者
启动一个消费者,监听我们刚才创建的`flume-kafka`主题:
启动一个消费者,监听我们刚才创建的 `flume-kafka` 主题:
```shell
# bin/kafka-console-consumer.sh --bootstrap-server hadoop001:9092 --topic flume-kafka
@ -66,7 +66,7 @@ bin/kafka-topics.sh --zookeeper hadoop001:2181 --list
### 4. 配置Flume
新建配置文件`exec-memory-kafka.properties`,文件内容如下。这里我们监听一个名为`kafka.log`的文件当文件内容有变化时将新增加的内容发送到Kafka`flume-kafka`主题上。
新建配置文件 `exec-memory-kafka.properties`,文件内容如下。这里我们监听一个名为 `kafka.log` 的文件,当文件内容有变化时,将新增加的内容发送到 Kafka`flume-kafka` 主题上。
```properties
a1.sources = s1
@ -107,10 +107,10 @@ flume-ng agent \
### 6. 测试
向监听的`/tmp/kafka.log `文件中追加内容查看Kafka消费者的输出
向监听的 `/tmp/kafka.log ` 文件中追加内容,查看 Kafka 消费者的输出:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/flume-kafka-01.png"/> </div>
可以看到`flume-kafka`主题的消费端已经收到了对应的消息:
可以看到 `flume-kafka` 主题的消费端已经收到了对应的消息:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/flume-kafka-2.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/flume-kafka-2.png"/> </div>

View File

@ -15,11 +15,11 @@
## 一、Flume简介
Apache Flume是一个分布式高可用的数据收集系统。它可以从不同的数据源收集数据经过聚合后发送到存储系统中通常用于日志数据的收集。Flume 分为 NG 和 OG (1.0 之前)两个版本NGOG的基础上进行了完全的重构是目前使用最为广泛的版本。下面的介绍均以NG为基础。
Apache Flume 是一个分布式高可用的数据收集系统。它可以从不同的数据源收集数据经过聚合后发送到存储系统中通常用于日志数据的收集。Flume 分为 NG 和 OG (1.0 之前) 两个版本NGOG 的基础上进行了完全的重构,是目前使用最为广泛的版本。下面的介绍均以 NG 为基础。
## 二、Flume架构和基本概念
下图为Flume的基本架构图
下图为 Flume 的基本架构图:
@ -27,7 +27,7 @@ Apache Flume是一个分布式高可用的数据收集系统。它可以从
### 2.1 基本架构
外部数据源以特定格式向Flume发送`events` (事件),当`source`接收到`events`时,它将其存储到一个或多个`channel``channe`会一直保存`events`直到它被`sink`所消费。`sink`的主要功能从`channel`中读取`events`,并将其存入外部存储系统或转发到下一个`source`,成功后再从`channel`中移除`events`
外部数据源以特定格式向 Flume 发送 `events` (事件),当 `source` 接收到 `events` 时,它将其存储到一个或多个 `channel``channe` 会一直保存 `events` 直到它被 `sink` 所消费。`sink` 的主要功能从 `channel` 中读取 `events`,并将其存入外部存储系统或转发到下一个 `source`,成功后再从 `channel` 中移除 `events`
@ -35,40 +35,40 @@ Apache Flume是一个分布式高可用的数据收集系统。它可以从
**1. Event**
`Evnet`Flume NG数据传输的基本单元。类似于JMS和消息系统中的消息。一个`Evnet`由标题和正文组成:前者是键/值映射,后者是任意字节数组。
`Evnet`Flume NG 数据传输的基本单元。类似于 JMS 和消息系统中的消息。一个 `Evnet` 由标题和正文组成:前者是键/值映射,后者是任意字节数组。
**2. Source**
数据收集组件从外部数据源收集数据并存储到Channel中。
数据收集组件,从外部数据源收集数据,并存储到 Channel 中。
**3. Channel**
`Channel`是源和接收器之间的管道,用于临时存储数据。可以是内存或持久化的文件系统:
`Channel` 是源和接收器之间的管道,用于临时存储数据。可以是内存或持久化的文件系统:
+ `Memory Channel` : 使用内存,优点是速度快,但数据可能会丢失(如突然宕机)
+ `Memory Channel` : 使用内存,优点是速度快,但数据可能会丢失 (如突然宕机)
+ `File Channel` : 使用持久化的文件系统,优点是能保证数据不丢失,但是速度慢。
**4. Sink**
`Sink`的主要功能从`Channel`中读取`Evnet`,并将其存入外部存储系统或将其转发到下一个`Source`,成功后再从`Channel`中移除`Event`
`Sink` 的主要功能从 `Channel` 中读取 `Evnet`,并将其存入外部存储系统或将其转发到下一个 `Source`,成功后再从 `Channel` 中移除 `Event`
**5. Agent**
是一个独立的(JVM)进程,包含`Source``Channel``Sink`等组件。
是一个独立的 (JVM) 进程,包含 `Source``Channel``Sink` 等组件。
### 2.3 组件种类
Flume中的每一个组件都提供了丰富的类型适用于不同场景
Flume 中的每一个组件都提供了丰富的类型,适用于不同场景:
- Source类型 :内置了几十种类型,如`Avro Source``Thrift Source``Kafka Source``JMS Source`
- Source 类型 :内置了几十种类型,如 `Avro Source``Thrift Source``Kafka Source``JMS Source`
- Sink类型 `HDFS Sink``Hive Sink``HBaseSinks``Avro Sink`等;
- Sink 类型 `HDFS Sink``Hive Sink``HBaseSinks``Avro Sink` 等;
- Channel类型 `Memory Channel``JDBC Channel``Kafka Channel``File Channel`等。
- Channel 类型 `Memory Channel``JDBC Channel``Kafka Channel``File Channel` 等。
对于Flume的使用除非有特别的需求否则通过组合内置的各种类型的SourceSinkChannel就能满足大多数的需求。在 [Flume官网](http://flume.apache.org/releases/content/1.9.0/FlumeUserGuide.html)上对所有类型组件的配置参数均以表格的方式做了详尽的介绍并附有配置样例同时不同版本的参数可能略有所不同所以使用时建议选取官网对应版本的User Guide作为主要参考资料。
对于 Flume 的使用,除非有特别的需求,否则通过组合内置的各种类型的 SourceSinkChannel 就能满足大多数的需求。在 [Flume 官网](http://flume.apache.org/releases/content/1.9.0/FlumeUserGuide.html) 上对所有类型组件的配置参数均以表格的方式做了详尽的介绍,并附有配置样例;同时不同版本的参数可能略有所不同,所以使用时建议选取官网对应版本的 User Guide 作为主要参考资料。
@ -84,7 +84,7 @@ Flume 支持多种架构模式,分别介绍如下
<br/>
Flume支持跨越多个Agent的数据传递这要求前一个AgentSink和下一个AgentSource都必须是`Avro`类型Sink指向Source所在主机名(或IP地址)和端口(详细配置见下文案例三)。
Flume 支持跨越多个 Agent 的数据传递,这要求前一个 AgentSink 和下一个 AgentSource 都必须是 `Avro` 类型Sink 指向 Source 所在主机名 (或 IP 地址) 和端口(详细配置见下文案例三)。
### 3.2 Consolidation
@ -94,21 +94,21 @@ Flume支持跨越多个Agent的数据传递这要求前一个Agent的Sink和
<br/>
日志收集中常常存在大量的客户端比如分布式web服务Flume支持使用多个Agent分别收集日志然后通过一个或者多个Agent聚合后再存储到文件系统中。
日志收集中常常存在大量的客户端(比如分布式 web 服务Flume 支持使用多个 Agent 分别收集日志,然后通过一个或者多个 Agent 聚合后再存储到文件系统中。
### 3.3 Multiplexing the flow
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/flume-multiplexing-the-flow.png"/> </div>
Flume支持从一个Source向多个Channel也就是向多个Sink传递事件这个操作称之为`Fan Out`(扇出)。默认情况下`Fan Out`是向所有的Channel复制`Event`即所有Channel收到的数据都是相同的。同时Flume也支持在`Source`上自定义一个复用选择器(multiplexing selector) 来实现自定义的路由规则。
Flume 支持从一个 Source 向多个 Channel也就是向多个 Sink 传递事件,这个操作称之为 `Fan Out`(扇出)。默认情况下 `Fan Out` 是向所有的 Channel 复制 `Event`,即所有 Channel 收到的数据都是相同的。同时 Flume 也支持在 `Source` 上自定义一个复用选择器 (multiplexing selector) 来实现自定义的路由规则。
## 四、Flume配置格式
Flume配置通常需要以下两个步骤
Flume 配置通常需要以下两个步骤:
1. 分别定义好AgentSourcesSinksChannels然后将SourcesSinks与通道进行绑定。需要注意的是一个Source可以配置多个Channel但一个Sink只能配置一个Channel。基本格式如下
1. 分别定义好 AgentSourcesSinksChannels然后将 SourcesSinks 与通道进行绑定。需要注意的是一个 Source 可以配置多个 Channel但一个 Sink 只能配置一个 Channel。基本格式如下
```shell
<Agent>.sources = <Source>
@ -122,7 +122,7 @@ Flume配置通常需要以下两个步骤
<Agent>.sinks.<Sink>.channel = <Channel1>
```
2. 分别定义SourceSinkChannel的具体属性。基本格式如下
2. 分别定义 SourceSinkChannel 的具体属性。基本格式如下:
```shell
@ -139,29 +139,29 @@ Flume配置通常需要以下两个步骤
## 五、Flume的安装部署
为方便大家后期查阅本仓库中所有软件的安装均单独成篇Flume的安装见
为方便大家后期查阅本仓库中所有软件的安装均单独成篇Flume 的安装见:
[Linux环境下Flume的安装部署](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Linux%E4%B8%8BFlume%E7%9A%84%E5%AE%89%E8%A3%85.md)
[Linux 环境下 Flume 的安装部署](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Linux%E4%B8%8BFlume%E7%9A%84%E5%AE%89%E8%A3%85.md)
## 六、Flume使用案例
介绍几个Flume的使用案例
介绍几个 Flume 的使用案例:
+ 案例一使用Flume监听文件内容变动将新增加的内容输出到控制台。
+ 案例二使用Flume监听指定目录将目录下新增加的文件存储到HDFS。
+ 案例三使用Avro将本服务器收集到的日志数据发送到另外一台服务器。
+ 案例一:使用 Flume 监听文件内容变动,将新增加的内容输出到控制台。
+ 案例二:使用 Flume 监听指定目录,将目录下新增加的文件存储到 HDFS。
+ 案例三:使用 Avro 将本服务器收集到的日志数据发送到另外一台服务器。
### 6.1 案例一
需求: 监听文件内容变动,将新增加的内容输出到控制台。
实现: 主要使用`Exec Source`配合`tail`命令实现。
实现: 主要使用 `Exec Source` 配合 `tail` 命令实现。
#### 1. 配置
新建配置文件`exec-memory-logger.properties`,其内容如下:
新建配置文件 `exec-memory-logger.properties`,其内容如下:
```properties
#指定agent的sources,sinks,channels
@ -211,9 +211,9 @@ flume-ng agent \
### 6.2 案例二
需求: 监听指定目录将目录下新增加的文件存储到HDFS。
需求: 监听指定目录,将目录下新增加的文件存储到 HDFS。
实现:使用`Spooling Directory Source``HDFS Sink`
实现:使用 `Spooling Directory Source``HDFS Sink`
#### 1. 配置
@ -257,7 +257,7 @@ flume-ng agent \
#### 3. 测试
拷贝任意文件到监听目录下可以从日志看到文件上传到HDFS的路径
拷贝任意文件到监听目录下,可以从日志看到文件上传到 HDFS 的路径:
```shell
# cp log.txt logs/
@ -265,7 +265,7 @@ flume-ng agent \
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/flume-example-3.png"/> </div>
查看上传到HDFS上的文件内容与本地是否一致
查看上传到 HDFS 上的文件内容与本地是否一致:
```shell
# hdfs dfs -cat /flume/events/19-04-09/13/log.txt.1554788567801
@ -279,11 +279,11 @@ flume-ng agent \
需求: 将本服务器收集到的数据发送到另外一台服务器。
实现:使用`avro sources``avro Sink`实现。
实现:使用 `avro sources``avro Sink` 实现。
#### 1. 配置日志收集Flume
新建配置`netcat-memory-avro.properties`,监听文件内容变化,然后将新的文件内容通过`avro sink`发送到hadoop001这台服务器的8888端口
新建配置 `netcat-memory-avro.properties`,监听文件内容变化,然后将新的文件内容通过 `avro sink` 发送到 hadoop001 这台服务器的 8888 端口:
```properties
#指定agent的sources,sinks,channels
@ -312,7 +312,7 @@ a1.channels.c1.transactionCapacity = 100
#### 2. 配置日志聚合Flume
使用 `avro source`监听hadoop001服务器的8888端口将获取到内容输出到控制台
使用 `avro source` 监听 hadoop001 服务器的 8888 端口,将获取到内容输出到控制台:
```properties
#指定agent的sources,sinks,channels
@ -342,7 +342,7 @@ a2.channels.c2.transactionCapacity = 100
#### 3. 启动
启动日志聚集Flume
启动日志聚集 Flume
```shell
flume-ng agent \
@ -351,7 +351,7 @@ flume-ng agent \
--name a2 -Dflume.root.logger=INFO,console
```
在启动日志收集Flume:
在启动日志收集 Flume:
```shell
flume-ng agent \
@ -360,16 +360,16 @@ flume-ng agent \
--name a1 -Dflume.root.logger=INFO,console
```
这里建议按以上顺序启动,原因是`avro.source`会先与端口进行绑定,这样`avro sink`连接时才不会报无法连接的异常。但是即使不按顺序启动也是没关系的,`sink`会一直重试,直至建立好连接。
这里建议按以上顺序启动,原因是 `avro.source` 会先与端口进行绑定,这样 `avro sink` 连接时才不会报无法连接的异常。但是即使不按顺序启动也是没关系的,`sink` 会一直重试,直至建立好连接。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/flume-retry.png"/> </div>
#### 4.测试
向文件`tmp/log.txt`中追加内容:
向文件 `tmp/log.txt` 中追加内容:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/flume-example-8.png"/> </div>
可以看到已经从8888端口监听到内容并成功输出到控制台
可以看到已经从 8888 端口监听到内容,并成功输出到控制台:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/flume-example-9.png"/> </div>

View File

@ -21,7 +21,7 @@
## 一、 简介
想要使用HDFS API需要导入依赖`hadoop-client`。如果是CDH版本的Hadoop还需要额外指明其仓库地址
想要使用 HDFS API需要导入依赖 `hadoop-client`。如果是 CDH 版本的 Hadoop还需要额外指明其仓库地址
```xml
<?xml version="1.0" encoding="UTF-8"?>
@ -42,7 +42,7 @@
</properties>
<!---配置CDH仓库地址-->
<!---配置 CDH 仓库地址-->
<repositories>
<repository>
<id>cloudera</id>
@ -75,7 +75,7 @@
### 2.1 FileSystem
FileSystem是所有HDFS操作的主入口。由于之后的每个单元测试都需要用到它这里使用`@Before`注解进行标注。
FileSystem 是所有 HDFS 操作的主入口。由于之后的每个单元测试都需要用到它,这里使用 `@Before` 注解进行标注。
```java
private static final String HDFS_PATH = "hdfs://192.168.0.106:8020";
@ -86,7 +86,7 @@ private static FileSystem fileSystem;
public void prepare() {
try {
Configuration configuration = new Configuration();
// 这里我启动的是单节点的Hadoop,所以副本系数设置为1,默认值为3
// 这里我启动的是单节点的 Hadoop,所以副本系数设置为 1,默认值为 3
configuration.set("dfs.replication", "1");
fileSystem = FileSystem.get(new URI(HDFS_PATH), configuration, HDFS_USER);
} catch (IOException e) {
@ -122,7 +122,7 @@ public void mkDir() throws Exception {
### 2.3 创建指定权限的目录
`FsPermission(FsAction u, FsAction g, FsAction o)` 的三个参数分别对应:创建者权限,同组其他用户权限,其他用户权限,权限值定义在`FsAction`枚举类中。
`FsPermission(FsAction u, FsAction g, FsAction o)` 的三个参数分别对应:创建者权限,同组其他用户权限,其他用户权限,权限值定义在 `FsAction` 枚举类中。
```java
@Test
@ -178,7 +178,7 @@ public void readToString() throws Exception {
}
```
`inputStreamToString`是一个自定义方法,代码如下:
`inputStreamToString` 是一个自定义方法,代码如下:
```java
/**
@ -228,8 +228,8 @@ public void rename() throws Exception {
public void delete() throws Exception {
/*
* 第二个参数代表是否递归删除
* + 如果path是一个目录且递归删除为true, 则删除该目录及其中所有文件;
* + 如果path是一个目录但递归删除为false,则会则抛出异常。
* + 如果 path 是一个目录且递归删除为 true, 则删除该目录及其中所有文件;
* + 如果 path 是一个目录但递归删除为 false,则会则抛出异常。
*/
boolean result = fileSystem.delete(new Path("/hdfs-api/test/b.txt"), true);
System.out.println(result);
@ -268,7 +268,7 @@ public void copyFromLocalFile() throws Exception {
public void progress() {
fileCount++;
// progress方法每上传大约64KB的数据后就会被调用一次
// progress 方法每上传大约 64KB 的数据后就会被调用一次
System.out.println("上传进度:" + (fileCount * 64 * 1024 / fileSize) * 100 + " %");
}
});
@ -288,11 +288,11 @@ public void copyToLocalFile() throws Exception {
Path src = new Path("/hdfs-api/test/kafka.tgz");
Path dst = new Path("D:\\app\\");
/*
* 第一个参数控制下载完成后是否删除源文件,默认是true,即删除;
* 最后一个参数表示是否将RawLocalFileSystem用作本地文件系统;
* RawLocalFileSystem默认为false,通常情况下可以不设置,
* 但如果你在执行时候抛出NullPointerException异常,则代表你的文件系统与程序可能存在不兼容的情况(window下常见),
* 此时可以将RawLocalFileSystem设置为true
* 第一个参数控制下载完成后是否删除源文件,默认是 true,即删除;
* 最后一个参数表示是否将 RawLocalFileSystem 用作本地文件系统;
* RawLocalFileSystem 默认为 false,通常情况下可以不设置,
* 但如果你在执行时候抛出 NullPointerException 异常,则代表你的文件系统与程序可能存在不兼容的情况 (window 下常见),
* 此时可以将 RawLocalFileSystem 设置为 true
*/
fileSystem.copyToLocalFile(false, src, dst, true);
}
@ -306,13 +306,13 @@ public void copyToLocalFile() throws Exception {
public void listFiles() throws Exception {
FileStatus[] statuses = fileSystem.listStatus(new Path("/hdfs-api"));
for (FileStatus fileStatus : statuses) {
//fileStatustoString方法被重写过直接打印可以看到所有信息
//fileStatustoString 方法被重写过,直接打印可以看到所有信息
System.out.println(fileStatus.toString());
}
}
```
`FileStatus`中包含了文件的基本信息,比如文件路径,是否是文件夹,修改时间,访问时间,所有者,所属组,文件权限,是否是符号链接等,输出内容示例如下:
`FileStatus` 中包含了文件的基本信息,比如文件路径,是否是文件夹,修改时间,访问时间,所有者,所属组,文件权限,是否是符号链接等,输出内容示例如下:
```properties
FileStatus{
@ -373,16 +373,16 @@ public void getFileBlockLocations() throws Exception {
}
```
块输出信息有三个值,分别是文件的起始偏移量(offset),文件大小(length),块所在的主机名(hosts)。
块输出信息有三个值,分别是文件的起始偏移量 (offset),文件大小 (length),块所在的主机名 (hosts)。
```
0,57028557,hadoop001
```
这里我上传的文件只有57M(小于128M)且程序中设置了副本系数为1所有只有一个块信息。
这里我上传的文件只有 57M(小于 128M),且程序中设置了副本系数为 1所有只有一个块信息。
<br/>
<br/>
**以上所有测试用例下载地址**[HDFS Java API](https://github.com/heibaiying/BigData-Notes/tree/master/code/Hadoop/hdfs-java-api)
**以上所有测试用例下载地址**[HDFS Java API](https://github.com/heibaiying/BigData-Notes/tree/master/code/Hadoop/hdfs-java-api)

View File

@ -29,7 +29,7 @@ hadoop fs -rm <path>
hadoop fs -rm -R <path>
```
**4. 从本地加载文件到HDFS**
**4. 从本地加载文件到 HDFS**
```shell
# 二选一执行即可
@ -38,7 +38,7 @@ hadoop fs - copyFromLocal [localsrc] [dst]
```
**5. 从HDFS导出文件到本地**
**5. 从 HDFS 导出文件到本地**
```shell
# 二选一执行即可
@ -78,7 +78,7 @@ hadoop fs -mv [src] [dst]
**10. 统计当前目录下各文件大小**
+ 默认单位字节
+ -s : 显示所有文件大小总和,
+ -h : 将以更友好的方式显示文件大小例如64.0m而不是67108864
+ -h : 将以更友好的方式显示文件大小(例如 64.0m 而不是 67108864
```shell
hadoop fs -du <path>
```
@ -103,7 +103,7 @@ hadoop fs -df -h /
```shell
hadoop fs -setrep [-R] [-w] <numReplicas> <path>
```
+ 更改文件的复制因子。如果path是目录则更改其下所有文件的复制因子
+ 更改文件的复制因子。如果 path 是目录,则更改其下所有文件的复制因子
+ -w : 请求命令是否等待复制完成
```shell
@ -127,13 +127,13 @@ hadoop fs -chown [-R] [OWNER][:[GROUP]] URI [URI ]
hadoop fs -test - [defsz] URI
```
可选选项:
+ -d如果路径是目录返回0。
+ -e如果路径存在则返回0。
+ -f如果路径是文件则返回0。
+ -s如果路径不为空则返回0。
+ -r如果路径存在且授予读权限则返回0。
+ -w如果路径存在且授予写入权限则返回0。
+ -z如果文件长度为零则返回0。
+ -d如果路径是目录返回 0。
+ -e如果路径存在则返回 0。
+ -f如果路径是文件则返回 0。
+ -s如果路径不为空则返回 0。
+ -r如果路径存在且授予读权限则返回 0。
+ -w如果路径存在且授予写入权限则返回 0。
+ -z如果文件长度为零则返回 0。
```shell
# 示例

View File

@ -29,7 +29,7 @@
## 一、介绍
**HDFS** **Hadoop Distributed File System**是Hadoop下的分布式文件系统具有高容错、高吞吐量等特性可以部署在低成本的硬件上。
**HDFS** **Hadoop Distributed File System**)是 Hadoop 下的分布式文件系统,具有高容错、高吞吐量等特性,可以部署在低成本的硬件上。
@ -39,40 +39,40 @@
### 2.1 HDFS 架构
HDFS 遵循主/从架构由单个NameNode(NN)和多个DataNode(DN)组成:
HDFS 遵循主/从架构,由单个 NameNode(NN) 和多个 DataNode(DN) 组成:
- **NameNode** : 负责执行有关`文件系统命名空间`的操作,例如打开,关闭、重命名文件和目录等。它同时还负责集群元数据的存储,记录着文件中各个数据块的位置信息。
- **NameNode** : 负责执行有关 ` 文件系统命名空间 ` 的操作,例如打开,关闭、重命名文件和目录等。它同时还负责集群元数据的存储,记录着文件中各个数据块的位置信息。
- **DataNode**:负责提供来自文件系统客户端的读写请求,执行块的创建,删除等操作。
### 2.2 文件系统命名空间
HDFS`文件系统命名空间`的层次结构与大多数文件系统类似(如Linux) 支持目录和文件的创建、移动、删除和重命名等操作,支持配置用户和访问权限,但不支持硬链接和软连接。`NameNode`负责维护文件系统名称空间,记录对名称空间或其属性的任何更改。
HDFS` 文件系统命名空间 ` 的层次结构与大多数文件系统类似 (如 Linux) 支持目录和文件的创建、移动、删除和重命名等操作,支持配置用户和访问权限,但不支持硬链接和软连接。`NameNode` 负责维护文件系统名称空间,记录对名称空间或其属性的任何更改。
### 2.3 数据复制
由于Hadoop被设计运行在廉价的机器上这意味着硬件是不可靠的为了保证容错性HDFS提供了数据复制机制。HDFS 将每一个文件存储为一系列**块**每个块由多个副本来保证容错块的大小和复制因子可以自行配置默认情况下块大小是128M默认复制因子是3
由于 Hadoop 被设计运行在廉价的机器上这意味着硬件是不可靠的为了保证容错性HDFS 提供了数据复制机制。HDFS 将每一个文件存储为一系列**块**,每个块由多个副本来保证容错,块的大小和复制因子可以自行配置(默认情况下,块大小是 128M默认复制因子是 3
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hdfsdatanodes.png"/> </div>
### 2.4 数据复制的实现原理
大型的HDFS实例在通常分布在多个机架的多台服务器上不同机架上的两台服务器之间通过交换机进行通讯。在大多数情况下同一机架中的服务器间的网络带宽大于不同机架中的服务器之间的带宽。因此HDFS采用机架感知副本放置策略对于常见情况当复制因子为3HDFS的放置策略是
大型的 HDFS 实例在通常分布在多个机架的多台服务器上,不同机架上的两台服务器之间通过交换机进行通讯。在大多数情况下,同一机架中的服务器间的网络带宽大于不同机架中的服务器之间的带宽。因此 HDFS 采用机架感知副本放置策略,对于常见情况,当复制因子为 3 HDFS 的放置策略是:
在写入程序位于`datanode`上时,就优先将写入文件的一个副本放置在该`datanode`上,否则放在随机`datanode`上。之后在另一个远程机架上的任意一个节点上放置另一个副本,并在该机架上的另一个节点上放置最后一个副本。此策略可以减少机架间的写入流量,从而提高写入性能。
在写入程序位于 `datanode` 上时,就优先将写入文件的一个副本放置在该 `datanode` 上,否则放在随机 `datanode` 上。之后在另一个远程机架上的任意一个节点上放置另一个副本,并在该机架上的另一个节点上放置最后一个副本。此策略可以减少机架间的写入流量,从而提高写入性能。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hdfs-机架.png"/> </div>
如果复制因子大于3则随机确定第4个和之后副本的放置位置,同时保持每个机架的副本数量低于上限,上限值通常为`(复制系数 - 1/机架数量 + 2`,需要注意的是不允许同一个`dataNode`上具有同一个块的多个副本。
如果复制因子大于 3则随机确定第 4 个和之后副本的放置位置,同时保持每个机架的副本数量低于上限,上限值通常为 `(复制系数 - 1/机架数量 + 2`,需要注意的是不允许同一个 `dataNode` 上具有同一个块的多个副本。
### 2.5 副本的选择
为了最大限度地减少带宽消耗和读取延迟HDFS在执行读取请求时优先读取距离读取器最近的副本。如果在与读取器节点相同的机架上存在副本则优先选择该副本。如果HDFS群集跨越多个数据中心则优先选择本地数据中心上的副本。
为了最大限度地减少带宽消耗和读取延迟HDFS 在执行读取请求时,优先读取距离读取器最近的副本。如果在与读取器节点相同的机架上存在副本,则优先选择该副本。如果 HDFS 群集跨越多个数据中心,则优先选择本地数据中心上的副本。
@ -80,17 +80,17 @@ HDFS的`文件系统命名空间`的层次结构与大多数文件系统类似(
#### 1. 心跳机制和重新复制
每个DataNode定期向NameNode发送心跳消息如果超过指定时间没有收到心跳消息则将DataNode标记为死亡。NameNode不会将任何新的IO请求转发给标记为死亡的DataNode也不会再使用这些DataNode上的数据。 由于数据不再可用可能会导致某些块的复制因子小于其指定值NameNode会跟踪这些块并在必要的时候进行重新复制。
每个 DataNode 定期向 NameNode 发送心跳消息,如果超过指定时间没有收到心跳消息,则将 DataNode 标记为死亡。NameNode 不会将任何新的 IO 请求转发给标记为死亡的 DataNode也不会再使用这些 DataNode 上的数据。 由于数据不再可用可能会导致某些块的复制因子小于其指定值NameNode 会跟踪这些块,并在必要的时候进行重新复制。
#### 2. 数据的完整性
由于存储设备故障等原因存储在DataNode上的数据块也会发生损坏。为了避免读取到已经损坏的数据而导致错误HDFS提供了数据完整性校验机制来保证数据的完整性具体操作如下
由于存储设备故障等原因,存储在 DataNode 上的数据块也会发生损坏。为了避免读取到已经损坏的数据而导致错误HDFS 提供了数据完整性校验机制来保证数据的完整性,具体操作如下:
当客户端创建HDFS文件时它会计算文件的每个块的`校验和`,并将`校验和`存储在同一HDFS命名空间下的单独的隐藏文件中。当客户端检索文件内容时它会验证从每个DataNode接收的数据是否与存储在关联校验和文件中的`校验和`匹配。如果匹配失败则证明数据已经损坏此时客户端会选择从其他DataNode获取该块的其他可用副本。
当客户端创建 HDFS 文件时,它会计算文件的每个块的 ` 校验和 `,并将 ` 校验和 ` 存储在同一 HDFS 命名空间下的单独的隐藏文件中。当客户端检索文件内容时,它会验证从每个 DataNode 接收的数据是否与存储在关联校验和文件中的 ` 校验和 ` 匹配。如果匹配失败,则证明数据已经损坏,此时客户端会选择从其他 DataNode 获取该块的其他可用副本。
#### 3.元数据的磁盘故障
`FsImage``EditLog`HDFS的核心数据这些数据的意外丢失可能会导致整个HDFS服务不可用。为了避免这个问题可以配置NameNode使其支持`FsImage``EditLog`多副本同步,这样`FsImage``EditLog`的任何改变都会引起每个副本`FsImage``EditLog`的同步更新。
`FsImage``EditLog`HDFS 的核心数据,这些数据的意外丢失可能会导致整个 HDFS 服务不可用。为了避免这个问题,可以配置 NameNode 使其支持 `FsImage``EditLog` 多副本同步,这样 `FsImage``EditLog` 的任何改变都会引起每个副本 `FsImage``EditLog` 的同步更新。
#### 4.支持快照
@ -102,23 +102,23 @@ HDFS的`文件系统命名空间`的层次结构与大多数文件系统类似(
### 3.1 高容错
由于HDFS 采用数据的多副本方案,所以部分硬件的损坏不会导致全部数据的丢失。
由于 HDFS 采用数据的多副本方案,所以部分硬件的损坏不会导致全部数据的丢失。
### 3.2 高吞吐量
HDFS设计的重点是支持高吞吐量的数据访问而不是低延迟的数据访问。
HDFS 设计的重点是支持高吞吐量的数据访问,而不是低延迟的数据访问。
### 3.3 大文件支持
HDFS适合于大文件的存储文档的大小应该是是GBTB级别的。
HDFS 适合于大文件的存储,文档的大小应该是是 GBTB 级别的。
### 3.3 简单一致性模型
HDFS更适合于一次写入多次读取(write-once-read-many)的访问模型。支持将内容追加到文件末尾,但不支持数据的随机访问,不能从文件任意位置新增数据。
HDFS 更适合于一次写入多次读取 (write-once-read-many) 的访问模型。支持将内容追加到文件末尾,但不支持数据的随机访问,不能从文件任意位置新增数据。
### 3.4 跨平台移植性
HDFS具有良好的跨平台移植性这使得其他大数据计算框架都将其作为数据持久化存储的首选方案。
HDFS 具有良好的跨平台移植性,这使得其他大数据计算框架都将其作为数据持久化存储的首选方案。
@ -156,7 +156,7 @@ HDFS具有良好的跨平台移植性这使得其他大数据计算框架都
**第三部分DataNode故障处理**
**第三部分DataNode 故障处理**
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hdfs-tolerance-4.jpg"/> </div>
@ -171,6 +171,6 @@ HDFS具有良好的跨平台移植性这使得其他大数据计算框架都
## 参考资料
1. [Apache Hadoop 2.9.2 > HDFS Architecture](http://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html)
2. Tom White . hadoop权威指南 [M] . 清华大学出版社 . 2017.
2. Tom White . hadoop 权威指南 [M] . 清华大学出版社 . 2017.
3. [翻译经典 HDFS 原理讲解漫画](https://blog.csdn.net/hudiefenmu/article/details/37655491)

View File

@ -1,384 +1,384 @@
# 分布式计算框架——MapReduce
<nav>
<a href="#一MapReduce概述">一、MapReduce概述</a><br/>
<a href="#二MapReduce编程模型简述">二、MapReduce编程模型简述</a><br/>
<a href="#三combiner--partitioner">三、combiner & partitioner</a><br/>
<a href="#四MapReduce词频统计案例">四、MapReduce词频统计案例</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-项目简介">4.1 项目简介</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-项目依赖">4.2 项目依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#43-WordCountMapper">4.3 WordCountMapper</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#44-WordCountReducer">4.4 WordCountReducer</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#44-WordCountApp">4.4 WordCountApp</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#45-提交到服务器运行">4.5 提交到服务器运行</a><br/>
<a href="#五词频统计案例进阶之Combiner">五、词频统计案例进阶之Combiner</a><br/>
<a href="#六词频统计案例进阶之Partitioner">六、词频统计案例进阶之Partitioner</a><br/>
</nav>
## 一、MapReduce概述
Hadoop MapReduce是一个分布式计算框架用于编写批处理应用程序。编写好的程序可以提交到Hadoop集群上用于并行处理大规模的数据集。
MapReduce作业通过将输入的数据集拆分为独立的块这些块由`map`以并行的方式处理,框架对`map`的输出进行排序,然后输入到`reduce`中。MapReduce框架专门用于`<keyvalue>`键值对处理,它将作业的输入视为一组`<keyvalue>`对,并生成一组`<keyvalue>`对作为输出。输出和输出的`key``value`都必须实现[Writable](http://hadoop.apache.org/docs/stable/api/org/apache/hadoop/io/Writable.html) 接口。
```
(input) <k1, v1> -> map -> <k2, v2> -> combine -> <k2, v2> -> reduce -> <k3, v3> (output)
```
## 二、MapReduce编程模型简述
这里以词频统计为例进行说明MapReduce处理的流程如下
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/mapreduceProcess.png"/> </div>
1. **input** : 读取文本文件;
2. **splitting** : 将文件按照行进行拆分,此时得到的`K1`行数,`V1`表示对应行的文本内容;
3. **mapping** : 并行将每一行按照空格进行拆分,拆分得到的`List(K2,V2)`,其中`K2`代表每一个单词,由于是做词频统计,所以`V2`的值为1代表出现1次;
4. **shuffling**:由于`Mapping`操作可能是在不同的机器上并行处理的,所以需要通过`shuffling`将相同`key`值的数据分发到同一个节点上去合并,这样才能统计出最终的结果,此时得到`K2`为每一个单词,`List(V2)`为可迭代集合,`V2`就是Mapping中的V2
5. **Reducing** : 这里的案例是统计单词出现的总次数,所以`Reducing``List(V2)`进行归约求和操作,最终输出。
MapReduce编程模型中`splitting``shuffing`操作都是由框架实现的,需要我们自己编程实现的只有`mapping``reducing`这也就是MapReduce这个称呼的来源。
## 三、combiner & partitioner
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/Detailed-Hadoop-MapReduce-Data-Flow-14.png"/> </div>
### 3.1 InputFormat & RecordReaders
`InputFormat`将输出文件拆分为多个`InputSplit`,并由`RecordReaders``InputSplit`转换为标准的<keyvalue>键值对作为map的输出。这一步的意义在于只有先进行逻辑拆分并转为标准的键值对格式后才能为多个`map`提供输入,以便进行并行处理。
### 3.2 Combiner
`combiner``map`运算后的可选操作,它实际上是一个本地化的`reduce`操作,它主要是在`map`计算出中间文件后做一个简单的合并重复`key`值的操作。这里以词频统计为例:
`map`在遇到一个hadoop的单词时就会记录为1但是这篇文章里hadoop可能会出现n多次,那么`map`输出文件冗余就会很多,因此在`reduce`计算前对相同的key做一个合并操作那么需要传输的数据量就会减少传输效率就可以得到提升。
但并非所有场景都适合使用`combiner`,使用它的原则是`combiner`的输出不会影响到`reduce`计算的最终输入,例如:求总数,最大值,最小值时都可以使用`combiner`,但是做平均值计算则不能使用`combiner`
不使用combiner的情况
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/mapreduce-without-combiners.png"/> </div>
使用combiner的情况
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/mapreduce-with-combiners.png"/> </div>
可以看到使用combiner的时候需要传输到reducer中的数据由12keys降低到10keys。降低的幅度取决于你keys的重复率下文词频统计案例会演示用combiner降低数百倍的传输量。
### 3.3 Partitioner
`partitioner`可以理解成分类器,将`map`的输出按照key值的不同分别分给对应的`reducer`,支持自定义实现,下文案例会给出演示。
## 四、MapReduce词频统计案例
### 4.1 项目简介
这里给出一个经典的词频统计的案例:统计如下样本数据中每个单词出现的次数。
```properties
Spark HBase
Hive Flink Storm Hadoop HBase Spark
Flink
HBase Storm
HBase Hadoop Hive Flink
HBase Flink Hive Storm
Hive Flink Hadoop
HBase Hive
Hadoop Spark HBase Storm
HBase Hadoop Hive Flink
HBase Flink Hive Storm
Hive Flink Hadoop
HBase Hive
```
为方便大家开发,我在项目源码中放置了一个工具类`WordCountDataUtils`用于模拟产生词频统计的样本生成的文件支持输出到本地或者直接写到HDFS上。
> 项目完整源码下载地址:[hadoop-word-count](https://github.com/heibaiying/BigData-Notes/tree/master/code/Hadoop/hadoop-word-count)
### 4.2 项目依赖
想要进行MapReduce编程需要导入`hadoop-client`依赖:
```xml
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop.version}</version>
</dependency>
```
### 4.3 WordCountMapper
将每行数据按照指定分隔符进行拆分。这里需要注意在MapReduce中必须使用Hadoop定义的类型因为Hadoop预定义的类型都是可序列化可比较的所有类型均实现了`WritableComparable`接口。
```java
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException,
InterruptedException {
String[] words = value.toString().split("\t");
for (String word : words) {
context.write(new Text(word), new IntWritable(1));
}
}
}
```
`WordCountMapper`对应下图的Mapping操作
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-code-mapping.png"/> </div>
`WordCountMapper`继承自`Mappe`类,这是一个泛型类,定义如下:
```java
WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable>
public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
......
}
```
+ **KEYIN** : `mapping`输入key的类型即每行的偏移量(每行第一个字符在整个文本中的位置)`Long`类型对应Hadoop中的`LongWritable`类型;
+ **VALUEIN** : `mapping`输入value的类型即每行数据`String`类型对应Hadoop`Text`类型;
+ **KEYOUT** `mapping`输出的key的类型即每个单词`String`类型对应Hadoop`Text`类型;
+ **VALUEOUT**`mapping`输出value的类型即每个单词出现的次数这里用`int`类型,对应`IntWritable`类型。
### 4.4 WordCountReducer
在Reduce中进行单词出现次数的统计
```java
public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException,
InterruptedException {
int count = 0;
for (IntWritable value : values) {
count += value.get();
}
context.write(key, new IntWritable(count));
}
}
```
如下图,`shuffling`的输出是reduce的输入。这里的key是每个单词values是一个可迭代的数据类型类似`(1,1,1,...)`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-code-reducer.png"/> </div>
### 4.4 WordCountApp
组装MapReduce作业并提交到服务器运行代码如下
```java
/**
* 组装作业 并提交到集群运行
*/
public class WordCountApp {
// 这里为了直观显示参数 使用了硬编码,实际开发中可以通过外部传参
private static final String HDFS_URL = "hdfs://192.168.0.107:8020";
private static final String HADOOP_USER_NAME = "root";
public static void main(String[] args) throws Exception {
// 文件输入路径和输出路径由外部传参指定
if (args.length < 2) {
System.out.println("Input and output paths are necessary!");
return;
}
// 需要指明hadoop用户名否则在HDFS上创建目录时可能会抛出权限不足的异常
System.setProperty("HADOOP_USER_NAME", HADOOP_USER_NAME);
Configuration configuration = new Configuration();
// 指明HDFS的地址
configuration.set("fs.defaultFS", HDFS_URL);
// 创建一个Job
Job job = Job.getInstance(configuration);
// 设置运行的主类
job.setJarByClass(WordCountApp.class);
// 设置MapperReducer
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 设置Mapper输出keyvalue的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 设置Reducer输出keyvalue的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 如果输出目录已经存在,则必须先删除,否则重复运行程序时会抛出异常
FileSystem fileSystem = FileSystem.get(new URI(HDFS_URL), configuration, HADOOP_USER_NAME);
Path outputPath = new Path(args[1]);
if (fileSystem.exists(outputPath)) {
fileSystem.delete(outputPath, true);
}
// 设置作业输入文件和输出文件的路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, outputPath);
// 将作业提交到群集并等待它完成参数设置为true代表打印显示对应的进度
boolean result = job.waitForCompletion(true);
// 关闭之前创建的fileSystem
fileSystem.close();
// 根据作业结果,终止当前运行的Java虚拟机,退出程序
System.exit(result ? 0 : -1);
}
}
```
需要注意的是:如果不设置`Mapper`操作的输出类型,则程序默认它和`Reducer`操作输出的类型相同。
### 4.5 提交到服务器运行
在实际开发中可以在本机配置hadoop开发环境直接在IDE中启动进行测试。这里主要介绍一下打包提交到服务器运行。由于本项目没有使用除Hadoop外的第三方依赖直接打包即可
```shell
# mvn clean package
```
使用以下命令提交作业:
```shell
hadoop jar /usr/appjar/hadoop-word-count-1.0.jar \
com.heibaiying.WordCountApp \
/wordcount/input.txt /wordcount/output/WordCountApp
```
作业完成后查看HDFS上生成目录
```shell
# 查看目录
hadoop fs -ls /wordcount/output/WordCountApp
# 查看统计结果
hadoop fs -cat /wordcount/output/WordCountApp/part-r-00000
```
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-wordcountapp.png"/> </div>
## 五、词频统计案例进阶之Combiner
### 5.1 代码实现
想要使用`combiner`功能只要在组装作业时,添加下面一行代码即可:
```java
// 设置Combiner
job.setCombinerClass(WordCountReducer.class);
```
### 5.2 执行结果
加入`combiner`后统计结果是不会有变化的,但是可以从打印的日志看出`combiner`的效果:
没有加入`combiner`的打印日志:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-no-combiner.png"/> </div>
加入`combiner`后的打印日志如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-combiner.png"/> </div>
这里我们只有一个输入文件并且小于128M所以只有一个Map进行处理。可以看到经过combiner后records`3519`降低为`6`(样本中单词种类就只有6种)在这个用例中combiner就能极大地降低需要传输的数据量。
## 六、词频统计案例进阶之Partitioner
### 6.1 默认的Partitioner
这里假设有个需求:将不同单词的统计结果输出到不同文件。这种需求实际上比较常见,比如统计产品的销量时,需要将结果按照产品种类进行拆分。要实现这个功能,就需要用到自定义`Partitioner`
这里先介绍下MapReduce默认的分类规则在构建job时候如果不指定默认的使用的是`HashPartitioner`对key值进行哈希散列并对`numReduceTasks`取余。其实现如下:
```java
public class HashPartitioner<K, V> extends Partitioner<K, V> {
public int getPartition(K key, V value,
int numReduceTasks) {
return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
}
}
```
### 6.2 自定义Partitioner
这里我们继承`Partitioner`自定义分类规则,这里按照单词进行分类:
```java
public class CustomPartitioner extends Partitioner<Text, IntWritable> {
public int getPartition(Text text, IntWritable intWritable, int numPartitions) {
return WordCountDataUtils.WORD_LIST.indexOf(text.toString());
}
}
```
在构建`job`时候指定使用我们自己的分类规则,并设置`reduce`的个数:
```java
// 设置自定义分区规则
job.setPartitionerClass(CustomPartitioner.class);
// 设置reduce个数
job.setNumReduceTasks(WordCountDataUtils.WORD_LIST.size());
```
### 6.3 执行结果
执行结果如下,分别生成6个文件,每个文件中为对应单词的统计结果:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-wordcountcombinerpartition.png"/> </div>
## 参考资料
1. [分布式计算框架MapReduce](https://zhuanlan.zhihu.com/p/28682581)
2. [Apache Hadoop 2.9.2 > MapReduce Tutorial](http://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html)
3. [MapReduce - Combiners]( https://www.tutorialscampus.com/tutorials/map-reduce/combiners.htm)
# 分布式计算框架——MapReduce
<nav>
<a href="#一MapReduce概述">一、MapReduce概述</a><br/>
<a href="#二MapReduce编程模型简述">二、MapReduce编程模型简述</a><br/>
<a href="#三combiner--partitioner">三、combiner & partitioner</a><br/>
<a href="#四MapReduce词频统计案例">四、MapReduce词频统计案例</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-项目简介">4.1 项目简介</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-项目依赖">4.2 项目依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#43-WordCountMapper">4.3 WordCountMapper</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#44-WordCountReducer">4.4 WordCountReducer</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#44-WordCountApp">4.4 WordCountApp</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#45-提交到服务器运行">4.5 提交到服务器运行</a><br/>
<a href="#五词频统计案例进阶之Combiner">五、词频统计案例进阶之Combiner</a><br/>
<a href="#六词频统计案例进阶之Partitioner">六、词频统计案例进阶之Partitioner</a><br/>
</nav>
## 一、MapReduce概述
Hadoop MapReduce 是一个分布式计算框架,用于编写批处理应用程序。编写好的程序可以提交到 Hadoop 集群上用于并行处理大规模的数据集。
MapReduce 作业通过将输入的数据集拆分为独立的块,这些块由 `map` 以并行的方式处理,框架对 `map` 的输出进行排序,然后输入到 `reduce` 中。MapReduce 框架专门用于 `<keyvalue>` 键值对处理,它将作业的输入视为一组 `<keyvalue>` 对,并生成一组 `<keyvalue>` 对作为输出。输出和输出的 `key``value` 都必须实现[Writable](http://hadoop.apache.org/docs/stable/api/org/apache/hadoop/io/Writable.html) 接口。
```
(input) <k1, v1> -> map -> <k2, v2> -> combine -> <k2, v2> -> reduce -> <k3, v3> (output)
```
## 二、MapReduce编程模型简述
这里以词频统计为例进行说明MapReduce 处理的流程如下:
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/mapreduceProcess.png"/> </div>
1. **input** : 读取文本文件;
2. **splitting** : 将文件按照行进行拆分,此时得到的 `K1` 行数,`V1` 表示对应行的文本内容;
3. **mapping** : 并行将每一行按照空格进行拆分,拆分得到的 `List(K2,V2)`,其中 `K2` 代表每一个单词,由于是做词频统计,所以 `V2` 的值为 1代表出现 1 次;
4. **shuffling**:由于 `Mapping` 操作可能是在不同的机器上并行处理的,所以需要通过 `shuffling` 将相同 `key` 值的数据分发到同一个节点上去合并,这样才能统计出最终的结果,此时得到 `K2` 为每一个单词,`List(V2)` 为可迭代集合,`V2` 就是 Mapping 中的 V2
5. **Reducing** : 这里的案例是统计单词出现的总次数,所以 `Reducing``List(V2)` 进行归约求和操作,最终输出。
MapReduce 编程模型中 `splitting` `shuffing` 操作都是由框架实现的,需要我们自己编程实现的只有 `mapping``reducing`,这也就是 MapReduce 这个称呼的来源。
## 三、combiner & partitioner
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/Detailed-Hadoop-MapReduce-Data-Flow-14.png"/> </div>
### 3.1 InputFormat & RecordReaders
`InputFormat` 将输出文件拆分为多个 `InputSplit`,并由 `RecordReaders``InputSplit` 转换为标准的<keyvalue>键值对,作为 map 的输出。这一步的意义在于只有先进行逻辑拆分并转为标准的键值对格式后,才能为多个 `map` 提供输入,以便进行并行处理。
### 3.2 Combiner
`combiner``map` 运算后的可选操作,它实际上是一个本地化的 `reduce` 操作,它主要是在 `map` 计算出中间文件后做一个简单的合并重复 `key` 值的操作。这里以词频统计为例:
`map` 在遇到一个 hadoop 的单词时就会记录为 1但是这篇文章里 hadoop 可能会出现 n 多次,那么 `map` 输出文件冗余就会很多,因此在 `reduce` 计算前对相同的 key 做一个合并操作,那么需要传输的数据量就会减少,传输效率就可以得到提升。
但并非所有场景都适合使用 `combiner`,使用它的原则是 `combiner` 的输出不会影响到 `reduce` 计算的最终输入,例如:求总数,最大值,最小值时都可以使用 `combiner`,但是做平均值计算则不能使用 `combiner`
不使用 combiner 的情况:
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/mapreduce-without-combiners.png"/> </div>
使用 combiner 的情况:
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/mapreduce-with-combiners.png"/> </div>
可以看到使用 combiner 的时候,需要传输到 reducer 中的数据由 12keys降低到 10keys。降低的幅度取决于你 keys 的重复率,下文词频统计案例会演示用 combiner 降低数百倍的传输量。
### 3.3 Partitioner
`partitioner` 可以理解成分类器,将 `map` 的输出按照 key 值的不同分别分给对应的 `reducer`,支持自定义实现,下文案例会给出演示。
## 四、MapReduce词频统计案例
### 4.1 项目简介
这里给出一个经典的词频统计的案例:统计如下样本数据中每个单词出现的次数。
```properties
Spark HBase
Hive Flink Storm Hadoop HBase Spark
Flink
HBase Storm
HBase Hadoop Hive Flink
HBase Flink Hive Storm
Hive Flink Hadoop
HBase Hive
Hadoop Spark HBase Storm
HBase Hadoop Hive Flink
HBase Flink Hive Storm
Hive Flink Hadoop
HBase Hive
```
为方便大家开发,我在项目源码中放置了一个工具类 `WordCountDataUtils`,用于模拟产生词频统计的样本,生成的文件支持输出到本地或者直接写到 HDFS 上。
> 项目完整源码下载地址:[hadoop-word-count](https://github.com/heibaiying/BigData-Notes/tree/master/code/Hadoop/hadoop-word-count)
### 4.2 项目依赖
想要进行 MapReduce 编程,需要导入 `hadoop-client` 依赖:
```xml
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop.version}</version>
</dependency>
```
### 4.3 WordCountMapper
将每行数据按照指定分隔符进行拆分。这里需要注意在 MapReduce 中必须使用 Hadoop 定义的类型,因为 Hadoop 预定义的类型都是可序列化,可比较的,所有类型均实现了 `WritableComparable` 接口。
```java
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException,
InterruptedException {
String[] words = value.toString().split("\t");
for (String word : words) {
context.write(new Text(word), new IntWritable(1));
}
}
}
```
`WordCountMapper` 对应下图的 Mapping 操作:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-code-mapping.png"/> </div>
`WordCountMapper` 继承自 `Mappe` 类,这是一个泛型类,定义如下:
```java
WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable>
public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
......
}
```
+ **KEYIN** : `mapping` 输入 key 的类型,即每行的偏移量 (每行第一个字符在整个文本中的位置)`Long` 类型,对应 Hadoop 中的 `LongWritable` 类型;
+ **VALUEIN** : `mapping` 输入 value 的类型,即每行数据;`String` 类型,对应 Hadoop`Text` 类型;
+ **KEYOUT** `mapping` 输出的 key 的类型,即每个单词;`String` 类型,对应 Hadoop`Text` 类型;
+ **VALUEOUT**`mapping` 输出 value 的类型,即每个单词出现的次数;这里用 `int` 类型,对应 `IntWritable` 类型。
### 4.4 WordCountReducer
Reduce 中进行单词出现次数的统计:
```java
public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException,
InterruptedException {
int count = 0;
for (IntWritable value : values) {
count += value.get();
}
context.write(key, new IntWritable(count));
}
}
```
如下图,`shuffling` 的输出是 reduce 的输入。这里的 key 是每个单词values 是一个可迭代的数据类型,类似 `(1,1,1,...)`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-code-reducer.png"/> </div>
### 4.4 WordCountApp
组装 MapReduce 作业,并提交到服务器运行,代码如下:
```java
/**
* 组装作业 并提交到集群运行
*/
public class WordCountApp {
// 这里为了直观显示参数 使用了硬编码,实际开发中可以通过外部传参
private static final String HDFS_URL = "hdfs://192.168.0.107:8020";
private static final String HADOOP_USER_NAME = "root";
public static void main(String[] args) throws Exception {
// 文件输入路径和输出路径由外部传参指定
if (args.length < 2) {
System.out.println("Input and output paths are necessary!");
return;
}
// 需要指明 hadoop 用户名,否则在 HDFS 上创建目录时可能会抛出权限不足的异常
System.setProperty("HADOOP_USER_NAME", HADOOP_USER_NAME);
Configuration configuration = new Configuration();
// 指明 HDFS 的地址
configuration.set("fs.defaultFS", HDFS_URL);
// 创建一个 Job
Job job = Job.getInstance(configuration);
// 设置运行的主类
job.setJarByClass(WordCountApp.class);
// 设置 MapperReducer
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 设置 Mapper 输出 keyvalue 的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 设置 Reducer 输出 keyvalue 的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 如果输出目录已经存在,则必须先删除,否则重复运行程序时会抛出异常
FileSystem fileSystem = FileSystem.get(new URI(HDFS_URL), configuration, HADOOP_USER_NAME);
Path outputPath = new Path(args[1]);
if (fileSystem.exists(outputPath)) {
fileSystem.delete(outputPath, true);
}
// 设置作业输入文件和输出文件的路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, outputPath);
// 将作业提交到群集并等待它完成,参数设置为 true 代表打印显示对应的进度
boolean result = job.waitForCompletion(true);
// 关闭之前创建的 fileSystem
fileSystem.close();
// 根据作业结果,终止当前运行的 Java 虚拟机,退出程序
System.exit(result ? 0 : -1);
}
}
```
需要注意的是:如果不设置 `Mapper` 操作的输出类型,则程序默认它和 `Reducer` 操作输出的类型相同。
### 4.5 提交到服务器运行
在实际开发中,可以在本机配置 hadoop 开发环境,直接在 IDE 中启动进行测试。这里主要介绍一下打包提交到服务器运行。由于本项目没有使用除 Hadoop 外的第三方依赖,直接打包即可:
```shell
# mvn clean package
```
使用以下命令提交作业:
```shell
hadoop jar /usr/appjar/hadoop-word-count-1.0.jar \
com.heibaiying.WordCountApp \
/wordcount/input.txt /wordcount/output/WordCountApp
```
作业完成后查看 HDFS 上生成目录:
```shell
# 查看目录
hadoop fs -ls /wordcount/output/WordCountApp
# 查看统计结果
hadoop fs -cat /wordcount/output/WordCountApp/part-r-00000
```
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-wordcountapp.png"/> </div>
## 五、词频统计案例进阶之Combiner
### 5.1 代码实现
想要使用 `combiner` 功能只要在组装作业时,添加下面一行代码即可:
```java
// 设置 Combiner
job.setCombinerClass(WordCountReducer.class);
```
### 5.2 执行结果
加入 `combiner` 后统计结果是不会有变化的,但是可以从打印的日志看出 `combiner` 的效果:
没有加入 `combiner` 的打印日志:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-no-combiner.png"/> </div>
加入 `combiner` 后的打印日志如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-combiner.png"/> </div>
这里我们只有一个输入文件并且小于 128M所以只有一个 Map 进行处理。可以看到经过 combiner records`3519` 降低为 `6`(样本中单词种类就只有 6 种),在这个用例中 combiner 就能极大地降低需要传输的数据量。
## 六、词频统计案例进阶之Partitioner
### 6.1 默认的Partitioner
这里假设有个需求:将不同单词的统计结果输出到不同文件。这种需求实际上比较常见,比如统计产品的销量时,需要将结果按照产品种类进行拆分。要实现这个功能,就需要用到自定义 `Partitioner`
这里先介绍下 MapReduce 默认的分类规则:在构建 job 时候,如果不指定,默认的使用的是 `HashPartitioner`:对 key 值进行哈希散列并对 `numReduceTasks` 取余。其实现如下:
```java
public class HashPartitioner<K, V> extends Partitioner<K, V> {
public int getPartition(K key, V value,
int numReduceTasks) {
return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
}
}
```
### 6.2 自定义Partitioner
这里我们继承 `Partitioner` 自定义分类规则,这里按照单词进行分类:
```java
public class CustomPartitioner extends Partitioner<Text, IntWritable> {
public int getPartition(Text text, IntWritable intWritable, int numPartitions) {
return WordCountDataUtils.WORD_LIST.indexOf(text.toString());
}
}
```
在构建 `job` 时候指定使用我们自己的分类规则,并设置 `reduce` 的个数:
```java
// 设置自定义分区规则
job.setPartitionerClass(CustomPartitioner.class);
// 设置 reduce 个数
job.setNumReduceTasks(WordCountDataUtils.WORD_LIST.size());
```
### 6.3 执行结果
执行结果如下,分别生成 6 个文件,每个文件中为对应单词的统计结果:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-wordcountcombinerpartition.png"/> </div>
## 参考资料
1. [分布式计算框架 MapReduce](https://zhuanlan.zhihu.com/p/28682581)
2. [Apache Hadoop 2.9.2 > MapReduce Tutorial](http://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html)
3. [MapReduce - Combiners]( https://www.tutorialscampus.com/tutorials/map-reduce/combiners.htm)

View File

@ -1,128 +1,128 @@
# 集群资源管理器——YARN
<nav>
<a href="#一hadoop-yarn-简介">一、hadoop yarn 简介</a><br/>
<a href="#二YARN架构">二、YARN架构</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#1-ResourceManager">1. ResourceManager</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#2-NodeManager">2. NodeManager</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#3-ApplicationMaster">3. ApplicationMaster </a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#4-Contain">4. Contain</a><br/>
<a href="#三YARN工作原理简述">三、YARN工作原理简述</a><br/>
<a href="#四YARN工作原理详述">四、YARN工作原理详述</a><br/>
<a href="#五提交作业到YARN上运行">五、提交作业到YARN上运行</a><br/>
</nav>
## 一、hadoop yarn 简介
**Apache YARN** (Yet Another Resource Negotiator) 是hadoop 2.0 引入的集群资源管理系统。用户可以将各种服务框架部署在YARN上由YARN进行统一地管理和资源分配。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/yarn-base.png"/> </div>
## 二、YARN架构
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/Figure3Architecture-of-YARN.png"/> </div>
### 1. ResourceManager
`ResourceManager`通常在独立的机器上以后台进程的形式运行,它是整个集群资源的主要协调者和管理者。`ResourceManager`负责给用户提交的所有应用程序分配资源它根据应用程序优先级、队列容量、ACLs、数据位置等信息做出决策然后以共享的、安全的、多租户的方式制定分配策略调度集群资源。
### 2. NodeManager
`NodeManager`YARN集群中的每个具体节点的管理者。主要负责该节点内所有容器的生命周期的管理监视资源和跟踪节点健康。具体如下
- 启动时向`ResourceManager`注册并定时发送心跳消息,等待`ResourceManager`的指令;
- 维护`Container`的生命周期,监控`Container`的资源使用情况;
- 管理任务运行时的相关依赖,根据`ApplicationMaster`的需要,在启动`Container`之前将需要的程序及其依赖拷贝到本地。
### 3. ApplicationMaster
在用户提交一个应用程序时YARN会启动一个轻量级的进程`ApplicationMaster``ApplicationMaster`负责协调来自 `ResourceManager`的资源,并通过`NodeManager` 监视容器内资源的使用情况,同时还负责任务的监控与容错。具体如下:
- 根据应用的运行状态来决定动态计算资源需求;
-`ResourceManager`申请资源,监控申请的资源的使用情况;
- 跟踪任务状态和进度,报告资源的使用情况和应用的进度信息;
- 负责任务的容错。
### 4. Contain
`Container`YARN中的资源抽象它封装了某个节点上的多维度资源如内存、CPU、磁盘、网络等。当AMRM申请资源时RMAM返回的资源是用`Container`表示的。YARN会为每个任务分配一个`Container`,该任务只能使用该`Container`中描述的资源。`ApplicationMaster`可在`Container`内运行任何类型的任务。例如,`MapReduce ApplicationMaster`请求一个容器来启动 map 或 reduce 任务,而`Giraph ApplicationMaster`请求一个容器来运行 Giraph 任务。
## 三、YARN工作原理简述
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/yarn工作原理简图.png"/> </div>
1. `Client`提交作业到YARN上
2. `Resource Manager`选择一个`Node Manager`,启动一个`Container`并运行`Application Master`实例;
3. `Application Master`根据实际需要向`Resource Manager`请求更多的`Container`资源(如果作业很小, 应用管理器会选择在其自己的JVM中运行任务
4. `Application Master`通过获取到的`Container`资源执行分布式计算。
## 四、YARN工作原理详述
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/yarn工作原理.png"/> </div>
#### 1. 作业提交
client调用job.waitForCompletion方法向整个集群提交MapReduce作业 (第1步) 。新的作业ID(应用ID)由资源管理器分配(第2步)。作业的client核实作业的输出, 计算输入的split, 将作业的资源(包括Jar包配置文件, split信息)拷贝给HDFS(第3步)。 最后, 通过调用资源管理器的submitApplication()来提交作业(第4步)。
#### 2. 作业初始化
当资源管理器收到submitApplciation()的请求时, 就将该请求发给调度器(scheduler), 调度器分配container, 然后资源管理器在该container内启动应用管理器进程, 由节点管理器监控(第5步)。
MapReduce作业的应用管理器是一个主类为MRAppMasterJava应用其通过创造一些bookkeeping对象来监控作业的进度, 得到任务的进度和完成报告(第6步)。然后其通过分布式文件系统得到由客户端计算好的输入split(第7步)然后为每个输入split创建一个map任务, 根据mapreduce.job.reduces创建reduce任务对象。
#### 3. 任务分配
如果作业很小, 应用管理器会选择在其自己的JVM中运行任务。
如果不是小作业, 那么应用管理器向资源管理器请求container来运行所有的mapreduce任务(第8步)。这些请求是通过心跳来传输的, 包括每个map任务的数据位置比如存放输入split的主机名和机架(rack),调度器利用这些信息来调度任务,尽量将任务分配给存储数据的节点, 或者分配给和存放输入split的节点相同机架的节点。
#### 4. 任务运行
当一个任务由资源管理器的调度器分配给一个container后应用管理器通过联系节点管理器来启动container(第9步)。任务由一个主类为YarnChildJava应用执行 在运行任务之前首先本地化任务需要的资源比如作业配置JAR文件, 以及分布式缓存的所有文件(第10步。 最后, 运行mapreduce任务(第11步)。
YarnChild运行在一个专用的JVM中, 但是YARN不支持JVM重用。
#### 5. 进度和状态更新
YARN中的任务将其进度和状态(包括counter)返回给应用管理器, 客户端每秒(通mapreduce.client.progressmonitor.pollinterval设置)向应用管理器请求进度更新, 展示给用户。
#### 6. 作业完成
除了向应用管理器请求作业进度外, 客户端每5分钟都会通过调用waitForCompletion()来检查作业是否完成时间间隔可以通过mapreduce.client.completion.pollinterval来设置。作业完成之后, 应用管理器和container会清理工作状态 OutputCommiter的作业清理方法也会被调用。作业的信息会被作业历史服务器存储以备之后用户核查。
## 五、提交作业到YARN上运行
这里以提交Hadoop Examples中计算PiMApReduce程序为例相关Jar包在Hadoop安装目录的`share/hadoop/mapreduce`目录下:
```shell
# 提交格式: hadoop jar jar包路径 主类名称 主类参数
# hadoop jar hadoop-mapreduce-examples-2.6.0-cdh5.15.2.jar pi 3 3
```
## 参考资料
1. [初步掌握Yarn的架构及原理](https://www.cnblogs.com/codeOfLife/p/5492740.html)
2. [Apache Hadoop 2.9.2 > Apache Hadoop YARN](http://hadoop.apache.org/docs/stable/hadoop-yarn/hadoop-yarn-site/YARN.html)
# 集群资源管理器——YARN
<nav>
<a href="#一hadoop-yarn-简介">一、hadoop yarn 简介</a><br/>
<a href="#二YARN架构">二、YARN架构</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#1-ResourceManager">1. ResourceManager</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#2-NodeManager">2. NodeManager</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#3-ApplicationMaster">3. ApplicationMaster </a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#4-Contain">4. Contain</a><br/>
<a href="#三YARN工作原理简述">三、YARN工作原理简述</a><br/>
<a href="#四YARN工作原理详述">四、YARN工作原理详述</a><br/>
<a href="#五提交作业到YARN上运行">五、提交作业到YARN上运行</a><br/>
</nav>
## 一、hadoop yarn 简介
**Apache YARN** (Yet Another Resource Negotiator) 是 hadoop 2.0 引入的集群资源管理系统。用户可以将各种服务框架部署在 YARN 上,由 YARN 进行统一地管理和资源分配。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/yarn-base.png"/> </div>
## 二、YARN架构
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/Figure3Architecture-of-YARN.png"/> </div>
### 1. ResourceManager
`ResourceManager` 通常在独立的机器上以后台进程的形式运行,它是整个集群资源的主要协调者和管理者。`ResourceManager` 负责给用户提交的所有应用程序分配资源它根据应用程序优先级、队列容量、ACLs、数据位置等信息做出决策然后以共享的、安全的、多租户的方式制定分配策略调度集群资源。
### 2. NodeManager
`NodeManager`YARN 集群中的每个具体节点的管理者。主要负责该节点内所有容器的生命周期的管理,监视资源和跟踪节点健康。具体如下:
- 启动时向 `ResourceManager` 注册并定时发送心跳消息,等待 `ResourceManager` 的指令;
- 维护 `Container` 的生命周期,监控 `Container` 的资源使用情况;
- 管理任务运行时的相关依赖,根据 `ApplicationMaster` 的需要,在启动 `Container` 之前将需要的程序及其依赖拷贝到本地。
### 3. ApplicationMaster
在用户提交一个应用程序时YARN 会启动一个轻量级的进程 `ApplicationMaster``ApplicationMaster` 负责协调来自 `ResourceManager` 的资源,并通过 `NodeManager` 监视容器内资源的使用情况,同时还负责任务的监控与容错。具体如下:
- 根据应用的运行状态来决定动态计算资源需求;
- `ResourceManager` 申请资源,监控申请的资源的使用情况;
- 跟踪任务状态和进度,报告资源的使用情况和应用的进度信息;
- 负责任务的容错。
### 4. Contain
`Container`YARN 中的资源抽象它封装了某个节点上的多维度资源如内存、CPU、磁盘、网络等。当 AMRM 申请资源时RMAM 返回的资源是用 `Container` 表示的。YARN 会为每个任务分配一个 `Container`,该任务只能使用该 `Container` 中描述的资源。`ApplicationMaster` 可在 `Container` 内运行任何类型的任务。例如,`MapReduce ApplicationMaster` 请求一个容器来启动 map 或 reduce 任务,而 `Giraph ApplicationMaster` 请求一个容器来运行 Giraph 任务。
## 三、YARN工作原理简述
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/yarn工作原理简图.png"/> </div>
1. `Client` 提交作业到 YARN 上;
2. `Resource Manager` 选择一个 `Node Manager`,启动一个 `Container` 并运行 `Application Master` 实例;
3. `Application Master` 根据实际需要向 `Resource Manager` 请求更多的 `Container` 资源(如果作业很小, 应用管理器会选择在其自己的 JVM 中运行任务);
4. `Application Master` 通过获取到的 `Container` 资源执行分布式计算。
## 四、YARN工作原理详述
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/yarn工作原理.png"/> </div>
#### 1. 作业提交
client 调用 job.waitForCompletion 方法,向整个集群提交 MapReduce 作业 (第 1 步) 。新的作业 ID(应用 ID) 由资源管理器分配 (第 2 步)。作业的 client 核实作业的输出, 计算输入的 split, 将作业的资源 (包括 Jar 包,配置文件, split 信息) 拷贝给 HDFS(第 3 步)。 最后, 通过调用资源管理器的 submitApplication() 来提交作业 (第 4 步)。
#### 2. 作业初始化
当资源管理器收到 submitApplciation() 的请求时, 就将该请求发给调度器 (scheduler), 调度器分配 container, 然后资源管理器在该 container 内启动应用管理器进程, 由节点管理器监控 (第 5 步)。
MapReduce 作业的应用管理器是一个主类为 MRAppMasterJava 应用,其通过创造一些 bookkeeping 对象来监控作业的进度, 得到任务的进度和完成报告 (第 6 步)。然后其通过分布式文件系统得到由客户端计算好的输入 split(第 7 步),然后为每个输入 split 创建一个 map 任务, 根据 mapreduce.job.reduces 创建 reduce 任务对象。
#### 3. 任务分配
如果作业很小, 应用管理器会选择在其自己的 JVM 中运行任务。
如果不是小作业, 那么应用管理器向资源管理器请求 container 来运行所有的 mapreduce 任务 (第 8 步)。这些请求是通过心跳来传输的, 包括每个 map 任务的数据位置,比如存放输入 split 的主机名和机架 (rack),调度器利用这些信息来调度任务,尽量将任务分配给存储数据的节点, 或者分配给和存放输入 split 的节点相同机架的节点。
#### 4. 任务运行
当一个任务由资源管理器的调度器分配给一个 container 后,应用管理器通过联系节点管理器来启动 container(第 9 步)。任务由一个主类为 YarnChildJava 应用执行, 在运行任务之前首先本地化任务需要的资源比如作业配置JAR 文件, 以及分布式缓存的所有文件 (第 10 步。 最后, 运行 mapreduce 任务 (第 11 步)。
YarnChild 运行在一个专用的 JVM 中, 但是 YARN 不支持 JVM 重用。
#### 5. 进度和状态更新
YARN 中的任务将其进度和状态 (包括 counter) 返回给应用管理器, 客户端每秒 (通 mapreduce.client.progressmonitor.pollinterval 设置) 向应用管理器请求进度更新, 展示给用户。
#### 6. 作业完成
除了向应用管理器请求作业进度外, 客户端每 5 分钟都会通过调用 waitForCompletion() 来检查作业是否完成,时间间隔可以通过 mapreduce.client.completion.pollinterval 来设置。作业完成之后, 应用管理器和 container 会清理工作状态, OutputCommiter 的作业清理方法也会被调用。作业的信息会被作业历史服务器存储以备之后用户核查。
## 五、提交作业到YARN上运行
这里以提交 Hadoop Examples 中计算 PiMApReduce 程序为例,相关 Jar 包在 Hadoop 安装目录的 `share/hadoop/mapreduce` 目录下:
```shell
# 提交格式: hadoop jar jar包路径 主类名称 主类参数
# hadoop jar hadoop-mapreduce-examples-2.6.0-cdh5.15.2.jar pi 3 3
```
## 参考资料
1. [初步掌握 Yarn 的架构及原理](https://www.cnblogs.com/codeOfLife/p/5492740.html)
2. [Apache Hadoop 2.9.2 > Apache Hadoop YARN](http://hadoop.apache.org/docs/stable/hadoop-yarn/hadoop-yarn-site/YARN.html)

View File

@ -11,19 +11,19 @@
## 一、简述
截至到目前(2019.04)HBase 有两个主要的版本分别是1.x 和 2.x 两个版本的Java API有所不同1.x 中某些方法在2.x中被标识为`@deprecated`过时。所以下面关于API的样例我会分别给出1.x2.x两个版本。完整的代码见本仓库
截至到目前 (2019.04)HBase 有两个主要的版本,分别是 1.x 和 2.x ,两个版本的 Java API 有所不同1.x 中某些方法在 2.x 中被标识为 `@deprecated` 过时。所以下面关于 API 的样例,我会分别给出 1.x2.x 两个版本。完整的代码见本仓库:
>+ [Java API 1.x Examples](https://github.com/heibaiying/BigData-Notes/tree/master/code/Hbase/hbase-java-api-1.x)
>
>+ [Java API 2.x Examples](https://github.com/heibaiying/BigData-Notes/tree/master/code/Hbase/hbase-java-api-2.x)
同时你使用的客户端的版本必须与服务端版本保持一致如果用2.x版本的客户端代码去连接1.x版本的服务端会抛出`NoSuchColumnFamilyException`等异常。
同时你使用的客户端的版本必须与服务端版本保持一致,如果用 2.x 版本的客户端代码去连接 1.x 版本的服务端,会抛出 `NoSuchColumnFamilyException` 等异常。
## 二、Java API 1.x 基本使用
#### 2.1 新建Maven工程导入项目依赖
要使用Java API 操作HBase需要引入`hbase-client`。这里选取的`HBase Client`的版本为`1.2.0`
要使用 Java API 操作 HBase需要引入 `hbase-client`。这里选取的 `HBase Client` 的版本为 `1.2.0`
```xml
<dependency>
@ -53,7 +53,7 @@ public class HBaseUtils {
}
/**
* 创建HBase表
* 创建 HBase
*
* @param tableName 表名
* @param columnFamilies 列族的数组
@ -79,7 +79,7 @@ public class HBaseUtils {
/**
* 删除hBase表
* 删除 hBase
*
* @param tableName 表名
*/
@ -142,7 +142,7 @@ public class HBaseUtils {
/**
* 根据rowKey获取指定行的数据
* 根据 rowKey 获取指定行的数据
*
* @param tableName 表名
* @param rowKey 唯一标识
@ -160,7 +160,7 @@ public class HBaseUtils {
/**
* 获取指定行指定列(cell)的最新版本的数据
* 获取指定行指定列 (cell) 的最新版本的数据
*
* @param tableName 表名
* @param rowKey 唯一标识
@ -227,8 +227,8 @@ public class HBaseUtils {
* 检索表中指定数据
*
* @param tableName 表名
* @param startRowKey 起始RowKey
* @param endRowKey 终止RowKey
* @param startRowKey 起始 RowKey
* @param endRowKey 终止 RowKey
* @param filterList 过滤器
*/
@ -292,7 +292,7 @@ public class HBaseUtils {
### 2.3 单元测试
以单元测试的方式对上面封装的API进行测试。
以单元测试的方式对上面封装的 API 进行测试。
```java
public class HBaseUtilsTest {
@ -396,7 +396,7 @@ public class HBaseUtilsTest {
#### 3.1 新建Maven工程导入项目依赖
这里选取的`HBase Client`的版本为最新的`2.1.4`
这里选取的 `HBase Client` 的版本为最新的 `2.1.4`
```xml
<dependency>
@ -408,13 +408,13 @@ public class HBaseUtilsTest {
#### 3.2 API 的基本使用
2.x 版本相比于1.x 废弃了一部分方法关于废弃的方法在源码中都会指明新的替代方法比如在2.x中创建表时`HTableDescriptor``HColumnDescriptor`等类都标识为废弃,取而代之的是使用`TableDescriptorBuilder``ColumnFamilyDescriptorBuilder`来定义表和列族。
2.x 版本相比于 1.x 废弃了一部分方法,关于废弃的方法在源码中都会指明新的替代方法,比如,在 2.x 中创建表时:`HTableDescriptor``HColumnDescriptor` 等类都标识为废弃,取而代之的是使用 `TableDescriptorBuilder``ColumnFamilyDescriptorBuilder` 来定义表和列族。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/deprecated.png"/> </div>
以下为HBase 2.x 版本Java API的使用示例
以下为 HBase 2.x 版本 Java API 的使用示例:
```java
public class HBaseUtils {
@ -434,7 +434,7 @@ public class HBaseUtils {
}
/**
* 创建HBase表
* 创建 HBase
*
* @param tableName 表名
* @param columnFamilies 列族的数组
@ -461,7 +461,7 @@ public class HBaseUtils {
/**
* 删除hBase表
* 删除 hBase
*
* @param tableName 表名
*/
@ -524,7 +524,7 @@ public class HBaseUtils {
/**
* 根据rowKey获取指定行的数据
* 根据 rowKey 获取指定行的数据
*
* @param tableName 表名
* @param rowKey 唯一标识
@ -542,7 +542,7 @@ public class HBaseUtils {
/**
* 获取指定行指定列(cell)的最新版本的数据
* 获取指定行指定列 (cell) 的最新版本的数据
*
* @param tableName 表名
* @param rowKey 唯一标识
@ -609,8 +609,8 @@ public class HBaseUtils {
* 检索表中指定数据
*
* @param tableName 表名
* @param startRowKey 起始RowKey
* @param endRowKey 终止RowKey
* @param startRowKey 起始 RowKey
* @param endRowKey 终止 RowKey
* @param filterList 过滤器
*/
@ -676,9 +676,9 @@ public class HBaseUtils {
## 四、正确连接Hbase
在上面的代码中在类加载时就初始化了Connection连接并且之后的方法都是复用这个Connection这时我们可能会考虑是否可以使用自定义连接池来获取更好的性能表现实际上这是没有必要的。
在上面的代码中,在类加载时就初始化了 Connection 连接,并且之后的方法都是复用这个 Connection这时我们可能会考虑是否可以使用自定义连接池来获取更好的性能表现实际上这是没有必要的。
首先官方对于`Connection`的使用说明如下:
首先官方对于 `Connection` 的使用说明如下:
```properties
Connection Pooling For applications which require high-end multithreaded
@ -686,8 +686,8 @@ access (e.g., web-servers or application servers that may serve many
application threads in a single JVM), you can pre-create a Connection,
as shown in the following example:
对于高并发多线程访问的应用程序例如在单个JVM中存在的为多个线程服务的Web服务器或应用程序服务器
您只需要预先创建一个Connection。例子如下
对于高并发多线程访问的应用程序(例如,在单个 JVM 中存在的为多个线程服务的 Web 服务器或应用程序服务器),
您只需要预先创建一个 Connection。例子如下
// Create a connection to the cluster.
Configuration conf = HBaseConfiguration.create();
@ -697,7 +697,7 @@ try (Connection connection = ConnectionFactory.createConnection(conf);
}
```
之所以能这样使用这是因为Connection并不是一个简单的socket连接[接口文档](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Connection.html)中对Connection的表述是
之所以能这样使用,这是因为 Connection 并不是一个简单的 socket 连接,[接口文档](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Connection.html) 中对 Connection 的表述是:
```properties
A cluster connection encapsulating lower level individual connections to actual servers and a
@ -705,25 +705,25 @@ connection to zookeeper. Connections are instantiated through the ConnectionFac
The lifecycle of the connection is managed by the caller, who has to close() the connection
to release the resources.
Connection是一个集群连接封装了与多台服务器Matser/Region Server的底层连接以及与zookeeper的连接。
连接通过ConnectionFactory 类实例化。连接的生命周期由调用者管理调用者必须使用close()关闭连接以释放资源。
Connection 是一个集群连接封装了与多台服务器Matser/Region Server的底层连接以及与 zookeeper 的连接。
连接通过 ConnectionFactory 类实例化。连接的生命周期由调用者管理,调用者必须使用 close() 关闭连接以释放资源。
```
之所以封装这些连接是因为HBase客户端需要连接三个不同的服务角色
之所以封装这些连接,是因为 HBase 客户端需要连接三个不同的服务角色:
+ **Zookeeper** :主要用于获取`meta`表的位置信息Master的信息
+ **HBase Master** 主要用于执行HBaseAdmin接口的一些操作例如建表等
+ **Zookeeper** :主要用于获取 `meta` 表的位置信息Master 的信息;
+ **HBase Master** :主要用于执行 HBaseAdmin 接口的一些操作,例如建表等;
+ **HBase RegionServer** :用于读、写数据。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-arc.png"/> </div>
Connection对象和实际的Socket连接之间的对应关系如下图
Connection 对象和实际的 Socket 连接之间的对应关系如下图:
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-connection.png"/> </div>
> 上面两张图片引用自博客:[连接HBase的正确姿势](https://yq.aliyun.com/articles/581702?spm=a2c4e.11157919.spm-cont-list.1.146c27aeFxoMsN%20%E8%BF%9E%E6%8E%A5HBase%E7%9A%84%E6%AD%A3%E7%A1%AE%E5%A7%BF%E5%8A%BF)
> 上面两张图片引用自博客:[连接 HBase 的正确姿势](https://yq.aliyun.com/articles/581702?spm=a2c4e.11157919.spm-cont-list.1.146c27aeFxoMsN%20%E8%BF%9E%E6%8E%A5HBase%E7%9A%84%E6%AD%A3%E7%A1%AE%E5%A7%BF%E5%8A%BF)
在HBase客户端代码中真正对应Socket连接的是`RpcConnection`对象。HBase使用`PoolMap`这种数据结构来存储客户端到HBase服务器之间的连接。`PoolMap`的内部有一个`ConcurrentHashMap`实例其key`ConnectionId`(封装了服务器地址和用户ticket)value是一个`RpcConnection`对象的资源池。当HBase需要连接一个服务器时首先会根据`ConnectionId`找到对应的连接池,然后从连接池中取出一个连接对象。
HBase 客户端代码中,真正对应 Socket 连接的是 `RpcConnection` 对象。HBase 使用 `PoolMap` 这种数据结构来存储客户端到 HBase 服务器之间的连接。`PoolMap` 的内部有一个 `ConcurrentHashMap` 实例,其 key`ConnectionId`(封装了服务器地址和用户 ticket)value 是一个 `RpcConnection` 对象的资源池。当 HBase 需要连接一个服务器时,首先会根据 `ConnectionId` 找到对应的连接池,然后从连接池中取出一个连接对象。
```java
@InterfaceAudience.Private
@ -740,7 +740,7 @@ public class PoolMap<K, V> implements Map<K, V> {
.....
```
HBase中提供了三种资源池的实现分别是`Reusable``RoundRobin``ThreadLocal`。具体实现可以通`hbase.client.ipc.pool.type`配置项指定,默认为`Reusable`。连接池的大小也可以通过`hbase.client.ipc.pool.size`配置项指定默认为1即每个Server 1个连接。也可以通过修改配置实现
HBase 中提供了三种资源池的实现,分别是 `Reusable``RoundRobin``ThreadLocal`。具体实现可以通 `hbase.client.ipc.pool.type` 配置项指定,默认为 `Reusable`。连接池的大小也可以通过 `hbase.client.ipc.pool.size` 配置项指定,默认为 1即每个 Server 1 个连接。也可以通过修改配置实现:
```java
config.set("hbase.client.ipc.pool.type",...);
@ -748,14 +748,14 @@ config.set("hbase.client.ipc.pool.size",...);
connection = ConnectionFactory.createConnection(config);
```
由此可以看出HBaseConnection类已经实现了对连接的管理功能所以我们不必在Connection上在做额外的管理。
由此可以看出 HBaseConnection 类已经实现了对连接的管理功能,所以我们不必在 Connection 上在做额外的管理。
另外Connection是线程安全的但TableAdmin却不是线程安全的因此正确的做法是一个进程共用一个Connection对象而在不同的线程中使用单独的TableAdmin对象。TableAdmin的获取操作`getTable()``getAdmin()`都是轻量级,所以不必担心性能的消耗,同时建议在使用完成后显示的调用`close()`方法来关闭它们。
另外Connection 是线程安全的,但 TableAdmin 却不是线程安全的,因此正确的做法是一个进程共用一个 Connection 对象,而在不同的线程中使用单独的 TableAdmin 对象。TableAdmin 的获取操作 `getTable()``getAdmin()` 都是轻量级,所以不必担心性能的消耗,同时建议在使用完成后显示的调用 `close()` 方法来关闭它们。
## 参考资料
1. [连接HBase的正确姿势](https://yq.aliyun.com/articles/581702?spm=a2c4e.11157919.spm-cont-list.1.146c27aeFxoMsN%20%E8%BF%9E%E6%8E%A5HBase%E7%9A%84%E6%AD%A3%E7%A1%AE%E5%A7%BF%E5%8A%BF)
1. [连接 HBase 的正确姿势](https://yq.aliyun.com/articles/581702?spm=a2c4e.11157919.spm-cont-list.1.146c27aeFxoMsN%20%E8%BF%9E%E6%8E%A5HBase%E7%9A%84%E6%AD%A3%E7%A1%AE%E5%A7%BF%E5%8A%BF)
2. [Apache HBase ™ Reference Guide](http://hbase.apache.org/book.htm)

View File

@ -29,7 +29,7 @@
## 一、基本命令
打开Hbase Shell
打开 Hbase Shell
```shell
# hbase shell
@ -68,7 +68,7 @@ list
#### 2.2 创建表
**命令格式** create '表名称', '列族名称1','列族名称2','列名称N'
**命令格式** create '表名称', '列族名称 1','列族名称 2','列名称 N'
```shell
# 创建一张名为Student的表,包含基本信息baseInfo、学校信息schoolInfo两个列族
@ -85,7 +85,7 @@ describe 'Student'
#### 2.4 表的启用/禁用
enabledisable可以启用/禁用这个表,is_enabledis_disabled来检查表是否被禁用
enabledisable 可以启用/禁用这个表,is_enabledis_disabled 来检查表是否被禁用
```shell
# 禁用表
@ -136,7 +136,7 @@ alter 'Student', {NAME => 'teacherInfo', METHOD => 'delete'}
#### 3.3 更改列族存储版本的限制
默认情况下,列族只存储一个版本的数据,如果需要存储多个版本的数据,则需要修改列族的属性。修改后可通过`desc`命令查看。
默认情况下,列族只存储一个版本的数据,如果需要存储多个版本的数据,则需要修改列族的属性。修改后可通过 `desc` 命令查看。
```shell
alter 'Student',{NAME=>'baseInfo',VERSIONS=>3}
@ -194,13 +194,13 @@ delete 'Student','rowkey3','baseInfo:name'
## 四、查询
hbase中访问数据有两种基本的方式
hbase 中访问数据有两种基本的方式:
+ 按指定rowkey获取数据get方法
+ 按指定 rowkey 获取数据get 方法;
+ 按指定条件获取数据scan方法。
+ 按指定条件获取数据scan 方法。
`scan`可以设置beginend参数来访问一个范围内所有的数据。get本质上就是beginend相等的一种特殊的scan。
`scan` 可以设置 beginend 参数来访问一个范围内所有的数据。get 本质上就是 beginend 相等的一种特殊的 scan。
#### 4.1Get查询
@ -232,9 +232,9 @@ scan 'Student', {COLUMN=>'baseInfo'}
scan 'Student', {COLUMNS=> 'baseInfo:birthday'}
```
除了列`COLUMNS`修饰词外HBase还支持`Limit`(限制查询结果行数),`STARTROW``ROWKEY`起始行,会先根据这个`key`定位到`region`,再向后扫描)、`STOPROW`(结束行)、`TIMERANGE`(限定时间戳范围)、`VERSIONS`(版本数)、和`FILTER`(按条件过滤行)等。
除了列 `COLUMNS` 修饰词外HBase 还支持 `Limit`(限制查询结果行数),`STARTROW``ROWKEY` 起始行,会先根据这个 `key` 定位到 `region`,再向后扫描)、`STOPROW`(结束行)、`TIMERANGE`(限定时间戳范围)、`VERSIONS`(版本数)、和 `FILTER`(按条件过滤行)等。
如下代表从`rowkey2`这个`rowkey`开始,查找下两个行的最新3个版本的name列的数据
如下代表从 `rowkey2` 这个 `rowkey` 开始,查找下两个行的最新 3 个版本的 name 列的数据:
```shell
scan 'Student', {COLUMNS=> 'baseInfo:name',STARTROW => 'rowkey2',STOPROW => 'wrowkey4',LIMIT=>2, VERSIONS=>3}
@ -242,32 +242,32 @@ scan 'Student', {COLUMNS=> 'baseInfo:name',STARTROW => 'rowkey2',STOPROW => 'wro
#### 4.5 条件过滤
Filter可以设定一系列条件来进行过滤。如我们要查询值等于24的所有数据
Filter 可以设定一系列条件来进行过滤。如我们要查询值等于 24 的所有数据:
```shell
scan 'Student', FILTER=>"ValueFilter(=,'binary:24')"
```
值包含yale的所有数据
值包含 yale 的所有数据:
```shell
scan 'Student', FILTER=>"ValueFilter(=,'substring:yale')"
```
列名中的前缀为birth的
列名中的前缀为 birth 的:
```shell
scan 'Student', FILTER=>"ColumnPrefixFilter('birth')"
```
FILTER中支持多个过滤条件通过括号、ANDOR进行组合
FILTER 中支持多个过滤条件通过括号、ANDOR 进行组合:
```shell
# 列名中的前缀为birth且列值中包含1998的数据
scan 'Student', FILTER=>"ColumnPrefixFilter('birth') AND ValueFilter ValueFilter(=,'substring:1998')"
```
`PrefixFilter`用于对Rowkey的前缀进行判断
`PrefixFilter` 用于对 Rowkey 的前缀进行判断:
```shell
scan 'Student', FILTER=>"PrefixFilter('wr')"

View File

@ -20,7 +20,7 @@
## 一、简述
在使用HBase时如果你的数据量达到了数十亿行或数百万列此时能否在查询中返回大量数据将受制于网络的带宽即便网络状况允许但是客户端的计算处理也未必能够满足要求。在这种情况下协处理器Coprocessors应运而生。它允许你将业务计算代码放入在RegionServer的协处理器中将处理好的数据再返回给客户端这可以极大地降低需要传输的数据量从而获得性能上的提升。同时协处理器也允许用户扩展实现HBase目前所不具备的功能如权限校验、二级索引、完整性约束等。
在使用 HBase 如果你的数据量达到了数十亿行或数百万列此时能否在查询中返回大量数据将受制于网络的带宽即便网络状况允许但是客户端的计算处理也未必能够满足要求。在这种情况下协处理器Coprocessors应运而生。它允许你将业务计算代码放入在 RegionServer 的协处理器中,将处理好的数据再返回给客户端,这可以极大地降低需要传输的数据量,从而获得性能上的提升。同时协处理器也允许用户扩展实现 HBase 目前所不具备的功能,如权限校验、二级索引、完整性约束等。
@ -30,24 +30,24 @@
#### 1. 功能
Observer协处理器类似于关系型数据库中的触发器当发生某些事件的时候这类协处理器会被Server端调用。通常可以用来实现下面功能
Observer 协处理器类似于关系型数据库中的触发器,当发生某些事件的时候这类协处理器会被 Server 端调用。通常可以用来实现下面功能:
+ **权限校验**:在执行`Get``Put`操作之前,您可以使用`preGet``prePut`方法检查权限;
+ **完整性约束** HBase不支持关系型数据库中的外键功能可以通过触发器在插入或者删除数据的时候对关联的数据进行检查
+ **权限校验**:在执行 `Get``Put` 操作之前,您可以使用 `preGet``prePut` 方法检查权限;
+ **完整性约束** HBase 不支持关系型数据库中的外键功能,可以通过触发器在插入或者删除数据的时候,对关联的数据进行检查;
+ **二级索引** 可以使用协处理器来维护二级索引。
</br>
#### 2. 类型
当前Observer协处理器有以下四种类型
当前 Observer 协处理器有以下四种类型:
+ **RegionObserver** :
允许您观察Region上的事件例如GetPut操作。
允许您观察 Region 上的事件,例如 GetPut 操作。
+ **RegionServerObserver** :
允许您观察与RegionServer操作相关的事件例如启动停止或执行合并提交或回滚。
允许您观察与 RegionServer 操作相关的事件,例如启动,停止或执行合并,提交或回滚。
+ **MasterObserver** :
允许您观察与HBase Master相关的事件例如表创建删除或schema修改。
允许您观察与 HBase Master 相关的事件,例如表创建,删除或 schema 修改。
+ **WalObserver** :
允许您观察与预写日志WAL相关的事件。
@ -55,11 +55,11 @@ Observer协处理器类似于关系型数据库中的触发器当发生某些
#### 3. 接口
以上四种类型的Observer协处理器均继承自`Coprocessor`接口这四个接口中分别定义了所有可用的钩子方法以便在对应方法前后执行特定的操作。通常情况下我们并不会直接实现上面接口而是继承其Base实现类Base实现类只是简单空实现了接口中的方法这样我们在实现自定义的协处理器时就不必实现所有方法只需要重写必要方法即可。
以上四种类型的 Observer 协处理器均继承自 `Coprocessor` 接口,这四个接口中分别定义了所有可用的钩子方法,以便在对应方法前后执行特定的操作。通常情况下,我们并不会直接实现上面接口,而是继承其 Base 实现类Base 实现类只是简单空实现了接口中的方法,这样我们在实现自定义的协处理器时,就不必实现所有方法,只需要重写必要方法即可。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-coprocessor.png"/> </div>
这里以`RegionObservers `为例,其接口类中定义了所有可用的钩子方法,下面截取了部分方法的定义,多数方法都是成对出现的,有`pre`就有`post`
这里以 `RegionObservers ` 为例,其接口类中定义了所有可用的钩子方法,下面截取了部分方法的定义,多数方法都是成对出现的,有 `pre` 就有 `post`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/RegionObserver.png"/> </div>
@ -72,32 +72,32 @@ Observer协处理器类似于关系型数据库中的触发器当发生某些
+ 客户端发出 put 请求
+ 该请求被分派给合适的 RegionServer 和 region
+ coprocessorHost 拦截该请求,然后在该表的每个 RegionObserver 上调用 prePut()
+ 如果没有被`prePut()`拦截,该请求继续送到 region然后进行处理
+ region 产生的结果再次被 CoprocessorHost 拦截,调用`postPut()`
+ 假如没有`postPut()`拦截该响应,最终结果被返回给客户端
+ 如果没有被 `prePut()` 拦截,该请求继续送到 region然后进行处理
+ region 产生的结果再次被 CoprocessorHost 拦截,调用 `postPut()`
+ 假如没有 `postPut()` 拦截该响应,最终结果被返回给客户端
如果大家了解Spring可以将这种执行方式类比于其AOP的执行原理即可官方文档当中也是这样类比的
如果大家了解 Spring可以将这种执行方式类比于其 AOP 的执行原理即可,官方文档当中也是这样类比的:
>If you are familiar with Aspect Oriented Programming (AOP), you can think of a coprocessor as applying advice by intercepting a request and then running some custom code,before passing the request on to its final destination (or even changing the destination).
>
>如果您熟悉面向切面编程AOP您可以将协处理器视为通过拦截请求然后运行一些自定义代码来使用Advice然后将请求传递到其最终目标或者更改目标
>如果您熟悉面向切面编程AOP您可以将协处理器视为通过拦截请求然后运行一些自定义代码来使用 Advice然后将请求传递到其最终目标或者更改目标
### 2.2 Endpoint协处理器
Endpoint协处理器类似于关系型数据库中的存储过程。客户端可以调用Endpoint协处理器在服务端对数据进行处理然后再返回。
Endpoint 协处理器类似于关系型数据库中的存储过程。客户端可以调用 Endpoint 协处理器在服务端对数据进行处理,然后再返回。
以聚集操作为例,如果没有协处理器,当用户需要找出一张表中的最大数据,即 max 聚合操作,就必须进行全表扫描,然后在客户端上遍历扫描结果,这必然会加重了客户端处理数据的压力。利用 Coprocessor用户可以将求最大值的代码部署到 HBase Server 端HBase将利用底层 cluster 的多个节点并发执行求最大值的操作。即在每个 Region 范围内执行求最大值的代码,将每个 Region 的最大值在 Region Server 端计算出来,仅仅将该 max 值返回给客户端。之后客户端只需要将每个 Region 的最大值进行比较而找到其中最大的值即可。
以聚集操作为例,如果没有协处理器,当用户需要找出一张表中的最大数据,即 max 聚合操作,就必须进行全表扫描,然后在客户端上遍历扫描结果,这必然会加重了客户端处理数据的压力。利用 Coprocessor用户可以将求最大值的代码部署到 HBase Server 端HBase 将利用底层 cluster 的多个节点并发执行求最大值的操作。即在每个 Region 范围内执行求最大值的代码,将每个 Region 的最大值在 Region Server 端计算出来,仅仅将该 max 值返回给客户端。之后客户端只需要将每个 Region 的最大值进行比较而找到其中最大的值即可。
## 三、协处理的加载方式
要使用我们自己开发的协处理器必须通过静态使用HBase配置或动态使用HBase ShellJava API加载它。
要使用我们自己开发的协处理器,必须通过静态(使用 HBase 配置)或动态(使用 HBase ShellJava API加载它。
+ 静态加载的协处理器称之为 **System Coprocessor**(系统级协处理器),作用范围是整个HBase上的所有表需要重启HBase服务
+ 动态加载的协处理器称之为 **Table Coprocessor**表处理器作用于指定的表不需要重启HBase服务。
+ 静态加载的协处理器称之为 **System Coprocessor**(系统级协处理器),作用范围是整个 HBase 上的所有表,需要重启 HBase 服务;
+ 动态加载的协处理器称之为 **Table Coprocessor**(表处理器),作用于指定的表,不需要重启 HBase 服务。
其加载和卸载方式分别介绍如下。
@ -109,7 +109,7 @@ Endpoint协处理器类似于关系型数据库中的存储过程。客户端可
静态加载分以下三步:
1.`hbase-site.xml`定义需要加载的协处理器。
1. `hbase-site.xml` 定义需要加载的协处理器。
```xml
<property>
@ -118,44 +118,44 @@ Endpoint协处理器类似于关系型数据库中的存储过程。客户端可
</property>
```
` <name>`标签的值必须是下面其中之一:
` <name>` 标签的值必须是下面其中之一:
+ RegionObservers 和 Endpoints协处理器`hbase.coprocessor.region.classes`
+ WALObservers协处理器 `hbase.coprocessor.wal.classes`
+ MasterObservers协处理器`hbase.coprocessor.master.classes`
+ RegionObservers 和 Endpoints 协处理器:`hbase.coprocessor.region.classes`
+ WALObservers 协处理器: `hbase.coprocessor.wal.classes`
+ MasterObservers 协处理器:`hbase.coprocessor.master.classes`
`<value>`必须是协处理器实现类的全限定类名。如果为加载指定了多个类,则类名必须以逗号分隔。
`<value>` 必须是协处理器实现类的全限定类名。如果为加载指定了多个类,则类名必须以逗号分隔。
2. 将jar(包含代码和所有依赖项)放入HBase安装目录中的`lib`目录下;
2. jar(包含代码和所有依赖项) 放入 HBase 安装目录中的 `lib` 目录下;
3. 重启HBase。
3. 重启 HBase。
</br>
### 4.2 静态卸载
1. 从hbase-site.xml中删除配置的协处理器的\<property>元素及其子元素;
1. hbase-site.xml 中删除配置的协处理器的\<property>元素及其子元素;
2. 从类路径或HBaselib目录中删除协处理器的JAR文件可选
2. 从类路径或 HBaselib 目录中删除协处理器的 JAR 文件(可选);
3. 重启HBase。
3. 重启 HBase。
## 五、动态加载与卸载
使用动态加载协处理器不需要重新启动HBase。但动态加载的协处理器是基于每个表加载的只能用于所指定的表。
使用动态加载协处理器,不需要重新启动 HBase。但动态加载的协处理器是基于每个表加载的只能用于所指定的表。
此外在使用动态加载必须使表脱机disable以加载协处理器。动态加载通常有两种方式Shell 和 Java API 。
> 以下示例基于两个前提:
>
> 1. coprocessor.jar 包含协处理器实现及其所有依赖项。
> 2. JAR 包存放在HDFS上的路径为hdfs// \<namenode>\<port> / user / \<hadoop-user> /coprocessor.jar
> 2. JAR 包存放在 HDFS 上的路径为hdfs// \<namenode>\<port> / user / \<hadoop-user> /coprocessor.jar
### 5.1 HBase Shell动态加载
1. 使用HBase Shell禁用表
1. 使用 HBase Shell 禁用表
```shell
hbase > disable 'tableName'
@ -169,12 +169,12 @@ user/<hadoop-user>/coprocessor.jar| org.myname.hbase.Coprocessor.RegionObserverE
arg1=1,arg2=2'
```
`Coprocessor`包含由管道(|)字符分隔的四个参数,按顺序解释如下:
`Coprocessor` 包含由管道(|)字符分隔的四个参数,按顺序解释如下:
+ **JAR包路径**通常为JAR包在HDFS上的路径。关于路径以下两点需要注意
+ 允许使用通配符,例如:`hdfs://<namenode>:<port>/user/<hadoop-user>/*.jar` 来添加指定的JAR包
+ **JAR 包路径**:通常为 JAR 包在 HDFS 上的路径。关于路径以下两点需要注意:
+ 允许使用通配符,例如:`hdfs://<namenode>:<port>/user/<hadoop-user>/*.jar` 来添加指定的 JAR 包;
+ 可以使指定目录,例如:`hdfs://<namenode>:<port>/user/<hadoop-user>/` 这会添加目录中的所有JAR包但不会搜索子目录中的JAR包。
+ 可以使指定目录,例如:`hdfs://<namenode>:<port>/user/<hadoop-user>/` ,这会添加目录中的所有 JAR 包,但不会搜索子目录中的 JAR 包。
+ **类名**:协处理器的完整类名。
+ **优先级**:协处理器的优先级,遵循数字的自然序,即值越小优先级越高。可以为空,在这种情况下,将分配默认优先级值。
@ -192,7 +192,7 @@ hbase > enable 'tableName'
hbase > describe 'tableName'
```
协处理器出现在`TABLE_ATTRIBUTES`属性中则代表加载成功。
协处理器出现在 `TABLE_ATTRIBUTES` 属性中则代表加载成功。
</br>
@ -241,7 +241,7 @@ admin.modifyTable(tableName, hTableDescriptor);
admin.enableTable(tableName);
```
在HBase 0.96及其以后版本中HTableDescriptoraddCoprocessor()方法提供了一种更为简便的加载方法。
HBase 0.96 及其以后版本中HTableDescriptoraddCoprocessor() 方法提供了一种更为简便的加载方法。
```java
TableName tableName = TableName.valueOf("users");
@ -291,7 +291,7 @@ admin.enableTable(tableName);
## 六、协处理器案例
这里给出一个简单的案例实现一个类似于Redis`append` 命令的协处理器当我们对已有列执行put操作时候HBase默认执行的是update操作这里我们修改为执行append操作。
这里给出一个简单的案例,实现一个类似于 Redis`append` 命令的协处理器,当我们对已有列执行 put 操作时候HBase 默认执行的是 update 操作,这里我们修改为执行 append 操作。
```shell
# redis append 命令示例
@ -316,7 +316,7 @@ hbase > create 'magazine','article','picture'
> 完整代码可见本仓库:[hbase-observer-coprocessor](https://github.com/heibaiying/BigData-Notes/tree/master/code/Hbase\hbase-observer-coprocessor)
新建Maven工程导入下面依赖
新建 Maven 工程,导入下面依赖:
```xml
<dependency>
@ -331,7 +331,7 @@ hbase > create 'magazine','article','picture'
</dependency>
```
继承`BaseRegionObserver`实现我们自定义的`RegionObserver`,对相同的`article:content`执行put命令时将新插入的内容添加到原有内容的末尾代码如下
继承 `BaseRegionObserver` 实现我们自定义的 `RegionObserver`,对相同的 `article:content` 执行 put 命令时,将新插入的内容添加到原有内容的末尾,代码如下:
```java
public class AppendRegionObserver extends BaseRegionObserver {
@ -369,7 +369,7 @@ public class AppendRegionObserver extends BaseRegionObserver {
### 6.3 打包项目
使用maven命令进行打包打包后的文件名为`hbase-observer-coprocessor-1.0-SNAPSHOT.jar`
使用 maven 命令进行打包,打包后的文件名为 `hbase-observer-coprocessor-1.0-SNAPSHOT.jar`
```shell
# mvn clean package
@ -411,7 +411,7 @@ hbase > enable 'magazine'
hbase > desc 'magazine'
```
协处理器出现在`TABLE_ATTRIBUTES`属性中则代表加载成功,如下图:
协处理器出现在 `TABLE_ATTRIBUTES` 属性中则代表加载成功,如下图:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-cp-load.png"/> </div>
@ -426,7 +426,7 @@ hbase > put 'magazine', 'rowkey1','article:content','World'
hbase > get 'magazine','rowkey1','article:content'
```
可以看到对于指定列的值已经执行了append操作
可以看到对于指定列的值已经执行了 append 操作:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-cp-helloworld.png"/> </div>
@ -439,7 +439,7 @@ hbase > put 'magazine', 'rowkey1','article:author','lisi'
hbase > get 'magazine','rowkey1','article:author'
```
可以看到对于正常的列还是执行update操作:
可以看到对于正常的列还是执行 update 操作:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-cp-lisi.png"/> </div>
@ -487,4 +487,4 @@ hbase > get 'magazine','rowkey1','article:content'
1. [Apache HBase Coprocessors](http://hbase.apache.org/book.html#cp)
2. [Apache HBase Coprocessor Introduction](https://blogs.apache.org/hbase/entry/coprocessor_introduction)
3. [HBase高階知識](https://www.itread01.com/content/1546245908.html)
3. [HBase 高階知識](https://www.itread01.com/content/1546245908.html)

View File

@ -1,196 +1,196 @@
# Hbase容灾与备份
<nav>
<a href="#一前言">一、前言</a><br/>
<a href="#二CopyTable">二、CopyTable</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-简介">2.1 简介</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-命令格式">2.2 命令格式</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-常用命令">2.3 常用命令</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#24-更多参数">2.4 更多参数</a><br/>
<a href="#三ExportImport">三、Export/Import</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-简介">3.1 简介</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-命令格式">3.2 命令格式</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#33-常用命令">3.3 常用命令</a><br/>
<a href="#四Snapshot">四、Snapshot</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-简介">4.1 简介</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-配置">4.2 配置</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#43-常用命令">4.3 常用命令</a><br/>
</nav>
## 一、前言
本文主要介绍Hbase常用的三种简单的容灾备份方案即**CopyTable**、**Export**/**Import**、**Snapshot**。分别介绍如下:
## 二、CopyTable
### 2.1 简介
**CopyTable**可以将现有表的数据复制到新表中,具有以下特点:
- 支持时间区间 、row区间 、改变表名称 、改变列族名称 、以及是否Copy已被删除的数据等功能
- 执行命令前,需先创建与原表结构相同的新表;
- `CopyTable`的操作是基于HBase Client API进行的即采用`scan`进行查询, 采用`put`进行写入。
### 2.2 命令格式
```shell
Usage: CopyTable [general options] [--starttime=X] [--endtime=Y] [--new.name=NEW] [--peer.adr=ADR] <tablename>
```
### 2.3 常用命令
1. 同集群下CopyTable
```shell
hbase org.apache.hadoop.hbase.mapreduce.CopyTable --new.name=tableCopy tableOrig
```
2. 不同集群下CopyTable
```shell
# 两表名称相同的情况
hbase org.apache.hadoop.hbase.mapreduce.CopyTable \
--peer.adr=dstClusterZK:2181:/hbase tableOrig
# 也可以指新的表名
hbase org.apache.hadoop.hbase.mapreduce.CopyTable \
--peer.adr=dstClusterZK:2181:/hbase \
--new.name=tableCopy tableOrig
```
3. 下面是一个官方给的比较完整的例子,指定开始和结束时间,集群地址,以及只复制指定的列族:
```shell
hbase org.apache.hadoop.hbase.mapreduce.CopyTable \
--starttime=1265875194289 \
--endtime=1265878794289 \
--peer.adr=server1,server2,server3:2181:/hbase \
--families=myOldCf:myNewCf,cf2,cf3 TestTable
```
### 2.4 更多参数
可以通过`--help`查看更多支持的参数
```shell
# hbase org.apache.hadoop.hbase.mapreduce.CopyTable --help
```
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-copy-table.png"/> </div>
## 三、Export/Import
### 3.1 简介
- `Export`支持导出数据到HDFS, `Import`支持从HDFS导入数据。`Export`还支持指定导出数据的开始时间和结束时间,因此可以用于增量备份。
- `Export`导出与`CopyTable`一样依赖HBase`scan`操作
### 3.2 命令格式
```shell
# Export
hbase org.apache.hadoop.hbase.mapreduce.Export <tablename> <outputdir> [<versions> [<starttime> [<endtime>]]]
# Inport
hbase org.apache.hadoop.hbase.mapreduce.Import <tablename> <inputdir>
```
+ 导出的`outputdir`目录可以不用预先创建,程序会自动创建。导出完成后,导出文件的所有权将由执行导出命令的用户所拥有。
+ 默认情况下,仅导出给定`Cell`的最新版本,而不管历史版本。要导出多个版本,需要将`<versions>`参数替换为所需的版本数。
### 3.3 常用命令
1. 导出命令
```shell
hbase org.apache.hadoop.hbase.mapreduce.Export tableName hdfs路径/tableName.db
```
2. 导入命令
```
hbase org.apache.hadoop.hbase.mapreduce.Import tableName hdfs路径/tableName.db
```
## 四、Snapshot
### 4.1 简介
HBase的快照(Snapshot)功能允许您获取表的副本(包括内容和元数据)并且性能开销很小。因为快照存储的仅仅是表的元数据和HFiles的信息。快照的`clone`操作会从该快照创建新表,快照的`restore`操作会将表的内容还原到快照节点。`clone``restore`操作不需要复制任何数据因为底层HFiles(包含HBase表数据的文件)不会被修改,修改的只是表的元数据信息。
### 4.2 配置
HBase快照功能默认没有开启如果要开启快照需要在`hbase-site.xml`文件中添加如下配置项:
```xml
<property>
<name>hbase.snapshot.enabled</name>
<value>true</value>
</property>
```
### 4.3 常用命令
快照的所有命令都需要在Hbase Shell交互式命令行中执行。
#### 1. Take a Snapshot
```shell
# 拍摄快照
hbase> snapshot '表名', '快照名'
```
默认情况下拍摄快照之前会在内存中执行数据刷新。以保证内存中的数据包含在快照中。但是如果你不希望包含内存中的数据,则可以使用`SKIP_FLUSH`选项禁止刷新。
```shell
# 禁止内存刷新
hbase> snapshot '表名', '快照名', {SKIP_FLUSH => true}
```
#### 2. Listing Snapshots
```shell
# 获取快照列表
hbase> list_snapshots
```
#### 3. Deleting Snapshots
```shell
# 删除快照
hbase> delete_snapshot '快照名'
```
#### 4. Clone a table from snapshot
```shell
# 从现有的快照创建一张新表
hbase> clone_snapshot '快照名', '新表名'
```
#### 5. Restore a snapshot
将表恢复到快照节点,恢复操作需要先禁用表
```shell
hbase> disable '表名'
hbase> restore_snapshot '快照名'
```
这里需要注意的是是如果HBase配置了基于Replication的主从复制由于Replication在日志级别工作而快照在文件系统级别工作因此在还原之后会出现副本与主服务器处于不同的状态的情况。这时候可以先停止同步所有服务器还原到一致的数据点后再重新建立同步。
## 参考资料
1. [Online Apache HBase Backups with CopyTable](https://blog.cloudera.com/blog/2012/06/online-hbase-backups-with-copytable-2/)
2. [Apache HBase ™ Reference Guide](http://hbase.apache.org/book.htm)
# Hbase容灾与备份
<nav>
<a href="#一前言">一、前言</a><br/>
<a href="#二CopyTable">二、CopyTable</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-简介">2.1 简介</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-命令格式">2.2 命令格式</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-常用命令">2.3 常用命令</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#24-更多参数">2.4 更多参数</a><br/>
<a href="#三ExportImport">三、Export/Import</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-简介">3.1 简介</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-命令格式">3.2 命令格式</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#33-常用命令">3.3 常用命令</a><br/>
<a href="#四Snapshot">四、Snapshot</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-简介">4.1 简介</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-配置">4.2 配置</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#43-常用命令">4.3 常用命令</a><br/>
</nav>
## 一、前言
本文主要介绍 Hbase 常用的三种简单的容灾备份方案,即**CopyTable**、**Export**/**Import**、**Snapshot**。分别介绍如下:
## 二、CopyTable
### 2.1 简介
**CopyTable**可以将现有表的数据复制到新表中,具有以下特点:
- 支持时间区间 、row 区间 、改变表名称 、改变列族名称 、以及是否 Copy 已被删除的数据等功能;
- 执行命令前,需先创建与原表结构相同的新表;
- `CopyTable` 的操作是基于 HBase Client API 进行的,即采用 `scan` 进行查询, 采用 `put` 进行写入。
### 2.2 命令格式
```shell
Usage: CopyTable [general options] [--starttime=X] [--endtime=Y] [--new.name=NEW] [--peer.adr=ADR] <tablename>
```
### 2.3 常用命令
1. 同集群下 CopyTable
```shell
hbase org.apache.hadoop.hbase.mapreduce.CopyTable --new.name=tableCopy tableOrig
```
2. 不同集群下 CopyTable
```shell
# 两表名称相同的情况
hbase org.apache.hadoop.hbase.mapreduce.CopyTable \
--peer.adr=dstClusterZK:2181:/hbase tableOrig
# 也可以指新的表名
hbase org.apache.hadoop.hbase.mapreduce.CopyTable \
--peer.adr=dstClusterZK:2181:/hbase \
--new.name=tableCopy tableOrig
```
3. 下面是一个官方给的比较完整的例子,指定开始和结束时间,集群地址,以及只复制指定的列族:
```shell
hbase org.apache.hadoop.hbase.mapreduce.CopyTable \
--starttime=1265875194289 \
--endtime=1265878794289 \
--peer.adr=server1,server2,server3:2181:/hbase \
--families=myOldCf:myNewCf,cf2,cf3 TestTable
```
### 2.4 更多参数
可以通过 `--help` 查看更多支持的参数
```shell
# hbase org.apache.hadoop.hbase.mapreduce.CopyTable --help
```
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-copy-table.png"/> </div>
## 三、Export/Import
### 3.1 简介
- `Export` 支持导出数据到 HDFS, `Import` 支持从 HDFS 导入数据。`Export` 还支持指定导出数据的开始时间和结束时间,因此可以用于增量备份。
- `Export` 导出与 `CopyTable` 一样,依赖 HBase`scan` 操作
### 3.2 命令格式
```shell
# Export
hbase org.apache.hadoop.hbase.mapreduce.Export <tablename> <outputdir> [<versions> [<starttime> [<endtime>]]]
# Inport
hbase org.apache.hadoop.hbase.mapreduce.Import <tablename> <inputdir>
```
+ 导出的 `outputdir` 目录可以不用预先创建,程序会自动创建。导出完成后,导出文件的所有权将由执行导出命令的用户所拥有。
+ 默认情况下,仅导出给定 `Cell` 的最新版本,而不管历史版本。要导出多个版本,需要将 `<versions>` 参数替换为所需的版本数。
### 3.3 常用命令
1. 导出命令
```shell
hbase org.apache.hadoop.hbase.mapreduce.Export tableName hdfs 路径/tableName.db
```
2. 导入命令
```
hbase org.apache.hadoop.hbase.mapreduce.Import tableName hdfs 路径/tableName.db
```
## 四、Snapshot
### 4.1 简介
HBase 的快照 (Snapshot) 功能允许您获取表的副本 (包括内容和元数据),并且性能开销很小。因为快照存储的仅仅是表的元数据和 HFiles 的信息。快照的 `clone` 操作会从该快照创建新表,快照的 `restore` 操作会将表的内容还原到快照节点。`clone``restore` 操作不需要复制任何数据,因为底层 HFiles(包含 HBase 表数据的文件) 不会被修改,修改的只是表的元数据信息。
### 4.2 配置
HBase 快照功能默认没有开启,如果要开启快照,需要在 `hbase-site.xml` 文件中添加如下配置项:
```xml
<property>
<name>hbase.snapshot.enabled</name>
<value>true</value>
</property>
```
### 4.3 常用命令
快照的所有命令都需要在 Hbase Shell 交互式命令行中执行。
#### 1. Take a Snapshot
```shell
# 拍摄快照
hbase> snapshot '表名', '快照名'
```
默认情况下拍摄快照之前会在内存中执行数据刷新。以保证内存中的数据包含在快照中。但是如果你不希望包含内存中的数据,则可以使用 `SKIP_FLUSH` 选项禁止刷新。
```shell
# 禁止内存刷新
hbase> snapshot '表名', '快照名', {SKIP_FLUSH => true}
```
#### 2. Listing Snapshots
```shell
# 获取快照列表
hbase> list_snapshots
```
#### 3. Deleting Snapshots
```shell
# 删除快照
hbase> delete_snapshot '快照名'
```
#### 4. Clone a table from snapshot
```shell
# 从现有的快照创建一张新表
hbase> clone_snapshot '快照名', '新表名'
```
#### 5. Restore a snapshot
将表恢复到快照节点,恢复操作需要先禁用表
```shell
hbase> disable '表名'
hbase> restore_snapshot '快照名'
```
这里需要注意的是:是如果 HBase 配置了基于 Replication 的主从复制,由于 Replication 在日志级别工作,而快照在文件系统级别工作,因此在还原之后,会出现副本与主服务器处于不同的状态的情况。这时候可以先停止同步,所有服务器还原到一致的数据点后再重新建立同步。
## 参考资料
1. [Online Apache HBase Backups with CopyTable](https://blog.cloudera.com/blog/2012/06/online-hbase-backups-with-copytable-2/)
2. [Apache HBase ™ Reference Guide](http://hbase.apache.org/book.htm)

View File

@ -23,9 +23,9 @@
## 一、Phoenix简介
`Phoenix`HBase的开源SQL中间层它允许你使用标准JDBC的方式来操作HBase上的数据。在`Phoenix`之前如果你要访问HBase只能调用它的Java API但相比于使用一行SQL就能实现数据查询HBaseAPI还是过于复杂。`Phoenix`的理念是`we put sql SQL back in NOSQL`即你可以使用标准的SQL就能完成对HBase上数据的操作。同时这也意味着你可以通过集成`Spring Data JPA``Mybatis`等常用的持久层框架来操作HBase。
`Phoenix`HBase 的开源 SQL 中间层,它允许你使用标准 JDBC 的方式来操作 HBase 上的数据。在 `Phoenix` 之前,如果你要访问 HBase只能调用它的 Java API但相比于使用一行 SQL 就能实现数据查询HBaseAPI 还是过于复杂。`Phoenix` 的理念是 `we put sql SQL back in NOSQL`,即你可以使用标准的 SQL 就能完成对 HBase 上数据的操作。同时这也意味着你可以通过集成 `Spring Data JPA``Mybatis` 等常用的持久层框架来操作 HBase。
其次`Phoenix`的性能表现也非常优异,`Phoenix`查询引擎会将SQL查询转换为一个或多个HBase Scan通过并行执行来生成标准的JDBC结果集。它通过直接使用HBase API以及协处理器和自定义过滤器可以为小型数据查询提供毫秒级的性能为千万行数据的查询提供秒级的性能。同时Phoenix还拥有二级索引等HBase不具备的特性因为以上的优点所以`Phoenix`成为了HBase最优秀的SQL中间层。
其次 `Phoenix` 的性能表现也非常优异,`Phoenix` 查询引擎会将 SQL 查询转换为一个或多个 HBase Scan通过并行执行来生成标准的 JDBC 结果集。它通过直接使用 HBase API 以及协处理器和自定义过滤器,可以为小型数据查询提供毫秒级的性能,为千万行数据的查询提供秒级的性能。同时 Phoenix 还拥有二级索引等 HBase 不具备的特性,因为以上的优点,所以 `Phoenix` 成为了 HBase 最优秀的 SQL 中间层。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/Phoenix-hadoop.png"/> </div>
@ -43,7 +43,7 @@
### 2.1 下载并解压
官方针对Apache版本和CDH版本的HBase均提供了安装包按需下载即可。官方下载地址: http://phoenix.apache.org/download.html
官方针对 Apache 版本和 CDH 版本的 HBase 均提供了安装包,按需下载即可。官方下载地址: http://phoenix.apache.org/download.html
```shell
# 下载
@ -54,9 +54,9 @@ tar tar apache-phoenix-4.14.0-cdh5.14.2-bin.tar.gz
### 2.2 拷贝Jar包
按照官方文档的说明,需要将`phoenix server jar`添加到所有`Region Servers`的安装目录的`lib`目录下。
按照官方文档的说明,需要将 `phoenix server jar` 添加到所有 `Region Servers` 的安装目录的 `lib` 目录下。
这里由于我搭建的是HBase伪集群所以只需要拷贝到当前机器的HBaselib目录下。如果是真实集群则使用scp命令分发到所有`Region Servers`机器上。
这里由于我搭建的是 HBase 伪集群,所以只需要拷贝到当前机器的 HBaselib 目录下。如果是真实集群,则使用 scp 命令分发到所有 `Region Servers` 机器上。
```shell
cp /usr/app/apache-phoenix-4.14.0-cdh5.14.2-bin/phoenix-4.14.0-cdh5.14.2-server.jar /usr/app/hbase-1.2.0-cdh5.15.2/lib
@ -73,10 +73,10 @@ start-hbase.sh
### 2.4 启动Phoenix
在Phoenix解压目录下的`bin`目录下执行如下命令需要指定Zookeeper的地址
Phoenix 解压目录下的 `bin` 目录下执行如下命令,需要指定 Zookeeper 的地址:
+ 如果HBase采用Standalone模式或者伪集群模式搭建则默认采用内置的 Zookeeper服务端口为2181
+ 如果是HBase是集群模式并采用外置的Zookeeper集群则按照自己的实际情况进行指定。
+ 如果 HBase 采用 Standalone 模式或者伪集群模式搭建,则默认采用内置的 Zookeeper 服务,端口为 2181
+ 如果是 HBase 是集群模式并采用外置的 Zookeeper 集群,则按照自己的实际情况进行指定。
```shell
# ./sqlline.py hadoop001:2181
@ -84,7 +84,7 @@ start-hbase.sh
### 2.5 启动结果
启动后则进入了Phoenix交互式SQL命令行可以使用`!table``!tables`查看当前所有表的信息
启动后则进入了 Phoenix 交互式 SQL 命令行,可以使用 `!table``!tables` 查看当前所有表的信息
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/phoenix-shell.png"/> </div>
@ -104,13 +104,13 @@ CREATE TABLE IF NOT EXISTS us_population (
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/Phoenix-create-table.png"/> </div>
新建的表会按照特定的规则转换为HBase上的表关于表的信息可以通过Hbase Web UI 进行查看:
新建的表会按照特定的规则转换为 HBase 上的表,关于表的信息,可以通过 Hbase Web UI 进行查看:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-web-ui-phoenix.png"/> </div>
### 3.2 插入数据
Phoenix 中插入数据采用的是`UPSERT`而不是`INSERT`,因为Phoenix并没有更新操作插入相同主键的数据就视为更新所以`UPSERT`就相当于`UPDATE`+`INSERT`
Phoenix 中插入数据采用的是 `UPSERT` 而不是 `INSERT`,因为 Phoenix 并没有更新操作,插入相同主键的数据就视为更新,所以 `UPSERT` 就相当于 `UPDATE`+`INSERT`
```shell
UPSERT INTO us_population VALUES('NY','New York',8143197);
@ -165,27 +165,27 @@ ORDER BY sum(population) DESC;
### 3.7 扩展
从上面的操作中可以看出Phoenix支持大多数标准的SQL语法。关于Phoenix支持的语法、数据类型、函数、序列等详细信息因为涉及内容很多可以参考其官方文档官方文档上有详细的说明
从上面的操作中可以看出Phoenix 支持大多数标准的 SQL 语法。关于 Phoenix 支持的语法、数据类型、函数、序列等详细信息,因为涉及内容很多,可以参考其官方文档,官方文档上有详细的说明:
+ **语法(Grammar)** https://phoenix.apache.org/language/index.html
+ **语法 (Grammar)** https://phoenix.apache.org/language/index.html
+ **函数(Functions)** http://phoenix.apache.org/language/functions.html
+ **函数 (Functions)** http://phoenix.apache.org/language/functions.html
+ **数据类型(Datatypes)** http://phoenix.apache.org/language/datatypes.html
+ **数据类型 (Datatypes)** http://phoenix.apache.org/language/datatypes.html
+ **序列(Sequences)** :http://phoenix.apache.org/sequences.html
+ **序列 (Sequences)** :http://phoenix.apache.org/sequences.html
+ **联结查询(Joins)** http://phoenix.apache.org/joins.html
+ **联结查询 (Joins)** http://phoenix.apache.org/joins.html
## 四、Phoenix Java API
因为Phoenix遵循JDBC规范并提供了对应的数据库驱动`PhoenixDriver`这使得采用Java语言对其进行操作的时候就如同对其他关系型数据库一样下面给出基本的使用示例。
因为 Phoenix 遵循 JDBC 规范,并提供了对应的数据库驱动 `PhoenixDriver`,这使得采用 Java 语言对其进行操作的时候,就如同对其他关系型数据库一样,下面给出基本的使用示例。
### 4.1 引入Phoenix core JAR包
如果是maven项目直接在maven中央仓库找到对应的版本导入依赖即可
如果是 maven 项目,直接在 maven 中央仓库找到对应的版本,导入依赖即可:
```xml
<!-- https://mvnrepository.com/artifact/org.apache.phoenix/phoenix-core -->
@ -196,7 +196,7 @@ ORDER BY sum(population) DESC;
</dependency>
```
如果是普通项目则可以从Phoenix解压目录下找到对应的JAR包然后手动引入
如果是普通项目,则可以从 Phoenix 解压目录下找到对应的 JAR 包,然后手动引入:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/phoenix-core-jar.png"/> </div>
@ -217,8 +217,8 @@ public class PhoenixJavaApi {
Class.forName("org.apache.phoenix.jdbc.PhoenixDriver");
/*
* 指定数据库地址,格式为 jdbc:phoenix:Zookeeper地址
* 如果HBase采用Standalone模式或者伪集群模式搭建则HBase默认使用内置的Zookeeper默认端口为2181
* 指定数据库地址,格式为 jdbc:phoenix:Zookeeper 地址
* 如果 HBase 采用 Standalone 模式或者伪集群模式搭建,则 HBase 默认使用内置的 Zookeeper默认端口为 2181
*/
Connection connection = DriverManager.getConnection("jdbc:phoenix:192.168.200.226:2181");
@ -243,7 +243,7 @@ public class PhoenixJavaApi {
实际的开发中我们通常都是采用第三方框架来操作数据库,如`mybatis``Hibernate``Spring Data`等。关于Phoenix与这些框架的整合步骤参见下一篇文章[Spring/Spring Boot + Mybatis + Phoenix](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Spring+Mybtais+Phoenix整合.md)
实际的开发中我们通常都是采用第三方框架来操作数据库,如 `mybatis``Hibernate``Spring Data` 等。关于 Phoenix 与这些框架的整合步骤参见下一篇文章:[Spring/Spring Boot + Mybatis + Phoenix](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Spring+Mybtais+Phoenix 整合.md)
# 参考资料

View File

@ -1,88 +1,88 @@
# HBase简介
<nav>
<a href="#一Hadoop的局限">一、Hadoop的局限</a><br/>
<a href="#二HBase简介">二、HBase简介</a><br/>
<a href="#三HBase-Table">三、HBase Table</a><br/>
<a href="#四Phoenix">四、Phoenix</a><br/>
</nav>
## 一、Hadoop的局限
HBase是一个构建在Hadoop文件系统之上的面向列的数据库管理系统。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase.jpg"/> </div>
要想明白为什么产生HBase就需要先了解一下Hadoop存在的限制Hadoop可以通过HDFS来存储结构化、半结构甚至非结构化的数据它是传统数据库的补充是海量数据存储的最佳方法它针对大文件的存储批量访问和流式访问都做了优化同时也通过多副本解决了容灾问题。
但是Hadoop的缺陷在于它只能执行批处理并且只能以顺序方式访问数据这意味着即使是最简单的工作也必须搜索整个数据集无法实现对数据的随机访问。实现数据的随机访问是传统的关系型数据库所擅长的但它们却不能用于海量数据的存储。在这种情况下必须有一种新的方案来解决海量数据存储和随机访问的问题HBase就是其中之一(HBaseCassandracouchDBDynamoMongoDB都能存储海量数据并支持随机访问)。
> 注:数据结构分类:
>
> - 结构化数据:即以关系型数据库表形式管理的数据;
> - 半结构化数据非关系模型的有基本固定结构模式的数据例如日志文件、XML文档、JSON文档、Email等
> - 非结构化数据没有固定模式的数据如WORD、PDF、PPT、EXL各种格式的图片、视频等。
## 二、HBase简介
HBase是一个构建在Hadoop文件系统之上的面向列的数据库管理系统。
HBase是一种类似于`Googles Big Table`的数据模型它是Hadoop生态系统的一部分它将数据存储在HDFS上客户端可以通过HBase实现对HDFS上数据的随机访问。它具有以下特性
+ 不支持复杂的事务,只支持行级事务,即单行数据的读写都是原子性的;
+ 由于是采用HDFS作为底层存储所以和HDFS一样支持结构化、半结构化和非结构化的存储
+ 支持通过增加机器进行横向扩展;
+ 支持数据分片;
+ 支持RegionServers之间的自动故障转移
+ 易于使用的Java客户端 API
+ 支持BlockCache和布隆过滤器
+ 过滤器支持谓词下推。
## 三、HBase Table
HBase是一个面向``的数据库管理系统这里更为确切的而说HBase是一个面向`列族`的数据库管理系统。表 schema 仅定义列族表具有多个列族每个列族可以包含任意数量的列列由多个单元格cell )组成,单元格可以存储多个版本的数据,多个版本数据以时间戳进行区分。
下图为HBase中一张表的
+ RowKey为行的唯一标识所有行按照RowKey的字典序进行排序
+ 该表具有两个列族分别是personaloffice;
+ 其中列族personal拥有name、city、phone三个列列族office拥有tel、addres两个列。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/HBase_table-iteblog.png"/> </div>
> *图片引用自 : HBase是列式存储数据库吗* *https://www.iteblog.com/archives/2498.html*
Hbase的表具有以下特点
- 容量大:一个表可以有数十亿行,上百万列;
- 面向列数据是按照列存储每一列都单独存放数据即索引在查询时可以只访问指定列的数据有效地降低了系统的I/O负担
- 稀疏性:空 (null) 列并不占用存储空间,表可以设计的非常稀疏
- 数据多版本:每个单元中的数据可以有多个版本,按照时间戳排序,新的数据在最上面;
- 存储类型:所有数据的底层存储格式都是字节数组(byte[])。
## 四、Phoenix
`Phoenix`HBase的开源SQL中间层它允许你使用标准JDBC的方式来操作HBase上的数据。在`Phoenix`之前如果你要访问HBase只能调用它的Java API但相比于使用一行SQL就能实现数据查询HBaseAPI还是过于复杂。`Phoenix`的理念是`we put sql SQL back in NOSQL`即你可以使用标准的SQL就能完成对HBase上数据的操作。同时这也意味着你可以通过集成`Spring Data JPA``Mybatis`等常用的持久层框架来操作HBase。
其次`Phoenix`的性能表现也非常优异,`Phoenix`查询引擎会将SQL查询转换为一个或多个HBase Scan通过并行执行来生成标准的JDBC结果集。它通过直接使用HBase API以及协处理器和自定义过滤器可以为小型数据查询提供毫秒级的性能为千万行数据的查询提供秒级的性能。同时Phoenix还拥有二级索引等HBase不具备的特性因为以上的优点所以`Phoenix`成为了HBase最优秀的SQL中间层。
## 参考资料
1. [HBase - Overview](https://www.tutorialspoint.com/hbase/hbase_overview.htm)
# HBase简介
<nav>
<a href="#一Hadoop的局限">一、Hadoop的局限</a><br/>
<a href="#二HBase简介">二、HBase简介</a><br/>
<a href="#三HBase-Table">三、HBase Table</a><br/>
<a href="#四Phoenix">四、Phoenix</a><br/>
</nav>
## 一、Hadoop的局限
HBase 是一个构建在 Hadoop 文件系统之上的面向列的数据库管理系统。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase.jpg"/> </div>
要想明白为什么产生 HBase就需要先了解一下 Hadoop 存在的限制Hadoop 可以通过 HDFS 来存储结构化、半结构甚至非结构化的数据,它是传统数据库的补充,是海量数据存储的最佳方法,它针对大文件的存储,批量访问和流式访问都做了优化,同时也通过多副本解决了容灾问题。
但是 Hadoop 的缺陷在于它只能执行批处理并且只能以顺序方式访问数据这意味着即使是最简单的工作也必须搜索整个数据集无法实现对数据的随机访问。实现数据的随机访问是传统的关系型数据库所擅长的但它们却不能用于海量数据的存储。在这种情况下必须有一种新的方案来解决海量数据存储和随机访问的问题HBase 就是其中之一 (HBaseCassandracouchDBDynamoMongoDB 都能存储海量数据并支持随机访问)。
> 注:数据结构分类:
>
> - 结构化数据:即以关系型数据库表形式管理的数据;
> - 半结构化数据非关系模型的有基本固定结构模式的数据例如日志文件、XML 文档、JSON 文档、Email 等;
> - 非结构化数据:没有固定模式的数据,如 WORD、PDF、PPT、EXL各种格式的图片、视频等。
## 二、HBase简介
HBase 是一个构建在 Hadoop 文件系统之上的面向列的数据库管理系统。
HBase 是一种类似于 `Googles Big Table` 的数据模型,它是 Hadoop 生态系统的一部分,它将数据存储在 HDFS 上,客户端可以通过 HBase 实现对 HDFS 上数据的随机访问。它具有以下特性:
+ 不支持复杂的事务,只支持行级事务,即单行数据的读写都是原子性的;
+ 由于是采用 HDFS 作为底层存储,所以和 HDFS 一样,支持结构化、半结构化和非结构化的存储;
+ 支持通过增加机器进行横向扩展;
+ 支持数据分片;
+ 支持 RegionServers 之间的自动故障转移;
+ 易于使用的 Java 客户端 API
+ 支持 BlockCache 和布隆过滤器;
+ 过滤器支持谓词下推。
## 三、HBase Table
HBase 是一个面向 `` 的数据库管理系统这里更为确切的而说HBase 是一个面向 ` 列族 ` 的数据库管理系统。表 schema 仅定义列族表具有多个列族每个列族可以包含任意数量的列列由多个单元格cell )组成,单元格可以存储多个版本的数据,多个版本数据以时间戳进行区分。
下图为 HBase 中一张表的:
+ RowKey 为行的唯一标识,所有行按照 RowKey 的字典序进行排序;
+ 该表具有两个列族,分别是 personaloffice;
+ 其中列族 personal 拥有 name、city、phone 三个列,列族 office 拥有 tel、addres 两个列。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/HBase_table-iteblog.png"/> </div>
> *图片引用自 : HBase 是列式存储数据库吗* *https://www.iteblog.com/archives/2498.html*
Hbase 的表具有以下特点:
- 容量大:一个表可以有数十亿行,上百万列;
- 面向列:数据是按照列存储,每一列都单独存放,数据即索引,在查询时可以只访问指定列的数据,有效地降低了系统的 I/O 负担;
- 稀疏性:空 (null) 列并不占用存储空间,表可以设计的非常稀疏
- 数据多版本:每个单元中的数据可以有多个版本,按照时间戳排序,新的数据在最上面;
- 存储类型:所有数据的底层存储格式都是字节数组 (byte[])。
## 四、Phoenix
`Phoenix`HBase 的开源 SQL 中间层,它允许你使用标准 JDBC 的方式来操作 HBase 上的数据。在 `Phoenix` 之前,如果你要访问 HBase只能调用它的 Java API但相比于使用一行 SQL 就能实现数据查询HBaseAPI 还是过于复杂。`Phoenix` 的理念是 `we put sql SQL back in NOSQL`,即你可以使用标准的 SQL 就能完成对 HBase 上数据的操作。同时这也意味着你可以通过集成 `Spring Data JPA``Mybatis` 等常用的持久层框架来操作 HBase。
其次 `Phoenix` 的性能表现也非常优异,`Phoenix` 查询引擎会将 SQL 查询转换为一个或多个 HBase Scan通过并行执行来生成标准的 JDBC 结果集。它通过直接使用 HBase API 以及协处理器和自定义过滤器,可以为小型数据查询提供毫秒级的性能,为千万行数据的查询提供秒级的性能。同时 Phoenix 还拥有二级索引等 HBase 不具备的特性,因为以上的优点,所以 `Phoenix` 成为了 HBase 最优秀的 SQL 中间层。
## 参考资料
1. [HBase - Overview](https://www.tutorialspoint.com/hbase/hbase_overview.htm)

View File

@ -21,23 +21,23 @@
## 一、基本概念
一个典型的Hbase Table 表如下:
一个典型的 Hbase Table 表如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-webtable.png"/> </div>
### 1.1 Row Key (行键)
`Row Key`是用来检索记录的主键。想要访问HBase Table中的数据只有以下三种方式
`Row Key` 是用来检索记录的主键。想要访问 HBase Table 中的数据,只有以下三种方式:
+ 通过指定的`Row Key`进行访问;
+ 通过指定的 `Row Key` 进行访问;
+ 通过Row Keyrange进行访问即访问指定范围内的行
+ 通过 Row Keyrange 进行访问,即访问指定范围内的行;
+ 进行全表扫描。
`Row Key`可以是任意字符串,存储时数据按照`Row Key`的字典序进行排序。这里需要注意以下两点:
`Row Key` 可以是任意字符串,存储时数据按照 `Row 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作左填充。
+ 因为字典序对 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 作左填充。
+ 行的一次读写操作时原子性的 (不论一次读写多少列)。
@ -45,31 +45,31 @@
### 1.2 Column Family列族
HBase表中的每个列都归属于某个列族。列族是表的Schema的一部分所以列族需要在创建表时进行定义。列族的所有列都以列族名作为前缀例如`courses:history``courses:math`都属于`courses`这个列族。
HBase 表中的每个列,都归属于某个列族。列族是表的 Schema 的一部分,所以列族需要在创建表时进行定义。列族的所有列都以列族名作为前缀,例如 `courses:history``courses:math` 都属于 `courses` 这个列族。
### 1.3 Column Qualifier (列限定符)
列限定符,你可以理解为是具体的列名,例如`courses:history``courses:math`都属于`courses`这个列族,它们的列限定符分别是`history``math`。需要注意的是列限定符不是表Schema的一部分你可以在插入数据的过程中动态创建列。
列限定符,你可以理解为是具体的列名,例如 `courses:history``courses:math` 都属于 `courses` 这个列族,它们的列限定符分别是 `history``math`。需要注意的是列限定符不是表 Schema 的一部分,你可以在插入数据的过程中动态创建列。
### 1.4 Column(列)
HBase中的列由列族和列限定符组成它们由`:`(冒号)进行分隔,即一个完整的列名应该表述为`列族名 :列限定符`
HBase 中的列由列族和列限定符组成,它们由 `:`(冒号) 进行分隔,即一个完整的列名应该表述为 ` 列族名 :列限定符 `
### 1.5 Cell
`Cell`是行列族和列限定符的组合并包含值和时间戳。你可以等价理解为关系型数据库中由指定行和指定列确定的一个单元格但不同的是HBase中的一个单元格是由多个版本的数据组成的每个版本的数据用时间戳进行区分。
`Cell` 是行,列族和列限定符的组合,并包含值和时间戳。你可以等价理解为关系型数据库中由指定行和指定列确定的一个单元格,但不同的是 HBase 中的一个单元格是由多个版本的数据组成的,每个版本的数据用时间戳进行区分。
### 1.6 Timestamp(时间戳)
HBase 中通过`row key``column`确定的为一个存储单元称为`Cell`。每个`Cell`都保存着同一份数据的多个版本。版本通过时间戳来索引,时间戳的类型是 64位整型时间戳可以由HBase在数据写入时自动赋值也可以由客户显式指定。每个`Cell`中,不同版本的数据按照时间戳倒序排列,即最新的数据排在最前面。
HBase 中通过 `row key``column` 确定的为一个存储单元称为 `Cell`。每个 `Cell` 都保存着同一份数据的多个版本。版本通过时间戳来索引,时间戳的类型是 64 位整型,时间戳可以由 HBase 在数据写入时自动赋值,也可以由客户显式指定。每个 `Cell` 中,不同版本的数据按照时间戳倒序排列,即最新的数据排在最前面。
@ -77,32 +77,32 @@ HBase 中通过`row key`和`column`确定的为一个存储单元称为`Cell`。
### 2.1 Regions
HBase Table中的所有行按照`Row Key`的字典序排列。HBase Tables 通过行键的范围(row key range)被水平切分成多个`Region`, 一个`Region`包含了在start key 和 end key之间的所有行。
HBase Table 中的所有行按照 `Row Key` 的字典序排列。HBase Tables 通过行键的范围 (row key range) 被水平切分成多个 `Region`, 一个 `Region` 包含了在 start key 和 end key 之间的所有行。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/HBaseArchitecture-Blog-Fig2.png"/> </div>
每个表一开始只有一个`Region`,随着数据不断增加,`Region`会不断增大,当增大到一个阀值的时候,`Region`就会等分为两个新的`Region`。当Table中的行不断增多就会有越来越多的`Region`
每个表一开始只有一个 `Region`,随着数据不断增加,`Region` 会不断增大,当增大到一个阀值的时候,`Region` 就会等分为两个新的 `Region`。当 Table 中的行不断增多,就会有越来越多的 `Region`
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-region-splite.png"/> </div>
`Region`HBase中**分布式存储和负载均衡的最小单元**。这意味着不同的`Region`可以分布在不同的`Region Server`上。但一个`Region`是不会拆分到多个Server上的。
`Region`HBase 中**分布式存储和负载均衡的最小单元**。这意味着不同的 `Region` 可以分布在不同的 `Region Server` 上。但一个 `Region` 是不会拆分到多个 Server 上的。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-region-dis.png"/> </div>
### 2.2 Region Server
`Region Server`运行在HDFSDataNode上。它具有以下组件
`Region Server` 运行在 HDFSDataNode 上。它具有以下组件:
- **WAL(Write Ahead Log预写日志)**:用于存储尚未进持久化存储的数据记录,以便在发生故障时进行恢复。
- **BlockCache**:读缓存。它将频繁读取的数据存储在内存中,如果存储不足,它将按照`最近最少使用原则`清除多余的数据。
- **MemStore**写缓存。它存储尚未写入磁盘的新数据并会在数据写入磁盘之前对其进行排序。每个Region上的每个列族都有一个MemStore。
- **HFile** 将行数据按照Key\Values的形式存储在文件系统上。
- **BlockCache**:读缓存。它将频繁读取的数据存储在内存中,如果存储不足,它将按照 ` 最近最少使用原则 ` 清除多余的数据。
- **MemStore**:写缓存。它存储尚未写入磁盘的新数据,并会在数据写入磁盘之前对其进行排序。每个 Region 上的每个列族都有一个 MemStore。
- **HFile** :将行数据按照 Key\Values 的形式存储在文件系统上。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-Region-Server.png"/> </div>
Region Server存取一个子表时会创建一个Region对象然后对表的每个列族创建一个`Store`实例,每个`Store`会有 0 个或多个`StoreFile`与之对应,每个`StoreFile`则对应一个`HFile`HFile 就是实际存储在HDFS上的文件。
Region Server 存取一个子表时,会创建一个 Region 对象,然后对表的每个列族创建一个 `Store` 实例,每个 `Store` 会有 0 个或多个 `StoreFile` 与之对应,每个 `StoreFile` 则对应一个 `HFile`HFile 就是实际存储在 HDFS 上的文件。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-hadoop.png"/> </div>
@ -112,47 +112,47 @@ Region Server存取一个子表时会创建一个Region对象然后对表
### 3.1 系统架构
HBase系统遵循Master/Salve架构由三种不同类型的组件组成
HBase 系统遵循 Master/Salve 架构,由三种不同类型的组件组成:
**Zookeeper**
1. 保证任何时候集群中只有一个Master
1. 保证任何时候,集群中只有一个 Master
2. 存贮所有Region的寻址入口
2. 存贮所有 Region 的寻址入口;
3. 实时监控Region Server的状态将Region Server的上线和下线信息实时通知给Master
3. 实时监控 Region Server 的状态,将 Region Server 的上线和下线信息实时通知给 Master
4. 存储HBaseSchema包括有哪些Table每个Table有哪些Column Family等信息。
4. 存储 HBaseSchema包括有哪些 Table每个 Table 有哪些 Column Family 等信息。
**Master**
1. 为Region Server分配Region
1. Region Server 分配 Region
2. 负责Region Server的负载均衡
2. 负责 Region Server 的负载均衡
3. 发现失效的Region Server并重新分配其上的Region
3. 发现失效的 Region Server 并重新分配其上的 Region
4. GFS上的垃圾文件回收
4. GFS 上的垃圾文件回收;
5. 处理Schema的更新请求。
5. 处理 Schema 的更新请求。
**Region Server**
1. Region Server负责维护Master分配给它的Region 并处理发送到Region上的IO请求
1. Region Server 负责维护 Master 分配给它的 Region ,并处理发送到 Region 上的 IO 请求;
2. Region Server负责切分在运行过程中变得过大的Region。
2. Region Server 负责切分在运行过程中变得过大的 Region。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/HBaseArchitecture-Blog-Fig1.png"/> </div>
### 3.2 组件间的协作
HBase使用ZooKeeper作为分布式协调服务来维护集群中的服务器状态。 Zookeeper负责维护可用服务列表并提供服务故障通知等服务
HBase 使用 ZooKeeper 作为分布式协调服务来维护集群中的服务器状态。 Zookeeper 负责维护可用服务列表,并提供服务故障通知等服务:
+ 每个Region Server都会在ZooKeeper上创建一个临时节点Master通过ZookeeperWatcher机制对节点进行监控从而可以发现新加入的Region Server或故障退出的Region Server
+ 每个 Region Server 都会在 ZooKeeper 上创建一个临时节点Master 通过 ZookeeperWatcher 机制对节点进行监控,从而可以发现新加入的 Region Server 或故障退出的 Region Server
+ 所有Masters会竞争性地在Zookeeper上创建同一个临时节点由于Zookeeper只能有一个同名节点所以必然只有一个Master能够创建成功此时该Master就是主Master主Master会定期向Zookeeper发送心跳。备用Masters则通过Watcher机制对主HMaster所在节点进行监听
+ 所有 Masters 会竞争性地在 Zookeeper 上创建同一个临时节点,由于 Zookeeper 只能有一个同名节点,所以必然只有一个 Master 能够创建成功,此时该 Master 就是主 Master Master 会定期向 Zookeeper 发送心跳。备用 Masters 则通过 Watcher 机制对主 HMaster 所在节点进行监听;
+ 如果主Master未能定时发送心跳则其持有的Zookeeper会话会过期相应的临时节点也会被删除这会触发定义在该节点上的Watcher事件使得备用的Master Servers得到通知。所有备用的Master Servers在接到通知后会再次去竞争性地创建临时节点完成主Master的选举。
+ 如果主 Master 未能定时发送心跳,则其持有的 Zookeeper 会话会过期,相应的临时节点也会被删除,这会触发定义在该节点上的 Watcher 事件,使得备用的 Master Servers 得到通知。所有备用的 Master Servers 在接到通知后,会再次去竞争性地创建临时节点,完成主 Master 的选举。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/HBaseArchitecture-Blog-Fig5.png"/> </div>
@ -162,19 +162,19 @@ HBase系统遵循Master/Salve架构由三种不同类型的组件组成
### 4.1 写入数据的流程
1. ClientRegion Server提交写请求
1. ClientRegion Server 提交写请求;
2. Region Server找到目标Region
2. Region Server 找到目标 Region
3. Region检查数据是否与Schema一致
3. Region 检查数据是否与 Schema 一致;
4. 如果客户端没有指定版本,则获取当前系统时间作为数据版本;
5. 将更新写入WAL Log
5. 将更新写入 WAL Log
6. 将更新写入Memstore
6. 将更新写入 Memstore
7. 判断Memstore存储是否已满如果存储已满则需要flushStore Hfile文件。
7. 判断 Memstore 存储是否已满,如果存储已满则需要 flushStore Hfile 文件。
> 更为详细写入流程可以参考:[HBase 数据写入流程解析](http://hbasefly.com/2016/03/23/hbase_writer/)
@ -182,25 +182,25 @@ HBase系统遵循Master/Salve架构由三种不同类型的组件组成
### 4.2 读取数据的流程
以下是客户端首次读写HBase上数据的流程
以下是客户端首次读写 HBase 上数据的流程:
1. 客户端从Zookeeper获取`META`表所在的Region Server
1. 客户端从 Zookeeper 获取 `META` 表所在的 Region Server
2. 客户端访问`META`表所在的Region Server`META`表中查询到访问行键所在的Region Server之后客户端将缓存这些信息以及`META`表的位置;
2. 客户端访问 `META` 表所在的 Region Server `META` 表中查询到访问行键所在的 Region Server之后客户端将缓存这些信息以及 `META` 表的位置;
3. 客户端从行键所在的Region Server上获取数据。
3. 客户端从行键所在的 Region Server 上获取数据。
如果再次读取客户端将从缓存中获取行键所在的Region Server。这样客户端就不需要再次查询`META`除非Region移动导致缓存失效这样的话则将会重新查询并更新缓存。
如果再次读取,客户端将从缓存中获取行键所在的 Region Server。这样客户端就不需要再次查询 `META` 表,除非 Region 移动导致缓存失效,这样的话,则将会重新查询并更新缓存。
注:`META`表是HBase中一张特殊的表它保存了所有Region的位置信息META表自己的位置信息则存储在ZooKeeper上。
注:`META` 表是 HBase 中一张特殊的表,它保存了所有 Region 的位置信息META 表自己的位置信息则存储在 ZooKeeper 上。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/HBaseArchitecture-Blog-Fig7.png"/> </div>
> 更为详细读取数据流程参考:
>
> [HBase原理数据读取流程解析](http://hbasefly.com/2016/12/21/hbase-getorscan/)
> [HBase 原理-数据读取流程解析](http://hbasefly.com/2016/12/21/hbase-getorscan/)
>
> [HBase原理迟到的数据读取流程部分细节](http://hbasefly.com/2017/06/11/hbase-scan-2/)
> [HBase 原理-迟到的‘数据读取流程部分细节](http://hbasefly.com/2017/06/11/hbase-scan-2/)
@ -212,7 +212,7 @@ HBase系统遵循Master/Salve架构由三种不同类型的组件组成
+ [HBase Architectural Components](https://mapr.com/blog/in-depth-look-hbase-architecture/#.VdMxvWSqqko)
+ [Hbase系统架构及数据结构](https://www.open-open.com/lib/view/open1346821084631.html)
+ [Hbase 系统架构及数据结构](https://www.open-open.com/lib/view/open1346821084631.html)
官方文档:

View File

@ -28,7 +28,7 @@
## 一、HBase过滤器简介
Hbase提供了种类丰富的过滤器filter来提高数据处理的效率用户可以通过内置或自定义的过滤器来对数据进行过滤所有的过滤器都在服务端生效即谓词下推predicate push down。这样可以保证过滤掉的数据不会被传送到客户端从而减轻网络传输和客户端处理的压力。
Hbase 提供了种类丰富的过滤器filter来提高数据处理的效率用户可以通过内置或自定义的过滤器来对数据进行过滤所有的过滤器都在服务端生效即谓词下推predicate push down。这样可以保证过滤掉的数据不会被传送到客户端从而减轻网络传输和客户端处理的压力。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-fliter.png"/> </div>
@ -38,14 +38,14 @@ Hbase提供了种类丰富的过滤器filter来提高数据处理的效率
### 2.1 Filter接口和FilterBase抽象类
Filter接口中定义了过滤器的基本方法FilterBase抽象类实现了Filter接口。所有内置的过滤器则直接或者间接继承自FilterBase抽象类。用户只需要将定义好的过滤器通过`setFilter`方法传递给`Scan``put`的实例即可。
Filter 接口中定义了过滤器的基本方法FilterBase 抽象类实现了 Filter 接口。所有内置的过滤器则直接或者间接继承自 FilterBase 抽象类。用户只需要将定义好的过滤器通过 `setFilter` 方法传递给 `Scan``put` 的实例即可。
```java
setFilter(Filter filter)
```
```java
// Scan 中定义的setFilter
// Scan 中定义的 setFilter
@Override
public Scan setFilter(Filter filter) {
super.setFilter(filter);
@ -54,7 +54,7 @@ setFilter(Filter filter)
```
```java
// Get 中定义的setFilter
// Get 中定义的 setFilter
@Override
public Get setFilter(Filter filter) {
super.setFilter(filter);
@ -62,9 +62,9 @@ setFilter(Filter filter)
}
```
FilterBase的所有子类过滤器如下<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-filterbase-subclass.png"/> </div>
FilterBase 的所有子类过滤器如下:<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-filterbase-subclass.png"/> </div>
> 说明上图基于当前时间点2019.4最新的Hbase-2.1.4 ,下文所有说明均基于此版本。
> 说明上图基于当前时间点2019.4)最新的 Hbase-2.1.4 ,下文所有说明均基于此版本。
@ -76,7 +76,7 @@ HBase 内置过滤器可以分为三类:分别是比较过滤器,专用过
## 三、比较过滤器
所有比较过滤器均继承自`CompareFilter`。创建一个比较过滤器需要两个参数,分别是**比较运算符**和**比较器实例**。
所有比较过滤器均继承自 `CompareFilter`。创建一个比较过滤器需要两个参数,分别是**比较运算符**和**比较器实例**。
```java
public CompareFilter(final CompareOp compareOp,final ByteArrayComparable comparator) {
@ -95,7 +95,7 @@ HBase 内置过滤器可以分为三类:分别是比较过滤器,专用过
- GREATER (>)
- NO_OP (排除所有符合条件的值)
比较运算符均定义在枚举类`CompareOperator`
比较运算符均定义在枚举类 `CompareOperator`
```java
@InterfaceAudience.Public
@ -110,42 +110,42 @@ public enum CompareOperator {
}
```
> 注意:在 1.x 版本的HBase中比较运算符定义在`CompareFilter.CompareOp`枚举类中但在2.0之后这个类就被标识为 @deprecated 并会在3.0移除。所以2.0之后版本的HBase需要使用 `CompareOperator`这个枚举类。
> 注意:在 1.x 版本的 HBase 中,比较运算符定义在 `CompareFilter.CompareOp` 枚举类中,但在 2.0 之后这个类就被标识为 @deprecated ,并会在 3.0 移除。所以 2.0 之后版本的 HBase 需要使用 `CompareOperator` 这个枚举类。
>
### 3.2 比较器
所有比较器均继承自`ByteArrayComparable`抽象类,常用的有以下几种:
所有比较器均继承自 `ByteArrayComparable` 抽象类,常用的有以下几种:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-bytearraycomparable.png"/> </div>
- **BinaryComparator** : 使用`Bytes.compareTo(byte []byte [])`按字典序比较指定的字节数组。
- **BinaryComparator** : 使用 `Bytes.compareTo(byte []byte [])` 按字典序比较指定的字节数组。
- **BinaryPrefixComparator** : 按字典序与指定的字节数组进行比较,但只比较到这个字节数组的长度。
- **RegexStringComparator** : 使用给定的正则表达式与指定的字节数组进行比较。仅支持`EQUAL``NOT_EQUAL`操作。
- **SubStringComparator** : 测试给定的子字符串是否出现在指定的字节数组中,比较不区分大小写。仅支持`EQUAL``NOT_EQUAL`操作。
- **RegexStringComparator** : 使用给定的正则表达式与指定的字节数组进行比较。仅支持 `EQUAL``NOT_EQUAL` 操作。
- **SubStringComparator** : 测试给定的子字符串是否出现在指定的字节数组中,比较不区分大小写。仅支持 `EQUAL``NOT_EQUAL` 操作。
- **NullComparator** :判断给定的值是否为空。
- **BitComparator** :按位进行比较。
`BinaryPrefixComparator``BinaryComparator`的区别不是很好理解,这里举例说明一下:
`BinaryPrefixComparator``BinaryComparator` 的区别不是很好理解,这里举例说明一下:
在进行`EQUAL`的比较时,如果比较器传入的是`abcd`的字节数组,但是待比较数据是`abcdefgh`
在进行 `EQUAL` 的比较时,如果比较器传入的是 `abcd` 的字节数组,但是待比较数据是 `abcdefgh`
+ 如果使用的是`BinaryPrefixComparator`比较器,则比较以`abcd`字节数组的长度为准,即`efgh`不会参与比较,这时候认为`abcd``abcdefgh` 是满足`EQUAL`条件的;
+ 如果使用的是`BinaryComparator`比较器,则认为其是不相等的。
+ 如果使用的是 `BinaryPrefixComparator` 比较器,则比较以 `abcd` 字节数组的长度为准,即 `efgh` 不会参与比较,这时候认为 `abcd``abcdefgh` 是满足 `EQUAL` 条件的;
+ 如果使用的是 `BinaryComparator` 比较器,则认为其是不相等的。
### 3.3 比较过滤器种类
比较过滤器共有五个Hbase 1.x 版本和2.x 版本相同),见下图:
比较过滤器共有五个Hbase 1.x 版本和 2.x 版本相同),见下图:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-compareFilter.png"/> </div>
+ **RowFilter** :基于行键来过滤数据;
+ **FamilyFilterr** :基于列族来过滤数据;
+ **QualifierFilterr** :基于列限定符(列名)来过滤数据;
+ **ValueFilterr** :基于单元格(cell) 的值来过滤数据;
+ **ValueFilterr** :基于单元格 (cell) 的值来过滤数据;
+ **DependentColumnFilter** :指定一个参考列来过滤其他列的过滤器,过滤的原则是基于参考列的时间戳来进行筛选 。
前四种过滤器的使用方法相同,均只要传递比较运算符和运算器实例即可构建,然后通过`setFilter`方法传递给`scan`
前四种过滤器的使用方法相同,均只要传递比较运算符和运算器实例即可构建,然后通过 `setFilter` 方法传递给 `scan`
```java
Filter filter = new RowFilter(CompareOperator.LESS_OR_EQUAL,
@ -153,11 +153,11 @@ public enum CompareOperator {
scan.setFilter(filter);
```
`DependentColumnFilter`的使用稍微复杂一点,这里单独做下说明。
`DependentColumnFilter` 的使用稍微复杂一点,这里单独做下说明。
### 3.4 DependentColumnFilter
可以把`DependentColumnFilter`理解为**一个valueFilter和一个时间戳过滤器的组合**。`DependentColumnFilter`有三个带参构造器,这里选择一个参数最全的进行说明:
可以把 `DependentColumnFilter` 理解为**一个 valueFilter 和一个时间戳过滤器的组合**。`DependentColumnFilter` 有三个带参构造器,这里选择一个参数最全的进行说明:
```java
DependentColumnFilter(final byte [] family, final byte[] qualifier,
@ -167,7 +167,7 @@ DependentColumnFilter(final byte [] family, final byte[] qualifier,
+ **family** :列族
+ **qualifier** :列限定符(列名)
+ **dropDependentColumn** 决定参考列是否被包含在返回结果内为true时表示参考列被返回为false时表示被丢弃
+ **dropDependentColumn** :决定参考列是否被包含在返回结果内,为 true 时表示参考列被返回,为 false 时表示被丢弃
+ **op** :比较运算符
+ **valueComparator** :比较器
@ -182,24 +182,24 @@ DependentColumnFilter dependentColumnFilter = new DependentColumnFilter(
new BinaryPrefixComparator(Bytes.toBytes("xiaolan")));
```
+ 首先会去查找`student:name`中值以`xiaolan`开头的所有数据获得`参考数据集`这一步等同于valueFilter过滤器
+ 首先会去查找 `student:name` 中值以 `xiaolan` 开头的所有数据获得 ` 参考数据集 `,这一步等同于 valueFilter 过滤器;
+ 其次再用参考数据集中所有数据的时间戳去检索其他列,获得时间戳相同的其他列的数据作为`结果数据集`,这一步等同于时间戳过滤器;
+ 其次再用参考数据集中所有数据的时间戳去检索其他列,获得时间戳相同的其他列的数据作为 ` 结果数据集 `,这一步等同于时间戳过滤器;
+ 最后如果`dropDependentColumn`true则返回`参考数据集`+`结果数据集`若为false则抛弃参考数据集只返回`结果数据集`
+ 最后如果 `dropDependentColumn`true则返回 ` 参考数据集 `+` 结果数据集 `,若为 false则抛弃参考数据集只返回 ` 结果数据集 `
## 四、专用过滤器
专用过滤器通常直接继承自`FilterBase`,适用于范围更小的筛选规则。
专用过滤器通常直接继承自 `FilterBase`,适用于范围更小的筛选规则。
### 4.1 单列列值过滤器 (SingleColumnValueFilter)
基于某列(参考列)的值决定某行数据是否被过滤。其实例有以下方法:
+ **setFilterIfMissing(boolean filterIfMissing)** 默认值为false即如果该行数据不包含参考列其依然被包含在最后的结果中设置为true时则不包含
+ **setLatestVersionOnly(boolean latestVersionOnly)** 默认为true即只检索参考列的最新版本数据设置为false则检索所有版本数据。
+ **setFilterIfMissing(boolean filterIfMissing)** :默认值为 false即如果该行数据不包含参考列其依然被包含在最后的结果中设置为 true 时,则不包含;
+ **setLatestVersionOnly(boolean latestVersionOnly)** :默认为 true即只检索参考列的最新版本数据设置为 false则检索所有版本数据。
```shell
SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(
@ -213,11 +213,11 @@ scan.setFilter(singleColumnValueFilter);
### 4.2 单列列值排除器 (SingleColumnValueExcludeFilter)
`SingleColumnValueExcludeFilter`继承自上面的`SingleColumnValueFilter`,过滤行为与其相反。
`SingleColumnValueExcludeFilter` 继承自上面的 `SingleColumnValueFilter`,过滤行为与其相反。
### 4.3 行键前缀过滤器 (PrefixFilter)
基于RowKey值决定某行数据是否被过滤。
基于 RowKey 值决定某行数据是否被过滤。
```java
PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes("xxx"));
@ -235,7 +235,7 @@ ColumnPrefixFilter columnPrefixFilter = new ColumnPrefixFilter(Bytes.toBytes("xx
### 4.5 分页过滤器 (PageFilter)
可以使用这个过滤器实现对结果按行进行分页创建PageFilter实例的时候需要传入每页的行数。
可以使用这个过滤器实现对结果按行进行分页,创建 PageFilter 实例的时候需要传入每页的行数。
```java
public PageFilter(final long pageSize) {
@ -246,17 +246,17 @@ public PageFilter(final long pageSize) {
下面的代码体现了客户端实现分页查询的主要逻辑,这里对其进行一下解释说明:
客户端进行分页查询,需要传递`startRow`(起始RowKey),知道起始`startRow`就可以返回对应的pageSize行数据。这里唯一的问题就是对于第一次查询显然`startRow`就是表格的第一行数据,但是之后第二次、第三次查询我们并不知道`startRow`只能知道上一次查询的最后一条数据的RowKey简单称之为`lastRow`)。
客户端进行分页查询,需要传递 `startRow`(起始 RowKey),知道起始 `startRow` 后,就可以返回对应的 pageSize 行数据。这里唯一的问题就是,对于第一次查询,显然 `startRow` 就是表格的第一行数据,但是之后第二次、第三次查询我们并不知道 `startRow`,只能知道上一次查询的最后一条数据的 RowKey简单称之为 `lastRow`)。
我们不能将`lastRow`作为新一次查询的`startRow`传入因为scan的查询区间是[startRowendRow) ,即前开后闭区间,这样`startRow`在新的查询也会被返回,这条数据就重复了。
我们不能将 `lastRow` 作为新一次查询的 `startRow` 传入,因为 scan 的查询区间是[startRowendRow) ,即前开后闭区间,这样 `startRow` 在新的查询也会被返回,这条数据就重复了。
同时在不使用第三方数据库存储RowKey的情况下我们是无法通过知道`lastRow`的下一个RowKey的因为RowKey的设计可能是连续的也有可能是不连续的。
同时在不使用第三方数据库存储 RowKey 的情况下,我们是无法通过知道 `lastRow` 的下一个 RowKey 的,因为 RowKey 的设计可能是连续的也有可能是不连续的。
由于HbaseRowKey是按照字典序进行排序的。这种情况下就可以在`lastRow`后面加上`0` ,作为`startRow`传入,因为按照字典序的规则,某个值加上`0` 后的新值在字典序上一定是这个值的下一个值对于HBase来说下一个RowKey在字典序上一定也是等于或者大于这个新值的。
由于 HbaseRowKey 是按照字典序进行排序的。这种情况下,就可以在 `lastRow` 后面加上 `0` ,作为 `startRow` 传入,因为按照字典序的规则,某个值加上 `0` 后的新值,在字典序上一定是这个值的下一个值,对于 HBase 来说下一个 RowKey 在字典序上一定也是等于或者大于这个新值的。
所以最后传入`lastRow`+`0`如果等于这个值的RowKey存在就从这个值开始scan,否则从字典序的下一个RowKey开始scan。
所以最后传入 `lastRow`+`0`,如果等于这个值的 RowKey 存在就从这个值开始 scan,否则从字典序的下一个 RowKey 开始 scan。
> 25个字母以及数字字符字典排序如下:
> 25 个字母以及数字字符,字典排序如下:
>
> `'0' < '1' < '2' < ... < '9' < 'a' < 'b' < ... < 'z'`
@ -272,7 +272,7 @@ while (true) {
Scan scan = new Scan();
scan.setFilter(filter);
if (lastRow != null) {
// 如果不是首行 则lastRow + 0
// 如果不是首行 则 lastRow + 0
byte[] startRow = Bytes.add(lastRow, POSTFIX);
System.out.println("start row: " +
Bytes.toStringBinary(startRow));
@ -293,7 +293,7 @@ while (true) {
System.out.println("total rows: " + totalRows);
```
>需要注意的是在多台Regin Services上执行分页过滤的时候由于并行执行的过滤器不能共享它们的状态和边界所以有可能每个过滤器都会在完成扫描前获取了PageCount行的结果这种情况下会返回比分页条数更多的数据分页过滤器就有失效的可能。
>需要注意的是在多台 Regin Services 上执行分页过滤的时候,由于并行执行的过滤器不能共享它们的状态和边界,所以有可能每个过滤器都会在完成扫描前获取了 PageCount 行的结果,这种情况下会返回比分页条数更多的数据,分页过滤器就有失效的可能。
@ -308,7 +308,7 @@ scan.setFilter(timestampsFilter);
### 4.7 首次行键过滤器 (FirstKeyOnlyFilter)
`FirstKeyOnlyFilter`只扫描每行的第一列,扫描完第一列后就结束对当前行的扫描,并跳转到下一行。相比于全表扫描,其性能更好,通常用于行数统计的场景,因为如果某一行存在,则行中必然至少有一列。
`FirstKeyOnlyFilter` 只扫描每行的第一列,扫描完第一列后就结束对当前行的扫描,并跳转到下一行。相比于全表扫描,其性能更好,通常用于行数统计的场景,因为如果某一行存在,则行中必然至少有一列。
```java
FirstKeyOnlyFilter firstKeyOnlyFilter = new FirstKeyOnlyFilter();
@ -321,13 +321,13 @@ scan.set(firstKeyOnlyFilter);
### 5.1 SkipFilter过滤器
`SkipFilter`包装一个过滤器当被包装的过滤器遇到一个需要过滤的KeyValue实例时则拓展过滤整行数据。下面是一个使用示例
`SkipFilter` 包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,则拓展过滤整行数据。下面是一个使用示例:
```java
// 定义ValueFilter过滤器
// 定义 ValueFilter 过滤器
Filter filter1 = new ValueFilter(CompareOperator.NOT_EQUAL,
new BinaryComparator(Bytes.toBytes("xxx")));
// 使用SkipFilter进行包装
// 使用 SkipFilter 进行包装
Filter filter2 = new SkipFilter(filter1);
```
@ -335,7 +335,7 @@ Filter filter2 = new SkipFilter(filter1);
### 5.2 WhileMatchFilter过滤器
`WhileMatchFilter`包装一个过滤器当被包装的过滤器遇到一个需要过滤的KeyValue实例时`WhileMatchFilter`则结束本次扫描,返回已经扫描到的结果。下面是其使用示例:
`WhileMatchFilter` 包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,`WhileMatchFilter` 则结束本次扫描,返回已经扫描到的结果。下面是其使用示例:
```java
Filter filter1 = new RowFilter(CompareOperator.NOT_EQUAL,
@ -353,7 +353,7 @@ scanner1.close();
System.out.println("--------------------");
// 使用WhileMatchFilter进行包装
// 使用 WhileMatchFilter 进行包装
Filter filter2 = new WhileMatchFilter(filter1);
scan.setFilter(filter2);
@ -383,11 +383,11 @@ rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0
rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0
```
可以看到被包装后,只返回了`rowKey4`之前的数据。
可以看到被包装后,只返回了 `rowKey4` 之前的数据。
## 六、FilterList
以上都是讲解单个过滤器的作用,当需要多个过滤器共同作用于一次查询的时候,就需要使用`FilterList``FilterList`支持通过构造器或者`addFilter`方法传入多个过滤器。
以上都是讲解单个过滤器的作用,当需要多个过滤器共同作用于一次查询的时候,就需要使用 `FilterList``FilterList` 支持通过构造器或者 `addFilter` 方法传入多个过滤器。
```java
// 构造器传入
@ -400,10 +400,10 @@ public FilterList(final Filter... filters)
public void addFilter(Filter filter)
```
多个过滤器组合的结果由`operator`参数定义 ,其可选参数定义在`Operator`枚举类中。只有`MUST_PASS_ALL``MUST_PASS_ONE`两个可选的值:
多个过滤器组合的结果由 `operator` 参数定义 ,其可选参数定义在 `Operator` 枚举类中。只有 `MUST_PASS_ALL``MUST_PASS_ONE` 两个可选的值:
+ **MUST_PASS_ALL** 相当于AND必须所有的过滤器都通过才认为通过
+ **MUST_PASS_ONE** 相当于OR只有要一个过滤器通过则认为通过。
+ **MUST_PASS_ALL** :相当于 AND必须所有的过滤器都通过才认为通过
+ **MUST_PASS_ONE** :相当于 OR只有要一个过滤器通过则认为通过。
```java
@InterfaceAudience.Public

View File

@ -25,15 +25,15 @@
### 1.1 Help
使用`hive -H`或者 `hive --help`命令可以查看所有命令的帮助,显示如下:
使用 `hive -H` 或者 `hive --help` 命令可以查看所有命令的帮助,显示如下:
```
usage: hive
-d,--define <key=value> Variable subsitution to apply to hive
commands. e.g. -d A=B or --define A=B --定义用户自定义变量
--database <databasename> Specify the database to use -- 指定使用的数据库
-e <quoted-query-string> SQL from command line -- 执行指定的SQL
-f <filename> SQL from files --执行SQL脚本
-e <quoted-query-string> SQL from command line -- 执行指定的 SQL
-f <filename> SQL from files --执行 SQL 脚本
-H,--help Print help information -- 打印帮助信息
--hiveconf <property=value> Use value for given property --自定义配置
--hivevar <key=value> Variable subsitution to apply to hive --自定义变量
@ -45,11 +45,11 @@ usage: hive
### 1.2 交互式命令行
直接使用`Hive`命令,不加任何参数,即可进入交互式命令行。
直接使用 `Hive` 命令,不加任何参数,即可进入交互式命令行。
### 1.3 执行SQL命令
在不进入交互式命令行的情况下,可以使用`hive -e `执行SQL命令。
在不进入交互式命令行的情况下,可以使用 `hive -e ` 执行 SQL 命令。
```sql
hive -e 'select * from emp';
@ -61,7 +61,7 @@ hive -e 'select * from emp';
### 1.4 执行SQL脚本
用于执行的sql脚本可以在本地文件系统也可以在HDFS上。
用于执行的 sql 脚本可以在本地文件系统,也可以在 HDFS 上。
```shell
# 本地文件系统
@ -71,7 +71,7 @@ hive -f /usr/file/simple.sql;
hive -f hdfs://hadoop001:8020/tmp/simple.sql;
```
其中`simple.sql`内容如下:
其中 `simple.sql` 内容如下:
```sql
select * from emp;
@ -79,7 +79,7 @@ select * from emp;
### 1.5 配置Hive变量
可以使用`--hiveconf`设置Hive运行时的变量。
可以使用 `--hiveconf` 设置 Hive 运行时的变量。
```sql
hive -e 'select * from emp' \
@ -87,27 +87,27 @@ hive -e 'select * from emp' \
--hiveconf mapred.reduce.tasks=4;
```
> hive.exec.scratchdir指定HDFS上目录位置用于存储不同map/reduce阶段的执行计划和这些阶段的中间输出结果。
> hive.exec.scratchdir指定 HDFS 上目录位置,用于存储不同 map/reduce 阶段的执行计划和这些阶段的中间输出结果。
### 1.6 配置文件启动
使用`-i`可以在进入交互模式之前运行初始化脚本,相当于指定配置文件启动。
使用 `-i` 可以在进入交互模式之前运行初始化脚本,相当于指定配置文件启动。
```shell
hive -i /usr/file/hive-init.conf;
```
其中`hive-init.conf`的内容如下:
其中 `hive-init.conf` 的内容如下:
```sql
set hive.exec.mode.local.auto = true;
```
> hive.exec.mode.local.auto 默认值为false这里设置为true ,代表开启本地模式。
> hive.exec.mode.local.auto 默认值为 false这里设置为 true ,代表开启本地模式。
### 1.7 用户自定义变量
`--define <key=value> ``--hivevar <key=value> `在功能上是等价的,都是用来实现自定义变量,这里给出一个示例:
`--define <key=value> ``--hivevar <key=value> ` 在功能上是等价的,都是用来实现自定义变量,这里给出一个示例:
定义变量:
@ -135,15 +135,15 @@ hive > select ${hivevar:j} from emp;
### 2.1 HiveServer2
Hive内置了HiveServerHiveServer2服务两者都允许客户端使用多种编程语言进行连接但是HiveServer不能处理多个客户端的并发请求所以产生了HiveServer2。
Hive 内置了 HiveServerHiveServer2 服务,两者都允许客户端使用多种编程语言进行连接,但是 HiveServer 不能处理多个客户端的并发请求,所以产生了 HiveServer2。
HiveServer2HS2允许远程客户端可以使用各种编程语言向Hive提交请求并检索结果支持多客户端并发访问和身份验证。HS2是由多个服务组成的单个进程其包括基于ThriftHive服务TCPHTTP和用于Web UIJetty Web服务器。
HiveServer2HS2允许远程客户端可以使用各种编程语言向 Hive 提交请求并检索结果支持多客户端并发访问和身份验证。HS2 是由多个服务组成的单个进程,其包括基于 ThriftHive 服务TCPHTTP和用于 Web UIJetty Web 服务器。
HiveServer2拥有自己的CLI(Beeline)Beeline是一个基于SQLLineJDBC客户端。由于HiveServer2Hive开发维护的重点(Hive0.15后就不再支持hiveserver)所以Hive CLI已经不推荐使用了官方更加推荐使用Beeline。
HiveServer2 拥有自己的 CLI(Beeline)Beeline 是一个基于 SQLLineJDBC 客户端。由于 HiveServer2Hive 开发维护的重点 (Hive0.15 后就不再支持 hiveserver),所以 Hive CLI 已经不推荐使用了,官方更加推荐使用 Beeline。
### 2.1 Beeline
Beeline拥有更多可使用参数可以使用`beeline --help` 查看,完整参数如下:
Beeline 拥有更多可使用参数,可以使用 `beeline --help` 查看,完整参数如下:
```properties
Usage: java org.apache.hive.cli.beeline.BeeLine
@ -192,22 +192,22 @@ Usage: java org.apache.hive.cli.beeline.BeeLine
### 2.3 常用参数
在Hive CLI中支持的参数Beeline都支持常用的参数如下。更多参数说明可以参见官方文档 [Beeline Command Options](https://cwiki.apache.org/confluence/display/Hive/HiveServer2+Clients#HiveServer2Clients-Beeline%E2%80%93NewCommandLineShell)
Hive CLI 中支持的参数Beeline 都支持,常用的参数如下。更多参数说明可以参见官方文档 [Beeline Command Options](https://cwiki.apache.org/confluence/display/Hive/HiveServer2+Clients#HiveServer2Clients-Beeline%E2%80%93NewCommandLineShell)
| 参数 | 说明 |
| -------------------------------------- | ------------------------------------------------------------ |
| **-u \<database URL>** | 数据库地址 |
| **-n \<username>** | 用户名 |
| **-p \<password>** | 密码 |
| **-d \<driver class>** | 驱动(可选) |
| **-e \<query>** | 执行SQL命令 |
| **-f \<file>** | 执行SQL脚本 |
| **-d \<driver class>** | 驱动 (可选) |
| **-e \<query>** | 执行 SQL 命令 |
| **-f \<file>** | 执行 SQL 脚本 |
| **-i (or)--init \<file or files>** | 在进入交互模式之前运行初始化脚本 |
| **--property-file \<file>** | 指定配置文件 |
| **--hiveconf** *property**=**value* | 指定配置属性 |
| **--hivevar** *name**=**value* | 用户自定义属性,在会话级别有效 |
示例: 使用用户名和密码连接Hive
示例: 使用用户名和密码连接 Hive
```shell
$ beeline -u jdbc:hive2://localhost:10000 -n username -p password
@ -217,18 +217,18 @@ $ beeline -u jdbc:hive2://localhost:10000 -n username -p password
## 三、Hive配置
可以通过三种方式对Hive的相关属性进行配置分别介绍如下
可以通过三种方式对 Hive 的相关属性进行配置,分别介绍如下:
### 3.1 配置文件
方式一为使用配置文件使用配置文件指定的配置是永久有效的。Hive有以下三个可选的配置文件
方式一为使用配置文件使用配置文件指定的配置是永久有效的。Hive 有以下三个可选的配置文件:
+ hive-site.xml Hive的主要配置文件
+ hive-site.xml Hive 的主要配置文件;
+ hivemetastore-site.xml 关于元数据的配置;
+ hiveserver2-site.xml关于HiveServer2的配置。
+ hiveserver2-site.xml关于 HiveServer2 的配置。
示例如下,在hive-site.xml配置`hive.exec.scratchdir`
示例如下,在 hive-site.xml 配置 `hive.exec.scratchdir`
```xml
<property>
@ -240,7 +240,7 @@ $ beeline -u jdbc:hive2://localhost:10000 -n username -p password
### 3.2 hiveconf
方式二为在启动命令行(Hive CLI / Beeline)的时候使用`--hiveconf`指定配置这种方式指定的配置作用于整个Session。
方式二为在启动命令行 (Hive CLI / Beeline) 的时候使用 `--hiveconf` 指定配置,这种方式指定的配置作用于整个 Session。
```
hive --hiveconf hive.exec.scratchdir=/tmp/mydir
@ -248,7 +248,7 @@ hive --hiveconf hive.exec.scratchdir=/tmp/mydir
### 3.3 set
方式三为在交互式环境下(Hive CLI / Beeline)使用set命令指定。这种设置的作用范围也是Session级别的配置对于执行该命令后的所有命令生效。set兼具设置参数和查看参数的功能。如下
方式三为在交互式环境下 (Hive CLI / Beeline),使用 set 命令指定。这种设置的作用范围也是 Session 级别的配置对于执行该命令后的所有命令生效。set 兼具设置参数和查看参数的功能。如下:
```shell
0: jdbc:hive2://hadoop001:10000> set hive.exec.scratchdir=/tmp/mydir;
@ -263,12 +263,12 @@ No rows affected (0.025 seconds)
### 3.4 配置优先级
配置的优先顺序如下(由低到高)
配置的优先顺序如下 (由低到高)
`hive-site.xml` - >` hivemetastore-site.xml `- > `hiveserver2-site.xml` - >` -- hiveconf`- > `set`
### 3.5 配置参数
Hive可选的配置参数非常多在用到时查阅官方文档即可[AdminManual Configuration](https://cwiki.apache.org/confluence/display/Hive/AdminManual+Configuration)
Hive 可选的配置参数非常多,在用到时查阅官方文档即可[AdminManual Configuration](https://cwiki.apache.org/confluence/display/Hive/AdminManual+Configuration)

View File

@ -11,11 +11,11 @@
### 1.1 概念
Hive中的表对应为HDFS上的指定目录在查询数据时候默认会对全表进行扫描这样时间和性能的消耗都非常大。
Hive 中的表对应为 HDFS 上的指定目录,在查询数据时候,默认会对全表进行扫描,这样时间和性能的消耗都非常大。
**分区为HDFS上表目录的子目录**,数据按照分区存储在子目录中。如果查询的`where`字句的中包含分区条件,则直接从该分区去查找,而不是扫描整个表目录,合理的分区设计可以极大提高查询速度和性能。
**分区为 HDFS 上表目录的子目录**,数据按照分区存储在子目录中。如果查询的 `where` 字句的中包含分区条件,则直接从该分区去查找,而不是扫描整个表目录,合理的分区设计可以极大提高查询速度和性能。
>这里说明一下分区表并Hive独有的概念实际上这个概念非常常见。比如在我们常用的Oracle数据库中当表中的数据量不断增大查询数据的速度就会下降这时也可以对表进行分区。表进行分区后逻辑上表仍然是一张完整的表只是将表中的数据存放到多个表空间物理文件上这样查询数据时就不必要每次都扫描整张表从而提升查询性能。
>这里说明一下分区表并 Hive 独有的概念,实际上这个概念非常常见。比如在我们常用的 Oracle 数据库中,当表中的数据量不断增大,查询数据的速度就会下降,这时也可以对表进行分区。表进行分区后,逻辑上表仍然是一张完整的表,只是将表中的数据存放到多个表空间(物理文件上),这样查询数据时,就不必要每次都扫描整张表,从而提升查询性能。
### 1.2 使用场景
@ -23,7 +23,7 @@ Hive中的表对应为HDFS上的指定目录在查询数据时候默认会
### 1.3 创建分区表
在Hive中可以使用`PARTITIONED BY`子句创建分区表。表可以包含一个或多个分区列,程序会为分区列中的每个不同值组合创建单独的数据目录。下面的我们创建一张雇员表作为测试:
Hive 中可以使用 `PARTITIONED BY` 子句创建分区表。表可以包含一个或多个分区列,程序会为分区列中的每个不同值组合创建单独的数据目录。下面的我们创建一张雇员表作为测试:
```shell
CREATE EXTERNAL TABLE emp_partition(
@ -53,13 +53,13 @@ LOAD DATA LOCAL INPATH "/usr/file/emp30.txt" OVERWRITE INTO TABLE emp_partition
### 1.5 查看分区目录
这时候我们直接查看表目录,可以看到表目录下存在两个子目录,分别是`deptno=20``deptno=30`,这就是分区目录,分区目录下才是我们加载的数据文件。
这时候我们直接查看表目录,可以看到表目录下存在两个子目录,分别是 `deptno=20``deptno=30`,这就是分区目录,分区目录下才是我们加载的数据文件。
```shell
# hadoop fs -ls hdfs://hadoop001:8020/hive/emp_partition/
```
这时候当你的查询语句的`where`包含`deptno=20`,则就去对应的分区目录下进行查找,而不用扫描全表。
这时候当你的查询语句的 `where` 包含 `deptno=20`,则就去对应的分区目录下进行查找,而不用扫描全表。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-hadoop-partitation.png"/> </div>
@ -69,15 +69,15 @@ LOAD DATA LOCAL INPATH "/usr/file/emp30.txt" OVERWRITE INTO TABLE emp_partition
### 1.1 简介
分区提供了一个隔离数据和优化查询的可行方案但是并非所有的数据集都可以形成合理的分区分区的数量也不是越多越好过多的分区条件可能会导致很多分区上没有数据。同时Hive会限制动态分区可以创建的最大分区数用来避免过多分区文件对文件系统产生负担。鉴于以上原因Hive还提供了一种更加细粒度的数据拆分方案分桶表(bucket Table)。
分区提供了一个隔离数据和优化查询的可行方案,但是并非所有的数据集都可以形成合理的分区,分区的数量也不是越多越好,过多的分区条件可能会导致很多分区上没有数据。同时 Hive 会限制动态分区可以创建的最大分区数用来避免过多分区文件对文件系统产生负担。鉴于以上原因Hive 还提供了一种更加细粒度的数据拆分方案:分桶表 (bucket Table)。
分桶表会将指定列的值进行哈希散列并对bucket桶数量取余然后存储到对应的bucket中。
分桶表会将指定列的值进行哈希散列,并对 bucket桶数量取余然后存储到对应的 bucket中。
### 1.2 理解分桶表
单从概念上理解分桶表可能会比较晦涩其实和分区一样分桶这个概念同样不是Hive独有的对于Java开发人员而言这可能是一个每天都会用到的概念因为Hive中的分桶概念和Java数据结构中的HashMap的分桶概念是一致的。
单从概念上理解分桶表可能会比较晦涩,其实和分区一样,分桶这个概念同样不是 Hive 独有的,对于 Java 开发人员而言,这可能是一个每天都会用到的概念,因为 Hive 中的分桶概念和 Java 数据结构中的 HashMap 的分桶概念是一致的。
当调用HashMapput()方法存储数据时程序会先对key值调用hashCode()方法计算出hashcode然后对数组长度取模计算出index最后将数据存储在数组index位置的链表上链表达到一定阈值后会转换为红黑树(JDK1.8+)。下图为HashMap的数据结构图
当调用 HashMapput() 方法存储数据时,程序会先对 key 值调用 hashCode() 方法计算出 hashcode然后对数组长度取模计算出 index最后将数据存储在数组 index 位置的链表上,链表达到一定阈值后会转换为红黑树 (JDK1.8+)。下图为 HashMap 的数据结构图:
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/HashMap-HashTable.png"/> </div>
@ -85,7 +85,7 @@ LOAD DATA LOCAL INPATH "/usr/file/emp30.txt" OVERWRITE INTO TABLE emp_partition
### 1.3 创建分桶表
在Hive中我们可以通过`CLUSTERED BY`指定分桶列,并通过`SORTED BY`指定桶中数据的排序参考列。下面为分桶表建表语句示例:
Hive 中,我们可以通过 `CLUSTERED BY` 指定分桶列,并通过 `SORTED BY` 指定桶中数据的排序参考列。下面为分桶表建表语句示例:
```sql
CREATE EXTERNAL TABLE emp_bucket(
@ -97,37 +97,37 @@ LOAD DATA LOCAL INPATH "/usr/file/emp30.txt" OVERWRITE INTO TABLE emp_partition
sal DECIMAL(7,2),
comm DECIMAL(7,2),
deptno INT)
CLUSTERED BY(empno) SORTED BY(empno ASC) INTO 4 BUCKETS --按照员工编号散列到四个bucket中
CLUSTERED BY(empno) SORTED BY(empno ASC) INTO 4 BUCKETS --按照员工编号散列到四个 bucket
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
LOCATION '/hive/emp_bucket';
```
### 1.4 加载数据到分桶表
这里直接使用`Load`语句向分桶表加载数据,数据时可以加载成功的,但是数据并不会分桶。
这里直接使用 `Load` 语句向分桶表加载数据,数据时可以加载成功的,但是数据并不会分桶。
这是由于分桶的实质是对指定字段做了hash散列然后存放到对应文件中这意味着向分桶表中插入数据是必然要通过MapReduce且Reducer的数量必须等于分桶的数量。由于以上原因分桶表的数据通常只能使用CTAS(CREATE TABLE AS SELECT)方式插入因为CTAS操作会触发MapReduce。加载数据步骤如下
这是由于分桶的实质是对指定字段做了 hash 散列然后存放到对应文件中,这意味着向分桶表中插入数据是必然要通过 MapReduce Reducer 的数量必须等于分桶的数量。由于以上原因,分桶表的数据通常只能使用 CTAS(CREATE TABLE AS SELECT) 方式插入,因为 CTAS 操作会触发 MapReduce。加载数据步骤如下
#### 1. 设置强制分桶
```sql
set hive.enforce.bucketing = true; --Hive 2.x不需要这一步
set hive.enforce.bucketing = true; --Hive 2.x 不需要这一步
```
在Hive 0.x and 1.x版本必须使用设置`hive.enforce.bucketing = true`表示强制分桶允许程序根据表结构自动选择正确数量的Reducercluster by column来进行分桶。
Hive 0.x and 1.x 版本,必须使用设置 `hive.enforce.bucketing = true`,表示强制分桶,允许程序根据表结构自动选择正确数量的 Reducercluster by column 来进行分桶。
#### 2. CTAS导入数据
```sql
INSERT INTO TABLE emp_bucket SELECT * FROM emp; --这里的emp表就是一张普通的雇员表
INSERT INTO TABLE emp_bucket SELECT * FROM emp; --这里的 emp 表就是一张普通的雇员表
```
可以从执行日志看到CTAS触发MapReduce操作且Reducer数量和建表时候指定bucket数量一致
可以从执行日志看到 CTAS 触发 MapReduce 操作,且 Reducer 数量和建表时候指定 bucket 数量一致:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-hadoop-mapreducer.png"/> </div>
### 1.5 查看分桶文件
bucket(桶)本质上就是表目录下的具体文件:
bucket(桶) 本质上就是表目录下的具体文件:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-hadoop-bucket.png"/> </div>
@ -135,7 +135,7 @@ bucket(桶)本质上就是表目录下的具体文件:
## 三、分区表和分桶表结合使用
分区表和分桶表的本质都是将数据按照不同粒度进行拆分从而使得在查询时候不必扫描全表只需要扫描对应的分区或分桶从而提升查询效率。两者可以结合起来使用从而保证表数据在不同粒度上都能得到合理的拆分。下面是Hive官方给出的示例
分区表和分桶表的本质都是将数据按照不同粒度进行拆分,从而使得在查询时候不必扫描全表,只需要扫描对应的分区或分桶,从而提升查询效率。两者可以结合起来使用,从而保证表数据在不同粒度上都能得到合理的拆分。下面是 Hive 官方给出的示例:
```sql
CREATE TABLE page_view_bucketed(

View File

@ -51,9 +51,9 @@ USE database_name;
语法:
```sql
CREATE (DATABASE|SCHEMA) [IF NOT EXISTS] database_name --DATABASE|SCHEMA是等价的
CREATE (DATABASE|SCHEMA) [IF NOT EXISTS] database_name --DATABASE|SCHEMA 是等价的
[COMMENT database_comment] --数据库注释
[LOCATION hdfs_path] --存储在HDFS上的位置
[LOCATION hdfs_path] --存储在 HDFS 上的位置
[WITH DBPROPERTIES (property_name=property_value, ...)]; --指定额外属性
```
@ -91,7 +91,7 @@ DESC DATABASE EXTENDED hive_test;
DROP (DATABASE|SCHEMA) [IF EXISTS] database_name [RESTRICT|CASCADE];
```
+ 默认行为是RESTRICT如果数据库中存在表则删除失败。要想删除库及其中的表可以使用CASCADE级联删除。
+ 默认行为是 RESTRICT如果数据库中存在表则删除失败。要想删除库及其中的表可以使用 CASCADE 级联删除。
示例:
@ -159,7 +159,7 @@ CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name --
LOCATION '/hive/emp_external';
```
使用 `desc format emp_external`命令可以查看表的详细信息如下:
使用 `desc format emp_external` 命令可以查看表的详细信息如下:
<div align="center"> <img width='700px' src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-external-table.png"/> </div>
@ -192,14 +192,14 @@ CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name --
sal DECIMAL(7,2),
comm DECIMAL(7,2),
deptno INT)
CLUSTERED BY(empno) SORTED BY(empno ASC) INTO 4 BUCKETS --按照员工编号散列到四个bucket中
CLUSTERED BY(empno) SORTED BY(empno ASC) INTO 4 BUCKETS --按照员工编号散列到四个 bucket
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
LOCATION '/hive/emp_bucket';
```
### 2.6 倾斜表
通过指定一个或者多个列经常出现的值严重偏斜Hive会自动将涉及到这些值的数据拆分为单独的文件。在查询时如果涉及到倾斜值它就直接从独立文件中获取数据而不是扫描所有文件这使得性能得到提升。
通过指定一个或者多个列经常出现的值严重偏斜Hive 会自动将涉及到这些值的数据拆分为单独的文件。在查询时,如果涉及到倾斜值,它就直接从独立文件中获取数据,而不是扫描所有文件,这使得性能得到提升。
```sql
CREATE EXTERNAL TABLE emp_skewed(
@ -211,14 +211,14 @@ CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name --
sal DECIMAL(7,2),
comm DECIMAL(7,2)
)
SKEWED BY (empno) ON (66,88,100) --指定empno的倾斜值66,88,100
SKEWED BY (empno) ON (66,88,100) --指定 empno 的倾斜值 66,88,100
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
LOCATION '/hive/emp_skewed';
```
### 2.7 临时表
临时表仅对当前session可见临时表的数据将存储在用户的暂存目录中并在会话结束后删除。如果临时表与永久表表名相同则对该表名的任何引用都将解析为临时表而不是永久表。临时表还具有以下两个限制
临时表仅对当前 session 可见,临时表的数据将存储在用户的暂存目录中,并在会话结束后删除。如果临时表与永久表表名相同,则对该表名的任何引用都将解析为临时表,而不是永久表。临时表还具有以下两个限制:
+ 不支持分区列;
+ 不支持创建索引。
@ -264,14 +264,14 @@ CREATE TEMPORARY EXTERNAL TABLE IF NOT EXISTS emp_co LIKE emp
### 2.10 加载数据到表
加载数据到表中属于DML操作这里为了方便大家测试先简单介绍一下加载本地数据到表中
加载数据到表中属于 DML 操作,这里为了方便大家测试,先简单介绍一下加载本地数据到表中:
```sql
-- 加载数据到emp表中
-- 加载数据到 emp 表中
load data local inpath "/usr/file/emp.txt" into table emp;
```
其中emp.txt的内容如下你可以直接复制使用也可以到本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources)目录下载:
其中 emp.txt 的内容如下,你可以直接复制使用,也可以到本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources) 目录下载:
```txt
7369 SMITH CLERK 7902 1980-12-17 00:00:00 800.00 20
@ -309,7 +309,7 @@ ALTER TABLE table_name RENAME TO new_table_name;
示例:
```sql
ALTER TABLE emp_temp RENAME TO new_emp; --把emp_temp表重命名为new_emp
ALTER TABLE emp_temp RENAME TO new_emp; --把 emp_temp 表重命名为 new_emp
```
@ -329,7 +329,7 @@ ALTER TABLE table_name [PARTITION partition_spec] CHANGE [COLUMN] col_old_name c
-- 修改字段名和类型
ALTER TABLE emp_temp CHANGE empno empno_new INT;
-- 修改字段sal的名称 并将其放置到empno字段后
-- 修改字段 sal 的名称 并将其放置到 empno 字段后
ALTER TABLE emp_temp CHANGE sal sal_new decimal(7,2) AFTER ename;
-- 为字段增加注释
@ -359,7 +359,7 @@ ALTER TABLE emp_temp ADD COLUMNS (address STRING COMMENT 'home address');
TRUNCATE TABLE table_name [PARTITION (partition_column = partition_col_value, ...)];
```
+ 目前只有内部表才能执行TRUNCATE操作外部表执行时会抛出异常`Cannot truncate non-managed table XXXX`
+ 目前只有内部表才能执行 TRUNCATE 操作,外部表执行时会抛出异常 `Cannot truncate non-managed table XXXX`
示例:
@ -377,8 +377,8 @@ TRUNCATE TABLE emp_mgt_ptn PARTITION (deptno=20);
DROP TABLE [IF EXISTS] table_name [PURGE];
```
+ 内部表不仅会删除表的元数据同时会删除HDFS上的数据
+ 外部表只会删除表的元数据不会删除HDFS上的数据
+ 内部表:不仅会删除表的元数据,同时会删除 HDFS 上的数据;
+ 外部表:只会删除表的元数据,不会删除 HDFS 上的数据;
+ 删除视图引用的表时,不会给出警告(但视图已经无效了,必须由用户删除或重新创建)。
@ -413,7 +413,7 @@ SHOW (DATABASES|SCHEMAS) [LIKE 'identifier_with_wildcards'];
SHOW DATABASES like 'hive*';
```
LIKE子句允许使用正则表达式进行过滤但是SHOW语句当中的LIKE子句只支持`*`(通配符)和`|`(条件或)两个符号。例如`employees``emp *``emp * | * ees`,所有这些都将匹配名为`employees`的数据库。
LIKE 子句允许使用正则表达式进行过滤,但是 SHOW 语句当中的 LIKE 子句只支持 `*`(通配符)和 `|`(条件或)两个符号。例如 `employees``emp *``emp * | * ees`,所有这些都将匹配名为 `employees` 的数据库。
**2. 查看表的列表**
@ -428,7 +428,7 @@ SHOW TABLES IN default;
**3. 查看视图列表**
```sql
SHOW VIEWS [IN/FROM database_name] [LIKE 'pattern_with_wildcards']; --仅支持Hive 2.2.0 +
SHOW VIEWS [IN/FROM database_name] [LIKE 'pattern_with_wildcards']; --仅支持 Hive 2.2.0 +
```
**4. 查看表的分区列表**
@ -447,4 +447,4 @@ SHOW CREATE TABLE ([db_name.]table_name|view_name);
## 参考资料
[LanguageManual DDL](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL)
[LanguageManual DDL](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL)

View File

@ -18,24 +18,24 @@ LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE]
INTO TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)]
```
- `LOCAL`关键字代表从本地文件系统加载文件省略则代表从HDFS上加载文件
+ 从本地文件系统加载文件时, `filepath`可以是绝对路径也可以是相对路径(建议使用绝对路径)
- `LOCAL` 关键字代表从本地文件系统加载文件,省略则代表从 HDFS 上加载文件:
+ 从本地文件系统加载文件时, `filepath` 可以是绝对路径也可以是相对路径 (建议使用绝对路径)
+ 从HDFS加载文件时候`filepath`为文件完整的URL地址`hdfs://namenode:port/user/hive/project/ data1`
+ 从 HDFS 加载文件时候,`filepath` 为文件完整的 URL 地址:如 `hdfs://namenode:port/user/hive/project/ data1`
- `filepath`可以是文件路径(在这种情况下Hive会将文件移动到表中),也可以目录路径(在这种情况下Hive会将该目录中的所有文件移动到表中)
- `filepath` 可以是文件路径 (在这种情况下 Hive 会将文件移动到表中),也可以目录路径 (在这种情况下Hive 会将该目录中的所有文件移动到表中)
- 如果使用OVERWRITE关键字则将删除目标表或分区的内容使用新的数据填充不使用此关键字则数据以追加的方式加入
- 如果使用 OVERWRITE 关键字,则将删除目标表(或分区)的内容,使用新的数据填充;不使用此关键字,则数据以追加的方式加入;
- 加载的目标可以是表或分区。如果是分区表,则必须指定加载数据的分区;
- 加载文件的格式必须与建表时使用` STORED AS`指定的存储格式相同。
- 加载文件的格式必须与建表时使用 ` STORED AS` 指定的存储格式相同。
> 使用建议:
>
> **不论是本地路径还是URL都建议使用完整的**。虽然可以使用不完整的URL地址此时Hive将使用hadoop中的fs.default.name配置来推断地址但是为避免不必要的错误建议使用完整的本地路径或URL地址
> **不论是本地路径还是 URL 都建议使用完整的**。虽然可以使用不完整的 URL 地址,此时 Hive 将使用 hadoop 中的 fs.default.name 配置来推断地址,但是为避免不必要的错误,建议使用完整的本地路径或 URL 地址;
>
> **加载对象是分区表时建议显示指定分区**。在Hive 3.0之后,内部将加载(LOAD)重写为INSERT AS SELECT此时如果不指定分区INSERT AS SELECT将假设最后一组列是分区列如果该列不是表定义的分区它将抛出错误。为避免错误还是建议显示指定分区。
> **加载对象是分区表时建议显示指定分区**。在 Hive 3.0 之后,内部将加载 (LOAD) 重写为 INSERT AS SELECT此时如果不指定分区INSERT AS SELECT 将假设最后一组列是分区列,如果该列不是表定义的分区,它将抛出错误。为避免错误,还是建议显示指定分区。
### 1.2 示例
@ -55,15 +55,15 @@ INTO TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)]
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";
```
从HDFS上加载数据到分区表
HDFS 上加载数据到分区表:
```sql
LOAD DATA INPATH "hdfs://hadoop001:8020/mydir/emp.txt" OVERWRITE INTO TABLE emp_ptn PARTITION (deptno=20);
```
> emp.txt文件可在本仓库的resources目录中下载
> emp.txt 文件可在本仓库的 resources 目录中下载
加载后表中数据如下,分区列deptno全部赋值成20
加载后表中数据如下,分区列 deptno 全部赋值成 20
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-emp-ptn.png"/> </div>
@ -79,15 +79,15 @@ INSERT INTO TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...)]
select_statement1 FROM from_statement;
```
+ Hive 0.13.0开始建表时可以通过使用TBLPROPERTIES“immutable”=“true”来创建不可变表(immutable table) 如果不可以变表中存在数据则INSERT INTO失败。INSERT OVERWRITE的语句不受`immutable`属性的影响);
+ Hive 0.13.0 开始,建表时可以通过使用 TBLPROPERTIES“immutable”=“true”来创建不可变表 (immutable table) ,如果不可以变表中存在数据,则 INSERT INTO 失败。INSERT OVERWRITE 的语句不受 `immutable` 属性的影响);
+ 可以对表或分区执行插入操作。如果表已分区,则必须通过指定所有分区列的值来指定表的特定分区;
+ 从Hive 1.1.0开始TABLE关键字是可选的
+ 从 Hive 1.1.0 开始TABLE 关键字是可选的;
+ 从Hive 1.2.0开始 可以采用INSERT INTO tablename(zxc1)指明插入列;
+ 从 Hive 1.2.0 开始 ,可以采用 INSERT INTO tablename(zxc1) 指明插入列;
+ 可以将SELECT语句的查询结果插入多个表或分区称为多表插入。语法如下
+ 可以将 SELECT 语句的查询结果插入多个表(或分区),称为多表插入。语法如下:
```sql
FROM from_statement
@ -107,22 +107,22 @@ INSERT INTO TABLE tablename PARTITION (partcol1[=val1], partcol2[=val2] ...)
select_statement FROM from_statement;
```
在向分区表插入数据时候分区列名是必须的但是列值是可选的。如果给出了分区列值我们将其称为静态分区否则它是动态分区。动态分区列必须在SELECT语句的列中最后指定并且与它们在PARTITION()子句中出现的顺序相同。
在向分区表插入数据时候,分区列名是必须的,但是列值是可选的。如果给出了分区列值,我们将其称为静态分区,否则它是动态分区。动态分区列必须在 SELECT 语句的列中最后指定,并且与它们在 PARTITION() 子句中出现的顺序相同。
注意Hive 0.9.0之前的版本动态分区插入是默认禁用的而0.9.0之后的版本则默认启用。以下是动态分区的相关配置:
注意Hive 0.9.0 之前的版本动态分区插入是默认禁用的,而 0.9.0 之后的版本则默认启用。以下是动态分区的相关配置:
| 配置 | 默认值 | 说明 |
| ------------------------------------------ | -------- | ------------------------------------------------------------ |
| `hive.exec.dynamic.partition` | `true` | 需要设置为true才能启用动态分区插入 |
| `hive.exec.dynamic.partition.mode` | `strict` | 在严格模式(strict)下,用户必须至少指定一个静态分区,以防用户意外覆盖所有分区,在非严格模式下,允许所有分区都是动态的 |
| `hive.exec.max.dynamic.partitions.pernode` | 100 | 允许在每个mapper/reducer节点中创建的最大动态分区数 |
| `hive.exec.dynamic.partition` | `true` | 需要设置为 true 才能启用动态分区插入 |
| `hive.exec.dynamic.partition.mode` | `strict` | 在严格模式 (strict) 下,用户必须至少指定一个静态分区,以防用户意外覆盖所有分区,在非严格模式下,允许所有分区都是动态的 |
| `hive.exec.max.dynamic.partitions.pernode` | 100 | 允许在每个 mapper/reducer 节点中创建的最大动态分区数 |
| `hive.exec.max.dynamic.partitions` | 1000 | 允许总共创建的最大动态分区数 |
| `hive.exec.max.created.files` | 100000 | 作业中所有mapper/reducer创建的HDFS文件的最大数量 |
| `hive.exec.max.created.files` | 100000 | 作业中所有 mapper/reducer 创建的 HDFS 文件的最大数量 |
| `hive.error.on.empty.partition` | `false` | 如果动态分区插入生成空结果,是否抛出异常 |
### 2.3 示例
1. 新建emp表作为查询对象表
1. 新建 emp 表,作为查询对象表
```sql
CREATE TABLE emp(
@ -136,26 +136,26 @@ CREATE TABLE emp(
deptno INT)
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";
-- 加载数据到emp表中 这里直接从本地加载
-- 加载数据到 emp 表中 这里直接从本地加载
load data local inpath "/usr/file/emp.txt" into table emp;
```
完成后`emp`表中数据如下:
完成后 `emp` 表中数据如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-emp.png"/> </div>
2. 为清晰演示,先清空`emp_ptn`表中加载的数据:
2. 为清晰演示,先清空 `emp_ptn` 表中加载的数据:
```sql
TRUNCATE TABLE emp_ptn;
```
3. 静态分区演示:从`emp`表中查询部门编号为20的员工数据并插入`emp_ptn`表中,语句如下:
3. 静态分区演示:从 `emp` 表中查询部门编号为 20 的员工数据,并插入 `emp_ptn` 表中,语句如下:
```sql
INSERT OVERWRITE TABLE emp_ptn PARTITION (deptno=20)
SELECT empno,ename,job,mgr,hiredate,sal,comm FROM emp WHERE deptno=20;
```
完成后`emp_ptn`表中数据如下:
完成后 `emp_ptn` 表中数据如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-emp-deptno-20.png"/> </div>
@ -165,12 +165,12 @@ SELECT empno,ename,job,mgr,hiredate,sal,comm FROM emp WHERE deptno=20;
-- 由于我们只有一个分区,且还是动态分区,所以需要关闭严格默认。因为在严格模式下,用户必须至少指定一个静态分区
set hive.exec.dynamic.partition.mode=nonstrict;
-- 动态分区 此时查询语句的最后一列为动态分区列即deptno
-- 动态分区 此时查询语句的最后一列为动态分区列,即 deptno
INSERT OVERWRITE TABLE emp_ptn PARTITION (deptno)
SELECT empno,ename,job,mgr,hiredate,sal,comm,deptno FROM emp WHERE deptno=30;
```
完成后`emp_ptn`表中数据如下:
完成后 `emp_ptn` 表中数据如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-emp-deptno-20-30.png"/> </div>
@ -184,8 +184,8 @@ VALUES ( value [, value ...] )
```
+ 使用时必须为表中的每个列都提供值。不支持只向部分列插入值(可以为缺省值的列提供空值来消除这个弊端);
+ 如果目标表表支持ACID及其事务管理器则插入后自动提交
+ 不支持支持复杂类型(array, map, struct, union)的插入。
+ 如果目标表表支持 ACID 及其事务管理器,则插入后自动提交;
+ 不支持支持复杂类型 (array, map, struct, union) 的插入。
@ -193,7 +193,7 @@ VALUES ( value [, value ...] )
### 4.1 语法
更新和删除的语法比较简单和关系型数据库一致。需要注意的是这两个操作都只能在支持ACID的表也就是事务表上才能执行。
更新和删除的语法比较简单,和关系型数据库一致。需要注意的是这两个操作都只能在支持 ACID 的表,也就是事务表上才能执行。
```sql
-- 更新
@ -207,7 +207,7 @@ DELETE FROM tablename [WHERE expression]
**1. 修改配置**
首先需要更改`hive-site.xml`添加如下配置开启事务支持配置完成后需要重启Hive服务。
首先需要更改 `hive-site.xml`,添加如下配置,开启事务支持,配置完成后需要重启 Hive 服务。
```xml
<property>
@ -238,11 +238,11 @@ DELETE FROM tablename [WHERE expression]
**2. 创建测试表**
创建用于测试的事务表,建表时候指定属性`transactional = true`则代表该表是事务表。需要注意的是,按照[官方文档](https://cwiki.apache.org/confluence/display/Hive/Hive+Transactions)的说明目前Hive中的事务表有以下限制
创建用于测试的事务表,建表时候指定属性 `transactional = true` 则代表该表是事务表。需要注意的是,按照[官方文档](https://cwiki.apache.org/confluence/display/Hive/Hive+Transactions) 的说明,目前 Hive 中的事务表有以下限制:
+ 必须是buckets Table;
+ 仅支持ORC文件格式
+ 不支持LOAD DATA ...语句。
+ 必须是 buckets Table;
+ 仅支持 ORC 文件格式;
+ 不支持 LOAD DATA ...语句。
```sql
CREATE TABLE emp_ts(
@ -259,7 +259,7 @@ TBLPROPERTIES ("transactional"="true");
INSERT INTO TABLE emp_ts VALUES (1,"ming"),(2,"hong");
```
插入数据依靠的是MapReduce作业执行成功后数据如下
插入数据依靠的是 MapReduce 作业,执行成功后数据如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-emp-ts.png"/> </div>
@ -273,7 +273,7 @@ UPDATE emp_ts SET ename = "lan" WHERE empno=1;
DELETE FROM emp_ts WHERE empno=2;
```
更新和删除数据依靠的也是MapReduce作业执行成功后数据如下
更新和删除数据依靠的也是 MapReduce 作业,执行成功后数据如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-emp-ts-2.png"/> </div>
@ -288,11 +288,11 @@ INSERT OVERWRITE [LOCAL] DIRECTORY directory1
SELECT ... FROM ...
```
+ OVERWRITE关键字表示输出文件存在时先删除后再重新写入
+ OVERWRITE 关键字表示输出文件存在时,先删除后再重新写入;
+ 和Load语句一样建议无论是本地路径还是URL地址都使用完整的
+ 和 Load 语句一样,建议无论是本地路径还是 URL 地址都使用完整的;
+ 写入文件系统的数据被序列化为文本,其中列默认由^A分隔行由换行符分隔。如果列不是基本类型则将其序列化为JSON格式。其中行分隔符不允许自定义但列分隔符可以自定义如下
+ 写入文件系统的数据被序列化为文本,其中列默认由^A 分隔,行由换行符分隔。如果列不是基本类型,则将其序列化为 JSON 格式。其中行分隔符不允许自定义,但列分隔符可以自定义,如下:
```sql
-- 定义列分隔符为'\t'
@ -306,7 +306,7 @@ INSERT OVERWRITE [LOCAL] DIRECTORY directory1
### 5.2 示例
这里我们将上面创建的`emp_ptn`表导出到本地文件系统,语句如下:
这里我们将上面创建的 `emp_ptn` 表导出到本地文件系统,语句如下:
```sql
INSERT OVERWRITE LOCAL DIRECTORY '/usr/file/ouput'

View File

@ -33,7 +33,7 @@
为了演示查询操作,这里需要预先创建三张表,并加载测试数据。
> 数据文件emp.txtdept.txt可以从本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources)目录下载。
> 数据文件 emp.txtdept.txt 可以从本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources) 目录下载。
### 1.1 员工表
@ -110,7 +110,7 @@ SELECT * FROM emp;
### 2.2 WHERE
```sql
-- 查询10号部门中员工编号大于 7782 的员工信息
-- 查询 10 号部门中员工编号大于 7782 的员工信息
SELECT * FROM emp WHERE empno > 7782 AND deptno = 10;
```
@ -118,7 +118,7 @@ SELECT * FROM emp WHERE empno > 7782 AND deptno = 10;
### 2.3 DISTINCT
Hive支持使用DISTINCT关键字去重。
Hive 支持使用 DISTINCT 关键字去重。
```sql
-- 查询所有工作类型
@ -129,7 +129,7 @@ SELECT DISTINCT job FROM emp;
### 2.4 分区查询
分区查询(Partition Based Queries),可以指定某个分区或者分区范围。
分区查询 (Partition Based Queries),可以指定某个分区或者分区范围。
```sql
-- 查询分区表中部门编号在[20,40]之间的员工
@ -142,7 +142,7 @@ WHERE emp_ptn.deptno >= 20 AND emp_ptn.deptno <= 40;
### 2.5 LIMIT
```sql
-- 查询薪资最高的5名员工
-- 查询薪资最高的 5 名员工
SELECT * FROM emp ORDER BY sal DESC LIMIT 5;
```
@ -150,7 +150,7 @@ SELECT * FROM emp ORDER BY sal DESC LIMIT 5;
### 2.6 GROUP BY
Hive支持使用GROUP BY进行分组聚合操作。
Hive 支持使用 GROUP BY 进行分组聚合操作。
```sql
set hive.map.aggr=true;
@ -159,20 +159,20 @@ set hive.map.aggr=true;
SELECT deptno,SUM(sal) FROM emp GROUP BY deptno;
```
`hive.map.aggr`控制程序如何进行聚合。默认值为false。如果设置为trueHive会在map阶段就执行一次聚合。这可以提高聚合效率但需要消耗更多内存。
`hive.map.aggr` 控制程序如何进行聚合。默认值为 false。如果设置为 trueHive 会在 map 阶段就执行一次聚合。这可以提高聚合效率,但需要消耗更多内存。
### 2.7 ORDER AND SORT
可以使用ORDER BY或者Sort BY对查询结果进行排序排序字段可以是整型也可以是字符串如果是整型则按照大小排序如果是字符串则按照字典序排序。ORDER BY 和 SORT BY 的区别如下:
可以使用 ORDER BY 或者 Sort BY 对查询结果进行排序排序字段可以是整型也可以是字符串如果是整型则按照大小排序如果是字符串则按照字典序排序。ORDER BY 和 SORT BY 的区别如下:
+ 使用ORDER BY时会有一个Reducer对全部查询结果进行排序可以保证数据的全局有序性
+ 使用SORT BY时只会在每个Reducer中进行排序这可以保证每个Reducer的输出数据是有序的但不能保证全局有序。
+ 使用 ORDER BY 时会有一个 Reducer 对全部查询结果进行排序,可以保证数据的全局有序性;
+ 使用 SORT BY 时只会在每个 Reducer 中进行排序,这可以保证每个 Reducer 的输出数据是有序的,但不能保证全局有序。
由于ORDER BY的时间可能很长如果你设置了严格模式(hive.mapred.mode = strict),则其后面必须再跟一个`limit`子句。
由于 ORDER BY 的时间可能很长,如果你设置了严格模式 (hive.mapred.mode = strict),则其后面必须再跟一个 `limit` 子句。
> hive.mapred.mode默认值是nonstrict ,也就是非严格模式。
> hive.mapred.mode 默认值是 nonstrict ,也就是非严格模式。
```sql
-- 查询员工工资,结果按照部门升序,按照工资降序排列
@ -183,10 +183,10 @@ SELECT empno, deptno, sal FROM emp ORDER BY deptno ASC, sal DESC;
### 2.8 HAVING
可以使用HAVING对分组数据进行过滤。
可以使用 HAVING 对分组数据进行过滤。
```sql
-- 查询工资总和大于9000的所有部门
-- 查询工资总和大于 9000 的所有部门
SELECT deptno,SUM(sal) FROM emp GROUP BY deptno HAVING SUM(sal)>9000;
```
@ -194,11 +194,11 @@ SELECT deptno,SUM(sal) FROM emp GROUP BY deptno HAVING SUM(sal)>9000;
### 2.9 DISTRIBUTE BY
默认情况下MapReduce程序会对Map输出结果的Key值进行散列并均匀分发到所有Reducer上。如果想要把具有相同Key值的数据分发到同一个Reducer进行处理这就需要使用DISTRIBUTE BY字句。
默认情况下MapReduce 程序会对 Map 输出结果的 Key 值进行散列,并均匀分发到所有 Reducer 上。如果想要把具有相同 Key 值的数据分发到同一个 Reducer 进行处理,这就需要使用 DISTRIBUTE BY 字句。
需要注意的是DISTRIBUTE BY虽然能保证具有相同Key值的数据分发到同一个Reducer但是不能保证数据在Reducer上是有序的。情况如下
需要注意的是DISTRIBUTE BY 虽然能保证具有相同 Key 值的数据分发到同一个 Reducer但是不能保证数据在 Reducer 上是有序的。情况如下:
把以下5个数据发送到两个Reducer上进行处理
把以下 5 个数据发送到两个 Reducer 上进行处理:
```properties
k1
@ -208,7 +208,7 @@ k3
k1
```
Reducer1得到如下乱序数据
Reducer1 得到如下乱序数据:
```properties
k1
@ -217,17 +217,17 @@ k1
```
Reducer2得到数据如下
Reducer2 得到数据如下:
```properties
k4
k3
```
如果想让Reducer上的数据时有序的可以结合`SORT BY`使用(示例如下)或者使用下面我们将要介绍的CLUSTER BY。
如果想让 Reducer 上的数据时有序的,可以结合 `SORT BY` 使用 (示例如下),或者使用下面我们将要介绍的 CLUSTER BY。
```sql
-- 将数据按照部门分发到对应的Reducer上处理
-- 将数据按照部门分发到对应的 Reducer 上处理
SELECT empno, deptno, sal FROM emp DISTRIBUTE BY deptno SORT BY deptno ASC;
```
@ -235,7 +235,7 @@ SELECT empno, deptno, sal FROM emp DISTRIBUTE BY deptno SORT BY deptno ASC;
### 2.10 CLUSTER BY
如果`SORT BY``DISTRIBUTE BY`指定的是相同字段且SORT BY排序规则是ASC此时可以使用`CLUSTER BY`进行替换,同时`CLUSTER BY`可以保证数据在全局是有序的。
如果 `SORT BY``DISTRIBUTE BY` 指定的是相同字段,且 SORT BY 排序规则是 ASC此时可以使用 `CLUSTER BY` 进行替换,同时 `CLUSTER BY` 可以保证数据在全局是有序的。
```sql
SELECT empno, deptno, sal FROM emp CLUSTER BY deptno ;
@ -245,9 +245,9 @@ SELECT empno, deptno, sal FROM emp CLUSTER BY deptno ;
## 三、多表联结查询
Hive支持内连接外连接左外连接右外连接笛卡尔连接这和传统数据库中的概念是一致的可以参见下图。
Hive 支持内连接,外连接,左外连接,右外连接,笛卡尔连接,这和传统数据库中的概念是一致的,可以参见下图。
需要特别强调JOIN语句的关联条件必须用ON指定不能用WHERE指定否则就会先做笛卡尔积再过滤这会导致你得不到预期的结果(下面的演示会有说明)。
需要特别强调JOIN 语句的关联条件必须用 ON 指定,不能用 WHERE 指定,否则就会先做笛卡尔积,再过滤,这会导致你得不到预期的结果 (下面的演示会有说明)。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/sql-join.jpg"/> </div>
@ -256,7 +256,7 @@ Hive支持内连接外连接左外连接右外连接笛卡尔连接
### 3.1 INNER JOIN
```sql
-- 查询员工编号为7369的员工的详细信息
-- 查询员工编号为 7369 的员工的详细信息
SELECT e.*,d.* FROM
emp e JOIN dept d
ON e.deptno = d.deptno
@ -268,7 +268,7 @@ SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1) JOIN c ON (c.key =
### 3.2 LEFT OUTER JOIN
LEFT OUTER JOIN 和 LEFT JOIN是等价的。
LEFT OUTER JOIN 和 LEFT JOIN 是等价的。
```sql
-- 左连接
@ -286,7 +286,7 @@ FROM emp e RIGHT OUTER JOIN dept d
ON e.deptno = d.deptno;
```
执行右连接后由于40号部门下没有任何员工所以此时员工信息为NULL。这个查询可以很好的复述上面提到的——JOIN语句的关联条件必须用ON指定不能用WHERE指定。你可以把ON改成WHERE你会发现无论如何都查不出40号部门这条数据因为笛卡尔运算不会有(NULL, 40)这种情况。
执行右连接后,由于 40 号部门下没有任何员工,所以此时员工信息为 NULL。这个查询可以很好的复述上面提到的——JOIN 语句的关联条件必须用 ON 指定,不能用 WHERE 指定。你可以把 ON 改成 WHERE你会发现无论如何都查不出 40 号部门这条数据,因为笛卡尔运算不会有 (NULL, 40) 这种情况。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-right-join.png"/> </div>
@ -303,7 +303,7 @@ ON e.deptno = d.deptno;
LEFT SEMI JOIN (左半连接)是 IN/EXISTS 子查询的一种更高效的实现。
+ JOIN 子句中右边的表只能在 ON 子句中设置过滤条件;
+ 查询结果只包含左边表的数据所以只能SELECT左表中的列。
+ 查询结果只包含左边表的数据,所以只能 SELECT 左表中的列。
```sql
-- 查询在纽约办公的所有员工信息
@ -318,7 +318,7 @@ WHERE emp.deptno IN (SELECT deptno FROM dept WHERE loc="NEW YORK");
### 3.6 JOIN
笛卡尔积连接,这个连接日常的开发中可能很少遇到,且性能消耗比较大,基于这个原因,如果在严格模式下(hive.mapred.mode = strict)Hive会阻止用户执行此操作。
笛卡尔积连接,这个连接日常的开发中可能很少遇到,且性能消耗比较大,基于这个原因,如果在严格模式下 (hive.mapred.mode = strict)Hive 会阻止用户执行此操作。
```sql
SELECT * FROM emp JOIN dept;
@ -330,13 +330,13 @@ SELECT * FROM emp JOIN dept;
### 4.1 STREAMTABLE
在多表进行联结的时候如果每个ON字句都使用到共同的列如下面的`b.key`此时Hive会进行优化将多表JOIN在同一个map / reduce作业上进行。同时假定查询的最后一个表如下面的 c 表是最大的一个表在对每行记录进行JOIN操作时它将尝试将其他的表缓存起来然后扫描最后那个表进行计算。因此用户需要保证查询的表的大小从左到右是依次增加的。
在多表进行联结的时候,如果每个 ON 字句都使用到共同的列(如下面的 `b.key`),此时 Hive 会进行优化,将多表 JOIN 在同一个 map / reduce 作业上进行。同时假定查询的最后一个表(如下面的 c 表)是最大的一个表,在对每行记录进行 JOIN 操作时,它将尝试将其他的表缓存起来,然后扫描最后那个表进行计算。因此用户需要保证查询的表的大小从左到右是依次增加的。
```sql
`SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key) JOIN c ON (c.key = b.key)`
```
然后用户并非需要总是把最大的表放在查询语句的最后面Hive提供了`/*+ STREAMTABLE() */`标志,用于标识最大的表,示例如下:
然后用户并非需要总是把最大的表放在查询语句的最后面Hive 提供了 `/*+ STREAMTABLE() */` 标志,用于标识最大的表,示例如下:
```sql
SELECT /*+ STREAMTABLE(d) */ e.*,d.*
@ -349,7 +349,7 @@ WHERE job='CLERK';
### 4.2 MAPJOIN
如果所有表中只有一张表是小表那么Hive把这张小表加载到内存中。这时候程序会在map阶段直接拿另外一个表的数据和内存中表数据做匹配由于在map就进行了JOIN操作从而可以省略reduce过程这样效率可以提升很多。Hive中提供了`/*+ MAPJOIN() */`来标记小表,示例如下:
如果所有表中只有一张表是小表,那么 Hive 把这张小表加载到内存中。这时候程序会在 map 阶段直接拿另外一个表的数据和内存中表数据做匹配,由于在 map 就进行了 JOIN 操作,从而可以省略 reduce 过程这样效率可以提升很多。Hive 中提供了 `/*+ MAPJOIN() */` 来标记小表,示例如下:
```sql
SELECT /*+ MAPJOIN(d) */ e.*,d.*
@ -372,20 +372,20 @@ SELECT current_database()
## 六、本地模式
在上面演示的语句中大多数都会触发MapReduce, 少部分不会触发,比如`select * from emp limit 5`就不会触发MR此时Hive只是简单的读取数据文件中的内容然后格式化后进行输出。在需要执行MapReduce的查询中你会发现执行时间可能会很长这时候你可以选择开启本地模式。
在上面演示的语句中,大多数都会触发 MapReduce, 少部分不会触发,比如 `select * from emp limit 5` 就不会触发 MR此时 Hive 只是简单的读取数据文件中的内容,然后格式化后进行输出。在需要执行 MapReduce 的查询中,你会发现执行时间可能会很长,这时候你可以选择开启本地模式。
```sql
--本地模式默认关闭,需要手动开启此功能
SET hive.exec.mode.local.auto=true;
```
启用后Hive将分析查询中每个map-reduce作业的大小如果满足以下条件则可以在本地运行它
启用后Hive 将分析查询中每个 map-reduce 作业的大小,如果满足以下条件,则可以在本地运行它:
- 作业的总输入大小低于hive.exec.mode.local.auto.inputbytes.max默认为128MB
- map-tasks的总数小于hive.exec.mode.local.auto.tasks.max默认为4
- 所需的reduce任务总数为1或0。
- 作业的总输入大小低于hive.exec.mode.local.auto.inputbytes.max默认为 128MB
- map-tasks 的总数小于hive.exec.mode.local.auto.tasks.max默认为 4
- 所需的 reduce 任务总数为 1 或 0。
因为我们测试的数据集很小所以你再次去执行上面涉及MR操作的查询你会发现速度会有显著的提升。
因为我们测试的数据集很小,所以你再次去执行上面涉及 MR 操作的查询,你会发现速度会有显著的提升。

View File

@ -15,14 +15,14 @@
## 一、简介
Hive是一个构建在Hadoop之上的数据仓库它可以将结构化的数据文件映射成表并提供类SQL查询功能用于查询的SQL语句会被转化为MapReduce作业然后提交到Hadoop上运行。
Hive 是一个构建在 Hadoop 之上的数据仓库,它可以将结构化的数据文件映射成表,并提供类 SQL 查询功能,用于查询的 SQL 语句会被转化为 MapReduce 作业,然后提交到 Hadoop 上运行。
**特点**
1. 简单、容易上手(提供了类似sql的查询语言hql)使得精通sql但是不了解Java编程的人也能很好地进行大数据分析
3. 灵活性高,可以自定义用户函数(UDF)和存储格式;
1. 简单、容易上手 (提供了类似 sql 的查询语言 hql),使得精通 sql 但是不了解 Java 编程的人也能很好地进行大数据分析;
3. 灵活性高,可以自定义用户函数 (UDF) 和存储格式;
4. 为超大的数据集设计的计算和存储能力,集群扩展容易;
5. 统一的元数据管理可与prestoimpalasparksql等共享数据
5. 统一的元数据管理,可与 prestoimpalasparksql 等共享数据;
5. 执行延迟高,不适合做数据的实时处理,但适合做海量数据的离线处理。
@ -33,29 +33,29 @@ Hive是一个构建在Hadoop之上的数据仓库它可以将结构化的数
### 2.1 command-line shell & thrift/jdbc
可以用command-line shellthriftjdbc两种方式来操作数据
可以用 command-line shellthriftjdbc 两种方式来操作数据:
+ **command-line shell**通过hive命令行的的方式来操作数据
+ **thriftjdbc**通过thrift协议按照标准的JDBC的方式操作数据。
+ **command-line shell**:通过 hive 命令行的的方式来操作数据;
+ **thriftjdbc**:通过 thrift 协议按照标准的 JDBC 的方式操作数据。
### 2.2 Metastore
在Hive中表名、表结构、字段名、字段类型、表的分隔符等统一被称为元数据。所有的元数据默认存储在Hive内置的derby数据库中但由于derby只能有一个实例也就是说不能有多个命令行客户端同时访问所以在实际生产环境中通常使用MySQL代替derby。
Hive 中,表名、表结构、字段名、字段类型、表的分隔符等统一被称为元数据。所有的元数据默认存储在 Hive 内置的 derby 数据库中,但由于 derby 只能有一个实例,也就是说不能有多个命令行客户端同时访问,所以在实际生产环境中,通常使用 MySQL 代替 derby。
Hive进行的是统一的元数据管理就是说你在Hive上创建了一张表然后在prestoimpalasparksql 中都是可以直接使用的它们会从Metastore中获取统一的元数据信息同样的你在prestoimpalasparksql中创建一张表在Hive中也可以直接使用。
Hive 进行的是统一的元数据管理,就是说你在 Hive 上创建了一张表,然后在 prestoimpalasparksql 中都是可以直接使用的,它们会从 Metastore 中获取统一的元数据信息,同样的你在 prestoimpalasparksql 中创建一张表,在 Hive 中也可以直接使用。
### 2.3 HQL的执行流程
Hive在执行一条HQL的时候会经过以下步骤
Hive 在执行一条 HQL 的时候,会经过以下步骤:
1. 语法解析Antlr定义SQL的语法规则完成SQL词法语法解析将SQL转化为抽象 语法树AST Tree
2. 语义解析遍历AST Tree抽象出查询的基本组成单元QueryBlock
3. 生成逻辑执行计划遍历QueryBlock翻译为执行操作树OperatorTree
4. 优化逻辑执行计划逻辑层优化器进行OperatorTree变换合并不必要的ReduceSinkOperator减少shuffle数据量
5. 生成物理执行计划遍历OperatorTree翻译为MapReduce任务
6. 优化物理执行计划物理层优化器进行MapReduce任务的变换生成最终的执行计划。
1. 语法解析Antlr 定义 SQL 的语法规则,完成 SQL 词法,语法解析,将 SQL 转化为抽象 语法树 AST Tree
2. 语义解析:遍历 AST Tree抽象出查询的基本组成单元 QueryBlock
3. 生成逻辑执行计划:遍历 QueryBlock翻译为执行操作树 OperatorTree
4. 优化逻辑执行计划:逻辑层优化器进行 OperatorTree 变换,合并不必要的 ReduceSinkOperator减少 shuffle 数据量;
5. 生成物理执行计划:遍历 OperatorTree翻译为 MapReduce 任务;
6. 优化物理执行计划:物理层优化器进行 MapReduce 任务的变换,生成最终的执行计划。
> 关于Hive SQL的详细执行流程可以参考美团技术团队的文章[Hive SQL的编译过程](https://tech.meituan.com/2014/02/12/hive-sql-to-mapreduce.html)
> 关于 Hive SQL 的详细执行流程可以参考美团技术团队的文章:[Hive SQL 的编译过程](https://tech.meituan.com/2014/02/12/hive-sql-to-mapreduce.html)
@ -63,14 +63,14 @@ Hive在执行一条HQL的时候会经过以下步骤
### 3.1 基本数据类型
Hive表中的列支持以下基本数据类型
Hive 表中的列支持以下基本数据类型:
| 大类 | 类型 |
| --------------------------------------- | ------------------------------------------------------------ |
| **Integers整型** | TINYINT—1字节的有符号整数 <br/>SMALLINT—2字节的有符号整数<br/> INT—4字节的有符号整数<br/> BIGINT—8字节的有符号整数 |
| **Integers整型** | TINYINT—1 字节的有符号整数 <br/>SMALLINT—2 字节的有符号整数<br/> INT—4 字节的有符号整数<br/> BIGINT—8 字节的有符号整数 |
| **Boolean布尔型** | BOOLEAN—TRUE/FALSE |
| **Floating point numbers浮点型** | FLOAT— 单精度浮点型 <br/>DOUBLE—双精度浮点型 |
| **Fixed point numbers定点数** | DECIMAL—用户自定义精度定点数比如DECIMAL(7,2) |
| **Fixed point numbers定点数** | DECIMAL—用户自定义精度定点数比如 DECIMAL(7,2) |
| **String types字符串** | STRING—指定字符集的字符序列<br/> VARCHAR—具有最大长度限制的字符序列 <br/>CHAR—固定长度的字符序列 |
| **Date and time types日期时间类型** | TIMESTAMP — 时间戳 <br/>TIMESTAMP WITH LOCAL TIME ZONE — 时间戳,纳秒精度<br/> DATE—日期类型 |
| **Binary types二进制类型** | BINARY—字节序列 |
@ -82,7 +82,7 @@ Hive表中的列支持以下基本数据类型
### 3.2 隐式转换
Hive中基本数据类型遵循以下的层次结构按照这个层次结构子类型到祖先类型允许隐式转换。例如INT类型的数据允许隐式转换为BIGINT类型。额外注意的是按照类型层次结构允许将STRING类型隐式转换为DOUBLE类型。
Hive 中基本数据类型遵循以下的层次结构,按照这个层次结构,子类型到祖先类型允许隐式转换。例如 INT 类型的数据允许隐式转换为 BIGINT 类型。额外注意的是:按照类型层次结构允许将 STRING 类型隐式转换为 DOUBLE 类型。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-data-type.png"/> </div>
@ -92,9 +92,9 @@ Hive中基本数据类型遵循以下的层次结构按照这个层次结构
| 类型 | 描述 | 示例 |
| ---------- | ------------------------------------------------------------ | -------------------------------------- |
| **STRUCT** | 类似于对象,是字段的集合,字段的类型可以不同,可以使用 `名称.字段名`方式进行访问 | STRUCT ('xiaoming', 12 , '2018-12-12') |
| **MAP** | 键值对的集合,可以使用`名称[key]`的方式访问对应的值 | map('a', 1, 'b', 2) |
| **ARRAY** | 数组是一组具有相同类型和名称的变量的集合,可以使用`名称[index]`访问对应的值 | ARRAY('a', 'b', 'c', 'd') |
| **STRUCT** | 类似于对象,是字段的集合,字段的类型可以不同,可以使用 ` 名称.字段名 ` 方式进行访问 | STRUCT ('xiaoming', 12 , '2018-12-12') |
| **MAP** | 键值对的集合,可以使用 ` 名称[key]` 的方式访问对应的值 | map('a', 1, 'b', 2) |
| **ARRAY** | 数组是一组具有相同类型和名称的变量的集合,可以使用 ` 名称[index]` 访问对应的值 | ARRAY('a', 'b', 'c', 'd') |
@ -116,16 +116,16 @@ CREATE TABLE students(
## 四、内容格式
当数据存储在文本文件中必须按照一定格式区别行和列如使用逗号作为分隔符的CSV文件(Comma-Separated Values)或者使用制表符作为分隔值的TSV文件(Tab-Separated Values)。但此时也存在一个缺点,就是正常的文件内容中也可能出现逗号或者制表符。
当数据存储在文本文件中,必须按照一定格式区别行和列,如使用逗号作为分隔符的 CSV 文件 (Comma-Separated Values) 或者使用制表符作为分隔值的 TSV 文件 (Tab-Separated Values)。但此时也存在一个缺点,就是正常的文件内容中也可能出现逗号或者制表符。
所以Hive默认使用了几个平时很少出现的字符这些字符一般不会作为内容出现在文件中。Hive默认的行和列分隔符如下表所示。
所以 Hive 默认使用了几个平时很少出现的字符这些字符一般不会作为内容出现在文件中。Hive 默认的行和列分隔符如下表所示。
| 分隔符 | 描述 |
| --------------- | ------------------------------------------------------------ |
| **\n** | 对于文本文件来说,每行是一条记录,所以可以使用换行符来分割记录 |
| **^A (Ctrl+A)** | 分割字段(列)在CREATE TABLE语句中也可以使用八进制编码 `\001` 来表示 |
| **^B** | 用于分割 ARRAY 或者 STRUCT 中的元素,或者用于 MAP 中键值对之间的分割,<br/>在CREATE TABLE语句中也可以使用八进制编码`\002` 表示 |
| **^C** | 用于 MAP 中键和值之间的分割在CREATE TABLE语句中也可以使用八进制编码`\003` 表示 |
| **^A (Ctrl+A)** | 分割字段 (列),在 CREATE TABLE 语句中也可以使用八进制编码 `\001` 来表示 |
| **^B** | 用于分割 ARRAY 或者 STRUCT 中的元素,或者用于 MAP 中键值对之间的分割,<br/> CREATE TABLE 语句中也可以使用八进制编码 `\002` 表示 |
| **^C** | 用于 MAP 中键和值之间的分割,在 CREATE TABLE 语句中也可以使用八进制编码 `\003` 表示 |
使用示例如下:
@ -144,22 +144,22 @@ CREATE TABLE page_view(viewTime INT, userid BIGINT)
### 5.1 支持的存储格式
Hive会在HDFS为每个数据库上创建一个目录数据库中的表是该目录的子目录表中的数据会以文件的形式存储在对应的表目录下。Hive支持以下几种文件存储格式
Hive 会在 HDFS 为每个数据库上创建一个目录数据库中的表是该目录的子目录表中的数据会以文件的形式存储在对应的表目录下。Hive 支持以下几种文件存储格式:
| 格式 | 说明 |
| ---------------- | ------------------------------------------------------------ |
| **TextFile** | 存储为纯文本文件。 这是Hive默认的文件存储格式。这种存储方式数据不做压缩磁盘开销大数据解析开销大。 |
| **SequenceFile** | SequenceFileHadoop API提供的一种二进制文件它将数据以<key,value>的形式序列化到文件中。这种二进制文件内部使用Hadoop的标准的Writable 接口实现序列化和反序列化。它与Hadoop API中的MapFile 是互相兼容的。Hive中的SequenceFile 继承自Hadoop API 的SequenceFile不过它的key为空使用value存放实际的值这样是为了避免MR在运行map阶段进行额外的排序操作。 |
| **RCFile** | RCFile文件格式是FaceBook开源的一种Hive的文件存储格式首先将表分为几个行组对每个行组内的数据按列存储每一列的数据都是分开存储。 |
| **ORC Files** | ORC是在一定程度上扩展了RCFile是对RCFile的优化。 |
| **Avro Files** | Avro是一个数据序列化系统设计用于支持大批量数据交换的应用。它的主要特点有支持二进制序列化方式可以便捷快速地处理大量数据动态语言友好Avro提供的机制使动态语言可以方便地处理Avro数据。 |
| **Parquet** | Parquet是基于Dremel的数据模型和算法实现的面向分析型业务的列式存储格式。它通过按列进行高效压缩和特殊的编码技术从而在降低存储空间的同时提高了IO效率。 |
| **TextFile** | 存储为纯文本文件。 这是 Hive 默认的文件存储格式。这种存储方式数据不做压缩,磁盘开销大,数据解析开销大。 |
| **SequenceFile** | SequenceFileHadoop API 提供的一种二进制文件,它将数据以<key,value>的形式序列化到文件中。这种二进制文件内部使用 Hadoop 的标准的 Writable 接口实现序列化和反序列化。它与 Hadoop API 中的 MapFile 是互相兼容的。Hive 中的 SequenceFile 继承自 Hadoop API 的 SequenceFile不过它的 key 为空,使用 value 存放实际的值,这样是为了避免 MR 在运行 map 阶段进行额外的排序操作。 |
| **RCFile** | RCFile 文件格式是 FaceBook 开源的一种 Hive 的文件存储格式,首先将表分为几个行组,对每个行组内的数据按列存储,每一列的数据都是分开存储。 |
| **ORC Files** | ORC 是在一定程度上扩展了 RCFile是对 RCFile 的优化。 |
| **Avro Files** | Avro 是一个数据序列化系统设计用于支持大批量数据交换的应用。它的主要特点有支持二进制序列化方式可以便捷快速地处理大量数据动态语言友好Avro 提供的机制使动态语言可以方便地处理 Avro 数据。 |
| **Parquet** | Parquet 是基于 Dremel 的数据模型和算法实现的,面向分析型业务的列式存储格式。它通过按列进行高效压缩和特殊的编码技术,从而在降低存储空间的同时提高了 IO 效率。 |
> 以上压缩格式中ORCParquet的综合性能突出使用较为广泛推荐使用这两种格式。
> 以上压缩格式中 ORCParquet 的综合性能突出,使用较为广泛,推荐使用这两种格式。
### 5.2 指定存储格式
通常在创建表的时候使用`STORED AS`参数指定:
通常在创建表的时候使用 `STORED AS` 参数指定:
```sql
CREATE TABLE page_view(viewTime INT, userid BIGINT)
@ -183,12 +183,12 @@ CREATE TABLE page_view(viewTime INT, userid BIGINT)
## 六、内部表和外部表
内部表又叫做管理表(Managed/Internal Table),创建表时不做任何指定,默认创建的就是内部表。想要创建外部表(External Table)则需要使用External进行修饰。 内部表和外部表主要区别如下:
内部表又叫做管理表 (Managed/Internal Table),创建表时不做任何指定,默认创建的就是内部表。想要创建外部表 (External Table),则需要使用 External 进行修饰。 内部表和外部表主要区别如下:
| | 内部表 | 外部表 |
| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 数据存储位置 | 内部表数据存储的位置由hive.metastore.warehouse.dir参数指定默认情况下表的数据存储在HDFS`/user/hive/warehouse/数据库名.db/表名/` 目录下 | 外部表数据的存储位置创建表时由`Location`参数指定; |
| 导入数据 | 在导入数据到内部表内部表将数据移动到自己的数据仓库目录下数据的生命周期由Hive来进行管理 | 外部表不会将数据移动到自己的数据仓库目录下,只是在元数据中存储了数据的位置 |
| 数据存储位置 | 内部表数据存储的位置由 hive.metastore.warehouse.dir 参数指定,默认情况下表的数据存储在 HDFS`/user/hive/warehouse/数据库名.db/表名/` 目录下 | 外部表数据的存储位置创建表时由 `Location` 参数指定; |
| 导入数据 | 在导入数据到内部表,内部表将数据移动到自己的数据仓库目录下,数据的生命周期由 Hive 来进行管理 | 外部表不会将数据移动到自己的数据仓库目录下,只是在元数据中存储了数据的位置 |
| 删除表 | 删除元数据metadata和文件 | 只删除元数据metadata |
@ -196,7 +196,7 @@ CREATE TABLE page_view(viewTime INT, userid BIGINT)
## 参考资料
1. [Hive Getting Started](https://cwiki.apache.org/confluence/display/Hive/GettingStarted)
2. [Hive SQL的编译过程](https://tech.meituan.com/2014/02/12/hive-sql-to-mapreduce.html)
2. [Hive SQL 的编译过程](https://tech.meituan.com/2014/02/12/hive-sql-to-mapreduce.html)
3. [LanguageManual DDL](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL)
4. [LanguageManual Types](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Types)
5. [Managed vs. External Tables](https://cwiki.apache.org/confluence/display/Hive/Managed+vs.+External+Tables)

View File

@ -11,7 +11,7 @@
### 1.1 简介
Hive 中的视图和RDBMS中视图的概念一致都是一组数据的逻辑表示本质上就是一条SELECT语句的结果集。视图是纯粹的逻辑对象没有关联的存储(Hive 3.0.0引入的物化视图除外)当查询引用视图时Hive可以将视图的定义与查询结合起来例如将查询中的过滤器推送到视图中。
Hive 中的视图和 RDBMS 中视图的概念一致,都是一组数据的逻辑表示,本质上就是一条 SELECT 语句的结果集。视图是纯粹的逻辑对象,没有关联的存储 (Hive 3.0.0 引入的物化视图除外)当查询引用视图时Hive 可以将视图的定义与查询结合起来,例如将查询中的过滤器推送到视图中。
### 1.2 创建视图
@ -23,19 +23,19 @@ CREATE VIEW [IF NOT EXISTS] [db_name.]view_name -- 视图名称
AS SELECT ...;
```
在Hive中可以使用`CREATE VIEW`创建视图,如果已存在具有相同名称的表或视图,则会抛出异常,建议使用`IF NOT EXISTS`预做判断。在使用视图时候需要注意以下事项:
Hive 中可以使用 `CREATE VIEW` 创建视图,如果已存在具有相同名称的表或视图,则会抛出异常,建议使用 `IF NOT EXISTS` 预做判断。在使用视图时候需要注意以下事项:
- 视图是只读的不能用作LOAD / INSERT / ALTER的目标
- 视图是只读的,不能用作 LOAD / INSERT / ALTER 的目标;
- 在创建视图时候视图就已经固定,对基表的后续更改(如添加列)将不会反映在视图;
- 删除基表并不会删除视图,需要手动删除视图;
- 视图可能包含ORDER BYLIMIT子句。如果引用视图的查询语句也包含这类子句其执行优先级低于视图对应字句。例如视图`custom_view`指定LIMIT 5查询语句为`select * from custom_view LIMIT 10`,此时结果最多返回5行。
- 视图可能包含 ORDER BYLIMIT 子句。如果引用视图的查询语句也包含这类子句,其执行优先级低于视图对应字句。例如,视图 `custom_view` 指定 LIMIT 5查询语句为 `select * from custom_view LIMIT 10`,此时结果最多返回 5 行。
- 创建视图时如果未提供列名则将从SELECT语句中自动派生列名
- 创建视图时,如果未提供列名,则将从 SELECT 语句中自动派生列名;
- 创建视图时如果SELECT语句中包含其他表达式例如x + y则列名称将以\_C0\_C1等形式生成
- 创建视图时,如果 SELECT 语句中包含其他表达式,例如 x + y则列名称将以\_C0\_C1 等形式生成;
```sql
CREATE VIEW IF NOT EXISTS custom_view AS SELECT empno, empno+deptno , 1+2 FROM emp;
@ -105,18 +105,18 @@ ALTER VIEW custom_view SET TBLPROPERTIES ('create'='heibaiying','date'='2019-05-
### 2.1 简介
Hive0.7.0引入了索引的功能,索引的设计目标是提高表某些列的查询速度。如果没有索引,带有谓词的查询(如'WHERE table1.column = 10'会加载整个表或分区并处理所有行。但是如果column存在索引则只需要加载和处理文件的一部分。
Hive0.7.0 引入了索引的功能,索引的设计目标是提高表某些列的查询速度。如果没有索引,带有谓词的查询(如'WHERE table1.column = 10')会加载整个表或分区并处理所有行。但是如果 column 存在索引,则只需要加载和处理文件的一部分。
### 2.2 索引原理
在指定列上建立索引会产生一张索引表表结构如下里面的字段包括索引列的值、该值对应的HDFS文件路径、该值在文件中的偏移量。在查询涉及到索引字段时首先到索引表查找索引列值对应的HDFS文件路径及偏移量这样就避免了全表扫描。
在指定列上建立索引,会产生一张索引表(表结构如下),里面的字段包括:索引列的值、该值对应的 HDFS 文件路径、该值在文件中的偏移量。在查询涉及到索引字段时,首先到索引表查找索引列值对应的 HDFS 文件路径及偏移量,这样就避免了全表扫描。
```properties
+--------------+----------------+----------+--+
| col_name | data_type | comment |
+--------------+----------------+----------+--+
| empno | int | 建立索引的列 |
| _bucketname | string | HDFS文件路径 |
| _bucketname | string | HDFS 文件路径 |
| _offsets | array<bigint> | 偏移量 |
+--------------+----------------+----------+--+
```
@ -162,7 +162,7 @@ DROP INDEX [IF EXISTS] index_name ON table_name;
ALTER INDEX index_name ON table_name [PARTITION partition_spec] REBUILD;
```
重建索引。如果指定了PARTITION则仅重建该分区的索引。
重建索引。如果指定了 PARTITION则仅重建该分区的索引。
@ -170,7 +170,7 @@ ALTER INDEX index_name ON table_name [PARTITION partition_spec] REBUILD;
### 3.1 创建索引
在emp表上针对`empno`字段创建名为`emp_index`,索引数据存储在`emp_index_table`索引表中
emp 表上针对 `empno` 字段创建名为 `emp_index`,索引数据存储在 `emp_index_table` 索引表中
```sql
create index emp_index on table emp(empno) as
@ -187,13 +187,13 @@ in table emp_index_table ;
alter index emp_index on emp rebuild;
```
Hive会启动MapReduce作业去建立索引建立好后查看索引表数据如下。三个表字段分别代表索引列的值、该值对应的HDFS文件路径、该值在文件中的偏移量。
Hive 会启动 MapReduce 作业去建立索引,建立好后查看索引表数据如下。三个表字段分别代表:索引列的值、该值对应的 HDFS 文件路径、该值在文件中的偏移量。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-index-table.png"/> </div>
### 3.3 自动使用索引
默认情况下虽然建立了索引但是Hive在查询时候是不会自动去使用索引的需要开启相关配置。开启配置后涉及到索引列的查询就会使用索引功能去优化查询。
默认情况下,虽然建立了索引,但是 Hive 在查询时候是不会自动去使用索引的,需要开启相关配置。开启配置后,涉及到索引列的查询就会使用索引功能去优化查询。
```sql
SET hive.input.format=org.apache.hadoop.hive.ql.io.HiveInputFormat;
@ -215,14 +215,14 @@ SHOW INDEX ON emp;
## 四、索引的缺陷
索引表最主要的一个缺陷在于索引表无法自动rebuild这也就意味着如果表中有数据新增或删除则必须手动rebuild重新执行MapReduce作业生成索引表数据。
索引表最主要的一个缺陷在于:索引表无法自动 rebuild这也就意味着如果表中有数据新增或删除则必须手动 rebuild重新执行 MapReduce 作业,生成索引表数据。
同时按照[官方文档](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Indexing)的说明Hive会从3.0开始移除索引功能,主要基于以下两个原因:
同时按照[官方文档](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Indexing) 的说明Hive 会从 3.0 开始移除索引功能,主要基于以下两个原因:
- 具有自动重写的物化视图(Materialized View)可以产生与索引相似的效果Hive 2.3.0增加了对物化视图的支持在3.0之后正式引入)。
- 具有自动重写的物化视图 (Materialized View) 可以产生与索引相似的效果Hive 2.3.0 增加了对物化视图的支持,在 3.0 之后正式引入)。
- 使用列式存储文件格式ParquetORC进行存储时这些格式支持选择性扫描可以跳过不需要的文件或块。
> ORC内置的索引功能可以参阅这篇文章[Hive性能优化之ORC索引Row Group Index vs Bloom Filter Index](http://lxw1234.com/archives/2016/04/632.htm)
> ORC 内置的索引功能可以参阅这篇文章:[Hive 性能优化之 ORC 索引Row Group Index vs Bloom Filter Index](http://lxw1234.com/archives/2016/04/632.htm)
@ -232,5 +232,5 @@ SHOW INDEX ON emp;
1. [Create/Drop/Alter View](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-Create/Drop/AlterView)
2. [Materialized views](https://cwiki.apache.org/confluence/display/Hive/Materialized+views)
3. [Hive索引](http://lxw1234.com/archives/2015/05/207.htm)
3. [Hive 索引](http://lxw1234.com/archives/2015/05/207.htm)
4. [Overview of Hive Indexes](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Indexing)

View File

@ -19,7 +19,7 @@
## 一、消费者和消费者群组
在Kafka中消费者通常是消费者群组的一部分多个消费者群组共同读取同一个主题时彼此之间互不影响。Kafka之所以要引入消费者群组这个概念是因为Kafka消费者经常会做一些高延迟的操作比如把数据写到数据库或HDFS 或者进行耗时的计算在这些情况下单个消费者无法跟上数据生成的速度。此时可以增加更多的消费者让它们分担负载分别处理部分分区的消息这就是Kafka实现横向伸缩的主要手段。
Kafka 消费者通常是消费者群组的一部分多个消费者群组共同读取同一个主题时彼此之间互不影响。Kafka 之所以要引入消费者群组这个概念是因为 Kafka 消费者经常会做一些高延迟的操作,比如把数据写到数据库或 HDFS ,或者进行耗时的计算,在这些情况下,单个消费者无法跟上数据生成的速度。此时可以增加更多的消费者,让它们分担负载,分别处理部分分区的消息,这就是 Kafka 实现横向伸缩的主要手段。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/kafka-consumer01.png"/> </div>
@ -27,42 +27,42 @@
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/kafka-consumer02.png"/> </div>
可以看到即便消费者Consumer5空闲了但是也不会去读取任何一个分区的数据这同时也提醒我们在使用时应该合理设置消费者的数量以免造成闲置和额外开销。
可以看到即便消费者 Consumer5 空闲了,但是也不会去读取任何一个分区的数据,这同时也提醒我们在使用时应该合理设置消费者的数量,以免造成闲置和额外开销。
## 二、分区再均衡
因为群组里的消费者共同读取主题的分区,所以当一个消费者被关闭或发生崩溃时,它就离开了群组,原本由它读取的分区将由群组里的其他消费者来读取。同时在主题发生变化时 比如添加了新的分区,也会发生分区与消费者的重新分配,分区的所有权从一个消费者转移到另一个消费者,这样的行为被称为再均衡。正是因为再均衡,所以消费费者群组才能保证高可用性和伸缩性。
消费者通过向群组协调器所在的broker发送心跳来维持它们和群组的从属关系以及它们对分区的所有权。只要消费者以正常的时间间隔发送心跳就被认为是活跃的说明它还在读取分区里的消息。消费者会在轮询消息或提交偏移量时发送心跳。如果消费者停止发送心跳的时间足够长会话就会过期群组协调器认为它已经死亡就会触发再均衡。
消费者通过向群组协调器所在的 broker 发送心跳来维持它们和群组的从属关系以及它们对分区的所有权。只要消费者以正常的时间间隔发送心跳,就被认为是活跃的,说明它还在读取分区里的消息。消费者会在轮询消息或提交偏移量时发送心跳。如果消费者停止发送心跳的时间足够长,会话就会过期,群组协调器认为它已经死亡,就会触发再均衡。
## 三、创建Kafka消费者
在创建消费者的时候以下以下三个选项是必选的:
- **bootstrap.servers** 指定broker的地址清单清单里不需要包含所有的broker地址生产者会从给定的broker里查找broker的信息。不过建议至少要提供两个broker的信息作为容错
- **bootstrap.servers** :指定 broker 的地址清单,清单里不需要包含所有的 broker 地址,生产者会从给定的 broker 里查找 broker 的信息。不过建议至少要提供两个 broker 的信息作为容错;
- **key.deserializer** :指定键的反序列化器;
- **value.deserializer** :指定值的反序列化器。
除此之外你还需要指明你需要想订阅的主题可以使用如下两个API :
除此之外你还需要指明你需要想订阅的主题,可以使用如下两个 API :
+ **consumer.subscribe(Collection\<String> topics)** :指明需要订阅的主题的集合;
+ **consumer.subscribe(Pattern pattern)** :使用正则来匹配需要订阅的集合。
最后只需要通过轮询API(`poll`)向服务器定时请求数据。一旦消费者订阅了主题,轮询就会处理所有的细节,包括群组协调、分区再均衡、发送心跳和获取数据,这使得开发者只需要关注从分区返回的数据,然后进行业务处理。 示例如下:
最后只需要通过轮询 API(`poll`) 向服务器定时请求数据。一旦消费者订阅了主题,轮询就会处理所有的细节,包括群组协调、分区再均衡、发送心跳和获取数据,这使得开发者只需要关注从分区返回的数据,然后进行业务处理。 示例如下:
```scala
String topic = "Hello-Kafka";
String group = "group1";
Properties props = new Properties();
props.put("bootstrap.servers", "hadoop001:9092");
/*指定分组ID*/
/*指定分组 ID*/
props.put("group.id", group);
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
/*订阅主题(s)*/
/*订阅主题 (s)*/
consumer.subscribe(Collections.singletonList(topic));
try {
@ -79,13 +79,13 @@ try {
}
```
> 本篇文章的所有示例代码可以从Github上进行下载[kafka-basis](https://github.com/heibaiying/BigData-Notes/tree/master/code/Kafka/kafka-basis)
> 本篇文章的所有示例代码可以从 Github 上进行下载:[kafka-basis](https://github.com/heibaiying/BigData-Notes/tree/master/code/Kafka/kafka-basis)
## 三、 自动提交偏移量
### 3.1 偏移量的重要性
Kafka的每一条消息都有一个偏移量属性记录了其在分区中的位置偏移量是一个单调递增的整数。消费者通过往一个叫作 `_consumer_offset` 的特殊主题发送消息,消息里包含每个分区的偏移量。 如果消费者一直处于运行状态,那么偏移量就没有
Kafka 的每一条消息都有一个偏移量属性,记录了其在分区中的位置,偏移量是一个单调递增的整数。消费者通过往一个叫作 `_consumer_offset` 的特殊主题发送消息,消息里包含每个分区的偏移量。 如果消费者一直处于运行状态,那么偏移量就没有
什么用处。不过,如果有消费者退出或者新分区加入,此时就会触发再均衡。完成再均衡之后,每个消费者可能分配到新的分区,而不是之前处理的那个。为了能够继续之前的工作,消费者需要读取每个分区最后一次提交的偏移量,然后从偏移量指定的地方继续处理。 因为这个原因,所以如果不能正确提交偏移量,就可能会导致数据丢失或者重复出现消费,比如下面情况:
+ 如果提交的偏移量小于客户端处理的最后一个消息的偏移量 ,那么处于两个偏移量之间的消息就会被重复消费;
@ -93,26 +93,26 @@ Kafka的每一条消息都有一个偏移量属性记录了其在分区中的
### 3.2 自动提交偏移量
Kafka支持自动提交和手动提交偏移量两种方式。这里先介绍比较简单的自动提交
Kafka 支持自动提交和手动提交偏移量两种方式。这里先介绍比较简单的自动提交:
只需要将消费者的`enable.auto.commit`属性配置为`true`即可完成自动提交的配置。 此时每隔固定的时间,消费者就会把`poll()`方法接收到的最大偏移量进行提交,提交间隔由`auto.commit.interval.ms`属性进行配置默认值是5s。
只需要将消费者的 `enable.auto.commit` 属性配置为 `true` 即可完成自动提交的配置。 此时每隔固定的时间,消费者就会把 `poll()` 方法接收到的最大偏移量进行提交,提交间隔由 `auto.commit.interval.ms` 属性进行配置,默认值是 5s。
使用自动提交是存在隐患的,假设我们使用默认的 5s 提交时间间隔,在最近一次提交之后的 3s 发生了再均衡,再均衡之后,消费者从最后一次提交的偏移量位置开始读取消息。这个时候偏移量已经落后了 3s ,所以在这 3s 内到达的消息会被重复处理。可以通过修改提交时间间隔来更频繁地提交偏移量减小可能出现重复消息的时间窗不过这种情况是无法完全避免的。基于这个原因Kafka也提供了手动提交偏移量的API使得用户可以更为灵活的提交偏移量。
使用自动提交是存在隐患的,假设我们使用默认的 5s 提交时间间隔,在最近一次提交之后的 3s 发生了再均衡,再均衡之后,消费者从最后一次提交的偏移量位置开始读取消息。这个时候偏移量已经落后了 3s ,所以在这 3s 内到达的消息会被重复处理。可以通过修改提交时间间隔来更频繁地提交偏移量减小可能出现重复消息的时间窗不过这种情况是无法完全避免的。基于这个原因Kafka 也提供了手动提交偏移量的 API使得用户可以更为灵活的提交偏移量。
## 四、手动提交偏移量
用户可以通过将`enable.auto.commit`设为`false`,然后手动提交偏移量。基于用户需求手动提交偏移量可以分为两大类:
用户可以通过将 `enable.auto.commit` 设为 `false`,然后手动提交偏移量。基于用户需求手动提交偏移量可以分为两大类:
+ 手动提交当前偏移量:即手动提交当前轮询的最大偏移量;
+ 手动提交固定偏移量:即按照业务需求,提交某一个固定的偏移量。
而按照Kafka API手动提交偏移量又可以分为同步提交和异步提交。
而按照 Kafka API手动提交偏移量又可以分为同步提交和异步提交。
### 4.1 同步提交
通过调用`consumer.commitSync()`来进行同步提交,不传递任何参数时提交的是当前轮询的最大偏移量。
通过调用 `consumer.commitSync()` 来进行同步提交,不传递任何参数时提交的是当前轮询的最大偏移量。
```java
while (true) {
@ -125,11 +125,11 @@ while (true) {
}
```
如果某个提交失败同步提交还会进行重试这可以保证数据能够最大限度提交成功但是同时也会降低程序的吞吐量。基于这个原因Kafka还提供了异步提交的API。
如果某个提交失败同步提交还会进行重试这可以保证数据能够最大限度提交成功但是同时也会降低程序的吞吐量。基于这个原因Kafka 还提供了异步提交的 API。
### 4.2 异步提交
异步提交可以提高程序的吞吐量因为此时你可以尽管请求数据而不用等待Broker的响应。代码如下
异步提交可以提高程序的吞吐量,因为此时你可以尽管请求数据,而不用等待 Broker 的响应。代码如下:
```java
while (true) {
@ -151,7 +151,7 @@ while (true) {
}
```
异步提交存在的问题是在提交失败的时候不会进行自动重试实际上也不能进行自动重试。假设程序同时提交了200300的偏移量此时200的偏移量失败的但是紧随其后的300的偏移量成功了此时如果重试就会存在200覆盖300偏移量的可能。同步提交就不存在这个问题因为在同步提交的情况下300的提交请求必须等待服务器返回200提交请求的成功反馈后才会发出。基于这个原因某些情况下需要同时组合同步和异步两种提交方式。
异步提交存在的问题是,在提交失败的时候不会进行自动重试,实际上也不能进行自动重试。假设程序同时提交了 200300 的偏移量,此时 200 的偏移量失败的,但是紧随其后的 300 的偏移量成功了,此时如果重试就会存在 200 覆盖 300 偏移量的可能。同步提交就不存在这个问题因为在同步提交的情况下300 的提交请求必须等待服务器返回 200 提交请求的成功反馈后才会发出。基于这个原因,某些情况下,需要同时组合同步和异步两种提交方式。
> 注:虽然程序不能在失败时候进行自动重试,但是我们是可以手动进行重试的,你可以通过一个 Map<TopicPartition, Integer> offsets 来维护你提交的每个分区的偏移量,然后当失败时候,你可以判断失败的偏移量是否小于你维护的同主题同分区的最后提交的偏移量,如果小于则代表你已经提交了更大的偏移量请求,此时不需要重试,否则就可以进行手动重试。
@ -183,7 +183,7 @@ try {
### 4.4 提交特定偏移量
在上面同步和异步提交的API中实际上我们都没有对commit方法传递参数此时默认提交的是当前轮询的最大偏移量如果你需要提交特定的偏移量可以调用它们的重载方法。
在上面同步和异步提交的 API 中,实际上我们都没有对 commit 方法传递参数,此时默认提交的是当前轮询的最大偏移量,如果你需要提交特定的偏移量,可以调用它们的重载方法。
```java
/*同步提交特定偏移量*/
@ -192,7 +192,7 @@ commitSync(Map<TopicPartition, OffsetAndMetadata> offsets)
commitAsync(Map<TopicPartition, OffsetAndMetadata> offsets, OffsetCommitCallback callback)
```
需要注意的是,因为你可以订阅多个主题,所以`offsets`中必须要包含所有主题的每个分区的偏移量,示例代码如下:
需要注意的是,因为你可以订阅多个主题,所以 `offsets` 中必须要包含所有主题的每个分区的偏移量,示例代码如下:
```java
try {
@ -203,7 +203,7 @@ try {
/*记录每个主题的每个分区的偏移量*/
TopicPartition topicPartition = new TopicPartition(record.topic(), record.partition());
OffsetAndMetadata offsetAndMetadata = new OffsetAndMetadata(record.offset()+1, "no metaData");
/*TopicPartition重写过hashCodeequals方法所以能够保证同一主题和分区的实例不会被重复添加*/
/*TopicPartition 重写过 hashCodeequals 方法,所以能够保证同一主题和分区的实例不会被重复添加*/
offsets.put(topicPartition, offsetAndMetadata);
}
/*提交特定偏移量*/
@ -218,7 +218,7 @@ try {
## 五、监听分区再均衡
因为分区再均衡会导致分区与消费者的重新划分,有时候你可能希望在再均衡前执行一些操作:比如提交已经处理但是尚未提交的偏移量,关闭数据库连接等。此时可以在订阅主题时候,调用`subscribe`的重载方法传入自定义的分区再均衡监听器。
因为分区再均衡会导致分区与消费者的重新划分,有时候你可能希望在再均衡前执行一些操作:比如提交已经处理但是尚未提交的偏移量,关闭数据库连接等。此时可以在订阅主题时候,调用 `subscribe` 的重载方法传入自定义的分区再均衡监听器。
```java
/*订阅指定集合内的所有主题*/
@ -255,7 +255,7 @@ try {
System.out.println(record);
TopicPartition topicPartition = new TopicPartition(record.topic(), record.partition());
OffsetAndMetadata offsetAndMetadata = new OffsetAndMetadata(record.offset() + 1, "no metaData");
/*TopicPartition重写过hashCodeequals方法所以能够保证同一主题和分区的实例不会被重复添加*/
/*TopicPartition 重写过 hashCodeequals 方法,所以能够保证同一主题和分区的实例不会被重复添加*/
offsets.put(topicPartition, offsetAndMetadata);
}
consumer.commitAsync(offsets, null);
@ -269,12 +269,12 @@ try {
## 六 、退出轮询
Kafka提供了`consumer.wakeup()`方法用于退出轮询,它通过抛出`WakeupException`异常来跳出循环。需要注意的是,在退出线程时最好显示的调用`consumer.close()` , 此时消费者会提交任何还没有提交的东西,并向群组协调器发送消息,告知自己要离开群组,接下来就会触发再均衡 ,而不需要等待会话超时。
Kafka 提供了 `consumer.wakeup()` 方法用于退出轮询,它通过抛出 `WakeupException` 异常来跳出循环。需要注意的是,在退出线程时最好显示的调用 `consumer.close()` , 此时消费者会提交任何还没有提交的东西,并向群组协调器发送消息,告知自己要离开群组,接下来就会触发再均衡 ,而不需要等待会话超时。
下面的示例代码为监听控制台输出,当输入`exit`时结束轮询,关闭消费者并退出程序:
下面的示例代码为监听控制台输出,当输入 `exit` 时结束轮询,关闭消费者并退出程序:
```java
/*调用wakeup优雅的退出*/
/*调用 wakeup 优雅的退出*/
final Thread mainThread = Thread.currentThread();
new Thread(() -> {
Scanner sc = new Scanner(System.in);
@ -301,10 +301,10 @@ try {
}
}
} catch (WakeupException e) {
//对于wakeup()调用引起的WakeupException异常可以不必处理
//对于 wakeup() 调用引起的 WakeupException 异常可以不必处理
} finally {
consumer.close();
System.out.println("consumer关闭");
System.out.println("consumer 关闭");
}
```
@ -312,7 +312,7 @@ try {
## 七、独立的消费者
因为Kafka的设计目标是高吞吐和低延迟所以在Kafka中消费者通常都是从属于某个群组的这是因为单个消费者的处理能力是有限的。但是某些时候你的需求可能很简单比如可能只需要一个消费者从一个主题的所有分区或者某个特定的分区读取数据这个时候就不需要消费者群组和再均衡了 只需要把主题或者分区分配给消费者,然后开始读取消息井提交偏移量即可。
因为 Kafka 的设计目标是高吞吐和低延迟,所以在 Kafka 中,消费者通常都是从属于某个群组的,这是因为单个消费者的处理能力是有限的。但是某些时候你的需求可能很简单,比如可能只需要一个消费者从一个主题的所有分区或者某个特定的分区读取数据,这个时候就不需要消费者群组和再均衡了, 只需要把主题或者分区分配给消费者,然后开始读取消息井提交偏移量即可。
在这种情况下,就不需要订阅主题, 取而代之的是消费者为自己分配分区。 一个消费者可以订阅主题(井加入消费者群组),或者为自己分配分区,但不能同时做这两件事情。 分配分区的示例代码如下:
@ -320,7 +320,7 @@ try {
List<TopicPartition> partitions = new ArrayList<>();
List<PartitionInfo> partitionInfos = consumer.partitionsFor(topic);
/*可以指定读取哪些分区 如这里假设只读取主题的0分区*/
/*可以指定读取哪些分区 如这里假设只读取主题的 0 分区*/
for (PartitionInfo partition : partitionInfos) {
if (partition.partition()==0){
partitions.add(new TopicPartition(partition.topic(), partition.partition()));
@ -347,19 +347,19 @@ while (true) {
### 1. fetch.min.byte
消费者从服务器获取记录的最小字节数。如果可用的数据量小于设置值broker会等待有足够的可用数据时才会把它返回给消费者。
消费者从服务器获取记录的最小字节数。如果可用的数据量小于设置值broker 会等待有足够的可用数据时才会把它返回给消费者。
### 2. fetch.max.wait.ms
broker返回给消费者数据的等待时间默认是500ms。
broker 返回给消费者数据的等待时间,默认是 500ms。
### 3. max.partition.fetch.bytes
该属性指定了服务器从每个分区返回给消费者的最大字节数默认为1MB。
该属性指定了服务器从每个分区返回给消费者的最大字节数,默认为 1MB。
### 4. session.timeout.ms
消费者在被认为死亡之前可以与服务器断开连接的时间默认是3s。
消费者在被认为死亡之前可以与服务器断开连接的时间,默认是 3s。
### 5. auto.offset.reset
@ -370,23 +370,23 @@ broker返回给消费者数据的等待时间默认是500ms。
### 6. enable.auto.commit
是否自动提交偏移量默认值是true。为了避免出现重复消费和数据丢失可以把它设置为false。
是否自动提交偏移量,默认值是 true。为了避免出现重复消费和数据丢失可以把它设置为 false。
### 7. client.id
客户端id服务器用来识别消息的来源。
客户端 id服务器用来识别消息的来源。
### 8. max.poll.records
单次调用`poll()`方法能够返回的记录数量。
单次调用 `poll()` 方法能够返回的记录数量。
### 9. receive.buffer.bytes & send.buffer.byte
这两个参数分别指定TCP socket 接收和发送数据包缓冲区的大小,-1代表使用操作系统的默认值。
这两个参数分别指定 TCP socket 接收和发送数据包缓冲区的大小,-1 代表使用操作系统的默认值。
## 参考资料
1. Neha Narkhede, Gwen Shapira ,Todd Palino(著) , 薛命灯(译) . Kafka权威指南 . 人民邮电出版社 . 2017-12-26
1. Neha Narkhede, Gwen Shapira ,Todd Palino(著) , 薛命灯 (译) . Kafka 权威指南 . 人民邮电出版社 . 2017-12-26

View File

@ -22,46 +22,46 @@
## 一、Kafka集群
Kafka使用Zookeeper来维护集群成员(brokers)的信息。每个broker都有一个唯一标识`broker.id`,用于标识自己在集群中的身份,可以在配置文件`server.properties`中进行配置或者由程序自动生成。下面是Kafka brokers集群自动创建的过程
Kafka 使用 Zookeeper 来维护集群成员 (brokers) 的信息。每个 broker 都有一个唯一标识 `broker.id`,用于标识自己在集群中的身份,可以在配置文件 `server.properties` 中进行配置,或者由程序自动生成。下面是 Kafka brokers 集群自动创建的过程:
+ 每一个broker启动的时候它会在Zookeeper`/brokers/ids`路径下创建一个`临时节点`,并将自己的`broker.id`写入,从而将自身注册到集群;
+ 当有多个broker时所有broker会竞争性地在Zookeeper上创建`/controller`节点由于Zookeeper上的节点不会重复所以必然只会有一个broker创建成功此时该broker称为controller broker。它除了具备其他broker的功能外**还负责管理主题分区及其副本的状态**。
+ 当broker出现宕机或者主动退出从而导致其持有的Zookeeper会话超时时会触发注册在Zookeeper上的watcher事件此时Kafka会进行相应的容错处理如果宕机的是controller broker时还会触发新的controller选举。
+ 每一个 broker 启动的时候,它会在 Zookeeper`/brokers/ids` 路径下创建一个 ` 临时节点 `,并将自己的 `broker.id` 写入,从而将自身注册到集群;
+ 当有多个 broker 时,所有 broker 会竞争性地在 Zookeeper 上创建 `/controller` 节点,由于 Zookeeper 上的节点不会重复,所以必然只会有一个 broker 创建成功,此时该 broker 称为 controller broker。它除了具备其他 broker 的功能外,**还负责管理主题分区及其副本的状态**。
+ 当 broker 出现宕机或者主动退出从而导致其持有的 Zookeeper 会话超时时,会触发注册在 Zookeeper 上的 watcher 事件,此时 Kafka 会进行相应的容错处理;如果宕机的是 controller broker 时,还会触发新的 controller 选举。
## 二、副本机制
为了保证高可用kafka的分区是多副本的如果一个副本丢失了那么还可以从其他副本中获取分区数据。但是这要求对应副本的数据必须是完整的这是Kafka数据一致性的基础所以才需要使用`controller broker`来进行专门的管理。下面将详解介绍Kafka的副本机制。
为了保证高可用kafka 的分区是多副本的,如果一个副本丢失了,那么还可以从其他副本中获取分区数据。但是这要求对应副本的数据必须是完整的,这是 Kafka 数据一致性的基础,所以才需要使用 `controller broker` 来进行专门的管理。下面将详解介绍 Kafka 的副本机制。
### 2.1 分区和副本
Kafka 的主题被分为多个分区 分区是Kafka最基本的存储单位。每个分区可以有多个副本(可以在创建主题时使用` replication-factor`参数进行指定)。其中一个副本是首领副本(Leader replica),所有的事件都直接发送给首领副本;其他副本是跟随者副本(Follower replica),需要通过复制来保持与首领副本数据一致,当首领副本不可用时,其中一个跟随者副本将成为新首领。
Kafka 的主题被分为多个分区 ,分区是 Kafka 最基本的存储单位。每个分区可以有多个副本 (可以在创建主题时使用 ` replication-factor` 参数进行指定)。其中一个副本是首领副本 (Leader replica),所有的事件都直接发送给首领副本;其他副本是跟随者副本 (Follower replica),需要通过复制来保持与首领副本数据一致,当首领副本不可用时,其中一个跟随者副本将成为新首领。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/kafka-cluster.png"/> </div>
### 2.2 ISR机制
每个分区都有一个ISR(in-sync Replica)列表,用于维护所有同步的、可用的副本。首领副本必然是同步副本,而对于跟随者副本来说,它需要满足以下条件才能被认为是同步副本:
每个分区都有一个 ISR(in-sync Replica) 列表,用于维护所有同步的、可用的副本。首领副本必然是同步副本,而对于跟随者副本来说,它需要满足以下条件才能被认为是同步副本:
+ 与Zookeeper之间有一个活跃的会话即必须定时向Zookeeper发送心跳
+ 与 Zookeeper 之间有一个活跃的会话,即必须定时向 Zookeeper 发送心跳;
+ 在规定的时间内从首领副本那里低延迟地获取过消息。
如果副本不满足上面条件的话就会被从ISR列表中移除直到满足条件才会被再次加入。
如果副本不满足上面条件的话,就会被从 ISR 列表中移除,直到满足条件才会被再次加入。
这里给出一个主题创建的示例:使用`--replication-factor`指定副本系数为3创建成功后使用`--describe `命令可以看到分区0的有0,1,2三个副本且三个副本都在ISR列表中其中1为首领副本。
这里给出一个主题创建的示例:使用 `--replication-factor` 指定副本系数为 3创建成功后使用 `--describe ` 命令可以看到分区 0 的有 0,1,2 三个副本,且三个副本都在 ISR 列表中,其中 1 为首领副本。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/kafka-分区副本.png"/> </div>
### 2.3 不完全的首领选举
对于副本机制在broker级别有一个可选的配置参数`unclean.leader.election.enable`默认值为fasle代表禁止不完全的首领选举。这是针对当首领副本挂掉且ISR中没有其他可用副本时是否允许某个不完全同步的副本成为首领副本这可能会导致数据丢失或者数据不一致在某些对数据一致性要求较高的场景(如金融领域)这可能无法容忍的所以其默认值为false如果你能够允许部分数据不一致的话可以配置为true。
对于副本机制,在 broker 级别有一个可选的配置参数 `unclean.leader.election.enable`,默认值为 fasle代表禁止不完全的首领选举。这是针对当首领副本挂掉且 ISR 中没有其他可用副本时,是否允许某个不完全同步的副本成为首领副本,这可能会导致数据丢失或者数据不一致,在某些对数据一致性要求较高的场景 (如金融领域),这可能无法容忍的,所以其默认值为 false如果你能够允许部分数据不一致的话可以配置为 true。
### 2.4 最少同步副本
ISR机制的另外一个相关参数是`min.insync.replicas` , 可以在broker或者主题级别进行配置代表ISR列表中至少要有几个可用副本。这里假设设置为2那么当可用副本数量小于该值时就认为整个分区处于不可用状态。此时客户端再向分区写入数据时候就会抛出异常`org.apache.kafka.common.errors.NotEnoughReplicasExceptoin: Messages are rejected since there are fewer in-sync replicas than required。`
ISR 机制的另外一个相关参数是 `min.insync.replicas` , 可以在 broker 或者主题级别进行配置,代表 ISR 列表中至少要有几个可用副本。这里假设设置为 2那么当可用副本数量小于该值时就认为整个分区处于不可用状态。此时客户端再向分区写入数据时候就会抛出异常 `org.apache.kafka.common.errors.NotEnoughReplicasExceptoin: Messages are rejected since there are fewer in-sync replicas than required。`
### 2.5 发送确认
Kafka在生产者上有一个可选的参数ack该参数指定了必须要有多少个分区副本收到消息生产者才会认为消息写入成功
Kafka 在生产者上有一个可选的参数 ack该参数指定了必须要有多少个分区副本收到消息生产者才会认为消息写入成功
- **acks=0** :消息发送出去就认为已经成功了,不会等待任何来自服务器的响应;
- **acks=1** 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应;
@ -71,44 +71,44 @@ Kafka在生产者上有一个可选的参数ack该参数指定了必须要有
### 3.1 元数据请求机制
在所有副本中只有领导副本才能进行消息的读写处理。由于不同分区的领导副本可能在不同的broker上如果某个broker收到了一个分区请求但是该分区的领导副本并不在该broker上那么它就会向客户端返回一个`Not a Leader for Partition`的错误响应。 为了解决这个问题Kafka提供了元数据请求机制。
在所有副本中,只有领导副本才能进行消息的读写处理。由于不同分区的领导副本可能在不同的 broker 上,如果某个 broker 收到了一个分区请求,但是该分区的领导副本并不在该 broker 上,那么它就会向客户端返回一个 `Not a Leader for Partition` 的错误响应。 为了解决这个问题Kafka 提供了元数据请求机制。
首先集群中的每个broker都会缓存所有主题的分区副本信息客户端会定期发送发送元数据请求然后将获取的元数据进行缓存。定时刷新元数据的时间间隔可以通过为客户端配置`metadata.max.age.ms`来进行指定。有了元数据信息后客户端就知道了领导副本所在的broker之后直接将读写请求发送给对应的broker即可。
首先集群中的每个 broker 都会缓存所有主题的分区副本信息,客户端会定期发送发送元数据请求,然后将获取的元数据进行缓存。定时刷新元数据的时间间隔可以通过为客户端配置 `metadata.max.age.ms` 来进行指定。有了元数据信息后,客户端就知道了领导副本所在的 broker之后直接将读写请求发送给对应的 broker 即可。
如果在定时请求的时间间隔内发生的分区副本的选举,则意味着原来缓存的信息可能已经过时了,此时还有可能会收到`Not a Leader for Partition`的错误响应这种情况下客户端会再次求发出元数据请求然后刷新本地缓存之后再去正确的broker上执行对应的操作过程如下图
如果在定时请求的时间间隔内发生的分区副本的选举,则意味着原来缓存的信息可能已经过时了,此时还有可能会收到 `Not a Leader for Partition` 的错误响应,这种情况下客户端会再次求发出元数据请求,然后刷新本地缓存,之后再去正确的 broker 上执行对应的操作,过程如下图:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/kafka-元数据请求.png"/> </div>
### 3.2 数据可见性
需要注意的是,并不是所有保存在分区首领上的数据都可以被客户端读取到,为了保证数据一致性,只有被所有同步副本(ISR中所有副本)都保存了的数据才能被客户端读取到。
需要注意的是,并不是所有保存在分区首领上的数据都可以被客户端读取到,为了保证数据一致性,只有被所有同步副本 (ISR 中所有副本) 都保存了的数据才能被客户端读取到。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/kafka-数据可见性.png"/> </div>
### 3.3 零拷贝
Kafka所有数据的写入和读取都是通过零拷贝来实现的。传统拷贝与零拷贝的区别如下
Kafka 所有数据的写入和读取都是通过零拷贝来实现的。传统拷贝与零拷贝的区别如下:
#### 传统模式下的四次拷贝与四次上下文切换
以将磁盘文件通过网络发送为例。传统模式下一般使用如下伪代码所示的方法先将文件数据读入内存然后通过Socket将内存中的数据发送出去。
以将磁盘文件通过网络发送为例。传统模式下,一般使用如下伪代码所示的方法先将文件数据读入内存,然后通过 Socket 将内存中的数据发送出去。
```java
buffer = File.read
Socket.send(buffer)
```
这一过程实际上发生了四次数据拷贝。首先通过系统调用将文件数据读入到内核态BufferDMA拷贝然后应用程序将内存态Buffer数据读入到用户态BufferCPU拷贝接着用户程序通过Socket发送数据时将用户态Buffer数据拷贝到内核态BufferCPU拷贝最后通过DMA拷贝将数据拷贝到NIC Buffer。同时还伴随着四次上下文切换如下图所示
这一过程实际上发生了四次数据拷贝。首先通过系统调用将文件数据读入到内核态 BufferDMA 拷贝),然后应用程序将内存态 Buffer 数据读入到用户态 BufferCPU 拷贝),接着用户程序通过 Socket 发送数据时将用户态 Buffer 数据拷贝到内核态 BufferCPU 拷贝),最后通过 DMA 拷贝将数据拷贝到 NIC Buffer。同时还伴随着四次上下文切换如下图所示
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/kafka-BIO.png"/> </div>
#### sendfile和transferTo实现零拷贝
Linux 2.4+内核通过`sendfile`系统调用提供了零拷贝。数据通过DMA拷贝到内核态Buffer后直接通过DMA拷贝到NIC Buffer无需CPU拷贝。这也是零拷贝这一说法的来源。除了减少数据拷贝外因为整个读文件到网络发送由一个`sendfile`调用完成,整个过程只有两次上下文切换,因此大大提高了性能。零拷贝过程如下图所示:
Linux 2.4+ 内核通过 `sendfile` 系统调用,提供了零拷贝。数据通过 DMA 拷贝到内核态 Buffer 后,直接通过 DMA 拷贝到 NIC Buffer无需 CPU 拷贝。这也是零拷贝这一说法的来源。除了减少数据拷贝外,因为整个读文件到网络发送由一个 `sendfile` 调用完成,整个过程只有两次上下文切换,因此大大提高了性能。零拷贝过程如下图所示:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/kafka-零拷贝.png"/> </div>
从具体实现来看Kafka的数据传输通过TransportLayer来完成其子类`PlaintextTransportLayer``transferFrom`方法通过调用Java NIOFileChannel`transferTo`方法实现零拷贝,如下所示:
从具体实现来看Kafka 的数据传输通过 TransportLayer 来完成,其子类 `PlaintextTransportLayer``transferFrom` 方法通过调用 Java NIOFileChannel`transferTo` 方法实现零拷贝,如下所示:
```java
@Override
@ -117,19 +117,19 @@ public long transferFrom(FileChannel fileChannel, long position, long count) thr
}
```
**注:** `transferTo``transferFrom`并不保证一定能使用零拷贝。实际上是否能使用零拷贝与操作系统相关,如果操作系统提供`sendfile`这样的零拷贝系统调用,则这两个方法会通过这样的系统调用充分利用零拷贝的优势,否则并不能通过这两个方法本身实现零拷贝。
**注:** `transferTo``transferFrom` 并不保证一定能使用零拷贝。实际上是否能使用零拷贝与操作系统相关,如果操作系统提供 `sendfile` 这样的零拷贝系统调用,则这两个方法会通过这样的系统调用充分利用零拷贝的优势,否则并不能通过这两个方法本身实现零拷贝。
## 四、物理存储
### 4.1 分区分配
在创建主题时Kafka会首先决定如何在broker间分配分区副本它遵循以下原则
在创建主题时Kafka 会首先决定如何在 broker 间分配分区副本,它遵循以下原则:
+ 在所有broker上均匀地分配分区副本
+ 确保分区的每个副本分布在不同的broker上
+ 如果使用了`broker.rack`参数为broker指定了机架信息那么会尽可能的把每个分区的副本分配到不同机架的broker上以避免一个机架不可用而导致整个分区不可用。
+ 在所有 broker 上均匀地分配分区副本;
+ 确保分区的每个副本分布在不同的 broker 上;
+ 如果使用了 `broker.rack` 参数为 broker 指定了机架信息,那么会尽可能的把每个分区的副本分配到不同机架的 broker 上,以避免一个机架不可用而导致整个分区不可用。
基于以上原因,如果你在一个单节点上创建一个3副本的主题,通常会抛出下面的异常:
基于以上原因,如果你在一个单节点上创建一个 3 副本的主题,通常会抛出下面的异常:
```properties
Error while executing topic command : org.apache.kafka.common.errors.InvalidReplicationFactor
@ -138,18 +138,18 @@ Exception: Replication factor: 3 larger than available brokers: 1.
### 4.2 分区数据保留规则
保留数据是 Kafka 的一个基本特性, 但是Kafka不会一直保留数据也不会等到所有消费者都读取了消息之后才删除消息。相反 Kafka为每个主题配置了数据保留期限规定数据被删除之前可以保留多长时间或者清理数据之前可以保留的数据量大小。分别对应以下四个参数
保留数据是 Kafka 的一个基本特性, 但是 Kafka 不会一直保留数据,也不会等到所有消费者都读取了消息之后才删除消息。相反, Kafka 为每个主题配置了数据保留期限,规定数据被删除之前可以保留多长时间,或者清理数据之前可以保留的数据量大小。分别对应以下四个参数:
- `log.retention.bytes` :删除数据前允许的最大数据量;默认值-1代表没有限制
- `log.retention.ms`:保存数据文件的毫秒数,如果未设置,则使用`log.retention.minutes`中的值默认为null
- `log.retention.minutes`:保留数据文件的分钟数,如果未设置,则使用`log.retention.hours`中的值默认为null
- `log.retention.hours`保留数据文件的小时数默认值为168也就是一周。
- `log.retention.ms`:保存数据文件的毫秒数,如果未设置,则使用 `log.retention.minutes` 中的值,默认为 null
- `log.retention.minutes`:保留数据文件的分钟数,如果未设置,则使用 `log.retention.hours` 中的值,默认为 null
- `log.retention.hours`:保留数据文件的小时数,默认值为 168也就是一周。
因为在一个大文件里查找和删除消息是很费时的也很容易出错所以Kafka把分区分成若干个片段当前正在写入数据的片段叫作活跃片段。活动片段永远不会被删除。如果按照默认值保留数据一周而且每天使用一个新片段那么你就会看到在每天使用一个新片段的同时会删除一个最老的片段所以大部分时间该分区会有7个片段存在。
因为在一个大文件里查找和删除消息是很费时的,也很容易出错,所以 Kafka 把分区分成若干个片段,当前正在写入数据的片段叫作活跃片段。活动片段永远不会被删除。如果按照默认值保留数据一周,而且每天使用一个新片段,那么你就会看到,在每天使用一个新片段的同时会删除一个最老的片段,所以大部分时间该分区会有 7 个片段存在。
### 4.3 文件格式
通常保存在磁盘上的数据格式与生产者发送过来消息格式是一样的。 如果生产者发送的是压缩过的消息,那么同一个批次的消息会被压缩在一起,被当作“包装消息”进行发送(格式如下所示) ,然后保存到磁盘上。之后消费者读取后再自己解压这个包装消息,获取每条消息的具体信息。
通常保存在磁盘上的数据格式与生产者发送过来消息格式是一样的。 如果生产者发送的是压缩过的消息,那么同一个批次的消息会被压缩在一起,被当作“包装消息”进行发送 (格式如下所示) ,然后保存到磁盘上。之后消费者读取后再自己解压这个包装消息,获取每条消息的具体信息。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/kafka-compress-message.png"/> </div>
@ -157,5 +157,5 @@ Exception: Replication factor: 3 larger than available brokers: 1.
## 参考资料
1. Neha Narkhede, Gwen Shapira ,Todd Palino(著) , 薛命灯(译) . Kafka权威指南 . 人民邮电出版社 . 2017-12-26
2. [Kafka高性能架构之道](http://www.jasongj.com/kafka/high_throughput/)
1. Neha Narkhede, Gwen Shapira ,Todd Palino(著) , 薛命灯 (译) . Kafka 权威指南 . 人民邮电出版社 . 2017-12-26
2. [Kafka 高性能架构之道](http://www.jasongj.com/kafka/high_throughput/)

View File

@ -13,11 +13,11 @@
## 一、生产者发送消息的过程
首先介绍一下Kafka生产者发送消息的过程
首先介绍一下 Kafka 生产者发送消息的过程:
+ Kafka会将发送消息包装为ProducerRecord对象 ProducerRecord对象包含了目标主题和要发送的内容同时还可以指定键和分区。在发送ProducerRecord对象前生产者会先把键和值对象序列化成字节数组这样它们才能够在网络上传输。
+ 接下来数据被传给分区器。如果之前已经在ProducerRecord对象里指定了分区那么分区器就不会再做任何事情。如果没有指定分区 那么分区器会根据ProducerRecord对象的键来选择一个分区紧接着这条记录被添加到一个记录批次里这个批次里的所有消息会被发送到相同的主题和分区上。有一个独立的线程负责把这些记录批次发送到相应的broker上。
+ 服务器在收到这些消息时会返回一个响应。如果消息成功写入Kafka就返回一个RecordMetaData对象它包含了主题和分区信息以及记录在分区里的偏移量。如果写入失败则会返回一个错误。生产者在收到错误之后会尝试重新发送消息如果达到指定的重试次数后还没有成功则直接抛出异常不再重试。
+ Kafka 会将发送消息包装为 ProducerRecord 对象, ProducerRecord 对象包含了目标主题和要发送的内容,同时还可以指定键和分区。在发送 ProducerRecord 对象前,生产者会先把键和值对象序列化成字节数组,这样它们才能够在网络上传输。
+ 接下来,数据被传给分区器。如果之前已经在 ProducerRecord 对象里指定了分区,那么分区器就不会再做任何事情。如果没有指定分区 ,那么分区器会根据 ProducerRecord 对象的键来选择一个分区,紧接着,这条记录被添加到一个记录批次里,这个批次里的所有消息会被发送到相同的主题和分区上。有一个独立的线程负责把这些记录批次发送到相应的 broker 上。
+ 服务器在收到这些消息时会返回一个响应。如果消息成功写入 Kafka就返回一个 RecordMetaData 对象,它包含了主题和分区信息,以及记录在分区里的偏移量。如果写入失败,则会返回一个错误。生产者在收到错误之后会尝试重新发送消息,如果达到指定的重试次数后还没有成功,则直接抛出异常,不再重试。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/kafka-send-messgaes.png"/> </div>
@ -25,7 +25,7 @@
### 2.1 项目依赖
本项目采用Maven构建想要调用Kafka生产者API需要导入`kafka-clients`依赖,如下:
本项目采用 Maven 构建,想要调用 Kafka 生产者 API需要导入 `kafka-clients` 依赖,如下:
```xml
<dependency>
@ -37,9 +37,9 @@
### 2.2 创建生产者
创建Kafka生产者时以下三个属性是必须指定的
创建 Kafka 生产者时,以下三个属性是必须指定的:
+ **bootstrap.servers** 指定broker的地址清单清单里不需要包含所有的broker地址生产者会从给定的broker里查找broker的信息。不过建议至少要提供两个broker的信息作为容错
+ **bootstrap.servers** :指定 broker 的地址清单,清单里不需要包含所有的 broker 地址,生产者会从给定的 broker 里查找 broker 的信息。不过建议至少要提供两个 broker 的信息作为容错;
+ **key.serializer** :指定键的序列化器;
+ **value.serializer** :指定值的序列化器。
@ -71,13 +71,13 @@ public class SimpleProducer {
}
```
> 本篇文章的所有示例代码可以从Github上进行下载[kafka-basis](https://github.com/heibaiying/BigData-Notes/tree/master/code/Kafka/kafka-basis)
> 本篇文章的所有示例代码可以从 Github 上进行下载:[kafka-basis](https://github.com/heibaiying/BigData-Notes/tree/master/code/Kafka/kafka-basis)
### 2.3 测试
#### 1. 启动Kakfa
Kafka的运行依赖于zookeeper需要预先启动可以启动Kafka内置的zookeeper也可以启动自己安装的
Kafka 的运行依赖于 zookeeper需要预先启动可以启动 Kafka 内置的 zookeeper也可以启动自己安装的
```shell
# zookeeper启动命令
@ -87,7 +87,7 @@ bin/zkServer.sh start
bin/zookeeper-server-start.sh config/zookeeper.properties
```
启动单节点kafka用于测试
启动单节点 kafka 用于测试:
```shell
# bin/kafka-server-start.sh config/server.properties
@ -116,7 +116,7 @@ bin/kafka-topics.sh --create \
#### 4. 运行项目
此时可以看到消费者控制台,输出如下,这里`kafka-console-consumer`只会打印出值信息,不会打印出键信息。
此时可以看到消费者控制台,输出如下,这里 `kafka-console-consumer` 只会打印出值信息,不会打印出键信息。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/kafka-simple-producer.png"/> </div>
@ -124,7 +124,7 @@ bin/kafka-topics.sh --create \
### 2.4 可能出现的问题
在这里可能出现的一个问题是生产者程序在启动后一直处于等待状态。这通常出现在你使用默认配置启动Kafka的情况下此时需要对`server.properties`文件中的`listeners`配置进行更改:
在这里可能出现的一个问题是:生产者程序在启动后,一直处于等待状态。这通常出现在你使用默认配置启动 Kafka 的情况下,此时需要对 `server.properties` 文件中的 `listeners` 配置进行更改:
```shell
# hadoop001 为我启动kafka服务的主机名你可以换成自己的主机名或者ip地址
@ -135,11 +135,11 @@ listeners=PLAINTEXT://hadoop001:9092
## 二、发送消息
上面的示例程序调用了`send`方法发送消息后没有做任何操作,在这种情况下,我们没有办法知道消息发送的结果。想要知道消息发送的结果,可以使用同步发送或者异步发送来实现。
上面的示例程序调用了 `send` 方法发送消息后没有做任何操作,在这种情况下,我们没有办法知道消息发送的结果。想要知道消息发送的结果,可以使用同步发送或者异步发送来实现。
### 2.1 同步发送
在调用`send`方法后可以接着调用`get()`方法,`send`方法的返回值是一个Future\<RecordMetadata>对象RecordMetadata里面包含了发送消息的主题、分区、偏移量等信息。改写后的代码如下
在调用 `send` 方法后可以接着调用 `get()` 方法,`send` 方法的返回值是一个 Future\<RecordMetadata>对象RecordMetadata 里面包含了发送消息的主题、分区、偏移量等信息。改写后的代码如下:
```scala
for (int i = 0; i < 10; i++) {
@ -155,7 +155,7 @@ for (int i = 0; i < 10; i++) {
}
```
此时得到的输出如下:偏移量和调用次数有关,所有记录都分配到了0分区,这是因为在创建`Hello-Kafka`主题时候,使用`--partitions`指定其分区数为1即只有一个分区。
此时得到的输出如下:偏移量和调用次数有关,所有记录都分配到了 0 分区,这是因为在创建 `Hello-Kafka` 主题时候,使用 `--partitions` 指定其分区数为 1即只有一个分区。
```shell
topic=Hello-Kafka, partition=0, offset=40
@ -172,7 +172,7 @@ topic=Hello-Kafka, partition=0, offset=49
### 2.2 异步发送
通常我们并不关心发送成功的情况更多关注的是失败的情况因此Kafka提供了异步发送和回调函数。 代码如下:
通常我们并不关心发送成功的情况,更多关注的是失败的情况,因此 Kafka 提供了异步发送和回调函数。 代码如下:
```scala
for (int i = 0; i < 10; i++) {
@ -196,10 +196,10 @@ for (int i = 0; i < 10; i++) {
## 三、自定义分区器
Kafka有着默认的分区机制
Kafka 有着默认的分区机制:
+ 如果键值为 null 则使用轮询(Round Robin)算法将消息均衡地分布到各个分区上;
+ 如果键值不为null那么Kafka会使用内置的散列算法对键进行散列然后分布到各个分区上。
+ 如果键值为 null 则使用轮询 (Round Robin) 算法将消息均衡地分布到各个分区上;
+ 如果键值不为 null那么 Kafka 会使用内置的散列算法对键进行散列,然后分布到各个分区上。
某些情况下,你可能有着自己的分区需求,这时候可以采用自定义分区器实现。这里给出一个自定义分区器的示例:
@ -222,7 +222,7 @@ public class CustomPartitioner implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value,
byte[] valueBytes, Cluster cluster) {
/*key值为分数当分数大于分数线时候分配到1分区,否则分配到0分区*/
/*key 值为分数,当分数大于分数线时候,分配到 1 分区,否则分配到 0 分区*/
return (Integer) key >= passLine ? 1 : 0;
}
@ -278,7 +278,7 @@ public class ProducerWithPartitioner {
--topic Kafka-Partitioner-Test
```
此时输入如下,可以看到分数大于等于6分的都被分到1分区,而小于6分的都被分到了0分区。
此时输入如下,可以看到分数大于等于 6 分的都被分到 1 分区,而小于 6 分的都被分到了 0 分区。
```shell
score:6, partition=1,
@ -299,7 +299,7 @@ score:5, partition=0,
## 四、生产者其他属性
上面生产者的创建都仅指定了服务地址键序列化器、值序列化器实际上Kafka的生产者还有很多可配置属性如下
上面生产者的创建都仅指定了服务地址,键序列化器、值序列化器,实际上 Kafka 的生产者还有很多可配置属性,如下:
### 1. acks
@ -315,7 +315,7 @@ acks 参数指定了必须要有多少个分区副本收到消息,生产者才
### 3. compression.type
默认情况下发送的消息不会被压缩。如果想要进行压缩可以配置此参数可选值有snappygziplz4。
默认情况下,发送的消息不会被压缩。如果想要进行压缩,可以配置此参数,可选值有 snappygziplz4。
### 4. retries
@ -331,29 +331,29 @@ acks 参数指定了必须要有多少个分区副本收到消息,生产者才
### 7. clent.id
客户端id,服务器用来识别消息的来源。
客户端 id,服务器用来识别消息的来源。
### 8. max.in.flight.requests.per.connection
指定了生产者在收到服务器响应之前可以发送多少个消息。它的值越高,就会占用越多的内存,不过也会提升吞吐量,把它设置为1可以保证消息是按照发送的顺序写入服务器,即使发生了重试。
指定了生产者在收到服务器响应之前可以发送多少个消息。它的值越高,就会占用越多的内存,不过也会提升吞吐量,把它设置为 1 可以保证消息是按照发送的顺序写入服务器,即使发生了重试。
### 9. timeout.ms, request.timeout.ms & metadata.fetch.timeout.ms
- timeout.ms 指定了borker等待同步副本返回消息的确认时间
- timeout.ms 指定了 borker 等待同步副本返回消息的确认时间;
- request.timeout.ms 指定了生产者在发送数据时等待服务器返回响应的时间;
- metadata.fetch.timeout.ms 指定了生产者在获取元数据(比如分区首领是谁)时等待服务器返回响应的时间。
### 10. max.block.ms
指定了在调用`send()`方法或使用`partitionsFor()`方法获取元数据时生产者的阻塞时间。当生产者的发送缓冲区已满或者没有可用的元数据时这些方法会阻塞。在阻塞时间达到max.block.ms 时,生产者会抛出超时异常。
指定了在调用 `send()` 方法或使用 `partitionsFor()` 方法获取元数据时生产者的阻塞时间。当生产者的发送缓冲区已满,或者没有可用的元数据时,这些方法会阻塞。在阻塞时间达到 max.block.ms 时,生产者会抛出超时异常。
### 11. max.request.size
该参数用于控制生产者发送的请求大小。它可以指发送的单个消息的最大值也可以指单个请求里所有消息总的大小。例如假设这个值为1000K 那么可以发送的单个最大消息为1000K ,或者生产者可以在单个请求里发送一个批次,该批次包含了 1000 个消息每个消息大小为1K。
该参数用于控制生产者发送的请求大小。它可以指发送的单个消息的最大值,也可以指单个请求里所有消息总的大小。例如,假设这个值为 1000K ,那么可以发送的单个最大消息为 1000K ,或者生产者可以在单个请求里发送一个批次,该批次包含了 1000 个消息,每个消息大小为 1K。
### 12. receive.buffer.bytes & send.buffer.byte
这两个参数分别指定TCP socket 接收和发送数据包缓冲区的大小,-1代表使用操作系统的默认值。
这两个参数分别指定 TCP socket 接收和发送数据包缓冲区的大小,-1 代表使用操作系统的默认值。
@ -361,4 +361,4 @@ acks 参数指定了必须要有多少个分区副本收到消息,生产者才
## 参考资料
1. Neha Narkhede, Gwen Shapira ,Todd Palino(著) , 薛命灯(译) . Kafka权威指南 . 人民邮电出版社 . 2017-12-26
1. Neha Narkhede, Gwen Shapira ,Todd Palino(著) , 薛命灯 (译) . Kafka 权威指南 . 人民邮电出版社 . 2017-12-26

View File

@ -12,25 +12,25 @@
## 一、简介
ApacheKafka是一个分布式的流处理平台。它具有以下特点
ApacheKafka 是一个分布式的流处理平台。它具有以下特点:
+ 支持消息的发布和订阅类似于RabbtMQ、ActiveMQ等消息队列
+ 支持消息的发布和订阅,类似于 RabbtMQ、ActiveMQ 等消息队列;
+ 支持数据实时处理;
+ 能保证消息的可靠性投递;
+ 支持消息的持久化存储,并通过多副本分布式的存储方案来保证消息的容错;
+ 高吞吐率单Broker可以轻松处理数千个分区以及每秒百万级的消息量。
+ 高吞吐率,单 Broker 可以轻松处理数千个分区以及每秒百万级的消息量。
## 二、基本概念
### 2.1 Messages And Batches
Kafka的基本数据单元被称为message(消息),为减少网络开销,提高效率,多个消息会被放入同一批次(Batch)中后再写入。
Kafka 的基本数据单元被称为 message(消息),为减少网络开销,提高效率,多个消息会被放入同一批次 (Batch) 中后再写入。
### 2.2 Topics And Partitions
Kafka的消息通过Topics(主题)进行分类一个主题可以被分为若干个Partitions(分区),一个分区就是一个提交日志(commit log)。消息以追加的方式写入分区然后以先入先出的顺序读取。Kafka通过分区来实现数据的冗余和伸缩性分区可以分布在不同的服务器上这意味着一个Topic可以横跨多个服务器以提供比单个服务器更强大的性能。
Kafka 的消息通过 Topics(主题) 进行分类,一个主题可以被分为若干个 Partitions(分区),一个分区就是一个提交日志 (commit log)。消息以追加的方式写入分区然后以先入先出的顺序读取。Kafka 通过分区来实现数据的冗余和伸缩性,分区可以分布在不同的服务器上,这意味着一个 Topic 可以横跨多个服务器,以提供比单个服务器更强大的性能。
由于一个Topic包含多个分区因此无法在整个Topic范围内保证消息的顺序性但可以保证消息在单个分区内的顺序性。
由于一个 Topic 包含多个分区,因此无法在整个 Topic 范围内保证消息的顺序性,但可以保证消息在单个分区内的顺序性。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/kafka-topic.png"/> </div>
@ -42,7 +42,7 @@ Kafka的消息通过Topics(主题)进行分类,一个主题可以被分为若
#### 2. 消费者
消费者是消费者群组的一部分,消费者负责消费消息。消费者可以订阅一个或者多个主题,并按照消息生成的顺序来读取它们。消费者通过检查消息的偏移量(offset)来区分读取过的消息。偏移量是一个不断递增的数值在创建消息时Kafka会把它添加到其中在给定的分区里每个消息的偏移量都是唯一的。消费者把每个分区最后读取的偏移量保存在ZookeeperKafka上如果消费者关闭或者重启它还可以重新获取该偏移量以保证读取状态不会丢失。
消费者是消费者群组的一部分,消费者负责消费消息。消费者可以订阅一个或者多个主题,并按照消息生成的顺序来读取它们。消费者通过检查消息的偏移量 (offset) 来区分读取过的消息。偏移量是一个不断递增的数值在创建消息时Kafka 会把它添加到其中,在给定的分区里,每个消息的偏移量都是唯一的。消费者把每个分区最后读取的偏移量保存在 ZookeeperKafka 上,如果消费者关闭或者重启,它还可以重新获取该偏移量,以保证读取状态不会丢失。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/kafka-producer-consumer.png"/> </div>
@ -52,11 +52,11 @@ Kafka的消息通过Topics(主题)进行分类,一个主题可以被分为若
### 2.4 Brokers And Clusters
一个独立的Kafka服务器被称为Broker。Broker接收来自生产者的消息为消息设置偏移量并提交消息到磁盘保存。Broker为消费者提供服务对读取分区的请求做出响应返回已经提交到磁盘的消息。
一个独立的 Kafka 服务器被称为 Broker。Broker 接收来自生产者的消息为消息设置偏移量并提交消息到磁盘保存。Broker 为消费者提供服务,对读取分区的请求做出响应,返回已经提交到磁盘的消息。
Broker是集群(Cluster)的组成部分。每一个集群都会选举出一个Broker作为集群控制器(Controller)集群控制器负责管理工作包括将分区分配给Broker和监控Broker。
Broker 是集群 (Cluster) 的组成部分。每一个集群都会选举出一个 Broker 作为集群控制器 (Controller),集群控制器负责管理工作,包括将分区分配给 Broker 和监控 Broker。
在集群中,一个分区(Partition)从属一个Broker该Broker被称为分区的首领(Leader)。一个分区可以分配给多个Brokers这个时候会发生分区复制。这种复制机制为分区提供了消息冗余如果有一个Broker失效其他Broker可以接管领导权。
在集群中,一个分区 (Partition) 从属一个 Broker Broker 被称为分区的首领 (Leader)。一个分区可以分配给多个 Brokers这个时候会发生分区复制。这种复制机制为分区提供了消息冗余如果有一个 Broker 失效,其他 Broker 可以接管领导权。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/kafka-cluster.png"/> </div>
@ -64,4 +64,4 @@ Broker是集群(Cluster)的组成部分。每一个集群都会选举出一个Br
## 参考资料
Neha Narkhede, Gwen Shapira ,Todd Palino(著) , 薛命灯(译) . Kafka权威指南 . 人民邮电出版社 . 2017-12-26
Neha Narkhede, Gwen Shapira ,Todd Palino(著) , 薛命灯 (译) . Kafka 权威指南 . 人民邮电出版社 . 2017-12-26

View File

@ -20,7 +20,7 @@
### 1.1 函数与方法
Scala中函数与方法的区别非常小如果函数作为某个对象的成员这样的函数被称为方法否则就是一个正常的函数。
Scala 中函数与方法的区别非常小,如果函数作为某个对象的成员,这样的函数被称为方法,否则就是一个正常的函数。
```scala
// 定义方法
@ -32,18 +32,18 @@ println(multi1(3)) //输出 9
println(multi2(3)) //输出 9
```
也可以使用`def`定义函数:
也可以使用 `def` 定义函数:
```scala
def multi3 = (x: Int) => {x * x}
println(multi3(3)) //输出9
println(multi3(3)) //输出 9
```
`multi2``multi3`本质上没有区别,这是因为函数是一等公民,`val multi2 = (x: Int) => {x * x}`这个语句相当于是使用`def`预先定义了函数,之后赋值给变量`multi2`
`multi2``multi3` 本质上没有区别,这是因为函数是一等公民,`val multi2 = (x: Int) => {x * x}` 这个语句相当于是使用 `def` 预先定义了函数,之后赋值给变量 `multi2`
### 1.2 函数类型
上面我们说过`multi2``multi3`本质上是一样的,那么作为函数它们是什么类型的?两者的类型实际上都是`Int => Int`前面一个Int代表输入参数类型后面一个Int代表返回值类型。
上面我们说过 `multi2``multi3` 本质上是一样的,那么作为函数它们是什么类型的?两者的类型实际上都是 `Int => Int`,前面一个 Int 代表输入参数类型,后面一个 Int 代表返回值类型。
```scala
scala> val multi2 = (x: Int) => {x * x}
@ -59,19 +59,19 @@ multi4: (Int, String) => String = $$Lambda$1093/1039732747@2eb4fe7
### 1.3 一等公民&匿名函数
在Scala中函数是一等公民这意味着不仅可以定义函数并调用它们还可以将它们作为值进行传递
Scala 中函数是一等公民,这意味着不仅可以定义函数并调用它们,还可以将它们作为值进行传递:
```scala
import scala.math.ceil
object ScalaApp extends App {
// 将函数ceil赋值给变量fun,使用下划线(_)指明是ceil函数但不传递参数
// 将函数 ceil 赋值给变量 fun,使用下划线 (_) 指明是 ceil 函数但不传递参数
val fun = ceil _
println(fun(2.3456)) //输出3.0
println(fun(2.3456)) //输出 3.0
}
```
在Scala中你不必给每一个函数都命名`(x: Int) => 3 * x`就是一个匿名函数:
Scala 中你不必给每一个函数都命名,如 `(x: Int) => 3 * x` 就是一个匿名函数:
```scala
object ScalaApp extends App {
@ -93,7 +93,7 @@ object ScalaApp extends App {
#### 1. 可变长度参数列表
在Java中如果你想要传递可变长度的参数需要使用`String ...args`这种形式Scala中等效的表达为`args: String*`
Java 中如果你想要传递可变长度的参数,需要使用 `String ...args` 这种形式Scala 中等效的表达为 `args: String*`
```scala
object ScalaApp extends App {
@ -134,7 +134,7 @@ object ScalaApp extends App {
def detail(name: String, age: Int = 88): Unit = println(name + ":" + age)
// 如果没有传递age值,则使用默认值
// 如果没有传递 age 值,则使用默认值
detail("heibaiying")
detail("heibaiying", 12)
@ -147,14 +147,14 @@ object ScalaApp extends App {
```scala
var more = 10
// addMore一个闭包函数:因为其捕获了自由变量more从而闭合了该函数字面量
// addMore 一个闭包函数:因为其捕获了自由变量 more 从而闭合了该函数字面量
val addMore = (x: Int) => x + more
```
如上函数`addMore`中有两个变量x和more:
如上函数 `addMore` 中有两个变量 x 和 more:
+ **x** : 是一个绑定变量(bound variable),因为其是该函数的入参,在函数的上下文中有明确的定义;
+ **more** : 是一个自由变量(free variable)因为函数字面量本生并没有给more赋予任何含义。
+ **x** : 是一个绑定变量 (bound variable),因为其是该函数的入参,在函数的上下文中有明确的定义;
+ **more** : 是一个自由变量 (free variable),因为函数字面量本生并没有给 more 赋予任何含义。
按照定义:在创建函数时,如果需要捕获自由变量,那么包含指向被捕获变量的引用的函数就被称为闭包函数。
@ -166,18 +166,18 @@ val addMore = (x: Int) => x + more
+ 闭包内部对自由变量的修改,在闭包外部也是可见的。
```scala
// 声明more变量
// 声明 more 变量
scala> var more = 10
more: Int = 10
// more变量必须已经被声明否则下面的语句会报错
// more 变量必须已经被声明,否则下面的语句会报错
scala> val addMore = (x: Int) => {x + more}
addMore: Int => Int = $$Lambda$1076/1844473121@876c4f0
scala> addMore(10)
res7: Int = 20
// 注意这里是给more变量赋值而不是重新声明more变量
// 注意这里是给 more 变量赋值,而不是重新声明 more 变量
scala> more=1000
more: Int = 1000
@ -190,7 +190,7 @@ res8: Int = 1010
自由变量可能随着程序的改变而改变,从而产生多个副本,但是闭包永远指向创建时候有效的那个变量副本。
```scala
// 第一次声明more变量
// 第一次声明 more 变量
scala> var more = 10
more: Int = 10
@ -202,7 +202,7 @@ addMore10: Int => Int = $$Lambda$1077/1144251618@1bdaa13c
scala> addMore10(9)
res9: Int = 19
// 重新声明more变量
// 重新声明 more 变量
scala> var more = 100
more: Int = 100
@ -210,20 +210,20 @@ more: Int = 100
scala> val addMore100 = (x: Int) => {x + more}
addMore100: Int => Int = $$Lambda$1078/626955849@4d0be2ac
// 引用的是重新声明more变量
// 引用的是重新声明 more 变量
scala> addMore100(9)
res10: Int = 109
// 引用的还是第一次声明的more变量
// 引用的还是第一次声明的 more 变量
scala> addMore10(9)
res11: Int = 19
// 对于全局而言more还是100
// 对于全局而言 more 还是 100
scala> more
res12: Int = 100
```
从上面的示例可以看出重新声明`more`后,全局的`more`的值是100但是对于闭包函数`addMore10`还是引用的是值为10`more`,这是由虚拟机来实现的,虚拟机会保证`more`变量在重新声明后,原来的被捕获的变量副本继续在堆上保持存活。
从上面的示例可以看出重新声明 `more` 后,全局的 `more` 的值是 100但是对于闭包函数 `addMore10` 还是引用的是值为 10`more`,这是由虚拟机来实现的,虚拟机会保证 `more` 变量在重新声明后,原来的被捕获的变量副本继续在堆上保持存活。
## 三、高阶函数
@ -239,7 +239,7 @@ object ScalaApp extends App {
x * x
}
// 2.定义高阶函数: 第一个参数是类型为Int => Int的函数
// 2.定义高阶函数: 第一个参数是类型为 Int => Int 的函数
def multi(fun: Int => Int, x: Int) = {
fun(x) * 100
}
@ -265,10 +265,10 @@ object ScalaApp extends App {
}
```
这里当你调用curriedSum时候实际上是连着做了两次传统的函数调用实际执行的柯里化过程如下
这里当你调用 curriedSum 时候,实际上是连着做了两次传统的函数调用,实际执行的柯里化过程如下:
+ 第一次调用接收一个名为`x`Int型参数返回一个用于第二次调用的函数假设`x`2则返回函数`2+y`
+ 返回的函数接收参数`y`,并计算并返回值`2+3`的值。
+ 第一次调用接收一个名为 `x`Int 型参数,返回一个用于第二次调用的函数,假设 `x`2则返回函数 `2+y`
+ 返回的函数接收参数 `y`,并计算并返回值 `2+3` 的值。
想要获得柯里化的中间返回的函数其实也比较简单:
@ -278,7 +278,7 @@ object ScalaApp extends App {
def curriedSum(x: Int)(y: Int) = x + y
println(curriedSum(2)(3)) //输出 5
// 获取传入值为10返回的中间函数 10 + y
// 获取传入值为 10 返回的中间函数 10 + y
val plus: Int => Int = curriedSum(10)_
println(plus(3)) //输出值 13
}
@ -301,8 +301,8 @@ object ScalaApp extends App {
## 参考资料
1. Martin Odersky . Scala编程(第3版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学Scala(第2版)[M] . 电子工业出版社 . 2017-7
1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7

View File

@ -17,25 +17,25 @@
## 一、List字面量
ListScala中非常重要的一个数据结构其与Array(数组)非常类似但是List是不可变的和Java中的List一样其底层实现是链表。
ListScala 中非常重要的一个数据结构,其与 Array(数组) 非常类似,但是 List 是不可变的,和 Java 中的 List 一样,其底层实现是链表。
```scala
scala> val list = List("hadoop", "spark", "storm")
list: List[String] = List(hadoop, spark, storm)
// List是不可变
// List 是不可变
scala> list(1) = "hive"
<console>:9: error: value update is not a member of List[String]
```
## 二、List类型
ScalaList具有以下两个特性
ScalaList 具有以下两个特性:
+ **同构(homogeneous)**同一个List中的所有元素都必须是相同的类型
+ **协变(covariant)**:如果S是T的子类型,那么`List[S]`就是`List[T]`的子类型,例如`List[String]``List[Object]`的子类型。
+ **同构 (homogeneous)**:同一个 List 中的所有元素都必须是相同的类型;
+ **协变 (covariant)**:如果 S 是 T 的子类型,那么 `List[S]` 就是 `List[T]` 的子类型,例如 `List[String]``List[Object]` 的子类型。
需要特别说明的是空列表的类型为`List[Nothing]`
需要特别说明的是空列表的类型为 `List[Nothing]`
```scala
scala> List()
@ -44,7 +44,7 @@ res1: List[Nothing] = List()
## 三、构建List
所有List都由两个基本单元构成`Nil``::`(读作"cons")。即列表要么是空列表(Nil)要么是由一个head加上一个tail组成而tail又是一个List。我们在上面使用的`List("hadoop", "spark", "storm")`最终也是被解释为` "hadoop"::"spark":: "storm"::Nil`
所有 List 都由两个基本单元构成:`Nil` `::`(读作"cons")。即列表要么是空列表 (Nil),要么是由一个 head 加上一个 tail 组成,而 tail 又是一个 List。我们在上面使用的 `List("hadoop", "spark", "storm")` 最终也是被解释为 ` "hadoop"::"spark":: "storm"::Nil`
```scala
scala> val list01 = "hadoop"::"spark":: "storm"::Nil
@ -57,7 +57,7 @@ list02: List[String] = List(hadoop, spark, storm)
## 四、模式匹配
Scala支持展开列表以实现模式匹配。
Scala 支持展开列表以实现模式匹配。
```scala
scala> val list = List("hadoop", "spark", "storm")
@ -92,16 +92,16 @@ object ScalaApp extends App {
// 2.返回列表中的第一个元素
list.head
// 3.返回列表中除第一个元素外的所有元素 这里输出List(spark, storm)
// 3.返回列表中除第一个元素外的所有元素 这里输出 List(spark, storm)
list.tail
// 4.tailhead可以结合使用
// 4.tailhead 可以结合使用
list.tail.head
// 5.返回列表中的最后一个元素 与head相反
// 5.返回列表中的最后一个元素 与 head 相反
list.init
// 6.返回列表中除了最后一个元素之外的其他元素 与tail相反 这里输出List(hadoop, spark)
// 6.返回列表中除了最后一个元素之外的其他元素 与 tail 相反 这里输出 List(hadoop, spark)
list.last
// 7.使用下标访问元素
@ -118,7 +118,7 @@ object ScalaApp extends App {
### 5.2 indices
indices方法返回所有下标。
indices 方法返回所有下标。
```scala
scala> list.indices
@ -127,8 +127,8 @@ res2: scala.collection.immutable.Range = Range(0, 1, 2)
### 5.3 take & drop & splitAt
- **take**:获取前n个元素;
- **drop**:删除前n个元素;
- **take**:获取前 n 个元素;
- **drop**:删除前 n 个元素;
- **splitAt**:从第几个位置开始拆分。
```scala
@ -144,7 +144,7 @@ res5: (List[String], List[String]) = (List(hadoop, spark),List(storm))
### 5.4 flatten
flatten接收一个由列表组成的列表并将其进行扁平化操作返回单个列表。
flatten 接收一个由列表组成的列表,并将其进行扁平化操作,返回单个列表。
```scala
scala> List(List(1, 2), List(3), List(), List(4, 5)).flatten
@ -153,7 +153,7 @@ res6: List[Int] = List(1, 2, 3, 4, 5)
### 5.5 zip & unzip
对两个List执行`zip`操作结果如下,返回对应位置元素组成的元组的列表,`unzip`则执行反向操作。
对两个 List 执行 `zip` 操作结果如下,返回对应位置元素组成的元组的列表,`unzip` 则执行反向操作。
```scala
scala> val list = List("hadoop", "spark", "storm")
@ -168,14 +168,14 @@ res7: (List[String], List[Int]) = (List(hadoop, spark, storm),List(10, 20, 30))
### 5.6 toString & mkString
toString 返回List的字符串表现形式。
toString 返回 List 的字符串表现形式。
```scala
scala> list.toString
res8: String = List(hadoop, spark, storm)
```
如果想改变List的字符串表现形式可以使用mkString。mkString有三个重载方法方法定义如下
如果想改变 List 的字符串表现形式,可以使用 mkString。mkString 有三个重载方法,方法定义如下:
```scala
// start前缀 sep分隔符 end:后缀
@ -220,7 +220,7 @@ object ScalaApp extends App {
}
```
toArraytoList用于List和数组之间的互相转换。
toArraytoList 用于 List 和数组之间的互相转换。
```scala
scala> val array = list.toArray
@ -230,7 +230,7 @@ scala> array.toList
res13: List[String] = List(hadoop, spark, storm)
```
copyToArrayList中的元素拷贝到数组中指定位置。
copyToArrayList 中的元素拷贝到数组中指定位置。
```scala
object ScalaApp extends App {
@ -250,14 +250,14 @@ object ScalaApp extends App {
### 6.1 列表转换map & flatMap & foreach
map 与 Java 8 函数式编程中的map类似都是对List中每一个元素执行指定操作。
map 与 Java 8 函数式编程中的 map 类似,都是对 List 中每一个元素执行指定操作。
```scala
scala> List(1,2,3).map(_+10)
res15: List[Int] = List(11, 12, 13)
```
flatMap 与 map 类似但如果List中的元素还是List则会对其进行flatten操作。
flatMap 与 map 类似,但如果 List 中的元素还是 List则会对其进行 flatten 操作。
```scala
scala> list.map(_.toList)
@ -267,7 +267,7 @@ scala> list.flatMap(_.toList)
res17: List[Char] = List(h, a, d, o, o, p, s, p, a, r, k, s, t, o, r, m)
```
foreach 要求右侧的操作是一个返回值为Unit的函数你也可以简单理解为执行一段没有返回值代码。
foreach 要求右侧的操作是一个返回值为 Unit 的函数,你也可以简单理解为执行一段没有返回值代码。
```scala
scala> var sum = 0
@ -281,21 +281,21 @@ res19: Int = 15
### 6.2 列表过滤filter & partition & find & takeWhile & dropWhile & span
filter用于筛选满足条件元素返回新的List。
filter 用于筛选满足条件元素,返回新的 List。
```scala
scala> List(1, 2, 3, 4, 5) filter (_ % 2 == 0)
res20: List[Int] = List(2, 4)
```
partition会按照筛选条件对元素进行分组返回类型是tuple(元组)。
partition 会按照筛选条件对元素进行分组,返回类型是 tuple(元组)。
```scala
scala> List(1, 2, 3, 4, 5) partition (_ % 2 == 0)
res21: (List[Int], List[Int]) = (List(2, 4),List(1, 3, 5))
```
find查找第一个满足条件的值由于可能并不存在这样的值所以返回类型是`Option`,可以通过`getOrElse`在不存在满足条件值的情况下返回默认值。
find 查找第一个满足条件的值,由于可能并不存在这样的值,所以返回类型是 `Option`,可以通过 `getOrElse` 在不存在满足条件值的情况下返回默认值。
```scala
scala> List(1, 2, 3, 4, 5) find (_ % 2 == 0)
@ -305,14 +305,14 @@ val result: Option[Int] = List(1, 2, 3, 4, 5) find (_ % 2 == 0)
result.getOrElse(10)
```
takeWhile遍历元素直到遇到第一个不符合条件的值则结束遍历返回所有遍历到的值。
takeWhile 遍历元素,直到遇到第一个不符合条件的值则结束遍历,返回所有遍历到的值。
```scala
scala> List(1, 2, 3, -4, 5) takeWhile (_ > 0)
res23: List[Int] = List(1, 2, 3)
```
dropWhile遍历元素直到遇到第一个不符合条件的值则结束遍历返回所有未遍历到的值。
dropWhile 遍历元素,直到遇到第一个不符合条件的值则结束遍历,返回所有未遍历到的值。
```scala
// 第一个值就不满足条件,所以返回列表中所有的值
@ -324,7 +324,7 @@ scala> List(1, 2, 3, -4, 5) dropWhile (_ < 3)
res26: List[Int] = List(3, -4, 5)
```
span遍历元素直到遇到第一个不符合条件的值则结束遍历将遍历到的值和未遍历到的值分别放入两个List中返回返回类型是tuple(元组)。
span 遍历元素,直到遇到第一个不符合条件的值则结束遍历,将遍历到的值和未遍历到的值分别放入两个 List 中返回,返回类型是 tuple(元组)。
```scala
scala> List(1, 2, 3, -4, 5) span (_ > 0)
@ -335,14 +335,14 @@ res27: (List[Int], List[Int]) = (List(1, 2, 3),List(-4, 5))
### 6.3 列表检查forall & exists
forall检查List中所有元素如果所有元素都满足条件则返回true。
forall 检查 List 中所有元素,如果所有元素都满足条件,则返回 true。
```scala
scala> List(1, 2, 3, -4, 5) forall ( _ > 0 )
res28: Boolean = false
```
exists检查List中的元素如果某个元素已经满足条件则返回true。
exists 检查 List 中的元素,如果某个元素已经满足条件,则返回 true。
```scala
scala> List(1, 2, 3, -4, 5) exists (_ > 0 )
@ -353,7 +353,7 @@ res29: Boolean = true
### 6.4 列表排序sortWith
sortWithList中所有元素按照指定规则进行排序由于List是不可变的所以排序返回一个新的List。
sortWithList 中所有元素按照指定规则进行排序,由于 List 是不可变的,所以排序返回一个新的 List。
```scala
scala> List(1, -3, 4, 2, 6) sortWith (_ < _)
@ -370,11 +370,11 @@ res33: List[String] = List(azkaban, hadoop, spark, hive)
## 七、List对象的方法
上面介绍的所有方法都是List类上的方法下面介绍的是List伴生对象中的方法。
上面介绍的所有方法都是 List 类上的方法,下面介绍的是 List 伴生对象中的方法。
### 7.1 List.range
List.range可以产生指定的前闭后开区间内的值组成的List它有三个可选参数: start(开始值)end(结束值,不包含)step(步长)。
List.range 可以产生指定的前闭后开区间内的值组成的 List它有三个可选参数: start(开始值)end(结束值,不包含)step(步长)。
```scala
scala> List.range(1, 5)
@ -389,7 +389,7 @@ res36: List[Int] = List(9, 6, 3)
### 7.2 List.fill
List.fill使用指定值填充List。
List.fill 使用指定值填充 List。
```scala
scala> List.fill(3)("hello")
@ -401,7 +401,7 @@ res38: List[List[String]] = List(List(world, world, world), List(world, world, w
### 7.3 List.concat
List.concat用于拼接多个List。
List.concat 用于拼接多个 List。
```scala
scala> List.concat(List('a', 'b'), List('c'))
@ -418,18 +418,18 @@ res41: List[Nothing] = List()
## 八、处理多个List
当多个List被放入同一个tuple中时候可以通过zipped对多个List进行关联处理。
当多个 List 被放入同一个 tuple 中时候,可以通过 zipped 对多个 List 进行关联处理。
```scala
// 两个List对应位置的元素相乘
// 两个 List 对应位置的元素相乘
scala> (List(10, 20), List(3, 4, 5)).zipped.map(_ * _)
res42: List[Int] = List(30, 80)
// 三个List的操作也是一样的
// 三个 List 的操作也是一样的
scala> (List(10, 20), List(3, 4, 5), List(100, 200)).zipped.map(_ * _ + _)
res43: List[Int] = List(130, 280)
// 判断第一个List中元素的长度与第二个List中元素的值是否相等
// 判断第一个 List 中元素的长度与第二个 List 中元素的值是否相等
scala> (List("abc", "de"), List(3, 2)).zipped.forall(_.length == _)
res44: Boolean = true
```
@ -438,7 +438,7 @@ res44: Boolean = true
## 九、缓冲列表ListBuffer
上面介绍的List由于其底层实现是链表这意味着能快速访问List头部元素但对尾部元素的访问则比较低效这时候可以采用`ListBuffer`ListBuffer提供了在常量时间内往头部和尾部追加元素。
上面介绍的 List由于其底层实现是链表这意味着能快速访问 List 头部元素,但对尾部元素的访问则比较低效,这时候可以采用 `ListBuffer`ListBuffer 提供了在常量时间内往头部和尾部追加元素。
```scala
import scala.collection.mutable.ListBuffer
@ -451,7 +451,7 @@ object ScalaApp extends App {
buffer += 2
// 2.在头部追加元素
3 +=: buffer
// 3. ListBufferList
// 3. ListBufferList
val list: List[Int] = buffer.toList
println(list)
}
@ -463,14 +463,14 @@ object ScalaApp extends App {
## 十、集(Set)
Set是不重复元素的集合。分为可变Set和不可变Set。
Set 是不重复元素的集合。分为可变 Set 和不可变 Set。
### 10.1 可变Set
```scala
object ScalaApp extends App {
// 可变Set
// 可变 Set
val mutableSet = new collection.mutable.HashSet[Int]
// 1.添加元素
@ -483,13 +483,13 @@ object ScalaApp extends App {
// 2.移除元素
mutableSet.remove(2)
// 3.调用mkString方法 输出1,3,4
// 3.调用 mkString 方法 输出 1,3,4
println(mutableSet.mkString(","))
// 4. 获取Set中最小元素
// 4. 获取 Set 中最小元素
println(mutableSet.min)
// 5. 获取Set中最大元素
// 5. 获取 Set 中最大元素
println(mutableSet.max)
}
@ -497,12 +497,12 @@ object ScalaApp extends App {
### 10.2 不可变Set
不可变Set没有add方法可以使用`+`添加元素但是此时会返回一个新的不可变Set原来的Set不变。
不可变 Set 没有 add 方法,可以使用 `+` 添加元素,但是此时会返回一个新的不可变 Set原来的 Set 不变。
```scala
object ScalaApp extends App {
// 不可变Set
// 不可变 Set
val immutableSet = new collection.immutable.HashSet[Int]
val ints: HashSet[Int] = immutableSet+1
@ -516,19 +516,19 @@ object ScalaApp extends App {
### 10.3 Set间操作
多个Set之间可以进行求交集或者合集等操作。
多个 Set 之间可以进行求交集或者合集等操作。
```scala
object ScalaApp extends App {
// 声明有序Set
// 声明有序 Set
val mutableSet = collection.mutable.SortedSet(1, 2, 3, 4, 5)
val immutableSet = collection.immutable.SortedSet(3, 4, 5, 6, 7)
// 两个Set的合集 输出TreeSet(1, 2, 3, 4, 5, 6, 7)
// 两个 Set 的合集 输出TreeSet(1, 2, 3, 4, 5, 6, 7)
println(mutableSet ++ immutableSet)
// 两个Set的交集 输出TreeSet(3, 4, 5)
// 两个 Set 的交集 输出TreeSet(3, 4, 5)
println(mutableSet intersect immutableSet)
}
@ -538,5 +538,5 @@ object ScalaApp extends App {
## 参考资料
1. Martin Odersky . Scala编程(第3版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学Scala(第2版)[M] . 电子工业出版社 . 2017-7
1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7

View File

@ -10,31 +10,31 @@
### 1.1 类型支持
Scala 拥有下表所示的数据类型其中Byte、Short、Int、LongChar类型统称为整数类型整数类型加上FloatDouble统称为数值类型。Scala数值类型的取值范围和Java对应类型的取值范围相同。
Scala 拥有下表所示的数据类型,其中 Byte、Short、Int、LongChar 类型统称为整数类型,整数类型加上 FloatDouble 统称为数值类型。Scala 数值类型的取值范围和 Java 对应类型的取值范围相同。
| 数据类型 | 描述 |
| -------- | ------------------------------------------------------------ |
| Byte | 8位有符号补码整数。数值区间为 -128 到 127 |
| Short | 16位有符号补码整数。数值区间为 -32768 到 32767 |
| Int | 32位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
| Long | 64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 |
| Byte | 8 位有符号补码整数。数值区间为 -128 到 127 |
| Short | 16 位有符号补码整数。数值区间为 -32768 到 32767 |
| Int | 32 位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
| Long | 64 位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 |
| Float | 32 位, IEEE 754 标准的单精度浮点数 |
| Double | 64 位 IEEE 754 标准的双精度浮点数 |
| Char | 16位无符号Unicode字符, 区间值为 U+0000 到 U+FFFF |
| Char | 16 位无符号 Unicode 字符, 区间值为 U+0000 到 U+FFFF |
| String | 字符序列 |
| Boolean | truefalse |
| Unit | 表示无值等同于Java中的void。用作不返回任何结果的方法的结果类型。Unit只有一个实例值写成()。 |
| Boolean | truefalse |
| Unit | 表示无值,等同于 Java 中的 void。用作不返回任何结果的方法的结果类型。Unit 只有一个实例值,写成 ()。 |
| Null | null 或空引用 |
| Nothing | Nothing类型在Scala的类层级的最低端它是任何其他类型的子类型。 |
| Any | Any是所有其他类的超类 |
| AnyRef | AnyRef类是Scala里所有引用类(reference class)的基类 |
| Nothing | Nothing 类型在 Scala 的类层级的最低端;它是任何其他类型的子类型。 |
| Any | Any 是所有其他类的超类 |
| AnyRef | AnyRef 类是 Scala 里所有引用类 (reference class) 的基类 |
### 1.2 定义变量
Scala的变量分为两种valvar其区别如下
Scala 的变量分为两种valvar其区别如下
+ **val** 类似于Java中的final变量一旦初始化就不能被重新赋值
+ **var** 类似于Java中的非final变量在整个声明周期内var可以被重新赋值
+ **val** 类似于 Java 中的 final 变量,一旦初始化就不能被重新赋值;
+ **var** :类似于 Java 中的非 final 变量,在整个声明周期内 var 可以被重新赋值;
```scala
scala> val a=1
@ -52,7 +52,7 @@ b: Int = 2
### 1.3 类型推断
在上面的演示中,并没有声明a是Int类型但是程序还是把a当做Int类型这就是Scala的类型推断。在大多数情况下你都无需指明变量的类型程序会自动进行推断。如果你想显式的声明类型可以在变量后面指定如下
在上面的演示中,并没有声明 a 是 Int 类型,但是程序还是把 a 当做 Int 类型,这就是 Scala 的类型推断。在大多数情况下,你都无需指明变量的类型,程序会自动进行推断。如果你想显式的声明类型,可以在变量后面指定,如下:
```scala
scala> val c:String="hello scala"
@ -61,7 +61,7 @@ c: String = hello scala
### 1.4 Scala解释器
在scala命令行中如果没有对输入的值指定赋值的变量则输入的值默认会赋值给`resX`(其中X是一个从0开始递增的整数)`res`result的缩写这个变量可以在后面的语句中进行引用。
scala 命令行中,如果没有对输入的值指定赋值的变量,则输入的值默认会赋值给 `resX`(其中 X 是一个从 0 开始递增的整数)`res`result 的缩写,这个变量可以在后面的语句中进行引用。
```scala
scala> 5
@ -78,7 +78,7 @@ scala> println(res1)
## 二、字面量
ScalaJava字面量在使用上很多相似比如都使用F或f表示浮点型,都使用L或l表示Long类型。下文主要介绍两者差异部分。
ScalaJava 字面量在使用上很多相似,比如都使用 F 或 f 表示浮点型,都使用 L 或 l 表示 Long 类型。下文主要介绍两者差异部分。
```scala
scala> 1.2
@ -102,7 +102,7 @@ res5: Long = 1
### 2.1 整数字面量
Scala支持10进制和16进制但不支持八进制字面量和以0开头的整数字面量。
Scala 支持 10 进制和 16 进制,但不支持八进制字面量和以 0 开头的整数字面量。
```scala
scala> 012
@ -113,7 +113,7 @@ scala> 012
#### 1. 字符字面量
字符字面量由一对单引号和中间的任意Unicode字符组成。你可以显式的给出原字符、也可以使用字符的Unicode码来表示还可以包含特殊的转义字符。
字符字面量由一对单引号和中间的任意 Unicode 字符组成。你可以显式的给出原字符、也可以使用字符的 Unicode 码来表示,还可以包含特殊的转义字符。
```scala
scala> '\u0041'
@ -137,7 +137,7 @@ res3: String = hello world
#### 3.原生字符串
Scala提供了`""" ... """`语法,通过三个双引号来表示原生字符串和多行字符串,使用该种方式,原生字符串中的特殊字符不会被转义。
Scala 提供了 `""" ... """` 语法,通过三个双引号来表示原生字符串和多行字符串,使用该种方式,原生字符串中的特殊字符不会被转义。
```scala
scala> "hello \tool"
@ -155,9 +155,9 @@ world
### 2.3 符号字面量
符号字面量写法为: `'标识符` ,这里 标识符可以是任何字母或数字的组合。符号字面量会被映射成`scala.Symbol`的实例,如:符号字面量 `'x `会被编译器翻译为`scala.Symbol("x")`。符号字面量可选方法很少,只能通过`.name`获取其名称。
符号字面量写法为: `'标识符 ` ,这里 标识符可以是任何字母或数字的组合。符号字面量会被映射成 `scala.Symbol` 的实例,如:符号字面量 `'x ` 会被编译器翻译为 `scala.Symbol("x")`。符号字面量可选方法很少,只能通过 `.name` 获取其名称。
注意:具有相同`name`的符号字面量一定指向同一个Symbol对象不同`name`的符号字面量一定指向不同的Symbol对象。
注意:具有相同 `name` 的符号字面量一定指向同一个 Symbol 对象,不同 `name` 的符号字面量一定指向不同的 Symbol 对象。
```scala
scala> val sym = 'ID008
@ -169,7 +169,7 @@ res12: String = ID008
### 2.4 插值表达式
Scala支持插值表达式。
Scala 支持插值表达式。
```scala
scala> val name="xiaoming"
@ -181,19 +181,19 @@ My name is xiaoming,I'm 18.
## 三、运算符
Scala和其他语言一样支持大多数的操作运算符
Scala 和其他语言一样,支持大多数的操作运算符:
- 算术运算符(+-*/%
- 关系运算符(==!=><>=<=
- 逻辑运算符(&&||!&|)
- 位运算符(~&|^<<>>>>>)
- 赋值运算符(=+=-=*=/=%=<<=>>=&=^=|=)
- 逻辑运算符 (&&||!&|)
- 位运算符 (~&|^<<>>>>>)
- 赋值运算符 (=+=-=*=/=%=<<=>>=&=^=|=)
以上操作符的基本使用与Java类似下文主要介绍差异部分和注意事项。
以上操作符的基本使用与 Java 类似,下文主要介绍差异部分和注意事项。
### 3.1 运算符即方法
Scala的面向对象比Java更加纯粹在Scala中一切都是对象。所以对于`1+2`,实际上是调用了Int类中名为`+`的方法所以1+2,也可以写成`1.+(2)`
Scala 的面向对象比 Java 更加纯粹,在 Scala 中一切都是对象。所以对于 `1+2`,实际上是调用了 Int 类中名为 `+` 的方法,所以 1+2,也可以写成 `1.+(2)`
```scala
scala> 1+2
@ -203,17 +203,17 @@ scala> 1.+(2)
res15: Int = 3
```
Int类中包含了多个重载的`+`方法,用于分别接收不同类型的参数。
Int 类中包含了多个重载的 `+` 方法,用于分别接收不同类型的参数。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-int+.png"/> </div>
### 3.2 逻辑运算符
和其他语言一样在Scala`&&``||`的执行是短路的,即如果左边的表达式能确定整个结果,右边的表达式就不会被执行,这满足大多数使用场景。但是如果你需要在无论什么情况下,都执行右边的表达式,则可以使用`&``|`代替。
和其他语言一样,在 Scala`&&``||` 的执行是短路的,即如果左边的表达式能确定整个结果,右边的表达式就不会被执行,这满足大多数使用场景。但是如果你需要在无论什么情况下,都执行右边的表达式,则可以使用 `&``|` 代替。
### 3.3 赋值运算符
在Scala中没有Java中的`++``--`运算符,如果你想要实现类似的操作,只能使用`+=1`,或者`-=1`
Scala 中没有 Java 中的 `++``--` 运算符,如果你想要实现类似的操作,只能使用 `+=1`,或者 `-=1`
```scala
scala> var a=1
@ -236,7 +236,7 @@ res10: Int = 1
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-操作符优先级.png"/> </div>
在表格中某个字符的优先级越高,那么以这个字符打头的方法就拥有更高的优先级。如`+`的优先级大于`<`,也就意味则`+`的优先级大于以`<`开头的`<<`,所以`2<<2+2` , 实际上等价于`2<<(2+2)` :
在表格中某个字符的优先级越高,那么以这个字符打头的方法就拥有更高的优先级。如 `+` 的优先级大于 `<`,也就意味则 `+` 的优先级大于以 `<` 开头的 `<<`,所以 `2<<2+2` , 实际上等价于 `2<<(2+2)` :
```scala
scala> 2<<2+2
@ -248,7 +248,7 @@ res1: Int = 32
### 3.5 对象相等性
如果想要判断两个对象是否相等,可以使用`==``!=`,这两个操作符可以用于所有的对象包括null。
如果想要判断两个对象是否相等,可以使用 `==``!=`,这两个操作符可以用于所有的对象,包括 null。
```scala
scala> 1==2
@ -271,4 +271,4 @@ res6: Boolean = true
## 参考资料
1. Martin Odersky . Scala编程(第3版)[M] . 电子工业出版社 . 2018-1-1
1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1

View File

@ -11,59 +11,59 @@
## 一、定长数组
在Scala中如果你需要一个长度不变的数组可以使用Array。但需要注意以下两点
Scala 中,如果你需要一个长度不变的数组,可以使用 Array。但需要注意以下两点
- 在Scala中使用`(index)`而不是`[index]`来访问数组中的元素因为访问元素对于Scala来说是方法调用`(index)`相当于执行了`.apply(index)`方法。
- Scala中的数组与Java中的是等价的`Array[Int]()`在虚拟机层面就等价于Java`int[]`
- Scala 中使用 `(index)` 而不是 `[index]` 来访问数组中的元素,因为访问元素,对于 Scala 来说是方法调用,`(index)` 相当于执行了 `.apply(index)` 方法。
- Scala 中的数组与 Java 中的是等价的,`Array[Int]()` 在虚拟机层面就等价于 Java`int[]`
```scala
// 10个整数的数组所有元素初始化为0
// 10 个整数的数组,所有元素初始化为 0
scala> val nums=new Array[Int](10)
nums: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
// 10个元素的字符串数组所有元素初始化为null
// 10 个元素的字符串数组,所有元素初始化为 null
scala> val strings=new Array[String](10)
strings: Array[String] = Array(null, null, null, null, null, null, null, null, null, null)
// 使用指定值初始化此时不需要new关键字
// 使用指定值初始化,此时不需要 new 关键字
scala> val a=Array("hello","scala")
a: Array[String] = Array(hello, scala)
// 使用()来访问元素
// 使用 () 来访问元素
scala> a(0)
res3: String = hello
```
## 二、变长数组
在scala中通过ArrayBuffer实现变长数组(又称缓冲数组)。在构建ArrayBuffer时必须给出类型参数但不必指定长度因为ArrayBuffer会在需要的时候自动扩容和缩容。变长数组的构建方式及常用操作如下
scala 中通过 ArrayBuffer 实现变长数组 (又称缓冲数组)。在构建 ArrayBuffer 时必须给出类型参数,但不必指定长度,因为 ArrayBuffer 会在需要的时候自动扩容和缩容。变长数组的构建方式及常用操作如下:
```java
import scala.collection.mutable.ArrayBuffer
object ScalaApp {
// 相当于Java中的main方法
// 相当于 Java 中的 main 方法
def main(args: Array[String]): Unit = {
// 1.声明变长数组(缓冲数组)
// 1.声明变长数组 (缓冲数组)
val ab = new ArrayBuffer[Int]()
// 2.在末端增加元素
ab += 1
// 3.在末端添加多个元素
ab += (2, 3, 4)
// 4.可以使用++=追加任何集合
// 4.可以使用 ++=追加任何集合
ab ++= Array(5, 6, 7)
// 5.缓冲数组可以直接打印查看
println(ab)
// 6.移除最后三个元素
ab.trimEnd(3)
// 7.在第1个元素之后插入多个新元素
// 7.在第 1 个元素之后插入多个新元素
ab.insert(1, 8, 9)
// 8.从第2个元素开始,移除3个元素,不指定第二个参数的话,默认值为1
// 8.从第 2 个元素开始,移除 3 个元素,不指定第二个参数的话,默认值为 1
ab.remove(2, 3)
// 9.缓冲数组转定长数组
val abToA = ab.toArray
// 10. 定长数组打印为其hashcode值
// 10. 定长数组打印为其 hashcode
println(abToA)
// 11. 定长数组转缓冲数组
val aToAb = abToA.toBuffer
@ -71,7 +71,7 @@ object ScalaApp {
}
```
需要注意的是:使用`+= `在末尾插入元素是一个高效的操作其时间复杂度是O(1)。而使用`insert`随机插入元素的时间复杂度是O(n),因为在其插入位置之后的所有元素都要进行对应的后移,所以在`ArrayBuffer`中随机插入元素是一个低效的操作。
需要注意的是:使用 `+= ` 在末尾插入元素是一个高效的操作,其时间复杂度是 O(1)。而使用 `insert` 随机插入元素的时间复杂度是 O(n),因为在其插入位置之后的所有元素都要进行对应的后移,所以在 `ArrayBuffer` 中随机插入元素是一个低效的操作。
## 三、数组遍历
@ -80,7 +80,7 @@ object ScalaApp extends App {
val a = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 1.方式一 相当于Java中的增强for循环
// 1.方式一 相当于 Java 中的增强 for 循环
for (elem <- a) {
print(elem)
}
@ -103,26 +103,26 @@ object ScalaApp extends App {
}
```
这里我们没有将代码写在main方法中而是继承自App.scala这是Scala提供的一种简写方式此时将代码写在类中等价于写在main方法中直接运行该类即可。
这里我们没有将代码写在 main 方法中,而是继承自 App.scala这是 Scala 提供的一种简写方式,此时将代码写在类中,等价于写在 main 方法中,直接运行该类即可。
## 四、数组转换
数组转换是指由现有数组产生新的数组。假设当前拥有a数组,想把a中的偶数元素乘以10后产生一个新的数组可以采用下面两种方式来实现
数组转换是指由现有数组产生新的数组。假设当前拥有 a 数组,想把 a 中的偶数元素乘以 10 后产生一个新的数组,可以采用下面两种方式来实现:
```scala
object ScalaApp extends App {
val a = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 1.方式一 yield关键字
// 1.方式一 yield 关键字
val ints1 = for (elem <- a if elem % 2 == 0) yield 10 * elem
for (elem <- ints1) {
println(elem)
}
// 2.方式二 采用函数式编程的方式,这和Java 8中的函数式编程是类似的这里采用下划线标表示其中的每个元素
// 2.方式二 采用函数式编程的方式,这和 Java 8 中的函数式编程是类似的,这里采用下划线标表示其中的每个元素
val ints2 = a.filter(_ % 2 == 0).map(_ * 10)
for (elem <- ints1) {
println(elem)
@ -134,7 +134,7 @@ object ScalaApp extends App {
## 五、多维数组
和Java中一样多维数组由单维数组组成。
Java 中一样,多维数组由单维数组组成。
```scala
object ScalaApp extends App {
@ -164,7 +164,7 @@ object ScalaApp extends App {
## 六、与Java互操作
由于Scala的数组是使用Java的数组来实现的所以两者之间可以相互转换。
由于 Scala 的数组是使用 Java 的数组来实现的,所以两者之间可以相互转换。
```scala
import java.util
@ -175,9 +175,9 @@ import scala.collection.{JavaConverters, mutable}
object ScalaApp extends App {
val element = ArrayBuffer("hadoop", "spark", "storm")
// ScalaJava
// ScalaJava
val javaList: util.List[String] = JavaConverters.bufferAsJavaList(element)
// JavaScala
// JavaScala
val scalaBuffer: mutable.Buffer[String] = JavaConverters.asScalaBuffer(javaList)
for (elem <- scalaBuffer) {
println(elem)
@ -189,5 +189,5 @@ object ScalaApp extends App {
## 参考资料
1. Martin Odersky . Scala编程(第3版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学Scala(第2版)[M] . 电子工业出版社 . 2017-7
1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7

View File

@ -22,17 +22,17 @@
### 1.1 构造Map
```scala
// 初始化一个空map
// 初始化一个空 map
val scores01 = new HashMap[String, Int]
// 从指定的值初始化Map方式一
// 从指定的值初始化 Map方式一
val scores02 = Map("hadoop" -> 10, "spark" -> 20, "storm" -> 30)
// 从指定的值初始化Map方式二
// 从指定的值初始化 Map方式二
val scores03 = Map(("hadoop", 10), ("spark", 20), ("storm", 30))
```
采用上面方式得到的都是不可变Map(immutable map)想要得到可变Map(mutable map),则需要使用:
采用上面方式得到的都是不可变 Map(immutable map),想要得到可变 Map(mutable map),则需要使用:
```scala
val scores04 = scala.collection.mutable.Map("hadoop" -> 10, "spark" -> 20, "storm" -> 30)
@ -45,7 +45,7 @@ object ScalaApp extends App {
val scores = Map("hadoop" -> 10, "spark" -> 20, "storm" -> 30)
// 1.获取指定key对应的值
// 1.获取指定 key 对应的值
println(scores("hadoop"))
// 2. 如果对应的值不存在则使用默认值
@ -55,17 +55,17 @@ object ScalaApp extends App {
### 1.3 新增/修改/删除值
可变Map允许进行新增、修改、删除等操作。
可变 Map 允许进行新增、修改、删除等操作。
```scala
object ScalaApp extends App {
val scores = scala.collection.mutable.Map("hadoop" -> 10, "spark" -> 20, "storm" -> 30)
// 1.如果key存在则更新
// 1.如果 key 存在则更新
scores("hadoop") = 100
// 2.如果key不存在则新增
// 2.如果 key 不存在则新增
scores("flink") = 40
// 3.可以通过 += 来进行多个更新或新增操作
@ -84,7 +84,7 @@ object ScalaApp extends App {
(hive,50)
```
不可变Map不允许进行新增、修改、删除等操作但是允许由不可变Map产生新的Map。
不可变 Map 不允许进行新增、修改、删除等操作,但是允许由不可变 Map 产生新的 Map。
```scala
object ScalaApp extends App {
@ -125,14 +125,14 @@ object ScalaApp extends App {
### 1.5 yield关键字
可以使用`yield`关键字从现有Map产生新的Map。
可以使用 `yield` 关键字从现有 Map 产生新的 Map。
```scala
object ScalaApp extends App {
val scores = Map("hadoop" -> 10, "spark" -> 20, "storm" -> 30)
// 1.将scores中所有的值扩大10倍
// 1.将 scores 中所有的值扩大 10
val newScore = for ((key, value) <- scores) yield (key, value * 10)
for (elem <- newScore) { println(elem) }
@ -155,16 +155,16 @@ object ScalaApp extends App {
### 1.6 其他Map结构
在使用Map时候如果不指定默认使用的是HashMap如果想要使用`TreeMap`或者`LinkedHashMap`,则需要显式的指定。
在使用 Map 时候,如果不指定,默认使用的是 HashMap如果想要使用 `TreeMap` 或者 `LinkedHashMap`,则需要显式的指定。
```scala
object ScalaApp extends App {
// 1.使用TreeMap,按照键的字典序进行排序
// 1.使用 TreeMap,按照键的字典序进行排序
val scores01 = scala.collection.mutable.TreeMap("B" -> 20, "A" -> 10, "C" -> 30)
for (elem <- scores01) {println(elem)}
// 2.使用LinkedHashMap,按照键值对的插入顺序进行排序
// 2.使用 LinkedHashMap,按照键值对的插入顺序进行排序
val scores02 = scala.collection.mutable.LinkedHashMap("B" -> 20, "A" -> 10, "C" -> 30)
for (elem <- scores02) {println(elem)}
}
@ -192,7 +192,7 @@ object ScalaApp extends App {
// 2. 判断是否为空
println(scores.isEmpty)
// 3. 判断是否包含特定的key
// 3. 判断是否包含特定的 key
println(scores.contains("A"))
}
@ -208,10 +208,10 @@ object ScalaApp extends App {
val scores = Map("hadoop" -> 10, "spark" -> 20, "storm" -> 30)
// scala mapjava map
// scala mapjava map
val javaMap: util.Map[String, Int] = JavaConverters.mapAsJavaMap(scores)
// java mapscala map
// java mapscala map
val scalaMap: mutable.Map[String, Int] = JavaConverters.mapAsScalaMap(javaMap)
for (elem <- scalaMap) {println(elem)}
@ -255,9 +255,9 @@ object ScalaApp extends App {
val array01 = Array("hadoop", "spark", "storm")
val array02 = Array(10, 20, 30)
// 1.zip方法得到的是多个tuple组成的数组
// 1.zip 方法得到的是多个 tuple 组成的数组
val tuples: Array[(String, Int)] = array01.zip(array02)
// 2.也可以在zip后调用toMap方法转换为Map
// 2.也可以在 zip 后调用 toMap 方法转换为 Map
val map: Map[String, Int] = array01.zip(array02).toMap
for (elem <- tuples) { println(elem) }
@ -278,5 +278,5 @@ object ScalaApp extends App {
## 参考资料
1. Martin Odersky . Scala编程(第3版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学Scala(第2版)[M] . 电子工业出版社 . 2017-7
1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7

View File

@ -13,15 +13,15 @@
## 一、模式匹配
Scala支持模式匹配机制可以代替swith语句、执行类型检查、以及支持析构表达式等。
Scala 支持模式匹配机制,可以代替 swith 语句、执行类型检查、以及支持析构表达式等。
### 1.1 更好的swith
Scala不支持swith可以使用模式匹配`match...case`语法代替。但是match语句与Java中的switch有以下三点不同
Scala 不支持 swith可以使用模式匹配 `match...case` 语法代替。但是 match 语句与 Java 中的 switch 有以下三点不同:
- Scala中的case语句支持任何类型而Javacase语句仅支持整型、枚举和字符串常量
- Scala中每个分支语句后面不需要写break因为在case语句中break是隐含的默认就有
- 在Scalamatch语句是有返回值的而Javaswitch语句是没有返回值的。如下
- Scala 中的 case 语句支持任何类型;而 Javacase 语句仅支持整型、枚举和字符串常量;
- Scala 中每个分支语句后面不需要写 break因为在 case 语句中 break 是隐含的,默认就有;
- Scalamatch 语句是有返回值的,而 Javaswitch 语句是没有返回值的。如下:
```scala
object ScalaApp extends App {
@ -66,12 +66,12 @@ object ScalaApp extends App {
object ScalaApp extends App {
def matchTest(x: Any) = x match {
case (0, _, _) => "匹配第一个元素为0的元组"
case (0, _, _) => "匹配第一个元素为 0 的元组"
case (a, b, c) => println(a + "~" + b + "~" + c)
case _ => "other"
}
println(matchTest((0, 1, 2))) // 输出: 匹配第一个元素为0的元组
println(matchTest((0, 1, 2))) // 输出: 匹配第一个元素为 0 的元组
matchTest((1, 2, 3)) // 输出: 1~2~3
println(matchTest(Array(10, 11, 12, 14))) // 输出: other
}
@ -83,33 +83,33 @@ object ScalaApp extends App {
object ScalaApp extends App {
def matchTest[T](x: Array[T]) = x match {
case Array(0) => "匹配只有一个元素0的数组"
case Array(0) => "匹配只有一个元素 0 的数组"
case Array(a, b) => println(a + "~" + b)
case Array(10, _*) => "第一个元素为10的数组"
case Array(10, _*) => "第一个元素为 10 的数组"
case _ => "other"
}
println(matchTest(Array(0))) // 输出: 匹配只有一个元素0的数组
println(matchTest(Array(0))) // 输出: 匹配只有一个元素 0 的数组
matchTest(Array(1, 2)) // 输出: 1~2
println(matchTest(Array(10, 11, 12))) // 输出: 第一个元素为10的数组
println(matchTest(Array(10, 11, 12))) // 输出: 第一个元素为 10 的数组
println(matchTest(Array(3, 2, 1))) // 输出: other
}
```
### 1.4 提取器
数组、列表和元组能使用模式匹配,都是依靠提取器(extractor)机制,它们伴生对象中定义了`unapply``unapplySeq`方法:
数组、列表和元组能使用模式匹配,都是依靠提取器 (extractor) 机制,它们伴生对象中定义了 `unapply``unapplySeq` 方法:
+ **unapply**:用于提取固定数量的对象;
+ **unapplySeq**:用于提取一个序列;
这里以数组为例,`Array.scala`定义了`unapplySeq`方法:
这里以数组为例,`Array.scala` 定义了 `unapplySeq` 方法:
```scala
def unapplySeq[T](x : scala.Array[T]) : scala.Option[scala.IndexedSeq[T]] = { /* compiled code */ }
```
`unapplySeq`返回一个序列,包含数组中的所有值,这样在模式匹配时,才能知道对应位置上的值。
`unapplySeq` 返回一个序列,包含数组中的所有值,这样在模式匹配时,才能知道对应位置上的值。
@ -117,7 +117,7 @@ def unapplySeq[T](x : scala.Array[T]) : scala.Option[scala.IndexedSeq[T]] = { /*
### 2.1 样例类
样例类是一种的特殊的类,它们被经过优化以用于模式匹配,样例类的声明比较简单,只需要在`class`前面加上关键字`case`。下面给出一个样例类及其用于模式匹配的示例:
样例类是一种的特殊的类,它们被经过优化以用于模式匹配,样例类的声明比较简单,只需要在 `class` 前面加上关键字 `case`。下面给出一个样例类及其用于模式匹配的示例:
```scala
//声明一个抽象类
@ -125,27 +125,27 @@ abstract class Person{}
```
```scala
// 样例类Employee
// 样例类 Employee
case class Employee(name: String, age: Int, salary: Double) extends Person {}
```
```scala
// 样例类Student
// 样例类 Student
case class Student(name: String, age: Int) extends Person {}
```
当你声明样例类后,编译器自动进行以下配置:
- 构造器中每个参数都默认为`val`
- 自动地生成`equals, hashCode, toString, copy`等方法;
- 伴生对象中自动生成`apply`方法使得可以不用new关键字就能构造出相应的对象
- 伴生对象中自动生成`unapply`方法,以支持模式匹配。
- 构造器中每个参数都默认为 `val`
- 自动地生成 `equals, hashCode, toString, copy` 等方法;
- 伴生对象中自动生成 `apply` 方法,使得可以不用 new 关键字就能构造出相应的对象;
- 伴生对象中自动生成 `unapply` 方法,以支持模式匹配。
除了上面的特征外,样例类和其他类相同,可以任意添加方法和字段,扩展它们。
### 2.3 用于模式匹配
样例的伴生对象中自动生成`unapply`方法,所以样例类可以支持模式匹配,使用如下:
样例的伴生对象中自动生成 `unapply` 方法,所以样例类可以支持模式匹配,使用如下:
```scala
object ScalaApp extends App {
@ -167,6 +167,6 @@ object ScalaApp extends App {
## 参考资料
1. Martin Odersky . Scala编程(第3版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学Scala(第2版)[M] . 电子工业出版社 . 2017-7
1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7

View File

@ -13,7 +13,7 @@
## 一、条件表达式if
Scala中的if/else语法结构与Java中的一样唯一不同的是Scala中的if表达式是有返回值的。
Scala 中的 if/else 语法结构与 Java 中的一样唯一不同的是Scala 中的 if 表达式是有返回值的。
```scala
object ScalaApp extends App {
@ -25,13 +25,13 @@ object ScalaApp extends App {
}
```
在Java中每行语句都需要使用`;`表示结束但是在Scala中并不需要。除非你在单行语句中写了多行代码。
Java 中,每行语句都需要使用 `;` 表示结束,但是在 Scala 中并不需要。除非你在单行语句中写了多行代码。
## 二、块表达式
在Scala中可以使用`{}`块包含一系列表达式,块中最后一个表达式的值就是块的值。
Scala 中,可以使用 `{}` 块包含一系列表达式,块中最后一个表达式的值就是块的值。
```scala
object ScalaApp extends App {
@ -45,7 +45,7 @@ object ScalaApp extends App {
// 输出: 6
```
如果块中的最后一个表达式没有返回值则块的返回值是Unit类型。
如果块中的最后一个表达式没有返回值,则块的返回值是 Unit 类型。
```scala
scala> val result ={ val a = 1 + 1; val b = 2 + 2 }
@ -56,7 +56,7 @@ result: Unit = ()
## 三、循环表达式while
Scala和大多数语言一样支持`while``do ... while`表达式。
Scala 和大多数语言一样,支持 `while``do ... while` 表达式。
```scala
object ScalaApp extends App {
@ -79,7 +79,7 @@ object ScalaApp extends App {
## 四、循环表达式for
for循环的基本使用如下
for 循环的基本使用如下:
```scala
object ScalaApp extends App {
@ -96,7 +96,7 @@ object ScalaApp extends App {
}
```
除了基本使用外,还可以使用`yield`关键字从for循环中产生Vector这称为for推导式。
除了基本使用外,还可以使用 `yield` 关键字从 for 循环中产生 Vector这称为 for 推导式。
```scala
scala> for (i <- 1 to 10) yield i * 6
@ -107,7 +107,7 @@ res1: scala.collection.immutable.IndexedSeq[Int] = Vector(6, 12, 18, 24, 30, 36,
## 五、异常处理try
和Java中一样支持`try...catch...finally`语句。
Java 中一样,支持 `try...catch...finally` 语句。
```scala
import java.io.{FileNotFoundException, FileReader}
@ -126,13 +126,13 @@ object ScalaApp extends App {
}
```
这里需要注意的是因为finally语句一定会被执行所以不要在该语句中返回值否则返回值会被作为整个try语句的返回值如下
这里需要注意的是因为 finally 语句一定会被执行,所以不要在该语句中返回值,否则返回值会被作为整个 try 语句的返回值,如下:
```scala
scala> def g():Int = try return 1 finally return 2
g: ()Int
// 方法g()总会返回2
// 方法 g() 总会返回 2
scala> g()
res3: Int = 2
```
@ -141,7 +141,7 @@ res3: Int = 2
## 六、条件选择表达式match
match类似于java中的switch语句。
match 类似于 java 中的 switch 语句。
```scala
object ScalaApp extends App {
@ -160,11 +160,11 @@ object ScalaApp extends App {
```
但是与Java中的switch有以下三点不同
但是与 Java 中的 switch 有以下三点不同:
+ Scala中的case语句支持任何类型而Javacase语句仅支持整型、枚举和字符串常量
+ Scala中每个分支语句后面不需要写break因为在case语句中break是隐含的默认就有
+ 在Scalamatch语句是有返回值的而Javaswitch语句是没有返回值的。如下
+ Scala 中的 case 语句支持任何类型;而 Javacase 语句仅支持整型、枚举和字符串常量;
+ Scala 中每个分支语句后面不需要写 break因为在 case 语句中 break 是隐含的,默认就有;
+ 在 Scalamatch 语句是有返回值的,而 Javaswitch 语句是没有返回值的。如下:
```scala
object ScalaApp extends App {
@ -188,13 +188,13 @@ object ScalaApp extends App {
## 七、没有break和continue
额外注意一下Scala中并不支持Java中的breakcontinue关键字。
额外注意一下Scala 中并不支持 Java 中的 breakcontinue 关键字。
## 八、输入与输出
在Scala中可以使用print、println、printf打印输出这与Java中是一样的。如果需要从控制台中获取输入则可以使用`StdIn`中定义的各种方法。
Scala 中可以使用 print、println、printf 打印输出,这与 Java 中是一样的。如果需要从控制台中获取输入,则可以使用 `StdIn` 中定义的各种方法。
```scala
val name = StdIn.readLine("Your name: ")
@ -207,5 +207,5 @@ println(s"Hello, ${name}! Next year, you will be ${age + 1}.")
## 参考资料
1. Martin Odersky . Scala编程(第3版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学Scala(第2版)[M] . 电子工业出版社 . 2017-7
1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7

View File

@ -10,7 +10,7 @@
### 1.1 概念
Scala全称为Scalable Language即“可伸缩的语言”之所以这样命名是因为它的设计目标是希望伴随着用户的需求一起成长。Scala是一门综合了**面向对象**和**函数式编程概念**的**静态类型**的编程语言它运行在标准的Java平台上可以与所有的Java类库无缝协作。
Scala 全称为 Scalable Language即“可伸缩的语言”之所以这样命名是因为它的设计目标是希望伴随着用户的需求一起成长。Scala 是一门综合了**面向对象**和**函数式编程概念**的**静态类型**的编程语言,它运行在标准的 Java 平台上,可以与所有的 Java 类库无缝协作。
@ -18,11 +18,11 @@ Scala全称为Scalable Language即“可伸缩的语言”之所以这样
#### 1. Scala是面向对象的
Scala是一种面向对象的语言每个值都是对象每个方法都是调用。举例来说如果你执行`1+2`则对于Scala而言实际是在调用Int类里定义的名为`+`的方法。
Scala 是一种面向对象的语言,每个值都是对象,每个方法都是调用。举例来说,如果你执行 `1+2`,则对于 Scala 而言,实际是在调用 Int 类里定义的名为 `+` 的方法。
#### 2. Scala是函数式的
Scala不只是一门纯的面对对象的语言它也是功能完整的函数式编程语言。函数式编程以两大核心理念为指导
Scala 不只是一门纯的面对对象的语言,它也是功能完整的函数式编程语言。函数式编程以两大核心理念为指导:
+ 函数是一等公民;
+ 程序中的操作应该将输入值映射成输出值,而不是当场修改数据。即方法不应该有副作用。
@ -33,19 +33,19 @@ Scala不只是一门纯的面对对象的语言它也是功能完整的函数
#### 1. 与Java的兼容
Scala可以与Java无缝对接其在执行时会被编译成JVM字节码这使得其性能与Java相当。Scala可以直接调用Java中的方法、访问Java中的字段、继承Java类、实现Java接口。Scala重度复用并包装了原生的Java类型并支持隐式转换。
Scala 可以与 Java 无缝对接,其在执行时会被编译成 JVM 字节码,这使得其性能与 Java 相当。Scala 可以直接调用 Java 中的方法、访问 Java 中的字段、继承 Java 类、实现 Java 接口。Scala 重度复用并包装了原生的 Java 类型,并支持隐式转换。
#### 2. 精简的语法
Scala的程序通常比较简洁相比Java而言代码行数会大大减少这使得程序员对代码的阅读和理解更快缺陷也更少。
Scala 的程序通常比较简洁,相比 Java 而言,代码行数会大大减少,这使得程序员对代码的阅读和理解更快,缺陷也更少。
#### 3. 高级语言的特性
Scala具有高级语言的特定对代码进行了高级别的抽象能够让你更好地控制程序的复杂度保证开发的效率。
Scala 具有高级语言的特定,对代码进行了高级别的抽象,能够让你更好地控制程序的复杂度,保证开发的效率。
#### 4. 静态类型
Scala拥有非常先进的静态类型系统Scala不仅拥有与Java类似的允许嵌套类的类型系统还支持使用泛型对类型进行参数化用交集intersection来组合类型以及使用抽象类型来进行隐藏类型的细节。通过这些特性可以更快地设计出安全易用的程序和接口。
Scala 拥有非常先进的静态类型系统Scala 不仅拥有与 Java 类似的允许嵌套类的类型系统还支持使用泛型对类型进行参数化用交集intersection来组合类型以及使用抽象类型来进行隐藏类型的细节。通过这些特性可以更快地设计出安全易用的程序和接口。
@ -55,11 +55,11 @@ Scala拥有非常先进的静态类型系统Scala不仅拥有与Java类似的
### 2.1 前置条件
Scala的运行依赖于JDKScala 2.12.x需要JDK 1.8+。
Scala 的运行依赖于 JDKScala 2.12.x 需要 JDK 1.8+。
### 2.2 安装Scala插件
IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打开 IDEA依次点击 **File** => **settings**=> **plugins** 选项卡搜索Scala插件(如下图)。找到插件后进行安装并重启IDEA使得安装生效。
IDEA 默认不支持 Scala 语言的开发,需要通过插件进行扩展。打开 IDEA依次点击 **File** => **settings**=> **plugins** 选项卡,搜索 Scala 插件 (如下图)。找到插件后进行安装,并重启 IDEA 使得安装生效。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/idea-scala-plugin.png"/> </div>
@ -67,7 +67,7 @@ IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打
### 2.3 创建Scala项目
在IDEA中依次点击 **File** => **New** => **Project** 选项卡,然后选择创建`Scala—IDEA`工程:
IDEA 中依次点击 **File** => **New** => **Project** 选项卡,然后选择创建 `Scala—IDEA` 工程:
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/idea-newproject-scala.png"/> </div>
@ -77,7 +77,7 @@ IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打
#### 1. 方式一
此时看到`Scala SDK`为空,依次点击`Create` => `Download` ,选择所需的版本后,点击`OK`按钮进行下载,下载完成点击`Finish`进入工程。
此时看到 `Scala SDK` 为空,依次点击 `Create` => `Download` ,选择所需的版本后,点击 `OK` 按钮进行下载,下载完成点击 `Finish` 进入工程。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/idea-scala-select.png"/> </div>
@ -85,15 +85,15 @@ IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打
#### 2. 方式二
方式一是Scala官方安装指南里使用的方式但下载速度通常比较慢且这种安装下并没有直接提供Scala命令行工具。所以个人推荐到官网下载安装包进行安装下载地址https://www.scala-lang.org/download/
方式一是 Scala 官方安装指南里使用的方式,但下载速度通常比较慢,且这种安装下并没有直接提供 Scala 命令行工具。所以个人推荐到官网下载安装包进行安装下载地址https://www.scala-lang.org/download/
这里我的系统是Windows下载msi版本的安装包后一直点击下一步进行安装安装完成后会自动配置好环境变量。
这里我的系统是 Windows下载 msi 版本的安装包后,一直点击下一步进行安装,安装完成后会自动配置好环境变量。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-other-resources.png"/> </div>
由于安装时已经自动配置好环境变量所以IDEA会自动选择对应版本的SDK。
由于安装时已经自动配置好环境变量,所以 IDEA 会自动选择对应版本的 SDK。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/idea-scala-2.1.8.png"/> </div>
@ -101,7 +101,7 @@ IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打
### 2.5 创建Hello World
在工程 `src`目录上右击 **New** => **Scala class** 创建`Hello.scala`。输入代码如下,完成后点击运行按钮,成功运行则代表搭建成功。
在工程 `src` 目录上右击 **New** => **Scala class** 创建 `Hello.scala`。输入代码如下,完成后点击运行按钮,成功运行则代表搭建成功。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-hello-world.png"/> </div>
@ -111,7 +111,7 @@ IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打
### 2.6 切换Scala版本
在日常的开发中由于对应软件如Spark的版本切换可能导致需要切换Scala的版本则可以在`Project Structures`中的`Global Libraries`选项卡中进行切换。
在日常的开发中,由于对应软件(如 Spark的版本切换可能导致需要切换 Scala 的版本,则可以在 `Project Structures` 中的 `Global Libraries` 选项卡中进行切换。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/idea-scala-change.png"/> </div>
@ -121,7 +121,7 @@ IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打
### 2.7 使用scala命令行
采用`msi`方式安装,程序会自动配置好环境变量。此时可以直接使用命令行工具:
采用 `msi` 方式安装,程序会自动配置好环境变量。此时可以直接使用命令行工具:
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-shell.png"/> </div>
@ -129,5 +129,5 @@ IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打
## 参考资料
1. Martin Odersky(著),高宇翔(译) . Scala编程(第3版)[M] . 电子工业出版社 . 2018-1-1
1. Martin Odersky(著),高宇翔 (译) . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
2. https://www.scala-lang.org/download/

View File

@ -17,36 +17,36 @@
## 一、初识类和对象
Scala的类与Java的类具有非常多的相似性示例如下
Scala 的类与 Java 的类具有非常多的相似性,示例如下:
```scala
// 1. 在scala中类不需要用public声明,所有的类都具有公共的可见性
// 1. 在 scala 中,类不需要用 public 声明,所有的类都具有公共的可见性
class Person {
// 2. 声明私有变量,用var修饰的变量默认拥有getter/setter属性
// 2. 声明私有变量,用 var 修饰的变量默认拥有 getter/setter 属性
private var age = 0
// 3.如果声明的变量不需要进行初始赋值此时Scala就无法进行类型推断所以需要显式指明类型
// 3.如果声明的变量不需要进行初始赋值,此时 Scala 就无法进行类型推断,所以需要显式指明类型
private var name: String = _
// 4. 定义方法,应指明传参类型。返回值类型不是必须的Scala可以自动推断出来但是为了方便调用者建议指明
// 4. 定义方法,应指明传参类型。返回值类型不是必须的Scala 可以自动推断出来,但是为了方便调用者,建议指明
def growUp(step: Int): Unit = {
age += step
}
// 5.对于改值器方法(即改变对象状态的方法),即使不需要传入参数,也建议在声明中包含()
// 5.对于改值器方法 (即改变对象状态的方法),即使不需要传入参数,也建议在声明中包含 ()
def growUpFix(): Unit = {
age += 10
}
// 6.对于取值器方法(即不会改变对象状态的方法),不必在声明中包含()
// 6.对于取值器方法 (即不会改变对象状态的方法),不必在声明中包含 ()
def currentAge: Int = {
age
}
/**
* 7.不建议使用return关键字,默认方法中最后一行代码的计算结果为返回值
* 7.不建议使用 return 关键字,默认方法中最后一行代码的计算结果为返回值
* 如果方法很简短,甚至可以写在同一行中
*/
def getName: String = name
@ -60,11 +60,11 @@ object Person {
def main(args: Array[String]): Unit = {
// 8.创建类的实例
val counter = new Person()
// 9.用var修饰的变量默认拥有getter/setter属性可以直接对其进行赋值
// 9.用 var 修饰的变量默认拥有 getter/setter 属性,可以直接对其进行赋值
counter.age = 12
counter.growUp(8)
counter.growUpFix()
// 10.用var修饰的变量默认拥有getter/setter属性可以直接对其进行取值输出: 30
// 10.用 var 修饰的变量默认拥有 getter/setter 属性,可以直接对其进行取值,输出: 30
println(counter.age)
// 输出: 30
println(counter.currentAge)
@ -81,17 +81,17 @@ object Person {
### 2.1 成员变量可见性
Scala中成员变量的可见性默认都是public如果想要保证其不被外部干扰可以声明为private并通过gettersetter方法进行访问。
Scala 中成员变量的可见性默认都是 public如果想要保证其不被外部干扰可以声明为 private并通过 gettersetter 方法进行访问。
### 2.2 getter和setter属性
gettersetter属性与声明变量时使用的关键字有关
gettersetter 属性与声明变量时使用的关键字有关:
+ 使用var关键字变量同时拥有gettersetter属性
+ 使用val关键字变量只拥有getter属性
+ 使用private[this]变量既没有getter属性、也没有setter属性只能通过内部的方法访问
+ 使用 var 关键字:变量同时拥有 gettersetter 属性;
+ 使用 val 关键字:变量只拥有 getter 属性;
+ 使用 private[this]:变量既没有 getter 属性、也没有 setter 属性,只能通过内部的方法访问;
需要特别说明的是假设变量名为age,则其对应的getset的方法名分别叫做` age``age_=`
需要特别说明的是:假设变量名为 age,则其对应的 getset 的方法名分别叫做 ` age``age_=`
```scala
class Person {
@ -99,7 +99,7 @@ class Person {
private val name = "heibaiying"
private var age = 12
private[this] var birthday = "2019-08-08"
// birthday只能被内部方法所访问
// birthday 只能被内部方法所访问
def getBirthday: String = birthday
}
@ -116,7 +116,7 @@ object Person {
> 解释说明:
>
> 示例代码中`person.age=30`在执行时内部实际是调用了方法`person.age_=(30) `,而`person.age`内部执行时实际是调用了`person.age()`方法。想要证明这一点,可以对代码进行反编译。同时为了说明成员变量可见性的问题,我们对下面这段代码进行反编译:
> 示例代码中 `person.age=30` 在执行时内部实际是调用了方法 `person.age_=(30) `,而 `person.age` 内部执行时实际是调用了 `person.age()` 方法。想要证明这一点,可以对代码进行反编译。同时为了说明成员变量可见性的问题,我们对下面这段代码进行反编译:
>
> ```scala
> class Person {
@ -132,7 +132,7 @@ object Person {
> > javap -private Person
> ```
>
> 编译结果如下从编译结果可以看到实际的getset的方法名(因为JVM不允许在方法名中出现所以它被翻译成$eq)同时也验证了成员变量默认的可见性为public。
> 编译结果如下,从编译结果可以看到实际的 getset 的方法名 (因为 JVM 不允许在方法名中出现=,所以它被翻译成$eq),同时也验证了成员变量默认的可见性为 public。
>
> ```java
> Compiled from "Person.scala"
@ -152,7 +152,7 @@ object Person {
### 2.3 @BeanProperty
在上面的例子中可以看到我们是使用`.`来对成员变量进行访问的如果想要额外生成和Java中一样的getXXXsetXXX方法则需要使用@BeanProperty进行注解
在上面的例子中可以看到我们是使用 `.` 来对成员变量进行访问的,如果想要额外生成和 Java 中一样的 getXXXsetXXX 方法,则需要使用@BeanProperty 进行注解。
```scala
class Person {
@ -172,15 +172,15 @@ object Person {
### 2.4 主构造器
和Java不同的是Scala类的主构造器直接写在类名后面但注意以下两点
Java 不同的是Scala 类的主构造器直接写在类名后面,但注意以下两点:
+ 主构造器传入的参数默认就是val类型的即不可变你没有办法在内部改变传参
+ 写在主构造器中的代码块会在类初始化的时候被执行功能类似于Java的静态代码块`static{}`
+ 主构造器传入的参数默认就是 val 类型的,即不可变,你没有办法在内部改变传参;
+ 写在主构造器中的代码块会在类初始化的时候被执行,功能类似于 Java 的静态代码块 `static{}`
```scala
class Person(val name: String, val age: Int) {
println("功能类似于Java的静态代码块static{}")
println("功能类似于 Java 的静态代码块 static{}")
def getDetail: String = {
//name="heibai" 无法通过编译
@ -196,7 +196,7 @@ object Person {
}
输出
功能类似于Java的静态代码块static{}
功能类似于 Java 的静态代码块 static{}
heibaiying:20
```
@ -206,7 +206,7 @@ heibaiying:20
辅助构造器有两点硬性要求:
+ 辅助构造器的名称必须为this
+ 辅助构造器的名称必须为 this
+ 每个辅助构造器必须以主构造器或其他的辅助构造器的调用开始。
```scala
@ -214,14 +214,14 @@ class Person(val name: String, val age: Int) {
private var birthday = ""
// 1.辅助构造器的名称必须为this
// 1.辅助构造器的名称必须为 this
def this(name: String, age: Int, birthday: String) {
// 2.每个辅助构造器必须以主构造器或其他的辅助构造器的调用开始
this(name, age)
this.birthday = birthday
}
// 3.重写toString方法
// 3.重写 toString 方法
override def toString: String = name + ":" + age + ":" + birthday
}
@ -236,7 +236,7 @@ object Person {
### 2.6 方法传参不可变
在Scala中方法传参默认是val类型即不可变这意味着你在方法体内部不能改变传入的参数。这和Scala的设计理念有关Scala遵循函数式编程理念强调方法不应该有副作用。
Scala 中,方法传参默认是 val 类型,即不可变,这意味着你在方法体内部不能改变传入的参数。这和 Scala 的设计理念有关Scala 遵循函数式编程理念,强调方法不应该有副作用。
```scala
class Person() {
@ -252,29 +252,29 @@ class Person() {
## 三、对象
Scala中的object(对象)主要有以下几个作用:
Scala 中的 object(对象) 主要有以下几个作用:
+ 因为object中的变量和方法都是静态的所以可以用于存放工具类
+ 因为 object 中的变量和方法都是静态的,所以可以用于存放工具类;
+ 可以作为单例对象的容器;
+ 可以作为类的伴生对象;
+ 可以拓展类或特质;
+ 可以拓展Enumeration来实现枚举。
+ 可以拓展 Enumeration 来实现枚举。
### 3.1 工具类&单例&全局静态常量&拓展特质
这里我们创建一个对象`Utils`,代码如下:
这里我们创建一个对象 `Utils`,代码如下:
```scala
object Utils {
/*
*1. 相当于Java中的静态代码块static,会在对象初始化时候被执行
*1. 相当于 Java 中的静态代码块 static,会在对象初始化时候被执行
* 这种方式实现的单例模式是饿汉式单例,即无论你的单例对象是否被用到,
* 都在一开始被初始化完成
*/
val person = new Person
// 2. 全局固定常量 等价于Javapublic static final
// 2. 全局固定常量 等价于 Javapublic static final
val CONSTANT = "固定常量"
// 3. 全局静态方法
@ -284,18 +284,18 @@ object Utils {
}
```
其中Person类代码如下
其中 Person 类代码如下:
```scala
class Person() {
println("Person默认构造器被调用")
println("Person 默认构造器被调用")
}
```
新建测试类:
```scala
// 1.ScalaApp对象扩展自trait App
// 1.ScalaApp 对象扩展自 trait App
object ScalaApp extends App {
// 2.验证单例
@ -310,7 +310,7 @@ object ScalaApp extends App {
}
// 输出如下:
Person默认构造器被调用
Person 默认构造器被调用
true
固定常量
abcdefg
@ -318,7 +318,7 @@ abcdefg
### 3.2 伴生对象
在Java中你通常会用到既有实例方法又有静态方法的类在Scala中可以通过类和与类同名的伴生对象来实现。类和伴生对象必须存在与同一个文件中。
Java 中,你通常会用到既有实例方法又有静态方法的类,在 Scala 中,可以通过类和与类同名的伴生对象来实现。类和伴生对象必须存在与同一个文件中。
```scala
class Person() {
@ -353,23 +353,23 @@ object Person {
### 3.3 实现枚举类
Scala中没有直接提供枚举类需要通过扩展`Enumeration`并调用其中的Value方法对所有枚举值进行初始化来实现。
Scala 中没有直接提供枚举类,需要通过扩展 `Enumeration`,并调用其中的 Value 方法对所有枚举值进行初始化来实现。
```scala
object Color extends Enumeration {
// 1.类型别名,建议声明,在import时有用
// 1.类型别名,建议声明,在 import 时有用
type Color = Value
// 2.调用Value方法
// 2.调用 Value 方法
val GREEN = Value
// 3.只传入id
// 3.只传入 id
val RED = Value(3)
// 4.只传入值
val BULE = Value("blue")
// 5.传入id和值
// 5.传入 id 和值
val YELLOW = Value(5, "yellow")
// 6. 不传入id时,id为上一个声明变量的id+1,值默认和变量名相同
// 6. 不传入 id 时,id 为上一个声明变量的 id+1,值默认和变量名相同
val PINK = Value
}
@ -407,6 +407,6 @@ true
## 参考资料
1. Martin Odersky . Scala编程(第3版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学Scala(第2版)[M] . 电子工业出版社 . 2017-7
1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7

View File

@ -1,467 +1,467 @@
# 类型参数
<nav>
<a href="#一泛型">一、泛型</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-泛型类">1.1 泛型类</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-泛型方法">1.2 泛型方法</a><br/>
<a href="#二类型限定">二、类型限定</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-类型上界限定">2.1 类型上界限定</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-视图界定">2.2 视图界定 </a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-类型约束">2.3 类型约束</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#24-上下文界定">2.4 上下文界定</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#25-ClassTag上下文界定">2.5 ClassTag上下文界定</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#26-类型下界限定">2.6 类型下界限定</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#27-多重界定">2.7 多重界定</a><br/>
<a href="#三Ordering--Ordered">三、Ordering & Ordered</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-Comparable">3.1 Comparable</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-Comparator">3.2 Comparator</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#33-上下文界定的优点">3.3 上下文界定的优点</a><br/>
<a href="#四通配符">四、通配符</a><br/>
</nav>
## 一、泛型
Scala支持类型参数化使得我们能够编写泛型程序。
### 1.1 泛型类
Java中使用`<>`符号来包含定义的类型参数Scala则使用`[]`
```scala
class Pair[T, S](val first: T, val second: S) {
override def toString: String = first + ":" + second
}
```
```scala
object ScalaApp extends App {
// 使用时候你直接指定参数类型,也可以不指定,由程序自动推断
val pair01 = new Pair("heibai01", 22)
val pair02 = new Pair[String,Int]("heibai02", 33)
println(pair01)
println(pair02)
}
```
### 1.2 泛型方法
函数和方法也支持类型参数。
```scala
object Utils {
def getHalf[T](a: Array[T]): Int = a.length / 2
}
```
## 二、类型限定
### 2.1 类型上界限定
ScalaJava一样对于对象之间进行大小比较要求被比较的对象实现`java.lang.Comparable`接口。所以如果想对泛型进行比较,需要限定类型上界为`java.lang.Comparable`,语法为` S <: T`,代表类型S是类型T的子类或其本身。示例如下:
```scala
// 使用 <: 符号,限定T必须是Comparable[T]的子类型
class Pair[T <: Comparable[T]](val first: T, val second: T) {
// 返回较小的值
def smaller: T = if (first.compareTo(second) < 0) first else second
}
```
```scala
// 测试代码
val pair = new Pair("abc", "abcd")
println(pair.smaller) // 输出 abc
```
>扩展如果你想要在Java中实现类型变量限定需要使用关键字extends来实现等价的Java代码如下
>
>```java
>public class Pair<T extends Comparable<T>> {
> private T first;
> private T second;
> Pair(T first, T second) {
> this.first = first;
> this.second = second;
> }
> public T smaller() {
> return first.compareTo(second) < 0 ? first : second;
> }
>}
>```
### 2.2 视图界定
在上面的例子中如果你使用Int类型或者Double等类型进行测试点击运行后你会发现程序根本无法通过编译
```scala
val pair1 = new Pair(10, 12)
val pair2 = new Pair(10.0, 12.0)
```
之所以出现这样的问题是因为Scala中的Int类并没有实现Comparable接口。在Scala中直接继承Comparable接口的是特质Ordered它在继承compareTo方法的基础上额外定义了关系符方法源码如下:
```scala
// 除了compareTo方法外还提供了额外的关系符方法
trait Ordered[A] extends Any with java.lang.Comparable[A] {
def compare(that: A): Int
def < (that: A): Boolean = (this compare that) < 0
def > (that: A): Boolean = (this compare that) > 0
def <= (that: A): Boolean = (this compare that) <= 0
def >= (that: A): Boolean = (this compare that) >= 0
def compareTo(that: A): Int = compare(that)
}
```
之所以在日常的编程中之所以你能够执行`3>2`这样的判断操作,是因为程序执行了定义在`Predef`中的隐式转换方法`intWrapper(x: Int) `将Int类型转换为RichInt类型而RichInt间接混入了Ordered特质所以能够进行比较。
```scala
// Predef.scala
@inline implicit def intWrapper(x: Int) = new runtime.RichInt(x)
```
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-richInt.png"/> </div>
要想解决传入数值无法进行比较的问题,可以使用视图界定。语法为`T <% U`,代表T能够通过隐式转换转为U即允许Int型参数在无法进行比较的时候转换为RichInt类型。示例如下
```scala
// 视图界定符号 <%
class Pair[T <% Comparable[T]](val first: T, val second: T) {
// 返回较小的值
def smaller: T = if (first.compareTo(second) < 0) first else second
}
```
> 注由于直接继承JavaComparable接口的是特质Ordered所以如下的视图界定和上面是等效的
>
> ```scala
> // 隐式转换为Ordered[T]
> class Pair[T <% Ordered[T]](val first: T, val second: T) {
> def smaller: T = if (first.compareTo(second) < 0) first else second
> }
> ```
### 2.3 类型约束
如果你用的Scala2.11+,会发现视图界定已被标识为废弃。官方推荐使用类型约束(type constraint)来实现同样的功能,其本质是使用隐式参数进行隐式转换,示例如下:
```scala
// 1.使用隐式参数隐式转换为Comparable[T]
class Pair[T](val first: T, val second: T)(implicit ev: T => Comparable[T])
def smaller: T = if (first.compareTo(second) < 0) first else second
}
// 2.由于直接继承JavaComparable接口的是特质Ordered所以也可以隐式转换为Ordered[T]
class Pair[T](val first: T, val second: T)(implicit ev: T => Ordered[T]) {
def smaller: T = if (first.compareTo(second) < 0) first else second
}
```
当然,隐式参数转换也可以运用在具体的方法上:
```scala
object PairUtils{
def smaller[T](a: T, b: T)(implicit order: T => Ordered[T]) = if (a < b) a else b
}
```
### 2.4 上下文界定
上下文界定的形式为`T:M`,其中M是一个泛型它要求必须存在一个类型为M[T]的隐式值,当你声明一个带隐式参数的方法时,需要定义一个隐式默认值。所以上面的程序也可以使用上下文界定进行改写:
```scala
class Pair[T](val first: T, val second: T) {
// 请注意 这个地方用的是Ordering[T]而上面视图界定和类型约束用的是Ordered[T],两者的区别会在后文给出解释
def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second
}
// 测试
val pair= new Pair(88, 66)
println(pair.smaller) //输出66
```
在上面的示例中我们无需手动添加隐式默认值就可以完成转换这是因为Scala自动引入了Ordering[Int]这个隐式值。为了更好的说明上下文界定,下面给出一个自定义类型的比较示例:
```scala
// 1.定义一个人员类
class Person(val name: String, val age: Int) {
override def toString: String = name + ":" + age
}
// 2.继承Ordering[T],实现自定义比较器,按照自己的规则重写比较方法
class PersonOrdering extends Ordering[Person] {
override def compare(x: Person, y: Person): Int = if (x.age > y.age) 1 else -1
}
class Pair[T](val first: T, val second: T) {
def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second
}
object ScalaApp extends App {
val pair = new Pair(new Person("hei", 88), new Person("bai", 66))
// 3.定义隐式默认值,如果不定义,则下一行代码无法通过编译
implicit val ImpPersonOrdering = new PersonOrdering
println(pair.smaller) //输出: bai:66
}
```
### 2.5 ClassTag上下文界定
这里先看一个例子:下面这段代码,没有任何语法错误,但是在运行时会抛出异常:`Error: cannot find class tag for element type T`, 这是由于ScalaJava一样都存在类型擦除即**泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉**。对于下面的代码在运行阶段创建Array时你必须明确指明其类型但是此时泛型信息已经被擦除导致出现找不到类型的异常。
```scala
object ScalaApp extends App {
def makePair[T](first: T, second: T) = {
// 创建以一个数组 并赋值
val r = new Array[T](2); r(0) = first; r(1) = second; r
}
}
```
Scala针对这个问题提供了ClassTag上下文界定即把泛型的信息存储在ClassTag中这样在运行阶段需要时只需要从ClassTag中进行获取即可。其语法为`T : ClassTag`,示例如下:
```scala
import scala.reflect._
object ScalaApp extends App {
def makePair[T : ClassTag](first: T, second: T) = {
val r = new Array[T](2); r(0) = first; r(1) = second; r
}
}
```
### 2.6 类型下界限定
2.1小节介绍了类型上界的限定Scala同时也支持下界的限定语法为`U >: T`,即U必须是类型T的超类或本身。
```scala
// 首席执行官
class CEO
// 部门经理
class Manager extends CEO
// 本公司普通员工
class Employee extends Manager
// 其他公司人员
class OtherCompany
object ScalaApp extends App {
// 限定:只有本公司部门经理以上人员才能获取权限
def Check[T >: Manager](t: T): T = {
println("获得审核权限")
t
}
// 错误写法: 省略泛型参数后,以下所有人都能获得权限,显然这是不正确的
Check(new CEO)
Check(new Manager)
Check(new Employee)
Check(new OtherCompany)
// 正确写法,传入泛型参数
Check[CEO](new CEO)
Check[Manager](new Manager)
/*
* 以下两条语句无法通过编译,异常信息为:
* do not conform to method Check's type parameter bounds(不符合方法Check的类型参数边界)
* 这种情况就完成了下界限制,即只有本公司经理及以上的人员才能获得审核权限
*/
Check[Employee](new Employee)
Check[OtherCompany](new OtherCompany)
}
```
### 2.7 多重界定
+ 类型变量可以同时有上界和下界。 写法为 `T > : Lower <: Upper`
+ 不能同时有多个上界或多个下界 。但可以要求一个类型实现多个特质,写法为 :
`T < : Comparable[T] with Serializable with Cloneable`
+ 你可以有多个上下文界定,写法为`T : Ordering : ClassTag` 。
## 三、Ordering & Ordered
上文中使用到OrderingOrdered特质它们最主要的区别在于分别继承自不同的Java接口ComparableComparator
+ **Comparable**:可以理解为内置的比较器,实现此接口的对象可以与自身进行比较;
+ **Comparator**:可以理解为外置的比较器;当对象自身并没有定义比较规则的时候,可以传入外部比较器进行比较。
为什么Java中要同时给出这两个比较接口这是因为你要比较的对象不一定实现了Comparable接口而你又想对其进行比较这时候当然你可以修改代码实现Comparable但是如果这个类你无法修改(如源码中的类)这时候就可以使用外置的比较器。同样的问题在Scala中当然也会出现所以Scala分别使用了OrderingOrdered来继承它们。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-ordered-ordering.png"/> </div>
下面分别给出JavaComparableComparator接口的使用示例
### 3.1 Comparable
```java
import java.util.Arrays;
// 实现Comparable接口
public class Person implements Comparable<Person> {
private String name;
private int age;
Person(String name,int age) {this.name=name;this.age=age;}
@Override
public String toString() { return name+":"+age; }
// 核心的方法是重写比较规则,按照年龄进行排序
@Override
public int compareTo(Person person) {
return this.age - person.age;
}
public static void main(String[] args) {
Person[] peoples= {new Person("hei", 66), new Person("bai", 55), new Person("ying", 77)};
Arrays.sort(peoples);
Arrays.stream(peoples).forEach(System.out::println);
}
}
输出:
bai:55
hei:66
ying:77
```
### 3.2 Comparator
```java
import java.util.Arrays;
import java.util.Comparator;
public class Person {
private String name;
private int age;
Person(String name,int age) {this.name=name;this.age=age;}
@Override
public String toString() { return name+":"+age; }
public static void main(String[] args) {
Person[] peoples= {new Person("hei", 66), new Person("bai", 55), new Person("ying", 77)};
// 这里为了直观直接使用匿名内部类,实现Comparator接口
//如果是Java8你也可以写成Arrays.sort(peoples, Comparator.comparingInt(o -> o.age));
Arrays.sort(peoples, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.age-o2.age;
}
});
Arrays.stream(peoples).forEach(System.out::println);
}
}
```
使用外置比较器还有一个好处,就是你可以随时定义其排序规则:
```scala
// 按照年龄大小排序
Arrays.sort(peoples, Comparator.comparingInt(o -> o.age));
Arrays.stream(peoples).forEach(System.out::println);
// 按照名字长度倒序排列
Arrays.sort(peoples, Comparator.comparingInt(o -> -o.name.length()));
Arrays.stream(peoples).forEach(System.out::println);
```
### 3.3 上下文界定的优点
这里再次给出上下文界定中的示例代码作为回顾:
```scala
// 1.定义一个人员类
class Person(val name: String, val age: Int) {
override def toString: String = name + ":" + age
}
// 2.继承Ordering[T],实现自定义比较器,这个比较器就是一个外置比较器
class PersonOrdering extends Ordering[Person] {
override def compare(x: Person, y: Person): Int = if (x.age > y.age) 1 else -1
}
class Pair[T](val first: T, val second: T) {
def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second
}
object ScalaApp extends App {
val pair = new Pair(new Person("hei", 88), new Person("bai", 66))
// 3.在当前上下文定义隐式默认值,这就相当于传入了外置比较器
implicit val ImpPersonOrdering = new PersonOrdering
println(pair.smaller) //输出: bai:66
}
```
使用上下文界定和Ordering带来的好处是传入`Pair`中的参数不一定需要可比较,只要在比较时传入外置比较器即可。
需要注意的是由于隐式默认值二义性的限制你不能像上面Java代码一样在同一个上下文作用域中传入两个外置比较器即下面的代码是无法通过编译的。但是你可以在不同的上下文作用域中引入不同的隐式默认值即使用不同的外置比较器。
```scala
implicit val ImpPersonOrdering = new PersonOrdering
println(pair.smaller)
implicit val ImpPersonOrdering2 = new PersonOrdering
println(pair.smaller)
```
## 四、通配符
在实际编码中通常需要把泛型限定在某个范围内比如限定为某个类及其子类。因此ScalaJava一样引入了通配符这个概念用于限定泛型的范围。不同的是Java使用`?`表示通配符Scala使用`_`表示通配符。
```scala
class Ceo(val name: String) {
override def toString: String = name
}
class Manager(name: String) extends Ceo(name)
class Employee(name: String) extends Manager(name)
class Pair[T](val first: T, val second: T) {
override def toString: String = "first:" + first + ", second: " + second
}
object ScalaApp extends App {
// 限定部门经理及以下的人才可以组队
def makePair(p: Pair[_ <: Manager]): Unit = {println(p)}
makePair(new Pair(new Employee("heibai"), new Manager("ying")))
}
```
目前Scala中的通配符在某些复杂情况下还不完善如下面的语句在Scala 2.12 中并不能通过编译:
```scala
def min[T <: Comparable[_ >: T]](p: Pair[T]) ={}
```
可以使用以下语法代替:
```scala
type SuperComparable[T] = Comparable[_ >: T]
def min[T <: SuperComparable[T]](p: Pair[T]) = {}
```
## 参考资料
1. Martin Odersky . Scala编程(3)[M] . 电子工业出版社 . 2018-1-1
2. .S.霍斯特曼 . 快学Scala(2)[M] . 电子工业出版社 . 2017-7
# 类型参数
<nav>
<a href="#一泛型">一、泛型</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-泛型类">1.1 泛型类</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-泛型方法">1.2 泛型方法</a><br/>
<a href="#二类型限定">二、类型限定</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-类型上界限定">2.1 类型上界限定</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-视图界定">2.2 视图界定 </a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-类型约束">2.3 类型约束</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#24-上下文界定">2.4 上下文界定</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#25-ClassTag上下文界定">2.5 ClassTag上下文界定</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#26-类型下界限定">2.6 类型下界限定</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#27-多重界定">2.7 多重界定</a><br/>
<a href="#三Ordering--Ordered">三、Ordering & Ordered</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-Comparable">3.1 Comparable</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-Comparator">3.2 Comparator</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#33-上下文界定的优点">3.3 上下文界定的优点</a><br/>
<a href="#四通配符">四、通配符</a><br/>
</nav>
## 一、泛型
Scala 支持类型参数化,使得我们能够编写泛型程序。
### 1.1 泛型类
Java 中使用 `<>` 符号来包含定义的类型参数Scala 则使用 `[]`
```scala
class Pair[T, S](val first: T, val second: S) {
override def toString: String = first + ":" + second
}
```
```scala
object ScalaApp extends App {
// 使用时候你直接指定参数类型,也可以不指定,由程序自动推断
val pair01 = new Pair("heibai01", 22)
val pair02 = new Pair[String,Int]("heibai02", 33)
println(pair01)
println(pair02)
}
```
### 1.2 泛型方法
函数和方法也支持类型参数。
```scala
object Utils {
def getHalf[T](a: Array[T]): Int = a.length / 2
}
```
## 二、类型限定
### 2.1 类型上界限定
ScalaJava 一样,对于对象之间进行大小比较,要求被比较的对象实现 `java.lang.Comparable` 接口。所以如果想对泛型进行比较,需要限定类型上界为 `java.lang.Comparable`,语法为 ` S <: T`,代表类型 S 是类型 T 的子类或其本身。示例如下:
```scala
// 使用 <: 符号,限定 T 必须是 Comparable[T]的子类型
class Pair[T <: Comparable[T]](val first: T, val second: T) {
// 返回较小的值
def smaller: T = if (first.compareTo(second) < 0) first else second
}
```
```scala
// 测试代码
val pair = new Pair("abc", "abcd")
println(pair.smaller) // 输出 abc
```
>扩展:如果你想要在 Java 中实现类型变量限定,需要使用关键字 extends 来实现,等价的 Java 代码如下:
>
>```java
>public class Pair<T extends Comparable<T>> {
> private T first;
> private T second;
> Pair(T first, T second) {
> this.first = first;
> this.second = second;
> }
> public T smaller() {
> return first.compareTo(second) < 0 ? first : second;
> }
>}
>```
### 2.2 视图界定
在上面的例子中,如果你使用 Int 类型或者 Double 等类型进行测试,点击运行后,你会发现程序根本无法通过编译:
```scala
val pair1 = new Pair(10, 12)
val pair2 = new Pair(10.0, 12.0)
```
之所以出现这样的问题,是因为 Scala 中的 Int 类并没有实现 Comparable 接口。在 Scala 中直接继承 Comparable 接口的是特质 Ordered它在继承 compareTo 方法的基础上,额外定义了关系符方法,源码如下:
```scala
// 除了 compareTo 方法外,还提供了额外的关系符方法
trait Ordered[A] extends Any with java.lang.Comparable[A] {
def compare(that: A): Int
def < (that: A): Boolean = (this compare that) < 0
def > (that: A): Boolean = (this compare that) > 0
def <= (that: A): Boolean = (this compare that) <= 0
def >= (that: A): Boolean = (this compare that) >= 0
def compareTo(that: A): Int = compare(that)
}
```
之所以在日常的编程中之所以你能够执行 `3>2` 这样的判断操作,是因为程序执行了定义在 `Predef` 中的隐式转换方法 `intWrapper(x: Int) `,将 Int 类型转换为 RichInt 类型,而 RichInt 间接混入了 Ordered 特质,所以能够进行比较。
```scala
// Predef.scala
@inline implicit def intWrapper(x: Int) = new runtime.RichInt(x)
```
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-richInt.png"/> </div>
要想解决传入数值无法进行比较的问题,可以使用视图界定。语法为 `T <% U`,代表 T 能够通过隐式转换转为 U即允许 Int 型参数在无法进行比较的时候转换为 RichInt 类型。示例如下:
```scala
// 视图界定符号 <%
class Pair[T <% Comparable[T]](val first: T, val second: T) {
// 返回较小的值
def smaller: T = if (first.compareTo(second) < 0) first else second
}
```
> 注:由于直接继承 JavaComparable 接口的是特质 Ordered所以如下的视图界定和上面是等效的
>
> ```scala
> // 隐式转换为 Ordered[T]
> class Pair[T <% Ordered[T]](val first: T, val second: T) {
> def smaller: T = if (first.compareTo(second) < 0) first else second
> }
> ```
### 2.3 类型约束
如果你用的 Scala2.11+,会发现视图界定已被标识为废弃。官方推荐使用类型约束 (type constraint) 来实现同样的功能,其本质是使用隐式参数进行隐式转换,示例如下:
```scala
// 1.使用隐式参数隐式转换为 Comparable[T]
class Pair[T](val first: T, val second: T)(implicit ev: T => Comparable[T])
def smaller: T = if (first.compareTo(second) < 0) first else second
}
// 2.由于直接继承 JavaComparable 接口的是特质 Ordered所以也可以隐式转换为 Ordered[T]
class Pair[T](val first: T, val second: T)(implicit ev: T => Ordered[T]) {
def smaller: T = if (first.compareTo(second) < 0) first else second
}
```
当然,隐式参数转换也可以运用在具体的方法上:
```scala
object PairUtils{
def smaller[T](a: T, b: T)(implicit order: T => Ordered[T]) = if (a < b) a else b
}
```
### 2.4 上下文界定
上下文界定的形式为 `T:M`,其中 M 是一个泛型,它要求必须存在一个类型为 M[T]的隐式值,当你声明一个带隐式参数的方法时,需要定义一个隐式默认值。所以上面的程序也可以使用上下文界定进行改写:
```scala
class Pair[T](val first: T, val second: T) {
// 请注意 这个地方用的是 Ordering[T],而上面视图界定和类型约束,用的是 Ordered[T],两者的区别会在后文给出解释
def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second
}
// 测试
val pair= new Pair(88, 66)
println(pair.smaller) //输出66
```
在上面的示例中,我们无需手动添加隐式默认值就可以完成转换,这是因为 Scala 自动引入了 Ordering[Int]这个隐式值。为了更好的说明上下文界定,下面给出一个自定义类型的比较示例:
```scala
// 1.定义一个人员类
class Person(val name: String, val age: Int) {
override def toString: String = name + ":" + age
}
// 2.继承 Ordering[T],实现自定义比较器,按照自己的规则重写比较方法
class PersonOrdering extends Ordering[Person] {
override def compare(x: Person, y: Person): Int = if (x.age > y.age) 1 else -1
}
class Pair[T](val first: T, val second: T) {
def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second
}
object ScalaApp extends App {
val pair = new Pair(new Person("hei", 88), new Person("bai", 66))
// 3.定义隐式默认值,如果不定义,则下一行代码无法通过编译
implicit val ImpPersonOrdering = new PersonOrdering
println(pair.smaller) //输出: bai:66
}
```
### 2.5 ClassTag上下文界定
这里先看一个例子:下面这段代码,没有任何语法错误,但是在运行时会抛出异常:`Error: cannot find class tag for element type T`, 这是由于 ScalaJava 一样,都存在类型擦除,即**泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉**。对于下面的代码,在运行阶段创建 Array 时,你必须明确指明其类型,但是此时泛型信息已经被擦除,导致出现找不到类型的异常。
```scala
object ScalaApp extends App {
def makePair[T](first: T, second: T) = {
// 创建以一个数组 并赋值
val r = new Array[T](2); r(0) = first; r(1) = second; r
}
}
```
Scala 针对这个问题,提供了 ClassTag 上下文界定,即把泛型的信息存储在 ClassTag 中,这样在运行阶段需要时,只需要从 ClassTag 中进行获取即可。其语法为 `T : ClassTag`,示例如下:
```scala
import scala.reflect._
object ScalaApp extends App {
def makePair[T : ClassTag](first: T, second: T) = {
val r = new Array[T](2); r(0) = first; r(1) = second; r
}
}
```
### 2.6 类型下界限定
2.1 小节介绍了类型上界的限定Scala 同时也支持下界的限定,语法为:`U >: T`,即 U 必须是类型 T 的超类或本身。
```scala
// 首席执行官
class CEO
// 部门经理
class Manager extends CEO
// 本公司普通员工
class Employee extends Manager
// 其他公司人员
class OtherCompany
object ScalaApp extends App {
// 限定:只有本公司部门经理以上人员才能获取权限
def Check[T >: Manager](t: T): T = {
println("获得审核权限")
t
}
// 错误写法: 省略泛型参数后,以下所有人都能获得权限,显然这是不正确的
Check(new CEO)
Check(new Manager)
Check(new Employee)
Check(new OtherCompany)
// 正确写法,传入泛型参数
Check[CEO](new CEO)
Check[Manager](new Manager)
/*
* 以下两条语句无法通过编译,异常信息为:
* do not conform to method Check's type parameter bounds(不符合方法 Check 的类型参数边界)
* 这种情况就完成了下界限制,即只有本公司经理及以上的人员才能获得审核权限
*/
Check[Employee](new Employee)
Check[OtherCompany](new OtherCompany)
}
```
### 2.7 多重界定
+ 类型变量可以同时有上界和下界。 写法为 `T > : Lower <: Upper`
+ 不能同时有多个上界或多个下界 。但可以要求一个类型实现多个特质,写法为 :
`T < : Comparable[T] with Serializable with Cloneable`
+ 你可以有多个上下文界定,写法为 `T : Ordering : ClassTag` 。
## 三、Ordering & Ordered
上文中使用到 OrderingOrdered 特质,它们最主要的区别在于分别继承自不同的 Java 接口ComparableComparator
+ **Comparable**:可以理解为内置的比较器,实现此接口的对象可以与自身进行比较;
+ **Comparator**:可以理解为外置的比较器;当对象自身并没有定义比较规则的时候,可以传入外部比较器进行比较。
为什么 Java 中要同时给出这两个比较接口,这是因为你要比较的对象不一定实现了 Comparable 接口,而你又想对其进行比较,这时候当然你可以修改代码实现 Comparable但是如果这个类你无法修改 (如源码中的类),这时候就可以使用外置的比较器。同样的问题在 Scala 中当然也会出现,所以 Scala 分别使用了 OrderingOrdered 来继承它们。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-ordered-ordering.png"/> </div>
下面分别给出 JavaComparableComparator 接口的使用示例:
### 3.1 Comparable
```java
import java.util.Arrays;
// 实现 Comparable 接口
public class Person implements Comparable<Person> {
private String name;
private int age;
Person(String name,int age) {this.name=name;this.age=age;}
@Override
public String toString() { return name+":"+age; }
// 核心的方法是重写比较规则,按照年龄进行排序
@Override
public int compareTo(Person person) {
return this.age - person.age;
}
public static void main(String[] args) {
Person[] peoples= {new Person("hei", 66), new Person("bai", 55), new Person("ying", 77)};
Arrays.sort(peoples);
Arrays.stream(peoples).forEach(System.out::println);
}
}
输出:
bai:55
hei:66
ying:77
```
### 3.2 Comparator
```java
import java.util.Arrays;
import java.util.Comparator;
public class Person {
private String name;
private int age;
Person(String name,int age) {this.name=name;this.age=age;}
@Override
public String toString() { return name+":"+age; }
public static void main(String[] args) {
Person[] peoples= {new Person("hei", 66), new Person("bai", 55), new Person("ying", 77)};
// 这里为了直观直接使用匿名内部类,实现 Comparator 接口
//如果是 Java8 你也可以写成 Arrays.sort(peoples, Comparator.comparingInt(o -> o.age));
Arrays.sort(peoples, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.age-o2.age;
}
});
Arrays.stream(peoples).forEach(System.out::println);
}
}
```
使用外置比较器还有一个好处,就是你可以随时定义其排序规则:
```scala
// 按照年龄大小排序
Arrays.sort(peoples, Comparator.comparingInt(o -> o.age));
Arrays.stream(peoples).forEach(System.out::println);
// 按照名字长度倒序排列
Arrays.sort(peoples, Comparator.comparingInt(o -> -o.name.length()));
Arrays.stream(peoples).forEach(System.out::println);
```
### 3.3 上下文界定的优点
这里再次给出上下文界定中的示例代码作为回顾:
```scala
// 1.定义一个人员类
class Person(val name: String, val age: Int) {
override def toString: String = name + ":" + age
}
// 2.继承 Ordering[T],实现自定义比较器,这个比较器就是一个外置比较器
class PersonOrdering extends Ordering[Person] {
override def compare(x: Person, y: Person): Int = if (x.age > y.age) 1 else -1
}
class Pair[T](val first: T, val second: T) {
def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second
}
object ScalaApp extends App {
val pair = new Pair(new Person("hei", 88), new Person("bai", 66))
// 3.在当前上下文定义隐式默认值,这就相当于传入了外置比较器
implicit val ImpPersonOrdering = new PersonOrdering
println(pair.smaller) //输出: bai:66
}
```
使用上下文界定和 Ordering 带来的好处是:传入 `Pair` 中的参数不一定需要可比较,只要在比较时传入外置比较器即可。
需要注意的是由于隐式默认值二义性的限制,你不能像上面 Java 代码一样,在同一个上下文作用域中传入两个外置比较器,即下面的代码是无法通过编译的。但是你可以在不同的上下文作用域中引入不同的隐式默认值,即使用不同的外置比较器。
```scala
implicit val ImpPersonOrdering = new PersonOrdering
println(pair.smaller)
implicit val ImpPersonOrdering2 = new PersonOrdering
println(pair.smaller)
```
## 四、通配符
在实际编码中,通常需要把泛型限定在某个范围内,比如限定为某个类及其子类。因此 ScalaJava 一样引入了通配符这个概念,用于限定泛型的范围。不同的是 Java 使用 `?` 表示通配符Scala 使用 `_` 表示通配符。
```scala
class Ceo(val name: String) {
override def toString: String = name
}
class Manager(name: String) extends Ceo(name)
class Employee(name: String) extends Manager(name)
class Pair[T](val first: T, val second: T) {
override def toString: String = "first:" + first + ", second: " + second
}
object ScalaApp extends App {
// 限定部门经理及以下的人才可以组队
def makePair(p: Pair[_ <: Manager]): Unit = {println(p)}
makePair(new Pair(new Employee("heibai"), new Manager("ying")))
}
```
目前 Scala 中的通配符在某些复杂情况下还不完善,如下面的语句在 Scala 2.12 中并不能通过编译:
```scala
def min[T <: Comparable[_ >: T]](p: Pair[T]) ={}
```
可以使用以下语法代替:
```scala
type SuperComparable[T] = Comparable[_ >: T]
def min[T <: SuperComparable[T]](p: Pair[T]) = {}
```
## 参考资料
1. Martin Odersky . Scala 编程 ( 3 )[M] . 电子工业出版社 . 2018-1-1
2. .S.霍斯特曼 . 快学 Scala( 2 )[M] . 电子工业出版社 . 2017-7

View File

@ -20,50 +20,50 @@
### 1.1 Scala中的继承结构
Scala中继承关系如下图
Scala 中继承关系如下图:
+ Any是整个继承关系的根节点
+ AnyRef包含Scala ClassesJava Classes等价于Java中的java.lang.Object
+ AnyVal是所有值类型的一个标记
+ Null是所有引用类型的子类型唯一实例是null可以将null赋值给除了值类型外的所有类型的变量;
+ Nothing是所有类型的子类型。
+ Any 是整个继承关系的根节点;
+ AnyRef 包含 Scala ClassesJava Classes等价于 Java 中的 java.lang.Object
+ AnyVal 是所有值类型的一个标记;
+ Null 是所有引用类型的子类型,唯一实例是 null可以将 null 赋值给除了值类型外的所有类型的变量;
+ Nothing 是所有类型的子类型。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala继承层次.png"/> </div>
### 1.2 extends & override
Scala的集成机制和Java有很多相似之处比如都使用`extends`关键字表示继承,都使用`override`关键字表示重写父类的方法或成员变量。示例如下:
Scala 的集成机制和 Java 有很多相似之处,比如都使用 `extends` 关键字表示继承,都使用 `override` 关键字表示重写父类的方法或成员变量。示例如下:
```scala
//父类
class Person {
var name = ""
// 1.不加任何修饰词,默认为public,能被子类和外部访问
// 1.不加任何修饰词,默认为 public,能被子类和外部访问
var age = 0
// 2.使用protected修饰的变量能子类访问但是不能被外部访问
// 2.使用 protected 修饰的变量能子类访问,但是不能被外部访问
protected var birthday = ""
// 3.使用private修饰的变量不能被子类和外部访问
// 3.使用 private 修饰的变量不能被子类和外部访问
private var sex = ""
def setSex(sex: String): Unit = {
this.sex = sex
}
// 4.重写父类的方法建议使用override关键字修饰
// 4.重写父类的方法建议使用 override 关键字修饰
override def toString: String = name + ":" + age + ":" + birthday + ":" + sex
}
```
使用`extends`关键字实现继承:
使用 `extends` 关键字实现继承:
```scala
// 1.使用extends关键字实现继承
// 1.使用 extends 关键字实现继承
class Employee extends Person {
override def toString: String = "Employee~" + super.toString
// 2.使用publicprotected关键字修饰的变量能被子类访问
// 2.使用 publicprotected 关键字修饰的变量能被子类访问
def setBirthday(date: String): Unit = {
birthday = date
}
@ -92,7 +92,7 @@ object ScalaApp extends App {
### 1.3 调用超类构造器
在Scala的类中每个辅助构造器都必须首先调用其他构造器或主构造器这样就导致了子类的辅助构造器永远无法直接调用超类的构造器只有主构造器才能调用超类的构造器。所以想要调用超类的构造器代码示例如下
Scala 的类中,每个辅助构造器都必须首先调用其他构造器或主构造器,这样就导致了子类的辅助构造器永远无法直接调用超类的构造器,只有主构造器才能调用超类的构造器。所以想要调用超类的构造器,代码示例如下:
```scala
class Employee(name:String,age:Int,salary:Double) extends Person(name:String,age:Int) {
@ -102,7 +102,7 @@ class Employee(name:String,age:Int,salary:Double) extends Person(name:String,age
### 1.4 类型检查和转换
想要实现类检查可以使用`isInstanceOf`,判断一个实例是否来源于某个类或者其子类,如果是,则可以使用`asInstanceOf`进行强制类型转换。
想要实现类检查可以使用 `isInstanceOf`,判断一个实例是否来源于某个类或者其子类,如果是,则可以使用 `asInstanceOf` 进行强制类型转换。
```scala
object ScalaApp extends App {
@ -117,7 +117,7 @@ object ScalaApp extends App {
// 2. 强制类型转换
var p: Person = employee.asInstanceOf[Person]
// 3. 判断一个实例是否来源于某个类(而不是其子类)
// 3. 判断一个实例是否来源于某个类 (而不是其子类)
println(employee.getClass == classOf[Employee])
}
@ -127,7 +127,7 @@ object ScalaApp extends App {
#### **1. 构造顺序**
在Scala中还有一个需要注意的问题如果你在子类中重写父类的val变量并且超类的构造器中使用了该变量那么可能会产生不可预期的错误。下面给出一个示例
Scala 中还有一个需要注意的问题,如果你在子类中重写父类的 val 变量,并且超类的构造器中使用了该变量,那么可能会产生不可预期的错误。下面给出一个示例:
```scala
// 父类
@ -151,7 +151,7 @@ object ScalaApp extends App {
}
```
这里初始化array用到了变量range这里你会发现实际上array既不会被初始化Array(10)也不会被初始化为Array(2),实际的输出应该如下:
这里初始化 array 用到了变量 range这里你会发现实际上 array 既不会被初始化 Array(10),也不会被初始化为 Array(2),实际的输出应该如下:
```properties
父类的默认构造器
@ -159,19 +159,19 @@ object ScalaApp extends App {
()
```
可以看到array被初始化为Array(0),主要原因在于父类构造器的执行顺序先于子类构造器,这里给出实际的执行步骤:
可以看到 array 被初始化为 Array(0),主要原因在于父类构造器的执行顺序先于子类构造器,这里给出实际的执行步骤:
1. 父类的构造器被调用,执行`new Array[Int](range)`语句;
2. 这里想要得到range的值会去调用子类range()方法,因为`override val`重写变量的同时也重写了其get方法
3. 调用子类的range()方法自然也是返回子类的range值但是由于子类的构造器还没有执行这也就意味着对range赋值的`range = 2`语句还没有被执行所以自然返回range的默认值也就是0。
1. 父类的构造器被调用,执行 `new Array[Int](range)` 语句;
2. 这里想要得到 range 的值,会去调用子类 range() 方法,因为 `override val` 重写变量的同时也重写了其 get 方法;
3. 调用子类的 range() 方法,自然也是返回子类的 range 值,但是由于子类的构造器还没有执行,这也就意味着对 range 赋值的 `range = 2` 语句还没有被执行,所以自然返回 range 的默认值,也就是 0。
这里可能比较疑惑的是为什么`val range = 2`没有被执行却能使用range变量这里因为在虚拟机层面是先对成员变量先分配存储空间并赋给默认值之后才赋予给定的值。想要证明这一点其实也比较简单代码如下:
这里可能比较疑惑的是为什么 `val range = 2` 没有被执行,却能使用 range 变量,这里因为在虚拟机层面,是先对成员变量先分配存储空间并赋给默认值,之后才赋予给定的值。想要证明这一点其实也比较简单,代码如下:
```scala
class Person {
// val range: Int = 10 正常代码 arrayArray(10)
// val range: Int = 10 正常代码 arrayArray(10)
val array: Array[Int] = new Array[Int](range)
val range: Int = 10 //如果把变量的声明放在使用之后此时数据arrayarray(0)
val range: Int = 10 //如果把变量的声明放在使用之后,此时数据 arrayarray(0)
}
object Person {
@ -186,15 +186,15 @@ object Person {
想要解决上面的问题,有以下几种方法:
(1) . 将变量用final修饰代表不允许被子类重写`final val range: Int = 10 `
(1) . 将变量用 final 修饰,代表不允许被子类重写,即 `final val range: Int = 10 `
(2) . 将变量使用lazy修饰代表懒加载即只有当你实际使用到array时候才去进行初始化
(2) . 将变量使用 lazy 修饰,代表懒加载,即只有当你实际使用到 array 时候,才去进行初始化;
```scala
lazy val array: Array[Int] = new Array[Int](range)
```
(3) . 采用提前定义代码如下代表range的定义优先于超类构造器。
(3) . 采用提前定义,代码如下,代表 range 的定义优先于超类构造器。
```scala
class Employee extends {
@ -208,13 +208,13 @@ class Employee extends {
但是这种语法也有其限制:你只能在上面代码块中重写已有的变量,而不能定义新的变量和方法,定义新的变量和方法只能写在下面代码块中。
>**注意事项**:类的继承和下文特质(trait)的继承都存在这个问题,也同样可以通过提前定义来解决。虽然如此,但还是建议合理设计以规避该类问题。
>**注意事项**:类的继承和下文特质 (trait) 的继承都存在这个问题,也同样可以通过提前定义来解决。虽然如此,但还是建议合理设计以规避该类问题。
<br/>
## 二、抽象类
Scala中允许使用`abstract`定义抽象类,并且通过`extends`关键字继承它。
Scala 中允许使用 `abstract` 定义抽象类,并且通过 `extends` 关键字继承它。
定义抽象类:
@ -227,7 +227,7 @@ abstract class Person {
// 2.定义抽象方法
def geDetail: String
// 3. scala的抽象类允许定义具体方法
// 3. scala 的抽象类允许定义具体方法
def print(): Unit = {
println("抽象类中的默认方法")
}
@ -254,10 +254,10 @@ class Employee extends Person {
### 3.1 trait & with
Scala中没有interface这个关键字想要实现类似的功能可以使用特质(trait)。trait等价于Java 8中的接口因为trait中既能定义抽象方法也能定义具体方法这和Java 8中的接口是类似的。
Scala 中没有 interface 这个关键字,想要实现类似的功能,可以使用特质 (trait)。trait 等价于 Java 8 中的接口,因为 trait 中既能定义抽象方法,也能定义具体方法,这和 Java 8 中的接口是类似的。
```scala
// 1.特质使用trait关键字修饰
// 1.特质使用 trait 关键字修饰
trait Logger {
// 2.定义抽象方法
@ -270,10 +270,10 @@ trait Logger {
}
```
想要使用特质,需要使用`extends`关键字,而不是`implements`关键字,如果想要添加多个特质,可以使用`with`关键字。
想要使用特质,需要使用 `extends` 关键字,而不是 `implements` 关键字,如果想要添加多个特质,可以使用 `with` 关键字。
```scala
// 1.使用extends关键字,而不是implements,如果想要添加多个特质可以使用with关键字
// 1.使用 extends 关键字,而不是 implements,如果想要添加多个特质,可以使用 with 关键字
class ConsoleLogger extends Logger with Serializable with Cloneable {
// 2. 实现特质中的抽象方法
@ -310,7 +310,7 @@ class InfoLogger extends Logger {
### 3.3 带有特质的对象
Scala支持在类定义的时混入`父类trait`,而在类实例化为具体对象的时候指明其实际使用的`子类trait`。示例如下:
Scala 支持在类定义的时混入 ` 父类 trait`,而在类实例化为具体对象的时候指明其实际使用的 ` 子类 trait`。示例如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala带有特质的对象.png"/> </div>
@ -327,7 +327,7 @@ trait Logger {
trait ErrorLogger
```scala
// 错误日志打印继承自Logger
// 错误日志打印,继承自 Logger
trait ErrorLogger extends Logger {
// 覆盖空方法
override def log(msg: String): Unit = {
@ -339,7 +339,7 @@ trait ErrorLogger extends Logger {
trait InfoLogger
```scala
// 通知日志打印继承自Logger
// 通知日志打印,继承自 Logger
trait InfoLogger extends Logger {
// 覆盖空方法
@ -352,7 +352,7 @@ trait InfoLogger extends Logger {
具体的使用类:
```scala
// 混入trait Logger
// 混入 trait Logger
class Person extends Logger {
// 调用定义的抽象方法
def printDetail(detail: String): Unit = {
@ -361,12 +361,12 @@ class Person extends Logger {
}
```
这里通过main方法来测试
这里通过 main 方法来测试:
```scala
object ScalaApp extends App {
// 使用with指明需要具体使用的trait
// 使用 with 指明需要具体使用的 trait
val person01 = new Person with InfoLogger
val person02 = new Person with ErrorLogger
val person03 = new Person with InfoLogger with ErrorLogger
@ -377,30 +377,30 @@ object ScalaApp extends App {
}
```
这里前面两个输出比较明显,因为只指明了一个具体的`trait`,这里需要说明的是第三个输出,**因为trait的调用是由右到左开始生效的**,所以这里打印出`Error:scala`
这里前面两个输出比较明显,因为只指明了一个具体的 `trait`,这里需要说明的是第三个输出,**因为 trait 的调用是由右到左开始生效的**,所以这里打印出 `Error:scala`
### 3.4 特质构造顺序
`trait`有默认的无参构造器,但是不支持有参构造器。一个类混入多个特质后初始化顺序应该如下:
`trait` 有默认的无参构造器,但是不支持有参构造器。一个类混入多个特质后初始化顺序应该如下:
```scala
// 示例
class Employee extends Person with InfoLogger with ErrorLogger {...}
```
1. 超类首先被构造即Person的构造器首先被执行
1. 超类首先被构造,即 Person 的构造器首先被执行;
2. 特质的构造器在超类构造器之前,在类构造器之后;特质由左到右被构造;每个特质中,父特质首先被构造;
+ Logger构造器执行LoggerInfoLogger的父类
+ InfoLogger构造器执行
+ ErrorLogger构造器执行;
+ Logger 构造器执行LoggerInfoLogger 的父类);
+ InfoLogger 构造器执行;
+ ErrorLogger 构造器执行;
3. 所有超类和特质构造完毕,子类才会被构造。
<br/>
## 参考资料
1. Martin Odersky . Scala编程(第3版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学Scala(第2版)[M] . 电子工业出版社 . 2017-7
1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7

View File

@ -16,7 +16,7 @@
### 1.1 使用隐式转换
隐式转换指的是以`implicit`关键字声明带有单个参数的转换函数,它将值从一种类型转换为另一种类型,以便使用之前类型所没有的功能。示例如下:
隐式转换指的是以 `implicit` 关键字声明带有单个参数的转换函数,它将值从一种类型转换为另一种类型,以便使用之前类型所没有的功能。示例如下:
```scala
// 普通人
@ -31,7 +31,7 @@ class Thor(val name: String) {
}
object Thor extends App {
// 定义隐式转换方法 将普通人转换为雷神 通常建议方法名使用source2Target,即被转换对象To转换对象
// 定义隐式转换方法 将普通人转换为雷神 通常建议方法名使用 source2Target,即:被转换对象 To 转换对象
implicit def person2Thor(p: Person): Thor = new Thor(p.name)
// 这样普通人也能举起雷神之锤
new Person("普通人").hammer()
@ -44,7 +44,7 @@ object Thor extends App {
### 1.2 隐式转换规则
并不是你使用`implicit`转换后,隐式转换就一定会发生,比如上面如果不调用`hammer()`方法的时候,普通人就还是普通人。通常程序会在以下情况下尝试执行隐式转换:
并不是你使用 `implicit` 转换后,隐式转换就一定会发生,比如上面如果不调用 `hammer()` 方法的时候,普通人就还是普通人。通常程序会在以下情况下尝试执行隐式转换:
+ 当对象访问一个不存在的成员时,即调用的方法不存在或者访问的成员变量不存在;
+ 当对象调用某个方法,该方法存在,但是方法的声明参数与传入参数不匹配时。
@ -52,7 +52,7 @@ object Thor extends App {
而在以下三种情况下编译器不会尝试执行隐式转换:
+ 如果代码能够在不使用隐式转换的前提下通过编译,则不会使用隐式转换;
+ 编译器不会尝试同时执行多个转换,比如`convert1(convert2(a))*b`
+ 编译器不会尝试同时执行多个转换,比如 `convert1(convert2(a))*b`
+ 转换存在二义性,也不会发生转换。
这里首先解释一下二义性,上面的代码进行如下修改,由于两个隐式转换都是生效的,所以就存在了二义性:
@ -97,16 +97,16 @@ object ImplicitTest extends App {
new ClassC
}
// 这行代码无法通过编译因为要调用到printB方法需要执行两次转换C2B(D2C(ClassD))
// 这行代码无法通过编译,因为要调用到 printB 方法,需要执行两次转换 C2B(D2C(ClassD))
new ClassD().printB(new ClassA)
/*
* 下面的这一行代码虽然也进行了两次隐式转换,但是两次的转换对象并不是一个对象,所以它是生效的:
* 转换流程如下:
* 1. ClassC中并没有printB方法,因此隐式转换为ClassB,然后调用printB方法;
* 2. 但是printB参数类型为ClassB,然而传入的参数类型是ClassA,所以需要将参数ClassA转换为ClassB,这是第二次;
* 1. ClassC 中并没有 printB 方法,因此隐式转换为 ClassB,然后调用 printB 方法;
* 2. 但是 printB 参数类型为 ClassB,然而传入的参数类型是 ClassA,所以需要将参数 ClassA 转换为 ClassB,这是第二次;
* 即: C2B(ClassC) -> ClassB.printB(ClassA) -> ClassB.printB(A2B(ClassA)) -> ClassB.printB(ClassB)
* 转换过程1的对象是ClassC,而转换过程2的转换对象是ClassA,所以虽然是一行代码两次转换,但是仍然是有效转换
* 转换过程 1 的对象是 ClassC,而转换过程 2 的转换对象是 ClassA,所以虽然是一行代码两次转换,但是仍然是有效转换
*/
new ClassC().printB(new ClassA)
}
@ -163,7 +163,7 @@ object Convert {
```
```scala
// 导入Convert下所有的隐式转换函数
// 导入 Convert 下所有的隐式转换函数
import com.heibaiying.Convert._
object ScalaApp extends App {
@ -171,7 +171,7 @@ object ScalaApp extends App {
}
```
> Scala自身的隐式转换函数大部分定义在`Predef.scala`中你可以打开源文件查看也可以在Scala交互式命令行中采用`:implicit -v`查看全部隐式转换函数。
> Scala 自身的隐式转换函数大部分定义在 `Predef.scala` 中,你可以打开源文件查看,也可以在 Scala 交互式命令行中采用 `:implicit -v` 查看全部隐式转换函数。
<br/>
@ -179,7 +179,7 @@ object ScalaApp extends App {
### 2.1 使用隐式参数
在定义函数或方法时可以使用标记为`implicit`的参数,这种情况下,编译器将会查找默认值,提供给函数调用。
在定义函数或方法时可以使用标记为 `implicit` 的参数,这种情况下,编译器将会查找默认值,提供给函数调用。
```scala
// 定义分隔符类
@ -200,20 +200,20 @@ object ScalaApp extends App {
关于隐式参数,有两点需要注意:
1.我们上面定义`formatted`函数的时候使用了柯里化,如果你不使用柯里化表达式,按照通常习惯只有下面两种写法:
1.我们上面定义 `formatted` 函数的时候使用了柯里化,如果你不使用柯里化表达式,按照通常习惯只有下面两种写法:
```scala
// 这种写法没有语法错误,但是无法通过编译
def formatted(implicit context: String, deli: Delimiters): Unit = {
println(deli.left + context + deli.right)
}
// 不存在这种写法IDEA直接会直接提示语法错误
// 不存在这种写法IDEA 直接会直接提示语法错误
def formatted( context: String, implicit deli: Delimiters): Unit = {
println(deli.left + context + deli.right)
}
```
上面第一种写法编译的时候会出现下面所示`error`信息,从中也可以看出`implicit`是作用于参数列表中每个参数的,这显然不是我们想要到达的效果,所以上面的写法采用了柯里化。
上面第一种写法编译的时候会出现下面所示 `error` 信息,从中也可以看出 `implicit` 是作用于参数列表中每个参数的,这显然不是我们想要到达的效果,所以上面的写法采用了柯里化。
```
not enough arguments for method formatted:
@ -228,7 +228,7 @@ implicit val brace = new Delimiters("{", "}")
formatted("this is context")
```
上面代码无法通过编译,出现错误提示`ambiguous implicit values`,即隐式值存在冲突。
上面代码无法通过编译,出现错误提示 `ambiguous implicit values`,即隐式值存在冲突。
@ -291,7 +291,7 @@ object ScalaApp extends App {
def smaller[T] (a: T, b: T) = if (a < b) a else b
```
在Scala中如果定义了一个如上所示的比较对象大小的泛型方法你会发现无法通过编译。对于对象之间进行大小比较ScalaJava一样都要求被比较的对象需要实现java.lang.Comparable接口。在Scala中直接继承JavaComparable接口的是特质Ordered它在继承compareTo方法的基础上额外定义了关系符方法源码如下:
Scala 中如果定义了一个如上所示的比较对象大小的泛型方法你会发现无法通过编译。对于对象之间进行大小比较ScalaJava 一样,都要求被比较的对象需要实现 java.lang.Comparable 接口。在 Scala 中,直接继承 JavaComparable 接口的是特质 Ordered它在继承 compareTo 方法的基础上,额外定义了关系符方法,源码如下:
```scala
trait Ordered[A] extends Any with java.lang.Comparable[A] {
@ -318,14 +318,14 @@ object Pair extends App {
}
```
视图限定限制了T可以通过隐式转换`Ordered[T]`,即对象一定可以进行大小比较。在上面的代码中`smaller(1,2)`中参数`1``2`实际上是通过定义在`Predef`中的隐式转换方法`intWrapper`转换为`RichInt`
视图限定限制了 T 可以通过隐式转换 `Ordered[T]`,即对象一定可以进行大小比较。在上面的代码中 `smaller(1,2)` 中参数 `1``2` 实际上是通过定义在 `Predef` 中的隐式转换方法 `intWrapper` 转换为 `RichInt`
```scala
// Predef.scala
@inline implicit def intWrapper(x: Int) = new runtime.RichInt(x)
```
为什么要这么麻烦执行隐式转换原因是Scala中的Int类型并不能直接进行比较因为其没有实现`Ordered`特质,真正实现`Ordered`特质的是`RichInt`
为什么要这么麻烦执行隐式转换,原因是 Scala 中的 Int 类型并不能直接进行比较,因为其没有实现 `Ordered` 特质,真正实现 `Ordered` 特质的是 `RichInt`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-richInt.png"/> </div>
@ -333,12 +333,12 @@ object Pair extends App {
#### 2. 利用隐式参数进行隐式转换
Scala2.11+后,视图界定被标识为废弃,官方推荐使用类型限定来解决上面的问题,本质上就是使用隐式参数进行隐式转换。
Scala2.11+ 后,视图界定被标识为废弃,官方推荐使用类型限定来解决上面的问题,本质上就是使用隐式参数进行隐式转换。
```scala
object Pair extends App {
// order既是一个隐式参数也是一个隐式转换即如果a不存在 < 方法则转换为order(a)<b
// order 既是一个隐式参数也是一个隐式转换,即如果 a 不存在 < 方法,则转换为 order(a)<b
def smaller[T](a: T, b: T)(implicit order: T => Ordered[T]) = if (a < b) a else b
println(smaller(1,2)) //输出 1
@ -349,8 +349,8 @@ object Pair extends App {
## 参考资料
1. Martin Odersky . Scala编程(第3版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学Scala(第2版)[M] . 电子工业出版社 . 2017-7
1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7

View File

@ -13,7 +13,7 @@
## 一、集合简介
Scala中拥有多种集合类型主要分为可变的和不可变的集合两大类
Scala 中拥有多种集合类型,主要分为可变的和不可变的集合两大类:
+ **可变集合** 可以被修改。即可以更改,添加,删除集合中的元素;
@ -21,7 +21,7 @@ Scala中拥有多种集合类型主要分为可变的和不可变的集合两
## 二、集合结构
Scala中的大部分集合类都存在三类变体分别位于`scala.collection`, `scala.collection.immutable`, `scala.collection.mutable`包中。还有部分集合类位于`scala.collection.generic`包下。
Scala 中的大部分集合类都存在三类变体,分别位于 `scala.collection`, `scala.collection.immutable`, `scala.collection.mutable` 包中。还有部分集合类位于 `scala.collection.generic` 包下。
- **scala.collection.immutable** :包是中的集合是不可变的;
- **scala.collection.mutable** :包中的集合是可变的;
@ -33,7 +33,7 @@ val mutableSet = collection.mutable.SortedSet(1, 2, 3, 4, 5)
val immutableSet = collection.immutable.SortedSet(1, 2, 3, 4, 5)
```
如果你仅写了`Set` 而没有加任何前缀也没有进行任何`import`则Scala默认采用不可变集合类。
如果你仅写了 `Set` 而没有加任何前缀也没有进行任何 `import`,则 Scala 默认采用不可变集合类。
```scala
scala> Set(1,2,3,4,5)
@ -42,31 +42,31 @@ res0: scala.collection.immutable.Set[Int] = Set(5, 1, 2, 3, 4)
### 3.1 scala.collection
scala.collection包中所有集合如下图
scala.collection 包中所有集合如下图:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-collection.png"/> </div>
### 3.2 scala.collection.mutable
scala.collection.mutable包中所有集合如下图
scala.collection.mutable 包中所有集合如下图:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-collection-m.png"/> </div>
### 3.2 scala.collection.immutable
scala.collection.immutable包中所有集合如下图
scala.collection.immutable 包中所有集合如下图:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-collection-imm.png"/> </div>
## 三、Trait Traversable
Scala中所有集合的顶层实现是`Traversable` 。它唯一的抽象方法是`foreach`
Scala 中所有集合的顶层实现是 `Traversable` 。它唯一的抽象方法是 `foreach`
```scala
def foreach[U](f: Elem => U)
```
实现`Traversable`的集合类只需要实现这个抽象方法,其他方法可以从`Traversable`继承。`Traversable`中的所有可用方法如下:
实现 `Traversable` 的集合类只需要实现这个抽象方法,其他方法可以从 `Traversable` 继承。`Traversable` 中的所有可用方法如下:
| **方法** | **作用** |
| ----------------------------------- | ------------------------------------------------------------ |
@ -86,7 +86,7 @@ def foreach[U](f: Elem => U)
| `xs.toIndexedSeq` | 将集合转化为一个 IndexedSeq |
| `xs.toStream` | 将集合转化为一个延迟计算的流 |
| `xs.toSet` | 将集合转化为一个 Set |
| `xs.toMap` | 将一个key, value对的集合转化为一个Map。 如果当前集合的元素类型不是key, value对形式 则报静态类型错误。 |
| `xs.toMap` | 将一个key, value对的集合转化为一个 Map。 如果当前集合的元素类型不是key, value对形式 则报静态类型错误。 |
| **Copying:** | |
| `xs copyToBuffer buf` | 拷贝集合中所有元素到缓存 buf |
| `xs copyToArray(arr,s,n)` | 从索引 s 开始,将集合中最多 n 个元素复制到数组 arr。 最后两个参数是可选的。 |
@ -105,8 +105,8 @@ def foreach[U](f: Elem => U)
| `xs.tail` | 除了第一个元素之外的其他元素组成的集合 |
| `xs.init` | 除了最后一个元素之外的其他元素组成的集合 |
| `xs slice (from, to)` | 返回给定索引范围之内的元素组成的集合 (包含 from 位置的元素但不包含 to 位置的元素) |
| `xs take n` | 返回 xs 的前n个元素组成的集合(如果无序,则返回任意n个元素) |
| `xs drop n` | 返回 xs 的后n个元素组成的集合(如果无序,则返回任意n个元素) |
| `xs take n` | 返回 xs 的前 n 个元素组成的集合(如果无序,则返回任意 n 个元素) |
| `xs drop n` | 返回 xs 的后 n 个元素组成的集合(如果无序,则返回任意 n 个元素) |
| `xs takeWhile p` | 从第一个元素开始查找满足条件 p 的元素, 直到遇到一个不满足条件的元素,返回所有遍历到的值。 |
| `xs dropWhile p` | 从第一个元素开始查找满足条件 p 的元素, 直到遇到一个不满足条件的元素,返回所有未遍历到的值。 |
| `xs filter p` | 返回满足条件 p 的所有元素的集合 |
@ -114,7 +114,7 @@ def foreach[U](f: Elem => U)
| `xs filterNot p` | 返回不满足条件 p 的所有元素组成的集合 |
| **Subdivisions:** | |
| `xs splitAt n` | 在给定位置拆分集合,返回一个集合对 (xs take n, xs drop n) |
| `xs span p` | 根据给定条件拆分集合,返回一个集合对(xs takeWhile p, xs dropWhile p)。即遍历元素,直到遇到第一个不符合条件的值则结束遍历,将遍历到的值和未遍历到的值分别放入两个集合返回。 |
| `xs span p` | 根据给定条件拆分集合,返回一个集合对 (xs takeWhile p, xs dropWhile p)。即遍历元素,直到遇到第一个不符合条件的值则结束遍历,将遍历到的值和未遍历到的值分别放入两个集合返回。 |
| `xs partition p` | 按照筛选条件对元素进行分组 |
| `xs groupBy f` | 根据鉴别器函数 f 将 xs 划分为集合映射 |
| **Element Conditions:** | |
@ -139,7 +139,7 @@ def foreach[U](f: Elem => U)
| `xs.stringPrefix` | 返回 xs.toString 字符串开头的集合名称 |
| **Views:** | |
| `xs.view` | 生成 xs 的视图 |
| `xs view (from, to)` | 生成 xs上指定索引范围内元素的视图 |
| `xs view (from, to)` | 生成 xs 上指定索引范围内元素的视图 |
@ -169,12 +169,12 @@ res5: String = [1-2-3]
## 四、Trait Iterable
Scala中所有的集合都直接或者间接实现了`Iterable`特质,`Iterable`拓展自`Traversable`,并额外定义了部分方法:
Scala 中所有的集合都直接或者间接实现了 `Iterable` 特质,`Iterable` 拓展自 `Traversable`,并额外定义了部分方法:
| **方法** | **作用** |
| ---------------------- | ------------------------------------------------------------ |
| **Abstract Method:** | |
| `xs.iterator` | 返回一个迭代器,用于遍历 xs 中的元素, 与foreach遍历元素的顺序相同。 |
| `xs.iterator` | 返回一个迭代器,用于遍历 xs 中的元素, 与 foreach 遍历元素的顺序相同。 |
| **Other Iterators:** | |
| `xs grouped size` | 返回一个固定大小的迭代器 |
| `xs sliding size` | 返回一个固定大小的滑动窗口的迭代器 |
@ -238,14 +238,14 @@ res8: Boolean = false
| 操作符 | 描述 | 集合类型 |
| ------------------------------------------------------------ | ------------------------------------------------- | --------------------- |
| coll(k)<br/>即coll.apply(k) | 获取指定位置的元素 | Seq, Map |
| coll(k)<br/> coll.apply(k) | 获取指定位置的元素 | Seq, Map |
| coll :+ elem<br/>elem +: coll | 向集合末尾或者集合头增加元素 | Seq |
| coll + elem<br/>coll + (e1, e2, ...) | 追加元素 | Seq, Map |
| coll - elem<br/>coll - (e1, e2, ...) | 删除元素 | Set, Map, ArrayBuffer |
| coll ++ coll2<br/>coll2 ++: coll | 合并集合 | Iterable |
| coll -- coll2 | 移除coll中包含的coll2中的元素 | Set, Map, ArrayBuffer |
| elem :: lst<br/>lst2 :: lst | 把指定列表(lst2)或者元素(elem)添加到列表(lst)头部 | List |
| list ::: list2 | 合并List | List |
| coll -- coll2 | 移除 coll 中包含的 coll2 中的元素 | Set, Map, ArrayBuffer |
| elem :: lst<br/>lst2 :: lst | 把指定列表 (lst2) 或者元素 (elem) 添加到列表 (lst) 头部 | List |
| list ::: list2 | 合并 List | List |
| set \| set2<br/>set & set2<br/>set &~ set2 | 并集、交集、差集 | Set |
| coll += elem<br/>coll += (e1, e2, ...)<br/>coll ++= coll2<br/>coll -= elem<br/>coll -= (e1, e2, ...)<br/>coll --= coll2 | 添加或者删除元素,并将修改后的结果赋值给集合本身 | 可变集合 |
| elem +=: coll<br/>coll2 ++=: coll | 在集合头部追加元素或集合 | ArrayBuffer |

View File

@ -17,13 +17,13 @@
## 一、Spark SQL简介
Spark SQLSpark中的一个子模块主要用于操作结构化数据。它具有以下特点
Spark SQLSpark 中的一个子模块,主要用于操作结构化数据。它具有以下特点:
+ 能够将SQL查询与Spark程序无缝混合允许您使用SQLDataFrame API对结构化数据进行查询
+ 能够将 SQL 查询与 Spark 程序无缝混合,允许您使用 SQLDataFrame API 对结构化数据进行查询;
+ 支持多种开发语言;
+ 支持多达上百种的外部数据源包括HiveAvroParquetORCJSONJDBC等
+ 支持HiveQL语法以及Hive SerDesUDF允许你访问现有的Hive仓库
+ 支持标准的JDBCODBC连接
+ 支持多达上百种的外部数据源,包括 HiveAvroParquetORCJSONJDBC 等;
+ 支持 HiveQL 语法以及 Hive SerDesUDF允许你访问现有的 Hive 仓库;
+ 支持标准的 JDBCODBC 连接;
+ 支持优化器,列式存储和代码生成等特性;
+ 支持扩展并能保证容错。
@ -33,7 +33,7 @@ Spark SQL是Spark中的一个子模块主要用于操作结构化数据。它
### 2.1 DataFrame
为了支持结构化数据的处理Spark SQL提供了新的数据结构DataFrame。DataFrame是一个由具名列组成的数据集。它在概念上等同于关系数据库中的表或R/Python语言中的`data frame`。 由于Spark SQL支持多种语言的开发所以每种语言都定义了`DataFrame`的抽象,主要如下:
为了支持结构化数据的处理Spark SQL 提供了新的数据结构 DataFrame。DataFrame 是一个由具名列组成的数据集。它在概念上等同于关系数据库中的表或 R/Python 语言中的 `data frame`。 由于 Spark SQL 支持多种语言的开发,所以每种语言都定义了 `DataFrame` 的抽象,主要如下:
| 语言 | 主要抽象 |
| ------ | -------------------------------------------- |
@ -44,23 +44,23 @@ Spark SQL是Spark中的一个子模块主要用于操作结构化数据。它
### 2.2 DataFrame 对比 RDDs
DataFrameRDDs最主要的区别在于一个面向的是结构化数据一个面向的是非结构化数据它们内部的数据结构如下
DataFrameRDDs 最主要的区别在于一个面向的是结构化数据,一个面向的是非结构化数据,它们内部的数据结构如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-dataFrame+RDDs.png"/> </div>
DataFrame内部的有明确Scheme结构即列名、列字段类型都是已知的这带来的好处是可以减少数据读取以及更好地优化执行计划从而保证查询效率。
DataFrame 内部的有明确 Scheme 结构,即列名、列字段类型都是已知的,这带来的好处是可以减少数据读取以及更好地优化执行计划,从而保证查询效率。
**DataFrameRDDs应该如何选择**
**DataFrameRDDs 应该如何选择?**
+ 如果你想使用函数式编程而不是DataFrame API则使用RDDs
+ 如果你的数据是非结构化的(比如流媒体或者字符流)则使用RDDs
+ 如果你的数据是结构化的(如RDBMS中的数据)或者半结构化的(如日志)出于性能上的考虑应优先使用DataFrame。
+ 如果你想使用函数式编程而不是 DataFrame API则使用 RDDs
+ 如果你的数据是非结构化的 (比如流媒体或者字符流),则使用 RDDs
+ 如果你的数据是结构化的 (如 RDBMS 中的数据) 或者半结构化的 (如日志),出于性能上的考虑,应优先使用 DataFrame。
### 2.3 DataSet
Dataset也是分布式的数据集合在Spark 1.6版本被引入它集成了RDDDataFrame的优点具备强类型的特点同时支持Lambda函数但只能在ScalaJava语言中使用。在Spark 2.0后为了方便开发者SparkDataFrameDatasetAPI融合到一起提供了结构化的API(Structured API)即用户可以通过一套标准的API就能完成对两者的操作。
Dataset 也是分布式的数据集合,在 Spark 1.6 版本被引入,它集成了 RDDDataFrame 的优点,具备强类型的特点,同时支持 Lambda 函数,但只能在 ScalaJava 语言中使用。在 Spark 2.0 为了方便开发者SparkDataFrameDatasetAPI 融合到一起,提供了结构化的 API(Structured API),即用户可以通过一套标准的 API 就能完成对两者的操作。
> 这里注意一下DataFrame被标记为Untyped API而DataSet被标记为Typed API后文会对两者做出解释。
> 这里注意一下DataFrame 被标记为 Untyped API DataSet 被标记为 Typed API后文会对两者做出解释。
@ -68,27 +68,27 @@ Dataset也是分布式的数据集合在Spark 1.6版本被引入,它集成
### 2.4 静态类型与运行时类型安全
静态类型(Static-typing)与运行时类型安全(runtime type-safety) 主要表现如下:
静态类型 (Static-typing) 与运行时类型安全 (runtime type-safety) 主要表现如下:
在实际使用中如果你用的是Spark SQL的查询语句则直到运行时你才会发现有语法错误而如果你用的是DataFrame和 Dataset则在编译时就可以发现错误(这节省了开发时间和整体代价)。DataFrameDataset主要区别在于
在实际使用中,如果你用的是 Spark SQL 的查询语句,则直到运行时你才会发现有语法错误,而如果你用的是 DataFrame 和 Dataset则在编译时就可以发现错误 (这节省了开发时间和整体代价)。DataFrameDataset 主要区别在于:
在DataFrame中当你调用了API之外的函数编译器就会报错但如果你使用了一个不存在的字段名字编译器依然无法发现。而DatasetAPI都是用Lambda函数和JVM类型对象表示的所有不匹配的类型参数在编译时就会被发现。
DataFrame 中,当你调用了 API 之外的函数,编译器就会报错,但如果你使用了一个不存在的字段名字,编译器依然无法发现。而 DatasetAPI 都是用 Lambda 函数和 JVM 类型对象表示的,所有不匹配的类型参数在编译时就会被发现。
以上这些最终都被解释成关于类型安全图谱对应开发中的语法和分析错误。在图谱中Dataset最严格但对于开发者来说效率最高。
以上这些最终都被解释成关于类型安全图谱对应开发中的语法和分析错误。在图谱中Dataset 最严格,但对于开发者来说效率最高。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-运行安全.png"/> </div>
上面的描述可能并没有那么直观下面的给出一个IDEA中代码编译的示例
上面的描述可能并没有那么直观,下面的给出一个 IDEA 中代码编译的示例:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-运行时类型安全.png"/> </div>
这里一个可能的疑惑是DataFrame明明是有确定的Scheme结构(即列名、列字段类型都是已知的)但是为什么还是无法对列名进行推断和错误判断这是因为DataFrameUntyped的。
这里一个可能的疑惑是 DataFrame 明明是有确定的 Scheme 结构 (即列名、列字段类型都是已知的),但是为什么还是无法对列名进行推断和错误判断,这是因为 DataFrameUntyped 的。
### 2.5 Untyped & Typed
在上面我们介绍过DataFrame API被标记为`Untyped API`而DataSet API被标记为`Typed API`。DataFrame`Untyped`是相对于语言或API层面而言它确实有明确的Scheme结构即列名列类型都是确定的但这些信息完全由Spark来维护Spark只会在运行时检查这些类型和指定类型是否一致。这也就是为什么在Spark 2.0之后官方推荐把DataFrame看做是`DatSet[Row]`RowSpark中定义的一个`trait`,其子类中封装了列字段的信息。
在上面我们介绍过 DataFrame API 被标记为 `Untyped API`,而 DataSet API 被标记为 `Typed API`。DataFrame`Untyped` 是相对于语言或 API 层面而言,它确实有明确的 Scheme 结构,即列名,列类型都是确定的,但这些信息完全由 Spark 来维护Spark 只会在运行时检查这些类型和指定类型是否一致。这也就是为什么在 Spark 2.0 之后,官方推荐把 DataFrame 看做是 `DatSet[Row]`RowSpark 中定义的一个 `trait`,其子类中封装了列字段的信息。
相对而言DataSet`Typed`即强类型。如下面代码DataSet的类型由Case Class(Scala)或者Java Bean(Java)来明确指定的,在这里即每一行数据代表一个`Person`这些信息由JVM来保证正确性所以字段名错误和类型错误在编译的时候就会被IDE所发现。
相对而言DataSet`Typed` 即强类型。如下面代码DataSet 的类型由 Case Class(Scala) 或者 Java Bean(Java) 来明确指定的,在这里即每一行数据代表一个 `Person`,这些信息由 JVM 来保证正确性,所以字段名错误和类型错误在编译的时候就会被 IDE 所发现。
```scala
case class Person(name: String, age: Long)
@ -101,10 +101,10 @@ val dataSet: Dataset[Person] = spark.read.json("people.json").as[Person]
这里对三者做一下简单的总结:
+ RDDs适合非结构化数据的处理而DataFrame & DataSet更适合结构化数据和半结构化的处理
+ DataFrame & DataSet可以通过统一的Structured API进行访问而RDDs则更适合函数式编程的场景
+ 相比于DataFrame而言DataSet是强类型的(Typed),有着更为严格的静态类型检查;
+ DataSets、DataFrames、SQL的底层都依赖了RDDs API并对外提供结构化的访问接口。
+ RDDs 适合非结构化数据的处理,而 DataFrame & DataSet 更适合结构化数据和半结构化的处理;
+ DataFrame & DataSet 可以通过统一的 Structured API 进行访问,而 RDDs 则更适合函数式编程的场景;
+ 相比于 DataFrame 而言DataSet 是强类型的 (Typed),有着更为严格的静态类型检查;
+ DataSets、DataFrames、SQL 的底层都依赖了 RDDs API并对外提供结构化的访问接口。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-structure-api.png"/> </div>
@ -112,16 +112,16 @@ val dataSet: Dataset[Person] = spark.read.json("people.json").as[Person]
## 四、Spark SQL的运行原理
DataFrame、DataSetSpark SQL的实际执行流程都是相同的
DataFrame、DataSetSpark SQL 的实际执行流程都是相同的:
1. 进行DataFrame/Dataset/SQL编程
2. 如果是有效的代码即代码没有编译错误Spark会将其转换为一个逻辑计划
3. Spark将此逻辑计划转换为物理计划同时进行代码优化
4. Spark然后在集群上执行这个物理计划(基于RDD操作) 。
1. 进行 DataFrame/Dataset/SQL 编程;
2. 如果是有效的代码即代码没有编译错误Spark 会将其转换为一个逻辑计划;
3. Spark 将此逻辑计划转换为物理计划,同时进行代码优化;
4. Spark 然后在集群上执行这个物理计划 (基于 RDD 操作) 。
### 4.1 逻辑计划(Logical Plan)
执行的第一个阶段是将用户代码转换成一个逻辑计划。它首先将用户代码转换成`unresolved logical plan`(未解决的逻辑计划),之所以这个计划是未解决的,是因为尽管您的代码在语法上是正确的,但是它引用的表或列可能不存在。 Spark使用`analyzer`(分析器)基于`catalog`(存储的所有表和`DataFrames`的信息)进行解析。解析失败则拒绝执行,解析成功则将结果传给`Catalyst`优化器(`Catalyst Optimizer`),优化器是一组规则的集合,用于优化逻辑计划,通过谓词下推等方式进行优化,最终输出优化后的逻辑执行计划。
执行的第一个阶段是将用户代码转换成一个逻辑计划。它首先将用户代码转换成 `unresolved logical plan`(未解决的逻辑计划),之所以这个计划是未解决的,是因为尽管您的代码在语法上是正确的,但是它引用的表或列可能不存在。 Spark 使用 `analyzer`(分析器) 基于 `catalog`(存储的所有表和 `DataFrames` 的信息) 进行解析。解析失败则拒绝执行,解析成功则将结果传给 `Catalyst` 优化器 (`Catalyst Optimizer`),优化器是一组规则的集合,用于优化逻辑计划,通过谓词下推等方式进行优化,最终输出优化后的逻辑执行计划。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-Logical-Planning.png"/> </div>
@ -129,13 +129,13 @@ DataFrame、DataSet和Spark SQL的实际执行流程都是相同的
### 4.2 物理计划(Physical Plan)
得到优化后的逻辑计划后Spark就开始了物理计划过程。 它通过生成不同的物理执行策略并通过成本模型来比较它们从而选择一个最优的物理计划在集群上面执行的。物理规划的输出结果是一系列的RDDs和转换关系(transformations)。
得到优化后的逻辑计划后Spark 就开始了物理计划过程。 它通过生成不同的物理执行策略,并通过成本模型来比较它们,从而选择一个最优的物理计划在集群上面执行的。物理规划的输出结果是一系列的 RDDs 和转换关系 (transformations)。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-Physical-Planning.png"/> </div>
### 4.3 执行
在选择一个物理计划后Spark运行其RDDs代码并在运行时执行进一步的优化生成本地Java字节码最后将运行结果返回给用户。
在选择一个物理计划后Spark 运行其 RDDs 代码,并在运行时执行进一步的优化,生成本地 Java 字节码,最后将运行结果返回给用户。

View File

@ -42,7 +42,7 @@
### 1.1 多数据源支持
Spark支持以下六个核心数据源同时Spark社区还提供了多达上百种数据源的读取方式能够满足绝大部分使用场景。
Spark 支持以下六个核心数据源,同时 Spark 社区还提供了多达上百种数据源的读取方式,能够满足绝大部分使用场景。
- CSV
- JSON
@ -51,11 +51,11 @@ Spark支持以下六个核心数据源同时Spark社区还提供了多达上
- JDBC/ODBC connections
- Plain-text files
> 注:以下所有测试文件均可从本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources)目录进行下载
> 注:以下所有测试文件均可从本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources) 目录进行下载
### 1.2 读数据格式
所有读取API遵循以下调用格式
所有读取 API 遵循以下调用格式:
```scala
// 格式
@ -64,9 +64,9 @@ DataFrameReader.format(...).option("key", "value").schema(...).load()
// 示例
spark.read.format("csv")
.option("mode", "FAILFAST") // 读取模式
.option("inferSchema", "true") // 是否自动推断schema
.option("inferSchema", "true") // 是否自动推断 schema
.option("path", "path/to/file(s)") // 文件路径
.schema(someSchema) // 使用预定义的schema
.schema(someSchema) // 使用预定义的 schema
.load()
```
@ -74,7 +74,7 @@ spark.read.format("csv")
| 读模式 | 描述 |
| --------------- | ------------------------------------------------------------ |
| `permissive` | 当遇到损坏的记录时将其所有字段设置为null并将所有损坏的记录放在名为_corruption t_record的字符串列中 |
| `permissive` | 当遇到损坏的记录时,将其所有字段设置为 null并将所有损坏的记录放在名为 _corruption t_record 的字符串列中 |
| `dropMalformed` | 删除格式不正确的行 |
| `failFast` | 遇到格式不正确的数据时立即失败 |
@ -105,7 +105,7 @@ dataframe.write.format("csv")
## 二、CSV
CSV是一种常见的文本文件格式其中每一行表示一条记录记录中的每个字段用逗号分隔。
CSV 是一种常见的文本文件格式,其中每一行表示一条记录,记录中的每个字段用逗号分隔。
### 2.1 读取CSV文件
@ -115,7 +115,7 @@ CSV是一种常见的文本文件格式其中每一行表示一条记录
spark.read.format("csv")
.option("header", "false") // 文件中的第一行是否为列的名称
.option("mode", "FAILFAST") // 是否快速失败
.option("inferSchema", "true") // 是否自动推断schema
.option("inferSchema", "true") // 是否自动推断 schema
.load("/usr/file/csv/dept.csv")
.show()
```
@ -151,7 +151,7 @@ df.write.format("csv").mode("overwrite").option("sep", "\t").save("/tmp/csv/dept
### 2.3 可选配置
为节省主文篇幅所有读写配置项见文末9.1小节。
为节省主文篇幅,所有读写配置项见文末 9.1 小节。
<br/>
@ -163,7 +163,7 @@ df.write.format("csv").mode("overwrite").option("sep", "\t").save("/tmp/csv/dept
spark.read.format("json").option("mode", "FAILFAST").load("/usr/file/json/dept.json").show(5)
```
需要注意的是:默认不支持一条数据记录跨越多行(如下),可以通过配置`multiLine``true`来进行更改,其默认值为`false`
需要注意的是:默认不支持一条数据记录跨越多行 (如下),可以通过配置 `multiLine``true` 来进行更改,其默认值为 `false`
```json
// 默认支持单行
@ -185,13 +185,13 @@ df.write.format("json").mode("overwrite").save("/tmp/spark/json/dept")
### 3.3 可选配置
为节省主文篇幅所有读写配置项见文末9.2小节。
为节省主文篇幅,所有读写配置项见文末 9.2 小节。
<br/>
## 四、Parquet
Parquet是一个开源的面向列的数据存储它提供了多种存储优化允许读取单独的列非整个文件这不仅节省了存储空间而且提升了读取效率它是Spark是默认的文件格式。
Parquet 是一个开源的面向列的数据存储,它提供了多种存储优化,允许读取单独的列非整个文件,这不仅节省了存储空间而且提升了读取效率,它是 Spark 是默认的文件格式。
### 4.1 读取Parquet文件
@ -207,12 +207,12 @@ df.write.format("parquet").mode("overwrite").save("/tmp/spark/parquet/dept")
### 2.3 可选配置
Parquet文件有着自己的存储规则因此其可选配置项比较少常用的有如下两个
Parquet 文件有着自己的存储规则,因此其可选配置项比较少,常用的有如下两个:
| 读写操作 | 配置项 | 可选值 | 默认值 | 描述 |
| -------- | -------------------- | ------------------------------------------------------------ | ------------------------------------------- | ------------------------------------------------------------ |
| Write | compression or codec | None,<br/>uncompressed,<br/>bzip2,<br/>deflate, gzip,<br/>lz4, or snappy | None | 压缩文件格式 |
| Read | mergeSchema | true, false | 取决于配置项`spark.sql.parquet.mergeSchema` | 当为真时Parquet数据源将所有数据文件收集的Schema合并在一起否则将从摘要文件中选择Schema如果没有可用的摘要文件则从随机数据文件中选择Schema。 |
| Read | mergeSchema | true, false | 取决于配置项 `spark.sql.parquet.mergeSchema` | 当为真时Parquet 数据源将所有数据文件收集的 Schema 合并在一起,否则将从摘要文件中选择 Schema如果没有可用的摘要文件则从随机数据文件中选择 Schema。 |
> 更多可选配置可以参阅官方文档https://spark.apache.org/docs/latest/sql-data-sources-parquet.html
@ -220,7 +220,7 @@ Parquet文件有着自己的存储规则因此其可选配置项比较少
## 五、ORC
ORC是一种自描述的、类型感知的列文件格式它针对大型数据的读写进行了优化也是大数据中常用的文件格式。
ORC 是一种自描述的、类型感知的列文件格式,它针对大型数据的读写进行了优化,也是大数据中常用的文件格式。
### 5.1 读取ORC文件
@ -238,11 +238,11 @@ csvFile.write.format("orc").mode("overwrite").save("/tmp/spark/orc/dept")
## 六、SQL Databases
Spark同样支持与传统的关系型数据库进行数据读写。但是Spark程序默认是没有提供数据库驱动的所以在使用前需要将对应的数据库驱动上传到安装目录下的`jars`目录中。下面示例使用的是Mysql数据库使用前需要将对应的`mysql-connector-java-x.x.x.jar`上传到`jars`目录下。
Spark 同样支持与传统的关系型数据库进行数据读写。但是 Spark 程序默认是没有提供数据库驱动的,所以在使用前需要将对应的数据库驱动上传到安装目录下的 `jars` 目录中。下面示例使用的是 Mysql 数据库,使用前需要将对应的 `mysql-connector-java-x.x.x.jar` 上传到 `jars` 目录下。
### 6.1 读取数据
读取全表数据示例如下,这里的`help_keyword`mysql内置的字典表只有`help_keyword_id``name`两个字段。
读取全表数据示例如下,这里的 `help_keyword`mysql 内置的字典表,只有 `help_keyword_id``name` 两个字段。
```scala
spark.read
@ -319,7 +319,7 @@ spark.read.jdbc("jdbc:mysql://127.0.0.1:3306/mysql", "help_keyword", predicates,
+---------------+-----------+
```
可以使用`numPartitions`指定读取数据的并行度:
可以使用 `numPartitions` 指定读取数据的并行度:
```scala
option("numPartitions", 10)
@ -336,7 +336,7 @@ val jdbcDf = spark.read.jdbc("jdbc:mysql://127.0.0.1:3306/mysql","help_keyword",
colName,lowerBound,upperBound,numPartitions,props)
```
想要验证分区内容,可以使用`mapPartitionsWithIndex`这个算子,代码如下:
想要验证分区内容,可以使用 `mapPartitionsWithIndex` 这个算子,代码如下:
```scala
jdbcDf.rdd.mapPartitionsWithIndex((index, iterator) => {
@ -348,7 +348,7 @@ jdbcDf.rdd.mapPartitionsWithIndex((index, iterator) => {
}).foreach(println)
```
执行结果如下:`help_keyword`这张表只有600条左右的数据本来数据应该均匀分布在10个分区但是0分区里面却有319条数据这是因为设置了下限所有小于300的数据都会被限制在第一个分区0分区。同理所有大于500的数据被分配在9分区,即最后一个分区。
执行结果如下:`help_keyword` 这张表只有 600 条左右的数据,本来数据应该均匀分布在 10 个分区,但是 0 分区里面却有 319 条数据,这是因为设置了下限,所有小于 300 的数据都会被限制在第一个分区,即 0 分区。同理所有大于 500 的数据被分配在 9 分区,即最后一个分区。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-mysql-分区上下限.png"/> </div>
@ -368,7 +368,7 @@ df.write
## 七、Text
Text文件在读写性能方面并没有任何优势且不能表达明确的数据结构所以其使用的比较少读写操作如下
Text 文件在读写性能方面并没有任何优势,且不能表达明确的数据结构,所以其使用的比较少,读写操作如下:
### 7.1 读取Text数据
@ -388,15 +388,15 @@ df.write.text("/tmp/spark/txt/dept")
### 8.1 并行读
多个Executors不能同时读取同一个文件但它们可以同时读取不同的文件。这意味着当您从一个包含多个文件的文件夹中读取数据时这些文件中的每一个都将成为DataFrame中的一个分区并由可用的Executors并行读取。
多个 Executors 不能同时读取同一个文件,但它们可以同时读取不同的文件。这意味着当您从一个包含多个文件的文件夹中读取数据时,这些文件中的每一个都将成为 DataFrame 中的一个分区,并由可用的 Executors 并行读取。
### 8.2 并行写
写入的文件或数据的数量取决于写入数据时DataFrame拥有的分区数量。默认情况下每个数据分区写一个文件。
写入的文件或数据的数量取决于写入数据时 DataFrame 拥有的分区数量。默认情况下,每个数据分区写一个文件。
### 8.3 分区写入
分区和分桶这两个概念和Hive中分区表和分桶表是一致的。都是将数据按照一定规则进行拆分存储。需要注意的是`partitionBy`指定的分区和RDD中分区不是一个概念这里的**分区表现为输出目录的子目录**,数据分别存储在对应的子目录中。
分区和分桶这两个概念和 Hive 中分区表和分桶表是一致的。都是将数据按照一定规则进行拆分存储。需要注意的是 `partitionBy` 指定的分区和 RDD 中分区不是一个概念:这里的**分区表现为输出目录的子目录**,数据分别存储在对应的子目录中。
```scala
val df = spark.read.format("json").load("/usr/file/json/emp.json")
@ -409,7 +409,7 @@ df.write.mode("overwrite").partitionBy("deptno").save("/tmp/spark/partitions")
### 8.3 分桶写入
分桶写入就是将数据按照指定的列和桶数进行散列目前分桶写入只支持保存为表实际上这就是Hive的分桶表。
分桶写入就是将数据按照指定的列和桶数进行散列,目前分桶写入只支持保存为表,实际上这就是 Hive 的分桶表。
```scala
val numberBuckets = 10
@ -420,12 +420,12 @@ df.write.format("parquet").mode("overwrite")
### 8.5 文件大小管理
如果写入产生小文件数量过多这时会产生大量的元数据开销。SparkHDFS一样都不能很好的处理这个问题这被称为“small file problem”。同时数据文件也不能过大否则在查询时会有不必要的性能开销因此要把文件大小控制在一个合理的范围内。
如果写入产生小文件数量过多这时会产生大量的元数据开销。SparkHDFS 一样都不能很好的处理这个问题这被称为“small file problem”。同时数据文件也不能过大否则在查询时会有不必要的性能开销因此要把文件大小控制在一个合理的范围内。
在上文我们已经介绍过可以通过分区数量来控制生成文件的数量从而间接控制文件大小。Spark 2.2引入了一种新的方法,以更自动化的方式控制文件大小,这就是`maxRecordsPerFile`参数,它允许你通过控制写入文件的记录数来控制文件大小。
在上文我们已经介绍过可以通过分区数量来控制生成文件的数量从而间接控制文件大小。Spark 2.2 引入了一种新的方法,以更自动化的方式控制文件大小,这就是 `maxRecordsPerFile` 参数,它允许你通过控制写入文件的记录数来控制文件大小。
```scala
// Spark将确保文件最多包含5000条记录
// Spark 将确保文件最多包含 5000 条记录
df.write.option(maxRecordsPerFile, 5000)
```
@ -444,12 +444,12 @@ df.write.option(“maxRecordsPerFile”, 5000)
| Read | ignoreLeadingWhiteSpace | true, false | false | 是否跳过值前面的空格 |
| Both | ignoreTrailingWhiteSpace | true, false | false | 是否跳过值后面的空格 |
| Both | nullValue | 任意字符 | “” | 声明文件中哪个字符表示空值 |
| Both | nanValue | 任意字符 | NaN | 声明哪个值表示NaN或者缺省值 |
| Both | nanValue | 任意字符 | NaN | 声明哪个值表示 NaN 或者缺省值 |
| Both | positiveInf | 任意字符 | Inf | 正无穷 |
| Both | negativeInf | 任意字符 | -Inf | 负无穷 |
| Both | compression or codec | None,<br/>uncompressed,<br/>bzip2, deflate,<br/>gzip, lz4, or<br/>snappy | none | 文件压缩格式 |
| Both | dateFormat | 任何能转换为 Java的 <br/>SimpleDataFormat的字符串 | yyyy-MM-dd | 日期格式 |
| Both | timestampFormat | 任何能转换为 Java的 <br/>SimpleDataFormat的字符串 | yyyy-MMddTHH:mm:ss.SSSZZ | 时间戳格式 |
| Both | dateFormat | 任何能转换为 Java <br/>SimpleDataFormat 的字符串 | yyyy-MM-dd | 日期格式 |
| Both | timestampFormat | 任何能转换为 Java <br/>SimpleDataFormat 的字符串 | yyyy-MMddTHH:mm:ss.SSSZZ | 时间戳格式 |
| Read | maxColumns | 任意整数 | 20480 | 声明文件中的最大列数 |
| Read | maxCharsPerColumn | 任意整数 | 1000000 | 声明一个列中的最大字符数。 |
| Read | escapeQuotes | true, false | true | 是否应该转义行中的引号。 |
@ -462,8 +462,8 @@ df.write.option(“maxRecordsPerFile”, 5000)
| 读\写操作 | 配置项 | 可选值 | 默认值 |
| --------- | ---------------------------------- | ------------------------------------------------------------ | -------------------------------- |
| Both | compression or codec | None,<br/>uncompressed,<br/>bzip2, deflate,<br/>gzip, lz4, or<br/>snappy | none |
| Both | dateFormat | 任何能转换为 Java的 SimpleDataFormat的字符串 | yyyy-MM-dd |
| Both | timestampFormat | 任何能转换为 Java的 SimpleDataFormat的字符串 | yyyy-MMddTHH:mm:ss.SSSZZ |
| Both | dateFormat | 任何能转换为 Java 的 SimpleDataFormat 的字符串 | yyyy-MM-dd |
| Both | timestampFormat | 任何能转换为 Java 的 SimpleDataFormat 的字符串 | yyyy-MMddTHH:mm:ss.SSSZZ |
| Read | primitiveAsString | true, false | false |
| Read | allowComments | true, false | false |
| Read | allowUnquotedFieldNames | true, false | false |
@ -481,10 +481,10 @@ df.write.option(“maxRecordsPerFile”, 5000)
| dbtable | 表名称 |
| driver | 数据库驱动 |
| partitionColumn,<br/>lowerBound, upperBoun | 分区总数,上界,下界 |
| numPartitions | 可用于表读写并行性的最大分区数。如果要写的分区数量超过这个限制那么可以调用coalesce(numpartition)重置分区数。 |
| numPartitions | 可用于表读写并行性的最大分区数。如果要写的分区数量超过这个限制,那么可以调用 coalesce(numpartition) 重置分区数。 |
| fetchsize | 每次往返要获取多少行数据。此选项仅适用于读取数据。 |
| batchsize | 每次往返插入多少行数据这个选项只适用于写入数据。默认值是1000。 |
| isolationLevel | 事务隔离级别可以是NONEREAD_COMMITTED, READ_UNCOMMITTEDREPEATABLE_READSERIALIZABLE即标准事务隔离级别。<br/>默认值是READ_UNCOMMITTED。这个选项只适用于数据读取。 |
| batchsize | 每次往返插入多少行数据,这个选项只适用于写入数据。默认值是 1000。 |
| isolationLevel | 事务隔离级别:可以是 NONEREAD_COMMITTED, READ_UNCOMMITTEDREPEATABLE_READSERIALIZABLE即标准事务隔离级别。<br/>默认值是 READ_UNCOMMITTED。这个选项只适用于数据读取。 |
| createTableOptions | 写入数据时自定义创建表的相关配置 |
| createTableColumnTypes | 写入数据时自定义创建列的列类型 |

View File

@ -26,17 +26,17 @@
### 1.1 数据准备
```scala
// 需要导入spark sql内置的函数包
// 需要导入 spark sql 内置的函数包
import org.apache.spark.sql.functions._
val spark = SparkSession.builder().appName("aggregations").master("local[2]").getOrCreate()
val empDF = spark.read.json("/usr/file/json/emp.json")
// 注册为临时视图用于后面演示SQL查询
// 注册为临时视图,用于后面演示 SQL 查询
empDF.createOrReplaceTempView("emp")
empDF.show()
```
> emp.json可以从本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources)目录下载。
> emp.json 可以从本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources) 目录下载。
### 1.2 count
@ -54,7 +54,7 @@ empDF.select(countDistinct("deptno")).show()
### 1.4 approx_count_distinct
通常在使用大型数据集时你可能关注的只是近似值而不是准确值这时可以使用approx_count_distinct函数并可以使用第二个参数指定最大允许误差。
通常在使用大型数据集时,你可能关注的只是近似值而不是准确值,这时可以使用 approx_count_distinct 函数,并可以使用第二个参数指定最大允许误差。
```scala
empDF.select(approx_count_distinct ("ename",0.1)).show()
@ -62,7 +62,7 @@ empDF.select(approx_count_distinct ("ename",0.1)).show()
### 1.5 first & last
获取DataFrame中指定列的第一个值或者最后一个值。
获取 DataFrame 中指定列的第一个值或者最后一个值。
```scala
empDF.select(first("ename"),last("job")).show()
@ -70,7 +70,7 @@ empDF.select(first("ename"),last("job")).show()
### 1.6 min & max
获取DataFrame中指定列的最小值或者最大值。
获取 DataFrame 中指定列的最小值或者最大值。
```scala
empDF.select(min("sal"),max("sal")).show()
@ -95,7 +95,7 @@ empDF.select(avg("sal")).show()
### 1.9 数学函数
Spark SQL中还支持多种数学聚合函数用于通常的数学计算以下是一些常用的例子
Spark SQL 中还支持多种数学聚合函数,用于通常的数学计算,以下是一些常用的例子:
```scala
// 1.计算总体方差、均方差、总体标准差、样本标准差
@ -129,7 +129,7 @@ scala> empDF.agg(collect_set("job"), collect_list("ename")).show()
```scala
empDF.groupBy("deptno", "job").count().show()
//等价SQL
//等价 SQL
spark.sql("SELECT deptno, job, count(*) FROM emp GROUP BY deptno, job").show()
输出
@ -154,7 +154,7 @@ spark.sql("SELECT deptno, job, count(*) FROM emp GROUP BY deptno, job").show()
empDF.groupBy("deptno").agg(count("ename").alias("人数"), sum("sal").alias("总工资")).show()
// 等价语法
empDF.groupBy("deptno").agg("ename"->"count","sal"->"sum").show()
// 等价SQL
// 等价 SQL
spark.sql("SELECT deptno, count(ename) ,sum(sal) FROM emp GROUP BY deptno").show()
输出
@ -171,10 +171,10 @@ spark.sql("SELECT deptno, count(ename) ,sum(sal) FROM emp GROUP BY deptno").show
## 三、自定义聚合函数
Scala提供了两种自定义聚合函数的方法分别如下
Scala 提供了两种自定义聚合函数的方法,分别如下:
- 有类型的自定义聚合函数主要适用于DataSet
- 无类型的自定义聚合函数主要适用于DataFrame。
- 有类型的自定义聚合函数,主要适用于 DataSet
- 无类型的自定义聚合函数,主要适用于 DataFrame。
以下分别使用两种方式来自定义一个求平均值的聚合函数,这里以计算员工平均工资为例。两种自定义方式分别如下:
@ -184,7 +184,7 @@ Scala提供了两种自定义聚合函数的方法分别如下
import org.apache.spark.sql.expressions.Aggregator
import org.apache.spark.sql.{Encoder, Encoders, SparkSession, functions}
// 1.定义员工类,对于可能存在null值的字段需要使用Option进行包装
// 1.定义员工类,对于可能存在 null 值的字段需要使用 Option 进行包装
case class Emp(ename: String, comm: scala.Option[Double], deptno: Long, empno: Long,
hiredate: String, job: String, mgr: scala.Option[Long], sal: Double)
@ -193,7 +193,7 @@ case class SumAndCount(var sum: Double, var count: Long)
/* 3.自定义聚合函数
* @IN 聚合操作的输入类型
* @BUF reduction操作输出值的类型
* @BUF reduction 操作输出值的类型
* @OUT 聚合操作的输出类型
*/
object MyAverage extends Aggregator[Emp, SumAndCount, Double] {
@ -201,14 +201,14 @@ object MyAverage extends Aggregator[Emp, SumAndCount, Double] {
// 4.用于聚合操作的的初始零值
override def zero: SumAndCount = SumAndCount(0, 0)
// 5.同一分区中的reduce操作
// 5.同一分区中的 reduce 操作
override def reduce(avg: SumAndCount, emp: Emp): SumAndCount = {
avg.sum += emp.sal
avg.count += 1
avg
}
// 6.不同分区中的merge操作
// 6.不同分区中的 merge 操作
override def merge(avg1: SumAndCount, avg2: SumAndCount): SumAndCount = {
avg1.sum += avg2.sum
avg1.count += avg2.count
@ -234,12 +234,12 @@ object SparkSqlApp {
import spark.implicits._
val ds = spark.read.json("file/emp.json").as[Emp]
// 10.使用内置avg()函数和自定义函数分别进行计算,验证自定义函数是否正确
// 10.使用内置 avg() 函数和自定义函数分别进行计算,验证自定义函数是否正确
val myAvg = ds.select(MyAverage.toColumn.name("average_sal")).first()
val avg = ds.select(functions.avg(ds.col("sal"))).first().get(0)
println("自定义average函数 : " + myAvg)
println("内置的average函数 : " + avg)
println("自定义 average 函数 : " + myAvg)
println("内置的 average 函数 : " + avg)
}
}
```
@ -250,10 +250,10 @@ object SparkSqlApp {
关于`zero`,`reduce`,`merge`,`finish`方法的作用在上图都有说明,这里解释一下中间类型和输出类型的编码转换,这个写法比较固定,基本上就是两种情况:
关于 `zero`,`reduce`,`merge`,`finish` 方法的作用在上图都有说明,这里解释一下中间类型和输出类型的编码转换,这个写法比较固定,基本上就是两种情况:
- 自定义类型Case Class或者元组就使用`Encoders.product`方法;
- 基本类型就使用其对应名称的方法,如`scalaByte ``scalaFloat``scalaShort`等,示例如下:
- 自定义类型 Case Class 或者元组就使用 `Encoders.product` 方法;
- 基本类型就使用其对应名称的方法,如 `scalaByte ``scalaFloat``scalaShort` 等,示例如下:
```scala
override def bufferEncoder: Encoder[SumAndCount] = Encoders.product
@ -283,7 +283,7 @@ object MyAverage extends UserDefinedAggregateFunction {
// 3.聚合操作输出参数的类型
def dataType: DataType = DoubleType
// 4.此函数是否始终在相同输入上返回相同的输出,通常为true
// 4.此函数是否始终在相同输入上返回相同的输出,通常为 true
def deterministic: Boolean = true
// 5.定义零值
@ -292,7 +292,7 @@ object MyAverage extends UserDefinedAggregateFunction {
buffer(1) = 0L
}
// 6.同一分区中的reduce操作
// 6.同一分区中的 reduce 操作
def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
if (!input.isNullAt(0)) {
buffer(0) = buffer.getLong(0) + input.getLong(0)
@ -300,7 +300,7 @@ object MyAverage extends UserDefinedAggregateFunction {
}
}
// 7.不同分区中的merge操作
// 7.不同分区中的 merge 操作
def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
buffer1(0) = buffer1.getLong(0) + buffer2.getLong(0)
buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1)
@ -326,8 +326,8 @@ object SparkSqlApp {
val myAvg = spark.sql("SELECT myAverage(sal) as avg_sal FROM emp").first()
val avg = spark.sql("SELECT avg(sal) as avg_sal FROM emp").first()
println("自定义average函数 : " + myAvg)
println("内置的average函数 : " + avg)
println("自定义 average 函数 : " + myAvg)
println("内置的 average 函数 : " + avg)
}
}
```

View File

@ -16,7 +16,7 @@
## 一、 数据准备
本文主要介绍Spark SQL的多表连接需要预先准备测试数据。分别创建员工和部门的Datafame并注册为临时视图代码如下
本文主要介绍 Spark SQL 的多表连接,需要预先准备测试数据。分别创建员工和部门的 Datafame并注册为临时视图代码如下
```scala
val spark = SparkSession.builder().appName("aggregations").master("local[2]").getOrCreate()
@ -31,7 +31,7 @@ deptDF.createOrReplaceTempView("dept")
两表的主要字段如下:
```properties
emp员工表
emp 员工表
|-- ENAME: 员工姓名
|-- DEPTNO: 部门编号
|-- EMPNO: 员工编号
@ -43,19 +43,19 @@ emp员工表
```
```properties
dept部门表
dept 部门表
|-- DEPTNO: 部门编号
|-- DNAME: 部门名称
|-- LOC: 部门所在城市
```
> emp.jsondept.json可以在本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources)目录进行下载。
> emp.jsondept.json 可以在本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources) 目录进行下载。
## 二、连接类型
Spark中支持多种连接类型
Spark 中支持多种连接类型:
+ **Inner Join** : 内连接;
+ **Full Outer Join** : 全外连接;
@ -64,23 +64,23 @@ Spark中支持多种连接类型
+ **Left Semi Join** : 左半连接;
+ **Left Anti Join** : 左反连接;
+ **Natural Join** : 自然连接;
+ **Cross (or Cartesian) Join** : 交叉(或笛卡尔)连接。
+ **Cross (or Cartesian) Join** : 交叉 (或笛卡尔) 连接。
其中内,外连接,笛卡尔积均与普通关系型数据库中的相同,如下图所示:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/sql-join.jpg"/> </div>
这里解释一下左半连接和左反连接,这两个连接等价于关系型数据库中的`IN``NOT IN`字句:
这里解释一下左半连接和左反连接,这两个连接等价于关系型数据库中的 `IN``NOT IN` 字句:
```sql
-- LEFT SEMI JOIN
SELECT * FROM emp LEFT SEMI JOIN dept ON emp.deptno = dept.deptno
-- 等价于如下的IN语句
-- 等价于如下的 IN 语句
SELECT * FROM emp WHERE deptno IN (SELECT deptno FROM dept)
-- LEFT ANTI JOIN
SELECT * FROM emp LEFT ANTI JOIN dept ON emp.deptno = dept.deptno
-- 等价于如下的IN语句
-- 等价于如下的 IN 语句
SELECT * FROM emp WHERE deptno NOT IN (SELECT deptno FROM dept)
```
@ -94,7 +94,7 @@ val joinExpression = empDF.col("deptno") === deptDF.col("deptno")
// 2.连接查询
empDF.join(deptDF,joinExpression).select("ename","dname").show()
// 等价SQL如下
// 等价 SQL 如下:
spark.sql("SELECT ename,dname FROM emp JOIN dept ON emp.deptno = dept.deptno").show()
```
@ -148,7 +148,7 @@ spark.sql("SELECT * FROM emp CROSS JOIN dept ON emp.deptno = dept.deptno").show(
spark.sql("SELECT * FROM emp NATURAL JOIN dept").show()
```
以下是一个自然连接的查询结果程序自动推断出使用两张表都存在的dept列进行连接其实际等价于
以下是一个自然连接的查询结果,程序自动推断出使用两张表都存在的 dept 列进行连接,其实际等价于:
```sql
spark.sql("SELECT * FROM emp JOIN dept ON emp.deptno = dept.deptno").show()
@ -162,17 +162,17 @@ spark.sql("SELECT * FROM emp JOIN dept ON emp.deptno = dept.deptno").show()
## 三、连接的执行
在对大表与大表之间进行连接操作时,通常都会触发`Shuffle Join`,两表的所有分区节点会进行`All-to-All`的通讯这种查询通常比较昂贵会对网络IO会造成比较大的负担。
在对大表与大表之间进行连接操作时,通常都会触发 `Shuffle Join`,两表的所有分区节点会进行 `All-to-All` 的通讯,这种查询通常比较昂贵,会对网络 IO 会造成比较大的负担。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-Big-tabletobig-table.png"/> </div>
而对于大表和小表的连接操作Spark会在一定程度上进行优化如果小表的数据量小于Worker Node的内存空间Spark会考虑将小表的数据广播到每一个Worker Node在每个工作节点内部执行连接计算这可以降低网络的IO但会加大每个Worker NodeCPU负担。
而对于大表和小表的连接操作Spark 会在一定程度上进行优化,如果小表的数据量小于 Worker Node 的内存空间Spark 会考虑将小表的数据广播到每一个 Worker Node在每个工作节点内部执行连接计算这可以降低网络的 IO但会加大每个 Worker NodeCPU 负担。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-Big-tabletosmall-table.png"/> </div>
是否采用广播方式进行`Join`取决于程序内部对小表的判断,如果想明确使用广播方式进行`Join`则可以在DataFrame API 中使用`broadcast`方法指定需要广播的小表:
是否采用广播方式进行 `Join` 取决于程序内部对小表的判断,如果想明确使用广播方式进行 `Join`,则可以在 DataFrame API 中使用 `broadcast` 方法指定需要广播的小表:
```scala
empDF.join(broadcast(deptDF), joinExpression).show()
@ -182,4 +182,4 @@ empDF.join(broadcast(deptDF), joinExpression).show()
## 参考资料
1. Matei Zaharia, Bill Chambers . Spark: The Definitive Guide[M] . 2018-02
1. Matei Zaharia, Bill Chambers . Spark: The Definitive Guide[M] . 2018-02

View File

@ -23,15 +23,15 @@
## 一、RDD简介
`RDD`全称为Resilient Distributed Datasets是Spark最基本的数据抽象它是只读的、分区记录的集合支持并行操作可以由外部数据集或其他RDD转换而来它具有以下特性
`RDD` 全称为 Resilient Distributed Datasets Spark 最基本的数据抽象,它是只读的、分区记录的集合,支持并行操作,可以由外部数据集或其他 RDD 转换而来,它具有以下特性:
+ 一个RDD由一个或者多个分区Partitions组成。对于RDD来说每个分区会被一个计算任务所处理用户可以在创建RDD时指定其分区个数如果没有指定则默认采用程序所分配到的CPU的核心数
+ RDD拥有一个用于计算分区的函数compute
+ RDD会保存彼此间的依赖关系RDD的每次转换都会生成一个新的依赖关系这种RDD之间的依赖关系就像流水线一样。在部分分区数据丢失后可以通过这种依赖关系重新计算丢失的分区数据而不是对RDD的所有分区进行重新计算
+ Key-Value型的RDD还拥有Partitioner(分区器)用于决定数据被存储在哪个分区中目前Spark中支持HashPartitioner(按照哈希分区)RangeParationer(按照范围进行分区)
+ 一个优先位置列表(可选),用于存储每个分区的优先位置(prefered location)。对于一个HDFS文件来说这个列表保存的就是每个分区所在的块的位置按照“移动数据不如移动计算“的理念Spark在进行任务调度的时候会尽可能的将计算任务分配到其所要处理数据块的存储位置。
+ 一个 RDD 由一个或者多个分区Partitions组成。对于 RDD 来说,每个分区会被一个计算任务所处理,用户可以在创建 RDD 时指定其分区个数,如果没有指定,则默认采用程序所分配到的 CPU 的核心数;
+ RDD 拥有一个用于计算分区的函数 compute
+ RDD 会保存彼此间的依赖关系RDD 的每次转换都会生成一个新的依赖关系,这种 RDD 之间的依赖关系就像流水线一样。在部分分区数据丢失后,可以通过这种依赖关系重新计算丢失的分区数据,而不是对 RDD 的所有分区进行重新计算;
+ Key-Value 型的 RDD 还拥有 Partitioner(分区器),用于决定数据被存储在哪个分区中,目前 Spark 中支持 HashPartitioner(按照哈希分区)RangeParationer(按照范围进行分区)
+ 一个优先位置列表 (可选),用于存储每个分区的优先位置 (prefered location)。对于一个 HDFS 文件来说这个列表保存的就是每个分区所在的块的位置按照“移动数据不如移动计算“的理念Spark 在进行任务调度的时候,会尽可能的将计算任务分配到其所要处理数据块的存储位置。
`RDD[T]`抽象类的部分相关代码如下:
`RDD[T]` 抽象类的部分相关代码如下:
```scala
// 由子类实现以计算给定分区
@ -54,28 +54,28 @@ protected def getPreferredLocations(split: Partition): Seq[String] = Nil
## 二、创建RDD
RDD有两种创建方式分别介绍如下
RDD 有两种创建方式,分别介绍如下:
### 2.1 由现有集合创建
这里使用`spark-shell`进行测试,启动命令如下:
这里使用 `spark-shell` 进行测试,启动命令如下:
```shell
spark-shell --master local[4]
```
启动`spark-shell`程序会自动创建应用上下文相当于执行了下面的Scala语句
启动 `spark-shell` 后,程序会自动创建应用上下文,相当于执行了下面的 Scala 语句:
```scala
val conf = new SparkConf().setAppName("Spark shell").setMaster("local[4]")
val sc = new SparkContext(conf)
```
由现有集合创建RDD你可以在创建时指定其分区个数如果没有指定则采用程序所分配到的CPU的核心数
由现有集合创建 RDD你可以在创建时指定其分区个数如果没有指定则采用程序所分配到的 CPU 的核心数:
```scala
val data = Array(1, 2, 3, 4, 5)
// 由现有集合创建RDD,默认分区数为程序所分配到的CPU的核心数
// 由现有集合创建 RDD,默认分区数为程序所分配到的 CPU 的核心数
val dataRDD = sc.parallelize(data)
// 查看分区数
dataRDD.getNumPartitions
@ -89,7 +89,7 @@ val dataRDD = sc.parallelize(data,2)
### 2.2 引用外部存储系统中的数据集
引用外部存储系统中的数据集例如本地文件系统HDFSHBase或支持Hadoop InputFormat的任何数据源。
引用外部存储系统中的数据集例如本地文件系统HDFSHBase 或支持 Hadoop InputFormat 的任何数据源。
```scala
val fileRDD = sc.textFile("/usr/file/emp.txt")
@ -106,10 +106,10 @@ fileRDD.take(1)
两者都可以用来读取外部文件,但是返回格式是不同的:
+ **textFile**:其返回格式是`RDD[String]` 返回的是就是文件内容RDD中每一个元素对应一行数据
+ **wholeTextFiles**:其返回格式是`RDD[(String, String)]`,元组中第一个参数是文件路径,第二个参数是文件内容;
+ **textFile**:其返回格式是 `RDD[String]` 返回的是就是文件内容RDD 中每一个元素对应一行数据;
+ **wholeTextFiles**:其返回格式是 `RDD[(String, String)]`,元组中第一个参数是文件路径,第二个参数是文件内容;
+ 两者都提供第二个参数来控制最小分区数;
+ 从HDFS上读取文件时Spark会为每个块创建一个分区。
+ 从 HDFS 上读取文件时Spark 会为每个块创建一个分区。
```scala
def textFile(path: String,minPartitions: Int = defaultMinPartitions): RDD[String] = withScope {...}
@ -120,11 +120,11 @@ def wholeTextFiles(path: String,minPartitions: Int = defaultMinPartitions): RDD[
## 三、操作RDD
RDD支持两种类型的操作*transformations*(转换,从现有数据集创建新数据集)和 *actions*在数据集上运行计算后将值返回到驱动程序。RDD中的所有转换操作都是惰性的它们只是记住这些转换操作但不会立即执行只有遇到 *action* 操作后才会真正的进行计算,这类似于函数式编程中的惰性求值。
RDD 支持两种类型的操作:*transformations*(转换,从现有数据集创建新数据集)和 *actions*在数据集上运行计算后将值返回到驱动程序。RDD 中的所有转换操作都是惰性的,它们只是记住这些转换操作,但不会立即执行,只有遇到 *action* 操作后才会真正的进行计算,这类似于函数式编程中的惰性求值。
```scala
val list = List(1, 2, 3)
// map 是一个transformations操作而foreach是一个actions操作
// map 是一个 transformations 操作,而 foreach 是一个 actions 操作
sc.parallelize(list).map(_ * 10).foreach(println)
// 输出: 10 20 30
```
@ -135,38 +135,38 @@ sc.parallelize(list).map(_ * 10).foreach(println)
### 4.1 缓存级别
Spark速度非常快的一个原因是RDD支持缓存。成功缓存后如果之后的操作使用到了该数据集则直接从缓存中获取。虽然缓存也有丢失的风险但是由于RDD之间的依赖关系如果某个分区的缓存数据丢失只需要重新计算该分区即可。
Spark 速度非常快的一个原因是 RDD 支持缓存。成功缓存后,如果之后的操作使用到了该数据集,则直接从缓存中获取。虽然缓存也有丢失的风险,但是由于 RDD 之间的依赖关系,如果某个分区的缓存数据丢失,只需要重新计算该分区即可。
Spark支持多种缓存级别
Spark 支持多种缓存级别
| Storage Level<br/>(存储级别) | Meaning含义 |
| ---------------------------------------------- | ------------------------------------------------------------ |
| `MEMORY_ONLY` | 默认的缓存级别,将 RDD以反序列化的Java对象的形式存储在 JVM 中。如果内存空间不够,则部分分区数据将不再缓存。 |
| `MEMORY_AND_DISK` | 将 RDD 以反序列化的Java对象的形式存储JVM中。如果内存空间不够将未缓存的分区数据存储到磁盘在需要使用这些分区时从磁盘读取。 |
| `MEMORY_ONLY_SER`<br/> | 将 RDD 以序列化的Java对象的形式进行存储每个分区为一个 byte 数组。这种方式比反序列化对象节省存储空间但在读取时会增加CPU的计算负担。仅支持JavaScala 。 |
| `MEMORY_AND_DISK_SER`<br/> | 类似于`MEMORY_ONLY_SER`但是溢出的分区数据会存储到磁盘而不是在用到它们时重新计算。仅支持JavaScala。 |
| `DISK_ONLY` | 只在磁盘上缓存RDD |
| `MEMORY_ONLY` | 默认的缓存级别,将 RDD 以反序列化的 Java 对象的形式存储在 JVM 中。如果内存空间不够,则部分分区数据将不再缓存。 |
| `MEMORY_AND_DISK` | 将 RDD 以反序列化的 Java 对象的形式存储 JVM 中。如果内存空间不够,将未缓存的分区数据存储到磁盘,在需要使用这些分区时从磁盘读取。 |
| `MEMORY_ONLY_SER`<br/> | 将 RDD 以序列化的 Java 对象的形式进行存储(每个分区为一个 byte 数组)。这种方式比反序列化对象节省存储空间,但在读取时会增加 CPU 的计算负担。仅支持 JavaScala 。 |
| `MEMORY_AND_DISK_SER`<br/> | 类似于 `MEMORY_ONLY_SER`,但是溢出的分区数据会存储到磁盘,而不是在用到它们时重新计算。仅支持 JavaScala。 |
| `DISK_ONLY` | 只在磁盘上缓存 RDD |
| `MEMORY_ONLY_2`, <br/>`MEMORY_AND_DISK_2`, etc | 与上面的对应级别功能相同,但是会为每个分区在集群中的两个节点上建立副本。 |
| `OFF_HEAP` | 与`MEMORY_ONLY_SER`类似,但将数据存储在堆外内存中。这需要启用堆外内存。 |
| `OFF_HEAP` | 与 `MEMORY_ONLY_SER` 类似,但将数据存储在堆外内存中。这需要启用堆外内存。 |
> 启动堆外内存需要配置两个参数:
>
> + **spark.memory.offHeap.enabled** 是否开启堆外内存默认值为false需要设置为true
> + **spark.memory.offHeap.size** : 堆外内存空间的大小默认值为0需要设置为正值。
> + **spark.memory.offHeap.enabled** :是否开启堆外内存,默认值为 false需要设置为 true
> + **spark.memory.offHeap.size** : 堆外内存空间的大小,默认值为 0需要设置为正值。
### 4.2 使用缓存
缓存数据的方法有两个:`persist``cache``cache`内部调用的也是`persist`,它是`persist`的特殊化形式,等价于`persist(StorageLevel.MEMORY_ONLY)`。示例如下:
缓存数据的方法有两个:`persist``cache``cache` 内部调用的也是 `persist`,它是 `persist` 的特殊化形式,等价于 `persist(StorageLevel.MEMORY_ONLY)`。示例如下:
```scala
// 所有存储级别均定义在StorageLevel对象中
// 所有存储级别均定义在 StorageLevel 对象中
fileRDD.persist(StorageLevel.MEMORY_AND_DISK)
fileRDD.cache()
```
### 4.3 移除缓存
Spark会自动监视每个节点上的缓存使用情况并按照最近最少使用LRU的规则删除旧数据分区。当然你也可以使用`RDD.unpersist()`方法进行手动删除。
Spark 会自动监视每个节点上的缓存使用情况并按照最近最少使用LRU的规则删除旧数据分区。当然你也可以使用 `RDD.unpersist()` 方法进行手动删除。
@ -174,7 +174,7 @@ Spark会自动监视每个节点上的缓存使用情况并按照最近最少
### 5.1 shuffle介绍
在Spark中一个任务对应一个分区通常不会跨分区操作数据。但如果遇到`reduceByKey`等操作Spark必须从所有分区读取数据并查找所有键的所有值然后汇总在一起以计算每个键的最终结果 ,这称为`Shuffle`
Spark 中,一个任务对应一个分区,通常不会跨分区操作数据。但如果遇到 `reduceByKey` 等操作Spark 必须从所有分区读取数据,并查找所有键的所有值,然后汇总在一起以计算每个键的最终结果 ,这称为 `Shuffle`
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-reducebykey.png"/> </div>
@ -182,26 +182,26 @@ Spark会自动监视每个节点上的缓存使用情况并按照最近最少
### 5.2 Shuffle的影响
Shuffle是一项昂贵的操作因为它通常会跨节点操作数据这会涉及磁盘I/O网络I/O和数据序列化。某些Shuffle操作还会消耗大量的堆内存因为它们使用堆内存来临时存储需要网络传输的数据。Shuffle还会在磁盘上生成大量中间文件从Spark 1.3开始这些文件将被保留直到相应的RDD不再使用并进行垃圾回收这样做是为了避免在计算时重复创建Shuffle文件。如果应用程序长期保留对这些RDD的引用则垃圾回收可能在很长一段时间后才会发生这意味着长时间运行的Spark作业可能会占用大量磁盘空间通常可以使用`spark.local.dir`参数来指定这些临时文件的存储目录。
Shuffle 是一项昂贵的操作,因为它通常会跨节点操作数据,这会涉及磁盘 I/O网络 I/O和数据序列化。某些 Shuffle 操作还会消耗大量的堆内存因为它们使用堆内存来临时存储需要网络传输的数据。Shuffle 还会在磁盘上生成大量中间文件,从 Spark 1.3 开始,这些文件将被保留,直到相应的 RDD 不再使用并进行垃圾回收,这样做是为了避免在计算时重复创建 Shuffle 文件。如果应用程序长期保留对这些 RDD 的引用,则垃圾回收可能在很长一段时间后才会发生,这意味着长时间运行的 Spark 作业可能会占用大量磁盘空间,通常可以使用 `spark.local.dir` 参数来指定这些临时文件的存储目录。
### 5.3 导致Shuffle的操作
由于Shuffle操作对性能的影响比较大所以需要特别注意使用以下操作都会导致Shuffle
由于 Shuffle 操作对性能的影响比较大,所以需要特别注意使用,以下操作都会导致 Shuffle
+ **涉及到重新分区操作**`repartition``coalesce`
+ **所有涉及到ByKey的操作**:如`groupByKey``reduceByKey`,但`countByKey`除外;
+ **联结操作**:如`cogroup``join`
+ **涉及到重新分区操作** `repartition``coalesce`
+ **所有涉及到 ByKey 的操作**:如 `groupByKey``reduceByKey`,但 `countByKey` 除外;
+ **联结操作**:如 `cogroup``join`
## 五、宽依赖和窄依赖
RDD和它的父RDD(s)之间的依赖关系分为两种不同的类型:
RDD 和它的父 RDD(s) 之间的依赖关系分为两种不同的类型:
- **窄依赖(narrow dependency)**父RDDs的一个分区最多被子RDDs一个分区所依赖
- **宽依赖(wide dependency)**父RDDs的一个分区可以被子RDDs的多个子分区所依赖。
- **窄依赖 (narrow dependency)**:父 RDDs 的一个分区最多被子 RDDs 一个分区所依赖;
- **宽依赖 (wide dependency)**:父 RDDs 的一个分区可以被子 RDDs 的多个子分区所依赖。
如下图每一个方框表示一个RDD带有颜色的矩形表示分区
如下图,每一个方框表示一个 RDD带有颜色的矩形表示分区
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-窄依赖和宽依赖.png"/> </div>
@ -209,17 +209,17 @@ RDD和它的父RDD(s)之间的依赖关系分为两种不同的类型:
区分这两种依赖是非常有用的:
+ 首先窄依赖允许在一个集群节点上以流水线的方式pipeline对父分区数据进行计算例如先执行map操作然后执行filter操作。而宽依赖则需要计算好所有父分区的数据然后再在节点之间进行Shuffle这与MapReduce类似。
+ 窄依赖能够更有效地进行数据恢复因为只需重新对丢失分区的父分区进行计算且不同节点之间可以并行计算而对于宽依赖而言如果数据丢失则需要对所有父分区数据进行计算并再次Shuffle。
+ 首先窄依赖允许在一个集群节点上以流水线的方式pipeline对父分区数据进行计算例如先执行 map 操作,然后执行 filter 操作。而宽依赖则需要计算好所有父分区的数据,然后再在节点之间进行 Shuffle这与 MapReduce 类似。
+ 窄依赖能够更有效地进行数据恢复,因为只需重新对丢失分区的父分区进行计算,且不同节点之间可以并行计算;而对于宽依赖而言,如果数据丢失,则需要对所有父分区数据进行计算并再次 Shuffle。
## 六、DAG的生成
RDD(s)及其之间的依赖关系组成了DAG(有向无环图)DAG定义了这些RDD(s)之间的Lineage(血统)关系通过血统关系如果一个RDD的部分或者全部计算结果丢失了也可以重新进行计算。那么Spark是如何根据DAG来生成计算任务呢主要是根据依赖关系的不同将DAG划分为不同的计算阶段(Stage)
RDD(s) 及其之间的依赖关系组成了 DAG(有向无环图)DAG 定义了这些 RDD(s) 之间的 Lineage(血统) 关系,通过血统关系,如果一个 RDD 的部分或者全部计算结果丢失了,也可以重新进行计算。那么 Spark 是如何根据 DAG 来生成计算任务呢?主要是根据依赖关系的不同将 DAG 划分为不同的计算阶段 (Stage)
+ 对于窄依赖,由于分区的依赖关系是确定的,其转换操作可以在同一个线程执行,所以可以划分到同一个执行阶段;
+ 对于宽依赖由于Shuffle的存在只能在父RDD(s)Shuffle处理完成后才能开始接下来的计算因此遇到宽依赖就需要重新划分阶段。
+ 对于宽依赖,由于 Shuffle 的存在,只能在父 RDD(s)Shuffle 处理完成后,才能开始接下来的计算,因此遇到宽依赖就需要重新划分阶段。
<div align="center"> <img width="600px" height="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-DAG.png"/> </div>
@ -229,7 +229,7 @@ RDD(s)及其之间的依赖关系组成了DAG(有向无环图)DAG定义了这
## 参考资料
1. 张安站 . Spark技术内幕深入解析Spark内核架构设计与实现原理[M] . 机械工业出版社 . 2015-09-01
1. 张安站 . Spark 技术内幕:深入解析 Spark 内核架构设计与实现原理[M] . 机械工业出版社 . 2015-09-01
2. [RDD Programming Guide](https://spark.apache.org/docs/latest/rdd-programming-guide.html#rdd-programming-guide)
3. [RDD基于内存的集群计算容错抽象](http://shiyanjun.cn/archives/744.html)

View File

@ -14,7 +14,7 @@
### 1.1 静态数据处理
在流处理之前数据通常存储在数据库文件系统或其他形式的存储系统中。应用程序根据需要查询数据或计算数据。这就是传统的静态数据处理架构。Hadoop采用HDFS进行数据存储采用MapReduce进行数据查询或分析这就是典型的静态数据处理架构。
在流处理之前数据通常存储在数据库文件系统或其他形式的存储系统中。应用程序根据需要查询数据或计算数据。这就是传统的静态数据处理架构。Hadoop 采用 HDFS 进行数据存储,采用 MapReduce 进行数据查询或分析,这就是典型的静态数据处理架构。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/01_data_at_rest_infrastructure.png"/> </div>
@ -26,7 +26,7 @@
大多数数据都是连续的流:传感器事件,网站上的用户活动,金融交易等等 ,所有这些数据都是随着时间的推移而创建的。
接收和发送数据流并执行应用程序或分析逻辑的系统称为**流处理器**。流处理器的基本职责是确保数据有效流动同时具备可扩展性和容错能力StormFlink就是其代表性的实现。
接收和发送数据流并执行应用程序或分析逻辑的系统称为**流处理器**。流处理器的基本职责是确保数据有效流动同时具备可扩展性和容错能力StormFlink 就是其代表性的实现。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/02_stream_processing_infrastructure.png"/> </div>
@ -47,19 +47,19 @@
### 2.1 简介
Spark StreamingSpark的一个子模块用于快速构建可扩展高吞吐量高容错的流处理程序。具有以下特点
Spark StreamingSpark 的一个子模块,用于快速构建可扩展,高吞吐量,高容错的流处理程序。具有以下特点:
+ 通过高级API构建应用程序简单易用
+ 支持多种语言如JavaScalaPython
+ 良好的容错性Spark Streaming支持快速从失败中恢复丢失的操作状态
+ 能够和Spark其他模块无缝集成将流处理与批处理完美结合
+ Spark Streaming可以从HDFSFlumeKafkaTwitterZeroMQ读取数据也支持自定义数据源。
+ 通过高级 API 构建应用程序,简单易用;
+ 支持多种语言,如 JavaScalaPython
+ 良好的容错性Spark Streaming 支持快速从失败中恢复丢失的操作状态;
+ 能够和 Spark 其他模块无缝集成,将流处理与批处理完美结合;
+ Spark Streaming 可以从 HDFSFlumeKafkaTwitterZeroMQ 读取数据,也支持自定义数据源。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-streaming-arch.png"/> </div>
### 2.2 DStream
Spark Streaming提供称为离散流(DStream)的高级抽象,用于表示连续的数据流。 DStream可以从来自KafkaFlumeKinesis等数据源的输入数据流创建也可以由其他DStream转化而来。**在内部DStream表示为一系列RDD**。
Spark Streaming 提供称为离散流 (DStream) 的高级抽象,用于表示连续的数据流。 DStream 可以从来自 KafkaFlumeKinesis 等数据源的输入数据流创建,也可以由其他 DStream 转化而来。**在内部DStream 表示为一系列 RDD**。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-streaming-flow.png"/> </div>
@ -67,7 +67,7 @@ Spark Streaming提供称为离散流(DStream)的高级抽象,用于表示连
### 2.3 Spark & Storm & Flink
stormFlink都是真正意义上的流计算框架但 Spark Streaming 只是将数据流进行极小粒度的拆分,拆分为多个批处理,使得其能够得到接近于流处理的效果,但其本质上还是批处理(或微批处理)。
stormFlink 都是真正意义上的流计算框架,但 Spark Streaming 只是将数据流进行极小粒度的拆分,拆分为多个批处理,使得其能够得到接近于流处理的效果,但其本质上还是批处理(或微批处理)。
@ -76,4 +76,4 @@ storm和Flink都是真正意义上的流计算框架但 Spark Streaming 只
## 参考资料
1. [Spark Streaming Programming Guide](https://spark.apache.org/docs/latest/streaming-programming-guide.html)
2. [What is stream processing?](https://www.ververica.com/what-is-stream-processing)
2. [What is stream processing?](https://www.ververica.com/what-is-stream-processing)

View File

@ -36,7 +36,7 @@ object NetworkWordCount {
def main(args: Array[String]) {
/*指定时间间隔为5s*/
/*指定时间间隔为 5s*/
val sparkConf = new SparkConf().setAppName("NetworkWordCount").setMaster("local[2]")
val ssc = new StreamingContext(sparkConf, Seconds(5))
@ -52,7 +52,7 @@ object NetworkWordCount {
}
```
使用本地模式启动Spark程序然后使用`nc -lk 9999`打开端口并输入测试数据:
使用本地模式启动 Spark 程序,然后使用 `nc -lk 9999` 打开端口并输入测试数据:
```shell
[root@hadoop001 ~]# nc -lk 9999
@ -70,18 +70,18 @@ storm storm flink azkaban
### 3.1 StreamingContext
Spark Streaming编程的入口类是StreamingContext在创建时候需要指明`sparkConf``batchDuration`(批次时间)Spark流处理本质是将流数据拆分为一个个批次然后进行微批处理`batchDuration`就是批次拆分的时间间隔。这个时间可以根据业务需求和服务器性能进行指定,如果业务要求低延迟并且服务器性能也允许,则这个时间可以指定得很短。
Spark Streaming 编程的入口类是 StreamingContext在创建时候需要指明 `sparkConf``batchDuration`(批次时间)Spark 流处理本质是将流数据拆分为一个个批次,然后进行微批处理,`batchDuration` 就是批次拆分的时间间隔。这个时间可以根据业务需求和服务器性能进行指定,如果业务要求低延迟并且服务器性能也允许,则这个时间可以指定得很短。
这里需要注意的是:示例代码使用的是本地模式,配置为`local[2]`,这里不能配置为`local[1]`。这是因为对于流数据的处理Spark必须有一个独立的Executor来接收数据然后再由其他的Executors来处理所以为了保证数据能够被处理至少要有2个Executors。这里我们的程序只有一个数据流在并行读取多个数据流的时候也需要保证有足够的Executors来接收和处理数据。
这里需要注意的是:示例代码使用的是本地模式,配置为 `local[2]`,这里不能配置为 `local[1]`。这是因为对于流数据的处理Spark 必须有一个独立的 Executor 来接收数据,然后再由其他的 Executors 来处理,所以为了保证数据能够被处理,至少要有 2 个 Executors。这里我们的程序只有一个数据流在并行读取多个数据流的时候也需要保证有足够的 Executors 来接收和处理数据。
### 3.2 数据源
在示例代码中使用的是`socketTextStream`来创建基于Socket的数据流实际上Spark还支持多种数据源分为以下两类
在示例代码中使用的是 `socketTextStream` 来创建基于 Socket 的数据流,实际上 Spark 还支持多种数据源,分为以下两类:
+ **基本数据源**包括文件系统、Socket连接等
+ **高级数据源**包括KafkaFlumeKinesis等。
+ **基本数据源**包括文件系统、Socket 连接等;
+ **高级数据源**:包括 KafkaFlumeKinesis 等。
在基本数据源中Spark支持监听HDFS上指定目录当有新文件加入时会获取其文件内容作为输入流。创建方式如下
在基本数据源中Spark 支持监听 HDFS 上指定目录,当有新文件加入时,会获取其文件内容作为输入流。创建方式如下:
```scala
// 对于文本文件,指明监听目录即可
@ -90,13 +90,13 @@ streamingContext.textFileStream(dataDirectory)
streamingContext.fileStream[KeyClass, ValueClass, InputFormatClass](dataDirectory)
```
被监听的目录可以是具体目录,如`hdfs://host:8040/logs/`;也可以使用通配符,如`hdfs://host:8040/logs/2017/*`
被监听的目录可以是具体目录,如 `hdfs://host:8040/logs/`;也可以使用通配符,如 `hdfs://host:8040/logs/2017/*`
> 关于高级数据源的整合单独整理至:[Spark Streaming整合Flume](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Spark_Streaming整合Flume.md) 和 [Spark Streaming整合Kafka](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Spark_Streaming整合Kafka.md)
> 关于高级数据源的整合单独整理至:[Spark Streaming 整合 Flume](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Spark_Streaming 整合 Flume.md) 和 [Spark Streaming 整合 Kafka](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Spark_Streaming 整合 Kafka.md)
### 3.3 服务的启动与停止
在示例代码中,使用`streamingContext.start()`代表启动服务,此时还要使用`streamingContext.awaitTermination()`使服务处于等待和可用的状态,直到发生异常或者手动使用`streamingContext.stop()`进行终止。
在示例代码中,使用 `streamingContext.start()` 代表启动服务,此时还要使用 `streamingContext.awaitTermination()` 使服务处于等待和可用的状态,直到发生异常或者手动使用 `streamingContext.stop()` 进行终止。
@ -104,13 +104,13 @@ streamingContext.fileStream[KeyClass, ValueClass, InputFormatClass](dataDirector
### 2.1 DStream与RDDs
DStreamSpark Streaming提供的基本抽象。它表示连续的数据流。在内部DStream由一系列连续的RDD表示。所以从本质上而言应用于DStream的任何操作都会转换为底层RDD上的操作。例如在示例代码中flatMap算子的操作实际上是作用在每个RDDs(如下图)。因为这个原因所以DStream能够支持RDD大部分的*transformation*算子。
DStreamSpark Streaming 提供的基本抽象。它表示连续的数据流。在内部DStream 由一系列连续的 RDD 表示。所以从本质上而言,应用于 DStream 的任何操作都会转换为底层 RDD 上的操作。例如,在示例代码中 flatMap 算子的操作实际上是作用在每个 RDDs(如下图)。因为这个原因,所以 DStream 能够支持 RDD 大部分的*transformation*算子。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-streaming-dstream-ops.png"/> </div>
### 2.2 updateStateByKey
除了能够支持RDD的算子外DStream还有部分独有的*transformation*算子,这当中比较常用的是`updateStateByKey`。文章开头的词频统计程序,只能统计每一次输入文本中单词出现的数量,想要统计所有历史输入中单词出现的数量,可以使用`updateStateByKey`算子。代码如下:
除了能够支持 RDD 的算子外DStream 还有部分独有的*transformation*算子,这当中比较常用的是 `updateStateByKey`。文章开头的词频统计程序,只能统计每一次输入文本中单词出现的数量,想要统计所有历史输入中单词出现的数量,可以使用 `updateStateByKey` 算子。代码如下:
```scala
object NetworkWordCountV2 {
@ -119,8 +119,8 @@ object NetworkWordCountV2 {
def main(args: Array[String]) {
/*
* 本地测试时最好指定hadoop用户名,否则会默认使用本地电脑的用户名,
* 此时在HDFS上创建目录时可能会抛出权限不足的异常
* 本地测试时最好指定 hadoop 用户名,否则会默认使用本地电脑的用户名,
* 此时在 HDFS 上创建目录时可能会抛出权限不足的异常
*/
System.setProperty("HADOOP_USER_NAME", "root")
@ -130,7 +130,7 @@ object NetworkWordCountV2 {
ssc.checkpoint("hdfs://hadoop001:8020/spark-streaming")
val lines = ssc.socketTextStream("hadoop001", 9999)
lines.flatMap(_.split(" ")).map(x => (x, 1))
.updateStateByKey[Int](updateFunction _) //updateStateByKey算子
.updateStateByKey[Int](updateFunction _) //updateStateByKey 算子
.print()
ssc.start()
@ -152,7 +152,7 @@ object NetworkWordCountV2 {
}
```
使用`updateStateByKey`算子,你必须使用`ssc.checkpoint()`设置检查点,这样当使用`updateStateByKey`算子时,它会去检查点中取出上一次保存的信息,并使用自定义的`updateFunction`函数将上一次的数据和本次数据进行相加,然后返回。
使用 `updateStateByKey` 算子,你必须使用 `ssc.checkpoint()` 设置检查点,这样当使用 `updateStateByKey` 算子时,它会去检查点中取出上一次保存的信息,并使用自定义的 `updateFunction` 函数将上一次的数据和本次数据进行相加,然后返回。
### 2.3 启动测试
@ -186,21 +186,21 @@ Deleting hdfs://hadoop001:8020/spark-streaming/checkpoint-1558945265000
### 3.1 输出API
Spark Streaming支持以下输出操作
Spark Streaming 支持以下输出操作:
| Output Operation | Meaning |
| :------------------------------------------ | :----------------------------------------------------------- |
| **print**() | 在运行流应用程序的driver节点上打印DStream中每个批次的前十个元素。用于开发调试。 |
| **saveAsTextFiles**(*prefix*, [*suffix*]) | 将DStream的内容保存为文本文件。每个批处理间隔的文件名基于前缀和后缀生成“prefix-TIME_IN_MS [.suffix]”。 |
| **saveAsObjectFiles**(*prefix*, [*suffix*]) | 将DStream的内容序列化为Java对象并保存到SequenceFiles。每个批处理间隔的文件名基于前缀和后缀生成“prefix-TIME_IN_MS [.suffix]”。 |
| **saveAsHadoopFiles**(*prefix*, [*suffix*]) | 将DStream的内容保存为Hadoop文件。每个批处理间隔的文件名基于前缀和后缀生成“prefix-TIME_IN_MS [.suffix]”。 |
| **foreachRDD**(*func*) | 最通用的输出方式它将函数func应用于从流生成的每个RDD。此函数应将每个RDD中的数据推送到外部系统例如将RDD保存到文件或通过网络将其写入数据库。 |
| **print**() | 在运行流应用程序的 driver 节点上打印 DStream 中每个批次的前十个元素。用于开发调试。 |
| **saveAsTextFiles**(*prefix*, [*suffix*]) | 将 DStream 的内容保存为文本文件。每个批处理间隔的文件名基于前缀和后缀生成“prefix-TIME_IN_MS [.suffix]”。 |
| **saveAsObjectFiles**(*prefix*, [*suffix*]) | 将 DStream 的内容序列化为 Java 对象,并保存到 SequenceFiles。每个批处理间隔的文件名基于前缀和后缀生成“prefix-TIME_IN_MS [.suffix]”。 |
| **saveAsHadoopFiles**(*prefix*, [*suffix*]) | 将 DStream 的内容保存为 Hadoop 文件。每个批处理间隔的文件名基于前缀和后缀生成“prefix-TIME_IN_MS [.suffix]”。 |
| **foreachRDD**(*func*) | 最通用的输出方式,它将函数 func 应用于从流生成的每个 RDD。此函数应将每个 RDD 中的数据推送到外部系统,例如将 RDD 保存到文件,或通过网络将其写入数据库。 |
前面的四个API都是直接调用即可下面主要讲解通用的输出方式`foreachRDD(func)`通过该API你可以将数据保存到任何你需要的数据源。
前面的四个 API 都是直接调用即可,下面主要讲解通用的输出方式 `foreachRDD(func)`,通过该 API 你可以将数据保存到任何你需要的数据源。
### 3.1 foreachRDD
这里我们使用Redis作为客户端对文章开头示例程序进行改变把每一次词频统计的结果写入到Redis并利用Redis`HINCRBY`命令来进行词频统计。这里需要导入Jedis依赖
这里我们使用 Redis 作为客户端,对文章开头示例程序进行改变,把每一次词频统计的结果写入到 Redis并利用 Redis`HINCRBY` 命令来进行词频统计。这里需要导入 Jedis 依赖:
```xml
<dependency>
@ -228,7 +228,7 @@ object NetworkWordCountToRedis {
/*创建文本输入流,并进行词频统计*/
val lines = ssc.socketTextStream("hadoop001", 9999)
val pairs: DStream[(String, Int)] = lines.flatMap(_.split(" ")).map(x => (x, 1)).reduceByKey(_ + _)
/*保存数据到Redis*/
/*保存数据到 Redis*/
pairs.foreachRDD { rdd =>
rdd.foreachPartition { partitionOfRecords =>
var jedis: Jedis = null
@ -250,7 +250,7 @@ object NetworkWordCountToRedis {
```
其中`JedisPoolUtil`的代码如下:
其中 `JedisPoolUtil` 的代码如下:
```java
import redis.clients.jedis.Jedis;
@ -259,7 +259,7 @@ import redis.clients.jedis.JedisPoolConfig;
public class JedisPoolUtil {
/* 声明为volatile防止指令重排序 */
/* 声明为 volatile 防止指令重排序 */
private static volatile JedisPool jedisPool = null;
private static final String HOST = "localhost";
private static final int PORT = 6379;
@ -283,7 +283,7 @@ public class JedisPoolUtil {
### 3.3 代码说明
这里将上面保存到Redis的代码单独抽取出来并去除异常判断的部分。精简后的代码如下
这里将上面保存到 Redis 的代码单独抽取出来,并去除异常判断的部分。精简后的代码如下:
```scala
pairs.foreachRDD { rdd =>
@ -295,7 +295,7 @@ pairs.foreachRDD { rdd =>
}
```
这里可以看到一共使用了三次循环分别是循环RDD循环分区循环每条记录上面我们的代码是在循环分区的时候获取连接也就是为每一个分区获取一个连接。但是这里大家可能会有疑问为什么不在循环RDD的时候为每一个RDD获取一个连接这样所需要的连接数会更少。实际上这是不可行的如果按照这种情况进行改写如下
这里可以看到一共使用了三次循环,分别是循环 RDD循环分区循环每条记录上面我们的代码是在循环分区的时候获取连接也就是为每一个分区获取一个连接。但是这里大家可能会有疑问为什么不在循环 RDD 的时候,为每一个 RDD 获取一个连接,这样所需要的连接数会更少。实际上这是不可行的,如果按照这种情况进行改写,如下:
```scala
pairs.foreachRDD { rdd =>
@ -307,9 +307,9 @@ pairs.foreachRDD { rdd =>
}
```
此时在执行时候就会抛出`Caused by: java.io.NotSerializableException: redis.clients.jedis.Jedis`这是因为在实际计算时Spark会将对RDD操作分解为多个TaskTask运行在具体的Worker Node上。在执行之前Spark会对任务进行闭包之后闭包被序列化并发送给每个Executor`Jedis`显然是不能被序列化的,所以会抛出异常。
此时在执行时候就会抛出 `Caused by: java.io.NotSerializableException: redis.clients.jedis.Jedis`这是因为在实际计算时Spark 会将对 RDD 操作分解为多个 TaskTask 运行在具体的 Worker Node 上。在执行之前Spark 会对任务进行闭包,之后闭包被序列化并发送给每个 Executor `Jedis` 显然是不能被序列化的,所以会抛出异常。
第二个需要注意的是ConnectionPool最好是一个静态惰性初始化连接池 。这是因为Spark的转换操作本身就是惰性的且没有数据流时不会触发写出操作所以出于性能考虑连接池应该是惰性的因此上面`JedisPool`在初始化时采用了懒汉式单例进行惰性初始化。
第二个需要注意的是 ConnectionPool 最好是一个静态,惰性初始化连接池 。这是因为 Spark 的转换操作本身就是惰性的,且没有数据流时不会触发写出操作,所以出于性能考虑,连接池应该是惰性的,因此上面 `JedisPool` 在初始化时采用了懒汉式单例进行惰性初始化。
### 3.4 启动测试
@ -323,7 +323,7 @@ hello world hello spark hive hive hadoop
storm storm flink azkaban
```
使用Redis Manager查看写入结果(如下图),可以看到与使用`updateStateByKey`算子得到的计算结果相同。
使用 Redis Manager 查看写入结果 (如下图),可以看到与使用 `updateStateByKey` 算子得到的计算结果相同。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-streaming-word-count-v3.png"/> </div>
@ -335,4 +335,4 @@ storm storm flink azkaban
## 参考资料
Spark官方文档http://spark.apache.org/docs/latest/streaming-programming-guide.html
Spark 官方文档http://spark.apache.org/docs/latest/streaming-programming-guide.html

View File

@ -20,15 +20,15 @@
## 一、简介
Apache Flume是一个分布式高可用的数据收集系统可以从不同的数据源收集数据经过聚合后发送到分布式计算框架或者存储系统中。Spark Straming提供了以下两种方式用于Flume的整合。
Apache Flume 是一个分布式高可用的数据收集系统可以从不同的数据源收集数据经过聚合后发送到分布式计算框架或者存储系统中。Spark Straming 提供了以下两种方式用于 Flume 的整合。
## 二、推送式方法
在推送式方法(Flume-style Push-based Approach)中Spark Streaming程序需要对某台服务器的某个端口进行监听Flume通过`avro Sink`将数据源源不断推送到该端口。这里以监听日志文件为例,具体整合方式如下:
在推送式方法 (Flume-style Push-based Approach) Spark Streaming 程序需要对某台服务器的某个端口进行监听Flume 通过 `avro Sink` 将数据源源不断推送到该端口。这里以监听日志文件为例,具体整合方式如下:
### 2.1 配置日志收集Flume
新建配置`netcat-memory-avro.properties`,使用`tail`命令监听文件内容变化,然后将新的文件内容通过`avro sink`发送到hadoop001这台服务器的8888端口
新建配置 `netcat-memory-avro.properties`,使用 `tail` 命令监听文件内容变化,然后将新的文件内容通过 `avro sink` 发送到 hadoop001 这台服务器的 8888 端口:
```properties
#指定agent的sources,sinks,channels
@ -57,7 +57,7 @@ a1.channels.c1.transactionCapacity = 100
### 2.2 项目依赖
项目采用Maven工程进行构建主要依赖为`spark-streaming``spark-streaming-flume`
项目采用 Maven 工程进行构建,主要依赖为 `spark-streaming``spark-streaming-flume`
```xml
<properties>
@ -72,7 +72,7 @@ a1.channels.c1.transactionCapacity = 100
<artifactId>spark-streaming_${scala.version}</artifactId>
<version>${spark.version}</version>
</dependency>
<!-- Spark Streaming整合Flume依赖-->
<!-- Spark Streaming 整合 Flume 依赖-->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-flume_${scala.version}</artifactId>
@ -84,7 +84,7 @@ a1.channels.c1.transactionCapacity = 100
### 2.3 Spark Streaming接收日志数据
调用 FlumeUtils工具类的`createStream`方法对hadoop0018888端口进行监听获取到流数据并进行打印
调用 FlumeUtils 工具类的 `createStream` 方法,对 hadoop0018888 端口进行监听,获取到流数据并进行打印:
```scala
import org.apache.spark.SparkConf
@ -109,9 +109,9 @@ object PushBasedWordCount {
### 2.4 项目打包
因为Spark安装目录下是不含有`spark-streaming-flume`依赖包的,所以在提交到集群运行时候必须提供该依赖包,你可以在提交命令中使用`--jar`指定上传到服务器的该依赖包,或者使用`--packages org.apache.spark:spark-streaming-flume_2.12:2.4.3`指定依赖包的完整名称,这样程序在启动时会先去中央仓库进行下载。
因为 Spark 安装目录下是不含有 `spark-streaming-flume` 依赖包的,所以在提交到集群运行时候必须提供该依赖包,你可以在提交命令中使用 `--jar` 指定上传到服务器的该依赖包,或者使用 `--packages org.apache.spark:spark-streaming-flume_2.12:2.4.3` 指定依赖包的完整名称,这样程序在启动时会先去中央仓库进行下载。
这里我采用的是第三种方式:使用`maven-shade-plugin`插件进行`ALL IN ONE`打包把所有依赖的Jar一并打入最终包中。需要注意的是`spark-streaming`包在Spark安装目录的`jars`目录中已经提供,所以不需要打入。插件配置如下:
这里我采用的是第三种方式:使用 `maven-shade-plugin` 插件进行 `ALL IN ONE` 打包,把所有依赖的 Jar 一并打入最终包中。需要注意的是 `spark-streaming` 包在 Spark 安装目录的 `jars` 目录中已经提供,所以不需要打入。插件配置如下:
```xml
@ -125,7 +125,7 @@ object PushBasedWordCount {
<target>8</target>
</configuration>
</plugin>
<!--使用shade进行打包-->
<!--使用 shade 进行打包-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
@ -174,7 +174,7 @@ object PushBasedWordCount {
</execution>
</executions>
</plugin>
<!--打包.scala文件需要配置此插件-->
<!--打包.scala 文件需要配置此插件-->
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
@ -204,13 +204,13 @@ object PushBasedWordCount {
```
> 本项目完整源码见:[spark-streaming-flume](https://github.com/heibaiying/BigData-Notes/tree/master/code/spark/spark-streaming-flume)
使用`mvn clean package`命令打包后会生产以下两个Jar包提交`original`开头的Jar即可。
使用 `mvn clean package` 命令打包后会生产以下两个 Jar 包,提交 `original` 开头的 Jar 即可。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-streaming-flume-jar.png"/> </div>
### 2.5 启动服务和提交作业
启动Flume服务
启动 Flume 服务:
```shell
flume-ng agent \
@ -219,7 +219,7 @@ flume-ng agent \
--name a1 -Dflume.root.logger=INFO,console
```
提交Spark Streaming作业
提交 Spark Streaming 作业:
```shell
spark-submit \
@ -230,11 +230,11 @@ spark-submit \
### 2.6 测试
这里使用`echo`命令模拟日志产生的场景,往日志文件中追加数据,然后查看程序的输出:
这里使用 `echo` 命令模拟日志产生的场景,往日志文件中追加数据,然后查看程序的输出:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-flume-input.png"/> </div>
Spark Streaming程序成功接收到数据并打印输出
Spark Streaming 程序成功接收到数据并打印输出:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-flume-console.png"/> </div>
@ -242,23 +242,23 @@ Spark Streaming程序成功接收到数据并打印输出
#### 1. 启动顺序
这里需要注意的不论你先启动Spark程序还是Flume程序由于两者的启动都需要一定的时间此时先启动的程序会短暂地抛出端口拒绝连接的异常此时不需要进行任何操作等待两个程序都启动完成即可。
这里需要注意的,不论你先启动 Spark 程序还是 Flume 程序,由于两者的启动都需要一定的时间,此时先启动的程序会短暂地抛出端口拒绝连接的异常,此时不需要进行任何操作,等待两个程序都启动完成即可。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/flume-retry.png"/> </div>
#### 2. 版本一致
最好保证用于本地开发和编译的Scala版本和SparkScala版本一致至少保证大版本一致如都是`2.11`
最好保证用于本地开发和编译的 Scala 版本和 SparkScala 版本一致,至少保证大版本一致,如都是 `2.11`
<br/>
## 三、拉取式方法
拉取式方法(Pull-based Approach using a Custom Sink)是将数据推送到`SparkSink`接收器中此时数据会保持缓冲状态Spark Streaming定时从接收器中拉取数据。这种方式是基于事务的即只有在Spark Streaming接收和复制数据完成后才会删除缓存的数据。与第一种方式相比具有更强的可靠性和容错保证。整合步骤如下
拉取式方法 (Pull-based Approach using a Custom Sink) 是将数据推送到 `SparkSink` 接收器中此时数据会保持缓冲状态Spark Streaming 定时从接收器中拉取数据。这种方式是基于事务的,即只有在 Spark Streaming 接收和复制数据完成后,才会删除缓存的数据。与第一种方式相比,具有更强的可靠性和容错保证。整合步骤如下:
### 3.1 配置日志收集Flume
新建Flume配置文件`netcat-memory-sparkSink.properties`,配置和上面基本一致,只是把`a1.sinks.k1.type`的属性修改为`org.apache.spark.streaming.flume.sink.SparkSink`即采用Spark接收器。
新建 Flume 配置文件 `netcat-memory-sparkSink.properties`,配置和上面基本一致,只是把 `a1.sinks.k1.type` 的属性修改为 `org.apache.spark.streaming.flume.sink.SparkSink`,即采用 Spark 接收器。
```properties
#指定agent的sources,sinks,channels
@ -302,11 +302,11 @@ a1.channels.c1.transactionCapacity = 100
</dependency>
```
注意添加这两个依赖只是为了本地测试Spark的安装目录下已经提供了这两个依赖所以在最终打包时需要进行排除。
注意添加这两个依赖只是为了本地测试Spark 的安装目录下已经提供了这两个依赖,所以在最终打包时需要进行排除。
### 2.3 Spark Streaming接收日志数据
这里和上面推送式方法的代码基本相同,只是将调用方法改为`createPollingStream`
这里和上面推送式方法的代码基本相同,只是将调用方法改为 `createPollingStream`
```scala
import org.apache.spark.SparkConf
@ -333,7 +333,7 @@ object PullBasedWordCount {
启动和提交作业流程与上面相同,这里给出执行脚本,过程不再赘述。
启动Flume进行日志收集
启动 Flume 进行日志收集:
```shell
flume-ng agent \
@ -342,7 +342,7 @@ flume-ng agent \
--name a1 -Dflume.root.logger=INFO,console
```
提交Spark Streaming作业
提交 Spark Streaming 作业:
```shel
spark-submit \
@ -356,4 +356,4 @@ spark-submit \
## 参考资料
- [streaming-flume-integration](https://spark.apache.org/docs/latest/streaming-flume-integration.html)
- 关于大数据应用常用的打包方式可以参见:[大数据应用常用打包方式](https://github.com/heibaiying/BigData-Notes/blob/master/notes/大数据应用常用打包方式.md)
- 关于大数据应用常用的打包方式可以参见:[大数据应用常用打包方式](https://github.com/heibaiying/BigData-Notes/blob/master/notes/大数据应用常用打包方式.md)

View File

@ -15,12 +15,12 @@
## 一、版本说明
Spark针对Kafka的不同版本提供了两套整合方案`spark-streaming-kafka-0-8``spark-streaming-kafka-0-10`,其主要区别如下:
Spark 针对 Kafka 的不同版本,提供了两套整合方案:`spark-streaming-kafka-0-8``spark-streaming-kafka-0-10`,其主要区别如下:
| | [spark-streaming-kafka-0-8](https://spark.apache.org/docs/latest/streaming-kafka-0-8-integration.html) | [spark-streaming-kafka-0-10](https://spark.apache.org/docs/latest/streaming-kafka-0-10-integration.html) |
| :-------------------------------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| Kafka版本 | 0.8.2.1 or higher | 0.10.0 or higher |
| AP状态 | Deprecated<br/>从Spark 2.3.0版本开始Kafka 0.8支持已被弃用 | Stable(稳定版) |
| Kafka 版本 | 0.8.2.1 or higher | 0.10.0 or higher |
| AP 状态 | Deprecated<br/> Spark 2.3.0 版本开始Kafka 0.8 支持已被弃用 | Stable(稳定版) |
| 语言支持 | Scala, Java, Python | Scala, Java |
| Receiver DStream | Yes | No |
| Direct DStream | Yes | Yes |
@ -28,11 +28,11 @@ Spark针对Kafka的不同版本提供了两套整合方案`spark-streaming
| Offset Commit API(偏移量提交) | No | Yes |
| Dynamic Topic Subscription<br/>(动态主题订阅) | No | Yes |
本文使用的Kafka版本为`kafka_2.12-2.2.0`,故采用第二种方式进行整合。
本文使用的 Kafka 版本为 `kafka_2.12-2.2.0`,故采用第二种方式进行整合。
## 二、项目依赖
项目采用Maven进行构建主要依赖如下
项目采用 Maven 进行构建,主要依赖如下:
```xml
<properties>
@ -46,7 +46,7 @@ Spark针对Kafka的不同版本提供了两套整合方案`spark-streaming
<artifactId>spark-streaming_${scala.version}</artifactId>
<version>${spark.version}</version>
</dependency>
<!-- Spark Streaming整合Kafka依赖-->
<!-- Spark Streaming 整合 Kafka 依赖-->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_${scala.version}</artifactId>
@ -59,7 +59,7 @@ Spark针对Kafka的不同版本提供了两套整合方案`spark-streaming
## 三、整合Kafka
通过调用`KafkaUtils`对象的`createDirectStream`方法来创建输入流,完整代码如下:
通过调用 `KafkaUtils` 对象的 `createDirectStream` 方法来创建输入流,完整代码如下:
```scala
import org.apache.kafka.common.serialization.StringDeserializer
@ -81,15 +81,15 @@ object KafkaDirectStream {
val kafkaParams = Map[String, Object](
/*
* 指定broker的地址清单清单里不需要包含所有的broker地址生产者会从给定的broker里查找其他broker的信息。
* 不过建议至少提供两个broker的信息作为容错。
* 指定 broker 的地址清单,清单里不需要包含所有的 broker 地址,生产者会从给定的 broker 里查找其他 broker 的信息。
* 不过建议至少提供两个 broker 的信息作为容错。
*/
"bootstrap.servers" -> "hadoop001:9092",
/*键的序列化器*/
"key.deserializer" -> classOf[StringDeserializer],
/*值的序列化器*/
"value.deserializer" -> classOf[StringDeserializer],
/*消费者所在分组的ID*/
/*消费者所在分组的 ID*/
"group.id" -> "spark-streaming-group",
/*
* 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
@ -122,7 +122,7 @@ object KafkaDirectStream {
### 3.1 ConsumerRecord
这里获得的输入流中每一个Record实际上是`ConsumerRecord<K, V> `的实例其包含了Record的所有可用信息源码如下
这里获得的输入流中每一个 Record 实际上是 `ConsumerRecord<K, V> ` 的实例,其包含了 Record 的所有可用信息,源码如下:
```scala
public class ConsumerRecord<K, V> {
@ -157,15 +157,15 @@ public class ConsumerRecord<K, V> {
### 3.2 生产者属性
在示例代码中`kafkaParams`封装了Kafka消费者的属性这些属性和Spark Streaming无关是Kafka原生API中就有定义的。其中服务器地址、键序列化器和值序列化器是必选的其他配置是可选的。其余可选的配置项如下
在示例代码中 `kafkaParams` 封装了 Kafka 消费者的属性,这些属性和 Spark Streaming 无关,是 Kafka 原生 API 中就有定义的。其中服务器地址、键序列化器和值序列化器是必选的,其他配置是可选的。其余可选的配置项如下:
#### 1. fetch.min.byte
消费者从服务器获取记录的最小字节数。如果可用的数据量小于设置值broker会等待有足够的可用数据时才会把它返回给消费者。
消费者从服务器获取记录的最小字节数。如果可用的数据量小于设置值broker 会等待有足够的可用数据时才会把它返回给消费者。
#### 2. fetch.max.wait.ms
broker返回给消费者数据的等待时间。
broker 返回给消费者数据的等待时间。
#### 3. max.partition.fetch.bytes
@ -184,29 +184,29 @@ broker返回给消费者数据的等待时间。
#### 6. enable.auto.commit
是否自动提交偏移量默认值是true,为了避免出现重复数据和数据丢失可以把它设置为false。
是否自动提交偏移量,默认值是 true,为了避免出现重复数据和数据丢失,可以把它设置为 false。
#### 7. client.id
客户端id服务器用来识别消息的来源。
客户端 id服务器用来识别消息的来源。
#### 8. max.poll.records
单次调用`poll()`方法能够返回的记录数量。
单次调用 `poll()` 方法能够返回的记录数量。
#### 9. receive.buffer.bytes 和 send.buffer.byte
这两个参数分别指定TCP socket 接收和发送数据包缓冲区的大小,-1代表使用操作系统的默认值。
这两个参数分别指定 TCP socket 接收和发送数据包缓冲区的大小,-1 代表使用操作系统的默认值。
### 3.3 位置策略
Spark Streaming中提供了如下三种位置策略用于指定Kafka主题分区与Spark执行程序Executors之间的分配关系
Spark Streaming 中提供了如下三种位置策略,用于指定 Kafka 主题分区与 Spark 执行程序 Executors 之间的分配关系:
+ **PreferConsistent** : 它将在所有的Executors上均匀分配分区
+ **PreferConsistent** : 它将在所有的 Executors 上均匀分配分区;
+ **PreferBrokers** : 当SparkExecutorKafka Broker在同一机器上时可以选择该选项它优先将该Broker上的首领分区分配给该机器上的Executor
+ **PreferBrokers** : 当 SparkExecutorKafka Broker 在同一机器上时可以选择该选项,它优先将该 Broker 上的首领分区分配给该机器上的 Executor
+ **PreferFixed** : 可以指定主题分区与特定主机的映射关系,显示地将分区分配到特定的主机,其构造器如下:
```scala
@ -223,13 +223,13 @@ def PreferFixed(hostMap: ju.Map[TopicPartition, String]): LocationStrategy =
### 3.4 订阅方式
Spark Streaming提供了两种主题订阅方式分别为`Subscribe``SubscribePattern`。后者可以使用正则匹配订阅主题的名称。其构造器分别如下:
Spark Streaming 提供了两种主题订阅方式,分别为 `Subscribe``SubscribePattern`。后者可以使用正则匹配订阅主题的名称。其构造器分别如下:
```scala
/**
* @param 需要订阅的主题的集合
* @param Kafka消费者参数
* @param offsets(可选): 在初始启动时开始的偏移量。如果没有则将使用保存的偏移量或auto.offset.reset属性的值
* @param Kafka 消费者参数
* @param offsets(可选): 在初始启动时开始的偏移量。如果没有,则将使用保存的偏移量或 auto.offset.reset 属性的值
*/
def Subscribe[K, V](
topics: ju.Collection[jl.String],
@ -238,8 +238,8 @@ def Subscribe[K, V](
/**
* @param 需要订阅的正则
* @param Kafka消费者参数
* @param offsets(可选): 在初始启动时开始的偏移量。如果没有则将使用保存的偏移量或auto.offset.reset属性的值
* @param Kafka 消费者参数
* @param offsets(可选): 在初始启动时开始的偏移量。如果没有,则将使用保存的偏移量或 auto.offset.reset 属性的值
*/
def SubscribePattern[K, V](
pattern: ju.regex.Pattern,
@ -247,16 +247,16 @@ def SubscribePattern[K, V](
offsets: collection.Map[TopicPartition, Long]): ConsumerStrategy[K, V] = { ... }
```
在示例代码中,我们实际上并没有指定第三个参数`offsets`,所以程序默认采用的是配置的`auto.offset.reset`属性的值latest即在偏移量无效的情况下消费者将从其启动之后生成的最新的记录开始读取数据。
在示例代码中,我们实际上并没有指定第三个参数 `offsets`,所以程序默认采用的是配置的 `auto.offset.reset` 属性的值 latest即在偏移量无效的情况下消费者将从其启动之后生成的最新的记录开始读取数据。
### 3.5 提交偏移量
在示例代码中,我们将`enable.auto.commit`设置为true代表自动提交。在某些情况下你可能需要更高的可靠性如在业务完全处理完成后再提交偏移量这时候可以使用手动提交。想要进行手动提交需要调用Kafka原生的API :
在示例代码中,我们将 `enable.auto.commit` 设置为 true代表自动提交。在某些情况下你可能需要更高的可靠性如在业务完全处理完成后再提交偏移量这时候可以使用手动提交。想要进行手动提交需要调用 Kafka 原生的 API :
+ `commitSync`: 用于异步提交;
+ `commitAsync`:用于同步提交。
具体提交方式可以参见:[Kafka消费者详解](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Kafka消费者详解.md)
具体提交方式可以参见:[Kafka 消费者详解](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Kafka 消费者详解.md)
@ -266,7 +266,7 @@ def SubscribePattern[K, V](
#### 1. 启动Kakfa
Kafka的运行依赖于zookeeper需要预先启动可以启动Kafka内置的zookeeper也可以启动自己安装的
Kafka 的运行依赖于 zookeeper需要预先启动可以启动 Kafka 内置的 zookeeper也可以启动自己安装的
```shell
# zookeeper启动命令
@ -276,7 +276,7 @@ bin/zkServer.sh start
bin/zookeeper-server-start.sh config/zookeeper.properties
```
启动单节点kafka用于测试
启动单节点 kafka 用于测试:
```shell
# bin/kafka-server-start.sh config/server.properties
@ -298,7 +298,7 @@ bin/kafka-topics.sh --create \
#### 3. 创建生产者
这里创建一个Kafka生产者用于发送测试数据
这里创建一个 Kafka 生产者,用于发送测试数据:
```shell
bin/kafka-console-producer.sh --broker-list hadoop001:9092 --topic spark-streaming-topic
@ -306,9 +306,9 @@ bin/kafka-console-producer.sh --broker-list hadoop001:9092 --topic spark-streami
### 4.2 本地模式测试
这里我直接使用本地模式启动Spark Streaming程序。启动后使用生产者发送数据从控制台查看结果。
这里我直接使用本地模式启动 Spark Streaming 程序。启动后使用生产者发送数据,从控制台查看结果。
从控制台输出中可以看到数据流已经被成功接收,由于采用`kafka-console-producer.sh`发送的数据默认是没有key的所以key值为null。同时从输出中也可以看到在程序中指定的`groupId`和程序自动分配的`clientId`
从控制台输出中可以看到数据流已经被成功接收,由于采用 `kafka-console-producer.sh` 发送的数据默认是没有 key 的,所以 key 值为 null。同时从输出中也可以看到在程序中指定的 `groupId` 和程序自动分配的 `clientId`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-straming-kafka-console.png"/> </div>

View File

@ -12,18 +12,18 @@
### 1.1 创建DataFrame
Spark中所有功能的入口点是`SparkSession`,可以使用`SparkSession.builder()`创建。创建后应用程序就可以从现有RDDHive表或Spark数据源创建DataFrame。示例如下
Spark 中所有功能的入口点是 `SparkSession`,可以使用 `SparkSession.builder()` 创建。创建后应用程序就可以从现有 RDDHive 表或 Spark 数据源创建 DataFrame。示例如下
```scala
val spark = SparkSession.builder().appName("Spark-SQL").master("local[2]").getOrCreate()
val df = spark.read.json("/usr/file/json/emp.json")
df.show()
// 建议在进行spark SQL编程前导入下面的隐式转换因为DataFramesdataSets中很多操作都依赖了隐式转换
// 建议在进行 spark SQL 编程前导入下面的隐式转换,因为 DataFramesdataSets 中很多操作都依赖了隐式转换
import spark.implicits._
```
可以使用`spark-shell`进行测试,需要注意的是`spark-shell`启动后会自动创建一个名为`spark``SparkSession`,在命令行中可以直接引用即可:
可以使用 `spark-shell` 进行测试,需要注意的是 `spark-shell` 启动后会自动创建一个名为 `spark``SparkSession`,在命令行中可以直接引用即可:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-sql-shell.png"/> </div>
@ -31,7 +31,7 @@ import spark.implicits._
### 1.2 创建Dataset
Spark支持由内部数据集和外部数据集来创建DataSet其创建方式分别如下
Spark 支持由内部数据集和外部数据集来创建 DataSet其创建方式分别如下
#### 1. 由外部数据集创建
@ -39,11 +39,11 @@ Spark支持由内部数据集和外部数据集来创建DataSet其创建方
// 1.需要导入隐式转换
import spark.implicits._
// 2.创建case class,等价于Java Bean
// 2.创建 case class,等价于 Java Bean
case class Emp(ename: String, comm: Double, deptno: Long, empno: Long,
hiredate: String, job: String, mgr: Long, sal: Double)
// 3.由外部数据集创建Datasets
// 3.由外部数据集创建 Datasets
val ds = spark.read.json("/usr/file/emp.json").as[Emp]
ds.show()
```
@ -54,11 +54,11 @@ ds.show()
// 1.需要导入隐式转换
import spark.implicits._
// 2.创建case class,等价于Java Bean
// 2.创建 case class,等价于 Java Bean
case class Emp(ename: String, comm: Double, deptno: Long, empno: Long,
hiredate: String, job: String, mgr: Long, sal: Double)
// 3.由内部数据集创建Datasets
// 3.由内部数据集创建 Datasets
val caseClassDS = Seq(Emp("ALLEN", 300.0, 30, 7499, "1981-02-20 00:00:00", "SALESMAN", 7698, 1600.0),
Emp("JONES", 300.0, 30, 7499, "1981-02-20 00:00:00", "SALESMAN", 7698, 1600.0))
.toDS()
@ -69,7 +69,7 @@ caseClassDS.show()
### 1.3 由RDD创建DataFrame
Spark支持两种方式把RDD转换为DataFrame分别是使用反射推断和指定Schema转换
Spark 支持两种方式把 RDD 转换为 DataFrame分别是使用反射推断和指定 Schema 转换:
#### 1. 使用反射推断
@ -80,12 +80,12 @@ import spark.implicits._
// 2.创建部门类
case class Dept(deptno: Long, dname: String, loc: String)
// 3.创建RDD并转换为dataSet
// 3.创建 RDD 并转换为 dataSet
val rddToDS = spark.sparkContext
.textFile("/usr/file/dept.txt")
.map(_.split("\t"))
.map(line => Dept(line(0).trim.toLong, line(1), line(2)))
.toDS() // 如果调用toDF()则转换为dataFrame
.toDS() // 如果调用 toDF() 则转换为 dataFrame
```
#### 2. 以编程方式指定Schema
@ -100,15 +100,15 @@ val fields = Array(StructField("deptno", LongType, nullable = true),
StructField("dname", StringType, nullable = true),
StructField("loc", StringType, nullable = true))
// 2.创建schema
// 2.创建 schema
val schema = StructType(fields)
// 3.创建RDD
// 3.创建 RDD
val deptRDD = spark.sparkContext.textFile("/usr/file/dept.txt")
val rowRDD = deptRDD.map(_.split("\t")).map(line => Row(line(0).toLong, line(1), line(2)))
// 4.将RDD转换为dataFrame
// 4.将 RDD 转换为 dataFrame
val deptDF = spark.createDataFrame(rowRDD, schema)
deptDF.show()
```
@ -117,7 +117,7 @@ deptDF.show()
### 1.4 DataFrames与Datasets互相转换
Spark提供了非常简单的转换方法用于DataFrameDataset间的互相转换示例如下
Spark 提供了非常简单的转换方法用于 DataFrameDataset 间的互相转换,示例如下:
```shell
# DataFrames转Datasets
@ -135,13 +135,13 @@ res2: org.apache.spark.sql.DataFrame = [COMM: double, DEPTNO: bigint ... 6 more
### 2.1 引用列
Spark支持多种方法来构造和引用列最简单的是使用 `col() ``column() `函数。
Spark 支持多种方法来构造和引用列,最简单的是使用 `col() ` `column() ` 函数。
```scala
col("colName")
column("colName")
// 对于Scala语言而言还可以使用$"myColumn"和'myColumn这两种语法糖进行引用。
// 对于 Scala 语言而言,还可以使用$"myColumn"和'myColumn 这两种语法糖进行引用。
df.select($"ename", $"job").show()
df.select('ename, 'job).show()
```
@ -168,7 +168,7 @@ df.drop("comm","job").show()
df.withColumnRenamed("comm", "common").show()
```
需要说明的是新增删除重命名列都会产生新的DataFrame原来的DataFrame不会被改变。
需要说明的是新增,删除,重命名列都会产生新的 DataFrame原来的 DataFrame 不会被改变。
<br/>
@ -178,13 +178,13 @@ df.withColumnRenamed("comm", "common").show()
// 1.查询员工姓名及工作
df.select($"ename", $"job").show()
// 2.filter 查询工资大于2000的员工信息
// 2.filter 查询工资大于 2000 的员工信息
df.filter($"sal" > 2000).show()
// 3.orderBy 按照部门编号降序,工资升序进行查询
df.orderBy(desc("deptno"), asc("sal")).show()
// 4.limit 查询工资最高的3名员工的信息
// 4.limit 查询工资最高的 3 名员工的信息
df.orderBy(desc("sal")).limit(3).show()
// 5.distinct 查询所有部门编号
@ -201,19 +201,19 @@ df.groupBy("deptno").count().show()
### 4.1 Spark SQL基本使用
```scala
// 1.首先需要将DataFrame注册为临时视图
// 1.首先需要将 DataFrame 注册为临时视图
df.createOrReplaceTempView("emp")
// 2.查询员工姓名及工作
spark.sql("SELECT ename,job FROM emp").show()
// 3.查询工资大于2000的员工信息
// 3.查询工资大于 2000 的员工信息
spark.sql("SELECT * FROM emp where sal > 2000").show()
// 4.orderBy 按照部门编号降序,工资升序进行查询
spark.sql("SELECT * FROM emp ORDER BY deptno DESC,sal ASC").show()
// 5.limit 查询工资最高的3名员工的信息
// 5.limit 查询工资最高的 3 名员工的信息
spark.sql("SELECT * FROM emp ORDER BY sal DESC LIMIT 3").show()
// 6.distinct 查询所有部门编号
@ -225,9 +225,9 @@ spark.sql("SELECT deptno,count(ename) FROM emp group by deptno").show()
### 4.2 全局临时视图
上面使用`createOrReplaceTempView`创建的是会话临时视图,它的生命周期仅限于会话范围,会随会话的结束而结束。
上面使用 `createOrReplaceTempView` 创建的是会话临时视图,它的生命周期仅限于会话范围,会随会话的结束而结束。
你也可以使用`createGlobalTempView`创建全局临时视图全局临时视图可以在所有会话之间共享并直到整个Spark应用程序终止后才会消失。全局临时视图被定义在内置的`global_temp`数据库下,需要使用限定名称进行引用,如`SELECT * FROM global_temp.view1`
你也可以使用 `createGlobalTempView` 创建全局临时视图,全局临时视图可以在所有会话之间共享,并直到整个 Spark 应用程序终止后才会消失。全局临时视图被定义在内置的 `global_temp` 数据库下,需要使用限定名称进行引用,如 `SELECT * FROM global_temp.view1`
```scala
// 注册为全局临时视图

View File

@ -27,28 +27,28 @@
## 一、Transformation
spark常用的Transformation算子如下表
spark 常用的 Transformation 算子如下表:
| Transformation算子 | Meaning含义 |
| Transformation 算子 | Meaning含义 |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| **map**(*func*) | 对原RDD中每个元素运用 *func* 函数并生成新的RDD |
| **filter**(*func*) | 对原RDD中每个元素使用*func* 函数进行过滤并生成新的RDD |
| **map**(*func*) | 对原 RDD 中每个元素运用 *func* 函数,并生成新的 RDD |
| **filter**(*func*) | 对原 RDD 中每个元素使用*func* 函数进行过滤,并生成新的 RDD |
| **flatMap**(*func*) | 与 map 类似,但是每一个输入的 item 被映射成 0 个或多个输出的 items *func* 返回类型需要为 Seq )。 |
| **mapPartitions**(*func*) | 与 map 类似但函数单独在RDD的每个分区上运行 *func*函数的类型为 Iterator\<T> => Iterator\<U> ,其中T是RDD的类型即RDD[T] |
| **mapPartitions**(*func*) | 与 map 类似,但函数单独在 RDD 的每个分区上运行, *func*函数的类型为 Iterator\<T> => Iterator\<U> ,其中 T 是 RDD 的类型,即 RDD[T] |
| **mapPartitionsWithIndex**(*func*) | 与 mapPartitions 类似,但 *func* 类型为 (Int, Iterator\<T>) => Iterator\<U> ,其中第一个参数为分区索引 |
| **sample**(*withReplacement*, *fraction*, *seed*) | 数据采样有三个可选参数设置是否放回withReplacement、采样的百分比*fraction*、随机数生成器的种子seed |
| **union**(*otherDataset*) | 合并两个RDD |
| **intersection**(*otherDataset*) | 求两个RDD的交集 |
| **union**(*otherDataset*) | 合并两个 RDD |
| **intersection**(*otherDataset*) | 求两个 RDD 的交集 |
| **distinct**([*numTasks*])) | 去重 |
| **groupByKey**([*numTasks*]) | 按照key值进行分区即在一个 (K, V) 对的 dataset 上调用时,返回一个 (K, Iterable\<V>) <br/>**Note:** 如果分组是为了在每一个 key 上执行聚合操作例如sum 或 average),此时使用 `reduceByKey``aggregateByKey` 性能会更好<br>**Note:** 默认情况下,并行度取决于父 RDD 的分区数。可以传入 `numTasks` 参数进行修改。 |
| **reduceByKey**(*func*, [*numTasks*]) | 按照key值进行分组并对分组后的数据执行归约操作。 |
| **aggregateByKey**(*zeroValue*,*numPartitions*)(*seqOp*, *combOp*, [*numTasks*]) | 当调用KV对的数据集时返回KU对的数据集其中使用给定的组合函数和zeroValue聚合每个键的值。与groupByKey类似reduce任务的数量可通过第二个参数进行配置。 |
| **sortByKey**([*ascending*], [*numTasks*]) | 按照key进行排序其中的key需要实现Ordered特质即可比较 |
| **join**(*otherDataset*, [*numTasks*]) | 在一个 (K, V) 和 (K, W) 类型的 dataset 上调用时,返回一个 (K, (V, W)) pairs 的 dataset等价于内连接操作。如果想要执行外连接可以使用`leftOuterJoin`, `rightOuterJoin``fullOuterJoin` 等算子。 |
| **groupByKey**([*numTasks*]) | 按照 key 值进行分区,即在一个 (K, V) 对的 dataset 上调用时,返回一个 (K, Iterable\<V>) <br/>**Note:** 如果分组是为了在每一个 key 上执行聚合操作例如sum 或 average),此时使用 `reduceByKey``aggregateByKey` 性能会更好<br>**Note:** 默认情况下,并行度取决于父 RDD 的分区数。可以传入 `numTasks` 参数进行修改。 |
| **reduceByKey**(*func*, [*numTasks*]) | 按照 key 值进行分组,并对分组后的数据执行归约操作。 |
| **aggregateByKey**(*zeroValue*,*numPartitions*)(*seqOp*, *combOp*, [*numTasks*]) | 当调用KV对的数据集时返回KU对的数据集其中使用给定的组合函数和 zeroValue 聚合每个键的值。与 groupByKey 类似reduce 任务的数量可通过第二个参数进行配置。 |
| **sortByKey**([*ascending*], [*numTasks*]) | 按照 key 进行排序,其中的 key 需要实现 Ordered 特质,即可比较 |
| **join**(*otherDataset*, [*numTasks*]) | 在一个 (K, V) 和 (K, W) 类型的 dataset 上调用时,返回一个 (K, (V, W)) pairs 的 dataset等价于内连接操作。如果想要执行外连接可以使用 `leftOuterJoin`, `rightOuterJoin``fullOuterJoin` 等算子。 |
| **cogroup**(*otherDataset*, [*numTasks*]) | 在一个 (K, V) 对的 dataset 上调用时,返回一个 (K, (Iterable\<V>, Iterable\<W>)) tuples 的 dataset。 |
| **cartesian**(*otherDataset*) | 在一个 T 和 U 类型的 dataset 上调用时,返回一个 (T, U) 类型的 dataset即笛卡尔积。 |
| **coalesce**(*numPartitions*) | 将RDD中的分区数减少为numPartitions。 |
| **repartition**(*numPartitions*) | 随机重新调整RDD中的数据以创建更多或更少的分区并在它们之间进行平衡。 |
| **coalesce**(*numPartitions*) | 将 RDD 中的分区数减少为 numPartitions。 |
| **repartition**(*numPartitions*) | 随机重新调整 RDD 中的数据以创建更多或更少的分区,并在它们之间进行平衡。 |
| **repartitionAndSortWithinPartitions**(*partitioner*) | 根据给定的 partitioner分区器对 RDD 进行重新分区,并对分区中的数据按照 key 值进行排序。这比调用 `repartition` 然后再 sorting排序效率更高因为它可以将排序过程推送到 shuffle 操作所在的机器。 |
下面分别给出这些算子的基本使用示例:
@ -73,7 +73,7 @@ sc.parallelize(list).filter(_ >= 10).foreach(println)
### 1.3 flatMap
`flatMap(func)``map`类似但每一个输入的item会被映射成 0 个或多个输出的items *func* 返回类型需要为`Seq`)。
`flatMap(func)``map` 类似,但每一个输入的 item 会被映射成 0 个或多个输出的 items *func* 返回类型需要为 `Seq`)。
```scala
val list = List(List(1, 2), List(3), List(), List(4, 5))
@ -82,7 +82,7 @@ sc.parallelize(list).flatMap(_.toList).map(_ * 10).foreach(println)
// 输出结果 10 20 30 40 50
```
flatMap 这个算子在日志分析中使用概率非常高这里进行一下演示拆分输入的每行数据为单个单词并赋值为1代表出现一次之后按照单词分组并统计其出现总次数代码如下
flatMap 这个算子在日志分析中使用概率非常高,这里进行一下演示:拆分输入的每行数据为单个单词,并赋值为 1代表出现一次之后按照单词分组并统计其出现总次数代码如下
```scala
val lines = List("spark flume spark",
@ -99,7 +99,7 @@ map(word=>(word,1)).reduceByKey(_+_).foreach(println)
### 1.4 mapPartitions
与 map 类似但函数单独在RDD的每个分区上运行 *func*函数的类型为`Iterator<T> => Iterator<U>` (其中T是RDD的类型),即输入和输出都必须是可迭代类型。
与 map 类似,但函数单独在 RDD 的每个分区上运行, *func*函数的类型为 `Iterator<T> => Iterator<U>` (其中 T 是 RDD 的类型),即输入和输出都必须是可迭代类型。
```scala
val list = List(1, 2, 3, 4, 5, 6)
@ -116,7 +116,7 @@ sc.parallelize(list, 3).mapPartitions(iterator => {
### 1.5 mapPartitionsWithIndex
与 mapPartitions 类似,但 *func* 类型为`(Int, Iterator<T>) => Iterator<U>` ,其中第一个参数为分区索引。
与 mapPartitions 类似,但 *func* 类型为 `(Int, Iterator<T>) => Iterator<U>` ,其中第一个参数为分区索引。
```scala
val list = List(1, 2, 3, 4, 5, 6)
@ -128,17 +128,17 @@ sc.parallelize(list, 3).mapPartitionsWithIndex((index, iterator) => {
buffer.toIterator
}).foreach(println)
//输出
0分区:100
0分区:200
1分区:300
1分区:400
2分区:500
2分区:600
0 分区:100
0 分区:200
1 分区:300
1 分区:400
2 分区:500
2 分区:600
```
### 1.6 sample
数据采样。有三个可选参数:设置是否放回(withReplacement)、采样的百分比(fraction)、随机数生成器的种子(seed)
数据采样。有三个可选参数:设置是否放回 (withReplacement)、采样的百分比 (fraction)、随机数生成器的种子 (seed)
```scala
val list = List(1, 2, 3, 4, 5, 6)
@ -147,7 +147,7 @@ sc.parallelize(list).sample(withReplacement = false, fraction = 0.5).foreach(pri
### 1.7 union
合并两个RDD
合并两个 RDD
```scala
val list1 = List(1, 2, 3)
@ -158,7 +158,7 @@ sc.parallelize(list1).union(sc.parallelize(list2)).foreach(println)
### 1.8 intersection
求两个RDD的交集
求两个 RDD 的交集:
```scala
val list1 = List(1, 2, 3, 4, 5)
@ -231,7 +231,7 @@ sc.parallelize(list02).sortBy(x=>x._2,ascending=false).foreach(println)
### 1.13 join
在一个 (K, V) 和 (K, W) 类型的 Dataset 上调用时,返回一个 (K, (V, W)) 的 Dataset等价于内连接操作。如果想要执行外连接可以使用`leftOuterJoin`, `rightOuterJoin``fullOuterJoin` 等算子。
在一个 (K, V) 和 (K, W) 类型的 Dataset 上调用时,返回一个 (K, (V, W)) 的 Dataset等价于内连接操作。如果想要执行外连接可以使用 `leftOuterJoin`, `rightOuterJoin``fullOuterJoin` 等算子。
```scala
val list01 = List((1, "student01"), (2, "student02"), (3, "student03"))
@ -246,7 +246,7 @@ sc.parallelize(list01).join(sc.parallelize(list02)).foreach(println)
### 1.14 cogroup
在一个 (K, V) 对的 Dataset 上调用时,返回多个类型为 (K, (Iterable\<V>, Iterable\<W>)) 的元组所组成的Dataset。
在一个 (K, V) 对的 Dataset 上调用时,返回多个类型为 (K, (Iterable\<V>, Iterable\<W>)) 的元组所组成的 Dataset。
```scala
val list01 = List((1, "a"),(1, "a"), (2, "b"), (3, "e"))
@ -254,7 +254,7 @@ val list02 = List((1, "A"), (2, "B"), (3, "E"))
val list03 = List((1, "[ab]"), (2, "[bB]"), (3, "eE"),(3, "eE"))
sc.parallelize(list01).cogroup(sc.parallelize(list02),sc.parallelize(list03)).foreach(println)
// 输出: 同一个RDD中的元素先按照key进行分组然后再对不同RDD中的元素按照key进行分组
// 输出: 同一个 RDD 中的元素先按照 key 进行分组,然后再对不同 RDD 中的元素按照 key 进行分组
(1,(CompactBuffer(a, a),CompactBuffer(A),CompactBuffer([ab])))
(3,(CompactBuffer(e),CompactBuffer(E),CompactBuffer(eE, eE)))
(2,(CompactBuffer(b),CompactBuffer(B),CompactBuffer([bB])))
@ -284,7 +284,7 @@ sc.parallelize(list1).cartesian(sc.parallelize(list2)).foreach(println)
### 1.16 aggregateByKey
当调用KV对的数据集时返回KU对的数据集其中使用给定的组合函数和zeroValue聚合每个键的值。与`groupByKey`类似reduce任务的数量可通过第二个参数`numPartitions`进行配置。示例如下:
当调用KV对的数据集时返回KU对的数据集其中使用给定的组合函数和 zeroValue 聚合每个键的值。与 `groupByKey` 类似reduce 任务的数量可通过第二个参数 `numPartitions` 进行配置。示例如下:
```scala
// 为了清晰,以下所有参数均使用具名传参
@ -299,11 +299,11 @@ sc.parallelize(list,numSlices = 2).aggregateByKey(zeroValue = 0,numPartitions =
(spark,7)
```
这里使用了`numSlices = 2`指定aggregateByKey父操作parallelize的分区数量为2其执行流程如下
这里使用了 `numSlices = 2` 指定 aggregateByKey 父操作 parallelize 的分区数量为 2其执行流程如下
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-aggregateByKey.png"/> </div>
基于同样的执行流程,如果`numSlices = 1`则意味着只有输入一个分区则其最后一步combOp相当于是无效的执行结果为
基于同样的执行流程,如果 `numSlices = 1`,则意味着只有输入一个分区,则其最后一步 combOp 相当于是无效的,执行结果为:
```properties
(hadoop,3)
@ -311,7 +311,7 @@ sc.parallelize(list,numSlices = 2).aggregateByKey(zeroValue = 0,numPartitions =
(spark,4)
```
同样的,如果每个单词对一个分区,即`numSlices = 6`,此时相当于求和操作,执行结果为:
同样的,如果每个单词对一个分区,即 `numSlices = 6`,此时相当于求和操作,执行结果为:
```properties
(hadoop,5)
@ -319,7 +319,7 @@ sc.parallelize(list,numSlices = 2).aggregateByKey(zeroValue = 0,numPartitions =
(spark,7)
```
`aggregateByKey(zeroValue = 0,numPartitions = 3)`的第二个参数`numPartitions`决定的是输出RDD的分区数量想要验证这个问题可以对上面代码进行改写使用`getNumPartitions`方法获取分区数量:
`aggregateByKey(zeroValue = 0,numPartitions = 3)` 的第二个参数 `numPartitions` 决定的是输出 RDD 的分区数量,想要验证这个问题,可以对上面代码进行改写,使用 `getNumPartitions` 方法获取分区数量:
```scala
sc.parallelize(list,numSlices = 6).aggregateByKey(zeroValue = 0,numPartitions = 3)(
@ -332,7 +332,7 @@ sc.parallelize(list,numSlices = 6).aggregateByKey(zeroValue = 0,numPartitions =
## 二、Action
Spark常用的Action算子如下
Spark 常用的 Action 算子如下:
| Action动作 | Meaning含义 |
| -------------------------------------------------- | ------------------------------------------------------------ |
@ -344,10 +344,10 @@ Spark常用的Action算子如下
| **takeSample**(*withReplacement*, *num*, [*seed*]) | 对一个 dataset 进行随机抽样 |
| **takeOrdered**(*n*, *[ordering]*) | 按自然顺序natural order或自定义比较器custom comparator排序后返回前 *n* 个元素。只适用于小结果集,因为所有数据都会被加载到驱动程序的内存中进行排序。 |
| **saveAsTextFile**(*path*) | 将 dataset 中的元素以文本文件的形式写入本地文件系统、HDFS 或其它 Hadoop 支持的文件系统中。Spark 将对每个元素调用 toString 方法,将元素转换为文本文件中的一行记录。 |
| **saveAsSequenceFile**(*path*) | 将 dataset 中的元素以Hadoop SequenceFile 的形式写入到本地文件系统、HDFS 或其它 Hadoop 支持的文件系统中。该操作要求RDD中的元素需要实现 Hadoop 的 Writable 接口。对于Scala语言而言它可以将Spark中的基本数据类型自动隐式转换为对应Writable类型。(目前仅支持Java and Scala) |
| **saveAsObjectFile**(*path*) | 使用 Java 序列化后存储,可以使用 `SparkContext.objectFile()` 进行加载。(目前仅支持Java and Scala) |
| **saveAsSequenceFile**(*path*) | 将 dataset 中的元素以 Hadoop SequenceFile 的形式写入到本地文件系统、HDFS 或其它 Hadoop 支持的文件系统中。该操作要求 RDD 中的元素需要实现 Hadoop 的 Writable 接口。对于 Scala 语言而言,它可以将 Spark 中的基本数据类型自动隐式转换为对应 Writable 类型。(目前仅支持 Java and Scala) |
| **saveAsObjectFile**(*path*) | 使用 Java 序列化后存储,可以使用 `SparkContext.objectFile()` 进行加载。(目前仅支持 Java and Scala) |
| **countByKey**() | 计算每个键出现的次数。 |
| **foreach**(*func*) | 遍历RDD中每个元素并对其执行*fun*函数 |
| **foreach**(*func*) | 遍历 RDD 中每个元素,并对其执行*fun*函数 |
### 2.1 reduce
@ -363,7 +363,7 @@ sc.parallelize(list).reduce(_ + _)
### 2.2 takeOrdered
按自然顺序natural order或自定义比较器custom comparator排序后返回前 *n* 个元素。需要注意的是`takeOrdered`使用隐式参数进行隐式转换,以下为其源码。所以在使用自定义排序时,需要继承`Ordering[T]`实现自定义比较器,然后将其作为隐式参数引入。
按自然顺序natural order或自定义比较器custom comparator排序后返回前 *n* 个元素。需要注意的是 `takeOrdered` 使用隐式参数进行隐式转换,以下为其源码。所以在使用自定义排序时,需要继承 `Ordering[T]` 实现自定义比较器,然后将其作为隐式参数引入。
```scala
def takeOrdered(num: Int)(implicit ord: Ordering[T]): Array[T] = withScope {
@ -374,7 +374,7 @@ def takeOrdered(num: Int)(implicit ord: Ordering[T]): Array[T] = withScope {
自定义规则排序:
```scala
// 继承Ordering[T],实现自定义比较器按照value值的长度进行排序
// 继承 Ordering[T],实现自定义比较器,按照 value 值的长度进行排序
class CustomOrdering extends Ordering[(Int, String)] {
override def compare(x: (Int, String), y: (Int, String)): Int
= if (x._2.length > y._2.length) 1 else -1

View File

@ -1,94 +1,94 @@
# Spark简介
<nav>
<a href="#一简介">一、简介</a><br/>
<a href="#二特点">二、特点</a><br/>
<a href="#三集群架构">三、集群架构</a><br/>
<a href="#四核心组件">四、核心组件</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-Spark--SQL">3.1 Spark SQL</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-Spark-Streaming">3.2 Spark Streaming</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#33-MLlib">3.3 MLlib</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#34-Graphx">3.4 Graphx</a><br/>
<a href="#"> </a><br/>
</nav>
## 一、简介
Spark2009年诞生于加州大学伯克利分校AMPLab2013年被捐赠给Apache软件基金会2014年2月成为Apache的顶级项目。相对于MapReduce的批处理计算Spark可以带来上百倍的性能提升因此它成为继MapReduce之后最为广泛使用的分布式计算框架。
## 二、特点
Apache Spark具有以下特点
+ 使用先进的DAG调度程序查询优化器和物理执行引擎以实现性能上的保证
+ 多语言支持目前支持的有JavaScalaPythonR
+ 提供了80多个高级API可以轻松地构建应用程序
+ 支持批处理,流处理和复杂的业务分析;
+ 丰富的类库支持包括SQLMLlibGraphXSpark Streaming等库并且可以将它们无缝地进行组合
+ 丰富的部署模式支持本地模式和自带的集群模式也支持在HadoopMesosKubernetes上运行
+ 多数据源支持支持访问HDFSAlluxioCassandraHBaseHive以及数百个其他数据源中的数据。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/future-of-spark.png"/> </div>
## 三、集群架构
| Term术语 | Meaning含义 |
| --------------- | ------------------------------------------------------------ |
| Application | Spark应用程序由集群上的一个Driver节点和多个Executor节点组成。 |
| Driver program | 主运用程序,该进程运行应用的 main() 方法并且创建 SparkContext |
| Cluster manager | 集群资源管理器例如Standlone ManagerMesosYARN |
| Worker node | 执行计算任务的工作节点 |
| Executor | 位于工作节点上的应用进程,负责执行计算任务并且将输出数据保存到内存或者磁盘中 |
| Task | 被发送到Executor中的工作单元 |
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-集群模式.png"/> </div>
**执行过程**
1. 用户程序创建SparkContext后它会连接到集群资源管理器集群资源管理器会为用户程序分配计算资源并启动Executor
2. Dirver将计算程序划分为不同的执行阶段和多个Task之后将Task发送给Executor
3. Executor负责执行Task并将执行状态汇报给Driver同时也会将当前节点资源的使用情况汇报给集群资源管理器。
## 四、核心组件
Spark基于Spark Core扩展了四个核心组件分别用于满足不同领域的计算需求。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-stack.png"/> </div>
### 3.1 Spark SQL
Spark SQL主要用于结构化数据的处理。其具有以下特点
- 能够将SQL查询与Spark程序无缝混合允许您使用SQLDataFrame API对结构化数据进行查询
- 支持多种数据源包括HiveAvroParquetORCJSONJDBC
- 支持HiveQL语法以及用户自定义函数(UDF)允许你访问现有的Hive仓库
- 支持标准的JDBCODBC连接
- 支持优化器,列式存储和代码生成等特性,以提高查询效率。
### 3.2 Spark Streaming
Spark Streaming主要用于快速构建可扩展高吞吐量高容错的流处理程序。支持从HDFSFlumeKafkaTwitterZeroMQ读取数据并进行处理。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-streaming-arch.png"/> </div>
Spark Streaming的本质是微批处理它将数据流进行极小粒度的拆分拆分为多个批处理从而达到接近于流处理的效果。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-streaming-flow.png"/> </div>
### 3.3 MLlib
MLlibSpark的机器学习库。其设计目标是使得机器学习变得简单且可扩展。它提供了以下工具
+ **常见的机器学习算法**:如分类,回归,聚类和协同过滤;
+ **特征化**:特征提取,转换,降维和选择;
+ **管道**用于构建评估和调整ML管道的工具
+ **持久性**:保存和加载算法,模型,管道数据;
+ **实用工具**:线性代数,统计,数据处理等。
### 3.4 Graphx
GraphXSpark中用于图形计算和图形并行计算的新组件。在高层次上GraphX通过引入一个新的图形抽象来扩展 RDD(一种具有附加到每个顶点和边缘的属性的定向多重图形)。为了支持图计算GraphX提供了一组基本运算符 subgraphjoinVertices 和 aggregateMessages以及优化后的Pregel API。此外GraphX还包括越来越多的图形算法和构建器以简化图形分析任务。
##
# Spark简介
<nav>
<a href="#一简介">一、简介</a><br/>
<a href="#二特点">二、特点</a><br/>
<a href="#三集群架构">三、集群架构</a><br/>
<a href="#四核心组件">四、核心组件</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-Spark--SQL">3.1 Spark SQL</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-Spark-Streaming">3.2 Spark Streaming</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#33-MLlib">3.3 MLlib</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#34-Graphx">3.4 Graphx</a><br/>
<a href="#"> </a><br/>
</nav>
## 一、简介
Spark2009 年诞生于加州大学伯克利分校 AMPLab2013 年被捐赠给 Apache 软件基金会2014 年 2 月成为 Apache 的顶级项目。相对于 MapReduce 的批处理计算Spark 可以带来上百倍的性能提升,因此它成为继 MapReduce 之后,最为广泛使用的分布式计算框架。
## 二、特点
Apache Spark 具有以下特点:
+ 使用先进的 DAG 调度程序,查询优化器和物理执行引擎,以实现性能上的保证;
+ 多语言支持,目前支持的有 JavaScalaPythonR
+ 提供了 80 多个高级 API可以轻松地构建应用程序
+ 支持批处理,流处理和复杂的业务分析;
+ 丰富的类库支持:包括 SQLMLlibGraphXSpark Streaming 等库,并且可以将它们无缝地进行组合;
+ 丰富的部署模式:支持本地模式和自带的集群模式,也支持在 HadoopMesosKubernetes 上运行;
+ 多数据源支持:支持访问 HDFSAlluxioCassandraHBaseHive 以及数百个其他数据源中的数据。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/future-of-spark.png"/> </div>
## 三、集群架构
| Term术语 | Meaning含义 |
| --------------- | ------------------------------------------------------------ |
| Application | Spark 应用程序,由集群上的一个 Driver 节点和多个 Executor 节点组成。 |
| Driver program | 主运用程序,该进程运行应用的 main() 方法并且创建 SparkContext |
| Cluster manager | 集群资源管理器例如Standlone ManagerMesosYARN |
| Worker node | 执行计算任务的工作节点 |
| Executor | 位于工作节点上的应用进程,负责执行计算任务并且将输出数据保存到内存或者磁盘中 |
| Task | 被发送到 Executor 中的工作单元 |
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-集群模式.png"/> </div>
**执行过程**
1. 用户程序创建 SparkContext 后,它会连接到集群资源管理器,集群资源管理器会为用户程序分配计算资源,并启动 Executor
2. Dirver 将计算程序划分为不同的执行阶段和多个 Task之后将 Task 发送给 Executor
3. Executor 负责执行 Task并将执行状态汇报给 Driver同时也会将当前节点资源的使用情况汇报给集群资源管理器。
## 四、核心组件
Spark 基于 Spark Core 扩展了四个核心组件,分别用于满足不同领域的计算需求。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-stack.png"/> </div>
### 3.1 Spark SQL
Spark SQL 主要用于结构化数据的处理。其具有以下特点:
- 能够将 SQL 查询与 Spark 程序无缝混合,允许您使用 SQLDataFrame API 对结构化数据进行查询;
- 支持多种数据源,包括 HiveAvroParquetORCJSONJDBC
- 支持 HiveQL 语法以及用户自定义函数 (UDF),允许你访问现有的 Hive 仓库;
- 支持标准的 JDBCODBC 连接;
- 支持优化器,列式存储和代码生成等特性,以提高查询效率。
### 3.2 Spark Streaming
Spark Streaming 主要用于快速构建可扩展,高吞吐量,高容错的流处理程序。支持从 HDFSFlumeKafkaTwitterZeroMQ 读取数据,并进行处理。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-streaming-arch.png"/> </div>
Spark Streaming 的本质是微批处理,它将数据流进行极小粒度的拆分,拆分为多个批处理,从而达到接近于流处理的效果。
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-streaming-flow.png"/> </div>
### 3.3 MLlib
MLlibSpark 的机器学习库。其设计目标是使得机器学习变得简单且可扩展。它提供了以下工具:
+ **常见的机器学习算法**:如分类,回归,聚类和协同过滤;
+ **特征化**:特征提取,转换,降维和选择;
+ **管道**:用于构建,评估和调整 ML 管道的工具;
+ **持久性**:保存和加载算法,模型,管道数据;
+ **实用工具**:线性代数,统计,数据处理等。
### 3.4 Graphx
GraphXSpark 中用于图形计算和图形并行计算的新组件。在高层次上GraphX 通过引入一个新的图形抽象来扩展 RDD(一种具有附加到每个顶点和边缘的属性的定向多重图形)。为了支持图计算GraphX 提供了一组基本运算符(如: subgraphjoinVertices 和 aggregateMessages以及优化后的 Pregel API。此外GraphX 还包括越来越多的图形算法和构建器,以简化图形分析任务。
##

View File

@ -1,105 +1,105 @@
# Spark 累加器与广播变量
<nav>
<a href="#一简介">一、简介</a><br/>
<a href="#二累加器">二、累加器</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-理解闭包">2.1 理解闭包</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-使用累加器">2.2 使用累加器</a><br/>
<a href="#三广播变量">三、广播变量</a><br/>
</nav>
## 一、简介
在Spark中提供了两种类型的共享变量累加器(accumulator)与广播变量(broadcast variable)
+ **累加器**:用来对信息进行聚合,主要用于累计计数等场景;
+ **广播变量**:主要用于在节点间高效分发大对象。
## 二、累加器
这里先看一个具体的场景,对于正常的累计求和,如果在集群模式中使用下面的代码进行计算,会发现执行结果并非预期:
```scala
var counter = 0
val data = Array(1, 2, 3, 4, 5)
sc.parallelize(data).foreach(x => counter += x)
println(counter)
```
counter最后的结果是0导致这个问题的主要原因是闭包。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-累加器1.png"/> </div>
### 2.1 理解闭包
**1. Scala中闭包的概念**
这里先介绍一下Scala中关于闭包的概念
```
var more = 10
val addMore = (x: Int) => x + more
```
如上函数`addMore`中有两个变量x和more:
- **x** : 是一个绑定变量(bound variable),因为其是该函数的入参,在函数的上下文中有明确的定义;
- **more** : 是一个自由变量(free variable)因为函数字面量本生并没有给more赋予任何含义。
按照定义:在创建函数时,如果需要捕获自由变量,那么包含指向被捕获变量的引用的函数就被称为闭包函数。
**2. Spark中的闭包**
在实际计算时Spark会将对RDD操作分解为TaskTask运行在Worker Node上。在执行之前Spark会对任务进行闭包如果闭包内涉及到自由变量则程序会进行拷贝并将副本变量放在闭包中之后闭包被序列化并发送给每个执行者。因此当在foreach函数中引用`counter`它将不再是Driver节点上的`counter`,而是闭包中的副本`counter`,默认情况下,副本`counter`更新后的值不会回传到Driver所以`counter`的最终值仍然为零。
需要注意的是在Local模式下有可能执行`foreach`Worker NodeDiver处在相同的JVM并引用相同的原始`counter`,这时候更新可能是正确的,但是在集群模式下一定不正确。所以在遇到此类问题时应优先使用累加器。
累加器的原理实际上很简单就是将每个副本变量的最终值传回Driver由Driver聚合后得到最终值并更新原始变量。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-集群模式.png"/> </div>
### 2.2 使用累加器
`SparkContext`中定义了所有创建累加器的方法需要注意的是被中横线划掉的累加器方法在Spark 2.0.0之后被标识为废弃。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-累加器方法.png"/> </div>
使用示例和执行结果分别如下:
```scala
val data = Array(1, 2, 3, 4, 5)
// 定义累加器
val accum = sc.longAccumulator("My Accumulator")
sc.parallelize(data).foreach(x => accum.add(x))
// 获取累加器的值
accum.value
```
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-累加器2.png"/> </div>
## 三、广播变量
在上面介绍中闭包的过程中我们说道每个Task任务的闭包都会持有自由变量的副本如果变量很大且Task任务很多的情况下这必然会对网络IO造成压力为了解决这个情况Spark提供了广播变量。
广播变量的做法很简单就是不把副本变量分发到每个Task中而是将其分发到每个ExecutorExecutor中的所有Task共享一个副本变量。
```scala
// 把一个数组定义为一个广播变量
val broadcastVar = sc.broadcast(Array(1, 2, 3, 4, 5))
// 之后用到该数组时应优先使用广播变量,而不是原值
sc.parallelize(broadcastVar.value).map(_ * 10).collect()
```
## 参考资料
[RDD Programming Guide](http://spark.apache.org/docs/latest/rdd-programming-guide.html#rdd-programming-guide)
# Spark 累加器与广播变量
<nav>
<a href="#一简介">一、简介</a><br/>
<a href="#二累加器">二、累加器</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-理解闭包">2.1 理解闭包</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-使用累加器">2.2 使用累加器</a><br/>
<a href="#三广播变量">三、广播变量</a><br/>
</nav>
## 一、简介
Spark 中,提供了两种类型的共享变量:累加器 (accumulator) 与广播变量 (broadcast variable)
+ **累加器**:用来对信息进行聚合,主要用于累计计数等场景;
+ **广播变量**:主要用于在节点间高效分发大对象。
## 二、累加器
这里先看一个具体的场景,对于正常的累计求和,如果在集群模式中使用下面的代码进行计算,会发现执行结果并非预期:
```scala
var counter = 0
val data = Array(1, 2, 3, 4, 5)
sc.parallelize(data).foreach(x => counter += x)
println(counter)
```
counter 最后的结果是 0导致这个问题的主要原因是闭包。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-累加器1.png"/> </div>
### 2.1 理解闭包
**1. Scala 中闭包的概念**
这里先介绍一下 Scala 中关于闭包的概念:
```
var more = 10
val addMore = (x: Int) => x + more
```
如上函数 `addMore` 中有两个变量 x 和 more:
- **x** : 是一个绑定变量 (bound variable),因为其是该函数的入参,在函数的上下文中有明确的定义;
- **more** : 是一个自由变量 (free variable),因为函数字面量本生并没有给 more 赋予任何含义。
按照定义:在创建函数时,如果需要捕获自由变量,那么包含指向被捕获变量的引用的函数就被称为闭包函数。
**2. Spark 中的闭包**
在实际计算时Spark 会将对 RDD 操作分解为 TaskTask 运行在 Worker Node 上。在执行之前Spark 会对任务进行闭包,如果闭包内涉及到自由变量,则程序会进行拷贝,并将副本变量放在闭包中,之后闭包被序列化并发送给每个执行者。因此,当在 foreach 函数中引用 `counter` 时,它将不再是 Driver 节点上的 `counter`,而是闭包中的副本 `counter`,默认情况下,副本 `counter` 更新后的值不会回传到 Driver所以 `counter` 的最终值仍然为零。
需要注意的是:在 Local 模式下,有可能执行 `foreach`Worker NodeDiver 处在相同的 JVM并引用相同的原始 `counter`,这时候更新可能是正确的,但是在集群模式下一定不正确。所以在遇到此类问题时应优先使用累加器。
累加器的原理实际上很简单:就是将每个副本变量的最终值传回 Driver Driver 聚合后得到最终值,并更新原始变量。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-集群模式.png"/> </div>
### 2.2 使用累加器
`SparkContext` 中定义了所有创建累加器的方法,需要注意的是:被中横线划掉的累加器方法在 Spark 2.0.0 之后被标识为废弃。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-累加器方法.png"/> </div>
使用示例和执行结果分别如下:
```scala
val data = Array(1, 2, 3, 4, 5)
// 定义累加器
val accum = sc.longAccumulator("My Accumulator")
sc.parallelize(data).foreach(x => accum.add(x))
// 获取累加器的值
accum.value
```
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-累加器2.png"/> </div>
## 三、广播变量
在上面介绍中闭包的过程中我们说道每个 Task 任务的闭包都会持有自由变量的副本,如果变量很大且 Task 任务很多的情况下,这必然会对网络 IO 造成压力为了解决这个情况Spark 提供了广播变量。
广播变量的做法很简单:就是不把副本变量分发到每个 Task 中,而是将其分发到每个 ExecutorExecutor 中的所有 Task 共享一个副本变量。
```scala
// 把一个数组定义为一个广播变量
val broadcastVar = sc.broadcast(Array(1, 2, 3, 4, 5))
// 之后用到该数组时应优先使用广播变量,而不是原值
sc.parallelize(broadcastVar.value).map(_ * 10).collect()
```
## 参考资料
[RDD Programming Guide](http://spark.apache.org/docs/latest/rdd-programming-guide.html#rdd-programming-guide)

View File

@ -12,49 +12,49 @@
### 1.1 spark-submit
Spark所有模式均使用`spark-submit`命令提交作业,其格式如下:
Spark 所有模式均使用 `spark-submit` 命令提交作业,其格式如下:
```shell
./bin/spark-submit \
--class <main-class> \ # 应用程序主入口类
--master <master-url> \ # 集群的Master Url
--master <master-url> \ # 集群的 Master Url
--deploy-mode <deploy-mode> \ # 部署模式
--conf <key>=<value> \ # 可选配置
... # other options
<application-jar> \ # Jar包路径
<application-jar> \ # Jar 包路径
[application-arguments] #传递给主入口类的参数
```
需要注意的是:在集群环境下,`application-jar`必须能被集群中所有节点都能访问可以是HDFS上的路径也可以是本地文件系统路径如果是本地文件系统路径则要求集群中每一个机器节点上的相同路径都存在该Jar包。
需要注意的是:在集群环境下,`application-jar` 必须能被集群中所有节点都能访问,可以是 HDFS 上的路径;也可以是本地文件系统路径,如果是本地文件系统路径,则要求集群中每一个机器节点上的相同路径都存在该 Jar 包。
### 1.2 deploy-mode
deploy-mode`cluster``client`两个可选参数,默认为`client`。这里以Spark On Yarn模式对两者的区别进行说明
deploy-mode`cluster``client` 两个可选参数,默认为 `client`。这里以 Spark On Yarn 模式对两者的区别进行说明
+ 在cluster模式下Spark Drvier在应用程序的Master进程内运行该进程由群集上的YARN管理提交作业的客户端可以在启动应用程序后关闭
+ 在client模式下Spark Drvier在提交作业的客户端进程中运行Master进程仅用于从YARN请求资源。
+ 在 cluster 模式下Spark Drvier 在应用程序的 Master 进程内运行,该进程由群集上的 YARN 管理,提交作业的客户端可以在启动应用程序后关闭;
+ 在 client 模式下Spark Drvier 在提交作业的客户端进程中运行Master 进程仅用于从 YARN 请求资源。
### 1.3 master-url
master-url的所有可选参数如下表所示
master-url 的所有可选参数如下表所示:
| Master URL | Meaning |
| --------------------------------- | ------------------------------------------------------------ |
| `local` | 使用一个线程本地运行Spark |
| `local` | 使用一个线程本地运行 Spark |
| `local[K]` | 使用 K 个 worker 线程本地运行 Spark |
| `local[K,F]` | 使用 K 个 worker 线程本地运行 , 第二个参数为Task的失败重试次数 |
| `local[*]` | 使用与CPU核心数一样的线程数在本地运行Spark |
| `local[*,F]` | 使用与CPU核心数一样的线程数在本地运行Spark<br/>第二个参数为Task的失败重试次数 |
| `local[K,F]` | 使用 K 个 worker 线程本地运行 , 第二个参数为 Task 的失败重试次数 |
| `local[*]` | 使用与 CPU 核心数一样的线程数在本地运行 Spark |
| `local[*,F]` | 使用与 CPU 核心数一样的线程数在本地运行 Spark<br/>第二个参数为 Task 的失败重试次数 |
| `spark://HOST:PORT` | 连接至指定的 standalone 集群的 master 节点。端口号默认是 7077。 |
| `spark://HOST1:PORT1,HOST2:PORT2` | 如果standalone集群采用Zookeeper实现高可用则必须包含由zookeeper设置的所有master主机地址。 |
| `mesos://HOST:PORT` | 连接至给定的Mesos集群。端口默认是 5050。对于使用了 ZooKeeper 的 Mesos cluster 来说,使用 `mesos://zk://...`来指定地址,使用 `--deploy-mode cluster`模式来提交。 |
| `yarn` | 连接至一个 YARN 集群,集群由配置的 `HADOOP_CONF_DIR` 或者 `YARN_CONF_DIR` 来决定。使用`--deploy-mode`参数来配置`client``cluster` 模式。 |
| `spark://HOST1:PORT1,HOST2:PORT2` | 如果 standalone 集群采用 Zookeeper 实现高可用,则必须包含由 zookeeper 设置的所有 master 主机地址。 |
| `mesos://HOST:PORT` | 连接至给定的 Mesos 集群。端口默认是 5050。对于使用了 ZooKeeper 的 Mesos cluster 来说,使用 `mesos://zk://...` 来指定地址,使用 `--deploy-mode cluster` 模式来提交。 |
| `yarn` | 连接至一个 YARN 集群,集群由配置的 `HADOOP_CONF_DIR` 或者 `YARN_CONF_DIR` 来决定。使用 `--deploy-mode` 参数来配置 `client` `cluster` 模式。 |
下面主要介绍三种常用部署模式及对应的作业提交方式。
## 二、Local模式
Local模式下提交作业最为简单不需要进行任何配置提交命令如下
Local 模式下提交作业最为简单,不需要进行任何配置,提交命令如下:
```shell
# 本地模式提交应用
@ -62,10 +62,10 @@ spark-submit \
--class org.apache.spark.examples.SparkPi \
--master local[2] \
/usr/app/spark-2.4.0-bin-hadoop2.6/examples/jars/spark-examples_2.11-2.4.0.jar \
100 # 传给SparkPi的参数
100 # 传给 SparkPi 的参数
```
`spark-examples_2.11-2.4.0.jar`Spark提供的测试用例包`SparkPi`用于计算Pi值执行结果如下
`spark-examples_2.11-2.4.0.jar`Spark 提供的测试用例包,`SparkPi` 用于计算 Pi 值,执行结果如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-pi.png"/> </div>
@ -73,10 +73,10 @@ spark-submit \
## 三、Standalone模式
StandaloneSpark提供的一种内置的集群模式采用内置的资源管理器进行管理。下面按照如图所示演示1个Mater和2个Worker节点的集群配置这里使用两台主机进行演示
StandaloneSpark 提供的一种内置的集群模式,采用内置的资源管理器进行管理。下面按照如图所示演示 1 个 Mater 和 2 个 Worker 节点的集群配置,这里使用两台主机进行演示:
+ hadoop001 由于只有两台主机所以hadoop001既是Master节点也是Worker节点;
+ hadoop002 Worker节点。
+ hadoop001 由于只有两台主机,所以 hadoop001 既是 Master 节点,也是 Worker 节点;
+ hadoop002 Worker 节点。
@ -86,13 +86,13 @@ Standalone是Spark提供的一种内置的集群模式采用内置的资源
### 3.1 环境配置
首先需要保证Spark已经解压在两台主机的相同路径上。然后进入hadoop001`${SPARK_HOME}/conf/`目录下,拷贝配置样本并进行相关配置:
首先需要保证 Spark 已经解压在两台主机的相同路径上。然后进入 hadoop001`${SPARK_HOME}/conf/` 目录下,拷贝配置样本并进行相关配置:
```shell
# cp spark-env.sh.template spark-env.sh
```
`spark-env.sh`中配置JDK的目录完成后将该配置使用scp命令分发到hadoop002上
`spark-env.sh` 中配置 JDK 的目录,完成后将该配置使用 scp 命令分发到 hadoop002 上:
```shell
# JDK安装位置
@ -101,13 +101,13 @@ JAVA_HOME=/usr/java/jdk1.8.0_201
### 3.2 集群配置
`${SPARK_HOME}/conf/`目录下,拷贝集群配置样本并进行相关配置:
`${SPARK_HOME}/conf/` 目录下,拷贝集群配置样本并进行相关配置:
```
# cp slaves.template slaves
```
指定所有Worker节点的主机名
指定所有 Worker 节点的主机名:
```shell
# A Spark Worker will be started on each of the machines listed below.
@ -117,19 +117,19 @@ hadoop002
这里需要注意以下三点:
+ 主机名与IP地址的映射必须在`/etc/hosts`文件中已经配置否则就直接使用IP地址
+ 主机名与 IP 地址的映射必须在 `/etc/hosts` 文件中已经配置,否则就直接使用 IP 地址;
+ 每个主机名必须独占一行;
+ SparkMaster主机是通过SSH访问所有的Worker节点所以需要预先配置免密登录。
+ SparkMaster 主机是通过 SSH 访问所有的 Worker 节点,所以需要预先配置免密登录。
### 3.3 启动
使用`start-all.sh`代表启动Master和所有Worker服务。
使用 `start-all.sh` 代表启动 Master 和所有 Worker 服务。
```shell
./sbin/start-master.sh
```
访问8080端口查看SparkWeb-UI界面,,此时应该显示有两个有效的工作节点:
访问 8080 端口,查看 SparkWeb-UI 界面,,此时应该显示有两个有效的工作节点:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-Standalone-web-ui.png"/> </div>
@ -150,7 +150,7 @@ spark-submit \
--class org.apache.spark.examples.SparkPi \
--master spark://207.184.161.138:7077 \
--deploy-mode cluster \
--supervise \ # 配置此参数代表开启监督如果主应用程序异常退出则自动重启Driver
--supervise \ # 配置此参数代表开启监督,如果主应用程序异常退出,则自动重启 Driver
--executor-memory 2G \
--total-executor-cores 10 \
/usr/app/spark-2.4.0-bin-hadoop2.6/examples/jars/spark-examples_2.11-2.4.0.jar \
@ -170,13 +170,13 @@ check your cluster UI to ensure that workers are registered and have sufficient
<br/>
这时候可以查看Web UI我这里是内存空间不足提交命令中要求作业的`executor-memory`2G但是实际的工作节点的`Memory`只有1G这时候你可以修改`--executor-memory`,也可以修改 Woker 的`Memory`其默认值为主机所有可用内存值减去1G。
这时候可以查看 Web UI我这里是内存空间不足提交命令中要求作业的 `executor-memory`2G但是实际的工作节点的 `Memory` 只有 1G这时候你可以修改 `--executor-memory`,也可以修改 Woker 的 `Memory`,其默认值为主机所有可用内存值减去 1G。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-内存不足.png"/> </div>
<br/>
关于MasterWoker节点的所有可选配置如下可以在`spark-env.sh`中进行对应的配置:
关于 MasterWoker 节点的所有可选配置如下,可以在 `spark-env.sh` 中进行对应的配置:
| Environment Variable环境变量 | Meaning含义 |
| -------------------------------- | ------------------------------------------------------------ |
@ -184,12 +184,12 @@ check your cluster UI to ensure that workers are registered and have sufficient
| `SPARK_MASTER_PORT` | master 节点地址端口默认7077 |
| `SPARK_MASTER_WEBUI_PORT` | master 的 web UI 的端口默认8080 |
| `SPARK_MASTER_OPTS` | 仅用于 master 的配置属性,格式是 "-Dx=y"默认none,所有属性可以参考官方文档:[spark-standalone-mode](https://spark.apache.org/docs/latest/spark-standalone.html#spark-standalone-mode) |
| `SPARK_LOCAL_DIRS` | spark 的临时存储的目录用于暂存map的输出和持久化存储RDDs。多个目录用逗号分隔 |
| `SPARK_WORKER_CORES` | spark worker节点可以使用CPU Cores的数量。默认全部可用 |
| `SPARK_WORKER_MEMORY` | spark worker节点可以使用的内存数量默认全部的内存减去1GB |
| `SPARK_WORKER_PORT` | spark worker节点的端口默认 random随机 |
| `SPARK_LOCAL_DIRS` | spark 的临时存储的目录,用于暂存 map 的输出和持久化存储 RDDs。多个目录用逗号分隔 |
| `SPARK_WORKER_CORES` | spark worker 节点可以使用 CPU Cores 的数量。(默认:全部可用) |
| `SPARK_WORKER_MEMORY` | spark worker 节点可以使用的内存数量(默认:全部的内存减去 1GB |
| `SPARK_WORKER_PORT` | spark worker 节点的端口(默认: random随机 |
| `SPARK_WORKER_WEBUI_PORT` | worker 的 web UI 的 Port端口默认8081 |
| `SPARK_WORKER_DIR` | worker运行应用程序的目录这个目录中包含日志和暂存空间defaultSPARK_HOME/work |
| `SPARK_WORKER_DIR` | worker 运行应用程序的目录这个目录中包含日志和暂存空间defaultSPARK_HOME/work |
| `SPARK_WORKER_OPTS` | 仅用于 worker 的配置属性,格式是 "-Dx=y"默认none。所有属性可以参考官方文档[spark-standalone-mode](https://spark.apache.org/docs/latest/spark-standalone.html#spark-standalone-mode) |
| `SPARK_DAEMON_MEMORY` | 分配给 spark master 和 worker 守护进程的内存。(默认: 1G |
| `SPARK_DAEMON_JAVA_OPTS` | spark master 和 worker 守护进程的 JVM 选项,格式是 "-Dx=y"默认none |
@ -199,11 +199,11 @@ check your cluster UI to ensure that workers are registered and have sufficient
## 三、Spark on Yarn模式
Spark支持将作业提交到Yarn上运行此时不需要启动Master节点也不需要启动Worker节点。
Spark 支持将作业提交到 Yarn 上运行,此时不需要启动 Master 节点,也不需要启动 Worker 节点。
### 3.1 配置
`spark-env.sh`中配置hadoop的配置目录的位置可以使用`YARN_CONF_DIR``HADOOP_CONF_DIR`进行指定:
`spark-env.sh` 中配置 hadoop 的配置目录的位置,可以使用 `YARN_CONF_DIR``HADOOP_CONF_DIR` 进行指定:
```properties
YARN_CONF_DIR=/usr/app/hadoop-2.6.0-cdh5.15.2/etc/hadoop
@ -213,7 +213,7 @@ JAVA_HOME=/usr/java/jdk1.8.0_201
### 3.2 启动
必须要保证Hadoop已经启动这里包括YARNHDFS都需要启动因为在计算过程中Spark会使用HDFS存储临时文件如果HDFS没有启动则会抛出异常。
必须要保证 Hadoop 已经启动,这里包括 YARNHDFS 都需要启动,因为在计算过程中 Spark 会使用 HDFS 存储临时文件,如果 HDFS 没有启动,则会抛出异常。
```shell
# start-yarn.sh

View File

@ -1,5 +1,6 @@
# Spring/Spring Boot 整合 Mybatis + Phoenix
# Spring/Spring Boot 整合 Mybatis + Phoenix
<nav>
<a href="#一前言">一、前言</a><br/>
<a href="#二Spring-+-Mybatis-+-Phoenix">二、Spring + Mybatis + Phoenix</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-项目结构">2.1 项目结构</a><br/>
@ -18,368 +19,368 @@
<a href="#附建表语句">附:建表语句</a><br/>
</nav>
## 一、前言
使用Spring+Mybatis操作Phoenix和操作其他的关系型数据库如MysqlOracle在配置上是基本相同的下面会分别给出Spring/Spring Boot 整合步骤,完整代码见本仓库:
+ [Spring + Mybatis + Phoenix](https://github.com/heibaiying/BigData-Notes/tree/master/code/Phoenix/spring-mybatis-phoenix)
+ [SpringBoot + Mybatis + Phoenix](https://github.com/heibaiying/BigData-Notes/tree/master/code/Phoenix/spring-boot-mybatis-phoenix)
## 二、Spring + Mybatis + Phoenix
### 2.1 项目结构
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spring-mybatis-phoenix.png"/> </div>
### 2.2 主要依赖
除了Spring相关依赖外还需要导入`phoenix-core`和对应的Mybatis依赖包
```xml
<!--mybatis 依赖包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!--phoenix core-->
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-core</artifactId>
<version>4.14.0-cdh5.14.2</version>
</dependency>
```
### 2.3 数据库配置文件
在数据库配置文件 `jdbc.properties` 中配置数据库驱动和zookeeper地址
```properties
# 数据库驱动
phoenix.driverClassName=org.apache.phoenix.jdbc.PhoenixDriver
# zookeeper地址
phoenix.url=jdbc:phoenix:192.168.0.105:2181
```
### 2.4 配置数据源和会话工厂
```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启注解包扫描-->
<context:component-scan base-package="com.heibaiying.*"/>
<!--指定配置文件的位置-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--Phoenix配置-->
<property name="driverClassName" value="${phoenix.driverClassName}"/>
<property name="url" value="${phoenix.url}"/>
</bean>
<!--配置 mybatis 会话工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--指定mapper文件所在的位置-->
<property name="mapperLocations" value="classpath*:/mappers/**/*.xml"/>
<property name="configLocation" value="classpath:mybatisConfig.xml"/>
</bean>
<!--扫描注册接口 -->
<!--作用:从接口的基础包开始递归搜索,并将它们注册为 MapperFactoryBean(只有至少一种方法的接口才会被注册;, 具体类将被忽略)-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定会话工厂 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 指定mybatis接口所在的包 -->
<property name="basePackage" value="com.heibaiying.dao"/>
</bean>
</beans>
```
### 2.5 Mybtais参数配置
新建mybtais配置文件按照需求配置额外参数 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- mybatis 配置文件 -->
<configuration>
<settings>
<!-- 开启驼峰命名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 打印查询sql -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>
```
### 2.6 查询接口
```java
public interface PopulationDao {
List<USPopulation> queryAll();
void save(USPopulation USPopulation);
USPopulation queryByStateAndCity(@Param("state") String state, @Param("city") String city);
void deleteByStateAndCity(@Param("state") String state, @Param("city") String city);
}
```
```xml
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heibaiying.dao.PopulationDao">
<select id="queryAll" resultType="com.heibaiying.bean.USPopulation">
SELECT * FROM us_population
</select>
<insert id="save">
UPSERT INTO us_population VALUES( #{state}, #{city}, #{population} )
</insert>
<select id="queryByStateAndCity" resultType="com.heibaiying.bean.USPopulation">
SELECT * FROM us_population WHERE state=#{state} AND city = #{city}
</select>
<delete id="deleteByStateAndCity">
DELETE FROM us_population WHERE state=#{state} AND city = #{city}
</delete>
</mapper>
```
### 2.7 单元测试
```java
@RunWith(SpringRunner.class)
@ContextConfiguration({"classpath:springApplication.xml"})
public class PopulationDaoTest {
@Autowired
private PopulationDao populationDao;
@Test
public void queryAll() {
List<USPopulation> USPopulationList = populationDao.queryAll();
if (USPopulationList != null) {
for (USPopulation USPopulation : USPopulationList) {
System.out.println(USPopulation.getCity() + " " + USPopulation.getPopulation());
}
}
}
@Test
public void save() {
populationDao.save(new USPopulation("TX", "Dallas", 66666));
USPopulation usPopulation = populationDao.queryByStateAndCity("TX", "Dallas");
System.out.println(usPopulation);
}
@Test
public void update() {
populationDao.save(new USPopulation("TX", "Dallas", 99999));
USPopulation usPopulation = populationDao.queryByStateAndCity("TX", "Dallas");
System.out.println(usPopulation);
}
@Test
public void delete() {
populationDao.deleteByStateAndCity("TX", "Dallas");
USPopulation usPopulation = populationDao.queryByStateAndCity("TX", "Dallas");
System.out.println(usPopulation);
}
}
```
## 三、SpringBoot + Mybatis + Phoenix
### 3.1 项目结构
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spring-boot-mybatis-phoenix.png"/> </div>
### 3.2 主要依赖
```xml
<!--spring 1.5 x 以上版本对应 mybatis 1.3.x (1.3.1)
关于更多spring-boot 与 mybatis 的版本对应可以参见 <a href="http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/">-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--phoenix core-->
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-core</artifactId>
<version>4.14.0-cdh5.14.2</version>
</dependency>
<dependency>
```
spring boot 与 mybatis 版本的对应关系:
| MyBatis-Spring-Boot-Starter 版本 | MyBatis-Spring 版本 | Spring Boot 版本 |
| -------------------------------- | ------------------- | ---------------- |
| **1.3.x (1.3.1)** | 1.3 or higher | 1.5 or higher |
| **1.2.x (1.2.1)** | 1.3 or higher | 1.4 or higher |
| **1.1.x (1.1.1)** | 1.3 or higher | 1.3 or higher |
| **1.0.x (1.0.2)** | 1.2 or higher | 1.3 or higher |
### 3.3 配置数据源
在application.yml 中配置数据源spring boot 2.x 版本默认采用Hikari作为数据库连接池Hikari是目前java平台性能最好的连接池性能好于druid。
```yaml
spring:
datasource:
#zookeeper地址
url: jdbc:phoenix:192.168.0.105:2181
driver-class-name: org.apache.phoenix.jdbc.PhoenixDriver
# 如果不想配置对数据库连接池做特殊配置的话,以下关于连接池的配置就不是必须的
# spring-boot 2.X 默认采用高性能的 Hikari 作为连接池 更多配置可以参考 https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby
type: com.zaxxer.hikari.HikariDataSource
hikari:
# 池中维护的最小空闲连接数
minimum-idle: 10
# 池中最大连接数,包括闲置和使用中的连接
maximum-pool-size: 20
# 此属性控制从池返回的连接的默认自动提交行为。默认为true
auto-commit: true
# 允许最长空闲时间
idle-timeout: 30000
# 此属性表示连接池的用户定义名称主要显示在日志记录和JMX管理控制台中以标识池和池配置。 默认值:自动生成
pool-name: custom-hikari
#此属性控制池中连接的最长生命周期值0表示无限生命周期默认1800000即30分钟
max-lifetime: 1800000
# 数据库连接超时时间,默认30秒30000
connection-timeout: 30000
# 连接测试sql 这个地方需要根据数据库方言差异而配置 例如 oracle 就应该写成 select 1 from dual
connection-test-query: SELECT 1
# mybatis 相关配置
mybatis:
configuration:
# 是否打印sql语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
```
### 3.4 新建查询接口
上面Spring+Mybatis我们使用了XML的方式来写SQL为了体现Mybatis支持多种方式这里使用注解的方式来写SQL。
```java
@Mapper
public interface PopulationDao {
@Select("SELECT * from us_population")
List<USPopulation> queryAll();
@Insert("UPSERT INTO us_population VALUES( #{state}, #{city}, #{population} )")
void save(USPopulation USPopulation);
@Select("SELECT * FROM us_population WHERE state=#{state} AND city = #{city}")
USPopulation queryByStateAndCity(String state, String city);
@Delete("DELETE FROM us_population WHERE state=#{state} AND city = #{city}")
void deleteByStateAndCity(String state, String city);
}
```
### 3.5 单元测试
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class PopulationTest {
@Autowired
private PopulationDao populationDao;
@Test
public void queryAll() {
List<USPopulation> USPopulationList = populationDao.queryAll();
if (USPopulationList != null) {
for (USPopulation USPopulation : USPopulationList) {
System.out.println(USPopulation.getCity() + " " + USPopulation.getPopulation());
}
}
}
@Test
public void save() {
populationDao.save(new USPopulation("TX", "Dallas", 66666));
USPopulation usPopulation = populationDao.queryByStateAndCity("TX", "Dallas");
System.out.println(usPopulation);
}
@Test
public void update() {
populationDao.save(new USPopulation("TX", "Dallas", 99999));
USPopulation usPopulation = populationDao.queryByStateAndCity("TX", "Dallas");
System.out.println(usPopulation);
}
@Test
public void delete() {
populationDao.deleteByStateAndCity("TX", "Dallas");
USPopulation usPopulation = populationDao.queryByStateAndCity("TX", "Dallas");
System.out.println(usPopulation);
}
}
```
## 附:建表语句
上面单元测试涉及到的测试表的建表语句如下:
```sql
CREATE TABLE IF NOT EXISTS us_population (
state CHAR(2) NOT NULL,
city VARCHAR NOT NULL,
population BIGINT
CONSTRAINT my_pk PRIMARY KEY (state, city));
-- 测试数据
UPSERT INTO us_population VALUES('NY','New York',8143197);
UPSERT INTO us_population VALUES('CA','Los Angeles',3844829);
UPSERT INTO us_population VALUES('IL','Chicago',2842518);
UPSERT INTO us_population VALUES('TX','Houston',2016582);
UPSERT INTO us_population VALUES('PA','Philadelphia',1463281);
UPSERT INTO us_population VALUES('AZ','Phoenix',1461575);
UPSERT INTO us_population VALUES('TX','San Antonio',1256509);
UPSERT INTO us_population VALUES('CA','San Diego',1255540);
UPSERT INTO us_population VALUES('CA','San Jose',912332);
```
## 一、前言
使用 Spring+Mybatis 操作 Phoenix 和操作其他的关系型数据库(如 MysqlOracle在配置上是基本相同的下面会分别给出 Spring/Spring Boot 整合步骤,完整代码见本仓库:
+ [Spring + Mybatis + Phoenix](https://github.com/heibaiying/BigData-Notes/tree/master/code/Phoenix/spring-mybatis-phoenix)
+ [SpringBoot + Mybatis + Phoenix](https://github.com/heibaiying/BigData-Notes/tree/master/code/Phoenix/spring-boot-mybatis-phoenix)
## 二、Spring + Mybatis + Phoenix
### 2.1 项目结构
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spring-mybatis-phoenix.png"/> </div>
### 2.2 主要依赖
除了 Spring 相关依赖外,还需要导入 `phoenix-core` 和对应的 Mybatis 依赖包
```xml
<!--mybatis 依赖包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!--phoenix core-->
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-core</artifactId>
<version>4.14.0-cdh5.14.2</version>
</dependency>
```
### 2.3 数据库配置文件
在数据库配置文件 `jdbc.properties` 中配置数据库驱动和 zookeeper 地址
```properties
# 数据库驱动
phoenix.driverClassName=org.apache.phoenix.jdbc.PhoenixDriver
# zookeeper地址
phoenix.url=jdbc:phoenix:192.168.0.105:2181
```
### 2.4 配置数据源和会话工厂
```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启注解包扫描-->
<context:component-scan base-package="com.heibaiying.*"/>
<!--指定配置文件的位置-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--Phoenix 配置-->
<property name="driverClassName" value="${phoenix.driverClassName}"/>
<property name="url" value="${phoenix.url}"/>
</bean>
<!--配置 mybatis 会话工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--指定 mapper 文件所在的位置-->
<property name="mapperLocations" value="classpath*:/mappers/**/*.xml"/>
<property name="configLocation" value="classpath:mybatisConfig.xml"/>
</bean>
<!--扫描注册接口 -->
<!--作用:从接口的基础包开始递归搜索,并将它们注册为 MapperFactoryBean(只有至少一种方法的接口才会被注册;, 具体类将被忽略)-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定会话工厂 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 指定 mybatis 接口所在的包 -->
<property name="basePackage" value="com.heibaiying.dao"/>
</bean>
</beans>
```
### 2.5 Mybtais参数配置
新建 mybtais 配置文件,按照需求配置额外参数, 更多 settings 配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- mybatis 配置文件 -->
<configuration>
<settings>
<!-- 开启驼峰命名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 打印查询 sql -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>
```
### 2.6 查询接口
```java
public interface PopulationDao {
List<USPopulation> queryAll();
void save(USPopulation USPopulation);
USPopulation queryByStateAndCity(@Param("state") String state, @Param("city") String city);
void deleteByStateAndCity(@Param("state") String state, @Param("city") String city);
}
```
```xml
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heibaiying.dao.PopulationDao">
<select id="queryAll" resultType="com.heibaiying.bean.USPopulation">
SELECT * FROM us_population
</select>
<insert id="save">
UPSERT INTO us_population VALUES( #{state}, #{city}, #{population} )
</insert>
<select id="queryByStateAndCity" resultType="com.heibaiying.bean.USPopulation">
SELECT * FROM us_population WHERE state=#{state} AND city = #{city}
</select>
<delete id="deleteByStateAndCity">
DELETE FROM us_population WHERE state=#{state} AND city = #{city}
</delete>
</mapper>
```
### 2.7 单元测试
```java
@RunWith(SpringRunner.class)
@ContextConfiguration({"classpath:springApplication.xml"})
public class PopulationDaoTest {
@Autowired
private PopulationDao populationDao;
@Test
public void queryAll() {
List<USPopulation> USPopulationList = populationDao.queryAll();
if (USPopulationList != null) {
for (USPopulation USPopulation : USPopulationList) {
System.out.println(USPopulation.getCity() + " " + USPopulation.getPopulation());
}
}
}
@Test
public void save() {
populationDao.save(new USPopulation("TX", "Dallas", 66666));
USPopulation usPopulation = populationDao.queryByStateAndCity("TX", "Dallas");
System.out.println(usPopulation);
}
@Test
public void update() {
populationDao.save(new USPopulation("TX", "Dallas", 99999));
USPopulation usPopulation = populationDao.queryByStateAndCity("TX", "Dallas");
System.out.println(usPopulation);
}
@Test
public void delete() {
populationDao.deleteByStateAndCity("TX", "Dallas");
USPopulation usPopulation = populationDao.queryByStateAndCity("TX", "Dallas");
System.out.println(usPopulation);
}
}
```
## 三、SpringBoot + Mybatis + Phoenix
### 3.1 项目结构
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spring-boot-mybatis-phoenix.png"/> </div>
### 3.2 主要依赖
```xml
<!--spring 1.5 x 以上版本对应 mybatis 1.3.x (1.3.1)
关于更多 spring-boot 与 mybatis 的版本对应可以参见 <a href="http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/">-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--phoenix core-->
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-core</artifactId>
<version>4.14.0-cdh5.14.2</version>
</dependency>
<dependency>
```
spring boot 与 mybatis 版本的对应关系:
| MyBatis-Spring-Boot-Starter 版本 | MyBatis-Spring 版本 | Spring Boot 版本 |
| -------------------------------- | ------------------- | ---------------- |
| **1.3.x (1.3.1)** | 1.3 or higher | 1.5 or higher |
| **1.2.x (1.2.1)** | 1.3 or higher | 1.4 or higher |
| **1.1.x (1.1.1)** | 1.3 or higher | 1.3 or higher |
| **1.0.x (1.0.2)** | 1.2 or higher | 1.3 or higher |
### 3.3 配置数据源
在 application.yml 中配置数据源spring boot 2.x 版本默认采用 Hikari 作为数据库连接池Hikari 是目前 java 平台性能最好的连接池,性能好于 druid。
```yaml
spring:
datasource:
#zookeeper 地址
url: jdbc:phoenix:192.168.0.105:2181
driver-class-name: org.apache.phoenix.jdbc.PhoenixDriver
# 如果不想配置对数据库连接池做特殊配置的话,以下关于连接池的配置就不是必须的
# spring-boot 2.X 默认采用高性能的 Hikari 作为连接池 更多配置可以参考 https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby
type: com.zaxxer.hikari.HikariDataSource
hikari:
# 池中维护的最小空闲连接数
minimum-idle: 10
# 池中最大连接数,包括闲置和使用中的连接
maximum-pool-size: 20
# 此属性控制从池返回的连接的默认自动提交行为。默认为 true
auto-commit: true
# 允许最长空闲时间
idle-timeout: 30000
# 此属性表示连接池的用户定义名称,主要显示在日志记录和 JMX 管理控制台中,以标识池和池配置。 默认值:自动生成
pool-name: custom-hikari
#此属性控制池中连接的最长生命周期,值 0 表示无限生命周期,默认 1800000 即 30 分钟
max-lifetime: 1800000
# 数据库连接超时时间,默认 30 秒,即 30000
connection-timeout: 30000
# 连接测试 sql 这个地方需要根据数据库方言差异而配置 例如 oracle 就应该写成 select 1 from dual
connection-test-query: SELECT 1
# mybatis 相关配置
mybatis:
configuration:
# 是否打印 sql 语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
```
### 3.4 新建查询接口
上面 Spring+Mybatis 我们使用了 XML 的方式来写 SQL为了体现 Mybatis 支持多种方式,这里使用注解的方式来写 SQL。
```java
@Mapper
public interface PopulationDao {
@Select("SELECT * from us_population")
List<USPopulation> queryAll();
@Insert("UPSERT INTO us_population VALUES( #{state}, #{city}, #{population} )")
void save(USPopulation USPopulation);
@Select("SELECT * FROM us_population WHERE state=#{state} AND city = #{city}")
USPopulation queryByStateAndCity(String state, String city);
@Delete("DELETE FROM us_population WHERE state=#{state} AND city = #{city}")
void deleteByStateAndCity(String state, String city);
}
```
### 3.5 单元测试
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class PopulationTest {
@Autowired
private PopulationDao populationDao;
@Test
public void queryAll() {
List<USPopulation> USPopulationList = populationDao.queryAll();
if (USPopulationList != null) {
for (USPopulation USPopulation : USPopulationList) {
System.out.println(USPopulation.getCity() + " " + USPopulation.getPopulation());
}
}
}
@Test
public void save() {
populationDao.save(new USPopulation("TX", "Dallas", 66666));
USPopulation usPopulation = populationDao.queryByStateAndCity("TX", "Dallas");
System.out.println(usPopulation);
}
@Test
public void update() {
populationDao.save(new USPopulation("TX", "Dallas", 99999));
USPopulation usPopulation = populationDao.queryByStateAndCity("TX", "Dallas");
System.out.println(usPopulation);
}
@Test
public void delete() {
populationDao.deleteByStateAndCity("TX", "Dallas");
USPopulation usPopulation = populationDao.queryByStateAndCity("TX", "Dallas");
System.out.println(usPopulation);
}
}
```
## 附:建表语句
上面单元测试涉及到的测试表的建表语句如下:
```sql
CREATE TABLE IF NOT EXISTS us_population (
state CHAR(2) NOT NULL,
city VARCHAR NOT NULL,
population BIGINT
CONSTRAINT my_pk PRIMARY KEY (state, city));
-- 测试数据
UPSERT INTO us_population VALUES('NY','New York',8143197);
UPSERT INTO us_population VALUES('CA','Los Angeles',3844829);
UPSERT INTO us_population VALUES('IL','Chicago',2842518);
UPSERT INTO us_population VALUES('TX','Houston',2016582);
UPSERT INTO us_population VALUES('PA','Philadelphia',1463281);
UPSERT INTO us_population VALUES('AZ','Phoenix',1461575);
UPSERT INTO us_population VALUES('TX','San Antonio',1256509);
UPSERT INTO us_population VALUES('CA','San Diego',1255540);
UPSERT INTO us_population VALUES('CA','San Jose',912332);
```

View File

@ -41,7 +41,7 @@
### 1. 查询MySQL所有数据库
通常用于SqoopMySQL连通测试
通常用于 SqoopMySQL 连通测试:
```shell
sqoop list-databases \
@ -71,9 +71,9 @@ sqoop list-tables \
#### 1. 导入命令
示例导出MySQL数据库中的`help_keyword`表到HDFS`/sqoop`目录下,如果导入目录存在则先删除再导入,使用3个`map tasks`并行导入。
示例:导出 MySQL 数据库中的 `help_keyword` 表到 HDFS`/sqoop` 目录下,如果导入目录存在则先删除再导入,使用 3 个 `map tasks` 并行导入。
> help_keywordMySQL内置的一张字典表之后的示例均使用这张表。
> help_keywordMySQL 内置的一张字典表,之后的示例均使用这张表。
```shell
sqoop import \
@ -84,13 +84,13 @@ sqoop import \
--delete-target-dir \ # 目标目录存在则先删除
--target-dir /sqoop \ # 导入的目标目录
--fields-terminated-by '\t' \ # 指定导出数据的分隔符
-m 3 # 指定并行执行的map tasks数量
-m 3 # 指定并行执行的 map tasks 数量
```
日志输出如下,可以看到输入数据被平均`split`为三份,分别由三个`map task`进行处理。数据默认以表的主键列作为拆分依据,如果你的表没有主键,有以下两种方案:
日志输出如下,可以看到输入数据被平均 `split` 为三份,分别由三个 `map task` 进行处理。数据默认以表的主键列作为拆分依据,如果你的表没有主键,有以下两种方案:
+ 添加`-- autoreset-to-one-mapper`参数,代表只启动一个`map task`,即不并行执行;
+ 若仍希望并行执行,则可以使用`--split-by <column-name>` 指明拆分数据的参考列。
+ 添加 `-- autoreset-to-one-mapper` 参数,代表只启动一个 `map task`,即不并行执行;
+ 若仍希望并行执行,则可以使用 `--split-by <column-name>` 指明拆分数据的参考列。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/sqoop-map-task.png"/> </div>
@ -103,7 +103,7 @@ hadoop fs -ls -R /sqoop
hadoop fs -text /sqoop/part-m-00000
```
查看HDFS导入目录,可以看到表中数据被分为3部分进行存储,这是由指定的并行度决定的。
查看 HDFS 导入目录,可以看到表中数据被分为 3 部分进行存储,这是由指定的并行度决定的。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/sqoop_hdfs_ls.png"/> </div>
@ -116,7 +116,7 @@ sqoop export \
--connect jdbc:mysql://hadoop001:3306/mysql \
--username root \
--password root \
--table help_keyword_from_hdfs \ # 导出数据存储在MySQLhelp_keyword_from_hdf的表中
--table help_keyword_from_hdfs \ # 导出数据存储在 MySQLhelp_keyword_from_hdf 的表中
--export-dir /sqoop \
--input-fields-terminated-by '\t'\
--m 3
@ -134,7 +134,7 @@ CREATE TABLE help_keyword_from_hdfs LIKE help_keyword ;
### 4.1 MySQL数据导入到Hive
Sqoop导入数据到Hive是通过先将数据导入到HDFS上的临时目录然后再将数据从HDFS`Load`Hive中最后将临时目录删除。可以使用`target-dir`来指定临时目录。
Sqoop 导入数据到 Hive 是通过先将数据导入到 HDFS 上的临时目录,然后再将数据从 HDFS`Load`Hive 中,最后将临时目录删除。可以使用 `target-dir` 来指定临时目录。
#### 1. 导入命令
@ -146,18 +146,18 @@ sqoop import \
--table help_keyword \ # 待导入的表
--delete-target-dir \ # 如果临时目录存在删除
--target-dir /sqoop_hive \ # 临时目录位置
--hive-database sqoop_test \ # 导入到Hivesqoop_test数据库数据库需要预先创建。不指定则默认为default库
--hive-import \ # 导入到Hive
--hive-overwrite \ # 如果Hive表中有数据则覆盖这会清除表中原有的数据然后再写入
--hive-database sqoop_test \ # 导入到 Hivesqoop_test 数据库,数据库需要预先创建。不指定则默认为 default
--hive-import \ # 导入到 Hive
--hive-overwrite \ # 如果 Hive 表中有数据则覆盖,这会清除表中原有的数据,然后再写入
-m 3 # 并行度
```
导入到Hive中的`sqoop_test`数据库需要预先创建不指定则默认使用Hive中的`default`库。
导入到 Hive 中的 `sqoop_test` 数据库需要预先创建,不指定则默认使用 Hive 中的 `default` 库。
```shell
# 查看hive中的所有数据库
# 查看 hive 中的所有数据库
hive> SHOW DATABASES;
# 创建sqoop_test数据库
# 创建 sqoop_test 数据库
hive> CREATE DATABASE sqoop_test;
```
@ -178,11 +178,11 @@ sqoop import \
<br/>
如果执行报错`java.io.IOException: java.lang.ClassNotFoundException: org.apache.hadoop.hive.conf.HiveConf`则需将Hive安装目录下`lib`下的`hive-exec-**.jar`放到sqoop 的`lib`
如果执行报错 `java.io.IOException: java.lang.ClassNotFoundException: org.apache.hadoop.hive.conf.HiveConf`,则需将 Hive 安装目录下 `lib` 下的 `hive-exec-**.jar` 放到 sqoop 的 `lib`
```shell
[root@hadoop001 lib]# ll hive-exec-*
-rw-r--r--. 1 1106 4001 19632031 11月 13 21:45 hive-exec-1.1.0-cdh5.15.2.jar
-rw-r--r--. 1 1106 4001 19632031 11 13 21:45 hive-exec-1.1.0-cdh5.15.2.jar
[root@hadoop001 lib]# cp hive-exec-1.1.0-cdh5.15.2.jar ${SQOOP_HOME}/lib
```
@ -190,7 +190,7 @@ sqoop import \
### 4.2 Hive 导出数据到MySQL
由于Hive的数据是存储在HDFS上的所以Hive导入数据到MySQL实际上就是HDFS导入数据到MySQL。
由于 Hive 的数据是存储在 HDFS 上的,所以 Hive 导入数据到 MySQL实际上就是 HDFS 导入数据到 MySQL。
#### 1. 查看Hive表在HDFS的存储位置
@ -201,7 +201,7 @@ hive> use sqoop_test;
hive> desc formatted help_keyword;
```
`Location`属性为其存储位置:
`Location` 属性为其存储位置:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/sqoop-hive-location.png"/> </div>
@ -221,7 +221,7 @@ sqoop export \
-input-fields-terminated-by '\001' \ # 需要注意的是 hive 中默认的分隔符为 \001
--m 3
```
MySQL中的表需要预先创建
MySQL 中的表需要预先创建:
```sql
CREATE TABLE help_keyword_from_hive LIKE help_keyword ;
@ -231,13 +231,13 @@ CREATE TABLE help_keyword_from_hive LIKE help_keyword ;
## 五、Sqoop 与 HBase
> 本小节只讲解从RDBMS导入数据到HBase因为暂时没有命令能够从HBase直接导出数据到RDBMS。
> 本小节只讲解从 RDBMS 导入数据到 HBase因为暂时没有命令能够从 HBase 直接导出数据到 RDBMS。
### 5.1 MySQL导入数据到HBase
#### 1. 导入数据
`help_keyword`表中数据导入到HBase上的 `help_keyword_hbase`表中,使用原表的主键`help_keyword_id`作为`RowKey`,原表的所有列都会在`keywordInfo`列族下,目前只支持全部导入到一个列族下,不支持分别指定列族。
`help_keyword` 表中数据导入到 HBase 上的 `help_keyword_hbase` 表中,使用原表的主键 `help_keyword_id` 作为 `RowKey`,原表的所有列都会在 `keywordInfo` 列族下,目前只支持全部导入到一个列族下,不支持分别指定列族。
```shell
sqoop import \
@ -247,10 +247,10 @@ sqoop import \
--table help_keyword \ # 待导入的表
--hbase-table help_keyword_hbase \ # hbase 表名称,表需要预先创建
--column-family keywordInfo \ # 所有列导入到 keywordInfo 列族下
--hbase-row-key help_keyword_id # 使用原表的help_keyword_id作为RowKey
--hbase-row-key help_keyword_id # 使用原表的 help_keyword_id 作为 RowKey
```
导入的HBase表需要预先创建
导入的 HBase 表需要预先创建:
```shell
# 查看所有表
@ -263,7 +263,7 @@ hbase> desc 'help_keyword_hbase'
#### 2. 导入验证
使用`scan`查看表数据:
使用 `scan` 查看表数据:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/sqoop_hbase.png"/> </div>
@ -273,16 +273,16 @@ hbase> desc 'help_keyword_hbase'
## 六、全库导出
Sqoop支持通过`import-all-tables`命令进行全库导出到HDFS/Hive但需要注意有以下两个限制
Sqoop 支持通过 `import-all-tables` 命令进行全库导出到 HDFS/Hive但需要注意有以下两个限制
+ 所有表必须有主键;或者使用`--autoreset-to-one-mapper`,代表只启动一个`map task`;
+ 你不能使用非默认的分割列也不能通过WHERE子句添加任何限制。
+ 所有表必须有主键;或者使用 `--autoreset-to-one-mapper`,代表只启动一个 `map task`;
+ 你不能使用非默认的分割列,也不能通过 WHERE 子句添加任何限制。
> 第二点解释得比较拗口,这里列出官方原本的说明:
>
> + You must not intend to use non-default splitting column, nor impose any conditions via a `WHERE` clause.
全库导出到HDFS
全库导出到 HDFS
```shell
sqoop import-all-tables \
@ -294,14 +294,14 @@ sqoop import-all-tables \
-m 3
```
全库导出到Hive
全库导出到 Hive
```shell
sqoop import-all-tables -Dorg.apache.sqoop.splitter.allow_text_splitter=true \
--connect jdbc:mysql://hadoop001:3306/数据库名 \
--username root \
--password root \
--hive-database sqoop_test \ # 导出到Hive对应的库
--hive-database sqoop_test \ # 导出到 Hive 对应的库
--hive-import \
--hive-overwrite \
-m 3
@ -313,7 +313,7 @@ sqoop import-all-tables -Dorg.apache.sqoop.splitter.allow_text_splitter=true \
### 7.1 query参数
Sqoop支持使用`query`参数定义查询SQL从而可以导出任何想要的结果集。使用示例如下
Sqoop 支持使用 `query` 参数定义查询 SQL从而可以导出任何想要的结果集。使用示例如下
```shell
sqoop import \
@ -323,19 +323,19 @@ sqoop import \
--query 'select * from help_keyword where $CONDITIONS and help_keyword_id < 50' \
--delete-target-dir \
--target-dir /sqoop_hive \
--hive-database sqoop_test \ # 指定导入目标数据库 不指定则默认使用Hive中的default库
--hive-database sqoop_test \ # 指定导入目标数据库 不指定则默认使用 Hive 中的 default
--hive-table filter_help_keyword \ # 指定导入目标表
--split-by help_keyword_id \ # 指定用于split的列
--hive-import \ # 导入到Hive
--split-by help_keyword_id \ # 指定用于 split 的列
--hive-import \ # 导入到 Hive
--hive-overwrite \
-m 3
```
在使用`query`进行数据过滤时,需要注意以下三点:
在使用 `query` 进行数据过滤时,需要注意以下三点:
+ 必须用`--hive-table`指明目标表;
+ 如果并行度`-m`不为1或者没有指定`--autoreset-to-one-mapper`,则需要用` --split-by `指明参考列;
+ SQL`where`字句必须包含`$CONDITIONS`,这是固定写法,作用是动态替换。
+ 必须用 `--hive-table` 指明目标表;
+ 如果并行度 `-m` 不为 1 或者没有指定 `--autoreset-to-one-mapper`,则需要用 ` --split-by ` 指明参考列;
+ SQL`where` 字句必须包含 `$CONDITIONS`,这是固定写法,作用是动态替换。
@ -356,23 +356,23 @@ sqoop import \
-m 3
```
`incremental`参数有以下两个可选的选项:
`incremental` 参数有以下两个可选的选项:
+ **append**:要求参考列的值必须是递增的,所有大于`last-value`的值都会被导入;
+ **lastmodified**:要求参考列的值必须是`timestamp`类型,且插入数据时候要在参考列插入当前时间戳,更新数据时也要更新参考列的时间戳,所有时间晚于``last-value``的数据都会被导入。
+ **append**:要求参考列的值必须是递增的,所有大于 `last-value` 的值都会被导入;
+ **lastmodified**:要求参考列的值必须是 `timestamp` 类型,且插入数据时候要在参考列插入当前时间戳,更新数据时也要更新参考列的时间戳,所有时间晚于 ``last-value`` 的数据都会被导入。
通过上面的解释我们可以看出来其实Sqoop的增量导入并没有太多神器的地方就是依靠维护的参考列来判断哪些是增量数据。当然我们也可以使用上面介绍的`query`参数来进行手动的增量导出,这样反而更加灵活。
通过上面的解释我们可以看出来,其实 Sqoop 的增量导入并没有太多神器的地方,就是依靠维护的参考列来判断哪些是增量数据。当然我们也可以使用上面介绍的 `query` 参数来进行手动的增量导出,这样反而更加灵活。
## 八、类型支持
Sqoop默认支持数据库的大多数字段类型但是某些特殊类型是不支持的。遇到不支持的类型程序会抛出异常`Hive does not support the SQL type for column xxx`异常,此时可以通过下面两个参数进行强制类型转换:
Sqoop 默认支持数据库的大多数字段类型,但是某些特殊类型是不支持的。遇到不支持的类型,程序会抛出异常 `Hive does not support the SQL type for column xxx` 异常,此时可以通过下面两个参数进行强制类型转换:
+ **--map-column-java\<mapping>** 重写SQLJava类型的映射
+ **--map-column-hive \<mapping>** 重写HiveJava类型的映射。
+ **--map-column-java\<mapping>** :重写 SQLJava 类型的映射;
+ **--map-column-hive \<mapping>** 重写 HiveJava 类型的映射。
示例如下,将原先`id`字段强制转为String类型`value`字段强制转为Integer类型
示例如下,将原先 `id` 字段强制转为 String 类型,`value` 字段强制转为 Integer 类型:
```
$ sqoop import ... --map-column-java id=String,value=Integer

View File

@ -13,9 +13,9 @@
## 一、Sqoop 简介
Sqoop是一个常用的数据迁移工具主要用于在不同存储系统之间实现数据的导入与导出
Sqoop 是一个常用的数据迁移工具,主要用于在不同存储系统之间实现数据的导入与导出:
+ 导入数据从MySQLOracle等关系型数据库中导入数据到HDFS、Hive、HBase等分布式文件存储系统中
+ 导入数据:从 MySQLOracle 等关系型数据库中导入数据到 HDFS、Hive、HBase 等分布式文件存储系统中;
+ 导出数据:从 分布式文件系统中导出数据到关系数据库中。
@ -25,7 +25,7 @@ Sqoop是一个常用的数据迁移工具主要用于在不同存储系统之
## 二、安装
版本选择目前SqoopSqoop 1Sqoop 2两个版本但是截至到目前官方并不推荐使用Sqoop 2因为其与Sqoop 1并不兼容且功能还没有完善所以这里优先推荐使用Sqoop 1。
版本选择:目前 SqoopSqoop 1Sqoop 2 两个版本,但是截至到目前,官方并不推荐使用 Sqoop 2因为其与 Sqoop 1 并不兼容,且功能还没有完善,所以这里优先推荐使用 Sqoop 1。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/sqoop-version-selected.png"/> </div>
@ -33,7 +33,7 @@ Sqoop是一个常用的数据迁移工具主要用于在不同存储系统之
### 2.1 下载并解压
下载所需版本的Sqoop ,这里我下载的是`CDH`版本的Sqoop 。下载地址为http://archive.cloudera.com/cdh5/cdh/5/
下载所需版本的 Sqoop ,这里我下载的是 `CDH` 版本的 Sqoop 。下载地址为http://archive.cloudera.com/cdh5/cdh/5/
```shell
# 下载后进行解压
@ -61,13 +61,13 @@ export PATH=$SQOOP_HOME/bin:$PATH
### 2.3 修改配置
进入安装目录下的`conf/`目录拷贝Sqoop的环境配置模板`sqoop-env.sh.template`
进入安装目录下的 `conf/` 目录,拷贝 Sqoop 的环境配置模板 `sqoop-env.sh.template`
```shell
# cp sqoop-env-template.sh sqoop-env.sh
```
修改`sqoop-env.sh`,内容如下(以下配置中`HADOOP_COMMON_HOME``HADOOP_MAPRED_HOME`是必选的,其他的是可选的)
修改 `sqoop-env.sh`,内容如下 (以下配置中 `HADOOP_COMMON_HOME``HADOOP_MAPRED_HOME` 是必选的,其他的是可选的)
```shell
# Set Hadoop-specific environment variables here.
@ -90,7 +90,7 @@ export ZOOCFGDIR=/usr/app/zookeeper-3.4.13/conf
### 2.4 拷贝数据库驱动
将MySQL驱动包拷贝到Sqoop安装目录的`lib`目录下, 驱动包的下载地址为https://dev.mysql.com/downloads/connector/j/ 。在本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources)目录下我也上传了一份,有需要的话可以自行下载。
MySQL 驱动包拷贝到 Sqoop 安装目录的 `lib` 目录下, 驱动包的下载地址为 https://dev.mysql.com/downloads/connector/j/ 。在本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources) 目录下我也上传了一份,有需要的话可以自行下载。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/sqoop-mysql-jar.png"/> </div>
@ -98,7 +98,7 @@ export ZOOCFGDIR=/usr/app/zookeeper-3.4.13/conf
### 2.5 验证
由于已经将sqoop`bin`目录配置到环境变量,直接使用以下命令验证是否配置成功:
由于已经将 sqoop`bin` 目录配置到环境变量,直接使用以下命令验证是否配置成功:
```shell
# sqoop version
@ -108,7 +108,7 @@ export ZOOCFGDIR=/usr/app/zookeeper-3.4.13/conf
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/sqoop-version.png"/> </div>
这里出现的两个`Warning`警告是因为我们本身就没有用到`HCatalog``Accumulo`忽略即可。Sqoop在启动时会去检查环境变量中是否有配置这些软件如果想去除这些警告可以修改`bin/configure-sqoop`,注释掉不必要的检查。
这里出现的两个 `Warning` 警告是因为我们本身就没有用到 `HCatalog``Accumulo`忽略即可。Sqoop 在启动时会去检查环境变量中是否有配置这些软件,如果想去除这些警告,可以修改 `bin/configure-sqoop`,注释掉不必要的检查。
```shell
# Check: If we can't find our dependencies, give up here.

View File

@ -12,11 +12,11 @@
## 一、简介
在将Storm Topology提交到服务器集群运行时需要先将项目进行打包。本文主要对比分析各种打包方式并将打包过程中需要注意的事项进行说明。主要打包方式有以下三种
在将 Storm Topology 提交到服务器集群运行时,需要先将项目进行打包。本文主要对比分析各种打包方式,并将打包过程中需要注意的事项进行说明。主要打包方式有以下三种:
+ 第一种不加任何插件直接使用mvn package打包
+ 第二种使用maven-assembly-plugin插件进行打包
+ 第三种使用maven-shade-plugin进行打包。
+ 第一种:不加任何插件,直接使用 mvn package 打包;
+ 第二种:使用 maven-assembly-plugin 插件进行打包;
+ 第三种:使用 maven-shade-plugin 进行打包。
以下分别进行详细的说明。
@ -26,19 +26,19 @@
### 2.1 mvn package的局限
不在POM中配置任何插件直接使用`mvn package`进行项目打包,这对于没有使用外部依赖包的项目是可行的。
不在 POM 中配置任何插件,直接使用 `mvn package` 进行项目打包,这对于没有使用外部依赖包的项目是可行的。
但如果项目中使用了第三方JAR包就会出现问题因为`mvn package`打包后的JAR中是不含有依赖包的如果此时你提交到服务器上运行就会出现找不到第三方依赖的异常。
但如果项目中使用了第三方 JAR 包,就会出现问题,因为 `mvn package` 打包后的 JAR 中是不含有依赖包的,如果此时你提交到服务器上运行,就会出现找不到第三方依赖的异常。
如果你想采用这种方式进行打包但是又使用了第三方JAR有没有解决办法答案是有的这一点在官方文档的[Command Line Client](http://storm.apache.org/releases/2.0.0-SNAPSHOT/Command-line-client.html)章节有所讲解,主要解决办法如下。
如果你想采用这种方式进行打包,但是又使用了第三方 JAR有没有解决办法答案是有的这一点在官方文档的[Command Line Client](http://storm.apache.org/releases/2.0.0-SNAPSHOT/Command-line-client.html) 章节有所讲解,主要解决办法如下。
### 2.2 解决办法
在使用`storm jar`提交Topology时可以使用如下方式指定第三方依赖
在使用 `storm jar` 提交 Topology 时,可以使用如下方式指定第三方依赖:
+ 如果第三方JAR包在本地可以使用`--jars`指定;
+ 如果第三方JAR包在远程中央仓库可以使用`--artifacts` 指定,此时如果想要排除某些依赖,可以使用 `^` 符号。指定后Storm会自动到中央仓库进行下载然后缓存到本地
+ 如果第三方JAR包在其他仓库还需要使用 `--artifactRepositories`指明仓库地址,库名和地址使用 `^` 符号分隔。
+ 如果第三方 JAR 包在本地,可以使用 `--jars` 指定;
+ 如果第三方 JAR 包在远程中央仓库,可以使用 `--artifacts` 指定,此时如果想要排除某些依赖,可以使用 `^` 符号。指定后 Storm 会自动到中央仓库进行下载,然后缓存到本地;
+ 如果第三方 JAR 包在其他仓库,还需要使用 `--artifactRepositories` 指明仓库地址,库名和地址使用 `^` 符号分隔。
以下是一个包含上面三种情况的命令示例:
@ -51,11 +51,11 @@ org.apache.storm.starter.RollingTopWords blobstore-remote2 remote \
HDPRepo^http://repo.hortonworks.com/content/groups/public/"
```
这种方式是建立在你能够连接到外网的情况下,如果你的服务器不能连接外网,或者你希望能把项目直接打包成一个`ALL IN ONE`JAR即包含所有相关依赖此时可以采用下面介绍的两个插件。
这种方式是建立在你能够连接到外网的情况下,如果你的服务器不能连接外网,或者你希望能把项目直接打包成一个 `ALL IN ONE`JAR即包含所有相关依赖此时可以采用下面介绍的两个插件。
## 三、maven-assembly-plugin插件
maven-assembly-plugin是官方文档中介绍的打包方法来源于官方文档[Running Topologies on a Production Cluster](http://storm.apache.org/releases/2.0.0-SNAPSHOT/Running-topologies-on-a-production-cluster.html)
maven-assembly-plugin 是官方文档中介绍的打包方法,来源于官方文档:[Running Topologies on a Production Cluster](http://storm.apache.org/releases/2.0.0-SNAPSHOT/Running-topologies-on-a-production-cluster.html)
> If you're using Maven, the [Maven Assembly Plugin](http://maven.apache.org/plugins/maven-assembly-plugin/) can do the packaging for you. Just add this to your pom.xml:
>
@ -79,12 +79,12 @@ maven-assembly-plugin是官方文档中介绍的打包方法来源于官方
官方文档主要说明了以下几点:
- 使用maven-assembly-plugin可以把所有的依赖一并打入到最后的JAR中
- 需要排除掉Storm集群环境中已经提供的Storm jars
- 通过` <mainClass>`标签指定主入口类;
- 通过`<descriptorRef>`标签指定打包相关配置。
- 使用 maven-assembly-plugin 可以把所有的依赖一并打入到最后的 JAR 中;
- 需要排除掉 Storm 集群环境中已经提供的 Storm jars
- 通过 ` <mainClass>` 标签指定主入口类;
- 通过 `<descriptorRef>` 标签指定打包相关配置。
`jar-with-dependencies`Maven[预定义](http://maven.apache.org/plugins/maven-assembly-plugin/descriptor-refs.html#jar-with-dependencies)的一种最基本的打包配置其XML文件如下
`jar-with-dependencies`Maven[预定义](http://maven.apache.org/plugins/maven-assembly-plugin/descriptor-refs.html#jar-with-dependencies) 的一种最基本的打包配置,其 XML 文件如下:
```xml
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
@ -107,11 +107,11 @@ maven-assembly-plugin是官方文档中介绍的打包方法来源于官方
</assembly>
```
我们可以通过对该配置文件进行拓展从而实现更多的功能比如排除指定的JAR等。使用示例如下
我们可以通过对该配置文件进行拓展,从而实现更多的功能,比如排除指定的 JAR 等。使用示例如下:
### 1. 引入插件
在POM.xml中引入插件并指定打包格式的配置文件为`assembly.xml`(名称可自定义)
POM.xml 中引入插件,并指定打包格式的配置文件为 `assembly.xml`(名称可自定义)
```xml
<build>
@ -133,7 +133,7 @@ maven-assembly-plugin是官方文档中介绍的打包方法来源于官方
</build>
```
`assembly.xml`拓展自`jar-with-dependencies.xml`,使用了`<excludes>`标签排除Storm jars具体内容如下
`assembly.xml` 拓展自 `jar-with-dependencies.xml`,使用了 `<excludes>` 标签排除 Storm jars具体内容如下
```xml
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
@ -155,7 +155,7 @@ maven-assembly-plugin是官方文档中介绍的打包方法来源于官方
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
<!--排除storm环境中已经提供的storm-core-->
<!--排除 storm 环境中已经提供的 storm-core-->
<excludes>
<exclude>org.apache.storm:storm-core</exclude>
</excludes>
@ -168,13 +168,13 @@ maven-assembly-plugin是官方文档中介绍的打包方法来源于官方
### 2. 打包命令
采用maven-assembly-plugin进行打包时命令如下
采用 maven-assembly-plugin 进行打包时命令如下:
```shell
# mvn assembly:assembly
```
打包后会同时生成两个JAR包其中后缀为`jar-with-dependencies`是含有第三方依赖的JAR包后缀是由`assembly.xml``<id>`标签指定的可以自定义修改。提交该JAR到集群环境即可直接使用。
打包后会同时生成两个 JAR 包,其中后缀为 `jar-with-dependencies` 是含有第三方依赖的 JAR 包,后缀是由 `assembly.xml``<id>` 标签指定的,可以自定义修改。提交该 JAR 到集群环境即可直接使用。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-jar.png"/> </div>
@ -184,7 +184,7 @@ maven-assembly-plugin是官方文档中介绍的打包方法来源于官方
### 4.1 官方文档说明
第三种方式是使用maven-shade-plugin既然已经有了maven-assembly-plugin为什么还需要maven-shade-plugin这一点在官方文档中也是有所说明的来自于官方对HDFS整合讲解的章节[Storm HDFS Integration](http://storm.apache.org/releases/2.0.0-SNAPSHOT/storm-hdfs.html),原文如下:
第三种方式是使用 maven-shade-plugin既然已经有了 maven-assembly-plugin为什么还需要 maven-shade-plugin这一点在官方文档中也是有所说明的来自于官方对 HDFS 整合讲解的章节[Storm HDFS Integration](http://storm.apache.org/releases/2.0.0-SNAPSHOT/storm-hdfs.html),原文如下:
>When packaging your topology, it's important that you use the [maven-shade-plugin](http://storm.apache.org/releases/2.0.0-SNAPSHOT/storm-hdfs.html) as opposed to the [maven-assembly-plugin](http://storm.apache.org/releases/2.0.0-SNAPSHOT/storm-hdfs.html).
>
@ -200,13 +200,13 @@ maven-assembly-plugin是官方文档中介绍的打包方法来源于官方
>
>If you are using maven to create your topology jar, you should use the following `maven-shade-plugin` configuration to create your topology jar。
这里第一句就说的比较清晰在集成HDFS时候你必须使用maven-shade-plugin来代替maven-assembly-plugin否则会抛出RuntimeException异常。
这里第一句就说的比较清晰,在集成 HDFS 时候,你必须使用 maven-shade-plugin 来代替 maven-assembly-plugin否则会抛出 RuntimeException 异常。
采用maven-shade-plugin打包有很多好处比如你的工程依赖很多的JAR包而被依赖的JAR又会依赖其他的JAR包这样,当工程中依赖到不同的版本的 JAR时并且JAR中具有相同名称的资源文件时shade插件会尝试将所有资源文件打包在一起时而不是和assembly一样执行覆盖操作。
采用 maven-shade-plugin 打包有很多好处,比如你的工程依赖很多的 JAR 包,而被依赖的 JAR 又会依赖其他的 JAR 包,这样,当工程中依赖到不同的版本的 JAR 时,并且 JAR 中具有相同名称的资源文件时shade 插件会尝试将所有资源文件打包在一起时,而不是和 assembly 一样执行覆盖操作。
### 4.2 配置
采用`maven-shade-plugin`进行打包时候,配置示例如下:
采用 `maven-shade-plugin` 进行打包时候,配置示例如下:
```xml
<plugin>
@ -257,42 +257,42 @@ maven-assembly-plugin是官方文档中介绍的打包方法来源于官方
</plugin>
```
以上配置示例来源于Storm Github这里做一下说明
以上配置示例来源于 Storm Github这里做一下说明
在上面的配置中排除了部分文件这是因为有些JAR包生成时会使用jarsigner生成文件签名完成性校验分为两个文件存放在META-INF目录下
在上面的配置中,排除了部分文件,这是因为有些 JAR 包生成时,会使用 jarsigner 生成文件签名(完成性校验),分为两个文件存放在 META-INF 目录下:
+ a signature file, with a .SF extension
+ a signature block file, with a .DSA, .RSA, or .EC extension
如果某些包的存在重复引用,这可能会导致在打包时候出现`Invalid signature file digest for Manifest main attributes`异常,所以在配置中排除这些文件。
如果某些包的存在重复引用,这可能会导致在打包时候出现 `Invalid signature file digest for Manifest main attributes` 异常,所以在配置中排除这些文件。
### 4.3 打包命令
使用maven-shade-plugin进行打包的时候打包命令和普通的一样
使用 maven-shade-plugin 进行打包的时候,打包命令和普通的一样:
```shell
# mvn package
```
打包后会生成两个JAR包提交到服务器集群时使用`original`开头的JAR。
打包后会生成两个 JAR 包,提交到服务器集群时使用 `original` 开头的 JAR。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-jar2.png"/> </div>
## 五、结论
通过以上三种打包方式的详细介绍,这里给出最后的结论:**建议使用maven-shade-plugin插件进行打包**因为其通用性最强操作最简单并且Storm Github中所有[examples](https://github.com/apache/storm/tree/master/examples)都是采用该方式进行打包。
通过以上三种打包方式的详细介绍,这里给出最后的结论:**建议使用 maven-shade-plugin 插件进行打包**,因为其通用性最强,操作最简单,并且 Storm Github 中所有[examples](https://github.com/apache/storm/tree/master/examples) 都是采用该方式进行打包。
## 六、打包注意事项
无论采用任何打包方式都必须排除集群环境中已经提供的storm jars。这里比较典型的是storm-core其在安装目录的lib目录下已经存在。
无论采用任何打包方式,都必须排除集群环境中已经提供的 storm jars。这里比较典型的是 storm-core其在安装目录的 lib 目录下已经存在。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-lib.png"/> </div>
如果你不排除storm-core通常会抛出下面的异常
如果你不排除 storm-core通常会抛出下面的异常
```properties
Caused by: java.lang.RuntimeException: java.io.IOException: Found multiple defaults.yaml resources.
@ -312,4 +312,4 @@ jar:file:/usr/appjar/storm-hdfs-integration-1.0.jar!/defaults.yaml]
## 参考资料
关于maven-shade-plugin的更多配置可以参考 [maven-shade-plugin 入门指南](https://www.jianshu.com/p/7a0e20b30401)
关于 maven-shade-plugin 的更多配置可以参考: [maven-shade-plugin 入门指南](https://www.jianshu.com/p/7a0e20b30401)

View File

@ -16,11 +16,11 @@
#### 1.1 简介
Storm 是一个开源的分布式实时计算框架可以以简单、可靠的方式进行大数据流的处理。通常用于实时分析在线机器学习、持续计算、分布式RPC、ETL等场景。Storm具有以下特点
Storm 是一个开源的分布式实时计算框架,可以以简单、可靠的方式进行大数据流的处理。通常用于实时分析,在线机器学习、持续计算、分布式 RPC、ETL 等场景。Storm 具有以下特点:
+ 支持水平横向扩展;
+ 具有高容错性通过ACK机制每个消息都不丢失
+ 处理速度非常快每个节点每秒能处理超过一百万个tuples
+ 具有高容错性,通过 ACK 机制每个消息都不丢失;
+ 处理速度非常快,每个节点每秒能处理超过一百万个 tuples
+ 易于设置和操作,并可以与任何编程语言一起使用;
+ 支持本地模式运行,对于开发人员来说非常友好;
+ 支持图形化管理界面。
@ -29,30 +29,30 @@ Storm 是一个开源的分布式实时计算框架,可以以简单、可靠
#### 1.2 Storm 与 Hadoop对比
Hadoop采用MapReduce处理数据而MapReduce主要是对数据进行批处理这使得Hadoop更适合于海量数据离线处理的场景。而Strom的设计目标是对数据进行实时计算这使得其更适合实时数据分析的场景。
Hadoop 采用 MapReduce 处理数据,而 MapReduce 主要是对数据进行批处理,这使得 Hadoop 更适合于海量数据离线处理的场景。而 Strom 的设计目标是对数据进行实时计算,这使得其更适合实时数据分析的场景。
#### 1.3 Storm 与 Spark Streaming对比
Spark Streaming并不是真正意义上的流处理框架。 Spark Streaming接收实时输入的数据流并将数据拆分为一系列批次然后进行微批处理。只不过 Spark Streaming 能够将数据流进行极小粒度的拆分,使得其能够得到接近于流处理的效果,但其本质上还是批处理(或微批处理)。
Spark Streaming 并不是真正意义上的流处理框架。 Spark Streaming 接收实时输入的数据流,并将数据拆分为一系列批次,然后进行微批处理。只不过 Spark Streaming 能够将数据流进行极小粒度的拆分,使得其能够得到接近于流处理的效果,但其本质上还是批处理(或微批处理)。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/streaming-flow.png"/> </div>
#### 1.4 Strom 与 Flink对比
stormFlink都是真正意义上的实时计算框架。其对比如下
stormFlink 都是真正意义上的实时计算框架。其对比如下:
| | storm | flink |
| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 状态管理 | 无状态 | 有状态 |
| 窗口支持 | 对事件窗口支持较弱,缓存整个窗口的所有数据,窗口结束时一起计算 | 窗口支持较为完善,自带一些窗口聚合方法,<br>并且会自动管理窗口状态 |
| 消息投递 | At Most Once<br/>At Least Once | At Most Once<br/>At Least Once<br/>**Exactly Once** |
| 容错方式 | ACK机制对每个消息进行全链路跟踪失败或者超时时候进行重发 | 检查点机制:通过分布式一致性快照机制,<br/>对数据流和算子状态进行保存。在发生错误时,使系统能够进行回滚。 |
| 容错方式 | ACK 机制:对每个消息进行全链路跟踪,失败或者超时时候进行重发 | 检查点机制:通过分布式一致性快照机制,<br/>对数据流和算子状态进行保存。在发生错误时,使系统能够进行回滚。 |
> 注 : 对于消息投递,一般有以下三种方案:
> + At Most Once : 保证每个消息会被投递0次或者1次,在这种机制下消息很有可能会丢失;
> + At Most Once : 保证每个消息会被投递 0 次或者 1 次,在这种机制下消息很有可能会丢失;
> + At Least Once : 保证了每个消息会被默认投递多次,至少保证有一次被成功接收,信息可能有重复,但是不会丢失;
> + Exactly Once : 每个消息对于接收者而言正好被接收一次,保证即不会丢失也不会重复。
@ -62,7 +62,7 @@ storm和Flink都是真正意义上的实时计算框架。其对比如下
#### 2.1 静态数据处理
在流处理之前数据通常存储在数据库或文件系统中应用程序根据需要查询或计算数据这就是传统的静态数据处理架构。Hadoop采用HDFS进行数据存储采用MapReduce进行数据查询或分析这就是典型的静态数据处理架构。
在流处理之前数据通常存储在数据库或文件系统中应用程序根据需要查询或计算数据这就是传统的静态数据处理架构。Hadoop 采用 HDFS 进行数据存储,采用 MapReduce 进行数据查询或分析,这就是典型的静态数据处理架构。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/01_data_at_rest_infrastructure.png"/> </div>
@ -72,7 +72,7 @@ storm和Flink都是真正意义上的实时计算框架。其对比如下
而流处理则是直接对运动中数据的处理,在接收数据的同时直接计算数据。实际上,在真实世界中的大多数数据都是连续的流,如传感器数据,网站用户活动数据,金融交易数据等等 ,所有这些数据都是随着时间的推移而源源不断地产生。
接收和发送数据流并执行应用程序或分析逻辑的系统称为**流处理器**。流处理器的基本职责是确保数据有效流动同时具备可扩展性和容错能力StormFlink就是其代表性的实现。
接收和发送数据流并执行应用程序或分析逻辑的系统称为**流处理器**。流处理器的基本职责是确保数据有效流动同时具备可扩展性和容错能力StormFlink 就是其代表性的实现。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/02_stream_processing_infrastructure.png"/> </div>
@ -95,4 +95,4 @@ storm和Flink都是真正意义上的实时计算框架。其对比如下
## 参考资料
1. [What is stream processing?](https://www.ververica.com/what-is-stream-processing)
2. [流计算框架FlinkStorm的性能对比](http://bigdata.51cto.com/art/201711/558416.htm)
2. [流计算框架 FlinkStorm 的性能对比](http://bigdata.51cto.com/art/201711/558416.htm)

View File

@ -22,59 +22,59 @@
### 1.1 Topologies拓扑
一个完整的Storm流处理程序被称为Storm topology(拓扑)。它是一个是由`Spouts``Bolts`通过`Stream`连接起来的有向无环图Storm会保持每个提交到集群的topology持续地运行从而处理源源不断的数据流直到你将主动其杀死(kill)为止。
一个完整的 Storm 流处理程序被称为 Storm topology(拓扑)。它是一个是由 `Spouts` `Bolts` 通过 `Stream` 连接起来的有向无环图Storm 会保持每个提交到集群的 topology 持续地运行,从而处理源源不断的数据流,直到你将主动其杀死 (kill) 为止。
### 1.2 Streams
`Stream`Storm中的核心概念。一个`Stream`是一个无界的、以分布式方式并行创建和处理的`Tuple`序列。Tuple可以包含大多数基本类型以及自定义类型的数据。简单来说Tuple就是流数据的实际载体而Stream就是一系列Tuple。
`Stream`Storm 中的核心概念。一个 `Stream` 是一个无界的、以分布式方式并行创建和处理的 `Tuple` 序列。Tuple 可以包含大多数基本类型以及自定义类型的数据。简单来说Tuple 就是流数据的实际载体,而 Stream 就是一系列 Tuple。
### 1.3 Spouts
`Spouts`是流数据的源头一个Spout 可以向不止一个`Streams`中发送数据。`Spout`通常分为**可靠**和**不可靠**两种:可靠的` Spout`能够在失败时重新发送 Tuple, 不可靠的`Spout`一旦把Tuple 发送出去就置之不理了。
`Spouts` 是流数据的源头,一个 Spout 可以向不止一个 `Streams` 中发送数据。`Spout` 通常分为**可靠**和**不可靠**两种:可靠的 ` Spout` 能够在失败时重新发送 Tuple, 不可靠的 `Spout` 一旦把 Tuple 发送出去就置之不理了。
### 1.4 Bolts
`Bolts`是流数据的处理单元,它可以从一个或者多个`Streams`中接收数据,处理完成后再发射到新的`Streams`中。`Bolts`可以执行过滤(filtering),聚合(aggregations),连接(joins)等操作,并能与文件系统或数据库进行交互。
`Bolts` 是流数据的处理单元,它可以从一个或者多个 `Streams` 中接收数据,处理完成后再发射到新的 `Streams` 中。`Bolts` 可以执行过滤 (filtering),聚合 (aggregations),连接 (joins) 等操作,并能与文件系统或数据库进行交互。
### 1.5 Stream groupings分组策略
<div align="center"> <img width="400px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/topology-tasks.png"/> </div>
`spouts``bolts`在集群上执行任务时是由多个Task并行执行(如上图每一个圆圈代表一个Task)。当一个Tuple需要从Bolt A发送给Bolt B执行的时候程序如何知道应该发送给Bolt B的哪一个Task执行呢
`spouts``bolts` 在集群上执行任务时,是由多个 Task 并行执行 (如上图,每一个圆圈代表一个 Task)。当一个 Tuple 需要从 Bolt A 发送给 Bolt B 执行的时候,程序如何知道应该发送给 Bolt B 的哪一个 Task 执行呢?
这是由Stream groupings分组策略来决定的Storm中一共有如下8个内置的Stream Grouping。当然你也可以通过实现 `CustomStreamGrouping`接口来实现自定义Stream分组策略。
这是由 Stream groupings 分组策略来决定的Storm 中一共有如下 8 个内置的 Stream Grouping。当然你也可以通过实现 `CustomStreamGrouping` 接口来实现自定义 Stream 分组策略。
1. **Shuffle grouping**
Tuples随机的分发到每个Bolt的每个Task上每个Bolt获取到等量的Tuples。
Tuples 随机的分发到每个 Bolt 的每个 Task 上,每个 Bolt 获取到等量的 Tuples。
2. **Fields grouping**
Streams通过grouping指定的字段(field)来分组。假设通过`user-id`字段进行分区,那么具有相同`user-id`Tuples就会发送到同一个Task。
Streams 通过 grouping 指定的字段 (field) 来分组。假设通过 `user-id` 字段进行分区,那么具有相同 `user-id`Tuples 就会发送到同一个 Task。
3. **Partial Key grouping**
Streams通过grouping中指定的字段(field)来分组,与`Fields Grouping`相似。但是对于两个下游的Bolt来说是负载均衡的可以在输入数据不平均的情况下提供更好的优化。
Streams 通过 grouping 中指定的字段 (field) 来分组,与 `Fields Grouping` 相似。但是对于两个下游的 Bolt 来说是负载均衡的,可以在输入数据不平均的情况下提供更好的优化。
4. **All grouping**
Streams会被所有的BoltTasks进行复制。由于存在数据重复处理所以需要谨慎使用。
Streams 会被所有的 BoltTasks 进行复制。由于存在数据重复处理,所以需要谨慎使用。
5. **Global grouping**
整个Streams会进入Bolt的其中一个Task通常会进入id最小的Task。
整个 Streams 会进入 Bolt 的其中一个 Task通常会进入 id 最小的 Task。
6. **None grouping**
当前None grouping 和Shuffle grouping等价都是进行随机分发。
当前 None grouping 和 Shuffle grouping 等价,都是进行随机分发。
7. **Direct grouping**
Direct grouping只能被用于direct streams 。使用这种方式需要由Tuple的生产者直接指定由哪个Task进行处理。
Direct grouping 只能被用于 direct streams 。使用这种方式需要由 Tuple 的生产者直接指定由哪个 Task 进行处理。
8. **Local or shuffle grouping**
如果目标BoltTasks和当前BoltTasks处在同一个Worker进程中那么则优先将Tuple Shuffled到处于同一个进程的目标BoltTasks上这样可以最大限度地减少网络传输。否则就和普通的`Shuffle Grouping`行为一致。
如果目标 BoltTasks 和当前 BoltTasks 处在同一个 Worker 进程中,那么则优先将 Tuple Shuffled 到处于同一个进程的目标 BoltTasks 上,这样可以最大限度地减少网络传输。否则,就和普通的 `Shuffle Grouping` 行为一致。
@ -84,49 +84,49 @@
### 2.1 Nimbus进程
也叫做Master Node是Storm集群工作的全局指挥官。主要功能如下
也叫做 Master Node Storm 集群工作的全局指挥官。主要功能如下:
1. 通过Thrift接口监听并接收Client提交的Topology
2. 根据集群Workers的资源情况将Client提交的Topology进行任务分配分配结果写入Zookeeper;
3. 通过Thrift接口监听Supervisor的下载Topology代码的请求并提供下载 ;
4. 通过Thrift接口监听UI对统计信息的读取从Zookeeper上读取统计信息返回给UI;
1. 通过 Thrift 接口,监听并接收 Client 提交的 Topology
2. 根据集群 Workers 的资源情况,将 Client 提交的 Topology 进行任务分配,分配结果写入 Zookeeper;
3. 通过 Thrift 接口,监听 Supervisor 的下载 Topology 代码的请求,并提供下载 ;
4. 通过 Thrift 接口,监听 UI 对统计信息的读取,从 Zookeeper 上读取统计信息,返回给 UI;
5. 若进程退出后,立即在本机重启,则不影响集群运行。
### 2.2 Supervisor进程
也叫做Worker Node , 是Storm集群的资源管理者按需启动Worker进程。主要功能如下
也叫做 Worker Node , 是 Storm 集群的资源管理者,按需启动 Worker 进程。主要功能如下:
1. 定时从Zookeeper检查是否有新Topology代码未下载到本地 并定时删除旧Topology代码 ;
2. 根据Nimbus的任务分配计划在本机按需启动1个或多个Worker进程并监控所有的Worker进程的情况
1. 定时从 Zookeeper 检查是否有新 Topology 代码未下载到本地 ,并定时删除旧 Topology 代码 ;
2. 根据 Nimbus 的任务分配计划,在本机按需启动 1 个或多个 Worker 进程,并监控所有的 Worker 进程的情况;
3. 若进程退出,立即在本机重启,则不影响集群运行。
### 2.3 zookeeper的作用
NimbusSupervisor进程都被设计为**快速失败**(遇到任何意外情况时进程自毁)和**无状态**所有状态保存在Zookeeper或磁盘上。 这样设计的好处就是如果它们的进程被意外销毁那么在重新启动后就只需要从Zookeeper上获取之前的状态数据即可并不会造成任何数据丢失。
NimbusSupervisor 进程都被设计为**快速失败**(遇到任何意外情况时进程自毁)和**无状态**(所有状态保存在 Zookeeper 或磁盘上)。 这样设计的好处就是如果它们的进程被意外销毁,那么在重新启动后,就只需要从 Zookeeper 上获取之前的状态数据即可,并不会造成任何数据丢失。
### 2.4 Worker进程
Storm集群的任务构造者 构造SpoultBoltTask实例启动Executor线程。主要功能如下
Storm 集群的任务构造者 ,构造 SpoultBoltTask 实例,启动 Executor 线程。主要功能如下:
1. 根据Zookeeper上分配的Task在本进程中启动1个或多个Executor线程将构造好的Task实例交给Executor去运行
2. 向Zookeeper写入心跳
3. 维持传输队列发送Tuple到其他的Worker
1. 根据 Zookeeper 上分配的 Task在本进程中启动 1 个或多个 Executor 线程,将构造好的 Task 实例交给 Executor 去运行;
2. Zookeeper 写入心跳
3. 维持传输队列,发送 Tuple 到其他的 Worker
4. 若进程退出,立即在本机重启,则不影响集群运行。
### 2.5 Executor线程
Storm集群的任务执行者 循环执行Task代码。主要功能如下
Storm 集群的任务执行者 ,循环执行 Task 代码。主要功能如下:
1. 执行1个或多个Task
2. 执行Acker机制负责发送Task处理状态给对应Spout所在的worker。
1. 执行 1 个或多个 Task
2. 执行 Acker 机制,负责发送 Task 处理状态给对应 Spout 所在的 worker。
@ -134,18 +134,18 @@ Storm集群的任务执行者 循环执行Task代码。主要功能如下
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/relationships-worker-processes-executors-tasks.png"/> </div>
1Worker进程执行的是1个Topology的子集不会出现1个Worker为多个Topology服务的情况因此1个运行中的Topology就是由集群中多台物理机上的多个Worker进程组成的。1Worker进程会启动1个或多个Executor线程来执行1个TopologyComponent(组件即SpoutBolt)。
1Worker 进程执行的是 1 个 Topology 的子集,不会出现 1 个 Worker 为多个 Topology 服务的情况,因此 1 个运行中的 Topology 就是由集群中多台物理机上的多个 Worker 进程组成的。1Worker 进程会启动 1 个或多个 Executor 线程来执行 1 个 TopologyComponent(组件,即 SpoutBolt)。
Executor是1个被Worker进程启动的单独线程。每个Executor会运行1个Component中的一个或者多个Task。
Executor 是 1 个被 Worker 进程启动的单独线程。每个 Executor 会运行 1 个 Component 中的一个或者多个 Task。
Task是组成Component的代码单元。Topology启动后1ComponentTask数目是固定不变的但该Component使用的Executor线程数可以动态调整例如1Executor线程可以执行该Component的1个或多个Task实例。这意味着对于1个Component来说`#threads<=#tasks`线程数小于等于Task数目这样的情况是存在的。默认情况下Task的数目等于Executor线程数1个Executor线程只运行1个Task。
Task 是组成 Component 的代码单元。Topology 启动后1ComponentTask 数目是固定不变的,但该 Component 使用的 Executor 线程数可以动态调整例如1Executor 线程可以执行该 Component 的 1 个或多个 Task 实例)。这意味着,对于 1 个 Component 来说,`#threads<=#tasks`(线程数小于等于 Task 数目)这样的情况是存在的。默认情况下 Task 的数目等于 Executor 线程数,即 1 个 Executor 线程只运行 1 个 Task。
**总结如下:**
+ 一个运行中的Topology由集群中的多个Worker进程组成的
+ 在默认情况下每个Worker进程默认启动一个Executor线程
+ 在默认情况下每个Executor默认启动一个Task线程
+ Task是组成Component的代码单元。
+ 一个运行中的 Topology 由集群中的多个 Worker 进程组成的;
+ 在默认情况下,每个 Worker 进程默认启动一个 Executor 线程;
+ 在默认情况下,每个 Executor 默认启动一个 Task 线程;
+ Task 是组成 Component 的代码单元。

View File

@ -20,20 +20,20 @@
## 一、简介
下图为Strom的运行流程图在开发Storm流处理程序时我们需要采用内置或自定义实现`spout`(数据源)`bolt`(处理单元),并通过`TopologyBuilder`将它们之间进行关联,形成`Topology`
下图为 Strom 的运行流程图,在开发 Storm 流处理程序时,我们需要采用内置或自定义实现 `spout`(数据源)`bolt`(处理单元),并通过 `TopologyBuilder` 将它们之间进行关联,形成 `Topology`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spout-bolt.png"/> </div>
## 二、IComponent接口
`IComponent`接口定义了Topology中所有组件(spout/bolt)的公共方法自定义的spoutbolt必须直接或间接实现这个接口。
`IComponent` 接口定义了 Topology 中所有组件 (spout/bolt) 的公共方法,自定义的 spoutbolt 必须直接或间接实现这个接口。
```java
public interface IComponent extends Serializable {
/**
* 声明此拓扑的所有流的输出模式。
* @param declarer这用于声明输出流id输出字段以及每个输出流是否是直接流direct stream
* @param declarer 这用于声明输出流 id输出字段以及每个输出流是否是直接流direct stream
*/
void declareOutputFields(OutputFieldsDeclarer declarer);
@ -50,48 +50,48 @@ public interface IComponent extends Serializable {
### 3.1 ISpout接口
自定义的spout需要实现`ISpout`接口它定义了spout的所有可用方法
自定义的 spout 需要实现 `ISpout` 接口,它定义了 spout 的所有可用方法:
```java
public interface ISpout extends Serializable {
/**
* 组件初始化时候被调用
*
* @param conf ISpout的配置
* @param context 应用上下文可以通过其获取任务ID和组件ID输入和输出信息等。
* @param collector 用来发送spout中的tuples它是线程安全的建议保存为此spout对象的实例变量
* @param conf ISpout 的配置
* @param context 应用上下文,可以通过其获取任务 ID 和组件 ID输入和输出信息等。
* @param collector 用来发送 spout 中的 tuples它是线程安全的建议保存为此 spout 对象的实例变量
*/
void open(Map conf, TopologyContext context, SpoutOutputCollector collector);
/**
* ISpout将要被关闭的时候调用。但是其不一定会被执行如果在集群环境中通过kill -9 杀死进程时其就无法被执行。
* ISpout 将要被关闭的时候调用。但是其不一定会被执行,如果在集群环境中通过 kill -9 杀死进程时其就无法被执行。
*/
void close();
/**
* 当ISpout从停用状态激活时被调用
* 当 ISpout 从停用状态激活时被调用
*/
void activate();
/**
* 当ISpout停用时候被调用
* 当 ISpout 停用时候被调用
*/
void deactivate();
/**
* 这是一个核心方法主要通过在此方法中调用collectortuples发送给下一个接收器这个方法必须是非阻塞的。
* nextTuple/ack/fail/是在同一个线程中执行的所以不用考虑线程安全方面。当没有tuples发出时应该让
* nextTuple休眠(sleep)一下以免浪费CPU。
* 这是一个核心方法,主要通过在此方法中调用 collectortuples 发送给下一个接收器,这个方法必须是非阻塞的。
* nextTuple/ack/fail/是在同一个线程中执行的,所以不用考虑线程安全方面。当没有 tuples 发出时应该让
* nextTuple 休眠 (sleep) 一下,以免浪费 CPU。
*/
void nextTuple();
/**
* 通过msgId进行tuples处理成功的确认被确认后的tuples不会再次被发送
* 通过 msgId 进行 tuples 处理成功的确认,被确认后的 tuples 不会再次被发送
*/
void ack(Object msgId);
/**
* 通过msgId进行tuples处理失败的确认被确认后的tuples会再次被发送进行处理
* 通过 msgId 进行 tuples 处理失败的确认,被确认后的 tuples 会再次被发送进行处理
*/
void fail(Object msgId);
}
@ -99,11 +99,11 @@ public interface ISpout extends Serializable {
### 3.2 BaseRichSpout抽象类
**通常情况下我们实现自定义的Spout时不会直接去实现`ISpout`接口,而是继承`BaseRichSpout`。**`BaseRichSpout`继承自`BaseCompont`,同时实现了`IRichSpout`接口。
**通常情况下,我们实现自定义的 Spout 时不会直接去实现 `ISpout` 接口,而是继承 `BaseRichSpout`。**`BaseRichSpout` 继承自 `BaseCompont`,同时实现了 `IRichSpout` 接口。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-baseRichSpout.png"/> </div>
`IRichSpout`接口继承自`ISpout``IComponent`,自身并没有定义任何方法:
`IRichSpout` 接口继承自 `ISpout``IComponent`,自身并没有定义任何方法:
```java
public interface IRichSpout extends ISpout, IComponent {
@ -111,7 +111,7 @@ public interface IRichSpout extends ISpout, IComponent {
}
```
`BaseComponent`抽象类空实现了`IComponent``getComponentConfiguration`方法:
`BaseComponent` 抽象类空实现了 `IComponent``getComponentConfiguration` 方法:
```java
public abstract class BaseComponent implements IComponent {
@ -122,7 +122,7 @@ public abstract class BaseComponent implements IComponent {
}
```
`BaseRichSpout`继承自`BaseCompont`类并实现了`IRichSpout`接口,并且空实现了其中部分方法:
`BaseRichSpout` 继承自 `BaseCompont` 类并实现了 `IRichSpout` 接口,并且空实现了其中部分方法:
```java
public abstract class BaseRichSpout extends BaseComponent implements IRichSpout {
@ -143,45 +143,45 @@ public abstract class BaseRichSpout extends BaseComponent implements IRichSpout
}
```
通过这样的设计,我们在继承`BaseRichSpout`实现自定义spout时就只有三个方法必须实现
通过这样的设计,我们在继承 `BaseRichSpout` 实现自定义 spout 时,就只有三个方法必须实现:
+ **open** 来源于ISpout可以通过此方法获取用来发送tuples`SpoutOutputCollector`
+ **nextTuple** 来源于ISpout必须在此方法内部发送tuples
+ **declareOutputFields** 来源于IComponent声明发送的tuples的名称这样下一个组件才能知道如何接受。
+ **open** 来源于 ISpout可以通过此方法获取用来发送 tuples`SpoutOutputCollector`
+ **nextTuple** :来源于 ISpout必须在此方法内部发送 tuples
+ **declareOutputFields** :来源于 IComponent声明发送的 tuples 的名称,这样下一个组件才能知道如何接受。
## 四、Bolt
bolt接口的设计与spout的类似
bolt 接口的设计与 spout 的类似:
### 4.1 IBolt 接口
```java
/**
* 在客户端计算机上创建的IBolt对象。会被被序列化到topology中使用Java序列化,并提交给集群的主机Nimbus
* Nimbus启动workers反序列化对象调用prepare然后开始处理tuples。
* 在客户端计算机上创建的 IBolt 对象。会被被序列化到 topology 中(使用 Java 序列化),并提交给集群的主机Nimbus
* Nimbus 启动 workers 反序列化对象,调用 prepare然后开始处理 tuples。
*/
public interface IBolt extends Serializable {
/**
* 组件初始化时候被调用
*
* @param conf storm中定义的此bolt的配置
* @param context 应用上下文可以通过其获取任务ID和组件ID输入和输出信息等。
* @param collector 用来发送spout中的tuples它是线程安全的建议保存为此spout对象的实例变量
* @param conf storm 中定义的此 bolt 的配置
* @param context 应用上下文,可以通过其获取任务 ID 和组件 ID输入和输出信息等。
* @param collector 用来发送 spout 中的 tuples它是线程安全的建议保存为此 spout 对象的实例变量
*/
void prepare(Map stormConf, TopologyContext context, OutputCollector collector);
/**
* 处理单个tuple输入。
* 处理单个 tuple 输入。
*
* @param Tuple对象包含关于它的元数据如来自哪个组件/流/任务)
* @param Tuple 对象包含关于它的元数据(如来自哪个组件/流/任务)
*/
void execute(Tuple input);
/**
* IBolt将要被关闭的时候调用。但是其不一定会被执行如果在集群环境中通过kill -9 杀死进程时其就无法被执行。
* IBolt 将要被关闭的时候调用。但是其不一定会被执行,如果在集群环境中通过 kill -9 杀死进程时其就无法被执行。
*/
void cleanup();
```
@ -190,11 +190,11 @@ public interface IBolt extends Serializable {
### 4.2 BaseRichBolt抽象类
同样的在实现自定义bolt时通常是继承`BaseRichBolt`抽象类来实现。`BaseRichBolt`继承自`BaseComponent`抽象类并实现了`IRichBolt`接口。
同样的,在实现自定义 bolt 时,通常是继承 `BaseRichBolt` 抽象类来实现。`BaseRichBolt` 继承自 `BaseComponent` 抽象类并实现了 `IRichBolt` 接口。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-baseRichbolt.png"/> </div>
`IRichBolt`接口继承自`IBolt``IComponent`,自身并没有定义任何方法:
`IRichBolt` 接口继承自 `IBolt``IComponent`,自身并没有定义任何方法:
```
public interface IRichBolt extends IBolt, IComponent {
@ -202,11 +202,11 @@ public interface IRichBolt extends IBolt, IComponent {
}
```
通过这样的设计,在继承`BaseRichBolt`实现自定义bolt时就只需要实现三个必须的方法
通过这样的设计,在继承 `BaseRichBolt` 实现自定义 bolt 时,就只需要实现三个必须的方法:
- **prepare** 来源于IBolt可以通过此方法获取用来发送tuples`OutputCollector`
- **execute**来源于IBolt处理tuples和发送处理完成的tuples
- **declareOutputFields** 来源于IComponent声明发送的tuples的名称这样下一个组件才能知道如何接收。
- **prepare** 来源于 IBolt可以通过此方法获取用来发送 tuples`OutputCollector`
- **execute**:来源于 IBolt处理 tuples 和发送处理完成的 tuples
- **declareOutputFields** :来源于 IComponent声明发送的 tuples 的名称,这样下一个组件才能知道如何接收。
@ -214,7 +214,7 @@ public interface IRichBolt extends IBolt, IComponent {
### 5.1 案例简介
这里我们使用自定义的`DataSourceSpout`产生词频数据,然后使用自定义的`SplitBolt``CountBolt`来进行词频统计。
这里我们使用自定义的 `DataSourceSpout` 产生词频数据,然后使用自定义的 `SplitBolt``CountBolt` 来进行词频统计。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-word-count-p.png"/> </div>
@ -273,7 +273,7 @@ public class DataSourceSpout extends BaseRichSpout {
}
```
上面类使用`productData`方法来产生模拟数据,产生数据的格式如下:
上面类使用 `productData` 方法来产生模拟数据,产生数据的格式如下:
```properties
Spark HBase
@ -351,7 +351,7 @@ public class CountBolt extends BaseRichBolt {
#### 5. LocalWordCountApp
通过TopologyBuilder将上面定义好的组件进行串联形成 Topology并提交到本地集群LocalCluster运行。通常在开发中可先用本地模式进行测试测试完成后再提交到服务器集群运行。
通过 TopologyBuilder 将上面定义好的组件进行串联形成 Topology并提交到本地集群LocalCluster运行。通常在开发中可先用本地模式进行测试测试完成后再提交到服务器集群运行。
```java
public class LocalWordCountApp {
@ -367,7 +367,7 @@ public class LocalWordCountApp {
// 指明将 SplitBolt 的数据发送到 CountBolt 中 处理
builder.setBolt("CountBolt", new CountBolt()).shuffleGrouping("SplitBolt");
// 创建本地集群用于测试 这种模式不需要本机安装storm,直接运行该Main方法即可
// 创建本地集群用于测试 这种模式不需要本机安装 storm,直接运行该 Main 方法即可
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("LocalWordCountApp",
new Config(), builder.createTopology());
@ -380,7 +380,7 @@ public class LocalWordCountApp {
#### 6. 运行结果
启动`WordCountApp`main方法即可运行采用本地模式Storm会自动在本地搭建一个集群所以启动的过程会稍慢一点启动成功后即可看到输出日志。
启动 `WordCountApp`main 方法即可运行,采用本地模式 Storm 会自动在本地搭建一个集群,所以启动的过程会稍慢一点,启动成功后即可看到输出日志。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-word-count-console.png"/> </div>
@ -390,9 +390,9 @@ public class LocalWordCountApp {
### 6.1 代码更改
提交到服务器的代码和本地代码略有不同,提交到服务器集群时需要使用`StormSubmitter`进行提交。主要代码如下:
提交到服务器的代码和本地代码略有不同,提交到服务器集群时需要使用 `StormSubmitter` 进行提交。主要代码如下:
> 为了结构清晰这里新建ClusterWordCountApp类来演示集群模式的提交。实际开发中可以将两种模式的代码写在同一个类中通过外部传参来决定启动何种模式。
> 为了结构清晰,这里新建 ClusterWordCountApp 类来演示集群模式的提交。实际开发中可以将两种模式的代码写在同一个类中,通过外部传参来决定启动何种模式。
```java
public class ClusterWordCountApp {
@ -408,7 +408,7 @@ public class ClusterWordCountApp {
// 指明将 SplitBolt 的数据发送到 CountBolt 中 处理
builder.setBolt("CountBolt", new CountBolt()).shuffleGrouping("SplitBolt");
// 使用StormSubmitter提交Topology到服务器集群
// 使用 StormSubmitter 提交 Topology 到服务器集群
try {
StormSubmitter.submitTopology("ClusterWordCountApp", new Config(), builder.createTopology());
} catch (AlreadyAliveException | InvalidTopologyException | AuthorizationException e) {
@ -421,7 +421,7 @@ public class ClusterWordCountApp {
### 6.2 打包上传
打包后上传到服务器任意位置,这里我打包后的名称为`storm-word-count-1.0.jar`
打包后上传到服务器任意位置,这里我打包后的名称为 `storm-word-count-1.0.jar`
```shell
# mvn clean package -Dmaven.test.skip=true
@ -429,14 +429,14 @@ public class ClusterWordCountApp {
### 6.3 提交Topology
使用以下命令提交Topology到集群
使用以下命令提交 Topology 到集群:
```shell
# 命令格式: storm jar jar包位置 主类的全路径 ...可选传参
storm jar /usr/appjar/storm-word-count-1.0.jar com.heibaiying.wordcount.ClusterWordCountApp
```
出现`successfully`则代表提交成功:
出现 `successfully` 则代表提交成功:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-submit-success.png"/> </div>
@ -454,7 +454,7 @@ storm kill ClusterWordCountApp -w 3
### 6.5 查看Topology与停止Topology界面方式
使用UI界面同样也可进行停止操作进入WEB UI界面8080端口`Topology Summary`中点击对应Topology 即可进入详情页面进行操作。
使用 UI 界面同样也可进行停止操作,进入 WEB UI 界面8080 端口),在 `Topology Summary` 中点击对应 Topology 即可进入详情页面进行操作。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-ui-actions.png"/> </div>
@ -470,13 +470,13 @@ storm kill ClusterWordCountApp -w 3
### mvn package的局限性
在上面的步骤中我们没有在POM中配置任何插件就直接使用`mvn package`进行项目打包这对于没有使用外部依赖包的项目是可行的。但如果项目中使用了第三方JAR包就会出现问题因为`package`打包后的JAR中是不含有依赖包的如果此时你提交到服务器上运行就会出现找不到第三方依赖的异常。
在上面的步骤中,我们没有在 POM 中配置任何插件,就直接使用 `mvn package` 进行项目打包,这对于没有使用外部依赖包的项目是可行的。但如果项目中使用了第三方 JAR 包,就会出现问题,因为 `package` 打包后的 JAR 中是不含有依赖包的,如果此时你提交到服务器上运行,就会出现找不到第三方依赖的异常。
这时候可能大家会有疑惑,在我们的项目中不是使用了`storm-core`这个依赖吗其实上面之所以我们能运行成功是因为在Storm的集群环境中提供了这个JAR包在安装目录的lib目录下
这时候可能大家会有疑惑,在我们的项目中不是使用了 `storm-core` 这个依赖吗?其实上面之所以我们能运行成功,是因为在 Storm 的集群环境中提供了这个 JAR 包,在安装目录的 lib 目录下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-lib.png"/> </div>
为了说明这个问题我在Maven中引入了一个第三方的JAR包并修改产生数据的方法
为了说明这个问题我在 Maven 中引入了一个第三方的 JAR 包,并修改产生数据的方法:
```xml
<dependency>
@ -486,7 +486,7 @@ storm kill ClusterWordCountApp -w 3
</dependency>
```
`StringUtils.join()`这个方法在`commons.lang3``storm-core`中都有,原来的代码无需任何更改,只需要在`import`时指明使用`commons.lang3`
`StringUtils.join()` 这个方法在 `commons.lang3``storm-core` 中都有,原来的代码无需任何更改,只需要在 `import` 时指明使用 `commons.lang3`
```java
import org.apache.commons.lang3.StringUtils;
@ -499,15 +499,15 @@ private String productData() {
}
```
此时直接使用`mvn clean package`打包运行就会抛出下图的异常。因此这种直接打包的方式并不适用于实际的开发因为实际开发中通常都是需要第三方的JAR包。
此时直接使用 `mvn clean package` 打包运行,就会抛出下图的异常。因此这种直接打包的方式并不适用于实际的开发,因为实际开发中通常都是需要第三方的 JAR 包。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-package-error.png"/> </div>
想把依赖包一并打入最后的JAR中maven提供了两个插件来实现分别是`maven-assembly-plugin``maven-shade-plugin`。鉴于本篇文章篇幅已经比较长且关于Storm打包还有很多需要说明的地方所以关于Storm的打包方式单独整理至下一篇文章
想把依赖包一并打入最后的 JAR maven 提供了两个插件来实现,分别是 `maven-assembly-plugin``maven-shade-plugin`。鉴于本篇文章篇幅已经比较长,且关于 Storm 打包还有很多需要说明的地方,所以关于 Storm 的打包方式单独整理至下一篇文章:
[Storm三种打包方式对比分析](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Storm三种打包方式对比分析.md)
[Storm 三种打包方式对比分析](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Storm 三种打包方式对比分析.md)
## 参考资料

View File

@ -17,8 +17,8 @@
项目主要依赖如下,有两个地方需要注意:
+ 这里由于我服务器上安装的是CDH版本的Hadoop在导入依赖时引入的也是CDH版本的依赖需要使用`<repository>`标签指定CDH的仓库地址
+ `hadoop-common``hadoop-client``hadoop-hdfs`均需要排除`slf4j-log4j12`依赖,原因是`storm-core`中已经有该依赖不排除的话有JAR包冲突的风险
+ 这里由于我服务器上安装的是 CDH 版本的 Hadoop在导入依赖时引入的也是 CDH 版本的依赖,需要使用 `<repository>` 标签指定 CDH 的仓库地址;
+ `hadoop-common``hadoop-client``hadoop-hdfs` 均需要排除 `slf4j-log4j12` 依赖,原因是 `storm-core` 中已经有该依赖,不排除的话有 JAR 包冲突的风险;
```xml
<properties>
@ -38,7 +38,7 @@
<artifactId>storm-core</artifactId>
<version>${storm.version}</version>
</dependency>
<!--Storm整合HDFS依赖-->
<!--Storm 整合 HDFS 依赖-->
<dependency>
<groupId>org.apache.storm</groupId>
<artifactId>storm-hdfs</artifactId>
@ -140,7 +140,7 @@ Hadoop Spark HBase Storm
### 1.4 将数据存储到HDFS
这里HDFS的地址和数据存储路径均使用了硬编码在实际开发中可以通过外部传参指定这样程序更为灵活。
这里 HDFS 的地址和数据存储路径均使用了硬编码,在实际开发中可以通过外部传参指定,这样程序更为灵活。
```java
public class DataToHdfsApp {
@ -150,24 +150,24 @@ public class DataToHdfsApp {
public static void main(String[] args) {
// 指定Hadoop的用户名 如果不指定,则在HDFS创建目录时候有可能抛出无权限的异常(RemoteException: Permission denied)
// 指定 Hadoop 的用户名 如果不指定,则在 HDFS 创建目录时候有可能抛出无权限的异常 (RemoteException: Permission denied)
System.setProperty("HADOOP_USER_NAME", "root");
// 定义输出字段(Field)之间的分隔符
// 定义输出字段 (Field) 之间的分隔符
RecordFormat format = new DelimitedRecordFormat()
.withFieldDelimiter("|");
// 同步策略: 每100tuples之后就会把数据从缓存刷新到HDFS中
// 同步策略: 每 100tuples 之后就会把数据从缓存刷新到 HDFS
SyncPolicy syncPolicy = new CountSyncPolicy(100);
// 文件策略: 每个文件大小上限1M,超过限定时,创建新文件并继续写入
// 文件策略: 每个文件大小上限 1M,超过限定时,创建新文件并继续写入
FileRotationPolicy rotationPolicy = new FileSizeRotationPolicy(1.0f, Units.MB);
// 定义存储路径
FileNameFormat fileNameFormat = new DefaultFileNameFormat()
.withPath("/storm-hdfs/");
// 定义HdfsBolt
// 定义 HdfsBolt
HdfsBolt hdfsBolt = new HdfsBolt()
.withFsUrl("hdfs://hadoop001:8020")
.withFileNameFormat(fileNameFormat)
@ -176,14 +176,14 @@ public class DataToHdfsApp {
.withSyncPolicy(syncPolicy);
// 构建Topology
// 构建 Topology
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout(DATA_SOURCE_SPOUT, new DataSourceSpout());
// save to HDFS
builder.setBolt(HDFS_BOLT, hdfsBolt, 1).shuffleGrouping(DATA_SOURCE_SPOUT);
// 如果外部传参cluster则代表线上环境启动,否则代表本地启动
// 如果外部传参 cluster 则代表线上环境启动,否则代表本地启动
if (args.length > 0 && args[0].equals("cluster")) {
try {
StormSubmitter.submitTopology("ClusterDataToHdfsApp", new Config(), builder.createTopology());
@ -201,13 +201,13 @@ public class DataToHdfsApp {
### 1.5 启动测试
可以用直接使用本地模式运行,也可以打包后提交到服务器集群运行。本仓库提供的源码默认采用`maven-shade-plugin`进行打包,打包命令如下:
可以用直接使用本地模式运行,也可以打包后提交到服务器集群运行。本仓库提供的源码默认采用 `maven-shade-plugin` 进行打包,打包命令如下:
```shell
# mvn clean package -D maven.test.skip=true
```
运行后数据会存储到HDFS`/storm-hdfs`目录下。使用以下命令可以查看目录内容:
运行后,数据会存储到 HDFS`/storm-hdfs` 目录下。使用以下命令可以查看目录内容:
```shell
# 查看目录内容
@ -226,7 +226,7 @@ hadoop fs -tail -f /strom-hdfs/文件名
### 2.1 项目结构
集成用例: 进行词频统计并将最后的结果存储到HBase项目主要结构如下
集成用例: 进行词频统计并将最后的结果存储到 HBase项目主要结构如下
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/WordCountToHBaseApp.png"/> </div>
@ -246,7 +246,7 @@ hadoop fs -tail -f /strom-hdfs/文件名
<artifactId>storm-core</artifactId>
<version>${storm.version}</version>
</dependency>
<!--Storm整合HBase依赖-->
<!--Storm 整合 HBase 依赖-->
<dependency>
<groupId>org.apache.storm</groupId>
<artifactId>storm-hbase</artifactId>
@ -389,7 +389,7 @@ public class CountBolt extends BaseRichBolt {
```java
/**
* 进行词频统计 并将统计结果存储到HBase中
* 进行词频统计 并将统计结果存储到 HBase
*/
public class WordCountToHBaseApp {
@ -400,31 +400,31 @@ public class WordCountToHBaseApp {
public static void main(String[] args) {
// storm的配置
// storm 的配置
Config config = new Config();
// HBase的配置
// HBase 的配置
Map<String, Object> hbConf = new HashMap<>();
hbConf.put("hbase.rootdir", "hdfs://hadoop001:8020/hbase");
hbConf.put("hbase.zookeeper.quorum", "hadoop001:2181");
// 将HBase的配置传入Storm的配置中
// 将 HBase 的配置传入 Storm 的配置中
config.put("hbase.conf", hbConf);
// 定义流数据与HBase中数据的映射
// 定义流数据与 HBase 中数据的映射
SimpleHBaseMapper mapper = new SimpleHBaseMapper()
.withRowKeyField("word")
.withColumnFields(new Fields("word","count"))
.withColumnFamily("info");
/*
* 给HBaseBolt传入表名、数据映射关系、和HBase的配置信息
* 给 HBaseBolt 传入表名、数据映射关系、和 HBase 的配置信息
* 表需要预先创建: create 'WordCount','info'
*/
HBaseBolt hbase = new HBaseBolt("WordCount", mapper)
.withConfigKey("hbase.conf");
// 构建Topology
// 构建 Topology
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout(DATA_SOURCE_SPOUT, new DataSourceSpout(),1);
// split
@ -435,7 +435,7 @@ public class WordCountToHBaseApp {
builder.setBolt(HBASE_BOLT, hbase, 1).shuffleGrouping(COUNT_BOLT);
// 如果外部传参cluster则代表线上环境启动,否则代表本地启动
// 如果外部传参 cluster 则代表线上环境启动,否则代表本地启动
if (args.length > 0 && args[0].equals("cluster")) {
try {
StormSubmitter.submitTopology("ClusterWordCountToRedisApp", config, builder.createTopology());
@ -453,13 +453,13 @@ public class WordCountToHBaseApp {
### 2.7 启动测试
可以用直接使用本地模式运行,也可以打包后提交到服务器集群运行。本仓库提供的源码默认采用`maven-shade-plugin`进行打包,打包命令如下:
可以用直接使用本地模式运行,也可以打包后提交到服务器集群运行。本仓库提供的源码默认采用 `maven-shade-plugin` 进行打包,打包命令如下:
```shell
# mvn clean package -D maven.test.skip=true
```
运行后数据会存储到HBase`WordCount`表中。使用以下命令查看表的内容:
运行后,数据会存储到 HBase`WordCount` 表中。使用以下命令查看表的内容:
```shell
hbase > scan 'WordCount'
@ -471,7 +471,7 @@ hbase > scan 'WordCount'
### 2.8 withCounterFields
在上面的用例中我们是手动编码来实现词频统计并将最后的结果存储到HBase中。其实也可以在构建`SimpleHBaseMapper`的时候通过`withCounterFields`指定count字段被指定的字段会自动进行累加操作这样也可以实现词频统计。需要注意的是withCounterFields指定的字段必须是Long类型不能是String类型。
在上面的用例中我们是手动编码来实现词频统计,并将最后的结果存储到 HBase 中。其实也可以在构建 `SimpleHBaseMapper` 的时候通过 `withCounterFields` 指定 count 字段,被指定的字段会自动进行累加操作,这样也可以实现词频统计。需要注意的是 withCounterFields 指定的字段必须是 Long 类型,不能是 String 类型。
```java
SimpleHBaseMapper mapper = new SimpleHBaseMapper()
@ -486,4 +486,4 @@ SimpleHBaseMapper mapper = new SimpleHBaseMapper()
## 参考资料
1. [Apache HDFS Integration](http://storm.apache.org/releases/2.0.0-SNAPSHOT/storm-hdfs.html)
2. [Apache HBase Integration](http://storm.apache.org/releases/2.0.0-SNAPSHOT/storm-hbase.html)
2. [Apache HBase Integration](http://storm.apache.org/releases/2.0.0-SNAPSHOT/storm-hbase.html)

View File

@ -9,12 +9,12 @@
## 一、整合说明
Storm官方对Kafka的整合分为两个版本官方说明文档分别如下
Storm 官方对 Kafka 的整合分为两个版本,官方说明文档分别如下:
+ [Storm Kafka Integration](http://storm.apache.org/releases/2.0.0-SNAPSHOT/storm-kafka.html) : 主要是针对0.8.x版本的Kafka提供整合支持
+ [Storm Kafka Integration (0.10.x+)]() : 包含Kafka 新版本的 consumer API主要对Kafka 0.10.x +提供整合支持。
+ [Storm Kafka Integration](http://storm.apache.org/releases/2.0.0-SNAPSHOT/storm-kafka.html) : 主要是针对 0.8.x 版本的 Kafka 提供整合支持;
+ [Storm Kafka Integration (0.10.x+)]() : 包含 Kafka 新版本的 consumer API主要对 Kafka 0.10.x + 提供整合支持。
这里我服务端安装的Kafka版本为2.2.0(Released Mar 22, 2019) 按照官方0.10.x+的整合文档进行整合不适用于0.8.x版本的Kafka。
这里我服务端安装的 Kafka 版本为 2.2.0(Released Mar 22, 2019) ,按照官方 0.10.x+ 的整合文档进行整合,不适用于 0.8.x 版本的 Kafka。
## 二、写入数据到Kafka
@ -111,7 +111,7 @@ Hadoop Spark HBase Storm
```java
/**
* 写入数据到Kafka中
* 写入数据到 Kafka
*/
public class WritingToKafkaApp {
@ -123,11 +123,11 @@ public class WritingToKafkaApp {
TopologyBuilder builder = new TopologyBuilder();
// 定义Kafka生产者属性
// 定义 Kafka 生产者属性
Properties props = new Properties();
/*
* 指定broker的地址清单清单里不需要包含所有的broker地址生产者会从给定的broker里查找其他broker的信息。
* 不过建议至少要提供两个broker的信息作为容错。
* 指定 broker 的地址清单,清单里不需要包含所有的 broker 地址,生产者会从给定的 broker 里查找其他 broker 的信息。
* 不过建议至少要提供两个 broker 的信息作为容错。
*/
props.put("bootstrap.servers", BOOTSTRAP_SERVERS);
/*
@ -166,11 +166,11 @@ public class WritingToKafkaApp {
### 2.5 测试准备工作
进行测试前需要启动Kakfa
进行测试前需要启动 Kakfa
#### 1. 启动Kakfa
Kafka的运行依赖于zookeeper需要预先启动可以启动Kafka内置的zookeeper,也可以启动自己安装的:
Kafka 的运行依赖于 zookeeper需要预先启动可以启动 Kafka 内置的 zookeeper,也可以启动自己安装的:
```shell
# zookeeper启动命令
@ -180,7 +180,7 @@ bin/zkServer.sh start
bin/zookeeper-server-start.sh config/zookeeper.properties
```
启动单节点kafka用于测试
启动单节点 kafka 用于测试:
```shell
# bin/kafka-server-start.sh config/server.properties
@ -206,7 +206,7 @@ bin/kafka-topics.sh --create --bootstrap-server hadoop001:9092 --replication-fac
### 2.6 测试
可以用直接使用本地模式运行,也可以打包后提交到服务器集群运行。本仓库提供的源码默认采用`maven-shade-plugin`进行打包,打包命令如下:
可以用直接使用本地模式运行,也可以打包后提交到服务器集群运行。本仓库提供的源码默认采用 `maven-shade-plugin` 进行打包,打包命令如下:
```shell
# mvn clean package -D maven.test.skip=true
@ -228,7 +228,7 @@ bin/kafka-topics.sh --create --bootstrap-server hadoop001:9092 --replication-fac
```java
/**
* 从Kafka中读取数据
* 从 Kafka 中读取数据
*/
public class ReadingFromKafkaApp {
@ -241,7 +241,7 @@ public class ReadingFromKafkaApp {
builder.setSpout("kafka_spout", new KafkaSpout<>(getKafkaSpoutConfig(BOOTSTRAP_SERVERS, TOPIC_NAME)), 1);
builder.setBolt("bolt", new LogConsoleBolt()).shuffleGrouping("kafka_spout");
// 如果外部传参cluster则代表线上环境启动,否则代表本地启动
// 如果外部传参 cluster 则代表线上环境启动,否则代表本地启动
if (args.length > 0 && args[0].equals("cluster")) {
try {
StormSubmitter.submitTopology("ClusterReadingFromKafkaApp", new Config(), builder.createTopology());
@ -257,11 +257,11 @@ public class ReadingFromKafkaApp {
private static KafkaSpoutConfig<String, String> getKafkaSpoutConfig(String bootstrapServers, String topic) {
return KafkaSpoutConfig.builder(bootstrapServers, topic)
// 除了分组ID,以下配置都是可选的。分组ID必须指定,否则会抛出InvalidGroupIdException异常
// 除了分组 ID,以下配置都是可选的。分组 ID 必须指定,否则会抛出 InvalidGroupIdException 异常
.setProp(ConsumerConfig.GROUP_ID_CONFIG, "kafkaSpoutTestGroup")
// 定义重试策略
.setRetry(getRetryService())
// 定时提交偏移量的时间间隔,默认是15s
// 定时提交偏移量的时间间隔,默认是 15s
.setOffsetCommitPeriodMs(10_000)
.build();
}
@ -279,7 +279,7 @@ public class ReadingFromKafkaApp {
```java
/**
* 打印从Kafka中获取的数据
* 打印从 Kafka 中获取的数据
*/
public class LogConsoleBolt extends BaseRichBolt {
@ -294,7 +294,7 @@ public class LogConsoleBolt extends BaseRichBolt {
try {
String value = input.getStringByField("value");
System.out.println("received from kafka : "+ value);
// 必须ack,否则会重复消费kafka中的消息
// 必须 ack,否则会重复消费 kafka 中的消息
collector.ack(input);
}catch (Exception e){
e.printStackTrace();
@ -309,11 +309,11 @@ public class LogConsoleBolt extends BaseRichBolt {
}
```
这里从`value`字段中获取kafka输出的值数据。
这里从 `value` 字段中获取 kafka 输出的值数据。
在开发中,我们可以通过继承`RecordTranslator`接口定义了KafkaRecord与输出流之间的映射关系可以在构建`KafkaSpoutConfig`的时候通过构造器或者`setRecordTranslator()`方法传入,并最后传递给具体的`KafkaSpout`
在开发中,我们可以通过继承 `RecordTranslator` 接口定义了 KafkaRecord 与输出流之间的映射关系,可以在构建 `KafkaSpoutConfig` 的时候通过构造器或者 `setRecordTranslator()` 方法传入,并最后传递给具体的 `KafkaSpout`
默认情况下使用内置的`DefaultRecordTranslator`,其源码如下,`FIELDS`中 定义了tuple中所有可用的字段主题分区偏移量消息键值。
默认情况下使用内置的 `DefaultRecordTranslator`,其源码如下,`FIELDS` 中 定义了 tuple 中所有可用的字段:主题,分区,偏移量,消息键,值。
```java
public class DefaultRecordTranslator<K, V> implements RecordTranslator<K, V> {
@ -350,7 +350,7 @@ public class DefaultRecordTranslator<K, V> implements RecordTranslator<K, V> {
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-kafka-producer.png"/> </div>
本地运行的项目接收到从Kafka发送过来的数据
本地运行的项目接收到从 Kafka 发送过来的数据:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-kafka-receiver.png"/> </div>

View File

@ -10,7 +10,7 @@
## 一、简介
Storm-Redis提供了StormRedis的集成支持你只需要引入对应的依赖即可使用
Storm-Redis 提供了 StormRedis 的集成支持,你只需要引入对应的依赖即可使用:
```xml
<dependency>
@ -21,13 +21,13 @@ Storm-Redis提供了Storm与Redis的集成支持你只需要引入对应的
</dependency>
```
Storm-Redis使用JedisRedis客户端并提供了如下三个基本的Bolt实现
Storm-Redis 使用 JedisRedis 客户端,并提供了如下三个基本的 Bolt 实现:
+ **RedisLookupBolt**从Redis中查询数据
+ **RedisStoreBolt**存储数据到Redis
+ **RedisLookupBolt**:从 Redis 中查询数据;
+ **RedisStoreBolt**:存储数据到 Redis
+ **RedisFilterBolt** : 查询符合条件的数据;
`RedisLookupBolt``RedisStoreBolt``RedisFilterBolt `均继承自`AbstractRedisBolt`抽象类。我们可以通过继承该抽象类实现自定义RedisBolt进行功能的拓展。
`RedisLookupBolt``RedisStoreBolt``RedisFilterBolt ` 均继承自 `AbstractRedisBolt` 抽象类。我们可以通过继承该抽象类,实现自定义 RedisBolt进行功能的拓展。
@ -35,7 +35,7 @@ Storm-Redis使用Jedis为Redis客户端并提供了如下三个基本的Bolt
### 2.1 项目结构
这里首先给出一个集成案例进行词频统计并将最后的结果存储到Redis。项目结构如下
这里首先给出一个集成案例:进行词频统计并将最后的结果存储到 Redis。项目结构如下
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-wordcounttoredis.png"/> </div>
@ -194,11 +194,11 @@ public class CountBolt extends BaseRichBolt {
### 2.6 WordCountStoreMapper
实现RedisStoreMapper接口定义tupleRedis中数据的映射关系即需要指定tuple中的哪个字段为key哪个字段为value并且存储到Redis的何种数据结构中。
实现 RedisStoreMapper 接口,定义 tupleRedis 中数据的映射关系:即需要指定 tuple 中的哪个字段为 key哪个字段为 value并且存储到 Redis 的何种数据结构中。
```java
/**
* 定义tupleRedis中数据的映射关系
* 定义 tupleRedis 中数据的映射关系
*/
public class WordCountStoreMapper implements RedisStoreMapper {
private RedisDataTypeDescription description;
@ -230,7 +230,7 @@ public class WordCountStoreMapper implements RedisStoreMapper {
```java
/**
* 进行词频统计 并将统计结果存储到Redis中
* 进行词频统计 并将统计结果存储到 Redis
*/
public class WordCountToRedisApp {
@ -257,7 +257,7 @@ public class WordCountToRedisApp {
RedisStoreBolt storeBolt = new RedisStoreBolt(poolConfig, storeMapper);
builder.setBolt(STORE_BOLT, storeBolt).shuffleGrouping(COUNT_BOLT);
// 如果外部传参cluster则代表线上环境启动否则代表本地启动
// 如果外部传参 cluster 则代表线上环境启动否则代表本地启动
if (args.length > 0 && args[0].equals("cluster")) {
try {
StormSubmitter.submitTopology("ClusterWordCountToRedisApp", new Config(), builder.createTopology());
@ -275,13 +275,13 @@ public class WordCountToRedisApp {
### 2.8 启动测试
可以用直接使用本地模式运行,也可以打包后提交到服务器集群运行。本仓库提供的源码默认采用`maven-shade-plugin`进行打包,打包命令如下:
可以用直接使用本地模式运行,也可以打包后提交到服务器集群运行。本仓库提供的源码默认采用 `maven-shade-plugin` 进行打包,打包命令如下:
```shell
# mvn clean package -D maven.test.skip=true
```
启动后查看Redis中的数据
启动后,查看 Redis 中的数据:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/store-redis-manager.png"/> </div>
@ -291,13 +291,13 @@ public class WordCountToRedisApp {
### 3.1 AbstractRedisBolt
`RedisLookupBolt``RedisStoreBolt``RedisFilterBolt `均继承自`AbstractRedisBolt`抽象类和我们自定义实现Bolt一样`AbstractRedisBolt`间接继承自`BaseRichBolt`
`RedisLookupBolt``RedisStoreBolt``RedisFilterBolt ` 均继承自 `AbstractRedisBolt` 抽象类,和我们自定义实现 Bolt 一样,`AbstractRedisBolt` 间接继承自 `BaseRichBolt`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-abstractRedisBolt.png"/> </div>
`AbstractRedisBolt`中比较重要的是prepare方法在该方法中通过外部传入的jedis连接池配置( jedisPoolConfig/jedisClusterConfig) 创建用于管理Jedis实例的容器`JedisCommandsInstanceContainer`
`AbstractRedisBolt` 中比较重要的是 prepare 方法,在该方法中通过外部传入的 jedis 连接池配置 ( jedisPoolConfig/jedisClusterConfig) 创建用于管理 Jedis 实例的容器 `JedisCommandsInstanceContainer`
```java
public abstract class AbstractRedisBolt extends BaseTickTupleAwareRichBolt {
@ -328,7 +328,7 @@ public abstract class AbstractRedisBolt extends BaseTickTupleAwareRichBolt {
}
```
`JedisCommandsInstanceContainer``build()`方法如下实际上就是创建JedisPoolJedisCluster并传入容器中。
`JedisCommandsInstanceContainer``build()` 方法如下,实际上就是创建 JedisPoolJedisCluster 并传入容器中。
```java
public static JedisCommandsInstanceContainer build(JedisPoolConfig config) {
@ -344,9 +344,9 @@ public static JedisCommandsInstanceContainer build(JedisPoolConfig config) {
### 3.2 RedisStoreBolt和RedisLookupBolt
`RedisStoreBolt`中比较重要的是process方法该方法主要从storeMapper中获取传入key/value的值并按照其存储类型`dataType`调用jedisCommand的对应方法进行存储。
`RedisStoreBolt` 中比较重要的是 process 方法,该方法主要从 storeMapper 中获取传入 key/value 的值,并按照其存储类型 `dataType` 调用 jedisCommand 的对应方法进行存储。
RedisLookupBolt 的实现基本类似从lookupMapper中获取传入的key值并进行查询操作。
RedisLookupBolt 的实现基本类似,从 lookupMapper 中获取传入的 key 值,并进行查询操作。
```java
public class RedisStoreBolt extends AbstractRedisBolt {
@ -438,7 +438,7 @@ public class RedisStoreBolt extends AbstractRedisBolt {
### 3.3 JedisCommands
JedisCommands接口中定义了所有的 Redis 客户端命令它有以下三个实现类分别是Jedis、JedisCluster、ShardedJedis。Strom中主要使用前两种实现类具体调用哪一个实现类来执行命令由传入的是jedisPoolConfig还是jedisClusterConfig来决定。
JedisCommands 接口中定义了所有的 Redis 客户端命令,它有以下三个实现类,分别是 Jedis、JedisCluster、ShardedJedis。Strom 中主要使用前两种实现类,具体调用哪一个实现类来执行命令,由传入的是 jedisPoolConfig 还是 jedisClusterConfig 来决定。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-jedicCommands.png"/> </div>
@ -452,13 +452,13 @@ RedisMapper 和 TupleMapper 定义了 tuple 和 Redis 中的数据如何进行
TupleMapper 主要定义了两个方法:
+ getKeyFromTuple(ITuple tuple) 从tuple中获取那个字段作为Key
+ getKeyFromTuple(ITuple tuple) tuple 中获取那个字段作为 Key
+ getValueFromTuple(ITuple tuple)从tuple中获取那个字段作为Value
+ getValueFromTuple(ITuple tuple):从 tuple 中获取那个字段作为 Value
#### 2. RedisMapper
定义了获取数据类型的方法`getDataTypeDescription()`,RedisDataTypeDescriptionRedisDataType枚举类定义了所有可用的Redis数据类型
定义了获取数据类型的方法 `getDataTypeDescription()`,RedisDataTypeDescriptionRedisDataType 枚举类定义了所有可用的 Redis 数据类型:
```java
public class RedisDataTypeDescription implements Serializable {
@ -470,16 +470,16 @@ public class RedisDataTypeDescription implements Serializable {
#### 3. RedisStoreMapper
RedisStoreMapper继承TupleMapperRedisMapper接口用于数据存储时没有定义额外方法。
RedisStoreMapper 继承 TupleMapperRedisMapper 接口,用于数据存储时,没有定义额外方法。
#### 4. RedisLookupMapper
RedisLookupMapper继承TupleMapperRedisMapper接口
RedisLookupMapper 继承 TupleMapperRedisMapper 接口:
+ 定义了declareOutputFields方法声明输出的字段。
+ 定义了toTuple方法将查询结果组装为StormValues的集合并用于发送。
+ 定义了 declareOutputFields 方法,声明输出的字段。
+ 定义了 toTuple 方法,将查询结果组装为 StormValues 的集合,并用于发送。
下面的例子表示从输入`Tuple`的获取`word`字段作为key使用`RedisLookupBolt`进行查询后将key和查询结果value组装为values并发送到下一个处理单元。
下面的例子表示从输入 `Tuple` 的获取 `word` 字段作为 key使用 `RedisLookupBolt` 进行查询后,将 key 和查询结果 value 组装为 values 并发送到下一个处理单元。
```java
class WordCountRedisLookupMapper implements RedisLookupMapper {
@ -523,7 +523,7 @@ class WordCountRedisLookupMapper implements RedisLookupMapper {
#### 5. RedisFilterMapper
RedisFilterMapper继承TupleMapperRedisMapper接口用于查询数据时定义了declareOutputFields方法声明输出的字段。如下面的实现
RedisFilterMapper 继承 TupleMapperRedisMapper 接口,用于查询数据时,定义了 declareOutputFields 方法,声明输出的字段。如下面的实现:
```java
@Override
@ -537,7 +537,7 @@ public void declareOutputFields(OutputFieldsDeclarer declarer) {
### 4.1 实现原理
自定义RedisBolt主要利用Redis中哈希结构的`hincrby key field`命令进行词频统计。在Redis`hincrby`的执行效果如下。hincrby可以将字段按照指定的值进行递增如果该字段不存在的话还会新建该字段并赋值为0。通过这个命令可以非常轻松的实现词频统计功能。
自定义 RedisBolt主要利用 Redis 中哈希结构的 `hincrby key field` 命令进行词频统计。在 Redis`hincrby` 的执行效果如下。hincrby 可以将字段按照指定的值进行递增,如果该字段不存在的话,还会新建该字段,并赋值为 0。通过这个命令可以非常轻松的实现词频统计功能。
```shell
redis> HSET myhash field 5
@ -559,7 +559,7 @@ redis>
```java
/**
* 自定义RedisBolt 利用Redis的哈希数据结构的hincrby key field命令进行词频统计
* 自定义 RedisBolt 利用 Redis 的哈希数据结构的 hincrby key field 命令进行词频统计
*/
public class RedisCountStoreBolt extends AbstractRedisBolt {
@ -609,7 +609,7 @@ public class RedisCountStoreBolt extends AbstractRedisBolt {
```java
/**
* 利用自定义的RedisBolt实现词频统计
* 利用自定义的 RedisBolt 实现词频统计
*/
public class CustomRedisCountApp {
@ -632,7 +632,7 @@ public class CustomRedisCountApp {
RedisCountStoreBolt countStoreBolt = new RedisCountStoreBolt(poolConfig, storeMapper);
builder.setBolt(STORE_BOLT, countStoreBolt).shuffleGrouping(SPLIT_BOLT);
// 如果外部传参cluster则代表线上环境启动,否则代表本地启动
// 如果外部传参 cluster 则代表线上环境启动,否则代表本地启动
if (args.length > 0 && args[0].equals("cluster")) {
try {
StormSubmitter.submitTopology("ClusterCustomRedisCountApp", new Config(), builder.createTopology());
@ -652,4 +652,4 @@ public class CustomRedisCountApp {
## 参考资料
1. [Storm Redis Integration](http://storm.apache.org/releases/2.0.0-SNAPSHOT/storm-redis.html)
1. [Storm Redis Integration](http://storm.apache.org/releases/2.0.0-SNAPSHOT/storm-redis.html)

View File

@ -20,13 +20,13 @@
## 一、前言
为了避免存储在Zookeeper上的数据被其他程序或者人为误修改Zookeeper提供了ACL(Access Control Lists)进行权限控制。只有拥有对应权限的用户才可以对节点进行增删改查等操作。下文分别介绍使用原生的Shell命令和Apache Curator客户端进行权限设置。
为了避免存储在 Zookeeper 上的数据被其他程序或者人为误修改Zookeeper 提供了 ACL(Access Control Lists) 进行权限控制。只有拥有对应权限的用户才可以对节点进行增删改查等操作。下文分别介绍使用原生的 Shell 命令和 Apache Curator 客户端进行权限设置。
## 二、使用Shell进行权限管理
### 2.1 设置与查看权限
想要给某个节点设置权限(ACL),有以下两个可选的命令:
想要给某个节点设置权限 (ACL),有以下两个可选的命令:
```shell
# 1.给已有节点赋予权限
@ -44,9 +44,9 @@ getAcl path
### 2.2 权限组成
Zookeeper的权限由[scheme : id :permissions]三部分组成其中SchemesPermissions内置的可选项分别如下
Zookeeper 的权限由[scheme : id :permissions]三部分组成,其中 SchemesPermissions 内置的可选项分别如下:
**Permissions可选项**
**Permissions 可选项**
- **CREATE**:允许创建子节点;
- **READ**:允许从节点获取数据并列出其子节点;
@ -54,19 +54,19 @@ Zookeeper的权限由[scheme : id :permissions]三部分组成其中Schemes
- **DELETE**:允许删除子节点;
- **ADMIN**:允许为节点设置权限。
**Schemes可选项**
**Schemes 可选项**
- **world**默认模式所有客户端都拥有指定的权限。world下只有一个id选项就是anyone通常组合写法为`world:anyone:[permissons]`
- **auth**:只有经过认证的用户才拥有指定的权限。通常组合写法为`auth:user:password:[permissons]`使用这种模式时你需要先进行登录之后采用auth模式设置权限时`user``password`都将使用登录的用户名和密码;
- **digest**:只有经过认证的用户才拥有指定的权限。通常组合写法为`auth:user:BASE64(SHA1(password)):[permissons]`这种形式下的密码必须通过SHA1BASE64进行双重加密
- **ip**限制只有特定IP的客户端才拥有指定的权限。通常组成写法为`ip:182.168.0.168:[permissions]`
- **super**代表超级管理员拥有所有的权限需要修改Zookeeper启动脚本进行配置。
- **world**默认模式所有客户端都拥有指定的权限。world 下只有一个 id 选项,就是 anyone通常组合写法为 `world:anyone:[permissons]`
- **auth**:只有经过认证的用户才拥有指定的权限。通常组合写法为 `auth:user:password:[permissons]`,使用这种模式时,你需要先进行登录,之后采用 auth 模式设置权限时,`user``password` 都将使用登录的用户名和密码;
- **digest**:只有经过认证的用户才拥有指定的权限。通常组合写法为 `auth:user:BASE64(SHA1(password)):[permissons]`,这种形式下的密码必须通过 SHA1BASE64 进行双重加密;
- **ip**:限制只有特定 IP 的客户端才拥有指定的权限。通常组成写法为 `ip:182.168.0.168:[permissions]`
- **super**:代表超级管理员,拥有所有的权限,需要修改 Zookeeper 启动脚本进行配置。
### 2.3 添加认证信息
可以使用如下所示的命令为当前Session添加用户认证信息等价于登录操作。
可以使用如下所示的命令为当前 Session 添加用户认证信息,等价于登录操作。
```shell
# 格式
@ -82,7 +82,7 @@ addauth digest heibai:root
#### 1. world模式
world是一种默认的模式即创建时如果不指定权限则默认的权限就是world。
world 是一种默认的模式,即创建时如果不指定权限,则默认的权限就是 world。
```shell
[zk: localhost:2181(CONNECTED) 32] create /hadoop 123
@ -103,13 +103,13 @@ Authentication is not valid : /hadoop # 权限不足
[zk: localhost:2181(CONNECTED) 36] addauth digest heibai:heibai # 登录
[zk: localhost:2181(CONNECTED) 37] setAcl /hadoop auth::cdrwa # 设置权限
[zk: localhost:2181(CONNECTED) 38] getAcl /hadoop # 获取权限
'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= #用户名和密码(密码经过加密处理)注意返回的权限类型是digest
'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= #用户名和密码 (密码经过加密处理),注意返回的权限类型是 digest
: cdrwa
#用户名和密码都是使用登录的用户名和密码,即使你在创建权限时候进行指定也是无效的
[zk: localhost:2181(CONNECTED) 39] setAcl /hadoop auth:root:root:cdrwa #指定用户名和密码为root
[zk: localhost:2181(CONNECTED) 39] setAcl /hadoop auth:root:root:cdrwa #指定用户名和密码为 root
[zk: localhost:2181(CONNECTED) 40] getAcl /hadoop
'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= #无效使用的用户名和密码依然还是heibai
'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= #无效,使用的用户名和密码依然还是 heibai
: cdrwa
```
@ -119,15 +119,15 @@ Authentication is not valid : /hadoop # 权限不足
```shell
[zk:44] create /spark "spark" digest:heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s=:cdrwa #指定用户名和加密后的密码
[zk:45] getAcl /spark #获取权限
'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= # 返回的权限类型是digest
'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= # 返回的权限类型是 digest
: cdrwa
```
到这里你可以发现使用`auth`模式设置的权限和使用`digest`模式设置的权限,在最终结果上,得到的权限模式都是`digest`。某种程度上,你可以把`auth`模式理解成是`digest`模式的一种简便实现。因为在`digest`模式下,每次设置都需要书写用户名和加密后的密码,这是比较繁琐的,采用`auth`模式就可以避免这种麻烦。
到这里你可以发现使用 `auth` 模式设置的权限和使用 `digest` 模式设置的权限,在最终结果上,得到的权限模式都是 `digest`。某种程度上,你可以把 `auth` 模式理解成是 `digest` 模式的一种简便实现。因为在 `digest` 模式下,每次设置都需要书写用户名和加密后的密码,这是比较繁琐的,采用 `auth` 模式就可以避免这种麻烦。
#### 4. ip模式
限定只有特定的ip才能访问。
限定只有特定的 ip 才能访问。
```shell
[zk: localhost:2181(CONNECTED) 46] create /hive "hive" ip:192.168.0.108:cdrwa
@ -135,11 +135,11 @@ Authentication is not valid : /hadoop # 权限不足
Authentication is not valid : /hive # 当前主机已经不能访问
```
这里可以看到当前主机已经不能访问想要能够再次访问可以使用对应IP的客户端或使用下面介绍的`super`模式。
这里可以看到当前主机已经不能访问,想要能够再次访问,可以使用对应 IP 的客户端,或使用下面介绍的 `super` 模式。
#### 5. super模式
需要修改启动脚本`zkServer.sh`,并在指定位置添加超级管理员账户和密码信息:
需要修改启动脚本 `zkServer.sh`,并在指定位置添加超级管理员账户和密码信息:
```shell
"-Dzookeeper.DigestAuthenticationProvider.superDigest=heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s="
@ -147,12 +147,12 @@ Authentication is not valid : /hive # 当前主机已经不能访问
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/zookeeper-super.png"/> </div>
修改完成后需要使用`zkServer.sh restart`重启服务此时再次访问限制IP的节点
修改完成后需要使用 `zkServer.sh restart` 重启服务,此时再次访问限制 IP 的节点:
```shell
[zk: localhost:2181(CONNECTED) 0] get /hive #访问受限
Authentication is not valid : /hive
[zk: localhost:2181(CONNECTED) 1] addauth digest heibai:heibai # 登录(添加认证信息)
[zk: localhost:2181(CONNECTED) 1] addauth digest heibai:heibai # 登录 (添加认证信息)
[zk: localhost:2181(CONNECTED) 2] get /hive #成功访问
hive
cZxid = 0x158
@ -176,7 +176,7 @@ numChildren = 0
```xml
<dependencies>
<!--Apache Curator相关依赖-->
<!--Apache Curator 相关依赖-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
@ -216,7 +216,7 @@ public class AclOperation {
public void prepare() {
RetryPolicy retryPolicy = new RetryNTimes(3, 5000);
client = CuratorFrameworkFactory.builder()
.authorization("digest", "heibai:123456".getBytes()) //等价于addauth命令
.authorization("digest", "heibai:123456".getBytes()) //等价于 addauth 命令
.connectString(zkServerPath)
.sessionTimeoutMs(10000).retryPolicy(retryPolicy)
.namespace("workspace").build();
@ -280,4 +280,4 @@ public class AclOperation {
}
```
> 完整源码见本仓库: https://github.com/heibaiying/BigData-Notes/tree/master/code/Zookeeper/curator
> 完整源码见本仓库: https://github.com/heibaiying/BigData-Notes/tree/master/code/Zookeeper/curator

View File

@ -21,11 +21,11 @@
## 一、基本依赖
CuratorNetflix公司开源的一个Zookeeper客户端目前由Apache进行维护。与Zookeeper原生客户端相比Curator的抽象层次更高功能也更加丰富是目前Zookeeper使用范围最广的Java客户端。本篇文章主要讲解其基本使用项目采用Maven构建以单元测试的方法进行讲解相关依赖如下
CuratorNetflix 公司开源的一个 Zookeeper 客户端,目前由 Apache 进行维护。与 Zookeeper 原生客户端相比Curator 的抽象层次更高,功能也更加丰富,是目前 Zookeeper 使用范围最广的 Java 客户端。本篇文章主要讲解其基本使用,项目采用 Maven 构建,以单元测试的方法进行讲解,相关依赖如下:
```xml
<dependencies>
<!--Curator相关依赖-->
<!--Curator 相关依赖-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
@ -58,7 +58,7 @@ Curator是Netflix公司开源的一个Zookeeper客户端目前由Apache进行
### 2.1 创建客户端实例
这里使用`@Before`在单元测试执行前创建客户端实例,并使用`@After`在单元测试后关闭客户端连接。
这里使用 `@Before` 在单元测试执行前创建客户端实例,并使用 `@After` 在单元测试后关闭客户端连接。
```java
public class BasicOperation {
@ -74,7 +74,7 @@ public class BasicOperation {
client = CuratorFrameworkFactory.builder()
.connectString(zkServerPath)
.sessionTimeoutMs(10000).retryPolicy(retryPolicy)
.namespace("workspace").build(); //指定命名空间后client的所有路径操作都会以/workspace开头
.namespace("workspace").build(); //指定命名空间后client 的所有路径操作都会以/workspace 开头
client.start();
}
@ -89,14 +89,14 @@ public class BasicOperation {
### 2.2 重试策略
在连接Zookeeper时Curator提供了多种重试策略以满足各种需求所有重试策略均继承自`RetryPolicy`接口,如下图:
在连接 Zookeeper Curator 提供了多种重试策略以满足各种需求,所有重试策略均继承自 `RetryPolicy` 接口,如下图:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/curator-retry-policy.png"/> </div>
这些重试策略类主要分为以下两类:
+ **RetryForever** :代表一直重试,直到连接成功;
+ **SleepingRetry** 基于一定间隔时间的重试。这里以其子类`ExponentialBackoffRetry`为例说明,其构造器如下:
+ **SleepingRetry** 基于一定间隔时间的重试。这里以其子类 `ExponentialBackoffRetry` 为例说明,其构造器如下:
```java
/**
@ -133,7 +133,7 @@ public void createNodes() throws Exception {
}
```
创建时可以指定节点类型这里的节点类型和Zookeeper原生的一致全部类型定义在枚举类`CreateMode`中:
创建时可以指定节点类型,这里的节点类型和 Zookeeper 原生的一致,全部类型定义在枚举类 `CreateMode` 中:
```java
public enum CreateMode {
@ -161,7 +161,7 @@ public void getNode() throws Exception {
}
```
如上所示,节点信息被封装在`Stat`类中,其主要属性如下:
如上所示,节点信息被封装在 `Stat` 类中,其主要属性如下:
```java
public class Stat implements Record {
@ -184,15 +184,15 @@ public class Stat implements Record {
| **状态属性** | **说明** |
| -------------- | ------------------------------------------------------------ |
| czxid | 数据节点创建时的事务ID |
| czxid | 数据节点创建时的事务 ID |
| ctime | 数据节点创建时的时间 |
| mzxid | 数据节点最后一次更新时的事务ID |
| mzxid | 数据节点最后一次更新时的事务 ID |
| mtime | 数据节点最后一次更新时的时间 |
| pzxid | 数据节点的子节点最后一次被修改时的事务ID |
| pzxid | 数据节点的子节点最后一次被修改时的事务 ID |
| cversion | 子节点的更改次数 |
| version | 节点数据的更改次数 |
| aversion | 节点的ACL的更改次数 |
| ephemeralOwner | 如果节点是临时节点则表示创建该节点的会话的SessionID如果节点是持久节点则该属性值为0 |
| aversion | 节点的 ACL 的更改次数 |
| ephemeralOwner | 如果节点是临时节点,则表示创建该节点的会话的 SessionID如果节点是持久节点则该属性值为 0 |
| dataLength | 数据内容的长度 |
| numChildren | 数据节点当前的子节点个数 |
@ -216,7 +216,7 @@ public void getChildrenNodes() throws Exception {
@Test
public void updateNode() throws Exception {
byte[] newData = "defg".getBytes();
client.setData().withVersion(0) // 传入版本号,如果版本号错误则拒绝更新操作,并抛出BadVersion异常
client.setData().withVersion(0) // 传入版本号,如果版本号错误则拒绝更新操作,并抛出 BadVersion 异常
.forPath(nodePath, newData);
}
```
@ -229,7 +229,7 @@ public void deleteNodes() throws Exception {
client.delete()
.guaranteed() // 如果删除失败,那么在会继续执行,直到成功
.deletingChildrenIfNeeded() // 如果有子节点,则递归删除
.withVersion(0) // 传入版本号,如果版本号错误则拒绝删除操作,并抛出BadVersion异常
.withVersion(0) // 传入版本号,如果版本号错误则拒绝删除操作,并抛出 BadVersion 异常
.forPath(nodePath);
}
```
@ -239,7 +239,7 @@ public void deleteNodes() throws Exception {
```java
@Test
public void existNode() throws Exception {
// 如果节点存在则返回其状态信息如果不存在则为null
// 如果节点存在则返回其状态信息如果不存在则为 null
Stat stat = client.checkExists().forPath(nodePath + "aa/bb/cc");
System.out.println("节点是否存在:" + !(stat == null));
}
@ -251,7 +251,7 @@ public void existNode() throws Exception {
### 3.1 创建一次性监听
和Zookeeper原生监听一样使用`usingWatcher`注册的监听是一次性的,即监听只会触发一次,触发后就销毁。示例如下:
Zookeeper 原生监听一样,使用 `usingWatcher` 注册的监听是一次性的,即监听只会触发一次,触发后就销毁。示例如下:
```java
@Test
@ -267,14 +267,14 @@ public void DisposableWatch() throws Exception {
### 3.2 创建永久监听
Curator还提供了创建永久监听的API其使用方式如下
Curator 还提供了创建永久监听的 API其使用方式如下
```java
@Test
public void permanentWatch() throws Exception {
// 使用NodeCache包装节点对其注册的监听作用于节点且是永久性的
// 使用 NodeCache 包装节点,对其注册的监听作用于节点,且是永久性的
NodeCache nodeCache = new NodeCache(client, nodePath);
// 通常设置为true, 代表创建nodeCache时,就去获取对应节点的值并缓存
// 通常设置为 true, 代表创建 nodeCache 时,就去获取对应节点的值并缓存
nodeCache.start(true);
nodeCache.getListenable().addListener(new NodeCacheListener() {
public void nodeChanged() {
@ -291,7 +291,7 @@ public void permanentWatch() throws Exception {
### 3.3 监听子节点
这里以监听`/hadoop`下所有子节点为例,实现方式如下:
这里以监听 `/hadoop` 下所有子节点为例,实现方式如下:
```scala
@Test
@ -300,10 +300,10 @@ public void permanentChildrenNodesWatch() throws Exception {
// 第三个参数代表除了节点状态外,是否还缓存节点内容
PathChildrenCache childrenCache = new PathChildrenCache(client, "/hadoop", true);
/*
* StartMode代表初始化方式:
* StartMode 代表初始化方式:
* NORMAL: 异步初始化
* BUILD_INITIAL_CACHE: 同步初始化
* POST_INITIALIZED_EVENT: 异步并通知,初始化之后会触发INITIALIZED事件
* POST_INITIALIZED_EVENT: 异步并通知,初始化之后会触发 INITIALIZED 事件
*/
childrenCache.start(StartMode.POST_INITIALIZED_EVENT);
@ -315,10 +315,10 @@ public void permanentChildrenNodesWatch() throws Exception {
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) {
switch (event.getType()) {
case INITIALIZED:
System.out.println("childrenCache初始化完成");
System.out.println("childrenCache 初始化完成");
break;
case CHILD_ADDED:
// 需要注意的是: 即使是之前已经存在的子节点也会触发该监听因为会把该子节点加入childrenCache缓存中
// 需要注意的是: 即使是之前已经存在的子节点,也会触发该监听,因为会把该子节点加入 childrenCache 缓存中
System.out.println("增加子节点:" + event.getData().getPath());
break;
case CHILD_REMOVED:
@ -333,4 +333,4 @@ public void permanentChildrenNodesWatch() throws Exception {
});
Thread.sleep(1000 * 1000); //休眠以观察测试效果
}
```
```

View File

@ -31,11 +31,11 @@ zkCli.sh -server hadoop001:2181
### 1.2 help命令
使用`help`可以查看所有命令及格式。
使用 `help` 可以查看所有命令及格式。
### 1.3 查看节点列表
查看节点列表有`ls path``ls2 path`两个命令,后者是前者的增强,不仅可以查看指定路径下的所有节点,还可以查看当前节点的信息。
查看节点列表有 `ls path` `ls2 path` 两个命令,后者是前者的增强,不仅可以查看指定路径下的所有节点,还可以查看当前节点的信息。
```shell
[zk: localhost:2181(CONNECTED) 0] ls /
@ -58,7 +58,7 @@ numChildren = 11
### 1.4 新增节点
```shell
create [-s] [-e] path data acl #其中-s为有序节点-e临时节点
create [-s] [-e] path data acl #其中-s 为有序节点,-e 临时节点
```
创建节点并写入数据:
@ -67,7 +67,7 @@ create [-s] [-e] path data acl #其中-s为有序节点-e临时节点
create /hadoop 123456
```
创建有序节点,此时创建的节点名为指定节点名+自增序号:
创建有序节点,此时创建的节点名为指定节点名 + 自增序号:
```shell
[zk: localhost:2181(CONNECTED) 23] create -s /a "aaa"
@ -110,25 +110,25 @@ dataLength = 6
numChildren = 0
```
节点各个属性如下表。其中一个重要的概念是Zxid(ZooKeeper Transaction Id)ZooKeeper节点的每一次更改都具有唯一的Zxid如果Zxid1小于Zxid2则Zxid1的更改发生在Zxid2更改之前。
节点各个属性如下表。其中一个重要的概念是 Zxid(ZooKeeper Transaction Id)ZooKeeper 节点的每一次更改都具有唯一的 Zxid如果 Zxid1 小于 Zxid2 Zxid1 的更改发生在 Zxid2 更改之前。
| **状态属性** | **说明** |
| -------------- | ------------------------------------------------------------ |
| cZxid | 数据节点创建时的事务ID |
| cZxid | 数据节点创建时的事务 ID |
| ctime | 数据节点创建时的时间 |
| mZxid | 数据节点最后一次更新时的事务ID |
| mZxid | 数据节点最后一次更新时的事务 ID |
| mtime | 数据节点最后一次更新时的时间 |
| pZxid | 数据节点的子节点最后一次被修改时的事务ID |
| pZxid | 数据节点的子节点最后一次被修改时的事务 ID |
| cversion | 子节点的更改次数 |
| dataVersion | 节点数据的更改次数 |
| aclVersion | 节点的ACL的更改次数 |
| ephemeralOwner | 如果节点是临时节点则表示创建该节点的会话的SessionID如果节点是持久节点则该属性值为0 |
| aclVersion | 节点的 ACL 的更改次数 |
| ephemeralOwner | 如果节点是临时节点,则表示创建该节点的会话的 SessionID如果节点是持久节点则该属性值为 0 |
| dataLength | 数据内容的长度 |
| numChildren | 数据节点当前的子节点个数 |
#### 2. 查看节点状态
可以使用`stat`命令查看节点状态,它的返回值和`get`命令类似,但不会返回节点数据。
可以使用 `stat` 命令查看节点状态,它的返回值和 `get` 命令类似,但不会返回节点数据。
```shell
[zk: localhost:2181(CONNECTED) 32] stat /hadoop
@ -147,7 +147,7 @@ numChildren = 0
### 1.6 更新节点
更新节点的命令是`set`,可以直接进行修改,如下:
更新节点的命令是 `set`,可以直接进行修改,如下:
```shell
[zk: localhost:2181(CONNECTED) 33] set /hadoop 345
@ -157,14 +157,14 @@ mZxid = 0x14c
mtime = Fri May 24 17:13:05 CST 2019
pZxid = 0x14b
cversion = 0
dataVersion = 1 # 注意更改后此时版本号为1默认创建时为0
dataVersion = 1 # 注意更改后此时版本号为 1默认创建时为 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 0
```
也可以基于版本号进行更改,此时类似于乐观锁机制,当你传入的数据版本号(dataVersion)和当前节点的数据版本号不符合时zookeeper会拒绝本次修改
也可以基于版本号进行更改,此时类似于乐观锁机制,当你传入的数据版本号 (dataVersion) 和当前节点的数据版本号不符合时zookeeper 会拒绝本次修改:
```shell
[zk: localhost:2181(CONNECTED) 34] set /hadoop 678 0
@ -179,7 +179,7 @@ version No is not valid : /hadoop #无效的版本号
delete path [version]
```
和更新节点数据一样,也可以传入版本号,当你传入的数据版本号(dataVersion)和当前节点的数据版本号不符合时zookeeper不会执行删除操作。
和更新节点数据一样,也可以传入版本号,当你传入的数据版本号 (dataVersion) 和当前节点的数据版本号不符合时zookeeper 不会执行删除操作。
```shell
[zk: localhost:2181(CONNECTED) 36] delete /hadoop 0
@ -188,13 +188,13 @@ version No is not valid : /hadoop #无效的版本号
[zk: localhost:2181(CONNECTED) 38]
```
要想删除某个节点及其所有后代节点,可以使用递归删除,命令为`rmr path`
要想删除某个节点及其所有后代节点,可以使用递归删除,命令为 `rmr path`
## 二、监听器
### 2.1 get path [watch]
使用`get path [watch]`注册的监听器能够在节点内容发生改变的时候向客户端发出通知。需要注意的是zookeeper的触发器是一次性的(One-time trigger),即触发一次后就会立即失效。
使用 `get path [watch]` 注册的监听器能够在节点内容发生改变的时候,向客户端发出通知。需要注意的是 zookeeper 的触发器是一次性的 (One-time trigger),即触发一次后就会立即失效。
```shell
[zk: localhost:2181(CONNECTED) 4] get /hadoop watch
@ -205,7 +205,7 @@ WatchedEvent state:SyncConnected type:NodeDataChanged path:/hadoop #节点值
### 2.2 stat path [watch]
使用`stat path [watch]`注册的监听器能够在节点状态发生改变的时候,向客户端发出通知。
使用 `stat path [watch]` 注册的监听器能够在节点状态发生改变的时候,向客户端发出通知。
```shell
[zk: localhost:2181(CONNECTED) 7] stat /hadoop watch
@ -216,7 +216,7 @@ WatchedEvent state:SyncConnected type:NodeDataChanged path:/hadoop #节点值
### 2.3 ls\ls2 path [watch]
使用`ls path [watch]``ls2 path [watch]`注册的监听器能够监听该节点下所有**子节点**的增加和删除操作。
使用 `ls path [watch]``ls2 path [watch]` 注册的监听器能够监听该节点下所有**子节点**的增加和删除操作。
```shell
[zk: localhost:2181(CONNECTED) 9] ls /hadoop watch
@ -233,14 +233,14 @@ WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/hadoop
| 命令 | 功能描述 |
| ---- | ------------------------------------------------------------ |
| conf | 打印服务配置的详细信息。 |
| cons | 列出连接到此服务器的所有客户端的完整连接/会话详细信息。包括接收/发送的数据包数量会话ID操作延迟上次执行的操作等信息。 |
| dump | 列出未完成的会话和临时节点。这只适用于Leader节点。 |
| cons | 列出连接到此服务器的所有客户端的完整连接/会话详细信息。包括接收/发送的数据包数量,会话 ID操作延迟上次执行的操作等信息。 |
| dump | 列出未完成的会话和临时节点。这只适用于 Leader 节点。 |
| envi | 打印服务环境的详细信息。 |
| ruok | 测试服务是否处于正确状态。如果正确则返回“imok”否则不做任何相应。 |
| stat | 列出服务器和连接客户端的简要详细信息。 |
| wchs | 列出所有watch的简单信息。 |
| wchc | 按会话列出服务器watch的详细信息。 |
| wchp | 按路径列出服务器watch的详细信息。 |
| wchs | 列出所有 watch 的简单信息。 |
| wchc | 按会话列出服务器 watch 的详细信息。 |
| wchp | 按路径列出服务器 watch 的详细信息。 |
> 更多四字命令可以参阅官方文档https://zookeeper.apache.org/doc/current/zookeeperAdmin.html

View File

@ -24,39 +24,39 @@
## 一、Zookeeper简介
Zookeeper是一个开源的分布式协调服务目前由Apache进行维护。Zookeeper可以用于实现分布式系统中常见的发布/订阅、负载均衡、命令服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。它具有以下特性
Zookeeper 是一个开源的分布式协调服务,目前由 Apache 进行维护。Zookeeper 可以用于实现分布式系统中常见的发布/订阅、负载均衡、命令服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。它具有以下特性:
+ **顺序一致性**从一个客户端发起的事务请求最终都会严格按照其发起顺序被应用到Zookeeper中
+ **顺序一致性**:从一个客户端发起的事务请求,最终都会严格按照其发起顺序被应用到 Zookeeper 中;
+ **原子性**:所有事务请求的处理结果在整个集群中所有机器上都是一致的;不存在部分机器应用了该事务,而另一部分没有应用的情况;
+ **单一视图**:所有客户端看到的服务端数据模型都是一致的;
+ **可靠性**:一旦服务端成功应用了一个事务,则其引起的改变会一直保留,直到被另外一个事务所更改;
+ **实时性**一旦一个事务被成功应用后Zookeeper可以保证客户端立即可以读取到这个事务变更后的最新状态的数据。
+ **实时性**一旦一个事务被成功应用后Zookeeper 可以保证客户端立即可以读取到这个事务变更后的最新状态的数据。
## 二、Zookeeper设计目标
Zookeeper致力于为那些高吞吐的大型分布式系统提供一个高性能、高可用、且具有严格顺序访问控制能力的分布式协调服务。它具有以下四个目标
Zookeeper 致力于为那些高吞吐的大型分布式系统提供一个高性能、高可用、且具有严格顺序访问控制能力的分布式协调服务。它具有以下四个目标:
### 2.1 目标一:简单的数据模型
Zookeeper通过树形结构来存储数据它由一系列被称为ZNode的数据节点组成类似于常见的文件系统。不过和常见的文件系统不同Zookeeper将数据全量存储在内存中以此来实现高吞吐减少访问延迟。
Zookeeper 通过树形结构来存储数据,它由一系列被称为 ZNode 的数据节点组成类似于常见的文件系统。不过和常见的文件系统不同Zookeeper 将数据全量存储在内存中,以此来实现高吞吐,减少访问延迟。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/zookeeper-zknamespace.jpg"/> </div>
### 2.2 目标二:构建集群
可以由一组Zookeeper服务构成Zookeeper集群集群中每台机器都会单独在内存中维护自身的状态并且每台机器之间都保持着通讯只要集群中有半数机器能够正常工作那么整个集群就可以正常提供服务。
可以由一组 Zookeeper 服务构成 Zookeeper 集群,集群中每台机器都会单独在内存中维护自身的状态,并且每台机器之间都保持着通讯,只要集群中有半数机器能够正常工作,那么整个集群就可以正常提供服务。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/zookeeper-zkservice.jpg"/> </div>
### 2.3 目标三:顺序访问
对于来自客户端的每个更新请求Zookeeper都会分配一个全局唯一的递增ID这个ID反映了所有事务请求的先后顺序。
对于来自客户端的每个更新请求Zookeeper 都会分配一个全局唯一的递增 ID这个 ID 反映了所有事务请求的先后顺序。
### 2.4 目标四:高性能高可用
ZooKeeper将数据存全量储在内存中以保持高性能并通过服务集群来实现高可用由于Zookeeper的所有更新和删除都是基于事务的所以其在读多写少的应用场景中有着很高的性能表现。
ZooKeeper 将数据存全量储在内存中以保持高性能,并通过服务集群来实现高可用,由于 Zookeeper 的所有更新和删除都是基于事务的,所以其在读多写少的应用场景中有着很高的性能表现。
@ -64,52 +64,52 @@ ZooKeeper将数据存全量储在内存中以保持高性能并通过服务
### 3.1 集群角色
Zookeeper集群中的机器分为以下三种角色
Zookeeper 集群中的机器分为以下三种角色:
+ **Leader** :为客户端提供读写服务,并维护集群状态,它是由集群选举所产生的;
+ **Follower** 为客户端提供读写服务并定期向Leader汇报自己的节点状态。同时也参与写操作“过半写成功”的策略和Leader的选举
+ **Observer** 为客户端提供读写服务并定期向Leader汇报自己的节点状态但不参与写操作“过半写成功”的策略和Leader的选举因此Observer可以在不影响写性能的情况下提升集群的读性能。
+ **Follower** :为客户端提供读写服务,并定期向 Leader 汇报自己的节点状态。同时也参与写操作“过半写成功”的策略和 Leader 的选举;
+ **Observer** :为客户端提供读写服务,并定期向 Leader 汇报自己的节点状态,但不参与写操作“过半写成功”的策略和 Leader 的选举,因此 Observer 可以在不影响写性能的情况下提升集群的读性能。
### 3.2 会话
Zookeeper客户端通过TCP长连接连接到服务集群会话(Session)从第一次连接开始就已经建立之后通过心跳检测机制来保持有效的会话状态。通过这个连接客户端可以发送请求并接收响应同时也可以接收到Watch事件的通知。
Zookeeper 客户端通过 TCP 长连接连接到服务集群,会话 (Session) 从第一次连接开始就已经建立,之后通过心跳检测机制来保持有效的会话状态。通过这个连接,客户端可以发送请求并接收响应,同时也可以接收到 Watch 事件的通知。
关于会话中另外一个核心的概念是sessionTimeOut(会话超时时间),当由于网络故障或者客户端主动断开等原因,导致连接断开,此时只要在会话超时时间之内重新建立连接,则之前创建的会话依然有效。
关于会话中另外一个核心的概念是 sessionTimeOut(会话超时时间),当由于网络故障或者客户端主动断开等原因,导致连接断开,此时只要在会话超时时间之内重新建立连接,则之前创建的会话依然有效。
### 3.3 数据节点
Zookeeper数据模型是由一系列基本数据单元`Znode`(数据节点)组成的节点树,其中根节点为`/`。每个节点上都会保存自己的数据和节点信息。Zookeeper中节点可以分为两大类
Zookeeper 数据模型是由一系列基本数据单元 `Znode`(数据节点) 组成的节点树,其中根节点为 `/`。每个节点上都会保存自己的数据和节点信息。Zookeeper 中节点可以分为两大类:
+ **持久节点** :节点一旦创建,除非被主动删除,否则一直存在;
+ **临时节点** :一旦创建该节点的客户端会话失效,则所有该客户端创建的临时节点都会被删除。
临时节点和持久节点都可以添加一个特殊的属性:`SEQUENTIAL`代表该节点是否具有递增属性。如果指定该属性那么在这个节点创建时Zookeeper会自动在其节点名称后面追加一个由父节点维护的递增数字。
临时节点和持久节点都可以添加一个特殊的属性:`SEQUENTIAL`代表该节点是否具有递增属性。如果指定该属性那么在这个节点创建时Zookeeper 会自动在其节点名称后面追加一个由父节点维护的递增数字。
### 3.4 节点信息
每个ZNode节点在存储数据的同时都会维护一个叫做`Stat`的数据结构,里面存储了关于该节点的全部状态信息。如下:
每个 ZNode 节点在存储数据的同时,都会维护一个叫做 `Stat` 的数据结构,里面存储了关于该节点的全部状态信息。如下:
| **状态属性** | **说明** |
| -------------- | ------------------------------------------------------------ |
| czxid | 数据节点创建时的事务ID |
| czxid | 数据节点创建时的事务 ID |
| ctime | 数据节点创建时的时间 |
| mzxid | 数据节点最后一次更新时的事务ID |
| mzxid | 数据节点最后一次更新时的事务 ID |
| mtime | 数据节点最后一次更新时的时间 |
| pzxid | 数据节点的子节点最后一次被修改时的事务ID |
| pzxid | 数据节点的子节点最后一次被修改时的事务 ID |
| cversion | 子节点的更改次数 |
| version | 节点数据的更改次数 |
| aversion | 节点的ACL的更改次数 |
| ephemeralOwner | 如果节点是临时节点则表示创建该节点的会话的SessionID如果节点是持久节点则该属性值为0 |
| aversion | 节点的 ACL 的更改次数 |
| ephemeralOwner | 如果节点是临时节点,则表示创建该节点的会话的 SessionID如果节点是持久节点则该属性值为 0 |
| dataLength | 数据内容的长度 |
| numChildren | 数据节点当前的子节点个数 |
### 3.5 Watcher
Zookeeper中一个常用的功能是Watcher(事件监听器)它允许用户在指定节点上针对感兴趣的事件注册监听当事件发生时监听器会被触发并将事件信息推送到客户端。该机制是Zookeeper实现分布式协调服务的重要特性。
Zookeeper 中一个常用的功能是 Watcher(事件监听器),它允许用户在指定节点上针对感兴趣的事件注册监听,当事件发生时,监听器会被触发,并将事件信息推送到客户端。该机制是 Zookeeper 实现分布式协调服务的重要特性。
### 3.6 ACL
Zookeeper采用ACL(Access Control Lists)策略来进行权限控制类似于UNIX文件系统的权限控制。它定义了如下五种权限
Zookeeper 采用 ACL(Access Control Lists) 策略来进行权限控制,类似于 UNIX 文件系统的权限控制。它定义了如下五种权限:
- **CREATE**:允许创建子节点;
- **READ**:允许从节点获取数据并列出其子节点;
@ -123,29 +123,29 @@ Zookeeper采用ACL(Access Control Lists)策略来进行权限控制类似于U
### 4.1 ZAB协议与数据一致性
ZAB协议是Zookeeper专门设计的一种支持崩溃恢复的原子广播协议。通过该协议Zookeepe基于主从模式的系统架构来保持集群中各个副本之间数据的一致性。具体如下
ZAB 协议是 Zookeeper 专门设计的一种支持崩溃恢复的原子广播协议。通过该协议Zookeepe 基于主从模式的系统架构来保持集群中各个副本之间数据的一致性。具体如下:
Zookeeper使用一个单一的主进程来接收并处理客户端的所有事务请求并采用原子广播协议将数据状态的变更以事务Proposal的形式广播到所有的副本进程上去。如下图
Zookeeper 使用一个单一的主进程来接收并处理客户端的所有事务请求,并采用原子广播协议将数据状态的变更以事务 Proposal 的形式广播到所有的副本进程上去。如下图:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/zookeeper-zkcomponents.jpg"/> </div>
具体流程如下:
所有的事务请求必须由唯一的Leader服务来处理Leader服务将事务请求转换为事务Proposal并将该Proposal分发给集群中所有的Follower服务。如果有半数的Follower服务进行了正确的反馈那么Leader就会再次向所有的Follower发出Commit消息要求将前一个Proposal进行提交。
所有的事务请求必须由唯一的 Leader 服务来处理Leader 服务将事务请求转换为事务 Proposal并将该 Proposal 分发给集群中所有的 Follower 服务。如果有半数的 Follower 服务进行了正确的反馈,那么 Leader 就会再次向所有的 Follower 发出 Commit 消息,要求将前一个 Proposal 进行提交。
### 4.2 ZAB协议的内容
ZAB协议包括两种基本的模式分别是崩溃恢复和消息广播
ZAB 协议包括两种基本的模式,分别是崩溃恢复和消息广播:
#### 1. 崩溃恢复
当整个服务框架在启动过程中或者当Leader服务器出现异常时ZAB协议就会进入恢复模式通过过半选举机制产生新的Leader之后其他机器将从新的Leader上同步状态当有过半机器完成状态同步后就退出恢复模式进入消息广播模式。
当整个服务框架在启动过程中,或者当 Leader 服务器出现异常时ZAB 协议就会进入恢复模式,通过过半选举机制产生新的 Leader之后其他机器将从新的 Leader 上同步状态,当有过半机器完成状态同步后,就退出恢复模式,进入消息广播模式。
#### 2. 消息广播
ZAB协议的消息广播过程使用的是原子广播协议。在整个消息的广播过程中Leader服务器会每个事物请求生成对应的Proposal并为其分配一个全局唯一的递增的事务ID(ZXID),之后再对其进行广播。具体过程如下:
ZAB 协议的消息广播过程使用的是原子广播协议。在整个消息的广播过程中Leader 服务器会每个事物请求生成对应的 Proposal并为其分配一个全局唯一的递增的事务 ID(ZXID),之后再对其进行广播。具体过程如下:
Leader服务会为每一个Follower服务器分配一个单独的队列然后将事务Proposal依次放入队列中并根据FIFO(先进先出)的策略进行消息发送。Follower服务在接收到Proposal后会将其以事务日志的形式写入本地磁盘中并在写入成功后反馈给Leader一个Ack响应。当Leader接收到超过半数FollowerAck响应后就会广播一个Commit消息给所有的Follower以通知其进行事务提交之后Leader自身也会完成对事务的提交。而每一个Follower则在接收到Commit消息后完成事务的提交。
Leader 服务会为每一个 Follower 服务器分配一个单独的队列,然后将事务 Proposal 依次放入队列中,并根据 FIFO(先进先出) 的策略进行消息发送。Follower 服务在接收到 Proposal 后,会将其以事务日志的形式写入本地磁盘中,并在写入成功后反馈给 Leader 一个 Ack 响应。当 Leader 接收到超过半数 FollowerAck 响应后,就会广播一个 Commit 消息给所有的 Follower 以通知其进行事务提交,之后 Leader 自身也会完成对事务的提交。而每一个 Follower 则在接收到 Commit 消息后,完成事务的提交。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/zookeeper-brocast.jpg"/> </div>
@ -157,41 +157,41 @@ Leader服务会为每一个Follower服务器分配一个单独的队列然后
数据的发布/订阅系统,通常也用作配置中心。在分布式系统中,你可能有成千上万个服务节点,如果想要对所有服务的某项配置进行更改,由于数据节点过多,你不可逐台进行修改,而应该在设计时采用统一的配置中心。之后发布者只需要将新的配置发送到配置中心,所有服务节点即可自动下载并进行更新,从而实现配置的集中管理和动态更新。
Zookeeper通过Watcher机制可以实现数据的发布和订阅。分布式系统的所有的服务节点可以对某个ZNode注册监听之后只需要将新的配置写入该ZNode所有服务节点都会收到该事件。
Zookeeper 通过 Watcher 机制可以实现数据的发布和订阅。分布式系统的所有的服务节点可以对某个 ZNode 注册监听,之后只需要将新的配置写入该 ZNode所有服务节点都会收到该事件。
### 5.2 命名服务
在分布式系统中通常需要一个全局唯一的名字如生成全局唯一的订单号等Zookeeper可以通过顺序节点的特性来生成全局唯一ID从而可以对分布式系统提供命名服务。
在分布式系统中通常需要一个全局唯一的名字如生成全局唯一的订单号等Zookeeper 可以通过顺序节点的特性来生成全局唯一 ID从而可以对分布式系统提供命名服务。
### 5.3 Master选举
分布式系统一个重要的模式就是主从模式(Master/Salves)Zookeeper可以用于该模式下的Matser选举。可以让所有服务节点去竞争性地创建同一个ZNode由于Zookeeper不能有路径相同的ZNode必然只有一个服务节点能够创建成功这样该服务节点就可以成为Master节点。
分布式系统一个重要的模式就是主从模式 (Master/Salves)Zookeeper 可以用于该模式下的 Matser 选举。可以让所有服务节点去竞争性地创建同一个 ZNode由于 Zookeeper 不能有路径相同的 ZNode必然只有一个服务节点能够创建成功这样该服务节点就可以成为 Master 节点。
### 5.4 分布式锁
可以通过Zookeeper的临时节点和Watcher机制来实现分布式锁这里以排它锁为例进行说明
可以通过 Zookeeper 的临时节点和 Watcher 机制来实现分布式锁,这里以排它锁为例进行说明:
分布式系统的所有服务节点可以竞争性地去创建同一个临时ZNode由于Zookeeper不能有路径相同的ZNode必然只有一个服务节点能够创建成功此时可以认为该节点获得了锁。其他没有获得锁的服务节点通过在该ZNode上注册监听从而当锁释放时再去竞争获得锁。锁的释放情况有以下两种
分布式系统的所有服务节点可以竞争性地去创建同一个临时 ZNode由于 Zookeeper 不能有路径相同的 ZNode必然只有一个服务节点能够创建成功此时可以认为该节点获得了锁。其他没有获得锁的服务节点通过在该 ZNode 上注册监听,从而当锁释放时再去竞争获得锁。锁的释放情况有以下两种:
+ 当正常执行完业务逻辑后客户端主动将临时ZNode删除此时锁被释放
+ 当获得锁的客户端发生宕机时临时ZNode会被自动删除此时认为锁已经释放。
+ 当正常执行完业务逻辑后,客户端主动将临时 ZNode 删除,此时锁被释放;
+ 当获得锁的客户端发生宕机时,临时 ZNode 会被自动删除,此时认为锁已经释放。
当锁被释放后,其他服务节点则再次去竞争性地进行创建,但每次都只有一个服务节点能够获取到锁,这就是排他锁。
### 5.5 集群管理
Zookeeper还能解决大多数分布式系统中的问题
Zookeeper 还能解决大多数分布式系统中的问题:
+ 如可以通过创建临时节点来建立心跳检测机制。如果分布式系统的某个服务节点宕机了,则其持有的会话会超时,此时该临时节点会被删除,相应的监听事件就会被触发。
+ 分布式系统的每个服务节点还可以将自己的节点状态写入临时节点,从而完成状态报告或节点工作进度汇报。
+ 通过数据的订阅和发布功能Zookeeper还能对分布式系统进行模块的解耦和任务的调度。
+ 通过数据的订阅和发布功能Zookeeper 还能对分布式系统进行模块的解耦和任务的调度。
+ 通过监听机制,还能对分布式系统的服务节点进行动态上下线,从而实现服务的动态扩容。
<br/>
## 参考资料
1. 倪超 . 从PaxosZookeeper——分布式一致性原理与实践 . 电子工业出版社 . 2015-02-01
1. 倪超 . 从 PaxosZookeeper——分布式一致性原理与实践 . 电子工业出版社 . 2015-02-01

View File

@ -12,9 +12,9 @@
### 1.1 下载并解压
Azkaban 在3.0版本之后就不提供对应的安装包,需要自己下载源码进行编译。
Azkaban 在 3.0 版本之后就不提供对应的安装包,需要自己下载源码进行编译。
下载所需版本的源码Azkaban的源码托管在GitHub上地址为https://github.com/azkaban/azkaban 。可以使用`git clone`的方式获取源码,也可以使用`wget`直接下载对应release版本的`tar.gz`文件,这里我采用第二种方式:
下载所需版本的源码Azkaban 的源码托管在 GitHub 上,地址为 https://github.com/azkaban/azkaban 。可以使用 `git clone` 的方式获取源码,也可以使用 `wget` 直接下载对应 release 版本的 `tar.gz` 文件,这里我采用第二种方式:
```shell
# 下载
@ -27,31 +27,31 @@ tar -zxvf azkaban-3.70.0.tar.gz
#### 1. JDK
Azkaban 编译依赖JDK 1.8+ JDK安装方式见本仓库
Azkaban 编译依赖 JDK 1.8+ JDK 安装方式见本仓库:
> [Linux环境下JDK安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK安装.md)
> [Linux 环境下 JDK 安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK 安装.md)
#### 2. Gradle
Azkaban 3.70.0编译需要依赖`gradle-4.6-all.zip`。Gradle是一个项目自动化构建开源工具类似于Maven但由于采用Groovy语言进行项目配置所以比Maven更为灵活目前广泛用于Android开发、Spring项目的构建。
Azkaban 3.70.0 编译需要依赖 `gradle-4.6-all.zip`。Gradle 是一个项目自动化构建开源工具,类似于 Maven但由于采用 Groovy 语言进行项目配置,所以比 Maven 更为灵活,目前广泛用于 Android 开发、Spring 项目的构建。
需要注意的是不同版本的Azkaban依赖Gradle版本不同可以在解压后的`/gradle/wrapper/gradle-wrapper.properties`文件查看
需要注意的是不同版本的 Azkaban 依赖 Gradle 版本不同,可以在解压后的 `/gradle/wrapper/gradle-wrapper.properties` 文件查看
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-gradle-wrapper.png"/> </div>
在编译时程序会自动去图中所示的地址进行下载,但是下载速度很慢。为避免影响编译过程,建议先手动下载至`/gradle/wrapper/`目录下:
在编译时程序会自动去图中所示的地址进行下载,但是下载速度很慢。为避免影响编译过程,建议先手动下载至 `/gradle/wrapper/` 目录下:
```shell
# wget https://services.gradle.org/distributions/gradle-4.6-all.zip
```
然后修改配置文件`gradle-wrapper.properties`中的`distributionUrl`属性指明使用本地的gradle。
然后修改配置文件 `gradle-wrapper.properties` 中的 `distributionUrl` 属性,指明使用本地的 gradle。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-gradle-wrapper-2.png"/> </div>
#### 3. Git
Azkaban 的编译过程需要用Git下载部分JAR包所以需要预先安装Git
Azkaban 的编译过程需要用 Git 下载部分 JAR 包,所以需要预先安装 Git
```shell
# yum install git
@ -59,7 +59,7 @@ Azkaban 的编译过程需要用Git下载部分JAR包所以需要预先安装
### 1.3 项目编译
在根目录下执行编译命令,编译成功后会有`BUILD SUCCESSFUL`的提示:
在根目录下执行编译命令,编译成功后会有 `BUILD SUCCESSFUL` 的提示:
```shell
# ./gradlew build installDist -x test
@ -67,8 +67,8 @@ Azkaban 的编译过程需要用Git下载部分JAR包所以需要预先安装
编译过程中需要注意以下问题:
+ 因为编译的过程需要下载大量的Jar包下载速度根据网络情况而定通常都不会很快如果网络不好耗费半个小时一个小时都是很正常的
+ 编译过程中如果出现网络问题而导致JAR无法下载编译可能会被强行终止这时候重复执行编译命令即可gradle会把已经下载的JAR缓存到本地所以不用担心会重复下载JAR包。
+ 因为编译的过程需要下载大量的 Jar 包,下载速度根据网络情况而定,通常都不会很快,如果网络不好,耗费半个小时,一个小时都是很正常的;
+ 编译过程中如果出现网络问题而导致 JAR 无法下载编译可能会被强行终止这时候重复执行编译命令即可gradle 会把已经下载的 JAR 缓存到本地,所以不用担心会重复下载 JAR 包。
@ -76,12 +76,12 @@ Azkaban 的编译过程需要用Git下载部分JAR包所以需要预先安装
>After version 3.0, we provide two modes: the stand alone “solo-server” mode and distributed multiple-executor mode. The following describes thedifferences between the two modes.
按照官方文档的说明Azkaban 3.x 之后版本提供2种运行模式:
按照官方文档的说明Azkaban 3.x 之后版本提供 2 种运行模式:
+ **solo server model(单服务模式)** 元数据默认存放在内置的H2数据库可以修改为MySQL该模式中`webServer`(管理服务器)和 `executorServer`(执行服务器)运行在同一个进程中,进程名是`AzkabanSingleServer`。该模式适用于小规模工作流的调度。
- **multiple-executor(分布式多服务模式)** 存放元数据的数据库为MySQLMySQL应采用主从模式进行备份和容错。这种模式下`webServer``executorServer`在不同进程中运行,彼此之间互不影响,适合用于生产环境。
+ **solo server model(单服务模式)** :元数据默认存放在内置的 H2 数据库(可以修改为 MySQL该模式中 `webServer`(管理服务器) `executorServer`(执行服务器) 运行在同一个进程中,进程名是 `AzkabanSingleServer`。该模式适用于小规模工作流的调度。
- **multiple-executor(分布式多服务模式)** :存放元数据的数据库为 MySQLMySQL 应采用主从模式进行备份和容错。这种模式下 `webServer``executorServer` 在不同进程中运行,彼此之间互不影响,适合用于生产环境。
下面主要介绍`Solo Server`模式。
下面主要介绍 `Solo Server` 模式。
@ -89,7 +89,7 @@ Azkaban 的编译过程需要用Git下载部分JAR包所以需要预先安装
### 2.1 解压
Solo Server 模式安装包在编译后的`/azkaban-solo-server/build/distributions`目录下,找到后进行解压即可:
Solo Server 模式安装包在编译后的 `/azkaban-solo-server/build/distributions` 目录下,找到后进行解压即可:
```shell
# 解压
@ -98,13 +98,13 @@ tar -zxvf azkaban-solo-server-3.70.0.tar.gz
### 2.2 修改时区
这一步不是必须的。但是因为Azkaban默认采用的时区是`America/Los_Angeles`,如果你的调度任务中有定时任务的话,就需要进行相应的更改,这里我更改为常用的`Asia/Shanghai`
这一步不是必须的。但是因为 Azkaban 默认采用的时区是 `America/Los_Angeles`,如果你的调度任务中有定时任务的话,就需要进行相应的更改,这里我更改为常用的 `Asia/Shanghai`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-setting.png"/> </div>
### 2.3 启动
执行启动命令,需要注意的是一定要在根目录下执行,不能进入`bin`目录下执行,不然会抛出`Cannot find 'database.properties'`异常。
执行启动命令,需要注意的是一定要在根目录下执行,不能进入 `bin` 目录下执行,不然会抛出 `Cannot find 'database.properties'` 异常。
```shell
# bin/start-solo.sh
@ -112,13 +112,13 @@ tar -zxvf azkaban-solo-server-3.70.0.tar.gz
### 2.4 验证
验证方式一:使用`jps`命令查看是否有`AzkabanSingleServer`进程:
验证方式一:使用 `jps` 命令查看是否有 `AzkabanSingleServer` 进程:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/akaban-jps.png"/> </div>
<br/>
验证方式二访问8081端口查看Web UI界面默认的登录名密码都是`azkaban`,如果需要修改或新增用户,可以在`conf/azkaban-users.xml `文件中进行配置:
验证方式二:访问 8081 端口,查看 Web UI 界面,默认的登录名密码都是 `azkaban`,如果需要修改或新增用户,可以在 `conf/azkaban-users.xml ` 文件中进行配置:
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/azkaban-web-ui.png"/> </div>

View File

@ -10,14 +10,14 @@
### 1.1 JDK版本说明
HBase 需要依赖JDK环境同时HBase 2.0+ 以上版本不再支持JDK 1.7 需要安装JDK 1.8+ 。JDK 安装方式见本仓库:
HBase 需要依赖 JDK 环境,同时 HBase 2.0+ 以上版本不再支持 JDK 1.7 ,需要安装 JDK 1.8+ 。JDK 安装方式见本仓库:
> [Linux环境下JDK安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK安装.md)
> [Linux 环境下 JDK 安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK 安装.md)
### 1.2 Standalone模式和伪集群模式的区别
+ 在`Standalone`模式下,所有守护进程都运行在一个`jvm`进程/实例中;
+ 在伪分布模式下HBase仍然在单个主机上运行但是每个守护进程(HMasterHRegionServer 和 ZooKeeper)则分别作为一个单独的进程运行。
+ 在 `Standalone` 模式下,所有守护进程都运行在一个 `jvm` 进程/实例中;
+ 在伪分布模式下HBase 仍然在单个主机上运行,但是每个守护进程 (HMasterHRegionServer 和 ZooKeeper) 则分别作为一个单独的进程运行。
**说明:两种模式任选其一进行部署即可,对于开发测试来说区别不大。**
@ -27,7 +27,7 @@ HBase 需要依赖JDK环境同时HBase 2.0+ 以上版本不再支持JDK 1.7
### 2.1 下载并解压
从[官方网站](https://hbase.apache.org/downloads.html)下载所需要版本的二进制安装包,并进行解压:
从[官方网站](https://hbase.apache.org/downloads.html) 下载所需要版本的二进制安装包,并进行解压:
```shell
# tar -zxvf hbase-2.1.4-bin.tar.gz
@ -54,14 +54,14 @@ export PATH=$HBASE_HOME/bin:$PATH
### 2.3 进行HBase相关配置
修改安装目录下的`conf/hbase-env.sh`,指定JDK的安装路径
修改安装目录下的 `conf/hbase-env.sh`,指定 JDK 的安装路径:
```shell
# The java implementation to use. Java 1.8+ required.
export JAVA_HOME=/usr/java/jdk1.8.0_201
```
修改安装目录下的`conf/hbase-site.xml`,增加如下配置:
修改安装目录下的 `conf/hbase-site.xml`,增加如下配置:
```xml
<configuration>
@ -80,15 +80,15 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201
</configuration>
```
`hbase.rootdir`: 配置hbase数据的存储路径
`hbase.rootdir`: 配置 hbase 数据的存储路径;
`hbase.zookeeper.property.dataDir`: 配置zookeeper数据的存储路径
`hbase.zookeeper.property.dataDir`: 配置 zookeeper 数据的存储路径;
`hbase.unsafe.stream.capability.enforce`: 使用本地文件系统存储不使用HDFS的情况下需要禁用此配置设置为false。
`hbase.unsafe.stream.capability.enforce`: 使用本地文件系统存储,不使用 HDFS 的情况下需要禁用此配置,设置为 false。
### 2.4 启动HBase
由于已经将HBasebin目录配置到环境变量直接使用以下命令启动
由于已经将 HBasebin 目录配置到环境变量,直接使用以下命令启动:
```shell
# start-hbase.sh
@ -96,7 +96,7 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201
### 2.5 验证启动是否成功
验证方式一 :使用`jps`命令查看HMaster进程是否启动。
验证方式一 :使用 `jps` 命令查看 HMaster 进程是否启动。
```
[root@hadoop001 hbase-2.1.4]# jps
@ -104,7 +104,7 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201
15500 HMaster
```
验证方式二 访问HBaseWeb UI 页面,默认端口为`16010`
验证方式二 :访问 HBaseWeb UI 页面,默认端口为 `16010`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-web-ui.png"/> </div>
@ -114,13 +114,13 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201
### 3.1 Hadoop单机伪集群安装
这里我们采用HDFS作为HBase的存储方案需要预先安装Hadoop。Hadoop的安装方式单独整理至
这里我们采用 HDFS 作为 HBase 的存储方案,需要预先安装 Hadoop。Hadoop 的安装方式单独整理至:
> [Hadoop单机伪集群搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Hadoop单机版本环境搭建.md)
> [Hadoop 单机伪集群搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Hadoop 单机版本环境搭建.md)
### 3.2 Hbase版本选择
HBase的版本必须要与Hadoop的版本兼容不然会出现各种Jar包冲突。这里我Hadoop安装的版本为`hadoop-2.6.0-cdh5.15.2`为保持版本一致选择的HBase版本为`hbase-1.2.0-cdh5.15.2` 。所有软件版本如下:
HBase 的版本必须要与 Hadoop 的版本兼容,不然会出现各种 Jar 包冲突。这里我 Hadoop 安装的版本为 `hadoop-2.6.0-cdh5.15.2`,为保持版本一致,选择的 HBase 版本为 `hbase-1.2.0-cdh5.15.2` 。所有软件版本如下:
+ Hadoop 版本: hadoop-2.6.0-cdh5.15.2
+ HBase 版本: hbase-1.2.0-cdh5.15.2
@ -159,14 +159,14 @@ export PATH=$HBASE_HOME/bin:$PATH
### 3.5 进行HBase相关配置
1.修改安装目录下的`conf/hbase-env.sh`,指定JDK的安装路径
1.修改安装目录下的 `conf/hbase-env.sh`,指定 JDK 的安装路径:
```shell
# The java implementation to use. Java 1.7+ required.
export JAVA_HOME=/usr/java/jdk1.8.0_201
```
2.修改安装目录下的`conf/hbase-site.xml`,增加如下配置(hadoop001为主机名)
2.修改安装目录下的 `conf/hbase-site.xml`,增加如下配置 (hadoop001 为主机名)
```xml
<configuration>
@ -175,7 +175,7 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<!--指定 HBase 数据存储路径为HDFS上的hbase目录,hbase目录不需要预先创建程序会自动创建-->
<!--指定 HBase 数据存储路径为 HDFS 上的 hbase 目录,hbase 目录不需要预先创建,程序会自动创建-->
<property>
<name>hbase.rootdir</name>
<value>hdfs://hadoop001:8020/hbase</value>
@ -188,7 +188,7 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201
</configuration>
```
3.修改安装目录下的`conf/regionservers`指定region servers的地址修改后其内容如下
3.修改安装目录下的 `conf/regionservers`,指定 region servers 的地址,修改后其内容如下:
```shell
hadoop001
@ -206,7 +206,7 @@ hadoop001
### 3.7 验证启动是否成功
验证方式一 :使用`jps`命令查看进程。其中`HMaster``HRegionServer`HBase的进程`HQuorumPeer`HBase内置的Zookeeper的进程其余的为HDFSYARN的进程。
验证方式一 :使用 `jps` 命令查看进程。其中 `HMaster``HRegionServer`HBase 的进程,`HQuorumPeer`HBase 内置的 Zookeeper 的进程,其余的为 HDFSYARN 的进程。
```shell
[root@hadoop001 conf]# jps
@ -223,6 +223,6 @@ hadoop001
21933 HMaster
```
验证方式二 访问HBase Web UI 界面需要注意的是1.2 版本的HBase的访问端口为`60010`
验证方式二 :访问 HBase Web UI 界面,需要注意的是 1.2 版本的 HBase 的访问端口为 `60010`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-60010.png"/> </div>

View File

@ -20,17 +20,17 @@
## 一、集群规划
这里搭建一个3节点的HBase集群其中三台主机上均为`Regin Server`。同时为了保证高可用除了在hadoop001上部署主`Master`服务外还在hadoop002上部署备用的`Master`服务。Master服务由Zookeeper集群进行协调管理如果主`Master`不可用,则备用`Master`会成为新的主`Master`
这里搭建一个 3 节点的 HBase 集群,其中三台主机上均为 `Regin Server`。同时为了保证高可用,除了在 hadoop001 上部署主 `Master` 服务外,还在 hadoop002 上部署备用的 `Master` 服务。Master 服务由 Zookeeper 集群进行协调管理,如果主 `Master` 不可用,则备用 `Master` 会成为新的主 `Master`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase集群规划.png"/> </div>
## 二、前置条件
HBase的运行需要依赖HadoopJDK(`HBase 2.0+`对应`JDK 1.8+`) 。同时为了保证高可用这里我们不采用HBase内置的Zookeeper服务而采用外置的Zookeeper集群。相关搭建步骤可以参阅
HBase 的运行需要依赖 HadoopJDK(`HBase 2.0+` 对应 `JDK 1.8+`) 。同时为了保证高可用,这里我们不采用 HBase 内置的 Zookeeper 服务,而采用外置的 Zookeeper 集群。相关搭建步骤可以参阅:
- [Linux环境下JDK安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK安装.md)
- [Zookeeper单机环境和集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Zookeeper单机环境和集群环境搭建.md)
- [Hadoop集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Hadoop集群环境搭建.md)
- [Linux 环境下 JDK 安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK 安装.md)
- [Zookeeper 单机环境和集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Zookeeper 单机环境和集群环境搭建.md)
- [Hadoop 集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Hadoop 集群环境搭建.md)
@ -38,7 +38,7 @@ HBase的运行需要依赖Hadoop和JDK(`HBase 2.0+`对应`JDK 1.8+`) 。同时
### 3.1 下载并解压
下载并解压这里我下载的是CDH版本HBase下载地址为http://archive.cloudera.com/cdh5/cdh/5/
下载并解压,这里我下载的是 CDH 版本 HBase下载地址为http://archive.cloudera.com/cdh5/cdh/5/
```shell
# tar -zxvf hbase-1.2.0-cdh5.15.2.tar.gz
@ -65,7 +65,7 @@ export PATH=$HBASE_HOME/bin:$PATH
### 3.3 集群配置
进入`${HBASE_HOME}/conf`目录下,修改配置:
进入 `${HBASE_HOME}/conf` 目录下,修改配置:
#### 1. hbase-env.sh
@ -81,17 +81,17 @@ export HBASE_MANAGES_ZK=false
```xml
<configuration>
<property>
<!-- 指定hbase以分布式集群的方式运行 -->
<!-- 指定 hbase 以分布式集群的方式运行 -->
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<!-- 指定hbaseHDFS上的存储位置 -->
<!-- 指定 hbaseHDFS 上的存储位置 -->
<name>hbase.rootdir</name>
<value>hdfs://hadoop001:8020/hbase</value>
</property>
<property>
<!-- 指定zookeeper的地址-->
<!-- 指定 zookeeper 的地址-->
<name>hbase.zookeeper.quorum</name>
<value>hadoop001:2181,hadoop002:2181,hadoop003:2181</value>
</property>
@ -112,11 +112,11 @@ hadoop003
hadoop002
```
` backup-masters`这个文件是不存在的需要新建主要用来指明备用的master节点可以是多个这里我们以1个为例。
` backup-masters` 这个文件是不存在的,需要新建,主要用来指明备用的 master 节点,可以是多个,这里我们以 1 个为例。
### 3.4 HDFS客户端配置
这里有一个可选的配置如果您在Hadoop集群上进行了HDFS客户端配置的更改比如将副本系数`dfs.replication`设置成5则必须使用以下方法之一来使HBase知道否则HBase将依旧使用默认的副本系数3来创建文件:
这里有一个可选的配置:如果您在 Hadoop 集群上进行了 HDFS 客户端配置的更改,比如将副本系数 `dfs.replication` 设置成 5则必须使用以下方法之一来使 HBase 知道,否则 HBase 将依旧使用默认的副本系数 3 来创建文件:
> 1. Add a pointer to your `HADOOP_CONF_DIR` to the `HBASE_CLASSPATH` environment variable in *hbase-env.sh*.
> 2. Add a copy of *hdfs-site.xml* (or *hadoop-site.xml*) or, better, symlinks, under *${HBASE_HOME}/conf*, or
@ -124,13 +124,13 @@ hadoop002
以上是官方文档的说明,这里解释一下:
**第一种** 将Hadoop配置文件的位置信息添加到`hbase-env.sh``HBASE_CLASSPATH` 属性,示例如下:
**第一种** :将 Hadoop 配置文件的位置信息添加到 `hbase-env.sh``HBASE_CLASSPATH` 属性,示例如下:
```shell
export HBASE_CLASSPATH=usr/app/hadoop-2.6.0-cdh5.15.2/etc/hadoop
```
**第二种** 将Hadoop` hdfs-site.xml``hadoop-site.xml` 拷贝到 `${HBASE_HOME}/conf `目录下,或者通过符号链接的方式。如果采用这种方式的话,建议将两者都拷贝或建立符号链接,示例如下:
**第二种** :将 Hadoop` hdfs-site.xml``hadoop-site.xml` 拷贝到 `${HBASE_HOME}/conf ` 目录下,或者通过符号链接的方式。如果采用这种方式的话,建议将两者都拷贝或建立符号链接,示例如下:
```shell
# 拷贝
@ -140,15 +140,15 @@ ln -s /usr/app/hadoop-2.6.0-cdh5.15.2/etc/hadoop/core-site.xml
ln -s /usr/app/hadoop-2.6.0-cdh5.15.2/etc/hadoop/hdfs-site.xml
```
> 注:`hadoop-site.xml`这个配置文件现在叫做`core-site.xml`
> 注:`hadoop-site.xml` 这个配置文件现在叫做 `core-site.xml`
**第三种** :如果你只有少量更改,那么直接配置到`hbase-site.xml`中即可。
**第三种** :如果你只有少量更改,那么直接配置到 `hbase-site.xml` 中即可。
### 3.5 安装包分发
将HBase的安装包分发到其他服务器分发后建议在这两台服务器上也配置一下HBase的环境变量。
HBase 的安装包分发到其他服务器,分发后建议在这两台服务器上也配置一下 HBase 的环境变量。
```shell
scp -r /usr/app/hbase-1.2.0-cdh5.15.2/ hadoop002:usr/app/
@ -161,7 +161,7 @@ scp -r /usr/app/hbase-1.2.0-cdh5.15.2/ hadoop003:usr/app/
### 4.1 启动ZooKeeper集群
分别到三台服务器上启动ZooKeeper服务
分别到三台服务器上启动 ZooKeeper 服务:
```shell
zkServer.sh start
@ -178,7 +178,7 @@ start-yarn.sh
### 4.3 启动HBase集群
进入hadoop001`${HBASE_HOME}/bin`使用以下命令启动HBase集群。执行此命令后会在hadoop001上启动`Master`服务在hadoop002上启动备用`Master`服务,在`regionservers`文件中配置的所有节点启动`region server`服务。
进入 hadoop001`${HBASE_HOME}/bin`,使用以下命令启动 HBase 集群。执行此命令后,会在 hadoop001 上启动 `Master` 服务,在 hadoop002 上启动备用 `Master` 服务,在 `regionservers` 文件中配置的所有节点启动 `region server` 服务。
```shell
start-hbase.sh
@ -188,14 +188,14 @@ start-hbase.sh
### 4.5 查看服务
访问HBaseWeb-UI界面这里我安装的HBase版本为1.2,访问端口为`60010`如果你安装的是2.0以上的版本,则访问端口号为`16010`。可以看到`Master`hadoop001上三个`Regin Servers`分别在hadoop001hadoop002和hadoop003上并且还有一个`Backup Matser` 服务在 hadoop002上。
访问 HBaseWeb-UI 界面,这里我安装的 HBase 版本为 1.2,访问端口为 `60010`,如果你安装的是 2.0 以上的版本,则访问端口号为 `16010`。可以看到 `Master`hadoop001 上,三个 `Regin Servers` 分别在 hadoop001hadoop002 hadoop003 上,并且还有一个 `Backup Matser` 服务在 hadoop002 上。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-集群搭建1.png"/> </div>
<br/>
hadoop002 上的 HBase出于备用状态
hadoop002 上的 HBase 出于备用状态:
<br/>
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-集群搭建2.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hbase-集群搭建2.png"/> </div>

View File

@ -12,19 +12,19 @@
## 一、前置条件
Hadoop的运行依赖JDK需要预先安装安装步骤见
Hadoop 的运行依赖 JDK需要预先安装安装步骤见
+ [LinuxJDK的安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/JDK%E5%AE%89%E8%A3%85.md)
+ [LinuxJDK 的安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/JDK%E5%AE%89%E8%A3%85.md)
## 二、配置免密登录
Hadoop组件之间需要基于SSH进行通讯。
Hadoop 组件之间需要基于 SSH 进行通讯。
#### 2.1 配置映射
配置ip地址和主机名映射
配置 ip 地址和主机名映射:
```shell
vim /etc/hosts
@ -42,13 +42,13 @@ ssh-keygen -t rsa
### 3.3 授权
进入`~/.ssh`目录下,查看生成的公匙和私匙,并将公匙写入到授权文件:
进入 `~/.ssh` 目录下,查看生成的公匙和私匙,并将公匙写入到授权文件:
```shell
[root@@hadoop001 sbin]# cd ~/.ssh
[root@@hadoop001 .ssh]# ll
-rw-------. 1 root root 1675 315 09:48 id_rsa
-rw-r--r--. 1 root root 388 315 09:48 id_rsa.pub
-rw-------. 1 root root 1675 3 15 09:48 id_rsa
-rw-r--r--. 1 root root 388 3 15 09:48 id_rsa.pub
```
```shell
@ -65,7 +65,7 @@ ssh-keygen -t rsa
### 3.1 下载并解压
下载Hadoop安装包这里我下载的是CDH版本的下载地址为http://archive.cloudera.com/cdh5/cdh/5/
下载 Hadoop 安装包,这里我下载的是 CDH 版本的下载地址为http://archive.cloudera.com/cdh5/cdh/5/
```shell
# 解压
@ -87,7 +87,7 @@ export HADOOP_HOME=/usr/app/hadoop-2.6.0-cdh5.15.2
export PATH=${HADOOP_HOME}/bin:$PATH
```
执行`source`命令,使得配置的环境变量立即生效:
执行 `source` 命令,使得配置的环境变量立即生效:
```shell
# source /etc/profile
@ -97,7 +97,7 @@ export PATH=${HADOOP_HOME}/bin:$PATH
### 3.3 修改Hadoop配置
进入`${HADOOP_HOME}/etc/hadoop/ `目录下,修改以下配置:
进入 `${HADOOP_HOME}/etc/hadoop/ ` 目录下,修改以下配置:
#### 1. hadoop-env.sh
@ -111,12 +111,12 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201/
```xml
<configuration>
<property>
<!--指定namenodehdfs协议文件系统的通信地址-->
<!--指定 namenodehdfs 协议文件系统的通信地址-->
<name>fs.defaultFS</name>
<value>hdfs://hadoop001:8020</value>
</property>
<property>
<!--指定hadoop存储临时文件的目录-->
<!--指定 hadoop 存储临时文件的目录-->
<name>hadoop.tmp.dir</name>
<value>/home/hadoop/tmp</value>
</property>
@ -130,7 +130,7 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201/
```xml
<configuration>
<property>
<!--由于我们这里搭建是单机版本所以指定dfs的副本系数为1-->
<!--由于我们这里搭建是单机版本,所以指定 dfs 的副本系数为 1-->
<name>dfs.replication</name>
<value>1</value>
</property>
@ -139,7 +139,7 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201/
#### 4. slaves
配置所有从属节点的主机名或IP地址由于是单机版本所以指定本机即可
配置所有从属节点的主机名或 IP 地址,由于是单机版本,所以指定本机即可:
```shell
hadoop001
@ -149,7 +149,7 @@ hadoop001
### 3.4 关闭防火墙
不关闭防火墙可能导致无法访问HadoopWeb UI界面
不关闭防火墙可能导致无法访问 HadoopWeb UI 界面:
```shell
# 查看防火墙状态
@ -162,7 +162,7 @@ sudo systemctl stop firewalld.service
### 3.5 初始化
第一次启动Hadoop时需要进行初始化进入`${HADOOP_HOME}/bin/`目录下,执行以下命令:
第一次启动 Hadoop 时需要进行初始化,进入 `${HADOOP_HOME}/bin/` 目录下,执行以下命令:
```shell
[root@hadoop001 bin]# ./hdfs namenode -format
@ -172,7 +172,7 @@ sudo systemctl stop firewalld.service
### 3.6 启动HDFS
进入`${HADOOP_HOME}/sbin/`目录下启动HDFS
进入 `${HADOOP_HOME}/sbin/` 目录下,启动 HDFS
```shell
[root@hadoop001 sbin]# ./start-dfs.sh
@ -182,7 +182,7 @@ sudo systemctl stop firewalld.service
### 3.7 验证是否启动成功
方式一:执行`jps`查看`NameNode``DataNode`服务是否已经启动:
方式一:执行 `jps` 查看 `NameNode``DataNode` 服务是否已经启动:
```shell
[root@hadoop001 hadoop-2.6.0-cdh5.15.2]# jps
@ -193,7 +193,7 @@ sudo systemctl stop firewalld.service
方式二查看Web UI界面端口为`50070`
方式二:查看 Web UI 界面,端口为 `50070`
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop安装验证.png"/> </div>
@ -203,7 +203,7 @@ sudo systemctl stop firewalld.service
### 4.1 修改配置
进入`${HADOOP_HOME}/etc/hadoop/ `目录下,修改以下配置:
进入 `${HADOOP_HOME}/etc/hadoop/ ` 目录下,修改以下配置:
#### 1. mapred-site.xml
@ -226,7 +226,7 @@ cp mapred-site.xml.template mapred-site.xml
```xml
<configuration>
<property>
<!--配置NodeManager上运行的附属服务。需要配置成mapreduce_shuffle后才可以在Yarn上运行MapReduce程序。-->
<!--配置 NodeManager 上运行的附属服务。需要配置成 mapreduce_shuffle 后才可以在 Yarn 上运行 MapReduce 程序。-->
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
@ -237,7 +237,7 @@ cp mapred-site.xml.template mapred-site.xml
### 4.2 启动服务
进入`${HADOOP_HOME}/sbin/`目录下启动YARN
进入 `${HADOOP_HOME}/sbin/` 目录下,启动 YARN
```shell
./start-yarn.sh
@ -247,7 +247,7 @@ cp mapred-site.xml.template mapred-site.xml
#### 4.3 验证是否启动成功
方式一:执行`jps`命令查看`NodeManager``ResourceManager`服务是否已经启动:
方式一:执行 `jps` 命令查看 `NodeManager``ResourceManager` 服务是否已经启动:
```shell
[root@hadoop001 hadoop-2.6.0-cdh5.15.2]# jps
@ -258,6 +258,6 @@ cp mapred-site.xml.template mapred-site.xml
9390 SecondaryNameNode
```
方式二查看Web UI界面端口号为`8088`
方式二:查看 Web UI 界面,端口号为 `8088`
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-yarn安装验证.png"/> </div>

View File

@ -21,15 +21,15 @@
## 一、集群规划
这里搭建一个3节点的Hadoop集群其中三台主机均部署`DataNode``NodeManager`服务但只有hadoop001上部署`NameNode``ResourceManager`服务。
这里搭建一个 3 节点的 Hadoop 集群,其中三台主机均部署 `DataNode``NodeManager` 服务,但只有 hadoop001 上部署 `NameNode``ResourceManager` 服务。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop集群规划.png"/> </div>
## 二、前置条件
Hadoop的运行依赖JDK需要预先安装。其安装步骤单独整理至
Hadoop 的运行依赖 JDK需要预先安装。其安装步骤单独整理至
+ [LinuxJDK的安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/JDK%E5%AE%89%E8%A3%85.md)
+ [LinuxJDK 的安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/JDK%E5%AE%89%E8%A3%85.md)
@ -37,7 +37,7 @@ Hadoop的运行依赖JDK需要预先安装。其安装步骤单独整理至
### 3.1 生成密匙
在每台主机上使用`ssh-keygen`命令生成公钥私钥对:
在每台主机上使用 `ssh-keygen` 命令生成公钥私钥对:
```shell
ssh-keygen
@ -45,7 +45,7 @@ ssh-keygen
### 3.2 免密登录
`hadoop001`的公钥写到本机和远程机器的` ~/ .ssh/authorized_key`文件中:
`hadoop001` 的公钥写到本机和远程机器的 ` ~/ .ssh/authorized_key` 文件中:
```shell
ssh-copy-id -i ~/.ssh/id_rsa.pub hadoop001
@ -66,7 +66,7 @@ ssh hadoop003
### 3.1 下载并解压
下载Hadoop。这里我下载的是CDH版本Hadoop下载地址为http://archive.cloudera.com/cdh5/cdh/5/
下载 Hadoop。这里我下载的是 CDH 版本 Hadoop下载地址为http://archive.cloudera.com/cdh5/cdh/5/
```shell
# tar -zvxf hadoop-2.6.0-cdh5.15.2.tar.gz
@ -74,7 +74,7 @@ ssh hadoop003
### 3.2 配置环境变量
编辑`profile`文件:
编辑 `profile` 文件:
```shell
# vim /etc/profile
@ -87,7 +87,7 @@ export HADOOP_HOME=/usr/app/hadoop-2.6.0-cdh5.15.2
export PATH=${HADOOP_HOME}/bin:$PATH
```
执行`source`命令,使得配置立即生效:
执行 `source` 命令,使得配置立即生效:
```shell
# source /etc/profile
@ -95,7 +95,7 @@ export PATH=${HADOOP_HOME}/bin:$PATH
### 3.3 修改配置
进入`${HADOOP_HOME}/etc/hadoop`目录下,修改配置文件。各个配置文件内容如下:
进入 `${HADOOP_HOME}/etc/hadoop` 目录下,修改配置文件。各个配置文件内容如下:
#### 1. hadoop-env.sh
@ -109,12 +109,12 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201/
```xml
<configuration>
<property>
<!--指定namenodehdfs协议文件系统的通信地址-->
<!--指定 namenodehdfs 协议文件系统的通信地址-->
<name>fs.defaultFS</name>
<value>hdfs://hadoop001:8020</value>
</property>
<property>
<!--指定hadoop集群存储临时文件的目录-->
<!--指定 hadoop 集群存储临时文件的目录-->
<name>hadoop.tmp.dir</name>
<value>/home/hadoop/tmp</value>
</property>
@ -125,12 +125,12 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201/
```xml
<property>
<!--namenode节点数据即元数据的存放位置可以指定多个目录实现容错多个目录用逗号分隔-->
<!--namenode 节点数据(即元数据)的存放位置,可以指定多个目录实现容错,多个目录用逗号分隔-->
<name>dfs.namenode.name.dir</name>
<value>/home/hadoop/namenode/data</value>
</property>
<property>
<!--datanode节点数据即数据块的存放位置-->
<!--datanode 节点数据(即数据块)的存放位置-->
<name>dfs.datanode.data.dir</name>
<value>/home/hadoop/datanode/data</value>
</property>
@ -141,12 +141,12 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201/
```xml
<configuration>
<property>
<!--配置NodeManager上运行的附属服务。需要配置成mapreduce_shuffle后才可以在Yarn上运行MapReduce程序。-->
<!--配置 NodeManager 上运行的附属服务。需要配置成 mapreduce_shuffle 后才可以在 Yarn 上运行 MapReduce 程序。-->
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<!--resourcemanager的主机名-->
<!--resourcemanager 的主机名-->
<name>yarn.resourcemanager.hostname</name>
<value>hadoop001</value>
</property>
@ -159,7 +159,7 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201/
```xml
<configuration>
<property>
<!--指定mapreduce作业运行在yarn上-->
<!--指定 mapreduce 作业运行在 yarn 上-->
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
@ -168,7 +168,7 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201/
#### 5. slaves
配置所有从属节点的主机名或IP地址每行一个。所有从属节点上的`DataNode`服务和`NodeManager`服务都会被启动。
配置所有从属节点的主机名或 IP 地址,每行一个。所有从属节点上的 `DataNode` 服务和 `NodeManager` 服务都会被启动。
```properties
hadoop001
@ -178,7 +178,7 @@ hadoop003
### 3.4 分发程序
将Hadoop安装包分发到其他两台服务器分发后建议在这两台服务器上也配置一下Hadoop的环境变量。
Hadoop 安装包分发到其他两台服务器,分发后建议在这两台服务器上也配置一下 Hadoop 的环境变量。
```shell
# 将安装包分发到hadoop002
@ -189,7 +189,7 @@ scp -r /usr/app/hadoop-2.6.0-cdh5.15.2/ hadoop003:/usr/app/
### 3.5 初始化
`Hadoop001`上执行namenode初始化命令
`Hadoop001` 上执行 namenode 初始化命令:
```
hdfs namenode -format
@ -197,7 +197,7 @@ hdfs namenode -format
### 3.6 启动集群
进入到`Hadoop001``${HADOOP_HOME}/sbin`目录下启动Hadoop。此时`hadoop002``hadoop003`上的相关服务也会被启动:
进入到 `Hadoop001``${HADOOP_HOME}/sbin` 目录下,启动 Hadoop。此时 `hadoop002``hadoop003` 上的相关服务也会被启动:
```shell
# 启动dfs服务
@ -208,19 +208,19 @@ start-yarn.sh
### 3.7 查看集群
在每台服务器上使用`jps`命令查看服务进程或直接进入Web-UI界面进行查看端口为`50070`。可以看到此时有三个可用的`Datanode`
在每台服务器上使用 `jps` 命令查看服务进程,或直接进入 Web-UI 界面进行查看,端口为 `50070`。可以看到此时有三个可用的 `Datanode`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-集群环境搭建.png"/> </div>
<BR/>
点击`Live Nodes`进入,可以看到每个`DataNode`的详细情况:
点击 `Live Nodes` 进入,可以看到每个 `DataNode` 的详细情况:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-集群搭建2.png"/> </div>
<BR/>
接着可以查看Yarn的情况端口号为`8088`
接着可以查看 Yarn 的情况,端口号为 `8088`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-集群搭建3.png"/> </div>
@ -228,7 +228,7 @@ start-yarn.sh
## 五、提交服务到集群
提交作业到集群的方式和单机环境完全一致这里以提交Hadoop内置的计算Pi的示例程序为例在任何一个节点上执行都可以命令如下
提交作业到集群的方式和单机环境完全一致,这里以提交 Hadoop 内置的计算 Pi 的示例程序为例,在任何一个节点上执行都可以,命令如下:
```shell
hadoop jar /usr/app/hadoop-2.6.0-cdh5.15.2/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.6.0-cdh5.15.2.jar pi 3 3

View File

@ -3,9 +3,9 @@
## 一、前置条件
Flume需要依赖JDK 1.8+JDK安装方式见本仓库
Flume 需要依赖 JDK 1.8+JDK 安装方式见本仓库:
> [Linux环境下JDK安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK安装.md)
> [Linux 环境下 JDK 安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK 安装.md)
@ -13,7 +13,7 @@ Flume需要依赖JDK 1.8+JDK安装方式见本仓库
### 2.1 下载并解压
下载所需版本的Flume这里我下载的是`CDH`版本的Flume。下载地址为http://archive.cloudera.com/cdh5/cdh/5/
下载所需版本的 Flume这里我下载的是 `CDH` 版本的 Flume。下载地址为http://archive.cloudera.com/cdh5/cdh/5/
```shell
# 下载后进行解压
@ -41,13 +41,13 @@ export PATH=$FLUME_HOME/bin:$PATH
### 2.3 修改配置
进入安装目录下的`conf/`目录拷贝Flume的环境配置模板`flume-env.sh.template`
进入安装目录下的 `conf/` 目录,拷贝 Flume 的环境配置模板 `flume-env.sh.template`
```shell
# cp flume-env.sh.template flume-env.sh
```
修改`flume-env.sh`,指定JDK的安装路径
修改 `flume-env.sh`,指定 JDK 的安装路径:
```shell
# Enviroment variables can be set here.
@ -56,7 +56,7 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201
### 2.4 验证
由于已经将Flumebin目录配置到环境变量直接使用以下命令验证是否配置成功
由于已经将 Flumebin 目录配置到环境变量,直接使用以下命令验证是否配置成功:
```shell
# flume-ng version

View File

@ -2,13 +2,13 @@
>**系统环境**centos 7.6
>
>**JDK版本**jdk 1.8.0_20
>**JDK 版本**jdk 1.8.0_20
### 1. 下载并解压
在[官网](https://www.oracle.com/technetwork/java/javase/downloads/index.html)下载所需版本的JDK这里我下载的版本为[JDK 1.8](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) ,下载后进行解压:
在[官网](https://www.oracle.com/technetwork/java/javase/downloads/index.html) 下载所需版本的 JDK这里我下载的版本为[JDK 1.8](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) ,下载后进行解压:
```shell
[root@ java]# tar -zxvf jdk-8u201-linux-x64.tar.gz
@ -31,7 +31,7 @@ export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
```
执行`source`命令,使得配置立即生效:
执行 `source` 命令,使得配置立即生效:
```shell
[root@ java]# source /etc/profile

View File

@ -2,11 +2,11 @@
>**系统环境**centos 7.6
>
>**Python版本**Python-3.6.8
>**Python 版本**Python-3.6.8
### 1. 环境依赖
Python3.x的安装需要依赖这四个组件gcc zlibzlib-developenssl-devel所以需要预先安装命令如下
Python3.x 的安装需要依赖这四个组件gcc zlibzlib-developenssl-devel所以需要预先安装命令如下
```shell
yum install gcc -y
@ -17,7 +17,7 @@ yum install openssl-devel -y
### 2. 下载编译
Python源码包下载地址 https://www.python.org/downloads/
Python 源码包下载地址: https://www.python.org/downloads/
```shell
# wget https://www.python.org/ftp/python/3.6.8/Python-3.6.8.tgz
@ -29,7 +29,7 @@ Python源码包下载地址 https://www.python.org/downloads/
# tar -zxvf Python-3.6.8.tgz
```
进入根目录进行编译,可以指定编译安装的路径,这里我们指定为`/usr/app/python3.6`
进入根目录进行编译,可以指定编译安装的路径,这里我们指定为 `/usr/app/python3.6`
```shell
# cd Python-3.6.8
@ -56,7 +56,7 @@ source /etc/profile
### 5. 验证安装是否成功
输入`python3`命令如果能进入python交互环境则代表安装成功
输入 `python3` 命令,如果能进入 python 交互环境,则代表安装成功:
```shell
[root@hadoop001 app]# python3

View File

@ -18,7 +18,7 @@
### 1.1 下载并解压
下载所需版本的Hive这里我下载版本为`cdh5.15.2`。下载地址http://archive.cloudera.com/cdh5/cdh/5/
下载所需版本的 Hive这里我下载版本为 `cdh5.15.2`。下载地址http://archive.cloudera.com/cdh5/cdh/5/
```shell
# 下载后进行解压
@ -48,13 +48,13 @@ export PATH=$HIVE_HOME/bin:$PATH
**1. hive-env.sh**
进入安装目录下的`conf/`目录拷贝Hive的环境配置模板`flume-env.sh.template`
进入安装目录下的 `conf/` 目录,拷贝 Hive 的环境配置模板 `flume-env.sh.template`
```shell
cp hive-env.sh.template hive-env.sh
```
修改`hive-env.sh`指定Hadoop的安装路径
修改 `hive-env.sh`,指定 Hadoop 的安装路径:
```shell
HADOOP_HOME=/usr/app/hadoop-2.6.0-cdh5.15.2
@ -62,7 +62,7 @@ HADOOP_HOME=/usr/app/hadoop-2.6.0-cdh5.15.2
**2. hive-site.xml**
新建hive-site.xml 文件内容如下主要是配置存放元数据的MySQL的地址、驱动、用户名和密码等信息
新建 hive-site.xml 文件,内容如下,主要是配置存放元数据的 MySQL 的地址、驱动、用户名和密码等信息:
```xml
<?xml version="1.0"?>
@ -96,7 +96,7 @@ HADOOP_HOME=/usr/app/hadoop-2.6.0-cdh5.15.2
### 1.4 拷贝数据库驱动
将MySQL驱动包拷贝到Hive安装目录的`lib`目录下, MySQL驱动的下载地址为https://dev.mysql.com/downloads/connector/j/ , 在本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources)目录下我也上传了一份,有需要的可以自行下载。
MySQL 驱动包拷贝到 Hive 安装目录的 `lib` 目录下, MySQL 驱动的下载地址为https://dev.mysql.com/downloads/connector/j/ , 在本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources) 目录下我也上传了一份,有需要的可以自行下载。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-mysql.png"/> </div>
@ -104,20 +104,20 @@ HADOOP_HOME=/usr/app/hadoop-2.6.0-cdh5.15.2
### 1.5 初始化元数据库
+ 当使用的 hive 是1.x版本时可以不进行初始化操作Hive会在第一次启动的时候会自动进行初始化但不会生成所有的元数据信息表只会初始化必要的一部分在之后的使用中用到其余表时会自动创建
+ 当使用的 hive 是 1.x 版本时可以不进行初始化操作Hive 会在第一次启动的时候会自动进行初始化,但不会生成所有的元数据信息表,只会初始化必要的一部分,在之后的使用中用到其余表时会自动创建;
+ 当使用的 hive 是2.x版本时必须手动初始化元数据库。初始化命令
+ 当使用的 hive 是 2.x 版本时,必须手动初始化元数据库。初始化命令:
```shell
# schematool 命令在安装目录的bin目录下由于上面已经配置过环境变量在任意位置执行即可
# schematool 命令在安装目录的 bin 目录下,由于上面已经配置过环境变量,在任意位置执行即可
schematool -dbType mysql -initSchema
```
这里我使用的是CDH`hive-1.1.0-cdh5.15.2.tar.gz`,对应`Hive 1.1.0` 版本,可以跳过这一步。
这里我使用的是 CDH`hive-1.1.0-cdh5.15.2.tar.gz`,对应 `Hive 1.1.0` 版本,可以跳过这一步。
### 1.6 启动
由于已经将Hivebin目录配置到环境变量直接使用以下命令启动成功进入交互式命令行后执行`show databases`命令,无异常则代表搭建成功。
由于已经将 Hivebin 目录配置到环境变量,直接使用以下命令启动,成功进入交互式命令行后执行 `show databases` 命令,无异常则代表搭建成功。
```shell
# hive
@ -125,7 +125,7 @@ HADOOP_HOME=/usr/app/hadoop-2.6.0-cdh5.15.2
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-install-2.png"/> </div>
在Mysql中也能看到Hive创建的库和存放元数据信息的表
Mysql 中也能看到 Hive 创建的库和存放元数据信息的表
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hive-mysql-tables.png"/> </div>
@ -133,15 +133,15 @@ HADOOP_HOME=/usr/app/hadoop-2.6.0-cdh5.15.2
## 二、HiveServer2/beeline
Hive内置了HiveServerHiveServer2服务两者都允许客户端使用多种编程语言进行连接但是HiveServer不能处理多个客户端的并发请求因此产生了HiveServer2。HiveServer2HS2允许远程客户端可以使用各种编程语言向Hive提交请求并检索结果支持多客户端并发访问和身份验证。HS2是由多个服务组成的单个进程其包括基于ThriftHive服务TCPHTTP和用于Web UIJetty Web服务。
Hive 内置了 HiveServerHiveServer2 服务,两者都允许客户端使用多种编程语言进行连接,但是 HiveServer 不能处理多个客户端的并发请求,因此产生了 HiveServer2。HiveServer2HS2允许远程客户端可以使用各种编程语言向 Hive 提交请求并检索结果支持多客户端并发访问和身份验证。HS2 是由多个服务组成的单个进程,其包括基于 ThriftHive 服务TCPHTTP和用于 Web UIJetty Web 服务。
HiveServer2拥有自己的CLI工具——Beeline。Beeline是一个基于SQLLineJDBC客户端。由于目前HiveServer2Hive开发维护的重点所以官方更加推荐使用Beeline而不是Hive CLI。以下主要讲解Beeline的配置方式。
HiveServer2 拥有自己的 CLI 工具——Beeline。Beeline 是一个基于 SQLLineJDBC 客户端。由于目前 HiveServer2Hive 开发维护的重点,所以官方更加推荐使用 Beeline 而不是 Hive CLI。以下主要讲解 Beeline 的配置方式。
### 2.1 修改Hadoop配置
修改 hadoop 集群的 core-site.xml 配置文件增加如下配置指定hadooproot用户可以代理本机上所有的用户。
修改 hadoop 集群的 core-site.xml 配置文件,增加如下配置,指定 hadooproot 用户可以代理本机上所有的用户。
```xml
<property>
@ -154,9 +154,9 @@ Hive内置了HiveServer和HiveServer2服务两者都允许客户端使用多
</property>
```
之所以要配置这一步是因为hadoop 2.0以后引入了安全伪装机制使得hadoop不允许上层系统如hive直接将实际用户传递到hadoop层而应该将实际用户传递给一个超级代理由该代理在hadoop上执行操作以避免任意客户端随意操作hadoop。如果不配置这一步在之后的连接中可能会抛出`AuthorizationException`异常。
之所以要配置这一步,是因为 hadoop 2.0 以后引入了安全伪装机制,使得 hadoop 不允许上层系统(如 hive直接将实际用户传递到 hadoop 层,而应该将实际用户传递给一个超级代理,由该代理在 hadoop 上执行操作,以避免任意客户端随意操作 hadoop。如果不配置这一步在之后的连接中可能会抛出 `AuthorizationException` 异常。
>关于Hadoop的用户代理机制可以参考[hadoop的用户代理机制](https://blog.csdn.net/u012948976/article/details/49904675#官方文档解读) 或 [Superusers Acting On Behalf Of Other Users](http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/Superusers.html)
>关于 Hadoop 的用户代理机制,可以参考:[hadoop 的用户代理机制](https://blog.csdn.net/u012948976/article/details/49904675#官方文档解读) 或 [Superusers Acting On Behalf Of Other Users](http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/Superusers.html)
@ -172,7 +172,7 @@ Hive内置了HiveServer和HiveServer2服务两者都允许客户端使用多
### 2.3 使用beeline
可以使用以下命令进入beeline交互式命令行出现`Connected`则代表连接成功。
可以使用以下命令进入 beeline 交互式命令行,出现 `Connected` 则代表连接成功。
```shell
# beeline -u jdbc:hive2://hadoop001:10000 -n root

View File

@ -10,7 +10,7 @@
### 1.1 下载并解压
官方下载地址http://spark.apache.org/downloads.html 选择Spark版本和对应的Hadoop版本后再下载
官方下载地址http://spark.apache.org/downloads.html ,选择 Spark 版本和对应的 Hadoop 版本后再下载:
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-download.png"/> </div>
@ -51,14 +51,14 @@ spark-shell --master local[2]
```
- **local**:只启动一个工作线程;
- **local[k]**:启动k个工作线程;
- **local[*]**启动跟cpu数目相同的工作线程数。
- **local[k]**:启动 k 个工作线程;
- **local[*]**:启动跟 cpu 数目相同的工作线程数。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-shell-local.png"/> </div>
<br/>
进入spark-shell后程序已经自动创建好了上下文`SparkContext`等效于执行了下面的Scala代码
进入 spark-shell 后,程序已经自动创建好了上下文 `SparkContext`,等效于执行了下面的 Scala 代码:
```scala
val conf = new SparkConf().setAppName("Spark shell").setMaster("local[2]")
@ -68,7 +68,7 @@ val sc = new SparkContext(conf)
## 二、词频统计案例
安装完成后可以先做一个简单的词频统计例子感受spark的魅力。准备一个词频统计的文件样本`wc.txt`,内容如下:
安装完成后可以先做一个简单的词频统计例子,感受 spark 的魅力。准备一个词频统计的文件样本 `wc.txt`,内容如下:
```txt
hadoop,spark,hadoop
@ -76,7 +76,7 @@ spark,flink,flink,spark
hadoop,hadoop
```
在scala交互式命令行中执行如下Scala语句
scala 交互式命令行中执行如下 Scala 语句:
```scala
val file = spark.sparkContext.textFile("file:///usr/app/wc.txt")
@ -88,7 +88,7 @@ wordCounts.collect
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-shell.png"/> </div>
同时还可以通过Web UI查看作业的执行情况访问端口为`4040`
同时还可以通过 Web UI 查看作业的执行情况,访问端口为 `4040`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-shell-web-ui.png"/> </div>
@ -98,15 +98,15 @@ wordCounts.collect
## 三、Scala开发环境配置
Spark是基于Scala语言进行开发的分别提供了基于Scala、Java、Python语言的API如果你想使用Scala语言进行开发则需要搭建Scala语言的开发环境。
Spark 是基于 Scala 语言进行开发的,分别提供了基于 Scala、Java、Python 语言的 API如果你想使用 Scala 语言进行开发,则需要搭建 Scala 语言的开发环境。
### 3.1 前置条件
Scala的运行依赖于JDK所以需要你本机有安装对应版本的JDK最新的Scala 2.12.x需要JDK 1.8+。
Scala 的运行依赖于 JDK所以需要你本机有安装对应版本的 JDK最新的 Scala 2.12.x 需要 JDK 1.8+。
### 3.2 安装Scala插件
IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打开 IDEA依次点击 **File** => **settings**=> **plugins** 选项卡搜索Scala插件(如下图)。找到插件后进行安装并重启IDEA使得安装生效。
IDEA 默认不支持 Scala 语言的开发,需要通过插件进行扩展。打开 IDEA依次点击 **File** => **settings**=> **plugins** 选项卡,搜索 Scala 插件 (如下图)。找到插件后进行安装,并重启 IDEA 使得安装生效。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/idea-scala-plugin.png"/> </div>
@ -114,7 +114,7 @@ IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打
### 3.3 创建Scala项目
在IDEA中依次点击 **File** => **New** => **Project** 选项卡,然后选择创建`Scala—IDEA`工程:
IDEA 中依次点击 **File** => **New** => **Project** 选项卡,然后选择创建 `Scala—IDEA` 工程:
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/idea-newproject-scala.png"/> </div>
@ -124,7 +124,7 @@ IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打
#### 1. 方式一
此时看到`Scala SDK`为空,依次点击`Create` => `Download` ,选择所需的版本后,点击`OK`按钮进行下载,下载完成点击`Finish`进入工程。
此时看到 `Scala SDK` 为空,依次点击 `Create` => `Download` ,选择所需的版本后,点击 `OK` 按钮进行下载,下载完成点击 `Finish` 进入工程。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/idea-scala-select.png"/> </div>
@ -132,15 +132,15 @@ IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打
#### 2. 方式二
方式一是Scala官方安装指南里使用的方式但下载速度通常比较慢且这种安装下并没有直接提供Scala命令行工具。所以个人推荐到官网下载安装包进行安装下载地址https://www.scala-lang.org/download/
方式一是 Scala 官方安装指南里使用的方式,但下载速度通常比较慢,且这种安装下并没有直接提供 Scala 命令行工具。所以个人推荐到官网下载安装包进行安装下载地址https://www.scala-lang.org/download/
这里我的系统是Windows下载msi版本的安装包后一直点击下一步进行安装安装完成后会自动配置好环境变量。
这里我的系统是 Windows下载 msi 版本的安装包后,一直点击下一步进行安装,安装完成后会自动配置好环境变量。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-other-resources.png"/> </div>
由于安装时已经自动配置好环境变量所以IDEA会自动选择对应版本的SDK。
由于安装时已经自动配置好环境变量,所以 IDEA 会自动选择对应版本的 SDK。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/idea-scala-2.1.8.png"/> </div>
@ -148,7 +148,7 @@ IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打
### 3.5 创建Hello World
在工程 `src`目录上右击 **New** => **Scala class** 创建`Hello.scala`。输入代码如下,完成后点击运行按钮,成功运行则代表搭建成功。
在工程 `src` 目录上右击 **New** => **Scala class** 创建 `Hello.scala`。输入代码如下,完成后点击运行按钮,成功运行则代表搭建成功。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-hello-world.png"/> </div>
@ -158,7 +158,7 @@ IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打
### 3.6 切换Scala版本
在日常的开发中由于对应软件如Spark的版本切换可能导致需要切换Scala的版本则可以在`Project Structures`中的`Global Libraries`选项卡中进行切换。
在日常的开发中,由于对应软件(如 Spark的版本切换可能导致需要切换 Scala 的版本,则可以在 `Project Structures` 中的 `Global Libraries` 选项卡中进行切换。
<div align="center"> <img width="700px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/idea-scala-change.png"/> </div>
@ -168,11 +168,11 @@ IDEA默认不支持Scala语言的开发需要通过插件进行扩展。打
### 3.7 可能出现的问题
在IDEA中有时候重新打开项目后右击并不会出现新建`scala`文件的选项或者在编写时没有Scala语法提示此时可以先删除`Global Libraries`中配置好的SDK之后再重新添加
IDEA 中有时候重新打开项目后,右击并不会出现新建 `scala` 文件的选项,或者在编写时没有 Scala 语法提示,此时可以先删除 `Global Libraries` 中配置好的 SDK之后再重新添加
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-sdk.png"/> </div>
**另外在IDEA中以本地模式运行Spark项目是不需要在本机搭建SparkHadoop环境的。**
**另外在 IDEA 中以本地模式运行 Spark 项目是不需要在本机搭建 SparkHadoop 环境的。**

View File

@ -20,23 +20,23 @@
## 一、集群规划
这里搭建一个3节点的Spark集群其中三台主机上均部署`Worker`服务。同时为了保证高可用除了在hadoop001上部署主`Master`服务外还在hadoop002hadoop003上分别部署备用的`Master`服务Master服务由Zookeeper集群进行协调管理如果主`Master`不可用,则备用`Master`会成为新的主`Master`
这里搭建一个 3 节点的 Spark 集群,其中三台主机上均部署 `Worker` 服务。同时为了保证高可用,除了在 hadoop001 上部署主 `Master` 服务外,还在 hadoop002hadoop003 上分别部署备用的 `Master` 服务Master 服务由 Zookeeper 集群进行协调管理,如果主 `Master` 不可用,则备用 `Master` 会成为新的主 `Master`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark集群规划.png"/> </div>
## 二、前置条件
搭建Spark集群前需要保证JDK环境、Zookeeper集群和Hadoop集群已经搭建相关步骤可以参阅
搭建 Spark 集群前,需要保证 JDK 环境、Zookeeper 集群和 Hadoop 集群已经搭建,相关步骤可以参阅:
- [Linux环境下JDK安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK安装.md)
- [Zookeeper单机环境和集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Zookeeper单机环境和集群环境搭建.md)
- [Hadoop集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Hadoop集群环境搭建.md)
- [Linux 环境下 JDK 安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK 安装.md)
- [Zookeeper 单机环境和集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Zookeeper 单机环境和集群环境搭建.md)
- [Hadoop 集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Hadoop 集群环境搭建.md)
## 三、Spark集群搭建
### 3.1 下载解压
下载所需版本的Spark官网下载地址http://spark.apache.org/downloads.html
下载所需版本的 Spark官网下载地址http://spark.apache.org/downloads.html
<div align="center"> <img width="600px" src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-download.png"/> </div>
@ -71,7 +71,7 @@ export PATH=${SPARK_HOME}/bin:$PATH
### 3.3 集群配置
进入`${SPARK_HOME}/conf`目录,拷贝配置样本进行修改:
进入 `${SPARK_HOME}/conf` 目录,拷贝配置样本进行修改:
#### 1. spark-env.sh
@ -94,7 +94,7 @@ SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER -Dspark.deploy.zoo
cp slaves.template slaves
```
配置所有Woker节点的位置
配置所有 Woker 节点的位置:
```properties
hadoop001
@ -104,7 +104,7 @@ hadoop003
### 3.4 安装包分发
将Spark的安装包分发到其他服务器分发后建议在这两台服务器上也配置一下Spark的环境变量。
Spark 的安装包分发到其他服务器,分发后建议在这两台服务器上也配置一下 Spark 的环境变量。
```shell
scp -r /usr/app/spark-2.4.0-bin-hadoop2.6/ hadoop002:usr/app/
@ -117,7 +117,7 @@ scp -r /usr/app/spark-2.4.0-bin-hadoop2.6/ hadoop003:usr/app/
### 4.1 启动ZooKeeper集群
分别到三台服务器上启动ZooKeeper服务
分别到三台服务器上启动 ZooKeeper 服务:
```shell
zkServer.sh start
@ -134,13 +134,13 @@ start-yarn.sh
### 4.3 启动Spark集群
进入hadoop001` ${SPARK_HOME}/sbin`目录下执行下面命令启动集群。执行命令后会在hadoop001上启动`Maser`服务,会在`slaves`配置文件中配置的所有节点上启动`Worker`服务。
进入 hadoop001` ${SPARK_HOME}/sbin` 目录下,执行下面命令启动集群。执行命令后,会在 hadoop001 上启动 `Maser` 服务,会在 `slaves` 配置文件中配置的所有节点上启动 `Worker` 服务。
```shell
start-all.sh
```
分别在hadoop002hadoop003上执行下面的命令启动备用的`Master`服务:
分别在 hadoop002hadoop003 上执行下面的命令,启动备用的 `Master` 服务:
```shell
# ${SPARK_HOME}/sbin 下执行
@ -149,11 +149,11 @@ start-master.sh
### 4.4 查看服务
查看SparkWeb-UI页面端口为`8080`。此时可以看到hadoop001上的Master节点处于`ALIVE`状态,并有3个可用的`Worker`节点。
查看 SparkWeb-UI 页面,端口为 `8080`。此时可以看到 hadoop001 上的 Master 节点处于 `ALIVE` 状态,并有 3 个可用的 `Worker` 节点。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-集群搭建1.png"/> </div>
而hadoop002hadoop003上的Master节点均处于`STANDBY`状态,没有可用的`Worker`节点。
hadoop002hadoop003 上的 Master 节点均处于 `STANDBY` 状态,没有可用的 `Worker` 节点。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-集群搭建2.png"/> </div>
@ -163,19 +163,19 @@ start-master.sh
## 五、验证集群高可用
此时可以使用`kill`命令杀死hadoop001上的`Master`进程,此时备用`Master`会中会有一个再次成为`Master`我这里是hadoop002可以看到hadoop2上的`Master`经过`RECOVERING`后成为了新的主`Master`,并且获得了全部可以用的`Workers`
此时可以使用 `kill` 命令杀死 hadoop001 上的 `Master` 进程,此时备用 `Master` 会中会有一个再次成为 `Master`,我这里是 hadoop002可以看到 hadoop2 上的 `Master` 经过 `RECOVERING` 后成为了新的主 `Master`,并且获得了全部可以用的 `Workers`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-集群搭建4.png"/> </div>
Hadoop002上的`Master`成为主`Master`,并获得了全部可以用的`Workers`
Hadoop002 上的 `Master` 成为主 `Master`,并获得了全部可以用的 `Workers`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/spark-集群搭建5.png"/> </div>
此时如果你再在hadoop001上使用`start-master.sh`启动Master服务那么其会作为备用`Master`存在。
此时如果你再在 hadoop001 上使用 `start-master.sh` 启动 Master 服务,那么其会作为备用 `Master` 存在。
## 六、提交作业
和单机环境下的提交到Yarn上的命令完全一致这里以Spark内置的计算Pi的样例程序为例提交命令如下
和单机环境下的提交到 Yarn 上的命令完全一致,这里以 Spark 内置的计算 Pi 的样例程序为例,提交命令如下:
```shell
spark-submit \

View File

@ -7,11 +7,11 @@
> 1. Java 7+ (Apache Storm 1.x is tested through travis ci against both java 7 and java 8 JDKs)
> 2. Python 2.6.6 (Python 3.x should work too, but is not tested as part of our CI enviornment)
按照[官方文档](http://storm.apache.org/releases/1.2.2/Setting-up-a-Storm-cluster.html)的说明storm 运行依赖于Java 7+ 和 Python 2.6.6 +,所以需要预先安装这两个软件。由于这两个软件在多个框架中都有依赖,其安装步骤单独整理至
按照[官方文档](http://storm.apache.org/releases/1.2.2/Setting-up-a-Storm-cluster.html) 的说明storm 运行依赖于 Java 7+ 和 Python 2.6.6 +,所以需要预先安装这两个软件。由于这两个软件在多个框架中都有依赖,其安装步骤单独整理至
+ [Linux环境下JDK安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK安装.md)
+ [Linux 环境下 JDK 安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK 安装.md)
+ [Linux环境下Python安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxPython安装.md)
+ [Linux 环境下 Python 安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxPython 安装.md)
@ -46,7 +46,7 @@ export PATH=$STORM_HOME/bin:$PATH
### 4. 启动相关进程
因为要启动多个进程,所以统一采用后台进程的方式启动。进入到`${STORM_HOME}/bin`目录下,依次执行下面的命令:
因为要启动多个进程,所以统一采用后台进程的方式启动。进入到 `${STORM_HOME}/bin` 目录下,依次执行下面的命令:
```shell
# 启动zookeeper
@ -76,6 +76,6 @@ nohup sh storm logviewer &
9630 logviewer
```
验证方式二: 访问8080端口查看Web-UI界面
验证方式二: 访问 8080 端口,查看 Web-UI 界面:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-web-ui.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-web-ui.png"/> </div>

View File

@ -23,17 +23,17 @@
## 一、集群规划
这里搭建一个3节点的Storm集群三台主机上均部署`Supervisor``LogViewer`服务。同时为了保证高可用除了在hadoop001上部署主`Nimbus`服务外还在hadoop002上部署备用的`Nimbus`服务。`Nimbus`服务由Zookeeper集群进行协调管理如果主`Nimbus`不可用,则备用`Nimbus`会成为新的主`Nimbus`
这里搭建一个 3 节点的 Storm 集群:三台主机上均部署 `Supervisor``LogViewer` 服务。同时为了保证高可用,除了在 hadoop001 上部署主 `Nimbus` 服务外,还在 hadoop002 上部署备用的 `Nimbus` 服务。`Nimbus` 服务由 Zookeeper 集群进行协调管理,如果主 `Nimbus` 不可用,则备用 `Nimbus` 会成为新的主 `Nimbus`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-集群规划.png"/> </div>
## 二、前置条件
Storm 运行依赖于Java 7+ 和 Python 2.6.6 +所以需要预先安装这两个软件。同时为了保证高可用这里我们不采用Storm内置的Zookeeper而采用外置的Zookeeper集群。由于这三个软件在多个框架中都有依赖其安装步骤单独整理至
Storm 运行依赖于 Java 7+ 和 Python 2.6.6 +,所以需要预先安装这两个软件。同时为了保证高可用,这里我们不采用 Storm 内置的 Zookeeper而采用外置的 Zookeeper 集群。由于这三个软件在多个框架中都有依赖,其安装步骤单独整理至
- [Linux环境下JDK安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK安装.md)
- [Linux环境下Python安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxPython安装.md)
- [Zookeeper单机环境和集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Zookeeper单机环境和集群环境搭建.md)
- [Linux 环境下 JDK 安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK 安装.md)
- [Linux 环境下 Python 安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxPython 安装.md)
- [Zookeeper 单机环境和集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Zookeeper 单机环境和集群环境搭建.md)
@ -70,7 +70,7 @@ export PATH=$STORM_HOME/bin:$PATH
### 3. 集群配置
修改`${STORM_HOME}/conf/storm.yaml`文件,配置如下:
修改 `${STORM_HOME}/conf/storm.yaml` 文件,配置如下:
```yaml
# Zookeeper集群的主机列表
@ -93,11 +93,11 @@ supervisor.slots.ports:
- 6703
```
`supervisor.slots.ports`参数用来配置workers进程接收消息的端口默认每个supervisor节点上会启动4个worker当然你也可以按照自己的需要和服务器性能进行设置假设只想启动2个worker的话此处配置2个端口即可。
`supervisor.slots.ports` 参数用来配置 workers 进程接收消息的端口,默认每个 supervisor 节点上会启动 4 个 worker当然你也可以按照自己的需要和服务器性能进行设置假设只想启动 2 个 worker 的话,此处配置 2 个端口即可。
### 4. 安装包分发
将Storm的安装包分发到其他服务器分发后建议在这两台服务器上也配置一下Storm的环境变量。
Storm 的安装包分发到其他服务器,分发后建议在这两台服务器上也配置一下 Storm 的环境变量。
```shell
scp -r /usr/app/apache-storm-1.2.2/ root@hadoop002:/usr/app/
@ -110,7 +110,7 @@ scp -r /usr/app/apache-storm-1.2.2/ root@hadoop003:/usr/app/
### 4.1 启动ZooKeeper集群
分别到三台服务器上启动ZooKeeper服务
分别到三台服务器上启动 ZooKeeper 服务:
```shell
zkServer.sh start
@ -118,7 +118,7 @@ scp -r /usr/app/apache-storm-1.2.2/ root@hadoop003:/usr/app/
### 4.2 启动Storm集群
因为要启动多个进程,所以统一采用后台进程的方式启动。进入到`${STORM_HOME}/bin`目录下,执行下面的命令:
因为要启动多个进程,所以统一采用后台进程的方式启动。进入到 `${STORM_HOME}/bin` 目录下,执行下面的命令:
**hadoop001 & hadoop002 **
@ -135,7 +135,7 @@ nohup sh storm logviewer &
**hadoop003 **
hadoop003上只需要启动`supervisor`服务和`logviewer`服务:
hadoop003 上只需要启动 `supervisor` 服务和 `logviewer` 服务:
```shell
# 启动从节点 supervisor
@ -148,7 +148,7 @@ nohup sh storm logviewer &
### 4.3 查看集群
使用`jps`查看进程,三台服务器的进程应该分别如下:
使用 `jps` 查看进程,三台服务器的进程应该分别如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-集群-shell.png"/> </div>
@ -156,7 +156,7 @@ nohup sh storm logviewer &
<br/>
访问hadoop001hadoop002`8080`端口,界面如下。可以看到有一主一备2个`Nimbus`和3个`Supervisor`,并且每个`Supervisor`有四个`slots`,即四个可用的`worker`进程,此时代表集群已经搭建成功。
访问 hadoop001hadoop002`8080` 端口,界面如下。可以看到有一主一备 2 个 `Nimbus` 和 3 个 `Supervisor`,并且每个 `Supervisor` 有四个 `slots`,即四个可用的 `worker` 进程,此时代表集群已经搭建成功。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-集群搭建1.png"/> </div>
@ -164,6 +164,6 @@ nohup sh storm logviewer &
## 五、高可用验证
这里手动模拟主`Nimbus`异常的情况在hadoop001上使用`kill`命令杀死`Nimbus`的线程此时可以看到hadoop001上的`Nimbus`已经处于`offline`状态而hadoop002上的`Nimbus`则成为新的`Leader`
这里手动模拟主 `Nimbus` 异常的情况,在 hadoop001 上使用 `kill` 命令杀死 `Nimbus` 的线程,此时可以看到 hadoop001 上的 `Nimbus` 已经处于 `offline` 状态,而 hadoop002 上的 `Nimbus` 则成为新的 `Leader`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm集群搭建2.png"/> </div>

View File

@ -20,7 +20,7 @@
### 1.1 下载
下载对应版本Zookeeper这里我下载的版本`3.4.14`。官方下载地址https://archive.apache.org/dist/zookeeper/
下载对应版本 Zookeeper这里我下载的版本 `3.4.14`。官方下载地址https://archive.apache.org/dist/zookeeper/
```shell
# wget https://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
@ -53,7 +53,7 @@ export PATH=$ZOOKEEPER_HOME/bin:$PATH
### 1.4 修改配置
进入安装目录的`conf/`目录下,拷贝配置样本并进行修改:
进入安装目录的 `conf/` 目录下,拷贝配置样本并进行修改:
```
# cp zoo_sample.cfg zoo.cfg
@ -95,12 +95,12 @@ clientPort=2181
>配置参数说明:
>
>- **tickTime**用于计算的基础时间单元。比如session超时N*tickTime
>- **initLimit**:用于集群,允许从节点连接并同步到 master节点的初始化连接时间以tickTime的倍数来表示
>- **syncLimit**:用于集群, master主节点与从节点之间发送消息请求和应答时间长度心跳机制
>- **tickTime**:用于计算的基础时间单元。比如 session 超时N*tickTime
>- **initLimit**:用于集群,允许从节点连接并同步到 master 节点的初始化连接时间,以 tickTime 的倍数来表示;
>- **syncLimit**:用于集群, master 主节点与从节点之间发送消息,请求和应答时间长度(心跳机制);
>- **dataDir**:数据存储位置;
>- **dataLogDir**:日志目录;
>- **clientPort**用于客户端连接的端口默认2181
>- **clientPort**:用于客户端连接的端口,默认 2181
@ -114,7 +114,7 @@ zkServer.sh start
### 1.6 验证
使用JPS验证进程是否已经启动出现`QuorumPeerMain`则代表启动成功。
使用 JPS 验证进程是否已经启动,出现 `QuorumPeerMain` 则代表启动成功。
```shell
[root@hadoop001 bin]# jps
@ -125,11 +125,11 @@ zkServer.sh start
## 二、集群环境搭建
为保证集群高可用Zookeeper集群的节点数最好是奇数最少有三个节点所以这里演示搭建一个三个节点的集群。这里我使用三台主机进行搭建主机名分别为hadoop001hadoop002hadoop003。
为保证集群高可用Zookeeper 集群的节点数最好是奇数,最少有三个节点,所以这里演示搭建一个三个节点的集群。这里我使用三台主机进行搭建,主机名分别为 hadoop001hadoop002hadoop003。
### 2.1 修改配置
解压一份zookeeper安装包修改其配置文件`zoo.cfg`内容如下。之后使用scp命令将安装包分发到三台服务器上
解压一份 zookeeper 安装包,修改其配置文件 `zoo.cfg`,内容如下。之后使用 scp 命令将安装包分发到三台服务器上:
```shell
tickTime=2000
@ -148,7 +148,7 @@ server.3=hadoop003:2287:3387
### 2.2 标识节点
分别在三台主机的`dataDir`目录下新建`myid`文件,并写入对应的节点标识。Zookeeper集群通过`myid`文件识别集群节点并通过上文配置的节点通信端口和选举端口来进行节点通信选举出Leader节点。
分别在三台主机的 `dataDir` 目录下新建 `myid` 文件,并写入对应的节点标识。Zookeeper 集群通过 `myid` 文件识别集群节点,并通过上文配置的节点通信端口和选举端口来进行节点通信,选举出 Leader 节点。
创建存储目录:
@ -157,7 +157,7 @@ server.3=hadoop003:2287:3387
mkdir -vp /usr/local/zookeeper-cluster/data/
```
创建并写入节点标识到`myid`文件:
创建并写入节点标识到 `myid` 文件:
```shell
# hadoop001主机
@ -178,7 +178,7 @@ echo "3" > /usr/local/zookeeper-cluster/data/myid
### 2.4 集群验证
启动后使用`zkServer.sh status`查看集群各个节点状态。如图所示三个节点进程均启动成功并且hadoop002leader节点hadoop001hadoop003follower节点。
启动后使用 `zkServer.sh status` 查看集群各个节点状态。如图所示:三个节点进程均启动成功,并且 hadoop002leader 节点hadoop001hadoop003follower 节点。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/zookeeper-hadoop001.png"/> </div>

View File

@ -40,7 +40,7 @@ HDFS 高可用架构主要由以下组件所构成:
目前 Hadoop 支持使用 Quorum Journal Manager (QJM) 或 Network File System (NFS) 作为共享的存储系统,这里以 QJM 集群为例进行说明Active NameNode 首先把 EditLog 提交到 JournalNode 集群,然后 Standby NameNode 再从 JournalNode 集群定时同步 EditLog当 Active NameNode 宕机后, Standby NameNode 在确认元数据完全同步之后就可以对外提供服务。
需要说明的是向 JournalNode 集群写入 EditLog 是遵循 “过半写入则成功” 的策略,所以你至少要有3个 JournalNode 节点,当然你也可以继续增加节点数量,但是应该保证节点总数是奇数。同时如果有 2N+1 台 JournalNode那么根据过半写的原则最多可以容忍有 N 台 JournalNode 节点挂掉。
需要说明的是向 JournalNode 集群写入 EditLog 是遵循 “过半写入则成功” 的策略,所以你至少要有 3 个 JournalNode 节点,当然你也可以继续增加节点数量,但是应该保证节点总数是奇数。同时如果有 2N+1 台 JournalNode那么根据过半写的原则最多可以容忍有 N 台 JournalNode 节点挂掉。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop-QJM-同步机制.png"/> </div>
@ -70,7 +70,7 @@ YARN ResourceManager 的高可用与 HDFS NameNode 的高可用类似,但是 R
## 二、集群规划
按照高可用的设计目标:需要保证至少有两个 NameNode (一主一备) 和 两个 ResourceManager (一主一备) ,同时为满足“过半写入则成功”的原则,需要至少要有3个 JournalNode 节点。这里使用三台主机进行搭建,集群规划如下:
按照高可用的设计目标:需要保证至少有两个 NameNode (一主一备) 和 两个 ResourceManager (一主一备) ,同时为满足“过半写入则成功”的原则,需要至少要有 3 个 JournalNode 节点。这里使用三台主机进行搭建,集群规划如下:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop高可用集群规划.png"/> </div>
@ -78,9 +78,9 @@ YARN ResourceManager 的高可用与 HDFS NameNode 的高可用类似,但是 R
## 三、前置条件
+ 所有服务器都安装有JDK安装步骤可以参见[LinuxJDK的安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/JDK%E5%AE%89%E8%A3%85.md)
+ 搭建好ZooKeeper集群搭建步骤可以参见[Zookeeper单机环境和集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Zookeeper单机环境和集群环境搭建.md)
+ 所有服务器之间都配置好SSH免密登录。
+ 所有服务器都安装有 JDK安装步骤可以参见[LinuxJDK 的安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/JDK%E5%AE%89%E8%A3%85.md)
+ 搭建好 ZooKeeper 集群,搭建步骤可以参见:[Zookeeper 单机环境和集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Zookeeper 单机环境和集群环境搭建.md)
+ 所有服务器之间都配置好 SSH 免密登录。
@ -88,7 +88,7 @@ YARN ResourceManager 的高可用与 HDFS NameNode 的高可用类似,但是 R
### 4.1 下载并解压
下载Hadoop。这里我下载的是CDH版本Hadoop下载地址为http://archive.cloudera.com/cdh5/cdh/5/
下载 Hadoop。这里我下载的是 CDH 版本 Hadoop下载地址为http://archive.cloudera.com/cdh5/cdh/5/
```shell
# tar -zvxf hadoop-2.6.0-cdh5.15.2.tar.gz
@ -96,7 +96,7 @@ YARN ResourceManager 的高可用与 HDFS NameNode 的高可用类似,但是 R
### 4.2 配置环境变量
编辑`profile`文件:
编辑 `profile` 文件:
```shell
# vim /etc/profile
@ -109,7 +109,7 @@ export HADOOP_HOME=/usr/app/hadoop-2.6.0-cdh5.15.2
export PATH=${HADOOP_HOME}/bin:$PATH
```
执行`source`命令,使得配置立即生效:
执行 `source` 命令,使得配置立即生效:
```shell
# source /etc/profile
@ -117,7 +117,7 @@ export PATH=${HADOOP_HOME}/bin:$PATH
### 4.3 修改配置
进入`${HADOOP_HOME}/etc/hadoop`目录下,修改配置文件。各个配置文件内容如下:
进入 `${HADOOP_HOME}/etc/hadoop` 目录下,修改配置文件。各个配置文件内容如下:
#### 1. hadoop-env.sh
@ -131,22 +131,22 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201/
```xml
<configuration>
<property>
<!-- 指定namenodehdfs协议文件系统的通信地址 -->
<!-- 指定 namenodehdfs 协议文件系统的通信地址 -->
<name>fs.defaultFS</name>
<value>hdfs://hadoop001:8020</value>
</property>
<property>
<!-- 指定hadoop集群存储临时文件的目录 -->
<!-- 指定 hadoop 集群存储临时文件的目录 -->
<name>hadoop.tmp.dir</name>
<value>/home/hadoop/tmp</value>
</property>
<property>
<!-- ZooKeeper集群的地址 -->
<!-- ZooKeeper 集群的地址 -->
<name>ha.zookeeper.quorum</name>
<value>hadoop001:2181,hadoop002:2181,hadoop002:2181</value>
</property>
<property>
<!-- ZKFC连接到ZooKeeper超时时长 -->
<!-- ZKFC 连接到 ZooKeeper 超时时长 -->
<name>ha.zookeeper.session-timeout.ms</name>
<value>10000</value>
</property>
@ -158,17 +158,17 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201/
```xml
<configuration>
<property>
<!-- 指定HDFS副本的数量 -->
<!-- 指定 HDFS 副本的数量 -->
<name>dfs.replication</name>
<value>3</value>
</property>
<property>
<!-- namenode节点数据即元数据的存放位置可以指定多个目录实现容错多个目录用逗号分隔 -->
<!-- namenode 节点数据(即元数据)的存放位置,可以指定多个目录实现容错,多个目录用逗号分隔 -->
<name>dfs.namenode.name.dir</name>
<value>/home/hadoop/namenode/data</value>
</property>
<property>
<!-- datanode节点数据即数据块的存放位置 -->
<!-- datanode 节点数据(即数据块)的存放位置 -->
<name>dfs.datanode.data.dir</name>
<value>/home/hadoop/datanode/data</value>
</property>
@ -178,57 +178,57 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201/
<value>mycluster</value>
</property>
<property>
<!-- NameNode ID列表-->
<!-- NameNode ID 列表-->
<name>dfs.ha.namenodes.mycluster</name>
<value>nn1,nn2</value>
</property>
<property>
<!-- nn1RPC通信地址 -->
<!-- nn1RPC 通信地址 -->
<name>dfs.namenode.rpc-address.mycluster.nn1</name>
<value>hadoop001:8020</value>
</property>
<property>
<!-- nn2RPC通信地址 -->
<!-- nn2RPC 通信地址 -->
<name>dfs.namenode.rpc-address.mycluster.nn2</name>
<value>hadoop002:8020</value>
</property>
<property>
<!-- nn1http通信地址 -->
<!-- nn1http 通信地址 -->
<name>dfs.namenode.http-address.mycluster.nn1</name>
<value>hadoop001:50070</value>
</property>
<property>
<!-- nn2http通信地址 -->
<!-- nn2http 通信地址 -->
<name>dfs.namenode.http-address.mycluster.nn2</name>
<value>hadoop002:50070</value>
</property>
<property>
<!-- NameNode元数据在JournalNode上的共享存储目录 -->
<!-- NameNode 元数据在 JournalNode 上的共享存储目录 -->
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://hadoop001:8485;hadoop002:8485;hadoop003:8485/mycluster</value>
</property>
<property>
<!-- Journal Edit Files的存储目录 -->
<!-- Journal Edit Files 的存储目录 -->
<name>dfs.journalnode.edits.dir</name>
<value>/home/hadoop/journalnode/data</value>
</property>
<property>
<!-- 配置隔离机制确保在任何给定时间只有一个NameNode处于活动状态 -->
<!-- 配置隔离机制,确保在任何给定时间只有一个 NameNode 处于活动状态 -->
<name>dfs.ha.fencing.methods</name>
<value>sshfence</value>
</property>
<property>
<!-- 使用sshfence机制时需要ssh免密登录 -->
<!-- 使用 sshfence 机制时需要 ssh 免密登录 -->
<name>dfs.ha.fencing.ssh.private-key-files</name>
<value>/root/.ssh/id_rsa</value>
</property>
<property>
<!-- SSH超时时间 -->
<!-- SSH 超时时间 -->
<name>dfs.ha.fencing.ssh.connect-timeout</name>
<value>30000</value>
</property>
<property>
<!-- 访问代理类用于确定当前处于Active状态的NameNode -->
<!-- 访问代理类,用于确定当前处于 Active 状态的 NameNode -->
<name>dfs.client.failover.proxy.provider.mycluster</name>
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
</property>
@ -245,57 +245,57 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201/
```xml
<configuration>
<property>
<!--配置NodeManager上运行的附属服务。需要配置成mapreduce_shuffle后才可以在Yarn上运行MapReduce程序。-->
<!--配置 NodeManager 上运行的附属服务。需要配置成 mapreduce_shuffle 后才可以在 Yarn 上运行 MapReduce 程序。-->
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<!-- 是否启用日志聚合(可选) -->
<!-- 是否启用日志聚合 (可选) -->
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>
<property>
<!-- 聚合日志的保存时间(可选) -->
<!-- 聚合日志的保存时间 (可选) -->
<name>yarn.log-aggregation.retain-seconds</name>
<value>86400</value>
</property>
<property>
<!-- 启用RM HA -->
<!-- 启用 RM HA -->
<name>yarn.resourcemanager.ha.enabled</name>
<value>true</value>
</property>
<property>
<!-- RM集群标识 -->
<!-- RM 集群标识 -->
<name>yarn.resourcemanager.cluster-id</name>
<value>my-yarn-cluster</value>
</property>
<property>
<!-- RM的逻辑ID列表 -->
<!-- RM 的逻辑 ID 列表 -->
<name>yarn.resourcemanager.ha.rm-ids</name>
<value>rm1,rm2</value>
</property>
<property>
<!-- RM1的服务地址 -->
<!-- RM1 的服务地址 -->
<name>yarn.resourcemanager.hostname.rm1</name>
<value>hadoop002</value>
</property>
<property>
<!-- RM2的服务地址 -->
<!-- RM2 的服务地址 -->
<name>yarn.resourcemanager.hostname.rm2</name>
<value>hadoop003</value>
</property>
<property>
<!-- RM1 Web应用程序的地址 -->
<!-- RM1 Web 应用程序的地址 -->
<name>yarn.resourcemanager.webapp.address.rm1</name>
<value>hadoop002:8088</value>
</property>
<property>
<!-- RM2 Web应用程序的地址 -->
<!-- RM2 Web 应用程序的地址 -->
<name>yarn.resourcemanager.webapp.address.rm2</name>
<value>hadoop003:8088</value>
</property>
<property>
<!-- ZooKeeper集群的地址 -->
<!-- ZooKeeper 集群的地址 -->
<name>yarn.resourcemanager.zk-address</name>
<value>hadoop001:2181,hadoop002:2181,hadoop003:2181</value>
</property>
@ -317,7 +317,7 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201/
```xml
<configuration>
<property>
<!--指定mapreduce作业运行在yarn上-->
<!--指定 mapreduce 作业运行在 yarn 上-->
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
@ -326,7 +326,7 @@ export JAVA_HOME=/usr/java/jdk1.8.0_201/
#### 5. slaves
配置所有从属节点的主机名或IP地址每行一个。所有从属节点上的`DataNode`服务和`NodeManager`服务都会被启动。
配置所有从属节点的主机名或 IP 地址,每行一个。所有从属节点上的 `DataNode` 服务和 `NodeManager` 服务都会被启动。
```properties
hadoop001
@ -336,7 +336,7 @@ hadoop003
### 4.4 分发程序
将Hadoop安装包分发到其他两台服务器分发后建议在这两台服务器上也配置一下Hadoop的环境变量。
Hadoop 安装包分发到其他两台服务器,分发后建议在这两台服务器上也配置一下 Hadoop 的环境变量。
```shell
# 将安装包分发到hadoop002
@ -351,7 +351,7 @@ scp -r /usr/app/hadoop-2.6.0-cdh5.15.2/ hadoop003:/usr/app/
### 5.1 启动ZooKeeper
分别到三台服务器上启动ZooKeeper服务
分别到三台服务器上启动 ZooKeeper 服务:
```ssh
zkServer.sh start
@ -359,7 +359,7 @@ scp -r /usr/app/hadoop-2.6.0-cdh5.15.2/ hadoop003:/usr/app/
### 5.2 启动Journalnode
分别到三台服务器的的`${HADOOP_HOME}/sbin`目录下,启动`journalnode`进程:
分别到三台服务器的的 `${HADOOP_HOME}/sbin` 目录下,启动 `journalnode` 进程:
```shell
hadoop-daemon.sh start journalnode
@ -367,13 +367,13 @@ hadoop-daemon.sh start journalnode
### 5.3 初始化NameNode
`hadop001`上执行`NameNode`初始化命令:
`hadop001` 上执行 `NameNode` 初始化命令:
```
hdfs namenode -format
```
执行初始化命令后,需要将`NameNode`元数据目录的内容,复制到其他未格式化的`NameNode`上。元数据存储目录就是我们在`hdfs-site.xml`中使用`dfs.namenode.name.dir`属性指定的目录。这里我们需要将其复制到`hadoop002`上:
执行初始化命令后,需要将 `NameNode` 元数据目录的内容,复制到其他未格式化的 `NameNode` 上。元数据存储目录就是我们在 `hdfs-site.xml` 中使用 `dfs.namenode.name.dir` 属性指定的目录。这里我们需要将其复制到 `hadoop002` 上:
```shell
scp -r /home/hadoop/namenode/data hadoop002:/home/hadoop/namenode/
@ -381,7 +381,7 @@ hdfs namenode -format
### 5.4 初始化HA状态
在任意一台`NameNode`上使用以下命令来初始化ZooKeeper中的HA状态
在任意一台 `NameNode` 上使用以下命令来初始化 ZooKeeper 中的 HA 状态:
```shell
hdfs zkfc -formatZK
@ -389,7 +389,7 @@ hdfs zkfc -formatZK
### 5.5 启动HDFS
进入到`hadoop001``${HADOOP_HOME}/sbin`目录下启动HDFS。此时`hadoop001``hadoop002`上的`NameNode`服务,和三台服务器上的`DataNode`服务都会被启动:
进入到 `hadoop001``${HADOOP_HOME}/sbin` 目录下,启动 HDFS。此时 `hadoop001``hadoop002` 上的 `NameNode` 服务,和三台服务器上的 `DataNode` 服务都会被启动:
```shell
start-dfs.sh
@ -397,13 +397,13 @@ start-dfs.sh
### 5.6 启动YARN
进入到`hadoop002``${HADOOP_HOME}/sbin`目录下启动YARN。此时`hadoop002`上的`ResourceManager`服务,和三台服务器上的`NodeManager`服务都会被启动:
进入到 `hadoop002``${HADOOP_HOME}/sbin` 目录下,启动 YARN。此时 `hadoop002` 上的 `ResourceManager` 服务,和三台服务器上的 `NodeManager` 服务都会被启动:
```SHEll
start-yarn.sh
```
需要注意的是,这个时候`hadoop003`上的`ResourceManager`服务通常是没有启动的,需要手动启动:
需要注意的是,这个时候 `hadoop003` 上的 `ResourceManager` 服务通常是没有启动的,需要手动启动:
```shell
yarn-daemon.sh start resourcemanager
@ -447,13 +447,13 @@ yarn-daemon.sh start resourcemanager
### 6.2 查看Web UI
HDFSYARN的端口号分别为`50070``8080`,界面应该如下:
HDFSYARN 的端口号分别为 `50070``8080`,界面应该如下:
此时hadoop001上的`NameNode`处于可用状态:
此时 hadoop001 上的 `NameNode` 处于可用状态:
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/hadoop高可用集群1.png"/> </div>
而hadoop002上的`NameNode`则处于备用状态:
hadoop002 上的 `NameNode` 则处于备用状态:
<br/>
@ -461,7 +461,7 @@ HDFS和YARN的端口号分别为`50070`和`8080`,界面应该如下:
<br/>
hadoop002上的`ResourceManager`处于可用状态:
hadoop002 上的 `ResourceManager` 处于可用状态:
<br/>
@ -469,7 +469,7 @@ hadoop002上的`ResourceManager`处于可用状态:
<br/>
hadoop003上的`ResourceManager`则处于备用状态:
hadoop003 上的 `ResourceManager` 则处于备用状态:
<br/>
@ -477,7 +477,7 @@ hadoop003上的`ResourceManager`则处于备用状态:
<br/>
同时界面上也有`Journal Manager`的相关信息:
同时界面上也有 `Journal Manager` 的相关信息:
<br/>
@ -485,21 +485,21 @@ hadoop003上的`ResourceManager`则处于备用状态:
## 七、集群的二次启动
上面的集群初次启动涉及到一些必要初始化操作所以过程略显繁琐。但是集群一旦搭建好后想要再次启用它是比较方便的步骤如下首选需要确保ZooKeeper集群已经启动
上面的集群初次启动涉及到一些必要初始化操作,所以过程略显繁琐。但是集群一旦搭建好后,想要再次启用它是比较方便的,步骤如下(首选需要确保 ZooKeeper 集群已经启动):
` hadoop001`启动 HDFS此时会启动所有与 HDFS 高可用相关的服务,包括 NameNodeDataNode 和 JournalNode
` hadoop001` 启动 HDFS此时会启动所有与 HDFS 高可用相关的服务,包括 NameNodeDataNode 和 JournalNode
```shell
start-dfs.sh
```
`hadoop002`启动YARN
`hadoop002` 启动 YARN
```SHEll
start-yarn.sh
```
这个时候`hadoop003`上的`ResourceManager`服务通常还是没有启动的,需要手动启动:
这个时候 `hadoop003` 上的 `ResourceManager` 服务通常还是没有启动的,需要手动启动:
```shell
yarn-daemon.sh start resourcemanager
@ -516,7 +516,7 @@ yarn-daemon.sh start resourcemanager
+ [HDFS High Availability Using the Quorum Journal Manager](https://hadoop.apache.org/docs/r3.1.2/hadoop-project-dist/hadoop-hdfs/HDFSHighAvailabilityWithQJM.html)
+ [ResourceManager High Availability](https://hadoop.apache.org/docs/r3.1.2/hadoop-yarn/hadoop-yarn-site/ResourceManagerHA.html)
关于Hadoop高可用原理的详细分析推荐阅读
关于 Hadoop 高可用原理的详细分析,推荐阅读:
[Hadoop NameNode 高可用 (High Availability) 实现解析](https://www.ibm.com/developerworks/cn/opensource/os-cn-hadoop-name-node/index.html)

View File

@ -17,11 +17,11 @@
## 一、Zookeeper集群搭建
为保证集群高可用Zookeeper集群的节点数最好是奇数最少有三个节点所以这里搭建一个三个节点的集群。
为保证集群高可用Zookeeper 集群的节点数最好是奇数,最少有三个节点,所以这里搭建一个三个节点的集群。
### 1.1 下载 & 解压
下载对应版本Zookeeper这里我下载的版本`3.4.14`。官方下载地址https://archive.apache.org/dist/zookeeper/
下载对应版本 Zookeeper这里我下载的版本 `3.4.14`。官方下载地址https://archive.apache.org/dist/zookeeper/
```shell
# 下载
@ -32,9 +32,9 @@ tar -zxvf zookeeper-3.4.14.tar.gz
### 1.2 修改配置
拷贝三份zookeeper安装包。分别进入安装目录的`conf`目录,拷贝配置样本`zoo_sample.cfg ``zoo.cfg`并进行修改,修改后三份配置文件内容分别如下:
拷贝三份 zookeeper 安装包。分别进入安装目录的 `conf` 目录,拷贝配置样本 `zoo_sample.cfg ``zoo.cfg` 并进行修改,修改后三份配置文件内容分别如下:
zookeeper01配置
zookeeper01 配置:
```shell
tickTime=2000
@ -51,9 +51,9 @@ server.2=127.0.0.1:2288:3388
server.3=127.0.0.1:2289:3389
```
> 如果是多台服务器则集群中每个节点通讯端口和选举端口可相同IP地址修改为每个节点所在主机IP即可。
> 如果是多台服务器则集群中每个节点通讯端口和选举端口可相同IP 地址修改为每个节点所在主机 IP 即可。
zookeeper02配置与zookeeper01相比只有`dataLogDir``dataLogDir`不同:
zookeeper02 配置,与 zookeeper01 相比,只有 `dataLogDir``dataLogDir` 不同:
```shell
tickTime=2000
@ -68,7 +68,7 @@ server.2=127.0.0.1:2288:3388
server.3=127.0.0.1:2289:3389
```
zookeeper03配置与zookeeper0102相比也只有`dataLogDir``dataLogDir`不同:
zookeeper03 配置,与 zookeeper0102 相比,也只有 `dataLogDir``dataLogDir` 不同:
```shell
tickTime=2000
@ -85,18 +85,18 @@ server.3=127.0.0.1:2289:3389
> 配置参数说明:
>
> - **tickTime**用于计算的基础时间单元。比如session超时N*tickTime
> - **initLimit**:用于集群,允许从节点连接并同步到 master节点的初始化连接时间以tickTime的倍数来表示
> - **syncLimit**:用于集群, master主节点与从节点之间发送消息请求和应答时间长度心跳机制
> - **tickTime**:用于计算的基础时间单元。比如 session 超时N*tickTime
> - **initLimit**:用于集群,允许从节点连接并同步到 master 节点的初始化连接时间,以 tickTime 的倍数来表示;
> - **syncLimit**:用于集群, master 主节点与从节点之间发送消息,请求和应答时间长度(心跳机制);
> - **dataDir**:数据存储位置;
> - **dataLogDir**:日志目录;
> - **clientPort**用于客户端连接的端口默认2181
> - **clientPort**:用于客户端连接的端口,默认 2181
### 1.3 标识节点
分别在三个节点的数据存储目录下新建`myid`文件,并写入对应的节点标识。Zookeeper集群通过`myid`文件识别集群节点并通过上文配置的节点通信端口和选举端口来进行节点通信选举出leader节点。
分别在三个节点的数据存储目录下新建 `myid` 文件,并写入对应的节点标识。Zookeeper 集群通过 `myid` 文件识别集群节点,并通过上文配置的节点通信端口和选举端口来进行节点通信,选举出 leader 节点。
创建存储目录:
@ -109,7 +109,7 @@ mkdir -vp /usr/local/zookeeper-cluster/data/02
mkdir -vp /usr/local/zookeeper-cluster/data/03
```
创建并写入节点标识到`myid`文件:
创建并写入节点标识到 `myid` 文件:
```shell
#server1
@ -135,7 +135,7 @@ echo "3" > /usr/local/zookeeper-cluster/data/03/myid
### 1.5 集群验证
使用jps查看进程并且使用`zkServer.sh status`查看集群各个节点状态。如图三个节点进程均启动成功并且两个节点为follower节点一个节点为leader节点。
使用 jps 查看进程,并且使用 `zkServer.sh status` 查看集群各个节点状态。如图三个节点进程均启动成功,并且两个节点为 follower 节点,一个节点为 leader 节点。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/zookeeper-cluster.png"/> </div>
@ -145,7 +145,7 @@ echo "3" > /usr/local/zookeeper-cluster/data/03/myid
### 2.1 下载解压
Kafka安装包官方下载地址http://kafka.apache.org/downloads ,本用例下载的版本为`2.2.0`,下载命令:
Kafka 安装包官方下载地址http://kafka.apache.org/downloads ,本用例下载的版本为 `2.2.0`,下载命令:
```shell
# 下载
@ -154,11 +154,11 @@ wget https://www-eu.apache.org/dist/kafka/2.2.0/kafka_2.12-2.2.0.tgz
tar -xzf kafka_2.12-2.2.0.tgz
```
>这里j解释一下kafka安装包的命名规则`kafka_2.12-2.2.0.tgz`为例前面的2.12代表Scala的版本号Kafka采用Scala语言进行开发后面的2.2.0则代表Kafka的版本号。
>这里 j 解释一下 kafka 安装包的命名规则:以 `kafka_2.12-2.2.0.tgz` 为例,前面的 2.12 代表 Scala 的版本号Kafka 采用 Scala 语言进行开发),后面的 2.2.0 则代表 Kafka 的版本号。
### 2.2 拷贝配置文件
进入解压目录的` config`目录下 ,拷贝三份配置文件:
进入解压目录的 ` config` 目录下 ,拷贝三份配置文件:
```shell
# cp server.properties server-1.properties
@ -201,11 +201,11 @@ log.dirs=/usr/local/kafka-logs/02
zookeeper.connect=hadoop001:2181,hadoop001:2182,hadoop001:2183
```
这里需要说明的是`log.dirs`指的是数据日志的存储位置,确切的说,就是分区数据的存储位置,而不是程序运行日志的位置。程序运行日志的位置是通过同一目录下的`log4j.properties`进行配置的。
这里需要说明的是 `log.dirs` 指的是数据日志的存储位置,确切的说,就是分区数据的存储位置,而不是程序运行日志的位置。程序运行日志的位置是通过同一目录下的 `log4j.properties` 进行配置的。
### 2.4 启动集群
分别指定不同配置文件启动三个Kafka节点。启动后可以使用jps查看进程此时应该有三个zookeeper进程和三个kafka进程。
分别指定不同配置文件,启动三个 Kafka 节点。启动后可以使用 jps 查看进程,此时应该有三个 zookeeper 进程和三个 kafka 进程。
```shell
bin/kafka-server-start.sh config/server-1.properties
@ -233,7 +233,7 @@ bin/kafka-topics.sh --describe --bootstrap-server hadoop001:9092 --topic my-repl
可以看到分区0的有0,1,2三个副本且三个副本都是可用副本都在ISR(in-sync Replica 同步副本)列表中,其中1为首领副本,此时代表集群已经搭建成功。
可以看到分区 0 的有 0,1,2 三个副本,且三个副本都是可用副本,都在 ISR(in-sync Replica 同步副本) 列表中,其中 1 为首领副本,此时代表集群已经搭建成功。

View File

@ -24,8 +24,8 @@
添加如下网络配置:
+ IPADDR需要和宿主机同一个网段
+ GATEWAY保持和宿主机一致
+ IPADDR 需要和宿主机同一个网段;
+ GATEWAY 保持和宿主机一致;
```properties
BOOTPROTO=static
@ -75,29 +75,29 @@ ONBOOT=yes
## 二、虚拟机多个静态IP配置
如果一台虚拟机需要经常在不同网络环境使用可以配置多个静态IP。
如果一台虚拟机需要经常在不同网络环境使用,可以配置多个静态 IP。
### 1. 配置多网卡
这里我是用的虚拟机是virtualBox开启多网卡配置方式如下
这里我是用的虚拟机是 virtualBox开启多网卡配置方式如下
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/virtualbox-multi-network.png"/> </div>
### 2. 查看网卡名称
使用`ifconfig`,查看第二块网卡名称,这里我的名称为`enp0s8`
使用 `ifconfig`,查看第二块网卡名称,这里我的名称为 `enp0s8`
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/mutli-net-ip.png"/> </div>
### 3. 配置第二块网卡
开启多网卡后并不会自动生成配置文件,需要拷贝`ifcfg-enp0s3`进行修改:
开启多网卡后并不会自动生成配置文件,需要拷贝 `ifcfg-enp0s3` 进行修改:
```shell
# cp ifcfg-enp0s3 ifcfg-enp0s8
```
静态IP配置方法如上这里不再赘述。除了静态IP参数外以下三个参数还需要修改UUID必须与`ifcfg-enp0s3`中的不一样:
静态 IP 配置方法如上,这里不再赘述。除了静态 IP 参数外以下三个参数还需要修改UUID 必须与 `ifcfg-enp0s3` 中的不一样:
```properties
NAME=enp0s8
@ -115,4 +115,4 @@ DEVICE=enp0s8
使用时只需要根据所处的网络环境,勾选对应的网卡即可,不使用的网卡尽量不要勾选启动。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/virtualbox启用网络.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/virtualbox启用网络.png"/> </div>

View File

@ -36,11 +36,11 @@
大数据处理最重要的环节就是数据分析,数据分析通常分为两种:批处理和流处理。
+ **批处理**:对一段时间内海量的离线数据进行统一的处理,对应的处理框架有 Hadoop MapReduce、Spark、Flink 等;
+ **流处理**:对运动中的数据进行处理,即在接收数据的同时就对其进行处理,对应的处理框架有 Storm、Spark Streaming、Flink Streaming等。
+ **流处理**:对运动中的数据进行处理,即在接收数据的同时就对其进行处理,对应的处理框架有 Storm、Spark Streaming、Flink Streaming 等。
批处理和流处理各有其适用的场景,时间不敏感或者硬件资源有限,可以采用批处理;时间敏感和及时性要求高就可以采用流处理。随着服务器硬件的价格越来越低和大家对及时性的要求越来越高,流处理越来越普遍,如股票价格预测和电商运营数据分析等。
上面的框架都是需要通过编程来进行数据分析,那么如果你不是一个后台工程师,是不是就不能进行数据的分析了?当然不是,大数据是一个非常完善的生态圈,有需求就有解决方案。为了能够让熟悉 SQL 的人员也能够进行数据的分析,查询分析框架应运而生,常用的有 Hive 、Spark SQL 、Flink SQL、 Pig、Phoenix 等。这些框架都能够使用标准的 SQL 或者 类SQL 语法灵活地进行数据的查询分析。这些 SQL 经过解析优化后转换为对应的作业程序来运行,如 Hive 本质上就是将 SQL 转换为 MapReduce 作业Spark SQL 将 SQL 转换为一系列的 RDDs 和转换关系transformationsPhoenix 将 SQL 查询转换为一个或多个HBase Scan。
上面的框架都是需要通过编程来进行数据分析,那么如果你不是一个后台工程师,是不是就不能进行数据的分析了?当然不是,大数据是一个非常完善的生态圈,有需求就有解决方案。为了能够让熟悉 SQL 的人员也能够进行数据的分析,查询分析框架应运而生,常用的有 Hive 、Spark SQL 、Flink SQL、 Pig、Phoenix 等。这些框架都能够使用标准的 SQL 或者 类 SQL 语法灵活地进行数据的查询分析。这些 SQL 经过解析优化后转换为对应的作业程序来运行,如 Hive 本质上就是将 SQL 转换为 MapReduce 作业Spark SQL 将 SQL 转换为一系列的 RDDs 和转换关系transformationsPhoenix 将 SQL 查询转换为一个或多个 HBase Scan。
### 1.4 数据应用
@ -66,25 +66,25 @@
大数据框架大多采用 Java 语言进行开发,并且几乎全部的框架都会提供 Java API 。Java 是目前比较主流的后台开发语言,所以网上免费的学习资源也比较多。如果你习惯通过书本进行学习,这里推荐以下入门书籍:
+ [《Java编程的逻辑》](https://book.douban.com/subject/30133440/):这里一本国人编写的系统入门 Java 的书籍,深入浅出,内容全面;
+ 《Java核心技术》目前最新的是第10版有[卷一](https://book.douban.com/subject/26880667/)和[卷二](https://book.douban.com/subject/27165931/)两册,卷二可以选择性阅读,因为其中很多章节的内容在实际开发中很少用到。
+ [《Java 编程的逻辑》](https://book.douban.com/subject/30133440/):这里一本国人编写的系统入门 Java 的书籍,深入浅出,内容全面;
+ 《Java 核心技术》:目前最新的是第 10 版,有[卷一](https://book.douban.com/subject/26880667/) 和[卷二](https://book.douban.com/subject/27165931/) 两册,卷二可以选择性阅读,因为其中很多章节的内容在实际开发中很少用到。
目前大多数框架要求 Java 版本至少是 1.8,这是由于 Java 1.8 提供了函数式编程,使得可以用更精简的代码来实现之前同样的功能,比如你调用 Spark API使用 1.8 可能比 1.7 少数倍的代码,所以这里额外推荐阅读 [《Java 8实战》](https://book.douban.com/subject/26772632/) 这本书籍。
目前大多数框架要求 Java 版本至少是 1.8,这是由于 Java 1.8 提供了函数式编程,使得可以用更精简的代码来实现之前同样的功能,比如你调用 Spark API使用 1.8 可能比 1.7 少数倍的代码,所以这里额外推荐阅读 [《Java 8 实战》](https://book.douban.com/subject/26772632/) 这本书籍。
#### 2. Scala
Scala 是一门综合了面向对象和函数式编程概念的静态类型的编程语言,它运行在 Java虚拟机上可以与所有的Java类库无缝协作著名的 Kafka 就是采用 Scala 语言进行开发的。
Scala 是一门综合了面向对象和函数式编程概念的静态类型的编程语言,它运行在 Java 虚拟机上,可以与所有的 Java 类库无缝协作,著名的 Kafka 就是采用 Scala 语言进行开发的。
为什么需要学习 Scala 语言 这是因为当前最火的计算框架 Flink 和 Spark 都提供了 Scala 语言的接口,使用它进行开发,比使用 Java 8 所需要的代码更少,且 Spark 就是使用 Scala 语言进行编写的,学习 Scala 可以帮助你更深入的理解 Spark。同样的对于习惯书本学习的小伙伴这里推荐两本入门书籍
- [《快学Scala(第2版)》](https://book.douban.com/subject/27093751/)
- [《Scala编程(第3版)》](https://book.douban.com/subject/27591387/)
- [《快学 Scala(第 2 版)》](https://book.douban.com/subject/27093751/)
- [《Scala 编程 (第 3 版)》](https://book.douban.com/subject/27591387/)
> 这里说明一下,如果你的时间有限,不一定要学完 Scala 才去学习大数据框架。Scala 确实足够的精简和灵活,但其在语言复杂度上略大于 Java例如隐式转换和隐式参数等概念在初次涉及时会比较难以理解所以你可以在了解 Spark 后再去学习 Scala因为类似隐式转换等概念在 Spark 源码中有大量的运用。
### 2.2 Linux 基础
通常大数据框架都部署在 Linux 服务器上,所以需要具备一定的 Linux 知识。Linux 书籍当中比较著名的是 《鸟哥私房菜》系列,这个系列很全面也很经典。但如果你希望能够快速地入门,这里推荐[《Linux就该这么学》](https://www.linuxprobe.com/),其网站上有免费的电子书版本。
通常大数据框架都部署在 Linux 服务器上,所以需要具备一定的 Linux 知识。Linux 书籍当中比较著名的是 《鸟哥私房菜》系列,这个系列很全面也很经典。但如果你希望能够快速地入门,这里推荐[《Linux 就该这么学》](https://www.linuxprobe.com/),其网站上有免费的电子书版本。
### 2.3 构建工具
@ -136,20 +136,20 @@ Scala 是一门综合了面向对象和函数式编程概念的静态类型的
大数据最权威和最全面的学习资料就是官方文档。热门的大数据框架社区都比较活跃、版本更新迭代也比较快,所以其出版物都明显滞后于其实际版本,基于这个原因采用书本学习不是一个最好的方案。比较庆幸的是,大数据框架的官方文档都写的比较好,内容完善,重点突出,同时都采用了大量配图进行辅助讲解。当然也有一些优秀的书籍历经时间的检验,至今依然很经典,这里列出部分个人阅读过的经典书籍:
- [《hadoop 权威指南(第四版)》](https://book.douban.com/subject/27115351/) 2017年
- [《Kafka权威指南》](https://book.douban.com/subject/27665114/) 2017年
- [《从PaxosZookeeper 分布式一致性原理与实践》](https://book.douban.com/subject/26292004/) 2015年
- [《Spark技术内幕 深入解析Spark内核架构设计与实现原理》](https://book.douban.com/subject/26649141/) 2015年
- [《Spark.The.Definitive.Guide》](https://book.douban.com/subject/27035127/) 2018年
- [《HBase权威指南》](https://book.douban.com/subject/10748460/) 2012年
- [《Hive编程指南》](https://book.douban.com/subject/25791255/) 2013年
- [《hadoop 权威指南 (第四版)》](https://book.douban.com/subject/27115351/) 2017
- [《Kafka 权威指南》](https://book.douban.com/subject/27665114/) 2017
- [《从 PaxosZookeeper 分布式一致性原理与实践》](https://book.douban.com/subject/26292004/) 2015
- [《Spark 技术内幕 深入解析 Spark 内核架构设计与实现原理》](https://book.douban.com/subject/26649141/) 2015
- [《Spark.The.Definitive.Guide》](https://book.douban.com/subject/27035127/) 2018
- [《HBase 权威指南》](https://book.douban.com/subject/10748460/) 2012
- [《Hive 编程指南》](https://book.douban.com/subject/25791255/) 2013
#### 3. 视频学习资料
上面我推荐的都是书籍学习资料,很少推荐视频学习资料,这里说明一下原因:因为书籍历经时间的考验,能够再版的或者豆瓣等平台评价高的证明都是被大众所认可的,从概率的角度上来说,其必然更加优秀,不容易浪费大家的学习时间和精力,所以我个人更倾向于官方文档或者书本的学习方式,而不是视频。因为视频学习资料,缺少一个公共的评价平台和完善的评价机制,所以其质量良莠不齐。但是视频任然有其不可替代的好处,学习起来更直观、印象也更深刻,所以对于习惯视频学习的小伙伴,这里我各推荐一个免费的和付费的视频学习资源,大家按需选择:
+ 免费学习资源:尚硅谷大数据学习路线 —— [下载链接](http://www.atguigu.com/bigdata_video.shtml#bigdata) \ [在线观看链接](https://space.bilibili.com/302417610/)
+ 付费学习资源:[慕课网 Michael PK的系列课程](https://www.imooc.com/t/2781843)
+ 付费学习资源:[慕课网 Michael PK 的系列课程](https://www.imooc.com/t/2781843)
## 三、开发工具
@ -161,7 +161,7 @@ Scala 是一门综合了面向对象和函数式编程概念的静态类型的
**MobaXterm**:大数据的框架通常都部署在服务器上,这里推荐使用 MobaXterm 进行连接。同样是免费开源的,支持多种连接协议,支持拖拽上传文件,支持使用插件扩展;
**Translate Man**:一款浏览器上免费的翻译插件(谷歌和火狐均支持)。它采用谷歌的翻译接口,准确性非常高,支持划词翻译,可以辅助进行官方文档的阅读。
**Translate Man**:一款浏览器上免费的翻译插件 (谷歌和火狐均支持)。它采用谷歌的翻译接口,准确性非常高,支持划词翻译,可以辅助进行官方文档的阅读。
## 四、结语

View File

@ -4,57 +4,57 @@
### 一、基础软件安装
1. [Linux环境下JDK安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK安装.md)
2. [Linux环境下Python安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxPython安装.md)
3. [虚拟机静态IP及多IP配置](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/虚拟机静态IP及多IP配置.md)
1. [Linux 环境下 JDK 安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxJDK 安装.md)
2. [Linux 环境下 Python 安装](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxPython 安装.md)
3. [虚拟机静态 IP 及多 IP 配置](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/虚拟机静态 IP 及多 IP 配置.md)
### 二、Hadoop
1. [Hadoop单机环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Hadoop单机环境搭建.md)
2. [Hadoop集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Hadoop集群环境搭建.md)
3. [基于Zookeeper搭建Hadoop高可用集群](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/基于Zookeeper搭建Hadoop高可用集群.md)
1. [Hadoop 单机环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Hadoop 单机环境搭建.md)
2. [Hadoop 集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Hadoop 集群环境搭建.md)
3. [基于 Zookeeper 搭建 Hadoop 高可用集群](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/基于 Zookeeper 搭建 Hadoop 高可用集群.md)
### 三、Spark
1. [Spark开发环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/SparkSpark开发环境搭建.md)
2. [基于Zookeeper搭建Spark高可用集群](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Spark集群环境搭建.md)
1. [Spark 开发环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/SparkSpark 开发环境搭建.md)
2. [基于 Zookeeper 搭建 Spark 高可用集群](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Spark 集群环境搭建.md)
### 四、Storm
1. [Storm单机环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Storm单机环境搭建.md)
2. [Storm集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Storm集群环境搭建.md)
1. [Storm 单机环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Storm 单机环境搭建.md)
2. [Storm 集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Storm 集群环境搭建.md)
### 五、HBase
1. [HBase单机环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/HBase单机环境搭建.md)
2. [HBase集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/HBase集群环境搭建.md)
1. [HBase 单机环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/HBase 单机环境搭建.md)
2. [HBase 集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/HBase 集群环境搭建.md)
### 六、Flume
1. [Linux环境下Flume的安装部署](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxFlume的安装.md)
1. [Linux 环境下 Flume 的安装部署](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/LinuxFlume 的安装.md)
### 七、Azkaban
1. [Azkaban3.x编译及部署](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Azkaban_3.x_编译及部署.md)
1. [Azkaban3.x 编译及部署](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Azkaban_3.x_ 编译及部署.md)
### 八、Hive
1. [Linux环境下Hive的安装部署](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Linux环境下Hive的安装部署.md)
1. [Linux 环境下 Hive 的安装部署](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Linux 环境下 Hive 的安装部署.md)
### 九、Zookeeper
1. [Zookeeper单机环境和集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Zookeeper单机环境和集群环境搭建.md)
1. [Zookeeper 单机环境和集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Zookeeper 单机环境和集群环境搭建.md)
### 十、Kafka
1. [基于Zookeeper搭建Kafka高可用集群](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/基于Zookeeper搭建Kafka高可用集群.md)
1. [基于 Zookeeper 搭建 Kafka 高可用集群](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/基于 Zookeeper 搭建 Kafka 高可用集群.md)
### 版本说明
由于Apache Hadoop 原有安装包之间兼容性比较差,所以如无特殊需求,本仓库一律选择 **CDH** (Cloudera's Distribution, including Apache Hadoop) 版本的安装包。它基于稳定版本的 Apache Hadoop 构建,并做了兼容性测试,是目前生产环境中使用最为广泛的版本。
由于 Apache Hadoop 原有安装包之间兼容性比较差,所以如无特殊需求,本仓库一律选择 **CDH** (Cloudera's Distribution, including Apache Hadoop) 版本的安装包。它基于稳定版本的 Apache Hadoop 构建,并做了兼容性测试,是目前生产环境中使用最为广泛的版本。
最新的 CDH 5 的下载地址为http://archive.cloudera.com/cdh5/cdh/5/ 。这个页面很大且加载速度比较慢,需要耐心等待页面加载完成。上半部分是文档链接,后半部分才是安装包。同一个 CDH 版本的不同框架间都做了集成测试可以保证没有任何JAR包冲突。安装包包名通常如下所示这里 CDH 版本都是 `5.15.2` ,前面是各个软件自己的版本 未避免出现不必要的JAR包冲突**请务必保持 CDH 的版本一致**。
最新的 CDH 5 的下载地址为http://archive.cloudera.com/cdh5/cdh/5/ 。这个页面很大且加载速度比较慢,需要耐心等待页面加载完成。上半部分是文档链接,后半部分才是安装包。同一个 CDH 版本的不同框架间都做了集成测试,可以保证没有任何 JAR 包冲突。安装包包名通常如下所示,这里 CDH 版本都是 `5.15.2` ,前面是各个软件自己的版本 ,未避免出现不必要的 JAR 包冲突,**请务必保持 CDH 的版本一致**。
```hsell
hadoop-2.6.0-cdh5.15.2.tar.gz

View File

@ -17,26 +17,26 @@
## 一、简介
在提交大数据作业到集群上运行时通常需要先将项目打成JAR包。这里以Maven为例常用打包方式如下
在提交大数据作业到集群上运行时,通常需要先将项目打成 JAR 包。这里以 Maven 为例,常用打包方式如下:
- 不加任何插件直接使用mvn package打包
- 使用maven-assembly-plugin插件
- 使用maven-shade-plugin插件
- 使用maven-jar-pluginmaven-dependency-plugin插件
- 不加任何插件,直接使用 mvn package 打包;
- 使用 maven-assembly-plugin 插件;
- 使用 maven-shade-plugin 插件;
- 使用 maven-jar-pluginmaven-dependency-plugin 插件;
以下分别进行详细的说明。
## 二、mvn package
不在POM中配置任何插件直接使用`mvn package`进行项目打包这对于没有使用外部依赖包的项目是可行的。但如果项目中使用了第三方JAR包就会出现问题因为`mvn package`打的JAR包中是不含有依赖包会导致作业运行时出现找不到第三方依赖的异常。这种方式局限性比较大因为实际的项目往往很复杂通常都会依赖第三方JAR。
不在 POM 中配置任何插件,直接使用 `mvn package` 进行项目打包,这对于没有使用外部依赖包的项目是可行的。但如果项目中使用了第三方 JAR 包,就会出现问题,因为 `mvn package` 打的 JAR 包中是不含有依赖包,会导致作业运行时出现找不到第三方依赖的异常。这种方式局限性比较大,因为实际的项目往往很复杂,通常都会依赖第三方 JAR。
大数据框架的开发者也考虑到这个问题,所以基本所有的框架都支持在提交作业时使用`--jars`指定第三方依赖包但是这种方式的问题同样很明显就是你必须保持生产环境与开发环境中的所有JAR包版本一致这是有维护成本的。
大数据框架的开发者也考虑到这个问题,所以基本所有的框架都支持在提交作业时使用 `--jars` 指定第三方依赖包,但是这种方式的问题同样很明显,就是你必须保持生产环境与开发环境中的所有 JAR 包版本一致,这是有维护成本的。
基于上面这些原因,最简单的是采用`All In One`的打包方式把所有依赖都打包到一个JAR文件中此时对环境的依赖性最小。要实现这个目的可以使用Maven提供的`maven-assembly-plugin``maven-shade-plugin`插件。
基于上面这些原因,最简单的是采用 `All In One` 的打包方式,把所有依赖都打包到一个 JAR 文件中,此时对环境的依赖性最小。要实现这个目的,可以使用 Maven 提供的 `maven-assembly-plugin``maven-shade-plugin` 插件。
## 三、maven-assembly-plugin插件
`Assembly`插件支持将项目的所有依赖、文件都打包到同一个输出文件中。目前支持输出以下文件类型:
`Assembly` 插件支持将项目的所有依赖、文件都打包到同一个输出文件中。目前支持输出以下文件类型:
- zip
- tar
@ -49,7 +49,7 @@
- war
### 3.1 基本使用
在POM.xml中引入插件指定打包格式的配置文件`assembly.xml`(名称可自定义),并指定作业的主入口类:
POM.xml 中引入插件,指定打包格式的配置文件 `assembly.xml`(名称可自定义),并指定作业的主入口类:
```xml
<build>
@ -71,7 +71,7 @@
</build>
```
assembly.xml文件内容如下
assembly.xml 文件内容如下:
```xml
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
@ -93,7 +93,7 @@ assembly.xml文件内容如下
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
<!--这里以排除storm环境中已经提供的storm-core为例演示排除Jar包-->
<!--这里以排除 storm 环境中已经提供的 storm-core 为例,演示排除 Jar 包-->
<excludes>
<exclude>org.apache.storm:storm-core</exclude>
</excludes>
@ -104,13 +104,13 @@ assembly.xml文件内容如下
### 3.2 打包命令
采用maven-assembly-plugin进行打包时命令如下
采用 maven-assembly-plugin 进行打包时命令如下:
```shell
# mvn assembly:assembly
```
打包后会同时生成两个JAR包其中后缀为`jar-with-dependencies`是含有第三方依赖的JAR包后缀是由`assembly.xml``<id>`标签指定的,可以自定义修改。
打包后会同时生成两个 JAR 包,其中后缀为 `jar-with-dependencies` 是含有第三方依赖的 JAR 包,后缀是由 `assembly.xml``<id>` 标签指定的,可以自定义修改。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-jar.png"/> </div>
@ -120,13 +120,13 @@ assembly.xml文件内容如下
## 四、maven-shade-plugin插件
`maven-shade-plugin``maven-assembly-plugin`功能更为强大比如你的工程依赖很多的JAR包而被依赖的JAR又会依赖其他的JAR包这样,当工程中依赖到不同的版本的 JAR时并且JAR中具有相同名称的资源文件时shade插件会尝试将所有资源文件打包在一起时而不是和assembly一样执行覆盖操作。
`maven-shade-plugin``maven-assembly-plugin` 功能更为强大,比如你的工程依赖很多的 JAR 包,而被依赖的 JAR 又会依赖其他的 JAR 包,这样,当工程中依赖到不同的版本的 JAR 时,并且 JAR 中具有相同名称的资源文件时shade 插件会尝试将所有资源文件打包在一起时,而不是和 assembly 一样执行覆盖操作。
**通常使用`maven-shade-plugin`就能够完成大多数的打包需求,其配置简单且适用性最广,因此建议优先使用此方式。**
**通常使用 `maven-shade-plugin` 就能够完成大多数的打包需求,其配置简单且适用性最广,因此建议优先使用此方式。**
### 4.1 基本配置
采用`maven-shade-plugin`进行打包时候,配置示例如下:
采用 `maven-shade-plugin` 进行打包时候,配置示例如下:
```xml
<plugin>
@ -177,22 +177,22 @@ assembly.xml文件内容如下
</plugin>
```
以上配置来源于Storm Github在上面的配置中排除了部分文件这是因为有些JAR包生成时会使用jarsigner生成文件签名(完成性校验)分为两个文件存放在META-INF目录下
以上配置来源于 Storm Github在上面的配置中排除了部分文件这是因为有些 JAR 包生成时,会使用 jarsigner 生成文件签名 (完成性校验),分为两个文件存放在 META-INF 目录下:
- a signature file, with a .SF extension
- a signature block file, with a .DSA, .RSA, or .EC extension。
如果某些包的存在重复引用,这可能会导致在打包时候出现`Invalid signature file digest for Manifest main attributes`异常,所以在配置中排除这些文件。
如果某些包的存在重复引用,这可能会导致在打包时候出现 `Invalid signature file digest for Manifest main attributes` 异常,所以在配置中排除这些文件。
### 4.2 打包命令
使用maven-shade-plugin进行打包的时候打包命令和普通打包一样
使用 maven-shade-plugin 进行打包的时候,打包命令和普通打包一样:
```shell
# mvn package
```
打包后会生成两个JAR包提交到服务器集群时使用非original开头的JAR。
打包后会生成两个 JAR 包,提交到服务器集群时使用非 original 开头的 JAR。
<div align="center"> <img src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/storm-jar2.png"/> </div>
@ -202,7 +202,7 @@ assembly.xml文件内容如下
### 1. 使用非Maven仓库中的Jar
通常上面两种打包能够满足大多数的使用场景。但是如果你想把某些没有被Maven管理JAR包打入到最终的JAR中比如你在`resources/lib`下引入的其他非Maven仓库中的JAR此时可以使用`maven-jar-plugin``maven-dependency-plugin`插件将其打入最终的JAR中。
通常上面两种打包能够满足大多数的使用场景。但是如果你想把某些没有被 Maven 管理 JAR 包打入到最终的 JAR 中,比如你在 `resources/lib` 下引入的其他非 Maven 仓库中的 JAR此时可以使用 `maven-jar-plugin``maven-dependency-plugin` 插件将其打入最终的 JAR 中。
```xml
<build>
@ -214,7 +214,7 @@ assembly.xml文件内容如下
<archive>
<manifest>
<addClasspath>true</addClasspath>
<!--指定resources/lib目录-->
<!--指定 resources/lib 目录-->
<classpathPrefix>lib/</classpathPrefix>
<!--应用的主入口类-->
<mainClass>com.heibaiying.BigDataApp</mainClass>
@ -230,11 +230,11 @@ assembly.xml文件内容如下
<id>copy</id>
<phase>compile</phase>
<goals>
<!--将resources/lib目录所有Jar包打进最终的依赖中-->
<!--将 resources/lib 目录所有 Jar 包打进最终的依赖中-->
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!--将resources/lib目录所有Jar包一并拷贝到输出目录的lib目录下-->
<!--将 resources/lib 目录所有 Jar 包一并拷贝到输出目录的 lib 目录下-->
<outputDirectory>
${project.build.directory}/lib
</outputDirectory>
@ -248,24 +248,24 @@ assembly.xml文件内容如下
### 2. 排除集群中已经存在的Jar
通常为了避免冲突官方文档都会建议你排除集群中已经提供的JAR包如下
通常为了避免冲突,官方文档都会建议你排除集群中已经提供的 JAR 包,如下:
Spark 官方文档 Submitting Applications 章节:
> When creating assembly jars, list Spark and Hadoop as `provided` dependencies; these need not be bundled since they are provided by the cluster manager at runtime.
Strom官方文档 Running Topologies on a Production Cluster 章节:
Strom 官方文档 Running Topologies on a Production Cluster 章节:
>Then run mvn assembly:assembly to get an appropriately packaged jar. Make sure you exclude the Storm jars since the cluster already has Storm on the classpath.
按照以上说明排除JAR包的方式主要有两种
按照以上说明,排除 JAR 包的方式主要有两种:
+ 对需要排除的依赖添加`<scope>provided</scope>`标签此时该JAR包会被排除但是不建议使用这种方式因为此时你在本地运行也无法使用该JAR包
+ 建议直接在`maven-assembly-plugin``maven-shade-plugin`的配置文件中使用`<exclude>`进行排除。
+ 对需要排除的依赖添加 `<scope>provided</scope>` 标签,此时该 JAR 包会被排除,但是不建议使用这种方式,因为此时你在本地运行也无法使用该 JAR 包;
+ 建议直接在 `maven-assembly-plugin``maven-shade-plugin` 的配置文件中使用 `<exclude>` 进行排除。
### 3. 打包Scala文件
如果你使用到Scala语言进行编程此时需要特别注意 默认情况下Maven是不会把`scala`文件打入最终的JAR中需要额外添加`maven-scala-plugin`插件,常用配置如下:
如果你使用到 Scala 语言进行编程,此时需要特别注意 :默认情况下 Maven 是不会把 `scala` 文件打入最终的 JAR 中,需要额外添加 `maven-scala-plugin` 插件,常用配置如下:
```xml
<plugin>
@ -297,10 +297,10 @@ Strom官方文档 Running Topologies on a Production Cluster 章节:
## 参考资料
关于Maven各个插件的详细配置可以查看其官方文档
关于 Maven 各个插件的详细配置可以查看其官方文档:
+ maven-assembly-plugin : http://maven.apache.org/plugins/maven-assembly-plugin/
+ maven-shade-plugin : http://maven.apache.org/plugins/maven-shade-plugin/
+ maven-jar-plugin : http://maven.apache.org/plugins/maven-jar-plugin/
+ maven-dependency-plugin : http://maven.apache.org/components/plugins/maven-dependency-plugin/
关于maven-shade-plugin的更多配置也可以参考该博客 [maven-shade-plugin 入门指南](https://www.jianshu.com/p/7a0e20b30401)
关于 maven-shade-plugin 的更多配置也可以参考该博客: [maven-shade-plugin 入门指南](https://www.jianshu.com/p/7a0e20b30401)

View File

@ -4,28 +4,28 @@
## :book: 经典书籍
- [《hadoop 权威指南(第四版)》](https://book.douban.com/subject/27115351/) 2017年
- [《Kafka权威指南》](https://book.douban.com/subject/27665114/) 2017年
- [《从PaxosZookeeper 分布式一致性原理与实践》](https://book.douban.com/subject/26292004/) 2015年
- [《Spark技术内幕 深入解析Spark内核架构设计与实现原理》](https://book.douban.com/subject/26649141/) 2015年
- [《Spark.The.Definitive.Guide》](https://book.douban.com/subject/27035127/) 2018年
- [《HBase权威指南》](https://book.douban.com/subject/10748460/) 2012年
- [《Hive编程指南》](https://book.douban.com/subject/25791255/) 2013年
- [《快学Scala(第2版)》](https://book.douban.com/subject/27093751/) 2017年
- [《Scala编程》](https://book.douban.com/subject/27591387/) 2018年
- [《hadoop 权威指南 (第四版)》](https://book.douban.com/subject/27115351/) 2017
- [《Kafka 权威指南》](https://book.douban.com/subject/27665114/) 2017
- [《从 PaxosZookeeper 分布式一致性原理与实践》](https://book.douban.com/subject/26292004/) 2015
- [《Spark 技术内幕 深入解析 Spark 内核架构设计与实现原理》](https://book.douban.com/subject/26649141/) 2015
- [《Spark.The.Definitive.Guide》](https://book.douban.com/subject/27035127/) 2018
- [《HBase 权威指南》](https://book.douban.com/subject/10748460/) 2012
- [《Hive 编程指南》](https://book.douban.com/subject/25791255/) 2013
- [《快学 Scala(第 2 版)》](https://book.douban.com/subject/27093751/) 2017
- [《Scala 编程》](https://book.douban.com/subject/27591387/) 2018
## :computer: 官方文档
上面的书籍我都列出了出版日期,可以看到大部分书籍的出版时间都比较久远了,虽然这些书籍比较经典,但是很多书籍在软件版本上已经滞后了很多。所以推荐优先选择各个框架的**官方文档**作为学习资料。大数据框架的官方文档都很全面,并且对知识点的讲解都做到了简明扼要。这里以 [Spark RDD 官方文档](https://spark.apache.org/docs/latest/rdd-programming-guide.html)为例你会发现不仅清晰的知识点导航而且所有示例都给出了JavaScalaPython三种语言的版本除了官方文档其他书籍很少能够做到这一点。
上面的书籍我都列出了出版日期,可以看到大部分书籍的出版时间都比较久远了,虽然这些书籍比较经典,但是很多书籍在软件版本上已经滞后了很多。所以推荐优先选择各个框架的**官方文档**作为学习资料。大数据框架的官方文档都很全面,并且对知识点的讲解都做到了简明扼要。这里以 [Spark RDD 官方文档](https://spark.apache.org/docs/latest/rdd-programming-guide.html) 为例,你会发现不仅清晰的知识点导航,而且所有示例都给出了 JavaScalaPython 三种语言的版本,除了官方文档,其他书籍很少能够做到这一点。
## :orange_book: 优秀博客
- 有态度的HBase/Spark/BigDatahttp://hbasefly.com/
- 深入Apache Spark的设计和实现原理 https://github.com/JerryLead/SparkInternals
- 有态度的 HBase/Spark/BigDatahttp://hbasefly.com/
- 深入 Apache Spark 的设计和实现原理 https://github.com/JerryLead/SparkInternals
@ -39,17 +39,17 @@
#### 2. MobaXterm
大数据的框架通常都部署在服务器上这里推荐使用MobaXterm进行连接。同样是免费开源的支持多种连接协议支持拖拽上传文件支持使用插件扩展。
大数据的框架通常都部署在服务器上,这里推荐使用 MobaXterm 进行连接。同样是免费开源的,支持多种连接协议,支持拖拽上传文件,支持使用插件扩展。
官方网站https://mobaxterm.mobatek.net/
#### 3. Translate Man
Translate Man是一款浏览器上的翻译插件(谷歌和火狐均支持)。它采用谷歌的翻译接口,准确性非常高,支持划词翻译,可以辅助进行官方文档的阅读。
Translate Man 是一款浏览器上的翻译插件 (谷歌和火狐均支持)。它采用谷歌的翻译接口,准确性非常高,支持划词翻译,可以辅助进行官方文档的阅读。
#### 4. ProcessOn
ProcessOn式一个在线绘图平台使用起来非常便捷可以用于笔记或者博客配图的绘制。
ProcessOn 式一个在线绘图平台,使用起来非常便捷,可以用于笔记或者博客配图的绘制。
官方网站https://www.processon.com/