Spark 2.0.2 中文文档

来源：ApacheCN

Spark 概述

原文链接 : http://spark.apache.org/docs/latest/index.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2883720

贡献者 : 漫步，那伊抹微笑

Apache Spark 是一个快速的、多用途的集群计算系统。在 Java，Scala，Python 和 R 语言以及一个支持常见的图计算的经过优化的引擎中提供了高级 API。它还支持一组丰富的高级工具，包括用于 SQL 和结构化数据处理的 Spark SQL，用于机器学习的 MLlib，用于图形处理的 GraphX、以及 Spark Streaming。

下载

从该项目官网的 下载页面 获取 Spark，该文档用于 Spark 2.0.2 版本。Spark 使用了用于 HDFS 和 YRAN 的 Hadoop client 的库。为了适用于主流的 Hadoop 版本可以下载先前的 package。用户还可以下载 “Hadoop free” binary 并且可以 通过增加 Spark 的 classpath 来与任何的 Hadoop 版本一起运行 Spark。

如果您希望从源码中构建 Spark，请访问 构建 Spark。

Spark 既可以在 Windows 上运行又可以在类似 UNIX 的系统（例如，Linux，Mac OS）上运行。它很容易在一台机器上本地运行 - 您只需要在您的系统 PATH 上安装 Java，或者将 JAVA_HOME 环境变量指向一个 Java 安装目录。

Spark 可运行在 Java 7+，Python 2.6+/3.4 和 R 3.1+ 的环境上。 针对 Scala API，Spark 2.0.1 使用了 Scala 2.11。 您将需要去使用一个可兼容的 Scala 版本（2.11.x）。

运行示例和 Shell

Spark 自带了几个示例程序。 Scala，Java，Python 和 R 的示例在 examples/src/main 目录中。在最顶层的 Spark 目录中使用 bin/run-example <class> [params] 该命令来运行 Java 或者 Scala 中的某个示例程序。（在该例子的底层，调用了 spark-submit 脚本以启动应用程序 ）。 例如，

./bin/run-example SparkPi 10

您也可以通过一个改进版的 Scala shell 来运行交互式的 Spark。这是一个来学习该框架比较好的方式。

 ./bin/spark-shell --master local[2]

这个 --master 选项可以指定为 分布式集群中的 master URL，或者指定为 local 以使用 1 个线程在本地运行，或者指定为 local[N] 以使用 N 个线程在本地运行 。您应该指定为 local 来启动以便测试。该选项的完整列表，请使用 --help 选项来运行 Spark shell。

Spark 同样支持 Python API。在 Python interpreter（解释器）中运行交互式的 Spark，请使用 bin/pyspark : 

 ./bin/pyspark --master local[2]

Python 中也提供了应用示例。例如，

./bin/spark-submit examples/src/main/python/pi.py 10

从 1.4 开始（仅包含了 DataFrames API）Spark 也提供了一个用于实验性的 R API。为了在 R interpreter（解释器）中运行交互式的 Spark，请执行 bin/sparkR : 

 ./bin/sparkR --master local[2]

R 中也提供了应用示例。例如，

./bin/spark-submit examples/src/main/r/dataframe.R

在集群上运行

Spark 集群模式概述 说明了在集群上运行的主要的概念。Spark 既可以独立运行，也可以在几个已存在的 Cluster Manager（集群管理器）上运行。它当前提供了几种用于部署的选项 : 

Spark Standalone 模式 : 在私有集群上部署 Spark 最简单的方式。

Spark on Mesos

Spark on YARN

快速跳转

编程指南 : 

快速入门 : 简单的介绍 Spark API，从这里开始！~

Spark 编程指南 : 在所有 Spark 支持的语言（Scala，Java，Python，R）中的详细概述。

构建在 Spark 之上的模块 : 

Spark Streaming : 实时数据流处理。

Spark SQL，Datasets，和 DataFrames : 支持结构化数据和关系查询。

MLlib : 内置的机器学习库。

GraphX : 新一代用于图形处理的 Spark API。

API文档:

Spark Scala API(Scaladoc)

Spark Java API(Javadoc)

Spark Python API(Sphinx)

Spark R API(Roxygen2)

部署指南:

集群模式概述 : 在集群上运行时概念和组件的概述。

提交应用程序 : 打包和部署应用。

部署模式 : 

Amazon EC2 : 花费大约5分钟的时间让您在EC2上启动一个集群的介绍

Spark Standalone 模式 : 在不依赖第三方 Cluster Manager 的情况下快速的启动一个独立的集群

Spark on Mesos : 使用 Apache Mesos 来部署一个私有的集群

Spark on YARN : 在 Hadoop NextGen（YARN）上部署 Spark

其他文件:

配置: 通过它的配置系统定制 Spark

监控 : 监控应用程序的运行情况

优化指南 : 性能优化和内存调优的最佳实践

作业调度 : 资源调度和任务调度

安全性 : Spark 安全性支持

硬件配置 : 集群硬件挑选的建议

与其他存储系统的集成 :

OpenStack Swift

构建 Spark : 使用 Maven 来构建 Spark

Contributing to Spark

Third Party Projects : 其它第三方 Spark 项目的支持

外部资源:

Spark 主页

Spark Wiki

Spark 社区 资源，包括当地的聚会

StackOverflow tag apache-spark

邮件列表 : 在这里询问关于 Spark 的问题

AMP 营地 在加州大学伯克利分校:一系列的训练营,特色和讨论 练习对 Spark,Spark Steaming,Mesos 以及更多。可以免费通过 视频 , 幻灯片 和 练习 学习。

代码示例 : 更多示例可以在 Spark 的子文件夹中（Scala , Java , Python , R ）获得。

编程指南

快速入门

Spark 编程指南

概述

Spark 依赖

Spark 的初始化

Shell 的使用

弹性分布式数据集（RDDS）

并行集合

外部数据集

RDD 操作

RDD 持久化

共享变量

Broadcast Variables （广播变量）

Accumulators （累加器）

部署应用到集群中

使用 Java / Scala 运行 spark Jobs

单元测试

Spark 1.0 版本前的应用程序迁移

下一步

Spark Streaming

Spark Streaming 概述

一个简单的示例

基本概念

依赖

初始化 StreamingContext

Discretized Streams（DStreams）（离散化流）

Input DStreams 和 Receivers

DStreams 上的 Transformations（转换）

DStreams 上的输出操作

DataFrame 和 SQL 操作

MLlib 操作

缓存 / 持久化

CheckPointing

累加器和广播变量

应用程序部署

监控应用程序

性能优化

降低批处理的时间

设置合理的批处理间隔

内存优化

容错语义

迁移指南（从 0.9.1 或者更低版本至 1.x 版本）

快速跳转

Kafka 集成指南

DataFrames，Datasets 和 SQL

Spark SQL 概述

SQL

Datasets 和 DataFrames

Spark SQL 入门指南

起始点 : SparkSession

创建 DataFrame

无类型 Dataset 操作（aka DataFrame 操作）

以编程的方式运行 SQL 查询

创建 Dataset

RDD 的互操作性

数据源

通用的 Load/Save 函数

Parquet文件

JSON Datasets

Hive 表

JDBC 连接其它数据库

故障排除

性能调优

缓存数据到内存

其它配置选项

分布式 SQL引擎

运行 Thrift JDBC/ODBC server

运行 Spark SQL CLI

迁移指南

从 Spark SQL 1.6 升级到 2.0

从 Spark SQL 1.5 升级到 1.6

从 Spark SQL 1.4 升级到 1.5

从 Spark SQL 1.3 升级到 1.4

从 Spark SQL 1.0~1.2 升级到 1.3

兼容 Apache Hive

参考

数据类型

NaN 语义

Structured Streaming

MLlib（机器学习）

机器学习库（MLlib）指南

ML Pipelines（ML管道）

Extracting, transforming and selecting features（特征的提取，转换和选择）

Classification and regression（分类和回归）

Clustering（聚类）

Collaborative Filtering（协同过滤）

ML Tuning: model selection and hyperparameter tuning（ML调优：模型选择和超参数调整）

Advanced topics（高级主题）

MLlib：基于RDD的API

Data Types - RDD-based API（数据类型）

Basic Statistics - RDD-based API（基本统计）

Classification and Regression - RDD-based API（分类和回归）

Collaborative Filtering - RDD-based API（协同过滤）

Clustering - RDD-based API（聚类 - 基于RDD的API）

Dimensionality Reduction - RDD-based API（降维）

Feature Extraction and Transformation - RDD-based API（特征的提取和转换）

Frequent Pattern Mining - RDD-based API（频繁模式挖掘）

Evaluation metrics - RDD-based API（评估指标）

PMML model export - RDD-based API（PMML模型导出）

Optimization - RDD-based API（最优化）

GraphX（图形处理）

Spark R

最近的更新

那伊抹微笑

Apache Spark 2.2.0 中文文档在2017-07-26更新查看变动

片刻

MLlib：基于RDD的API 在2017-07-21更新查看变动

GraphX（图形处理）在2017-07-20更新查看变动

小瑶

Spark Standalone 模式在2017-07-18更新查看变动

片刻

Scala的类：变量／方法／构造方法／继承在2017-07-05更新查看变动

陈留锁

RDD 持久化在2017-07-04发表了评论

无尴尬不青春

优化指南在2017-06-19更新查看变动

Spark 配置在2017-06-19更新查看变动

容错语义在2017-06-13更新查看变动

那伊抹微笑

初始化 StreamingContext 在2017-06-11更新查看变动

Spark Streaming 在2017-06-11更新查看变动

Spark 配置在2017-06-06更新查看变动

片刻

累加器和广播变量在2017-06-05更新查看变动

DStreams 上的 Transformations（转换）在2017-06-01更新查看变动

Input DStreams 和 Receivers 在2017-06-01更新查看变动

依赖在2017-06-01更新查看变动

基本概念在2017-06-01更新查看变动

Spark Streaming 在2017-06-01更新查看变动

应用程序部署在2017-06-01更新查看变动

MLlib 操作在2017-06-01更新查看变动

显示更多请等待

快速入门

原文链接 : http://spark.apache.org/docs/latest/quick-start.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2883730

贡献者 : 漫步，那伊抹微笑

本教程提供了如何使用 Spark 的简要介绍。首先通过运行 Spark 交互式的 shell（在 Python 或 Scala 中）来介绍 API，然后展示如何使用 Java ，Scala 和 Python 来编写应用程序。更多信息请参考 Spark 编程指南。

为了继续阅读本指南，首先从 Spark 官网 下载 Spark 的发行包。因为我们将不使用 HDFS，所以你可以下载一个任何 Hadoop 版本的软件包。

使用 Spark Shell 进行交互式分析

基础

Spark shell 提供了一种来学习该 API 比较简单的方式，以及一个来分析数据交互的强大的工具。在 Scala（运行于 Java 虚拟机之上，并能很好的调用已存在的 Java 类库）或者 Python 中它是可用的。通过在 Spark 目录中运行以下的命令来启动它 : 

Scala

./bin/spark-shell

Spark 的主要抽象是一个称为弹性分布式数据集（RDD）的分布式的 item 集合。RDD 可以从 Hadoop 的 InputFormats（例如 HDFS文件）或者通过其它 RDD 的转换来创建。让我们从源目录中的 README 文件中的文本创建一个新的 RDD : 

Scala

scala> val textFile = sc.textFile("README.md")
textFile: org.apache.spark.rdd.RDD[String] = README.md MapPartitionsRDD[1] at textFile at <console>:25

RDD 有可以返回值的 actions（动作），还有可以返回指定的新 RDD 的 transformations（转换）。让我们启动一个新的 actions（动作） : 

Scala

scala> textFile.count() // 在这个 RDD 中 items 的数量
res0: Long = 126

scala> textFile.first() // 在这个 RDD 中的第一个 item
res1: String = # Apache Spark

现在让我们使用一个 transformation（转换）。我们将使用 filter transaction（转换）来返回一个新的 RDD（文件中 item 的一个子集）。

Scala

scala> val linesWithSpark = textFile.filter(line => line.contains("Spark"))
linesWithSpark: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[2] at filter at <console>:27

我们可以链式操作 transformation（转换） 和 action（动作）。

Scala

scala> textFile.filter(line => line.contains("Spark")).count() // How many lines contain "Spark"?
res3: Long = 15

更多 RDD 上的操作

RDD actions（操作）和 transformations（转换）可以用于更复杂的计算。例如，统计出现次数最多的单词 : 

Scala

scala> textFile.map(line => line.split(" ").size).reduce((a, b) => if (a > b) a else b)
res4: Long = 15

第一个 map 操作创建一个新的 RDD，将一行数据 map 为一个整型值。reduce RDD 找到最大的行计数。参数 map 与 reduce 是 Python 的 匿名函数（lambda表达式），但我们也可以通过我们想要的任何顶级的 Python 功能。例如，我们将定义一个 max 函数来使代码更易于理解 : 

Scala

scala> import java.lang.Math
import java.lang.Math

scala> textFile.map(line => line.split(" ").size).reduce((a, b) => Math.max(a, b))
res5: Int = 15

一种常见的数据流模式是被 Hadoop 所推广的 MapReduce。Spark 可以很容易实现 MapReduce : 

Scala

scala> val wordCounts = textFile.flatMap(line => line.split(" ")).map(word => (word, 1)).reduceByKey((a, b) => a + b)
wordCounts: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[8] at reduceByKey at <console>:28

在这里，我们结合了 flatMap，map 和 reduceByKey transformations（转换）来计算文件中每一个单词的数量作为一个（string，int）的 RDD pairs（对）。对每个单词计数。为了在我们的 shell 中统计单词出现的次数，我们可以使用 collect action（动作）: 

Scala

>>> wordCounts.collect()
[(u'and', 9), (u'A', 1), (u'webpage', 1), (u'README', 1), (u'Note', 1), (u'"local"', 1), (u'variable', 1), ...]

缓存

Spark 还支持 Pulling（拉取）数据集到一个群集范围的内存缓存中。例如当查询一个小的 “hot” 数据集或运行一个像 PageRANK 这样的迭代算法时，在数据被重复访问时是非常高效的。举一个简单的例子，让我们标记我们的 linesWithSpark 数据集到缓存中 : 

Scala

scala> linesWithSpark.cache()
res7: linesWithSpark.type = MapPartitionsRDD[2] at filter at <console>:27

scala> linesWithSpark.count()
res8: Long = 19

scala> linesWithSpark.count()
res9: Long = 19

使用 Spark 来探索和缓存一个 100 行的文本文件看起来比较愚蠢。有趣的是，即使在他们跨越几十或者几百个节点时，这些相同的函数也可以用于非常大的数据集。您也可以像 编程指南 中描述的一样通过连接 bin/spark-shell 到集群中，使用交互式的方式来做这件事情。

独立的应用

假设我们希望使用 Spark API 来创建一个独立的应用程序。我们在 Scala（SBT），Java（Maven）和 Python 中练习一个简单应用程序。

我们将在 Scala 中创建一个非常简单的 Spark 应用程序 - 很简单的，事实上，它名为 SimpleApp.scala : 

Scala

/* SimpleApp.scala */
import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._
import org.apache.spark.SparkConf

object SimpleApp {
  def main(args: Array[String]) {
    val logFile = "YOUR_SPARK_HOME/README.md" // Should be some file on your system
    val conf = new SparkConf().setAppName("Simple Application")
    val sc = new SparkContext(conf)
    val logData = sc.textFile(logFile, 2).cache()
    val numAs = logData.filter(line => line.contains("a")).count()
    val numBs = logData.filter(line => line.contains("b")).count()
    println("Lines with a: %s, Lines with b: %s".format(numAs, numBs))
  }
}

注意这个应用程序我们应该定义一个 main() 方法而不是去扩展 scala.App。使用 scala.App 的子类可能不会正常运行。

该程序仅仅统计了 Spark README 文件中每一行包含 ‘a’ 的数量和包含 ‘b’ 的数量。注意，您需要将 YOUR_SPARK_HOME 替换为您 Spark 安装的位置。不像先前使用 spark shell 操作的示例，它们初始化了它们自己的 SparkContext，我们初始化了一个 SparkContext 作为应用程序的一部分。

我们传递给了 SparkContext 构造器一个包含我们应用程序信息的 SparkConf 对象。

我们的应用依赖了 Spark API，所以我们将包含一个名为 simple.sbt 的 sbt 配置文件，它说明了 Spark 是一个依赖。

该文件也添加了一个 Spark 依赖的仓库 : 

name := "Simple Project"

version := "1.0"

scalaVersion := "2.11.7"

libraryDependencies += "org.apache.spark" %% "spark-core" % "2.0.2"

为了让 sbt 正常的运行，我们需要根据经典的目录结构来布局 SimpleApp.scala 和 simple.sbt 文件。在成功后，我们可以创建一个包含应用程序代码的 JAR 包，然后使用 spark-submit 脚本来运行我们的程序。

# Your directory layout should look like this
$ find .
.
./simple.sbt
./src
./src/main
./src/main/scala
./src/main/scala/SimpleApp.scala

# Package a jar containing your application
$ sbt package
...
[info] Packaging {..}/{..}/target/scala-2.11/simple-project_2.11-1.0.jar

# Use spark-submit to run your application
$ YOUR_SPARK_HOME/bin/spark-submit \
  --class "SimpleApp" \
  --master local[4] \
  target/scala-2.11/simple-project_2.11-1.0.jar
...
Lines with a: 46, Lines with b: 23

快读跳转

恭喜您成功的运行了您的第一个 Spark 应用程序！

更多 API 的深入概述，从 Spark 编程指南 开始，或查阅 “编程指南” 菜单下的其它组件。

为了在集群上运行应用程序，前往 集群模式概述。

最后，在 examples 目录（Scala，Java，Python，R）中 Spark 包括了一些例子。您可以按照如下方式来运行它们 : 

# 针对 Scala 和 Java, 使用 run-example :
./bin/run-example SparkPi

# 针对 Python 示例, 直接使用 spark-submit :
./bin/spark-submit examples/src/main/python/pi.py

# 针对 R 示例，直接使用 spark-submit :
./bin/spark-submit examples/src/main/r/dataframe.R

Spark 编程指南

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2884780

贡献者 : 片刻，多啦a梦，那伊抹微笑

概述

在一个较高的水平上，每一个 Spark 应用程序由一个在集群上运行着用户的 main 函数和执行各种并行操作的 driver program（驱动程序）组成。Spark 提供的主要抽象是一个弹性分布式数据集（RDD），它是可以执行并行操作且跨集群节点的元素的集合。RDD 可以从一个 Hadoop 文件系统（或者任何其它 Hadoop 支持的文件系统），或者一个在 driver program（驱动程序）中已存在的 Scala 集合，以及通过 transforming（转换）来创建一个 RDD。用户为了让它在整个并行操作中更高效的重用，也许会让 Spark persist（持久化）一个 RDD 到内存中。最后，RDD 会自动的从节点故障中恢复。

在 Spark 中的第二个抽象是能够用于并行操作的 shared variables（共享变量），默认情况下，当 Spark 的一个函数作为一组不同节点上的任务运行时，它将每一个变量的副本应用到每一个任务的函数中去。有时候，一个变量需要在整个任务中，或者在任务和 driver program（驱动程序）之间来共享。Spark 支持两种类型的共享变量 : broadcast variables（广播变量），它可以用于在所有节点上缓存一个值，和 accumulators（累加器），他是一个只能被 “added（增加）” 的变量，例如 counters 和 sums。

本指南介绍了每一种 Spark 所支持的语言的特性。如果您启动 Spark 的交互式 shell - 针对 Scala shell 使用 bin/spark-shell 或者针对 Python 使用 bin/pyspark 是很容易来学习的。

Spark 依赖

Spark 2.0.2 默认使用 Scala 2.11 来构建和发布直到运行。（当然，Spark 也可以与其它的 Scala 版本一起运行）。为了使用 Scala 编写应用程序，您需要使用可兼容的 Scala 版本（例如，2.11.X）。

要编写一个 Spark 的应用程序，您需要在 Spark 上添加一个 Maven 依赖。Spark 可以通过 Maven 中央仓库获取 : 

groupId = org.apache.spark
artifactId = spark-core_2.11
version = 2.0.2

此外，如果您想访问一个 HDFS 集群，则需要针对您的 HDFS 版本添加一个 hadoop-client（hadoop 客户端）依赖。

groupId = org.apache.hadoop
artifactId = hadoop-client
version = <your-hdfs-version>

最后，您需要导入一些 Spark classes（类）到您的程序中去。添加下面几行 : 

import org.apache.spark.SparkContext
import org.apache.spark.SparkConf

 （ 在 Spark 1.3.0 之前，您需要明确导入org.apache.spark.SparkContext._ 来启用必不可少的隐式转换。）

Spark 的初始化

Spark 程序必须做的第一件事情是创建一个 SparkContext 对象，它会告诉 Spark 如何访问集群。为了创建一个 SparkContext，首先需要构建一个包含应用程序的信息的 SparkConf 对象。

每一个 JVM 可能只能激活一个 SparkContext 对象。在创新一个新的对象之前，必须调用 stop() 该方法停止活跃的 SparkContext。

val conf = new SparkConf().setAppName(appName).setMaster(master)
new SparkContext(conf)

这个 appName 参数是一个在集群 UI 上展示应用程序的名称。 master 是一个 Spark，Mesos 或 YARN 群集的 URL 地址，或者指定为 “local” 字符串以在 local mode（本地模式）中运行。在实际工作中，当在集群上运行时，您不希望在程序中将 master 给硬编码，而是用 使用 spark-submit 启动应用程序 并且接收它。然而，对于本地测试和单元测试，您可以通过 “local” 来运行 Spark 进程。

Shell  的使用

在 Spark Shell 中，一个特殊的 interpreter-aware（可用的解析器）SparkContext 已经为您创建好了，称之为 sc 的变量。创建您自己的 SparkContext 将不起作用。您可以使用 --master 参数设置这个 SparkContext 连接到哪一个 master 上，并且您可以通过 --jars 参数传递一个逗号分隔的列表来添加 JARs 到 classpath 中。也可以通过 --packages 参数应用一个用逗号分隔的 maven coordinates（maven 坐标）方式来添加依赖（例如，Spark 包）到您的 shell session 中去。任何额外存在且依赖的仓库（例如 Sonatype）可以传递到 --repositories 参数。例如，要明确使用四个核（CPU）来运行 bin/spark-shell，使用 : 

$ ./bin/spark-shell --master local[4]

或者，也可以添加 code.jar 到它的 classpath 中去，使用 : 

$ ./bin/spark-shell --master local[4] --jars code.jar

为了使用 maven coordinates（坐标）来包含一个依赖 : 

$ ./bin/spark-shell --master local[4] --packages "org.example:example:0.1"

有关选项的完整列表，请运行 spark-shell --help。在后台，spark-shell 调用了较一般的 spark-submit 脚本。

弹性分布式数据集（RDDS）

Spark 主要以一个弹性分布式数据集（RDD）的概念为中心，它是一个容错且可以执行并行操作的元素的集合。有两种方法可以创建 RDD : 在你的 driver program（驱动程序）中 parallelizing 一个已存在的集合，或者在外部存储系统中引用一个数据集，例如，一个共享文件系统，HDFS，HBase，或者提供 Hadoop InputFormat 的任何数据源。

并行集合

可以在您的 driver program（驱动程序）中已存在的集合上通过调用  SparkContext 的 parallelize 方法来创建并行集合。该集合的元素从一个可以并行操作的 distributed dataset（分布式数据集）中复制到另一个 dataset（数据集）中去。例如，这里是一个如何去创建一个保存数字 1 ~ 5 的并行集合。

Scala

val data = Array(1, 2, 3, 4, 5)
val distData = sc.parallelize(data)

在创建后，该 distributed dataset（分布式数据集）（distData）可以并行的执行操作。例如，我们可以调用 distData.reduce((a, b) => a + b) 来合计数组中的元素。后面我们将介绍 distributed dataset（分布式数据集）上的操作。

并行集合中一个很重要参数是 partitions（分区）的数量，它可用来切割 dataset（数据集）。Spark 将在集群中的每一个分区上运行一个任务。通常您希望群集中的每一个 CPU 计算 2-4 个分区。一般情况下，Spark 会尝试根据您的群集情况来自动的设置的分区的数量。当然，您也可以将分区数作为第二个参数传递到 parallelize (e.g. sc.parallelize(data, 10)) 方法中来手动的设置它。

外部数据集

Spark 可以从 Hadoop 所支持的任何存储源中创建 distributed dataset（分布式数据集），包括本地文件系统，HDFS，Cassandra，HBase，Amazon S3 等等。 Spark 支持文本文件，SequenceFiles，以及任何其它的 Hadoop InputFormat。

可以使用 SparkContext 的 textFile 方法来创建文本文件的 RDD。此方法需要一个文件的 URI（计算机上的本地路径 ，hdfs://，s3n:// 等等的 URI），并且读取它们作为一个 lines（行）的集合。下面是一个调用示例 : 

scala> val distFile = sc.textFile("data.txt")
distFile: org.apache.spark.rdd.RDD[String] = data.txt MapPartitionsRDD[10] at textFile at <console>:26

在创建后，distFile 可以使用 dataset（数据集）的操作。例如，我们可以使用下面的 map 和 reduce 操作来合计所有行的数量 : distFile.map(s => s.length).reduce((a, b) => a + b)。

使用 Spark 来读取文件的一些注意事项 : 

如果使用本地文件系统的路径，所工作节点的相同访问路径下该文件必须可以访问。复制文件到所有工作节点上，或着使用共享的网络挂载文件系统。

所有 Spark 中基于文件的输入方法，包括 textFile（文本文件），支持目录，压缩文件，或者通配符来操作。例如，您可以用 textFile("/my/directory")，textFile("/my/directory/*.txt") 和 textFile("/my/directory/*.gz")。

textFile 方法也可以通过第二个可选的参数来控制该文件的分区数量。默认情况下，Spark 为文件的每一个 block（块）创建的一个分区（HDFS 中块大小默认是 64M），当然你也可以通过传递一个较大的值来要求一个较高的分区数量。请注意，分区的数量不能够小于块的数量。

除了文本文件之外，Spark 的 Scala API 也支持一些其它的数据格式 : 

SparkContext.wholeTextFiles 可以读取包含多个小文本文件的目录，并返回它们的每一个 (filename, content) 对。这与 textFile 形成对比，它的每一个文件中的每一行将返回一个记录。

针对 SequenceFiles，使用 SparkContext 的 sequenceFile[K, V] 方法，其中 K 和 V 指的是它们在文件中的类型。这些应该是 Hadoop 中 Writable 接口的子类，例如 IntWritable 和 Text。此外，Spark 可以让您为一些常见的 Writables 指定原生类型。例如，sequenceFile[Int, String] 会自动读取 IntWritables 和 Texts 。

针对其它的 Hadoop InputFormats，您可以使用 SparkContext.hadoopRDD 方法，它接受一个任意 JobConf 和 input format（输入格式）类，key 类和 value 类。通过相同的方法你可以设置你 Hadoop Job 的输入源。你还可以使用基于 “new” 的 MapReduce API（org.apache.hadoop.mapreduce）来使用 SparkContext.newAPIHadoopRDD 以设置 InputFormats。

RDD.saveAsObjectFile 和 SparkContext.objectFile 支持使用简单的序列化的 Java Object 来保存 RDD。虽然这不像 Avro 这种专用的格式一样高效，但其提供了一种更简单的方式来保存任何的 RDD。

RDD 操作

RDDS 支持两种类型的操作 : 

transformations（转换）：在一个已存在的 dataset 上创建一个新的 dataset。

actions（动作）: 将在 dataset 上运行的计算结果返回到驱动程序。

例如 : map 是一个通过让每个数据集元素都执行一个函数，并返回的新 RDD 结果的 transformation；另一方面，reduce 通过执行一些函数，聚合 RDD 中所有元素，并将最终结果给返回驱动程序（虽然也有一个并行reduceByKey 返回一个分布式数据集）的 action。

Spark 中的所有 transformations 都是 lazy（懒加载的），因此它不会立刻计算出结果。相反，他们只记得应用于一些基本数据集（例如 : 文件）的转换。只有当需要返回结果给驱动程序时，transformations 才开始计算。这种设计使 Spark 的运行更高效。例如，我们可以意识到，map 所创建的数据集将被用在 reduce 中，并且只有 reduce 的计算结果返回给驱动程序，而不是映射一个更大的数据集。

默认情况下，每次你在 RDD 运行一个 action 的时， 每个 transformed RDD 都会被重新计算。但是，您也可用 persist (或 cache) 方法将 RDD persist（持久化）到内存中；在这种情况下，Spark 为了下次查询时可以更快地访问，会把数据保存在集群上。此外，还支持持续持久化 RDDs 到磁盘，或复制到多个结点。

概述

为了说明 RDD 基础，请思考下面的简单程序 : 

val lines = sc.textFile("data.txt")
val lineLengths = lines.map(s => s.length)
val totalLength = lineLengths.reduce((a, b) => a + b)

第一行从外部文件中定义了一个基本的 RDD，但这个数据集并未加载到内存中或即将被行动 : line 仅仅是一个类似指针的东西，指向该文件。

第二行定义了 lineLengths 作为 map transformation 的结果。请注意，由于 laziness（延迟加载）lineLengths 不会被立即计算，。

最后，我们运行 reduce，这是一个 action。在这此时，Spark 分发计算任务到不同的机器上运行，每台机器都运行在 map 的一部分并本地运行 reduce，仅仅返回它聚合后的结果给驱动程序。

如果我们也希望以后再次使用lineLengths，我们还可以添加：

lineLengths.persist()

在 reduce 之前，这将导致 lineLengths 在第一次计算之后就被保存在 memory 中。

传递函数给 Spark

当驱动程序在集群上运行时，Spark 的 API 在很大程度上依赖于传递函数。有2种推荐的方式来做到这一点 : 

匿名函数的语法 Anonymous function syntax，它可以用于短的代码片断。

在全局单例对象中的静态方法。例如，你可以定义对象 MyFunctions 然后传递 MyFunctions.func1，具体如下 : 

object MyFunctions {
  def func1(s: String): String = { ... }
}

myRdd.map(MyFunctions.func1)

请注意，虽然也有可能传递一个类的实例（与单例对象相反）的方法的引用，这需要发送整个对象，包括类中其它方法。例如，考虑 : 

class MyClass {
  def func1(s: String): String = { ... }
  def doStuff(rdd: RDD[String]): RDD[String] = { rdd.map(func1) }
}

这里，如果我们创建一个MyClass的实例，并调用doStuff，在map内有MyClass实例的func1方法的引用，所以整个对象需要被发送到集群的。

它类似于 rdd.map(x => this.func1(x))。

类似的方式，访问外部对象的字段将引用整个对象 : 

class MyClass {
  val field = "Hello"
  def doStuff(rdd: RDD[String]): RDD[String] = { rdd.map(x => field + x) }
}

相当于写 rdd.map(x => this.field + x) ，它引用这个对象所有的东西。为了避免这个问题，最简单的方法是 field（域）到本地变量，而不是从外部访问它的 : 

def doStuff(rdd: RDD[String]): RDD[String] = {
  val field_ = this.field
  rdd.map(x => field_ + x)
}

理解闭包

在集群中执行代码时，一个关于 Spark 更难的事情是理解的变量和方法的范围和生命周期。

修改其范围之外的变量 RDD 操作可以混淆的常见原因。在下面的例子中，我们将看一下使用的 foreach() 代码递增累加计数器，但类似的问题，也可能会出现其他操作上。

示例

考虑一个简单的 RDD 元素求和，以下行为可能不同，具体取决于是否在同一个 JVM 中执行。

一个常见的例子是当 Spark 运行在本地模式（--master = local[n]）时，与部署 Spark 应用到群集（例如，通过 spark-submit 到 YARN）: 

var counter = 0
var rdd = sc.parallelize(data)

// Wrong: Don't do this!!
rdd.foreach(x => counter += x)

println("Counter value: " + counter)

本地 VS  集群模式

上面的代码行为是不确定的，并且可能无法按预期正常工作。Spark 执行作业时，会分解 RDD 操作到每个执行者里。在执行之前，Spark 计算任务的 closure（闭包）。而闭包是在 RDD 上的执行者必须能够访问的变量和方法（在此情况下的 foreach() ）。闭包被序列化并被发送到每个执行器。

闭包的变量副本发给每个 executor ，当 counter 被 foreach 函数引用的时候，它已经不再是 driver node 的 counter 了。虽然在 driver node 仍然有一个 counter 在内存中，但是对 executors 已经不可见。executor 看到的只是序列化的闭包一个副本。所以 counter 最终的值还是 0，因为对 counter 所有的操作所有操作均引用序列化的 closure 内的值。

在本地模式，在某些情况下的 foreach 功能实际上是同一 JVM 上的驱动程序中执行，并会引用同一个原始的计数器，实际上可能更新。

为了确保这些类型的场景明确的行为应该使用的 Accumulator（累加器）。当一个执行的任务分配到集群中的各个 worker 结点时，Spark 的累加器是专门提供安全更新变量的机制。本指南的累加器的部分会更详细地讨论这些。

在一般情况下，closures - constructs 像循环或本地定义的方法，不应该被用于改动一些全局状态。Spark 没有规定或保证突变的行为，以从封闭件的外侧引用的对象。一些代码，这可能以本地模式运行，但是这只是偶然和这样的代码如预期在分布式模式下不会表现。改用如果需要一些全局聚集累加器。

打印 RDD 的元素

另一种常见的语法用于打印 RDD 的所有元素使用 rdd.foreach(println) 或 rdd.map(println)。在一台机器上，这将产生预期的输出和打印 RDD 的所有元素。然而，在集群 cluster 模式下，stdout 输出正在被执行写操作 executors 的 stdout 代替，而不是在一个驱动程序上，因此stdout 的 driver 程序不会显示这些！要打印 driver 程序的所有元素，可以使用的 collect() 方法首先把 RDD 放到 driver 程序节点上 : rdd.collect().foreach(println)。这可能会导致 driver 程序耗尽内存，虽说，因为 collect() 获取整个 RDD 到一台机器; 如果你只需要打印 RDD 的几个元素，一个更安全的方法是使用 take() : rdd.take(100).foreach(println)。

使用 Key-Value 对工作

虽然大多数 Spark 操作工作在包含任何类型对象的 RDDs 上，只有少数特殊的操作可用于 Key-Value 对的 RDDs。最常见的是分布式 “shuffle” 操作，如通过元素的 key 来进行 grouping 或 aggregating 操作。

在 Scala 中，这些操作时自动可用于包含 Tuple2 对象的 RDDs（在语言中内置的元组，通过简单的写 (a, b) ）。在 PairRDDFunctions 类中该 Key-Value 对的操作有效的，其中围绕元组的 RDD 自动包装。

例如，下面的代码使用的 Key-Value 对的 reduceByKey 操作统计文本文件中每一行出现了多少次 : 

val lines = sc.textFile("data.txt")
val pairs = lines.map(s => (s, 1))
val counts = pairs.reduceByKey((a, b) => a + b)

我们也可以使用 counts.sortByKey() ，例如，在对按字母顺序排序，最后 counts.collect() 把他们作为一个数据对象返回给的驱动程序。

注意 : 使用自定义对象作为 Key-Value 对操作的 key 时，您必须确保自定义 equals() 方法有一个 hashCode() 方法相匹配。有关详情，请参见这是 Object.hashCode() documentation 中列出的约定。

Transformations （转换）

下表列出了一些 Spark 常用的 transformations（转换）。详情请参考 RDD API 文档（Scala，Java，Python，R）和 pair RDD 函数文档（Scala，Java）。

Transformation（转换） | Meaning（含义）
map(func) | 返回一个新的 distributed dataset（分布式数据集），它由每个 source（数据源）中的元素应用一个函数 func 来生成。
filter(func) | 返回一个新的 distributed dataset（分布式数据集），它由每个 source（数据源）中应用一个函数 func 且返回值为 true 的元素来生成。
flatMap(func) | 与 map 类似，但是每一个输入的 item 可以被映射成 0 个或多个输出的 items（所以 func 应该返回一个 Seq 而不是一个单独的 item）
mapPartitions(func) | 与 map 类似，但是单独的运行在在每个 RDD 的 partition（分区，block）上，所以在一个类型为 T 的 RDD 上运行时 func 必须是 Iterator<T> => Iterator<U> 类型。
mapPartitionsWithIndex(func) | 与 mapPartitions 类似，但是也需要提供一个代表 partition 的 index（索引）的 interger value（整型值）作为参数的 func，所以在一个类型为 T 的 RDD 上运行时 func 必须是 (Int, Iterator<T>) => Iterator<U> 类型。
sample(withReplacement, fraction, seed) | 样本数据，设置是否放回（withReplacement）、采样的百分比（fraction）、使用指定的随机数生成器的种子（seed）。
union(otherDataset) | 返回一个新的 dataset，它包含了 source dataset（源数据集）和 otherDataset（其它数据集）的并集。
intersection(otherDataset) | 返回一个新的 RDD，它包含了 source dataset（源数据集）和 otherDataset（其它数据集）的交集。
distinct([numTasks])) | 返回一个新的 dataset，它包含了 source dataset（源数据集）中去重的元素。
groupByKey([numTasks]) | 在一个 (K, V) pair 的 dataset 上调用时，返回一个 (K, Iterable<V>) pairs 的 dataset。 注意 : 如果分组是为了在每一个 key 上执行聚合操作（例如，sum 或 average)，此时使用 reduceByKey 或 aggregateByKey 来计算性能会更好。 注意 : 默认情况下，并行度取决于父 RDD 的分区数。可以传递一个可选的 numTasks 参数来设置不同的任务数。
reduceByKey(func, [numTasks]) | 在一个 (K, V) pair 的 dataset 上调用时，返回一个 (K, Iterable<V>) pairs 的 dataset，它的值会针对每一个 key 使用指定的 reduce 函数 func 来聚合，它必须为 (V,V) => V 类型。像 groupByKey 一样，可通过第二个可选参数来配置 reduce 任务的数量。
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) | 在一个 (K, V) pair 的 dataset 上调用时，返回一个 (K, Iterable<V>) pairs 的 dataset，它的值会针对每一个 key 使用指定的 combine 函数和一个中间的 “zero” 值来聚合，它必须为 (V,V) => V 类型。为了避免不必要的配置，可以使用一个不同与 input value 类型的 aggregated value 类型。
sortByKey([ascending], [numTasks]) | 在一个 (K, V) pair 的 dataset 上调用时，其中的 K 实现了 Ordered，返回一个按 keys 升序或降序的 (K, V) pairs 的 dataset。
join(otherDataset, [numTasks]) | 在一个 (K, V) 和 (K, W) 类型的 dataset 上调用时，返回一个 (K, (V, W)) pairs 的 dataset，它拥有每个 key 中所有的元素对。Outer joins 可以通过 leftOuterJoin，rightOuterJoin 和fullOuterJoin 来实现。
cogroup(otherDataset, [numTasks]) | 在一个 (K, V) 和的 dataset 上调用时，返回一个 (K, (Iterable<V>, Iterable<W>)) tuples 的 dataset。这个操作也调用了 groupWith。
cartesian(otherDataset) | 在一个 T 和 U 类型的 dataset 上调用时，返回一个 (T, U) pairs 类型的 dataset（所有元素的 pairs，即笛卡尔积）。
pipe(command, [envVars]) | 通过使用 shell 命令来将每个 RDD 的分区给 Pipe。例如，一个 Perl 或 bash 脚本。RDD 的元素会被写入进程的标准输入（stdin），并且 lines（行）输出到它的标准输出（stdout）被作为一个字符串型 RDD 的 string 返回。
coalesce(numPartitions) | Decrease（降低）RDD 中 partitions（分区）的数量为 numPartitions。对于执行过滤后一个大的 dataset 操作是更有效的。
repartition(numPartitions) | Reshuffle（重新洗牌）RDD 中的数据以创建或者更多的 partitions（分区）并将每个分区中的数据尽量保持均匀。该操作总是通过网络来 shuffles 所有的数据。
repartitionAndSortWithinPartitions(partitioner) | 根据给定的 partitioner（分区器）对 RDD 进行重新分区，并在每个结果分区中，按照 key 值对记录排序。这比每一个分区中先调用 repartition 然后再 sorting（排序）效率更高，因为它可以将排序过程推送到 shuffle 操作的机器上进行。

Actions （动作）

下面列出了一些 Spark 常用的 actions 操作。详细请参考 RDD API 文档 (Scala, Java, Python, R) 和 pair RDD 函数文档 (Scala, Java)。

Action | 意思
reduce(func) | 使用函数 func 聚合数据集（dataset）中的元素，这个函数 func 输入为两个元素，返回为一个元素。这个函数应该是可交换（commutative ）和关联（associative）的，这样才能保证它可以被并行地正确计算。
collect() | 在驱动程序中，以一个数组的形式返回数据集的所有元素。这在返回足够小（sufficiently small）的数据子集的过滤器（filter）或其他操作（other operation）之后通常是有用的。
count() | 返回数据集中元素的个数。
first() | 返回数据集中的第一个元素（类似于 take(1)）。
take(n) | 将数据集中的前 n 个元素作为一个数组返回。
takeSample(withReplacement, num, [seed]) | 对一个数据集随机抽样，返回一个包含 num 个随机抽样（random sample）元素的数组，参数 withReplacement 指定是否有放回抽样，参数 seed 指定生成随机数的种子。
takeOrdered(n, [ordering]) | 返回 RDD 按自然顺序（natural order）或自定义比较器（custom comparator）排序后的前 n 个元素。
saveAsTextFile(path) | 将数据集中的元素以文本文件（或文本文件集合）的形式写入本地文件系统、HDFS 或其它 Hadoop 支持的文件系统中的给定目录中。Spark 将对每个元素调用 toString 方法，将数据元素转换为文本文件中的一行记录。
saveAsSequenceFile(path) (Java and Scala) | 将数据集中的元素以 Hadoop SequenceFile 的形式写入到本地文件系统、HDFS 或其它 Hadoop 支持的文件系统指定的路径中。该操作可以在实现了 Hadoop 的 Writable 接口的键值对（key-value pairs）的 RDD 上使用。在 Scala 中，它还可以隐式转换为 Writable 的类型（Spark 包括了基本类型的转换，例如 Int、Double、String 等等)。
saveAsObjectFile(path) (Java and Scala) | 使用 Java 序列化（serialization）以简单的格式（simple format）编写数据集的元素，然后使用 SparkContext.objectFile() 进行加载。
countByKey() | 仅适用于（K,V）类型的 RDD 。返回具有每个 key 的计数的 （K , Int）对 的 hashmap。
foreach(func) | 对数据集中每个元素运行函数 func 。这通常用于副作用（side effects），例如更新一个累加器（Accumulator）或与外部存储系统（external storage systems）进行交互。注意：修改除 foreach() 之外的累加器以外的变量（variables）可能会导致未定义的行为（undefined behavior）。详细介绍请阅读 理解闭包（Understanding closures） 部分。

shuffle 操作

Spark 里的某些操作会触发 shuffle。shuffle 是spark 重新分配数据的一种机制，使得这些数据可以跨不同的区域进行分组。这通常涉及在 executors 和 机器之间拷贝数据，这使得 shuffle 成为一个复杂的、代价高的操作。

后台

为了明白 shuffle 操作的过程，我们以 reduceByKey 为例。reduceBykey 操作产生一个新的 RDD，其中 key 相同的所有的值组合成为一个 tuple - key 以及 与 key 相关联的所有值在 reduce 函数上的执行结果。但问题是，一个 key 的所有值不一定都在一个同一个分区里，甚至是不一定在同一台机器里，但是它们必须共同被计算。

在 spark 里，特定的操作需要数据不跨分区分布。在计算期间，一个任务在一个分区上执行，为了所有数据都在单个 reduceByKey 的 reduce 任务上运行，我们需要执行一个 all-to-all 操作。它必须从所有分区读取所有的 key 和 key对应的所有的值，并且跨分区聚集去计算每个 key 的结果 - 这个过程就叫做 shuffle。

尽管每个分区新 shuffle 的数据集将是确定的，分区本身的顺序也是这样，但是这些数据的顺序是不确定的。如果希望 shuffle 后的数据是有序的，可以使用 : 

mapPartitions 对每个分区进行排序，例如 .sorted。

repartitionAndSortWithinPartitions 在分区的同时对分区进行高效的排序。

sortBy 做一个整体的排序。

触发 shuffle 的操作包括 repartition 操作，如 repartition、coalesce；'ByKey' 操作（除了 counting 相关操作），如 groupByKey、reduceByKey 和 join 操作，如 cogroup 和 join。

性能影响

shuffle 是一个代价比较高的操作，它涉及磁盘 IO、数据序列化、网络 IO。为了准备 shuffle 操作的数据，Spark 启动了一系列的 map 任务和 reduce 任务，map 任务组织数据，reduce 完成数据的聚合。这里的 map、reduce 来自 MapReduce，跟 Spark 的 map 操作和 reduce 操作没有关系。

在内部，一个 map 任务的所有结果数据会保存在内存，直到内存不能全部存储为止。然后，这些数据将基于目标分区进行排序并写入一个单独的文件中。在 reduce 时，任务将读取相关的已排序的数据块。

某些 shuffle 操作会大量消耗堆内存空间，因为 shuffle 操作在数据转换前后，需要在使用内存中的数据结构对数据进行组织。需要特别说明的是，reduceByKey 和 aggregateByKey 在 map 时会创建这些数据结构，ByKey 操作在 reduce 时创建这些数据结构。当内存满的时候，Spark 会把溢出的数据存到磁盘上，这将导致额外的磁盘 IO 开销和垃圾回收开销的增加。

shuffle 操作还会在磁盘上生成大量的中间文件。在 Spark 1.3 中，这些文件将会保留至对应的 RDD 不在使用并被垃圾回收为止。这么做的好处是，如果在 Spark 重新计算 RDD 的血统关系（lineage）时，shuffle 操作产生的这些中间文件不需要重新创建。如果 Spark 应用长期保持对 RDD 的引用，或者垃圾回收不频繁，这将导致垃圾回收的周期比较长。这意味着，长期运行 Spark 任务可能会消耗大量的磁盘空间。临时数据存储路径可以通过 SparkContext 中设置参数 spark.local.dir 进行配置。

shuffle 操作的行为可以通过调节多个参数进行设置。详细的说明请看 配置页面 中的 “Shuffle 行为” 部分。

RDD 持久化

Spark 中一个很重要的能力是将数据持久化（或称为缓存），在多个操作间都可以访问这些持久化的数据。当持久化一个 RDD 时，每个节点的其它分区都可以使用 RDD 在内存中进行计算，在该数据上的其他 action 操作将直接使用内存中的数据。这样会让以后的 action 操作计算速度加快（通常运行速度会加速 10 倍）。缓存是迭代算法和快速的交互式使用的重要工具。

RDD 可以使用 persist() 方法或 cache() 方法进行持久化。数据将会在第一次 action 操作时进行计算，并缓存在节点的内存中。Spark 的缓存具有容错机制，如果一个缓存的 RDD 的某个分区丢失了，Spark 将按照原来的计算过程，自动重新计算并进行缓存。

另外，每个持久化的 RDD 可以使用不同的存储级别进行缓存，例如，持久化到磁盘、已序列化的 Java 对象形式持久化到内存（可以节省空间）、跨节点间复制、以 off-heap 的方式存储在 Tachyon。这些存储级别通过传递一个 StorageLevel 对象（Scala、Java、Python）给 persist() 方法进行设置。cache() 方法是使用默认存储级别的快捷设置方法，默认的存储级别是 StorageLevel.MEMORY_ONLY（将反序列化的对象存储到内存中）。详细的存储级别介绍如下 : 

MEMORY_ONLY : 将 RDD 以反序列化 Java 对象的形式存储在 JVM 中。如果内存空间不够，部分数据分区将不再缓存，在每次需要用到这些数据时重新进行计算。这是默认的级别。

MEMORY_AND_DISK : 将 RDD 以反序列化 Java 对象的形式存储在 JVM 中。如果内存空间不够，将未缓存的数据分区存储到磁盘，在需要使用这些分区时从磁盘读取。

MEMORY_ONLY_SER : 将 RDD 以序列化的 Java 对象的形式进行存储（每个分区为一个 byte 数组）。这种方式会比反序列化对象的方式节省很多空间，尤其是在使用 fast serializer时会节省更多的空间，但是在读取时会增加 CPU 的计算负担。

MEMORY_AND_DISK_SER : 类似于 MEMORY_ONLY_SER ，但是溢出的分区会存储到磁盘，而不是在用到它们时重新计算。

DISK_ONLY : 只在磁盘上缓存 RDD。

MEMORY_ONLY_2，MEMORY_AND_DISK_2，等等 : 与上面的级别功能相同，只不过每个分区在集群中两个节点上建立副本。

OFF_HEAP（实验中）: 类似于 MEMORY_ONLY_SER ，但是将数据存储在 off-heap memory，这需要启动 off-heap 内存。

注意，在 Python 中，缓存的对象总是使用 Pickle 进行序列化，所以在 Python 中不关心你选择的是哪一种序列化级别。python 中的存储级别包括 MEMORY_ONLY，MEMORY_ONLY_2，MEMORY_AND_DISK，MEMORY_AND_DISK_2，DISK_ONLY 和 DISK_ONLY_2 。

在 shuffle 操作中（例如 reduceByKey），即便是用户没有调用 persist 方法，Spark 也会自动缓存部分中间数据。这么做的目的是，在 shuffle 的过程中某个节点运行失败时，不需要重新计算所有的输入数据。如果用户想多次使用某个 RDD，强烈推荐在该 RDD 上调用 persist 方法。

如何选择存储级别

Spark 的存储级别的选择，核心问题是在内存使用率和 CPU 效率之间进行权衡。建议按下面的过程进行存储级别的选择 : 

如果使用默认的存储级别（MEMORY_ONLY），存储在内存中的 RDD 没有发生溢出，那么就选择默认的存储级别。默认存储级别可以最大程度的提高 CPU 的效率,可以使在 RDD 上的操作以最快的速度运行。

如果内存不能全部存储 RDD，那么使用 MEMORY_ONLY_SER，并挑选一个快速序列化库将对象序列化，以节省内存空间。使用这种存储级别，计算速度仍然很快。

除了在计算该数据集的代价特别高，或者在需要过滤大量数据的情况下，尽量不要将溢出的数据存储到磁盘。因为，重新计算这个数据分区的耗时与从磁盘读取这些数据的耗时差不多。

如果想快速还原故障，建议使用多副本存储级别（例如，使用 Spark 作为 web 应用的后台服务，在服务出故障时需要快速恢复的场景下）。所有的存储级别都通过重新计算丢失的数据的方式，提供了完全容错机制。但是多副本级别在发生数据丢失时，不需要重新计算对应的数据库，可以让任务继续运行。

删除数据

Spark 自动监控各个节点上的缓存使用率，并以最近最少使用的方式（LRU）将旧数据块移除内存。如果想手动移除一个 RDD，而不是等待该 RDD 被 Spark 自动移除，可以使用 RDD.unpersist() 方法。

共享变量

通常情况下，一个传递给 Spark 操作（例如  map 或  reduce）的函数 func 是在远程的集群节点上执行的。该函数 func 在多个节点执行过程中使用的变量，是同一个变量的多个副本。这些变量的以副本的方式拷贝到每个机器上，并且各个远程机器上变量的更新并不会传播回 driver program（驱动程序）。通用且支持 read-write（读-写） 的共享变量在任务间是不能胜任的。所以，Spark 提供了两种特定类型的共享变量 : broadcast variables（广播变量）和 accumulators（累加器）。

Broadcast variables（广播变量）

Broadcast variables（广播变量）允许程序员将一个 read-only（只读的）变量缓存到每台机器上，而不是给任务传递一个副本。它们是如何来使用呢，例如，广播变量可以用一种高效的方式给每个节点传递一份比较大的 input dataset（输入数据集）副本。在使用广播变量时，Spark 也尝试使用高效广播算法分发 broadcast variables（广播变量）以降低通信成本。

Spark 的 action（动作）操作是通过一系列的 stage（阶段）进行执行的，这些 stage（阶段）是通过分布式的 "shuffle" 操作进行拆分的。Spark 会自动广播出每个 stage（阶段）内任务所需要的公共数据。这种情况下广播的数据使用序列化的形式进行缓存，并在每个任务运行前进行反序列化。这也就意味着，只有在跨越多个 stage（阶段）的多个任务会使用相同的数据，或者在使用反序列化形式的数据特别重要的情况下，使用广播变量会有比较好的效果。

广播变量通过在一个变量  v 上调用 SparkContext.broadcast(v) 方法来进行创建。广播变量是 v 的一个 wrapper（包装器），可以通过调用 value 方法来访问它的值。代码示例如下 : 

scala> val broadcastVar = sc.broadcast(Array(1, 2, 3))
broadcastVar: org.apache.spark.broadcast.Broadcast[Array[Int]] = Broadcast(0)
scala> broadcastVar.value
res0: Array[Int] = Array(1, 2, 3)

在创建广播变量之后，在集群上执行的所有的函数中，应该使用该广播变量代替原来的 v 值，所以节点上的 v 最多分发一次。另外，对象 v 在广播后不应该再被修改，以保证分发到所有的节点上的广播变量具有同样的值（例如，如果以后该变量会被运到一个新的节点）。

Accumulators（累加器）

Accumulators（累加器）是一个仅可以执行 “added”（添加）的变量来通过一个关联和交换操作，因此可以高效地执行支持并行。累加器可以用于实现 counter（ 计数，类似在 MapReduce 中那样）或者 sums（求和）。原生 Spark 支持数值型的累加器，并且程序员可以添加新的支持类型。

创建 accumulators（累加器）并命名之后，在 Spark 的 UI 界面上将会显示它。这样可以帮助理解正在运行的阶段的运行情况（注意 : 该特性在 Python 中还不支持）。

可以通过调用 SparkContext.longAccumulator() 或 SparkContext.doubleAccumulator() 方法创建数值类型的 accumulator（累加器）以分别累加 Long 或 Double 类型的值。集群上正在运行的任务就可以使用 add 方法来累计数值。然而，它们不能够读取它的值。只有 driver program（驱动程序）才可以使用 value 方法读取累加器的值。

 下面的代码展示了一个 accumulator（累加器）被用于对一个数字中的元素求和。

Scala

scala> val accum = sc.longAccumulator("My Accumulator")
accum: org.apache.spark.util.LongAccumulator = LongAccumulator(id: 0, name: Some(My Accumulator), value: 0)

scala> sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum.add(x))
...
10/09/29 18:41:08 INFO SparkContext: Tasks finished in 0.317106 s

scala> accum.value
res2: Long = 10

上面的代码示例使用的是 Spark 内置的 Long 类型的累加器，程序员可以通过继承 AccumulatorV2 类创建新的累加器类型。AccumulatorV2 抽象类有几个需要 override（重写）的方法 : reset 方法可将累加器重置为 0，add 方法可将其它值添加到累加器中，merge 方法可将其他同样类型的累加器合并为一个。其他需要重写的方法可参考 scala API 文档。 例如，假设我们有一个表示数学上 vectors（向量）的 MyVector 类，我们可以写成 : 

Scala

object VectorAccumulatorV2 extends AccumulatorV2[MyVector, MyVector] {
  val vec_ : MyVector = MyVector.createZeroVector
  def reset(): MyVector = {
    vec_.reset()
  }
  def add(v1: MyVector, v2: MyVector): MyVector = {
    vec_.add(v2)
  }
  ...
}

// Then, create an Accumulator of this type:
val myVectorAcc = new VectorAccumulatorV2
// Then, register it into spark context:
sc.register(myVectorAcc, "MyVectorAcc1")

注意，在开发者定义自己的  AccumulatorV2 类型时， resulting type（返回值类型）可能与添加的元素的类型不一致。

累加器的更新只发生在 action 操作中，Spark 保证每个任务只更新累加器一次，例如，重启任务不会更新值。在 transformations（转换）中， 用户需要注意的是，如果 task（任务）或 job stages（阶段）重新执行，每个任务的更新操作可能会执行多次。

累加器不会改变 Spark lazy evaluation（懒加载）的模式。如果累加器在 RDD 中的一个操作中进行更新，它们的值仅被更新一次，RDD 被作为 action 的一部分来计算。因此，在一个像 map() 这样的 transformation（转换）时，累加器的更新并没有执行。下面的代码片段证明了这个特性 : 

Scala

val accum = sc.accumulator(0)
data.map { x => accum += x; x }
// 在这里，accus 仍然为 0, 因为没有 actions（动作）来让 map 操作被计算。

部署应用到集群中

应用提交指南 描述了如何将应用提交到集群中。简单的说，在您将应用打包成一个JAR（针对 Java/Scala）或者一组 .py 或 .zip 文件（Python）后，就可以通过 bin/spark-submit 脚本将应用提交到任何支持的集群管理器中。

使用 Java / Scala 运行 spark Jobs

org.apache.spark.launcher 包提供了使用简单的 Java API 作为子进程启动 Spark Jobs 的类。

单元测试

Spark 可以友好的使用流行的单元测试框架进行单元测试。在将 master URL 设置为 local 来测试时会简单的创建一个 SparkContext，运行您的操作，然后调用 SparkContext.stop() 将该作业停止。因为 Spark 不支持在同一个程序中并行的运行两个 contexts，所以需要确保使用 finally 块或者测试框架的 tearDown 方法将 context 停止。

Spark 1.0 版本前的应用程序迁移

Spark 1.0 冻结了 1.X 系列的 Spark Core 的 API，因此，当前没有标记为 "experimental" 或者 “developer API” 的 API 都将在未来的版本中进行支持。

针对 Scala 的变化是分组操作，例如 groupByKey、cogroup 和 join，它们的返回值类型由 (Key,Seq[Value]) 变为 (Key,Iterable[Value])。

该迁移指南也适用于 Spark Streaming，MLlib 和 GraphX。

下一步

您可以在 Spark 官网上看一些 Spark 编程示例。另外，在 Spark 的 examples 目录中包含了许多例子（Scala，Java，Python，R）。您可以通过传递 class name 到 Spark 的 bin/run-example 脚本以运行 Java 和 Scala 示例。例如 : 

./bin/run-example SparkPi

针对 Python 示例，使用 spark-submit 来代替 : 

./bin/spark-submit examples/src/main/python/pi.py

针对 R 示例，使用 spark-submit 来代替 : 

./bin/spark-submit examples/src/main/r/dataframe.R

针对应用程序的优化，Spark 配置 和 优化指南 提供了一些最佳实践的信息。这些优化建议在确保你的数据以高效的格式存储在内存中尤其重要。针对部署参考，请阅读 集群模式概述，该文档描述了分布式操作和支持的集群管理器的组件。

最后，所有的 API 文档可在 Scala，Java，Python，R 中获取。

概述

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html#overview

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2883730

贡献者 : 片刻，那伊抹微笑

在一个较高的水平上，每一个 Spark 应用程序由一个在集群上运行着用户的 main 函数和执行各种并行操作的 driver program（驱动程序）组成。Spark 提供的主要抽象是一个弹性分布式数据集（RDD），它是可以执行并行操作且跨集群节点的元素的集合。RDD 可以从一个 Hadoop 文件系统（或者任何其它 Hadoop 支持的文件系统），或者一个在 driver program（驱动程序）中已存在的 Scala 集合，以及通过 transforming（转换）来创建一个 RDD。用户为了让它在整个并行操作中更高效的重用，也许会让 Spark persist（持久化）一个 RDD 到内存中。最后，RDD 会自动的从节点故障中恢复。

在 Spark 中的第二个抽象是能够用于并行操作的 shared variables（共享变量），默认情况下，当 Spark 的一个函数作为一组不同节点上的任务运行时，它将每一个变量的副本应用到每一个任务的函数中去。有时候，一个变量需要在整个任务中，或者在任务和 driver program（驱动程序）之间来共享。Spark 支持两种类型的共享变量 : broadcast variables（广播变量），它可以用于在所有节点上缓存一个值，和 accumulators（累加器），他是一个只能被 “added（增加）” 的变量，例如 counters 和 sums。

本指南介绍了每一种 Spark 所支持的语言的特性。如果您启动 Spark 的交互式 shell - 针对 Scala shell 使用 bin/spark-shell 或者针对 Python 使用 bin/pyspark 是很容易来学习的。

Spark 依赖

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html#linking-with-spark

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2883730

贡献者 : 片刻，那伊抹微笑

Spark 2.0.2 默认使用 Scala 2.11 来构建和发布直到运行。（当然，Spark 也可以与其它的 Scala 版本一起运行）。为了使用 Scala 编写应用程序，您需要使用可兼容的 Scala 版本（例如，2.11.X）。

要编写一个 Spark 的应用程序，您需要在 Spark 上添加一个 Maven 依赖。Spark 可以通过 Maven 中央仓库获取 : 

groupId = org.apache.spark
artifactId = spark-core_2.11
version = 2.0.2

此外，如果您想访问一个 HDFS 集群，则需要针对您的 HDFS 版本添加一个 hadoop-client（hadoop 客户端）依赖。

groupId = org.apache.hadoop
artifactId = hadoop-client
version = <your-hdfs-version>

最后，您需要导入一些 Spark classes（类）到您的程序中去。添加下面几行 : 

import org.apache.spark.SparkContext
import org.apache.spark.SparkConf

 （ 在 Spark 1.3.0 之前，您需要明确导入org.apache.spark.SparkContext._ 来启用必不可少的隐式转换。）

Spark 的初始化

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html#initializing-spark

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2883730

贡献者 : 片刻，那伊抹微笑

Spark 程序必须做的第一件事情是创建一个 SparkContext 对象，它会告诉 Spark 如何访问集群。为了创建一个 SparkContext，首先需要构建一个包含应用程序的信息的 SparkConf 对象。

每一个 JVM 可能只能激活一个 SparkContext 对象。在创新一个新的对象之前，必须调用 stop() 该方法停止活跃的 SparkContext。

val conf = new SparkConf().setAppName(appName).setMaster(master) new SparkContext(conf)

这个 appName 参数是一个在集群 UI 上展示应用程序的名称。 master 是一个 Spark，Mesos 或 YARN 群集的 URL 地址，或者指定为 “local” 字符串以在 local mode（本地模式）中运行。在实际工作中，当在集群上运行时，您不希望在程序中将 master 给硬编码，而是用 使用 spark-submit 启动应用程序 并且接收它。然而，对于本地测试和单元测试，您可以通过 “local” 来运行 Spark 进程。

Shell 的使用

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html#using-the-shell

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2883730

贡献者 : 片刻，那伊抹微笑

在 Spark Shell 中，一个特殊的 interpreter-aware（可用的解析器）SparkContext 已经为您创建好了，称之为 sc 的变量。创建您自己的 SparkContext 将不起作用。您可以使用 --master 参数设置这个 SparkContext 连接到哪一个 master 上，并且您可以通过 --jars 参数传递一个逗号分隔的列表来添加 JARs 到 classpath 中。也可以通过 --packages 参数应用一个用逗号分隔的 maven coordinates（maven 坐标）方式来添加依赖（例如，Spark 包）到您的 shell session 中去。任何额外存在且依赖的仓库（例如 Sonatype）可以传递到 --repositories 参数。例如，要明确使用四个核（CPU）来运行 bin/spark-shell，使用 : 

$ ./bin/spark-shell --master local[4]

或者，也可以添加 code.jar 到它的 classpath 中去，使用 : 

$ ./bin/spark-shell --master local[4] --jars code.jar

为了使用 maven coordinates（坐标）来包含一个依赖 : 

$ ./bin/spark-shell --master local[4] --packages "org.example:example:0.1"

有关选项的完整列表，请运行 spark-shell --help。在后台，spark-shell 调用了较一般的 spark-submit 脚本。

弹性分布式数据集（RDDS）

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html#resilient-distributed-datasets-rdds

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2883730

贡献者 : 片刻，那伊抹微笑，geekidentity

Spark 主要以一个弹性分布式数据集（RDD）的概念为中心，它是一个容错且可以执行并行操作的元素的集合。有两种方法可以创建 RDD : 在你的 driver program（驱动程序）中 parallelizing 一个已存在的集合，或者在外部存储系统中引用一个数据集，例如，一个共享文件系统，HDFS，HBase，或者提供 Hadoop InputFormat 的任何数据源。

并行集合

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html#parallelized-collections

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2883730

贡献者 : 片刻，那伊抹微笑

可以在您的 driver program（驱动程序）中已存在的集合上通过调用  SparkContext 的 parallelize 方法来创建并行集合。该集合的元素从一个可以并行操作的 distributed dataset（分布式数据集）中复制到另一个 dataset（数据集）中去。例如，这里是一个如何去创建一个保存数字 1 ~ 5 的并行集合。

Scala

val data = Array(1, 2, 3, 4, 5)
val distData = sc.parallelize(data)

在创建后，该 distributed dataset（分布式数据集）（distData）可以并行的执行操作。例如，我们可以调用 distData.reduce((a, b) => a + b) 来合计数组中的元素。后面我们将介绍 distributed dataset（分布式数据集）上的操作。

并行集合中一个很重要参数是 partitions（分区）的数量，它可用来切割 dataset（数据集）。Spark 将在集群中的每一个分区上运行一个任务。通常您希望群集中的每一个 CPU 计算 2-4 个分区。一般情况下，Spark 会尝试根据您的群集情况来自动的设置的分区的数量。当然，您也可以将分区数作为第二个参数传递到 parallelize (e.g. sc.parallelize(data, 10)) 方法中来手动的设置它。

注意 :

在代码中有些地方使用了 term slices（词片）（分区的同义词）以保持向后的兼容性。

外部数据集

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html#external-datasets

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2883730

贡献者 : 片刻，那伊抹微笑

Spark 可以从 Hadoop 所支持的任何存储源中创建 distributed dataset（分布式数据集），包括本地文件系统，HDFS，Cassandra，HBase，Amazon S3 等等。 Spark 支持文本文件，SequenceFiles，以及任何其它的 Hadoop InputFormat。

可以使用 SparkContext 的 textFile 方法来创建文本文件的 RDD。此方法需要一个文件的 URI（计算机上的本地路径 ，hdfs://，s3n:// 等等的 URI），并且读取它们作为一个 lines（行）的集合。下面是一个调用示例 : 

scala> val distFile = sc.textFile("data.txt")
distFile: org.apache.spark.rdd.RDD[String] = data.txt MapPartitionsRDD[10] at textFile at <console>:26

在创建后，distFile 可以使用 dataset（数据集）的操作。例如，我们可以使用下面的 map 和 reduce 操作来合计所有行的数量 : distFile.map(s => s.length).reduce((a, b) => a + b)。

使用 Spark 来读取文件的一些注意事项 : 

如果使用本地文件系统的路径，所工作节点的相同访问路径下该文件必须可以访问。复制文件到所有工作节点上，或着使用共享的网络挂载文件系统。

所有 Spark 中基于文件的输入方法，包括 textFile（文本文件），支持目录，压缩文件，或者通配符来操作。例如，您可以用 textFile("/my/directory")，textFile("/my/directory/*.txt") 和 textFile("/my/directory/*.gz")。

textFile 方法也可以通过第二个可选的参数来控制该文件的分区数量。默认情况下，Spark 为文件的每一个 block（块）创建的一个分区（HDFS 中块大小默认是 64M），当然你也可以通过传递一个较大的值来要求一个较高的分区数量。请注意，分区的数量不能够小于块的数量。

除了文本文件之外，Spark 的 Scala API 也支持一些其它的数据格式 : 

SparkContext.wholeTextFiles 可以读取包含多个小文本文件的目录，并返回它们的每一个 (filename, content) 对。这与 textFile 形成对比，它的每一个文件中的每一行将返回一个记录。

针对 SequenceFiles，使用 SparkContext 的 sequenceFile[K, V] 方法，其中 K 和 V 指的是它们在文件中的类型。这些应该是 Hadoop 中 Writable 接口的子类，例如 IntWritable 和 Text。此外，Spark 可以让您为一些常见的 Writables 指定原生类型。例如，sequenceFile[Int, String] 会自动读取 IntWritables 和 Texts 。

针对其它的 Hadoop InputFormats，您可以使用 SparkContext.hadoopRDD 方法，它接受一个任意 JobConf 和 input format（输入格式）类，key 类和 value 类。通过相同的方法你可以设置你 Hadoop Job 的输入源。你还可以使用基于 “new” 的 MapReduce API（org.apache.hadoop.mapreduce）来使用 SparkContext.newAPIHadoopRDD 以设置 InputFormats。

RDD.saveAsObjectFile 和 SparkContext.objectFile 支持使用简单的序列化的 Java Object 来保存 RDD。虽然这不像 Avro 这种专用的格式一样高效，但其提供了一种更简单的方式来保存任何的 RDD。

RDD 操作

基础

传递函数给 Spark

理解闭包

使用 Key-Value 对工作

Transformations （转换）

Actions （动作）

shuffle 操作

后台

性能影响

RDDS 支持两种类型的操作 : 

transformations（转换）：在一个已存在的 dataset 上创建一个新的 dataset。

actions（动作）: 将在 dataset 上运行的计算结果返回到驱动程序。

例如 : map 是一个通过让每个数据集元素都执行一个函数，并返回的新 RDD 结果的 transformation；另一方面，reduce 通过执行一些函数，聚合 RDD 中所有元素，并将最终结果给返回驱动程序（虽然也有一个并行reduceByKey 返回一个分布式数据集）的 action。

Spark 中的所有 transformations 都是 lazy（懒加载的），因此它不会立刻计算出结果。相反，他们只记得应用于一些基本数据集（例如 : 文件）的转换。只有当需要返回结果给驱动程序时，transformations 才开始计算。这种设计使 Spark 的运行更高效。例如，我们可以意识到，map 所创建的数据集将被用在 reduce 中，并且只有 reduce 的计算结果返回给驱动程序，而不是映射一个更大的数据集。

默认情况下，每次你在 RDD 运行一个 action 的时， 每个 transformed RDD 都会被重新计算。但是，您也可用 persist (或 cache) 方法将 RDD persist（持久化）到内存中；在这种情况下，Spark 为了下次查询时可以更快地访问，会把数据保存在集群上。此外，还支持持续持久化 RDDs 到磁盘，或复制到多个结点。

RDD 持久化

如何选择存储级别

删除数据

Spark 中一个很重要的能力是将数据持久化（或称为缓存），在多个操作间都可以访问这些持久化的数据。当持久化一个 RDD 时，每个节点的其它分区都可以使用 RDD 在内存中进行计算，在该数据上的其他 action 操作将直接使用内存中的数据。这样会让以后的 action 操作计算速度加快（通常运行速度会加速 10 倍）。缓存是迭代算法和快速的交互式使用的重要工具。

RDD 可以使用 persist() 方法或 cache() 方法进行持久化。数据将会在第一次 action 操作时进行计算，并缓存在节点的内存中。Spark 的缓存具有容错机制，如果一个缓存的 RDD 的某个分区丢失了，Spark 将按照原来的计算过程，自动重新计算并进行缓存。

另外，每个持久化的 RDD 可以使用不同的存储级别进行缓存，例如，持久化到磁盘、已序列化的 Java 对象形式持久化到内存（可以节省空间）、跨节点间复制、以 off-heap 的方式存储在 Tachyon。这些存储级别通过传递一个 StorageLevel 对象（Scala、Java、Python）给 persist() 方法进行设置。cache() 方法是使用默认存储级别的快捷设置方法，默认的存储级别是 StorageLevel.MEMORY_ONLY（将反序列化的对象存储到内存中）。详细的存储级别介绍如下 : 

MEMORY_ONLY : 将 RDD 以反序列化 Java 对象的形式存储在 JVM 中。如果内存空间不够，部分数据分区将不再缓存，在每次需要用到这些数据时重新进行计算。这是默认的级别。

MEMORY_AND_DISK : 将 RDD 以反序列化 Java 对象的形式存储在 JVM 中。如果内存空间不够，将未缓存的数据分区存储到磁盘，在需要使用这些分区时从磁盘读取。

MEMORY_ONLY_SER : 将 RDD 以序列化的 Java 对象的形式进行存储（每个分区为一个 byte 数组）。这种方式会比反序列化对象的方式节省很多空间，尤其是在使用 fast serializer时会节省更多的空间，但是在读取时会增加 CPU 的计算负担。

MEMORY_AND_DISK_SER : 类似于 MEMORY_ONLY_SER ，但是溢出的分区会存储到磁盘，而不是在用到它们时重新计算。

DISK_ONLY : 只在磁盘上缓存 RDD。

MEMORY_ONLY_2，MEMORY_AND_DISK_2，等等 : 与上面的级别功能相同，只不过每个分区在集群中两个节点上建立副本。

OFF_HEAP（实验中）: 类似于 MEMORY_ONLY_SER ，但是将数据存储在 off-heap memory，这需要启动 off-heap 内存。

注意，在 Python 中，缓存的对象总是使用 Pickle 进行序列化，所以在 Python 中不关心你选择的是哪一种序列化级别。python 中的存储级别包括 MEMORY_ONLY，MEMORY_ONLY_2，MEMORY_AND_DISK，MEMORY_AND_DISK_2，DISK_ONLY 和 DISK_ONLY_2 。

在 shuffle 操作中（例如 reduceByKey），即便是用户没有调用 persist 方法，Spark 也会自动缓存部分中间数据。这么做的目的是，在 shuffle 的过程中某个节点运行失败时，不需要重新计算所有的输入数据。如果用户想多次使用某个 RDD，强烈推荐在该 RDD 上调用 persist 方法。

共享变量

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html#unit-testing

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2883720

贡献者 : 多啦a梦，xuliang，那伊抹微笑

通常情况下，一个传递给 Spark 操作（例如  map 或  reduce）的函数 func 是在远程的集群节点上执行的。该函数 func 在多个节点执行过程中使用的变量，是同一个变量的多个副本。这些变量的以副本的方式拷贝到每个机器上，并且各个远程机器上变量的更新并不会传播回 driver program（驱动程序）。通用且支持 read-write（读-写） 的共享变量在任务间是不能胜任的。所以，Spark 提供了两种特定类型的共享变量 : broadcast variables（广播变量）和 accumulators（累加器）。

Broadcast Variables （广播变量）

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html#broadcast-variables

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2886547

贡献者 : 多啦a梦 xuliang 那伊抹微笑 片刻 ApacheCN Apache中文网

Broadcast variables（广播变量）允许程序员将一个 read-only（只读的）变量缓存到每台机器上，而不是给任务传递一个副本。它们是如何来使用呢，例如，广播变量可以用一种高效的方式给每个节点传递一份比较大的 input dataset（输入数据集）副本。在使用广播变量时，Spark 也尝试使用高效广播算法分发 broadcast variables（广播变量）以降低通信成本。

Spark 的 action（动作）操作是通过一系列的 stage（阶段）进行执行的，这些 stage（阶段）是通过分布式的 "shuffle" 操作进行拆分的。Spark 会自动广播出每个 stage（阶段）内任务所需要的公共数据。这种情况下广播的数据使用序列化的形式进行缓存，并在每个任务运行前进行反序列化。这也就意味着，只有在跨越多个 stage（阶段）的多个任务会使用相同的数据，或者在使用反序列化形式的数据特别重要的情况下，使用广播变量会有比较好的效果。

广播变量通过在一个变量  v 上调用 SparkContext.broadcast(v) 方法来进行创建。广播变量是 v 的一个 wrapper（包装器），可以通过调用 value 方法来访问它的值。代码示例如下 : 

scala> val broadcastVar = sc.broadcast(Array(1, 2, 3))
broadcastVar: org.apache.spark.broadcast.Broadcast[Array[Int]] = Broadcast(0)
scala> broadcastVar.value
res0: Array[Int] = Array(1, 2, 3)

在创建广播变量之后，在集群上执行的所有的函数中，应该使用该广播变量代替原来的 v 值，所以节点上的 v 最多分发一次。另外，对象 v 在广播后不应该再被修改，以保证分发到所有的节点上的广播变量具有同样的值（例如，如果以后该变量会被运到一个新的节点）。

Accumulators （累加器）

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html#accumulators

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2886549

贡献者 : 多啦a梦，xuliang，那伊抹微笑

Accumulators（累加器）是一个仅可以执行 “added”（添加）的变量来通过一个关联和交换操作，因此可以高效地执行支持并行。累加器可以用于实现 counter（ 计数，类似在 MapReduce 中那样）或者 sums（求和）。原生 Spark 支持数值型的累加器，并且程序员可以添加新的支持类型。

创建 accumulators（累加器）并命名之后，在 Spark 的 UI 界面上将会显示它。这样可以帮助理解正在运行的阶段的运行情况（注意 : 该特性在 Python 中还不支持）。

可以通过调用 SparkContext.longAccumulator() 或 SparkContext.doubleAccumulator() 方法创建数值类型的 accumulator（累加器）以分别累加 Long 或 Double 类型的值。集群上正在运行的任务就可以使用 add 方法来累计数值。然而，它们不能够读取它的值。只有 driver program（驱动程序）才可以使用 value 方法读取累加器的值。

 下面的代码展示了一个 accumulator（累加器）被用于对一个数字中的元素求和。

Scala

scala> val accum = sc.longAccumulator("My Accumulator")
accum: org.apache.spark.util.LongAccumulator = LongAccumulator(id: 0, name: Some(My Accumulator), value: 0)

scala> sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum.add(x))
...
10/09/29 18:41:08 INFO SparkContext: Tasks finished in 0.317106 s

scala> accum.value
res2: Long = 10

上面的代码示例使用的是 Spark 内置的 Long 类型的累加器，程序员可以通过继承 AccumulatorV2 类创建新的累加器类型。AccumulatorV2 抽象类有几个需要 override（重写）的方法 : reset 方法可将累加器重置为 0，add 方法可将其它值添加到累加器中，merge 方法可将其他同样类型的累加器合并为一个。其他需要重写的方法可参考 scala API 文档。 例如，假设我们有一个表示数学上 vectors（向量）的 MyVector 类，我们可以写成 : 

Scala

object VectorAccumulatorV2 extends AccumulatorV2[MyVector, MyVector] {
  val vec_ : MyVector = MyVector.createZeroVector
  def reset(): MyVector = {
    vec_.reset()
  }
  def add(v1: MyVector, v2: MyVector): MyVector = {
    vec_.add(v2)
  }
  ...
}

// Then, create an Accumulator of this type:
val myVectorAcc = new VectorAccumulatorV2
// Then, register it into spark context:
sc.register(myVectorAcc, "MyVectorAcc1")

注意，在开发者定义自己的  AccumulatorV2 类型时， resulting type（返回值类型）可能与添加的元素的类型不一致。

累加器的更新只发生在 action 操作中，Spark 保证每个任务只更新累加器一次，例如，重启任务不会更新值。在 transformations（转换）中， 用户需要注意的是，如果 task（任务）或 job stages（阶段）重新执行，每个任务的更新操作可能会执行多次。

累加器不会改变 Spark lazy evaluation（懒加载）的模式。如果累加器在 RDD 中的一个操作中进行更新，它们的值仅被更新一次，RDD 被作为 action 的一部分来计算。因此，在一个像 map() 这样的 transformation（转换）时，累加器的更新并没有执行。下面的代码片段证明了这个特性 : 

Scala

val accum = sc.accumulator(0)
data.map { x => accum += x; x }
// 在这里，accus 仍然为 0, 因为没有 actions（动作）来让 map 操作被计算。

部署应用到集群中

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html#deploying-to-a-cluster

译文链接 : 部署应用到集群中

贡献者 : 多啦a梦，那伊抹微笑

应用提交指南 描述了如何将应用提交到集群中。简单的说，在您将应用打包成一个JAR（针对 Java/Scala）或者一组 .py 或 .zip 文件（Python）后，就可以通过 bin/spark-submit 脚本将应用提交到任何支持的集群管理器中。

使用 Java / Scala 运行 spark Jobs

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html#launching-spark-jobs-from-java--scala

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2883720

贡献者 : 多啦a梦，那伊抹微笑

org.apache.spark.launcher 包提供了使用简单的 Java API 作为子进程启动 Spark Jobs 的类。

单元测试

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html#unit-testing

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2883720

贡献者 : 多啦a梦，那伊抹微笑

Spark 可以友好的使用流行的单元测试框架进行单元测试。在将 master URL 设置为 local 来测试时会简单的创建一个 SparkContext，运行您的操作，然后调用 SparkContext.stop() 将该作业停止。因为 Spark 不支持在同一个程序中并行的运行两个 contexts，所以需要确保使用 finally 块或者测试框架的 tearDown 方法将 context 停止。

Spark 1.0 版本前的应用程序迁移

原文链接 : http://spark.apache.org/docs/2.0.0/programming-guide.html#migrating-from-pre-10-versions-of-spark

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2886557

贡献者 : 多啦a梦，那伊抹微笑

Spark 1.0 冻结了 1.X 系列的 Spark Core 的 API，因此，当前没有标记为 "experimental" 或者 “developer API” 的 API 都将在未来的版本中进行支持。

针对 Scala 的变化是分组操作，例如 groupByKey、cogroup 和 join，它们的返回值类型由 (Key,Seq[Value]) 变为 (Key,Iterable[Value])。

该迁移指南也适用于 Spark Streaming，MLlib 和 GraphX。

下一步

原文链接 : http://spark.apache.org/docs/latest/programming-guide.html#where-to-go-from-here

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2886559

贡献者 : 多啦a梦，那伊抹微笑

您可以在 Spark 官网上看一些 Spark 编程示例。另外，在 Spark 的 examples 目录中包含了许多例子（Scala，Java，Python，R）。您可以通过传递 class name 到 Spark 的 bin/run-example 脚本以运行 Java 和 Scala 示例。例如 : 

./bin/run-example SparkPi

针对 Python 示例，使用 spark-submit 来代替 : 

./bin/spark-submit examples/src/main/python/pi.py

针对 R 示例，使用 spark-submit 来代替 : 

./bin/spark-submit examples/src/main/r/dataframe.R

针对应用程序的优化，Spark 配置 和 优化指南 提供了一些最佳实践的信息。这些优化建议在确保你的数据以高效的格式存储在内存中尤其重要。针对部署参考，请阅读 集群模式概述，该文档描述了分布式操作和支持的集群管理器的组件。

最后，所有的 API 文档可在 Scala，Java，Python，R 中获取。

Spark Streaming

原文链接 : http://spark.apache.org/docs/latest/streaming-programming-guide.html

译文链接 : http://www.apache.wiki/display/Spark/Spark+Streaming

贡献者 : 小瑶 漫步 那伊抹微笑 片刻

概述

Spark Streaming 是 Spark Core API 的扩展，它支持弹性的，高吞吐的，容错的实时数据流的处理。数据可以通过多种数据源获取，例如 Kafka，Flume，Kinesis 以及 TCP sockets，也可以通过例如 map，reduce，join，window 等的高阶函数组成的复杂算法处理。最终，处理后的数据可以输出到文件系统，数据库以及实时仪表盘中。事实上，你还可以在数据流上使用 Spark机器学习 以及 图形处理算法 。

在内部，它工作原理如下，Spark Streaming 接收实时输入数据流并将数据切分成多个批数据，然后交由 Spark 引擎处理并分批的生成结果数据流。

Spark Streaming 提供了一个高层次的抽象叫做离散流（discretized stream）或者 DStream，它代表一个连续的数据流。DStream 可以通过来自数据源的输入数据流创建，例如 Kafka，Flume 以及 Kinesis，或者在其他 DStream 上进行高层次的操作创建。在内部，一个 DStream 是通过一系列的 RDD 来表示。

本指南告诉你如何使用 DStream 来编写一个 Spark Streaming 程序.你可以使用 Scala ，Java 或者 Python（Spark 1.2 版本后引进）来编写程序。

注意 :

在 Python 有些 API 可能会有不同或不可用，在这个指南里你将会发现 Python API 的标签来高亮显示这些不同。

一个简单的示例

在我们详细介绍如何你自己的 Spark Streaming 程序的细节之前，让我们先来看一看一个简单的 Spark Streaming 程序的样子。比方说，我们想要计算从一个监听 TCP socket 的数据服务器接收到的文本数据（text data）中的字数。所有你需要做的就是照着下面的步骤做。

首先，我们导入了 Spark Streaming 类和部分从 StreamingContext 隐式转换到我们的环境的名称，目的是添加有用的方法到我们需要的其他类（如 DStream）。 StreamingContext 是所有流功能的主要入口点。我们创建了一个带有 2 个执行线程和间歇时间为 1 秒的本地 StreamingContext 。

import org.apache.spark._
import org.apache.spark.streaming._
import org.apache.spark.streaming.StreamingContext._ //自从 Spark 1.3 开始，不再是必要的了

// 创建一个具有两个工作线程(working thread)和批次间隔为1秒的本地 StreamingContext
// master 需要 2 个核，以防止饥饿情况(starvation scenario)。
val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
val ssc = new StreamingContext(conf, Seconds(1))

在使用这种背景下，我们可以创建一个代表从 TCP 源流数据的离散流（DStream），指定主机名（hostname）（例如 localhost）和端口（例如 9999）。

// 创建一个将要连接到 hostname:port 的离散流，如 localhost:9999
val lines = ssc.socketTextStream("localhost", 9999)

上一步的这个 lines 离散流（DStream）表示将要从数据服务器接收到的数据流。在这个 离散流（DStream）中的每一条记录都是一行文本（text）。接下来，我们想要通过空格字符（space characters）拆分这些数据行（lines）成单词（words）。

// 将每一行拆分成单词 val words = lines.flatMap(_.split(" "))
val words = lines.flatMap(_.split(" "))

flatMap 是一种一对多的离散流（DStream）操作，它会通过在源离散流（source DStream）中根据每个记录（record）生成多个新纪录的形式创建一个新的离散流（DStream）。在这种情况下，在这种情况下，每一行（each line）都将被拆分成多个单词（words）和代表单词离散流（words DStream）的单词流。接下来，我们想要计算这些单词。

import org.apache.spark.streaming.StreamingContext._ // 自从 Spark 1.3 不再是必要的
// 计算每一个批次中的每一个单词
val pairs = words.map(word => (word, 1)) val wordCounts = pairs.reduceByKey(_ + _)

// 在控制台打印出在这个离散流（DStream）中生成的每个 RDD 的前十个元素
// 注意 : 必需要触发 action（很多初学者会忘记触发action操作，导致报错：No output operations registered, so nothing to execute）
wordCounts.print()

上一步的 words 离散流进行了进一步的映射（一对一的转变）为一个 (word, 1)对 的离散流（DStream），这个离散流然后被规约（reduce）来获得数据中每个批次（batch）的单词频率。最后，wordCounts.print() 将会打印一些每秒生成的计数。

请注意，当这些行（lines）被执行的时候， Spark Streaming 只有建立在启动时才会执行计算，在它已经开始之后，并没有真正地处理。为了在所有的转换都已经完成之后开始处理，我们在最后运行 : 

ssc.start() // 启动计算
ssc.awaitTermination() // 等待计算的终止

完整的代码可以在 Spark Streaming 的例子 NetworkWordCount 中找到。

如果你已经 下载 并且 建立 了 Spark ，你可以运行下面的例子。你首先需要运行 Netcat（一个在大多数类 Unix 系统中的小工具）作为我们使用的数据服务器。

$ nc -lk 9999

然后，在另一个不同的终端，你可以运行这个例子通过执行 : 

$ ./bin/run-example streaming.NetworkWordCount localhost 9999

然后，在运行在 netcat 服务器上的终端输入的任何行（lines），都将被计算，并且每一秒都显示在屏幕上，它看起来就像下面这样 : 

# TERMINAL 1:
# Running Netcat

$ nc -lk 9999

hello world

# TERMINAL 2: RUNNING NetworkWordCount

$ ./bin/run-example streaming.NetworkWordCount localhost 9999
...
-------------------------------------------
Time: 1357008430000 ms
-------------------------------------------
(hello,1)
(world,1)
...

基本概念

接下来，我们了解完了简单的例子，开始阐述 Spark Streaming 的基本知识。

依赖

与 Spark 类似， Spark Streaming 可以通过 Maven 来管理依赖。为了写你自己的 Spark Streaming 程序，你必须添加以下的依赖到你的 SBT 或者 Maven 项目中。

Maven 示例 : 

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-streaming_2.11</artifactId>
    <version>2.0.2</version>
</dependency>

对于从现在没有在 Spark Streaming Core API 中的数据源获取数据，如 Kafka， Flume，Kinesis ，你必须添加相应的组件 spark-streaming-xyz_2.11 到依赖中。例如，有一些常见的依赖如下。

源 | 坐标
Kafka | spark-streaming-kafka-0-8_2.11
Flume | spark-streaming-flume_2.11
Kinesis | spark-streaming-kinesis-asl_2.11 [Amazon Software License]

想要查看一个实时更新的列表，请参阅 Maven repository 来了解支持的源文件和组件的完整列表。

初始化 StreamingContext

为了初始化一个 Spark Streaming 程序，一个 StreamingContext 对象必须要被创建出来，它是所有的 Spark Streaming 功能的主入口点。

一个 StreamingContext 对象可以从一个 SparkConf 对象中创建出来。

import org.apache.spark._
import org.apache.spark.streaming._

val conf = new SparkConf().setAppName(appName).setMaster(master)
val ssc = new StreamingContext(conf, Seconds(1))

这个 appName 参数是展示在集群用户界面上的你的应用程序的名称。 master 是一个  Spark, Mesos or YARN cluster URL ，或者一个特殊的 "local[*]" 字符串运行在本地模式下。在实践中，在一个集群上运行时，你不会想在程序中硬编码 master，而是使用 spark-submit 启动应用程序，并且接收这个参数。然而，对于本地测试和单元测试，你可以传递 "local[*]" 去运行 Spark Streaming 过程（检测本地系统中内核的个数）。请注意，这内部创建了一个 SparkContext （所有 Spark 功能的出发点），它可以像这样被访问 ssc.sparkContext。

这个批处理间隔（batch interval）必须根据您的应用程序和可用的集群资源的等待时间要求进行设置。详情请参阅 优化指南 部分。

一个 StreamingContext 对象也可以从一个现有的 SparkContext 对象中创建出。

import org.apache.spark.streaming._

val sc = ...                // existing SparkContext
val ssc = new StreamingContext(sc, Seconds(1))

一个 context 定义之后，你必须做以下几个方面。

通过创建输入 DStreams 定义输入源。

通过应用转换和输出操作 DStreams 定义流计算（streaming computations）。

开始接收数据，并用 streamingContext.start() 处理它。

等待处理被停止（手动停止或者因为任何错误停止）使用 StreamingContext.awaitTermination() 。

该处理可以使用 streamingContext.stop() 手动停止。

要记住的要点 : 

一旦一个 context 已经启动，将不会有新的数据流的计算可以被创建或者添加到它。

一旦一个 context 已经停止，它不会被重新启动。

同一时间内在 JVM 中只有一个 StreamingContext 可以被激活。

在 StreamingContext 上的 stop() 同样也停止了 SparkContext 。为了只停止 StreamingContext ，设置 stop() 的可选参数，名叫 stopSparkContext 为 false 。

一个 SparkContext 就可以被重用以创建多个 StreamingContexts，只要前一个 StreamingContext 在下一个StreamingContext 被创建之前停止（不停止 SparkContext）。 

Discretized Streams（DStreams）（离散化流）

Discretized Stream（离散化流）或者 DStream（离散流）是 Spark Streaming 提供的基本抽象。它代表了一个连续的数据流，无论是从源接收到的输入数据流，还是通过变换输入流所产生的处理过的数据流。在内部，一个离散流（DStream）被表示为一系列连续的 RDDs，RDD 是 Spark 的一个不可改变的，分布式的数据集的抽象（查看 编程指南了解更多）。在一个 DStream 中的每个 RDD 包含来自一定的时间间隔的数据，如下图所示。

应用于 DStream 的任何操作转化为对于底层的 RDDs 的操作。例如，在先前的例子，转换一个行（lines）流成为单词（words）中，flatMap 操作被应用于在行离散流（lines DStream）中的每个 RDD 来生成单词离散流（words DStream）的 RDDs 。如下图所示。

这些底层的 RDD 变换由 Spark 引擎（engine）计算。 DStream 操作隐藏了大多数这些细节并为了方便起见，提供给了开发者一个更高级别的 API 。这些操作细节会在后边的章节中讨论。

Input DStreams 和 Receivers

输入 DStreams 是代表输入数据是从流的源数据（streaming sources）接收到的流的 DStream 。在快速简单的例子中，行（lines）是一个输入 DStream ，因为它代表着从 netcat 服务器接收到的数据的流。每个输入离散流（input DStream）（除了文件流（file stream），在后面的章节进行讨论）都会与一个接收器（Scala doc，Java doc）对象联系，这个接收器对象从一个源头接收数据并且存储到 Sparks 的内存中用于处理。

Spark Streaming 提供了两种内置的流来源（streaming source）。

Basic sources（基本来源）: 在 StreamingContext API 中直接可用的源（source）。例如，文件系统（file systems），和 socket 连接（socket connections）。

Advanced sources（高级来源）: 就像 Kafka，Flume，Kinesis 之类的通过额外的实体类来使用的来源。这些都需要连接额外的依赖，就像在 连接 部分的讨论。

在本节的后边，我们将讨论每种类别中的现有的一些来源。

需要注意的是，如果你想要在你的流处理程序中并行的接收多个数据流，你可以创建多个输入离散流（input DStreams）（在 性能调整 部分进一步讨论）。这将创建同时接收多个数据流的多个接收器（receivers）。但需要注意，一个 Spark 的 worker/executor 是一个长期运行的任务（task），因此它将占用分配给 Spark Streaming 的应用程序的所有核中的一个核（core）。因此，要记住，一个 Spark Streaming 应用需要分配足够的核（core）（或线程（threads），如果本地运行的话）来处理所接收的数据，以及来运行接收器（receiver(s)）。

需要记住的要点

当在本地运行一个 Spark Streaming 程序的时候，不要使用 "local" 或者 "local[1]" 作为 master 的 URL 。这两种方法中的任何一个都意味着只有一个线程将用于运行本地任务。如果你正在使用一个基于接收器（receiver）的输入离散流（input DStream）（例如， sockets ，Kafka ，Flume 等），则该单独的线程将用于运行接收器（receiver），而没有留下任何的线程用于处理接收到的数据。因此，在本地运行时，总是用 "local[n]" 作为 master URL ，其中的 n > 运行接收器的数量（查看 更多 来了解怎样去设置 master 的信息）。

将逻辑扩展到集群上去运行，分配给 Spark Streaming 应用程序的内核（core）的内核数必须大于接收器（receiver）的数量。否则系统将接收数据，但是无法处理它。

Basic Sources（基本来源）

我们已经简单地了解过了 ssc.socketTextStream(...) 在快速开始的例子中，例子中是从通过一个 TCP socket 连接接收到的文本数据中创建了一个离散流（DStream）。除了 sockets，StreamingContext API 也提供了根据文件作为输入来源创建离散流（DStreams）的方法。

文件流（File Streams）：用于从文件中读取数据，在任何与 HDFS API 兼容的文件系统中（即，HDFS，S3，NFS 等），一个离散流（DStream）可以像下面这样创建 : 

Spark Streaming 将监控 dataDirectory 目录，并处理任何在该目录下创建的文件（写在嵌套目录中的文件是不支持的）。注意 : 

文件必须具有相同的数据格式。

文件必须在 dataDirectory 目录中通过原子移动或者重命名它们到这个 dataDirectory 目录下来创建。

一旦移动，这些文件必须不能再更改，因此如果文件被连续地追加，新的数据将不会被读取。

对于简单的文本文件，还有一个更加简单的方法 streamingContext.textFileStream(dataDirectory)。并且文件流（file streams）不需要运行一个接收器（receiver），因此，不需要分配内核（core）。

在 Python API 中 Python API fileStream 是不可用的，只有 textFileStream 是可用的。

Streams based on Custom Receivers（基于自定义的接收器的流）: 离散流（DStreams）可以使用通过自定义的接收器接收到的数据来创建。查看 自定义接收器指南 来了解更多细节。

Queue of RDDs as a Stream（RDDs 队列作为一个流）: 为了使用测试数据测试 Spark Streaming 应用程序，还可以使用 streamingContext.queueStream(queueOfRDDs) 创建一个基于 RDDs 队列的离散流（DStream），每个进入队列的 RDD 都将被视为 DStream 中的一个批次数据，并且就像一个流进行处理。

想要了解更多的关于从 sockets 和文件（files）创建的流的细节，查看相关功能的 API 文档，Scala 中的 StreamingContext ，Java 中的 JavaStreamingContext 和 Python 中的 StreamingContext 。

Advanced Sources（高级来源）

Python API  在 Spark 2.0.2 中，这些来源中，Kafka，Kinesis 和 Flume 在 Python API 中都是可用的。

这一类别的来源需要使用非 Spark 库中的外部接口，它们中的其中一些还需要比较复杂的依赖关系（例如， Kafka 和 Flume）。因此，为了最小化有关的依赖关系的版本冲突的问题，这些资源本身不能创建 DStream 的功能，它是通过连接单独的类库实现创建 DStream 的功能。

需要注意的是这些高级来源在 Spark Shell 中是不可用的。因此，基于这些高级来源的应用程序不能在 shell 中被测试。如果你真的想要在 Spark shell 中使用它们，你必须下载带有它的依赖的相应的 Maven 组件的 JAR ，并且将其添加到 classpath 。

一些高级来源如下。

Kafka : Spark Streaming 2.0.2 与 Kafka 0.8.2.1 以及更高版本兼容。查看 Kafka 集成指南 来了解更多细节。

Flume : Spark Streaming 2.0.2 与 Flume 1.6.0 兼容。查看 Flume 集成指南 来了解更多细节。

Kinesis : Spark Streaming 2.0.2 与 Kinesis 客户端库 1.2.1 兼容。查看 Kinesis 集成指南 来了解更多细节。

Custom Sources（自定义来源）

在 Python 中 Python API 还不支持自定义来源。

输入离散流（Input DStreams）也可以从创建自定义数据源。所有你需要做的就是实现一个用户定义（user-defined）的接收器（receiver）（查看下一章节去了解那是什么），这个接收器可以从自定义的数据源接收数据并将它推送到 Spark 。查看 自定义接收器指南（Custom Receiver Guide） 来了解更多。

Reveiver Reliability（接收器的可靠性）

可以有两种基于他们的可靠性的数据源。数据源（如 Kafka 和 Flume）允许传输的数据被确认。如果系统从这些可靠的数据来源接收数据，并且被确认（acknowledges）正确地接收数据，它可以确保数据不会因为任何类型的失败而导致数据丢失。这样就出现了 2 种接收器（receivers）: 

Reliable Receiver（可靠的接收器）- 当数据被接收并存储在 Spark 中并带有备份副本时，一个可靠的接收器（reliable receiver）正确地发送确认（acknowledgment）给一个可靠的数据源（reliable source）。

Unreliable Receiver（不可靠的接收器）- 一个不可靠的接收器（ unreliable receiver ）不发送确认（acknowledgment）到数据源。这可以用于不支持确认的数据源，或者甚至是可靠的数据源当你不想或者不需要进行复杂的确认的时候。

在 自定义接收器指南（Custom Receiver Guide） 中描述了关于如何去编写一个可靠的接收器的细节。

DStreams 上的 Transformations（转换）

与 RDD 类似，transformation 允许从 input DStream 输入的数据做修改。DStreams 支持很多在 RDD 中可用的 transformation 算子。一些常用的算子如下所示 : 

Transformation（转换） | Meaning（含义）
map(func) | 利用函数 func 处理原 DStream 的每个元素，返回一个新的 DStream。
flatMap(func) | 与 map 相似，但是每个输入项可用被映射为 0 个或者多个输出项。
filter(func) | 返回一个新的 DStream，它仅仅包含源 DStream 中满足函数 func 的项。
repartition(numPartitions) | 通过创建更多或者更少的 partition 改变这个 DStream 的并行级别（level of parallelism）。
union(otherStream) | 返回一个新的 DStream，它包含源 DStream 和 otherStream 的所有元素。
count() | 通过计算源 DStream 中每个 RDD 的元素数量，返回一个包含单元素（single-element）RDDs 的新 DStream。
reduce(func) | 利用函数 func 聚集源 DStream 中每个 RDD 的元素，返回一个包含单元素（single-element）RDDs 的新 DStream。函数应该是相关联的，以使计算可以并行化。
countByValue() | 这个算子应用于元素类型为 K 的 DStream上，返回一个（K,long）对的新 DStream，每个键的值是在原 DStream 的每个 RDD 中的频率。
reduceByKey(func, [numTasks]) | 当在一个由 (K,V) 对组成的 DStream 上调用这个算子，返回一个新的由 (K,V) 对组成的 DStream，每一个 key 的值均由给定的 reduce 函数聚集起来。 注意：在默认情况下，这个算子利用了 Spark 默认的并发任务数去分组。你可以用 numTasks 参数设置不同的任务数。
join(otherStream, [numTasks]) | 当应用于两个 DStream（一个包含（K,V）对，一个包含 (K,W) 对），返回一个包含 (K, (V, W)) 对的新 DStream。
cogroup(otherStream, [numTasks]) | 当应用于两个 DStream（一个包含（K,V）对，一个包含 (K,W) 对），返回一个包含 (K, Seq[V], Seq[W]) 的元组。
transform(func) | 通过对源 DStream 的每个 RDD 应用 RDD-to-RDD 函数，创建一个新的 DStream。这个可以在 DStream 中的任何 RDD 操作中使用。
updateStateByKey(func) | 利用给定的函数更新 DStream 的状态，返回一个新 "state" 的 DStream。

最后两 个transformation 算子需要重点介绍一下 : 

UpdateStateByKey 操作

updateStateByKey 操作允许不断用新信息更新它的同时保持任意状态。你需要通过两步来使用它 : 

定义状态 - 状态可以是任何的数据类型。

定义状态更新函数 - 怎样利用更新前的状态和从输入流里面获取的新值更新状态。

在每个 batch 中，Spark 会使用更新状态函数为所有的关键字更新状态，不管在 batch 中是否含有新的数据。如果这个更新函数返回一个 none，这个键值对也会被消除。

让我们举个例子说明。在例子中，你想保持一个文本数据流中每个单词的运行次数，运行次数用一个 state 表示，它的类型是整数 : 

def updateFunction(newValues: Seq[Int], runningCount: Option[Int]): Option[Int] = {
    val newCount = ...  // add the new values with the previous running count to get the new count
    Some(newCount)
}

这里是一个应用于包含 words（单词）的 DStream 上（也就是说，在 更前面的示例 中，该 pairs DStream 包含了 (word, 1) pair）。

val runningCounts = pairs.updateStateByKey[Int](updateFunction _)

更新函数将会被每个单词调用，newValues 拥有一系列的 1（从 (word, 1) 组对应），runningCount 拥有之前的次数。要看完整的代码，请看 StatefulNetworkWordCount.scala 示例。

注意，使用 updateStateByKey 需要配置的检查点的目录，这里是更详细关于讨论 CheckPointing 的部分。

Transform 操作

transform 操作（以及它的变化形式如 transformWith）允许在 DStream 运行任何 RDD-to-RDD 函数。它能够被用来应用任何没在 DStream API 中提供的 RDD 操作（It can be used to apply any RDD operation that is not exposed in the DStream API）。 例如，连接数据流中的每个批（batch）和另外一个数据集的功能并没有在 DStream API 中提供，然而你可以简单的利用 transform 方法做到。如果你想通过连接带有预先计算的垃圾邮件信息的输入数据流 来清理实时数据，然后过了它们，你可以按如下方法来做 : 

val spamInfoRDD = ssc.sparkContext.newAPIHadoopRDD(...) // RDD containing spam information

val cleanedDStream = wordCounts.transform(rdd => {
  rdd.join(spamInfoRDD).filter(...) // join data stream with spam information to do data cleaning
  ...
})

请注意，每批间隔提供的函数被调用。 这允许你做 时变抽样操作,即抽样操作,数量的分区，广播变量，批次之间等可以改变。

Window （窗口）操作

Spark Streaming 也支持窗口计算，它允许你在一个滑动窗口数据上应用 transformation 算子。下图阐明了这个滑动窗口。

如上图显示，窗口在源 DStream 上滑动，合并和操作落入窗内的源 RDDs，产生窗口化的 DStream 的 RDDs。在这个具体的例子中，程序在三个时间单元的数据上进行窗口操作，并且每两个时间单元滑动一次。 这说明，任何一个窗口操作都需要指定两个参数 : 

window length（窗口长度）: 窗口的持续时间。

sliding interval（滑动的时间间隔）: 窗口操作执行的时间间隔。

这两个参数必须是源 DStream 上  batch interval（批时间间隔）的倍数。

下面举例说明窗口操作。例如，你想扩展前面的例子用来计算过去 30 秒的词频，间隔时间是 10 秒。为了达到这个目的，我们必须在过去 30 秒的 pairs DStream 上应用 reduceByKey 操作。用方法 reduceByKeyAndWindow 实现。

// Reduce last 30 seconds of data, every 10 seconds
val windowedWordCounts = pairs.reduceByKeyAndWindow((a:Int,b:Int) => (a + b), Seconds(30), Seconds(10))

一些常用的窗口操作如下所示，这些操作都需要用到上文提到的两个参数 - 窗口长度和滑动的时间间隔。

转换 | 含义
window ( windowLength , slideInterval ) | 返回一个新的 DStream，基于窗口的批 DStream 来源。
countByWindow ( windowLength , slideInterval ) | 返回一个滑动窗口中的元素计算流。
reduceByWindow ( func, windowLength , slideInterval ) | 返回一个新创建的单个元素流,通过聚合元素流了 滑动时间间隔使用 函数 。 函数应该关联和交换,以便它可以计算 正确地并行执行。
reduceByKeyAndWindow ( func, windowLength , slideInterval , ( numTasks ]) | 当呼吁DStream(K、V)对,返回一个新的DStream(K、V) 对每个键的值在哪里聚合使用给定的reduce函数 函数 在一个滑动窗口批次。 注意: 默认情况下,它使用引发的默认数量 并行任务(2为本地模式,在集群模式是由配置数量 财产 spark.default.parallelism 分组)。 你可以通过一个可选的 numTasks 参数设置不同数量的任务。
reduceByKeyAndWindow ( func, invFunc , windowLength , slideInterval ,( numTasks ]) | 上面的reduceByKeyAndWindow()的一个更有效的版本，其中每个窗口的reduce值是使用上一个窗口的reduce值递增计算的。 这是通过减少进入滑动窗口的新数据和“反向减少”离开窗口的旧数据来完成的。 一个例子是在窗口滑动时“添加”和“减去”键的计数。 然而，它仅适用于“可逆缩减函数”，即，具有对应的“逆缩减”函数（作为参数invFunc）的那些缩减函数。 像reduceByKeyAndWindow中一样，reduce任务的数量可通过可选参数进行配置。 请注意，必须启用检查点设置才能使用此操作。
countByValueAndWindow ( windowLength , slideInterval ,[ numTasks ]) | 当呼吁DStream(K、V)对,返回一个新的DStream(K,长)对的 每个键的值是它的频率在一个滑动窗口。 就像在 reduceByKeyAndWindow ,通过一个减少任务的数量是可配置的 可选参数。

Join 操作

最后，Spark streaming 可以很容易与其它的数据源进行 join。

Stream-stream 连接

stream 可以很容易与其他 stream 进行 join

val stream1: DStream[String, String] = ...
val stream2: DStream[String, String] = ...
val joinedStream = stream1.join(stream2)

之类，在每一个批间隔中，生成的抽样 stream1 将与生成的抽样 stream2 进行 join 操作。 也可以做 leftOuterJoin ，rightOuterJoin，fullOuterJoin。此外，它通常是非常有用的做连接的窗口 (window) stream。 这是非常容易的。

val windowedStream1 = stream1.window(Seconds(20))
val windowedStream2 = stream2.window(Minutes(1))
val joinedStream = windowedStream1.join(windowedStream2)

Stream-dataset连接

这已经被证明在早些时候解释  DStream.transform 操作。 这是另一个例子，加入一个有窗口的流数据集。

Scala

val dataset: RDD[String, String] = ...
val windowedStream = stream.window(Seconds(20))...
val joinedStream = windowedStream.transform { rdd => rdd.join(dataset) }

事实上，你也可以动态地改变你想加入的数据集。 提供的函数变换 评估每批间隔,因此将使用当前数据集作为参考点。

DStream 转换的完整列表可以在 API 文档。Scala API 看到 DStream 和 PairDStreamFunctions 。Java API 明白了 JavaDStream 和 JavaPairDStream 。Python API 看到 DStream。

DStreams 上的输出操作

输出操作允许 DStream 的操作推到如数据库、文件系统等外部系统中。因为输出操作实际上是允许外部系统消费转换后的数据，它们触发的实际操作是 DStream 转换。目前，定义了下面几种输出操作 : 

输出操作 | 含义
print() | 在 DStream 的每个批数据中打印前 10 条元素，这个操作在开发和调试中都非常有用。在 Python API 中调用 pprint()。
saveAsObjectFiles(prefix, [suffix]) | 保存 DStream 的内容为一个序列化的文件 SequenceFile。每一个批间隔的文件的文件名基于 prefix 和 suffix 生成。"prefix-TIME_IN_MS[.suffix]"，在 Python API 中不可用。
saveAsTextFiles(prefix, [suffix]) | 保存 DStream 的内容为一个文本文件。每一个批间隔的文件的文件名基于 prefix 和 suffix 生成。"prefix-TIME_IN_MS[.suffix]"
saveAsHadoopFiles(prefix, [suffix]) | 保存 DStream 的内容为一个 hadoop 文件。每一个批间隔的文件的文件名基于 prefix 和 suffix 生成。"prefix-TIME_IN_MS[.suffix]"，在 Python API 中不可用。
foreachRDD(func) | 在从流中生成的每个 RDD 上应用函数 func 的最通用的输出操作。这个函数应该推送每个 RDD 的数据到外部系统，例如保存 RDD 到文件或者通过网络写到数据库中。 需要注意的是，func 函数在驱动程序中执行，并且通常都有 RDD action 在里面推动 RDD 流的计算。

foreachRDD 设计模式的使用

dstream.foreachRDD 是一个强大的原语，发送数据到外部系统中。然而，明白怎样正确地、有效地用这个原语是非常重要的。下面几点介绍了如何避免一般错误。

经常写数据到外部系统需要建一个连接对象（例如到远程服务器的 TCP 连接），用它发送数据到远程系统。为了达到这个目的，开发人员可能不经意的在 Spark 驱动中创建一个连接对象，但是在 Spark worker 中 尝试调用这个连接对象保存记录到 RDD 中，如下 : 

dstream.foreachRDD { rdd =>
  rdd.foreach { record =>
    val connection = createNewConnection()
    connection.send(record)
    connection.close()
  }
}

这是不正确的，因为这需要先序列化连接对象，然后将它从 driver 发送到 worker 中。这样的连接对象在机器之间不能传送。它可能表现为序列化错误（连接对象不可序列化）或者初始化错误（连接对象应该在 worker 中初始化）等等。正确的解决办法是在 worker 中创建连接对象。

然而，这会造成另外一个常见的错误 - 为每一个记录创建了一个连接对象。例如 : 

dstream.foreachRDD { rdd =>
  rdd.foreachPartition { partitionOfRecords =>
    // ConnectionPool is a static, lazily initialized pool of connections
    val connection = ConnectionPool.getConnection()
    partitionOfRecords.foreach(record => connection.send(record))
    ConnectionPool.returnConnection(connection)  // return to the pool for future reuse
  }
}

需要注意的是，池中的连接对象应该根据需要延迟创建，并且在空闲一段时间后自动超时。这样就获取了最有效的方式发生数据到外部系统。

其它需要注意的地方 : 

输出操作通过懒执行的方式操作 DStream，正如 RDD action 通过懒执行的方式操作 RDD。具体地看，RDD action 和 DStream 输出操作接收数据的处理。因此，如果你的应用程序没有任何输出操作或者 用于输出操作 dstream.foreachRDD()，但是没有任何 RDD action 操作在 dstream.foreachRDD() 里面，那么什么也不会执行。系统仅仅会接收输入，然后丢弃它们。

默认情况下，DStreams 输出操作是分时执行的，它们按照应用程序的定义顺序按序执行。

DataFrame 和 SQL 操作

你可以很容易地使用 DataFrames 和 SQL Streaming 操作数据。 需要使用 SparkContext 或者正在使用的 StreamingContext 创建一个 SparkSession。这样做的目的就是为了使得驱动程序可以在失败之后进行重启。 使用懒加载模式创建单例的 SparkSession 对象。下面的示例所示。在原先的 单词统计 程序的基础上进行修改，使用 DataFrames 和 SQL 生成单词统计。 每个 RDD 转换为 DataFrame，注册为临时表,然后使用 SQL 查询。

/** 流程序中的DataFrame操作 */

val words: DStream[String] = ...

words.foreachRDD { rdd =>

  // 获取单例的SQLContext
  val sqlContext = SQLContext.getOrCreate(rdd.sparkContext)
  import sqlContext.implicits._

  // 将RDD [String]转换为DataFrame
  val wordsDataFrame = rdd.toDF("word")

  // 注册临时表
  wordsDataFrame.registerTempTable("words")

  // 在DataFrame上使用SQL进行字计数并打印它
  val wordCountsDataFrame =
    sqlContext.sql("select word, count(*) as total from words group by word")
  wordCountsDataFrame.show()
}

这里是完整 源代码 。

你可以在使用其他线程读取的流数据上进行 SQL 查询（就是说，可以异步运行 StreamingContext）。 只要确保 StreamingContext 可以缓存一定量的数据来满足查询的需求。 否则 StreamingContext，检测不到任何异步 SQL 查询，在完成查询之前将删除旧的数据。 例如，如果您想查询最后一批，但您的查询可以运行需要 5 分钟,然后调用 streamingContext.remember(Minutes(5))（在 Scala 中，或其他语言）。

请看 DataFrames，Datasets 和 SQL  指南学习更多关于 DataFrames 的知识。

MLlib 操作

你还可以轻松地使用所提供的机器学习算法 MLlib 。 首先这些streaming，（如机器学习算法。 Streaming 线性回归，StreamingKMeans 等）可以同时学习 Streaming 数据的应用模型。 除了这些,对于一个大得多的机器学习算法,可以学习模型离线（即使用历史数据），然后应用该模型在线流媒体数据。访问 MLlib 指南更多细节。

缓存 / 持久化

类似于抽样，DStreams 还允许开发人员持久化 streama €™数据在内存中。 也就是说，在 DStream 上使用 persist() 方法，它会自动把每个抽样持续化到内存中 。 这个非常有用，如果数据多次 DStream（如同样的数据进行多次操作）。 像 reduceByWindow、reduceByKeyAndWindow 和 updateStateByKey 这些都隐式的开启了 “persist()”。 因此，DStreams 生成的窗口操作会自动保存在内存中，如果没有开发人员调用 persist()。

对于通过网络接收数据的输入流（如 Kafka、Flume、Sockets 等），默认的持久性级别被设置为复制两个节点的数据容错。

注意，与抽样不同,默认的序列化数据持久性 DStreams。更多细节在 性能优化 部分查阅。 可以在 Spark 编程指南 中查看不同持久化级别的详情。

CheckPointing

一个 Streaming 应用程序必须全天候运行，所有必须能够解决应用程序逻辑无关的故障（如系统错误，JVM 崩溃等）。为了使这成为可能，Spark Streaming 需要 checkpoint 足够的信息到容错存储系统中， 以使系统从故障中恢复。

Metadata checkpointing : 保存流计算的定义信息到容错存储系统如 HDFS 中。这用来恢复应用程序中运行 worker 的节点的故障。元数据包括 : 

Configuration : 创建 Spark Streaming 应用程序的配置信息。

DStream operations : 定义 Streaming 应用程序的操作集合。

Incomplete batches : 操作存在队列中的未完成的批。

Data checkpointing : 保存生成的 RDD 到可靠的存储系统中，这在有状态 transformation（如结合跨多个批次的数据）中是必须的。在这样一个 transformation 中，生成的 RDD 依赖于之前批的 RDD，随着时间的推移，这个依赖链的长度会持续增长。在恢复的过程中，为了避免这种无限增长。有状态的 transformation 的中间 RDD 将会定时地存储到可靠存储系统中，以截断这个依赖链。

元数据 checkpoint 主要是为了从 driver 故障中恢复数据。如果 transformation 操作被用到了，数据 checkpoint 即使在简单的操作中都是必须的。

何时启用 CheckPointing

应用程序在下面两种情况下必须开启 checkpoint : 

使用有状态的 ransformation。如果在应用程序中用到了updateStateByKey 或者 reduceByKeyAndWindow，checkpoint 目录必需提供用以定期 checkpoint RDD。

从运行应用程序的 driver 的故障中恢复过来。使用元数据 checkpoint 恢复处理信息。

注意，没有前述的有状态的 transformation 的简单流应用程序在运行时可以不开启 checkpoint。在这种情况下，从 driver 故障的恢复将是部分恢复（接收到了但是还没有处理的数据将会丢失）。 这通常是可以接受的，许多运行的 Spark Streaming 应用程序都是这种方式。

如何配置 CheckPointing

在容错、可靠的文件系统（HDFS、s3 等）中设置一个目录用于保存 checkpoint 信息。这可以通过 streamingContext.checkpoint(checkpointDirectory) 方法来做。这运行你用之前介绍的 有状态 transformation。另外，如果你想从 driver 故障中恢复，你应该以下面的方式重写你的Streaming 应用程序。

当应用程序是第一次启动，新建一个 StreamingContext，启动所有 Stream，然后调用 start() 方法。

当应用程序因为故障重新启动，它将会从 checkpoint 目录 checkpoint 数据重新创建 StreamingContext。

这种配置的方式很简单，通过使用 StreamingContext.getOrCreate 即可，如下所示 : 

// Function to create and setup a new StreamingContext
def functionToCreateContext(): StreamingContext = {
  val ssc = new StreamingContext(...)   // new context
  val lines = ssc.socketTextStream(...) // create DStreams
  ...
  ssc.checkpoint(checkpointDirectory)   // set checkpoint directory
  ssc
}

// Get StreamingContext from checkpoint data or create a new one
val context = StreamingContext.getOrCreate(checkpointDirectory, functionToCreateContext _)

// Do additional setup on context that needs to be done,
// irrespective of whether it is being started or restarted
context. ...

// Start the context
context.start()
context.awaitTermination()

如果 checkpointDirectory 存在，上下文将会利用 checkpoint 数据重新创建。如果这个目录不存在，将会调用 functionToCreateContext 函数创建一个新的上下文，建立 DStream。 请看RecoverableNetworkWordCount 例子。

除了使用 getOrCreate，开发者必须保证在故障发生时，driver 处理自动重启。只能通过部署运行应用程序的基础设施来达到该目的。在部署章节将有更进一步的讨论。

注意，RDD 的 checkpointing 有存储成本。这会导致批数据（包含的 RDD 被 checkpoint）的处理时间增加。因此，需要小心的设置批处理的时间间隔。在最小的批容量（包含 1 秒的数据）情况下，checkpoint 每批数据会显著的减少 操作的吞吐量。相反，checkpointing 太少会导致谱系以及任务大小增大，这会产生有害的影响。因为有状态的 transformation 需要 RDD checkpoint。默认的间隔时间是批间隔时间的倍数，最少 10 秒。它可以通过 dstream.checkpoint 来设置。典型的情况下，设置 checkpoint 间隔是 DStream 的滑动间隔的 5-10 大小是一个好的尝试。

累加器和广播变量

累加器 和 广播变量 是不能从 Spark Streaming 中恢复。 如果要使 checkpoint 可用，累加器 或 广播变量，需要使用 懒加载的方式实例化这两种变量以至于他们能够可以在驱动程序失败重启之后进行再次实例化。下面的示例所示。

object WordBlacklist {

  @volatile private var instance: Broadcast[Seq[String]] = null

  def getInstance(sc: SparkContext): Broadcast[Seq[String]] = {
    if (instance == null) {
      synchronized {
        if (instance == null) {
          val wordBlacklist = Seq("a", "b", "c")
          instance = sc.broadcast(wordBlacklist)
        }
      }
    }
    instance
  }
}

object DroppedWordsCounter {

  @volatile private var instance: Accumulator[Long] = null

  def getInstance(sc: SparkContext): Accumulator[Long] = {
    if (instance == null) {
      synchronized {
        if (instance == null) {
          instance = sc.accumulator(0L, "WordsInBlacklistCounter")
        }
      }
    }
    instance
  }
}

wordCounts.foreachRDD((rdd: RDD[(String, Int)], time: Time) => {
  // Get or register the blacklist Broadcast
  val blacklist = WordBlacklist.getInstance(rdd.sparkContext)
  // Get or register the droppedWordsCounter Accumulator
  val droppedWordsCounter = DroppedWordsCounter.getInstance(rdd.sparkContext)
  // Use blacklist to drop words and use droppedWordsCounter to count them
  val counts = rdd.filter { case (word, count) =>
    if (blacklist.value.contains(word)) {
      droppedWordsCounter += count
      false
    } else {
      true
    }
  }.collect()
  val output = "Counts at time " + time + " " + counts
})

完整代码（源代码）。

应用程序部署

本节讨论部署 Spark Streaming 应用程序的步骤。

要求

要运行一个 Spark Streaming 应用，你需要有以下几点。

有管理器的集群 - 这是任何 Spark 应用程序都需要的需求，详见 部署指南。

将应用程序打为 jar 包 - 你必须编译你的应用程序为 jar 包。如果你用 spark-submit 启动应用程序，你不需要将 Spark 和 Spark Streaming 打包进这个 jar 包。 如果你的应用程序用到了高级源（如 kafka，flume），你需要将它们关联的外部 artifact 以及它们的依赖打包进需要部署的应用程序jar包中。例如，一个应用程序用到了TwitterUtils，那么就需要将 spark-streaming-twitter_2.10 以及它的所有依赖打包到应用程序 jar 中。

为 executors 配置足够的内存-因为接收的数据必须存储在内存中，executors 必须配置足够的内存用来保存接收的数据。注意，如果你正在做 10 分钟的窗口操作，系统的内存要至少能保存 10 分钟的数据。所以，应用程序的内存需求依赖于使用 它的操作。

配置 checkpointing - 如果 stream 应用程序需要 checkpointing，然后一个与 Hadoop API 兼容的容错存储目录必须配置为检查点的目录，流应用程序将 checkpoint 信息写入该目录用于错误恢复。

配置应用程序 driver 的自动重启 - 为了自动从 driver 故障中恢复，运行流应用程序的部署设施必须能监控 driver 进程，如果失败了能够重启它。不同的集群管理器，有不同的工具得到该功能

SparkStandalone : 一个 Spark 应用程序 driver 可以提交到 Spark 独立集群运行，也就是说 driver 运行在一个 worker 节点上。进一步来看，独立的集群管理器能够被指示用来监控 driver，并且在 driver 失败（或者是由于非零的退出代码如 exit(1)， 或者由于运行 driver 的节点的故障）的情况下重启 driver。

YARN : YARN 为自动重启应用程序提供了类似的机制。

Mesos : Mesos 可以用Marathon提供该功能

配置 write ahead logs - 在 Spark 1.2 中，为了获得极强的容错保证，我们引入了一个新的实验性的特性-预写日志（write ahead logs）。如果该特性开启，从 receiver 获取的所有数据会将预写日志写入配置的 checkpoint 目录。 这可以防止 driver 故障丢失数据，从而保证零数据丢失。这个功能可以通过设置配置参数 spark.streaming.receiver.writeAheadLogs.enable 为 true 来开启。然而，这些较强的语义可能以 receiver 的接收吞吐量为代价。这可以通过 并行运行多个 receiver 增加吞吐量来解决。另外，当预写日志开启时，Spark 中的复制数据的功能推荐不用，因为该日志已经存储在了一个副本在存储系统中。可以通过设置输入 DStream 的存储级别为 StorageLevel.MEMORY_AND_DISK_SER 获得该功能。

升级应用程序代码

如果运行的 Spark Streaming应用 程序需要升级，有两种可能的方法。

启动升级的应用程序，使其与未升级的应用程序并行运行。一旦新的程序（与就程序接收相同的数据）已经准备就绪，旧的应用程序就可以关闭。这种方法支持将数据发送到两个不同的目的地（新程序一个，旧程序一个）

首先，平滑的关闭（StreamingContext.stop(...) 或 JavaStreamingContext.stop(...)）现有的应用程序。在关闭之前，要保证已经接收的数据完全处理完。然后，就可以启动升级的应用程序，升级的应用程序会接着旧应用程序的点开始处理。这种方法仅支持具有源端缓存功能的输入源（如 flume，kafka），这是因为当旧的应用程序已经关闭，升级的应用程序还没有启动的时候，数据需要被缓存。

监控应用程序

除了 Spark 自己的 监控功能 之外，针对 Spark Streaming 它也有一些其他的功能。当使用 StreamingContext 时，Spark Web UI 显示了一个额外的 Streaming 标签，它显示了有关正在运行的 Receivers（接收器）（是否 Receivers 处于活动状态，记录接受数量，接收器误差，等等）和已完成的 Batches（批处理时间，查询延迟，等等）的统计信息。这可以用于监控 Spark 应用程序的进度。

在 Web UI 中以下两个指标特别重要 : 

Processing Time（处理时间）- 用来处理每批数据的时间。

Scheduling Delay（调度延迟）- 一批数据在队列中等待，直到上一批数据处理完成所需的时间。

如果批处理时间始终大于批的间隔 并且（或者）队列延迟不断增加，那么说明系统不能尽可能快的处理 Batches，它们（Batches）正在被生成并且落后（处理），在这种情况下，可以考虑 降低 批处理时间。

Spark Streaming 应用程序处理的进度，也可以使用 StreamingListener 接口监控，它允许你获取 Receiver 的状态以及批处理时间。注意，这是一个开发者 API 并且在将来它很可能被改进（即上报更多的信息）。

性能优化

为了在集群中获得 Spark Streaming 应用程序的最佳性能需要一些优化。这部分解释了一部分能够调整用来提升您应用程序性能的参数和配置。在一个较高的水平上，您需要考虑两件事情 : 

通过有效的利用群集资源来减少每批数据的处理时间。

设置正确的 Batch 大小，这样的话当它们被接受时 Batch 数据能够被尽量快的处理（换言之，数据处理能够赶得上数据摄取）。

降低批处理时间

有许多优化可以在 Spark 中来完成，使每批数据的处理时间最小化。这些都在 优化指南 中详细讨论。本章介绍一些最重要的问题。

数据接收的并行级别

通过网络（像 Kafka，Flume，socket，等等）接受数据需要数据反序列化然后存在 Spark 中。如果数据接收成为了系统中的瓶颈，则需要考虑并行的数据接收。注意，每个 Input DStream（输入流）创建一个接受单个数据流的单独的 Receiver（接收器）（运行在一个 Worker 机器上）。接受多个数据流因此可以通过创建多个 Input DStreams 以及配置他们去从数据源（S）的不同分区接收数据流来实现。例如，一个单一的 Kafka Input DStream 接收两个 Topic（主题） 的数据能够被拆分成两个 Kafka 输入流，每个仅接收一个 Topic。这将运行两个 Receiver（接收器），使得数据可以被并行接受，因此将提高整体吞吐量。这些多个 DStream 可以合并在一起以创建一个单独的 DStream。然后被用于在一个单独的 Input DStream 上的 Transformations（转换）可以在统一的流上被应用。按照以下步骤进行 : 

val numStreams = 5
val kafkaStreams = (1 to numStreams).map { i => KafkaUtils.createStream(...) }
val unifiedStream = streamingContext.union(kafkaStreams)
unifiedStream.print()

应当考虑的另一个参数是 Receiver（接收器）的阻塞间隔，它通 过配置参数 spark.streaming.blockInterval 来决定。对于大部分 Receiver（接收器） 来说，在存储到 Spark 的 Memory（内存）之前时接收的数据被合并成数据 Block（块）。每批 Block（块）的数量确定了将用于处理在一个类似 Map Transformation（转换）的接收数据的任务数量。每个 Receiver（接收器）的每个 Batch 的任务数量大约为（Batch 间隔 / Block 间隔）。例如，200ms 的 Block（块）间隔和每 2s 的 Batch 将创建 10 个任务。如果任务的数量过低（即，小于每台机器的 Core（CPU）数量），那么效率会很低，因为所有可用的 Core（CPU）将不会用来处理数据。以增加给定的 Batch 间隔的任务数量，降低该 Block（间隔）。然而，Block（块）间隔推荐的最低值约为 50ms，低于该推荐值的任务的运行开销可能是一个问题。

与多个 Input Streams / Receivers 接受数据的另一种方法是明确的 Repartition （重分区）输入的数据流（使用 inputStream.repartition（<partition（分区）的数量>））。这样在进一步处理之前就分发接收数据的 Batch 到群集中指定数量的机器上去。

数据处理的并行级别

集群资源的利用率可能会很低，如果在任何计算阶段中并行任务数量的不是很多的话。例如，对于像 reduceByKey 和 reduceByKeyAndWindow 这样的分布式 Reduce 操作来说，默认的并行任务数量由 spark.default.parallelism 配置属性 控制。您可以传递并行的的参数（请看 PairDStreamFunctions 文档），或者设置 spark.default.parallelism 配置属性 来改变默认值。

数据序列化

可以通过调整序列化的格式来减少数据序列化的开销。在流式传输的情况下，有两种数据类型会被序列化。

Input Data（输入的数据）: 默认情况下，通过 Receivers（接收器）接收的输入数据被存储在 Executor 的内存与 StorageLevel.MEMORY_AND_DISK_SER_2 中。也就是说，数据被序列化成 Bytes（字节）以降低 GC 开销，以及被复制用于 Executor 的失败容错。此外，数据首先保存在 Memory（内存）中，如果内存不足已容纳所有用于流计算的输入数据将被溢出到硬盘上。这个序列化操作显然也需要开销 - Receiver（接收器）必须反序列化接收到的数据并且使用 Spark 的序列化格式重新序列化它们。

通过 Streaming 操作产生的持久的 RDDs : 通过流计算产生的 RDDs 可能被持久化在内存中。例如，Window（窗口）操作将数据持久化在内存中，因为他们可能被多次处理。然而，不像 Spark Core 默认的 StorageLevel.MEMORY_ONLY，通过流计产生的持久化 RDDs 被使用 StorageLevel.MEMORY_ONLY_SER（也就是序列化）存储，默认 GC 开销降至最低。

在这两种情况下，使用 Kryo 序列化能够减少 CPU 和 内存的开销。更多细节请看 Spark 优化指南。对于 Kyro 来说，考虑注册自定义的 Class，并且禁用 Object（对象）引用跟踪（在 配置指南 中看 Kyro 相关的配置）。

在特定的情况下，需要用于保留 Streaming 应用程序的数据量不是很大，这样也许是可行的，来保存反序列化的数据（两种类型）不用引起过度的 GC 开销。例如，如果您使用 Batch 的间隔有几秒钟并且没有 Window（窗口）操作，然后你可以通过显式地设置存储相应的级别来尝试禁用序列化保存数据。这将减少由于序列化的 CPU 开销，可能不需要太多的 GC 开销就能提升性能。

任务启动开销

如果每秒任务启动的数据很高（比如，每秒 50 个任务或者更多），那么发送 Task（任务） 到 Slave 的负载可能很大，将很难实现 亚秒级 延迟。可以通过下面的改变来降低负载 : 

Execution Mode（运行模式）: 以 Standlone 模式或者 coarse-grained（粗粒度的）Mesos 模式运行 Spark 会比 fine-grained（细粒度的）Mesos 模式运行 Spark 获得更佳的任务启动时间。更详细的信息请参考 Mesos 运行指南。

这些改变也许能减少批处理的时间（100s of milliseconds），因此亚秒级的 Batch 大小是可行的。

设置合理的批处理间隔

对于群集上运行的 Spark Streaming 应用程序来说应该是稳定的，系统应该尽可能快的处理接收到的数据。换句话说，批数据在生成后应该尽快被处理。对于应用程序来说无论这个是不是真的都可以通过在 Streaming Web UI 找到 监控 的处理时间，批处理时间应该小于批间隔。

取决于流计算的性质，所用的批间隔可能在通过群集中一组固定资源上的应用程序持续的数据速率有显著的影响。例如，让我们考虑下更早的 WordCountNetwork 例子。对于一个特定的数据速率。系统可能能够保持每 2 秒报告一次单词统计（即，批间隔是 2 秒），而不是每 500 毫秒一次。因此批间隔需要被设置，使得线上的预期数据速率可持续。

要为您的应用程序找出一个合理的批大小是去使用一个保守的批间隔（例如，5~10 秒）和一个较低的数据速率来测试它。为了验证系统是否能够保持数据速率，您可以通过每个被处理的 Batch 来检查端到端的延迟情况（或者在 Spark Driver log4j 日志中看 “Total delay”，或者使用 StreamingListener 接口）。如果延迟与批大小相比较处于一个稳定的状态，那么系统是稳定的。否则，如果延迟继续增加，它意味着系统不能保持下去，因此它是不稳定的。一旦你有了一个稳定的配置，你可以去试着增加数据速率 和/或者  降低批大小。注意一个短暂的延迟增加是由于临时的数据速率增加可能会变好，只要延迟降低到一个比较低的值。（即，小于批大小）。

内存优化

调优 Spark 应用程序的内存使用情况和 GC 行为已经在 优化指南 中详细讨论了。强烈推荐您阅读它。在这一章，我们讨论在 Spark Streaming 应用程序 Context 中指定的一些优化参数。

Spark Streaming 应用程序在群集中需要的 Memory（内存） 数量取决于使用的 Transformations（转换）上的类型行为。例如，如果您想要在最近 10 分钟的时候上使用 Window（窗口）函数，那么您的群集应有有足够的 Memory 以保存 10 分钟值的数据在内存中。或者您想要在大量的 keys 中使用 updateStateByKey，那么所需要的内存将会更高。与之相反，如果您想要去做一个简单的 map-filter-store 操作，那么所需的内存将会更少。

在一般情况下，从数据通过 Receiver（接收器）被接收时起使用 StorageLevel.MEMORY_AND_DISK_SER_2 存储，数据在内存中放不下时将拆分到硬盘中去。这样也许降低了 Streaming 应用程序的性能，因此建议为您的 Streaming 应用程序提供足够的内存。它最好去试一试，并且在小范围看看 Memory（内存）使用情况然后估算相应的值。

内存调优的另一个方面是垃圾收集。Streaming 应用程序需要低延迟，JVM 垃圾回收造成的大量暂停是不可取的。

这里有一些能够帮助你调整内存使用情况以及 GC 开销的参数 : 

Persistence Level of DStreams（DStream 的持久化级别）: 像前面所提到的 数据序列化 部分，输入的数据和 RDDs 默认持久化为序列化的字节。和反序列化持久性相比，这样减少了内存使用和 GC 开销。启用 Kryo 序列化进一步减少了序列化大小和内存使用。进一步减少内存使用可以用压缩实现（请看 Spark 配置 spark.rdd.compress），付出的是 CPU 时间。

Clearing old data（清除旧数据）: 默认情况下，所有的输入数据和通过 DStreamtransformation（转换）产生的持久化 RDDs 将被自动的清除。SparkStreaming 决定何时清除基于使用 transformation（转换）的数据。例如，如果你使用一个 10 分钟的 Window（窗口）操作，那么 SparkStreaming 将保存最近 10 分钟的数据，并主动扔掉旧的数据。数据也能够通过设置 streamingContext.remeber 保持更久（例如，交互式查询旧数据）。

CMS Garbage Collector（CMS 垃圾回收器）: 使用并发的 mark-sweep GC 是强烈推荐的用于保持 GC 相关的暂停更低。即使知道并发的 GC 降低了整个系统处理的吞吐量。仍然建议使用，以获得更一致的批处理时间。确定你在 Driver（在 spark-submit 中使用 --driver-java-options）和 Executor（使用 Spark 配置spark.executor.extraJavaOptions）上设置的 CMS GC。

Other tips（其它建议）: 为了进一步降低 GC 开销，这里有些更多的建议可以尝试。

持久化 RDDs 使用 OFF_HEAP 存储界别。更多详情请看 Spark 编程指南

使用更多的 Executor 和更小的 heap size（堆大小）。这将在每个 JVM heap 内降低 GC 压力。

应该记住的要点

一个 DStream 和一个单独的 Receiver（接收器）关联。为了达到并行的读取多个 Receiver（接收器）。例如，多个 DStreams 需要被创建。一个 Receiver 运行在一个 Executor 内。它占有一个 Core（CPU）。确保在 Receiver Slot 被预定后有足够的 Core 。例如，spark.cores.max 应该考虑 Receiver Slot。Receiver 以循环的方式被分配到 Executor。

当数据从一个 Stream 源被接收时，Receiver（接收器） 创建了数据块。每个块间隔的毫秒内产生一个新的数据块。N 个数据块在批间隔（N = 批间隔 / 块间隔）的时候被创建。这些 Block（块）通过当前的 Executor 的 BlockManager 发布到其它 Executor 的 BlockManager。在那之后，运行在 Driver 上的  Network Input Tracker 获取 Block 位置用于进一步处理。

一个 RDD 创建在 Driver 上，因为 Block 创建在 batchInterval（批间隔）期间。Block 在 batchInterval 划分成 RDD 时生成。每个分区是 Spark 中的一个任务。blockInterval == batchInterval 将意味着那是一个单独的分区被创建并且可能它在本地被处理过了。

Block 上的 Map 任务在 Executor 中被处理（一个接收 Block，另一个 Block 被复制）无论块的间隔，除非非本地调度死亡。有更大的块间隔意味着更大的块。在本地节点上一个高的值 spark.locality.wait 增加处理 Blcok 的机会。需要发现一个平衡在这两个参数来确保更大的块被本都处理之间。

而不是依靠 batchInterval 和 blockInterval，你可以通过调用 inputDstream.repartition(n) 来定义分区的数量。这样会 reshuffles RDD 中的数据随机来创建 N 个分区。是的，为了更好的并行，虽然增加了 shuffle 的成本。一个 RDD 的处理通过 Driver 的 JobScheduler 作为一个 Job 来调度。在给定的时间点仅有一个 Job 是活跃的。所以，如果一个 Job 正在执行那么其它的 Job 会排队。

如果你有两个 DStream，那将有两个 RDDs 形成并且将有两个 Job 被创建，他们将被一个一个的调度。为了避免这个，你可以合并两个 DStream。这将确保两个 RDD 的 DStream 形成一个单独的 unionRDD。这个 unionRDD 被作为一个单独的 Job 考虑。然而分区的 RDDs 不受影响。

如果批处理时间超过了批间隔，那么显然 Receiver（接收器）的内存将开始填满，最走将抛出异常（最可能的是 BlockNotFoundException）。当前没有方法去暂停 Receiver。使用 SparkConf 配置  spark.streaming.receiver.maxRate，Receiver（接收器）的速率可以被限制。

容错语义

在这部分，我们将讨论 Spark Streaming 应用程序在发生故障时的行为。

背景

为了理解 Spark Streaming 提供的语义，让我们记住 Spark RDDs 最基本的容错语义。

RDD 是不可变的，确定重新计算的，分布式的 DataSet（数据集）。

如果任何分区的 RDD 由于 Worker 节点失败而丢失，那么这个分区能够从原来原始容错的 DataSet（数据集）使用操作的 lineage（血统）关系来重新计算。

假设所有的 RDD 转换是确定的，在最终转换的 RDD 中的数据总是相同的，无论 Spark 群集中的失败。

Spark 在数据上的操作像  HDFS 或者 S3 这样容错的文件系统一样。因此，所有从容错的数据中产生的 RDDs 也是容错的。然而，这种情况在 Spark Streaming 中不适用，因为在大多数情况下数据通过网络被接收（除非使用 fileStream）。为了对所有产生的 RDDs 实现相同的容错语义属性，接收的数据被复制到群集中 Worker 节点的多个 Spark Executor 之间（默认复制因子是 2）。这将会造成需要在发生故障时去恢复两种文件系统的数据类型 : 

Data received and replicated（数据接收并且被复制）- 这份数据幸存于一个单独的 Worker 节点故障中因为其它的节点复制了它。

Data received but buffered for replication（数据接收但是缓冲了副本）- 因为这份数据没有被复制，唯一恢复这份数据的方式是从 Source（源/数据源）再次获取它。

此外，我们应该关注两种类型的故障 : 

Failure of a Worker Node（Worker 节点的故障）- 任何运行 Executor 的 Worker 节点都是可以故障，并且这些节点所有在 Memory（内存）中的数据将会丢失。如果任何 Receiver（接收器）运行在故障的节点，那么他们缓存的数据将丢失。

Failure of the Driver Node（Driver 节点的故障）- 如果 Driver 节点运行的 Spark Streaming 应用程序发生故障，那么很显然 SparkContext 将会丢失，所有 Executor 与它们在内存中的数据都会丢失。

与这个基础知识一起，让我们理解 Spark Streaming 的容错语义。

定义

Streaming 处理系统经常会捕获系统异常并记录执行次数以保障系统容错，其中在这三种条件下可以保障服务，等等。

最多一次 : 每个记录将被处理一次或者根本不处理。

至少一次 : 每个记录将被处理一次或多次。这主要是在最后一次次，因为它确保数据不会丢失。但也有可能是重复处理的。

只有一次 : 每个记录将被处理一次，确保这一次数据完整。这显然是三者的最强保障。

基础语义

在任何流处理系统，从广义上讲，处理数据有三个步骤。

接收数据 : 接受数据或者从其他数据源接受数据。

转换数据 : 所接收的数据使用 DStream 和 RDD 变换转化。

输出数据 : 最后变换的数据被推送到外部系统，如文件系统，数据库，DashBoard 等。

如果一个 Stream 应用程序来实现端到端的数据传输，则每个环节都需要一次性完整保障。也就是说，每个记录都必须被接收正好一次，恰好转化一次，被推到下游系统一次。让我们了解在 Spark Streaming 的情况下这些步骤的语义。

接收数据 : 不同的输入源提供不同的容错。详细过程在下一小节中讨论。

转换数据 : 已经接收将被处理一次的所有数据，这要依靠于 RDDs 提供保证。即使有故障，只要接收到的输入数据是可读的，最终通过 RDDs 的转化将始终具有相同的内容。

输出数据 : 定义了数据至少一次输出，因为它依赖于输出数据操作类型（是否支持转换）和定义的下游系统（是否支持事务）。但是，用户可以实现自己的传输机制，以实现只要一次定义。会在后面小节里有更详细的讨论。

接收数据的语义

不同的输入源提供不同的保障，从至少一次到正好一次。阅读更多的细节。

关于文件

如果所有的输入数据已经存在如 HDFS 的容错文件系统，Spark Streaming 总是可以从任何故障中恢复所有数据。这种定义，在一次处理后就能恢复。

关于基于 Receiver（接收器） 的 Source（源）

对于基于 receiver 的输入源，容错的语义既依赖于故障的情形也依赖于 receiver 的类型。正如之前讨论的，有两种类型的 receiver : 

Reliable Receiver（可靠的接收器）: 这些 receivers 只有在确保数据复制之后才会告知可靠源。如果这样一个 receiver 失败了，缓冲（非复制）数据不会被源所承认。如果 receiver 重启，源会重发数 据，因此不会丢失数据。

Unreliable Receiver（不可靠的接收器）: 当 worker 或者 driver 节点故障，这种 receiver 会丢失数据。

选择哪种类型的 receiver 依赖于这些语义。如果一个 worker 节点出现故障，Reliable Receiver 不会丢失数据，Unreliable Receiver 会丢失接收了但是没有复制的数据。如果 driver 节点出现故障，除了以上情况下的数据丢失，所有过去接收并复制到内存中的数据都会丢失，这会影响有状态 transformation 的结果。

为了避免丢失过去接收的数据，Spark 1.2 引入了一个实验性的特征（预写日志机制）write ahead logs，它保存接收的数据到容错存储系统中。有了 write ahead logs 和 Reliable Receiver，我们可以做到零数据丢失以及 exactly-once 语义。

下表总结了根据故障的语义 : 

Deployment Scenario | Worker Failure | Driver Failure
Spark 1.1 或者更早, 没有 write ahead log 的 Spark 1.2 | 在 Unreliable Receiver 情况下缓冲数据丢失；在 Reliable Receiver 和文件的情况下，零数据丢失 | 在 Unreliable Receiver 情况下缓冲数据丢失；在所有 receiver 情况下，过去的数据丢失；在文件的情况下，零数据丢失
带有 write ahead log 的 Spark 1.2 | 在 Reliable Receiver 和文件的情况下，零数据丢失 | 在 Reliable Receiver 和文件的情况下，零数据丢失

Kafka Direct API

在 Spark 1.3 中，我们引入了一个新的 kafka Direct 的 API，它可以保证所有 kafka 数据由 spark stream 收到一次。如果实现仅一次输出操作，就可以实现保证终端到终端的一次。这种方法（版本 Spark2.0.2）中进一步讨论 kafka集成指南。

输出操作的语义

输出操作（例如 foreachRDD）至少被定义一次，即变换后的数据在一次人工故障的情况下可能会不止一次写入外部实体。虽然可以通过操作 saveAs***Files 保存文件到系统上（具有相同的数据将简单地被覆盖），额外的尝试可能是必要的，以实现一次准确的语义。有两种方法。

幂等更新 : 多次尝试写相同的数据。例如，saveAs***Files 总是写入相同数据生成的文件。

事务更新 : 所有更新事务作出这样的更新是恰好遵循原子性。要做到这一点,如下 2 种方式。

使用批处理时间创建一个标识符（foreachRDD）和 RDD 的分区索引。给这个应用定义唯一标识符标识 blob 数据。

更新外部系统与当前事务（即原子性），如果已经被标识过得数据应用已经存在，那么就跳过更新。

dstream.foreachRDD { (rdd, time) =>
  rdd.foreachPartition { partitionIterator =>
    val partitionId = TaskContext.get.partitionId()
    val uniqueId = generateUniqueId(time.milliseconds, partitionId)
    // use this uniqueId to transactionally commit the data in partitionIterator
  }
}

迁移指南（从 0.9.1 或者更低版本至 1.x 版本）

在 Spark 0.9.1 和 Spark 1.0 之间，它们有一些 API 的改变以确保未来 API 的稳定性。这一章阐述需要迁移您已经在的代码到 1.0 版本的步骤。

Input DSteams（输入流）: 所有创建 Input Steam（输入流）的操作（例如，StreamingContext.socketStream，FlumeUtils.createStream，等等。）现在返回 InputDStream / ReceiverInputDStream (而不是 DStream) 的 Scala 版本，和 JavaInputDStream / JavaPairInputDStream /JavaReceiverInputDStream / JavaPairReceiverInputDStream（而不是 JavaDStream) 的 Java 版。这将确保特定输入流的功能可以添加到这些类中在未来不需要破坏二进制兼容性。注意您已经存在的 Spark Streaming 应用程序应该不需要任何改变（因为那些类是 DStream / JavaDStream 的子类），但可能需要使用 Spark 1.0 重新编译。

Custom Network Receivers（自定义网络接收器）: 从发布 Spark Streaming 以来，自定义网络接收器能够在 Scala 使用 NetworkReceiver 类自定义，该 API 在错误处理和报告中被限制了，并且不能够被 Java 使用。从 Spark 1.0 开始，自个类被 Receiver 替换了并且带来了下面的好处。

像 stop 和 restart 这样的方法已经被添加以更好的控制 Receiver（接收器）的生命周期。更多详细信息请看 自定义 Receiver 指南。

自定义 Receiver 能够使用 Scala 和 Java 实现。

为了迁移你已存在的自定义 Receiver，从更早的 NetworkReceiver 到新的 Receiver。您需要去做如下事情。

让您自定义的 Receiver 继承 org.apache.spark.streaming.receiver.Receiver 而不是 org.apache.spark.streaming.dstream.NetworkReceiver。

之前，一个 BlockGenerator 对象必须去通过自定义 Receiver 创建，它接收的数据被添加然后存在 Spark 中。它必须显示的从 onStart() 和 stop() 方法来启动和停止。新的 Receiver 类让这个是不必要的因为它添加了一组名为 store（<data>）的方法，能够在 Spark 中被调用以存储数据。所以，为了迁移你自定义的网络接收器，删除任何的 BlockGenerator 对象（在 Spark 1.0 中再也不存在），并且在接收数据时使用 store(...)方法。

Actor-base Receivers（基于 Actor 的接收器）: 这个基于 Actor 的接收器 API 已经被移到 DStream Akka 中去了。更多详细信息请参考该工程。

快速跳转

其它的指南

Kafka 集成指南

Kinesis 集成指南

自定义 Receiver 指南

外部 DStream 数据源

DStream MQTT

DStream Twitter

DStream Akka

DStream ZeroMQ

API 文档

Scala 文档

StreamingContext and DStream

KafkaUtils, FlumeUtils, KinesisUtils,

Java 文档

JavaStreamingContext, JavaDStream and JavaPairDStream

KafkaUtils, FlumeUtils, KinesisUtils

Python 文档

StreamingContext and DStream

KafkaUtils

Scala，Java 和 Python 中的更多示例。

描述 Spark Streaming 的 书籍 和 视频。

Spark Streaming 概述

原文链接 : http://spark.apache.org/docs/latest/streaming-programming-guide.html#overview

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2885885

贡献者 : 小瑶 漫步 那伊抹微笑 片刻

Spark Streaming 是 Spark Core API 的扩展，它支持弹性的，高吞吐的，容错的实时数据流的处理。数据可以通过多种数据源获取，例如 Kafka，Flume，Kinesis 以及 TCP sockets，也可以通过例如 map，reduce，join，window 等的高阶函数组成的复杂算法处理。最终，处理后的数据可以输出到文件系统，数据库以及实时仪表盘中。事实上，你还可以在数据流上使用 Spark机器学习 以及 图形处理算法 。

在内部，它工作原理如下，Spark Streaming 接收实时输入数据流并将数据切分成多个批数据，然后交由 Spark 引擎处理并分批的生成结果数据流。

Spark Streaming 提供了一个高层次的抽象叫做离散流（discretized stream）或者 DStream，它代表一个连续的数据流。DStream 可以通过来自数据源的输入数据流创建，例如 Kafka，Flume 以及 Kinesis，或者在其他 DStream 上进行高层次的操作创建。在内部，一个 DStream 是通过一系列的 RDD 来表示。

本指南告诉你如何使用 DStream 来编写一个 Spark Streaming 程序.你可以使用 Scala ，Java 或者 Python（Spark 1.2 版本后引进）来编写程序。

注意 :

在 Python 有些 API 可能会有不同或不可用，在这个指南里你将会发现 Python API 的标签来高亮显示这些不同。

一个简单的示例

原文链接 : http://spark.apache.org/docs/1.6.1/streaming-programming-guide.html#a-quick-example

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2885892

贡献者 : 小瑶 那伊抹微笑 片刻

在我们详细介绍如何你自己的 Spark Streaming 程序的细节之前，让我们先来看一看一个简单的 Spark Streaming 程序的样子。比方说，我们想要计算从一个监听 TCP socket 的数据服务器接收到的文本数据（text data）中的字数。所有你需要做的就是照着下面的步骤做。

首先，我们导入了 Spark Streaming 类和部分从 StreamingContext 隐式转换到我们的环境的名称，目的是添加有用的方法到我们需要的其他类（如 DStream）。 StreamingContext 是所有流功能的主要入口点。我们创建了一个带有 2 个执行线程和间歇时间为 1 秒的本地 StreamingContext 。

import org.apache.spark._
import org.apache.spark.streaming._
import org.apache.spark.streaming.StreamingContext._ //自从 Spark 1.3 开始，不再是必要的了

// 创建一个具有两个工作线程(working thread)和批次间隔为1秒的本地 StreamingContext
// master 需要 2 个核，以防止饥饿情况(starvation scenario)。
val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
val ssc = new StreamingContext(conf, Seconds(1))

在使用这种背景下，我们可以创建一个代表从 TCP 源流数据的离散流（DStream），指定主机名（hostname）（例如 localhost）和端口（例如 9999）。

// 创建一个将要连接到 hostname:port 的离散流，如 localhost:9999
val lines = ssc.socketTextStream("localhost", 9999)

上一步的这个 lines 离散流（DStream）表示将要从数据服务器接收到的数据流。在这个 离散流（DStream）中的每一条记录都是一行文本（text）。接下来，我们想要通过空格字符（space characters）拆分这些数据行（lines）成单词（words）。

// 将每一行拆分成单词 val words = lines.flatMap(_.split(" "))
val words = lines.flatMap(_.split(" "))

flatMap 是一种一对多的离散流（DStream）操作，它会通过在源离散流（source DStream）中根据每个记录（record）生成多个新纪录的形式创建一个新的离散流（DStream）。在这种情况下，在这种情况下，每一行（each line）都将被拆分成多个单词（words）和代表单词离散流（words DStream）的单词流。接下来，我们想要计算这些单词。

import org.apache.spark.streaming.StreamingContext._ // 自从 Spark 1.3 不再是必要的
// 计算每一个批次中的每一个单词
val pairs = words.map(word => (word, 1)) val wordCounts = pairs.reduceByKey(_ + _)

// 在控制台打印出在这个离散流（DStream）中生成的每个 RDD 的前十个元素
// 注意 : 必需要触发 action（很多初学者会忘记触发action操作，导致报错：No output operations registered, so nothing to execute）
wordCounts.print()

上一步的 words 离散流进行了进一步的映射（一对一的转变）为一个 (word, 1)对 的离散流（DStream），这个离散流然后被规约（reduce）来获得数据中每个批次（batch）的单词频率。最后，wordCounts.print() 将会打印一些每秒生成的计数。

请注意，当这些行（lines）被执行的时候， Spark Streaming 只有建立在启动时才会执行计算，在它已经开始之后，并没有真正地处理。为了在所有的转换都已经完成之后开始处理，我们在最后运行 : 

ssc.start() // 启动计算
ssc.awaitTermination() // 等待计算的终止

完整的代码可以在 Spark Streaming 的例子 NetworkWordCount 中找到。

如果你已经 下载 并且 建立 了 Spark ，你可以运行下面的例子。你首先需要运行 Netcat（一个在大多数类 Unix 系统中的小工具）作为我们使用的数据服务器。

$ nc -lk 9999

然后，在另一个不同的终端，你可以运行这个例子通过执行 : 

$ ./bin/run-example streaming.NetworkWordCount localhost 9999

然后，在运行在 netcat 服务器上的终端输入的任何行（lines），都将被计算，并且每一秒都显示在屏幕上，它看起来就像下面这样 : 

# TERMINAL 1:
# Running Netcat

$ nc -lk 9999

hello world

# TERMINAL 2: RUNNING NetworkWordCount

$ ./bin/run-example streaming.NetworkWordCount localhost 9999
...
-------------------------------------------
Time: 1357008430000 ms
-------------------------------------------
(hello,1)
(world,1)
...

基本概念

依赖

初始化 StreamingContext

Discretized Streams（DStreams）（离散化流）

Input DStreams 和 Receivers

DStreams 上的 Transformations（转换）

DStreams 上的输出操作

DataFrame 和 SQL 操作

MLlib 操作

缓存 / 持久化

CheckPointing

累加器和广播变量

应用程序部署

监控应用程序

原文链接 : http://spark.apache.org/docs/latest/streaming-programming-guide.html#basic-concepts

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2885894

贡献者 : 小瑶 漫步 那伊抹微笑 片刻

接下来，我们了解完了简单的例子，开始阐述 Spark Streaming 的基本知识。

依赖

原文链接 : http://spark.apache.org/docs/latest/streaming-programming-guide.html#linking

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2885898

贡献者 : 小瑶 漫步 那伊抹微笑 片刻

与 Spark 类似， Spark Streaming 可以通过 Maven 来管理依赖。为了写你自己的 Spark Streaming 程序，你必须添加以下的依赖到你的 SBT 或者 Maven 项目中。

Maven 示例 : 

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-streaming_2.11</artifactId>
    <version>2.0.2</version>
</dependency>

对于从现在没有在 Spark Streaming Core API 中的数据源获取数据，如 Kafka， Flume，Kinesis ，你必须添加相应的组件 spark-streaming-xyz_2.11 到依赖中。例如，有一些常见的依赖如下。

源 | 坐标
Kafka | spark-streaming-kafka-0-8_2.11
Flume | spark-streaming-flume_2.11
Kinesis | spark-streaming-kinesis-asl_2.11 [Amazon Software License]

想要查看一个实时更新的列表，请参阅 Maven repository 来了解支持的源文件和组件的完整列表。

初始化 StreamingContext

原文链接 : http://spark.apache.org/docs/latest/streaming-programming-guide.html#initializing-streamingcontext

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2885902

贡献者 : 小瑶 漫步 那伊抹微笑 片刻

为了初始化一个 Spark Streaming 程序，一个 StreamingContext 对象必须要被创建出来，它是所有的 Spark Streaming 功能的主入口点。

一个 StreamingContext 对象可以从一个 SparkConf 对象中创建出来。

import org.apache.spark._
import org.apache.spark.streaming._

val conf = new SparkConf().setAppName(appName).setMaster(master)
val ssc = new StreamingContext(conf, Seconds(1))

这个 appName 参数是展示在集群用户界面上的你的应用程序的名称。 master 是一个  Spark, Mesos or YARN cluster URL ，或者一个特殊的 "local[*]" 字符串运行在本地模式下。在实践中，在一个集群上运行时，你不会想在程序中硬编码 master，而是使用 spark-submit 启动应用程序，并且接收这个参数。然而，对于本地测试和单元测试，你可以传递 "local[*]" 去运行 Spark Streaming 过程（检测本地系统中内核的个数）。请注意，这内部创建了一个 SparkContext （所有 Spark 功能的出发点），它可以像这样被访问 ssc.sparkContext。

这个批处理间隔（batch interval）必须根据您的应用程序和可用的集群资源的等待时间要求进行设置。详情请参阅 优化指南 部分。

一个 StreamingContext 对象也可以从一个现有的 SparkContext 对象中创建出。

import org.apache.spark.streaming._

val sc = ...                // existing SparkContext
val ssc = new StreamingContext(sc, Seconds(1))

一个 context 定义之后，你必须做以下几个方面。

通过创建输入 DStreams 定义输入源。

通过应用转换和输出操作 DStreams 定义流计算（streaming computations）。

开始接收数据，并用 streamingContext.start() 处理它。

等待处理被停止（手动停止或者因为任何错误停止）使用 StreamingContext.awaitTermination() 。

该处理可以使用 streamingContext.stop() 手动停止。

要记住的要点 : 

一旦一个 context 已经启动，将不会有新的数据流的计算可以被创建或者添加到它。

一旦一个 context 已经停止，它不会被重新启动。

同一时间内在 JVM 中只有一个 StreamingContext 可以被激活。

在 StreamingContext 上的 stop() 同样也停止了 SparkContext 。为了只停止 StreamingContext ，设置 stop() 的可选参数，名叫 stopSparkContext 为 false 。

一个 SparkContext 可以被重新用于创建多个 StreamingContexts，只要前一个 StreamingContext 在下一个StreamingContext 被创建之前停止（不停止 SparkContext）。 

Discretized Streams（DStreams）（离散化流）

Discretized Stream（离散化流）或者 DStream（离散流）是 Spark Streaming 提供的基本抽象。它代表了一个连续的数据流，无论是从源接收到的输入数据流，还是通过变换输入流所产生的处理过的数据流。在内部，一个离散流（DStream）被表示为一系列连续的 RDDs，RDD 是 Spark 的一个不可改变的，分布式的数据集的抽象（查看 编程指南了解更多）。在一个 DStream 中的每个 RDD 包含来自一定的时间间隔的数据，如下图所示。

应用于 DStream 的任何操作转化为对于底层的 RDDs 的操作。例如，在先前的例子，转换一个行（lines）流成为单词（words）中，flatMap 操作被应用于在行离散流（lines DStream）中的每个 RDD 来生成单词离散流（words DStream）的 RDDs 。如下图所示。

这些底层的 RDD 变换由 Spark 引擎（engine）计算。 DStream 操作隐藏了大多数这些细节并为了方便起见，提供给了开发者一个更高级别的 API 。这些操作细节会在后边的章节中讨论。

Input DStreams 和 Receivers

原文链接 : http://spark.apache.org/docs/latest/streaming-programming-guide.html#input-dstreams-and-receivers

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2885906

贡献者 : 小瑶 漫步 那伊抹微笑 片刻

输入 DStreams 是代表输入数据是从流的源数据（streaming sources）接收到的流的 DStream 。在快速简单的例子中，行（lines）是一个输入 DStream ，因为它代表着从 netcat 服务器接收到的数据的流。每个输入离散流（input DStream）（除了文件流（file stream），在后面的章节进行讨论）都会与一个接收器（Scala doc，Java doc）对象联系，这个接收器对象从一个源头接收数据并且存储到 Sparks 的内存中用于处理。

Spark Streaming 提供了两种内置的流来源（streaming source）。

Basic sources（基本来源）: 在 StreamingContext API 中直接可用的源（source）。例如，文件系统（file systems），和 socket 连接（socket connections）。

Advanced sources（高级来源）: 就像 Kafka，Flume，Kinesis 之类的通过额外的实体类来使用的来源。这些都需要连接额外的依赖，就像在 连接 部分的讨论。

在本节的后边，我们将讨论每种类别中的现有的一些来源。

需要注意的是，如果你想要在你的流处理程序中并行的接收多个数据流，你可以创建多个输入离散流（input DStreams）（在 性能调整 部分进一步讨论）。这将创建同时接收多个数据流的多个接收器（receivers）。但需要注意，一个 Spark 的 worker/executor 是一个长期运行的任务（task），因此它将占用分配给 Spark Streaming 的应用程序的所有核中的一个核（core）。因此，要记住，一个 Spark Streaming 应用需要分配足够的核（core）（或线程（threads），如果本地运行的话）来处理所接收的数据，以及来运行接收器（receiver(s)）。

需要记住的要点

当在本地运行一个 Spark Streaming 程序的时候，不要使用 "local" 或者 "local[1]" 作为 master 的 URL 。这两种方法中的任何一个都意味着只有一个线程将用于运行本地任务。如果你正在使用一个基于接收器（receiver）的输入离散流（input DStream）（例如， sockets ，Kafka ，Flume 等），则该单独的线程将用于运行接收器（receiver），而没有留下任何的线程用于处理接收到的数据。因此，在本地运行时，总是用 "local[n]" 作为 master URL ，其中的 n > 运行接收器的数量（查看 更多 来了解怎样去设置 master 的信息）。

将逻辑扩展到集群上去运行，分配给 Spark Streaming 应用程序的内核（core）的内核数必须大于接收器（receiver）的数量。否则系统将接收数据，但是无法处理它。

Basic Sources（基本来源）

我们已经简单地了解过了 ssc.socketTextStream(...) 在快速开始的例子中，例子中是从通过一个 TCP socket 连接接收到的文本数据中创建了一个离散流（DStream）。除了 sockets，StreamingContext API 也提供了根据文件作为输入来源创建离散流（DStreams）的方法。

文件流（FileStreams）：用于从文件中读取数据，在任何与 HDFSAPI 兼容的文件系统中（即，HDFS，S3，NFS 等），一个离散流（DStream）可以像下面这样创建 : 

  streamingContext.fileStream[KeyClass, ValueClass, InputFormatClass](dataDirectory)

Spark Streaming 将监控 dataDirectory 目录，并处理任何在该目录下创建的文件（写在嵌套目录中的文件是不支持的）。注意 : 

文件必须具有相同的数据格式。

文件必须在 dataDirectory 目录中通过原子移动或者重命名它们到这个 dataDirectory 目录下来创建。

一旦移动，这些文件必须不能再更改，因此如果文件被连续地追加，新的数据将不会被读取。

对于简单的文本文件，还有一个更加简单的方法 streamingContext.textFileStream(dataDirectory)。并且文件流（file streams）不需要运行一个接收器（receiver），因此，不需要分配内核（core）。

在 Python API 中 Python API fileStream 是不可用的，只有 textFileStream 是可用的。

Streams based on Custom Receivers（基于自定义的接收器的流）: 离散流（DStreams）可以使用通过自定义的接收器接收到的数据来创建。查看 自定义接收器指南 来了解更多细节。

Queue of RDDs as a Stream（RDDs 队列作为一个流）: 为了使用测试数据测试 Spark Streaming 应用程序，还可以使用 streamingContext.queueStream(queueOfRDDs) 创建一个基于 RDDs 队列的离散流（DStream），每个进入队列的 RDD 都将被视为 DStream 中的一个批次数据，并且就像一个流进行处理。

想要了解更多的关于从 sockets 和文件（files）创建的流的细节，查看相关功能的 API 文档，Scala 中的 StreamingContext ，Java 中的 JavaStreamingContext 和 Python 中的 StreamingContext 。

Advanced Sources（高级来源）

Python API  在 Spark 2.0.2 中，这些来源中，Kafka，Kinesis 和 Flume 在 Python API 中都是可用的。

这一类别的来源需要使用非 Spark 库中的外部接口，它们中的其中一些还需要比较复杂的依赖关系（例如， Kafka 和 Flume）。因此，为了最小化有关的依赖关系的版本冲突的问题，这些资源本身不能创建 DStream 的功能，它是通过连接单独的类库实现创建 DStream 的功能。

需要注意的是这些高级来源在 Spark Shell 中是不可用的。因此，基于这些高级来源的应用程序不能在 shell 中被测试。如果你真的想要在 Spark shell 中使用它们，你必须下载带有它的依赖的相应的 Maven 组件的 JAR ，并且将其添加到 classpath 。

一些高级来源如下。

Kafka : Spark Streaming 2.0.2 与 Kafka 0.8.2.1 以及更高版本兼容。查看 Kafka 集成指南 来了解更多细节。

Flume : Spark Streaming 2.0.2 与 Flume 1.6.0 兼容。查看 Flume 集成指南 来了解更多细节。

Kinesis : Spark Streaming 2.0.2 与 Kinesis 客户端库 1.2.1 兼容。查看 Kinesis 集成指南 来了解更多细节。

Custom Sources（自定义来源）

在 Python 中 Python API 还不支持自定义来源。

输入离散流（Input DStreams）也可以从创建自定义数据源。所有你需要做的就是实现一个用户定义（user-defined）的接收器（receiver）（查看下一章节去了解那是什么），这个接收器可以从自定义的数据源接收数据并将它推送到 Spark 。查看 自定义接收器指南（Custom Receiver Guide） 来了解更多。

Reveiver Reliability（接收器的可靠性）

可以有两种基于他们的可靠性的数据源。数据源（如 Kafka 和 Flume）允许传输的数据被确认。如果系统从这些可靠的数据来源接收数据，并且被确认（acknowledges）正确地接收数据，它可以确保数据不会因为任何类型的失败而导致数据丢失。这样就出现了 2 种接收器（receivers）: 

Reliable Receiver（可靠的接收器）- 当数据被接收并存储在 Spark 中并带有备份副本时，一个可靠的接收器（reliable receiver）正确地发送确认（acknowledgment）给一个可靠的数据源（reliable source）。

Unreliable Receiver（不可靠的接收器）- 一个不可靠的接收器（ unreliable receiver ）不发送确认（acknowledgment）到数据源。这可以用于不支持确认的数据源，或者甚至是可靠的数据源当你不想或者不需要进行复杂的确认的时候。

在 自定义接收器指南（Custom Receiver Guide） 中描述了关于如何去编写一个可靠的接收器的细节。

DStreams 上的 Transformations（转换）

原文链接 : http://spark.apache.org/docs/latest/streaming-programming-guide.html#transformations-on-dstreams

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2885990

贡献者 : 小瑶 漫步 那伊抹微笑 片刻

与 RDD 类似，transformation 允许从 input DStream 输入的数据做修改。DStreams 支持很多在 RDD 中可用的 transformation 算子。一些常用的算子如下所示 : 

Transformation（转换） | Meaning（含义）
map(func) | 利用函数 func 处理原 DStream 的每个元素，返回一个新的 DStream。
flatMap(func) | 与 map 相似，但是每个输入项可用被映射为 0 个或者多个输出项。
filter(func) | 返回一个新的 DStream，它仅仅包含源 DStream 中满足函数 func 的项。
repartition(numPartitions) | 通过创建更多或者更少的 partition 改变这个 DStream 的并行级别（level of parallelism）。
union(otherStream) | 返回一个新的 DStream，它包含源 DStream 和 otherStream 的所有元素。
count() | 通过计算源 DStream 中每个 RDD 的元素数量，返回一个包含单元素（single-element）RDDs 的新 DStream。
reduce(func) | 利用函数 func 聚集源 DStream 中每个 RDD 的元素，返回一个包含单元素（single-element）RDDs 的新 DStream。 函数应该是相关联的，以使计算可以并行化。
countByValue() | 这个算子应用于元素类型为 K 的 DStream上，返回一个（K,long）对的新 DStream，每个键的值是在原 DStream 的每个 RDD 中的频率。
reduceByKey(func, [numTasks]) | 当在一个由 (K,V) 对组成的 DStream 上调用这个算子，返回一个新的由 (K,V) 对组成的 DStream，每一个 key 的值均由给定的 reduce 函数聚集起来。 注意：在默认情况下，这个算子利用了 Spark 默认的并发任务数去分组。你可以用 numTasks 参数设置不同的任务数。
join(otherStream, [numTasks]) | 当应用于两个 DStream（一个包含（K,V）对，一个包含 (K,W) 对），返回一个包含 (K, (V, W)) 对的新 DStream。
cogroup(otherStream, [numTasks]) | 当应用于两个 DStream（一个包含（K,V）对，一个包含 (K,W) 对），返回一个包含 (K, Seq[V], Seq[W]) 的元组。
transform(func) | 通过对源 DStream 的每个 RDD 应用 RDD-to-RDD 函数，创建一个新的 DStream。这个可以在 DStream 中的任何 RDD 操作中使用。
updateStateByKey(func) | 利用给定的函数更新 DStream 的状态，返回一个新 "state" 的 DStream。

最后两 个transformation 算子需要重点介绍一下 : 

UpdateStateByKey 操作

updateStateByKey 操作允许不断用新信息更新它的同时保持任意状态。你需要通过两步来使用它 : 

定义状态 - 状态可以是任何的数据类型。

定义状态更新函数 - 怎样利用更新前的状态和从输入流里面获取的新值更新状态。

在每个 batch 中，Spark 会使用更新状态函数为所有的关键字更新状态，不管在 batch 中是否含有新的数据。如果这个更新函数返回一个 none，这个键值对也会被消除。

让我们举个例子说明。在例子中，你想保持一个文本数据流中每个单词的运行次数，运行次数用一个 state 表示，它的类型是整数 : 

Scala：

def updateFunction(newValues: Seq[Int], runningCount: Option[Int]): Option[Int] = {
    val newCount = ...  // add the new values with the previous running count to get the new count
    Some(newCount)
}

这里是一个应用于包含 words（单词）的 DStream 上（也就是说，在 更前面的示例 中，该 pairs DStream 包含了 (word, 1) pair）。

val runningCounts = pairs.updateStateByKey[Int](updateFunction _)

更新函数将会被每个单词调用，newValues 拥有一系列的 1（从 (word, 1) 组对应），runningCount 拥有之前的次数。要看完整的代码，见案例。

注意，使用 updateStateByKey 需要配置的检查点的目录，这里是更详细关于讨论 CheckPointing 的部分。

Transform 操作

transform 操作（以及它的变化形式如 transformWith）允许在 DStream 运行任何 RDD-to-RDD 函数。它能够被用来应用任何没在 DStream API 中提供的 RDD 操作（It can be used to apply any RDD operation that is not exposed in the DStream API）。 例如，连接数据流中的每个批（batch）和另外一个数据集的功能并没有在 DStream API 中提供，然而你可以简单的利用 transform 方法做到。如果你想通过连接带有预先计算的垃圾邮件信息的输入数据流 来清理实时数据，然后过了它们，你可以按如下方法来做 : 

val spamInfoRDD = ssc.sparkContext.newAPIHadoopRDD(...) // RDD containing spam information

val cleanedDStream = wordCounts.transform(rdd => {
  rdd.join(spamInfoRDD).filter(...) // join data stream with spam information to do data cleaning
  ...
})

请注意，提供的函数在每个批次间隔中被调用。这允许您进行随时间变化的RDD操作，即RDD操作，分区数，广播变量等可以在批次之间更改。

Window （窗口）操作

Spark Streaming 也支持窗口计算，它允许你在一个滑动窗口数据上应用 transformation 算子。下图阐明了这个滑动窗口。

如上图显示，窗口在源 DStream 上滑动，合并和操作落入窗内的源 RDDs，产生窗口化的 DStream 的 RDDs。在这个具体的例子中，程序在三个时间单元的数据上进行窗口操作，并且每两个时间单元滑动一次。 这说明，任何一个窗口操作都需要指定两个参数 : 

window length（窗口长度）: 窗口的持续时间。

sliding interval（滑动的时间间隔）: 窗口操作执行的时间间隔。

这两个参数必须是源 DStream 上  batch interval（批时间间隔）的倍数。

下面举例说明窗口操作。例如，你想扩展前面的例子用来计算过去 30 秒的词频，间隔时间是 10 秒。为了达到这个目的，我们必须在过去 30 秒的 pairs DStream 上应用 reduceByKey 操作。用方法 reduceByKeyAndWindow 实现。

// Reduce last 30 seconds of data, every 10 seconds
val windowedWordCounts = pairs.reduceByKeyAndWindow((a:Int,b:Int) => (a + b), Seconds(30), Seconds(10))

一些常用的窗口操作如下所示，这些操作都需要用到上文提到的两个参数 - 窗口长度和滑动的时间间隔。

转换 | 含义
window ( windowLength , slideInterval ) | 返回一个新的 DStream，基于窗口的批 DStream 来源。
countByWindow ( windowLength , slideInterval ) | 返回一个滑动窗口中的元素计算流。
reduceByWindow ( func, windowLength , slideInterval ) | 返回一个新创建的单个元素流,通过聚合元素流了 滑动时间间隔使用 函数 。 函数应该关联和交换,以便它可以计算 正确地并行执行。
reduceByKeyAndWindow ( func, windowLength , slideInterval , ( numTasks ]) | 当调用DStream(K、V)对,返回一个新的DStream(K、V) 对每个键的值在哪里聚合使用给定的reduce函数 函数 在一个滑动窗口批次。 注意: 默认情况下,它使用引发的默认数量 并行任务(2为本地模式,在集群模式是由配置数量 财产 spark.default.parallelism分组)。 你可以通过一个可选的 numTasks参数设置不同数量的任务。

reduceByKeyAndWindow ( func, invFunc , windowLength ,

slideInterval ,( numTasks ])

上面的reduceByKeyAndWindow()的一个更有效的版本，其中每个窗口的reduce值是使用上一个窗口的reduce值递增计算的。

这是通过减少进入滑动窗口的新数据和“反向减少”离开窗口的旧数据来完成的。 一个例子是在窗口滑动时“添加”和“减去”键的计数。

然而，它仅适用于“可逆缩减函数”，即，具有对应的“逆缩减”函数（作为参数invFunc）的那些缩减函数。

像reduceByKeyAndWindow中一样，reduce任务的数量可通过可选参数进行配置。 请注意，必须启用检查点设置才能使用此操作。

countByValueAndWindow ( windowLength , slideInterval ,[ numTasks ]) | 当调用DStream(K、V)对,返回一个新的DStream(K,长)对的 每个键的值是它的频率在一个滑动窗口。 就像在 reduceByKeyAndWindow，通过一个减少任务的数量是可配置的 可选参数。

Join 操作

最后，Spark streaming 可以很容易与其它的数据源进行 join。

Stream-stream 连接

stream 可以很容易与其他 stream 进行 join

val stream1: DStream[String, String] = ...
val stream2: DStream[String, String] = ...
val joinedStream = stream1.join(stream2)

之类，在每一个批间隔中，生成的抽样 stream1 将与生成的抽样 stream2 进行 join 操作。 也可以做 leftOuterJoin ，rightOuterJoin，fullOuterJoin。此外，它通常是非常有用的做连接的窗口 (window) stream。 这是非常容易的。

val windowedStream1 = stream1.window(Seconds(20))
val windowedStream2 = stream2.window(Minutes(1))
val joinedStream = windowedStream1.join(windowedStream2)

Stream-dataset连接

这已经被证明在早些时候解释  DStream.transform 操作。 这是另一个例子，加入一个有窗口的流数据集。

Scala

val dataset: RDD[String, String] = ...
val windowedStream = stream.window(Seconds(20))...
val joinedStream = windowedStream.transform { rdd => rdd.join(dataset) }

事实上，你也可以动态地改变你想加入的数据集。 提供的函数变换评估每批间隔,因此将使用当前数据集作为参考点。

DStream 转换的完整列表可以在 API 文档。Scala API 看到 DStream 和 PairDStreamFunctions 。Java API 明白了 JavaDStream 和 JavaPairDStream 。Python API 看到 DStream。

DStreams 上的输出操作

原文链接 : http://spark.apache.org/docs/latest/streaming-programming-guide.html#output-operations-on-dstreams

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2886082

贡献者 : 小瑶 漫步 那伊抹微笑 片刻

输出操作允许 DStream 的操作推到如数据库、文件系统等外部系统中。因为输出操作实际上是允许外部系统消费转换后的数据，它们触发的实际操作是 DStream 转换。目前，定义了下面几种输出操作 : 

输出操作 | 含义
print() | 在 DStream 的每个批数据中打印前 10 条元素，这个操作在开发和调试中都非常有用。在 Python API 中调用 pprint()。
saveAsObjectFiles(prefix, [suffix]) | 保存 DStream 的内容为一个序列化的文件 SequenceFile。每一个批间隔的文件的文件名基于 prefix 和 suffix 生成。 "prefix-TIME_IN_MS[.suffix]"，在 Python API 中不可用。
saveAsTextFiles(prefix, [suffix]) | 保存 DStream 的内容为一个文本文件。每一个批间隔的文件的文件名基于 prefix 和 suffix 生成。"prefix-TIME_IN_MS[.suffix]"
saveAsHadoopFiles(prefix, [suffix]) | 保存 DStream 的内容为一个 hadoop 文件。每一个批间隔的文件的文件名基于 prefix 和 suffix 生成。 "prefix-TIME_IN_MS[.suffix]"，在 Python API 中不可用。
foreachRDD(func) | 在从流中生成的每个 RDD 上应用函数 func 的最通用的输出操作。 这个函数应该推送每个 RDD 的数据到外部系统，例如保存 RDD 到文件或者通过网络写到数据库中。 需要注意的是，func 函数在驱动程序中执行，并且通常都有 RDD action 在里面推动 RDD 流的计算。

foreachRDD 设计模式的使用

dstream.foreachRDD 是一个强大的原语，发送数据到外部系统中。然而，明白怎样正确地、有效地用这个原语是非常重要的。下面几点介绍了如何避免一般错误。

经常写数据到外部系统需要建一个连接对象（例如到远程服务器的 TCP 连接），用它发送数据到远程系统。为了达到这个目的，开发人员可能不经意的在 Spark 驱动中创建一个连接对象，但是在 Spark worker 中 尝试调用这个连接对象保存记录到 RDD 中，如下 : 

dstream.foreachRDD { rdd =>
  rdd.foreach { record =>
    val connection = createNewConnection()
    connection.send(record)
    connection.close()
  }
}

这是不正确的，因为这需要先序列化连接对象，然后将它从 driver 发送到 worker 中。这样的连接对象在机器之间不能传送。它可能表现为序列化错误（连接对象不可序列化）或者初始化错误（连接对象应该在 worker 中初始化）等等。正确的解决办法是在 worker 中创建连接对象。

然而，这会造成另外一个常见的错误 - 为每一个记录创建了一个连接对象。例如 : 

dstream.foreachRDD { rdd =>
  rdd.foreachPartition { partitionOfRecords =>
    // ConnectionPool is a static, lazily initialized pool of connections
    val connection = ConnectionPool.getConnection()
    partitionOfRecords.foreach(record => connection.send(record))
    ConnectionPool.returnConnection(connection)  // return to the pool for future reuse
  }
}

需要注意的是，池中的连接对象应该根据需要延迟创建，并且在空闲一段时间后自动超时。这样就获取了最有效的方式发生数据到外部系统。

其它需要注意的地方 : 

输出操作通过懒执行的方式操作 DStream，正如 RDD action 通过懒执行的方式操作 RDD。具体地看，RDD action 和 DStream 输出操作接收数据的处理。因此，如果你的应用程序没有任何输出操作或者 用于输出操作 dstream.foreachRDD()，但是没有任何 RDD action 操作在 dstream.foreachRDD() 里面，那么什么也不会执行。系统仅仅会接收输入，然后丢弃它们。

默认情况下，DStreams 输出操作是分时执行的，它们按照应用程序的定义顺序按序执行。

DataFrame 和 SQL 操作

你可以很容易地使用 DataFrames 和 SQL Streaming 操作数据。 需要使用 SparkContext 或者正在使用的 StreamingContext 创建一个 SparkSession。这样做的目的就是为了使得驱动程序可以在失败之后进行重启。 使用懒加载模式创建单例的 SparkSession 对象。下面的示例所示。在原先的 单词统计 程序的基础上进行修改，使用 DataFrames 和 SQL 生成单词统计。 每个 RDD 转换为 DataFrame，注册为临时表,然后使用 SQL 查询。

/** 流程序中的DataFrame操作 */

val words: DStream[String] = ...

words.foreachRDD { rdd =>

  // 获取单例的SQLContext
  val sqlContext = SQLContext.getOrCreate(rdd.sparkContext)
  import sqlContext.implicits._

  // 将RDD [String]转换为DataFrame
  val wordsDataFrame = rdd.toDF("word")

  // 注册临时表
  wordsDataFrame.registerTempTable("words")

  // 在DataFrame上使用SQL进行字计数并打印它
  val wordCountsDataFrame =
    sqlContext.sql("select word, count(*) as total from words group by word")
  wordCountsDataFrame.show()
}

这里是完整 源代码 。

你可以在使用其他线程读取的流数据上进行 SQL 查询（就是说，可以异步运行 StreamingContext）。 只要确保 StreamingContext 可以缓存一定量的数据来满足查询的需求。 否则 StreamingContext，检测不到任何异步 SQL 查询，在完成查询之前将删除旧的数据。 例如，如果您想查询最后一批，但您的查询可以运行需要 5 分钟,然后调用 streamingContext.remember(Minutes(5))（在 Scala 中，或其他语言）。

请看 DataFrames，Datasets 和 SQL  指南学习更多关于 DataFrames 的知识。

MLlib 操作

原文链接 : http://spark.apache.org/docs/latest/streaming-programming-guide.html#mllib-operations

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2886490

贡献者 : 小瑶 漫步 那伊抹微笑 片刻

你还可以轻松地使用所提供的机器学习算法 MLlib 。 首先这些streaming，（如机器学习算法。 Streaming 线性回归，StreamingKMeans 等）可以同时学习 Streaming 数据的应用模型。 除了这些,对于一个大得多的机器学习算法,可以学习模型离线（即使用历史数据），然后应用该模型在线流媒体数据。访问 MLlib 指南更多细节。

缓存 / 持久化

类似于抽样，DStreams 还允许开发人员持久化 streama €™数据在内存中。 也就是说，在 DStream 上使用 persist() 方法，它会自动把每个抽样持续化到内存中 。 这个非常有用，如果数据多次 DStream（如同样的数据进行多次操作）。 像 reduceByWindow、reduceByKeyAndWindow 和 updateStateByKey 这些都隐式的开启了 “persist()”。 因此，DStreams 生成的窗口操作会自动保存在内存中，如果没有开发人员调用 persist()。

对于通过网络接收数据的输入流（如 Kafka、Flume、Sockets 等），默认的持久性级别被设置为复制两个节点的数据容错。

注意，与抽样不同,默认的序列化数据持久性 DStreams。更多细节在 性能优化 部分查阅。 可以在 Spark 编程指南 中查看不同持久化级别的详情。

CheckPointing

一个 Streaming 应用程序必须全天候运行，所有必须能够解决应用程序逻辑无关的故障（如系统错误，JVM 崩溃等）。为了使这成为可能，Spark Streaming 需要 checkpoint 足够的信息到容错存储系统中， 以使系统从故障中恢复。

Metadata checkpointing : 保存流计算的定义信息到容错存储系统如 HDFS 中。这用来恢复应用程序中运行 worker 的节点的故障。元数据包括 : 

Configuration : 创建 Spark Streaming 应用程序的配置信息。

DStream operations : 定义 Streaming 应用程序的操作集合。

Incomplete batches : 操作存在队列中的未完成的批。

Data checkpointing : 保存生成的 RDD 到可靠的存储系统中，这在有状态 transformation（如结合跨多个批次的数据）中是必须的。在这样一个 transformation 中，生成的 RDD 依赖于之前批的 RDD，随着时间的推移，这个依赖链的长度会持续增长。在恢复的过程中，为了避免这种无限增长。有状态的 transformation 的中间 RDD 将会定时地存储到可靠存储系统中，以截断这个依赖链。

元数据 checkpoint 主要是为了从 driver 故障中恢复数据。如果 transformation 操作被用到了，数据 checkpoint 即使在简单的操作中都是必须的。

何时启用 CheckPointing

应用程序在下面两种情况下必须开启 checkpoint : 

使用有状态的 ransformation。如果在应用程序中用到了updateStateByKey 或者 reduceByKeyAndWindow，checkpoint 目录必需提供用以定期 checkpoint RDD。

从运行应用程序的 driver 的故障中恢复过来。使用元数据 checkpoint 恢复处理信息。

注意，没有前述的有状态的 transformation 的简单流应用程序在运行时可以不开启 checkpoint。在这种情况下，从 driver 故障的恢复将是部分恢复（接收到了但是还没有处理的数据将会丢失）。 这通常是可以接受的，许多运行的 Spark Streaming 应用程序都是这种方式。

如何配置 CheckPointing

在容错、可靠的文件系统（HDFS、s3 等）中设置一个目录用于保存 checkpoint 信息。这可以通过 streamingContext.checkpoint(checkpointDirectory) 方法来做。这运行你用之前介绍的 有状态 transformation。另外，如果你想从 driver 故障中恢复，你应该以下面的方式重写你的Streaming 应用程序。

当应用程序是第一次启动，新建一个 StreamingContext，启动所有 Stream，然后调用 start() 方法。

当应用程序因为故障重新启动，它将会从 checkpoint 目录 checkpoint 数据重新创建 StreamingContext。

这种配置的方式很简单，通过使用 StreamingContext.getOrCreate 即可，如下所示 : 

// Function to create and setup a new StreamingContext
def functionToCreateContext(): StreamingContext = {
  val ssc = new StreamingContext(...)   // new context
  val lines = ssc.socketTextStream(...) // create DStreams
  ...
  ssc.checkpoint(checkpointDirectory)   // set checkpoint directory
  ssc
}

// Get StreamingContext from checkpoint data or create a new one
val context = StreamingContext.getOrCreate(checkpointDirectory, functionToCreateContext _)

// Do additional setup on context that needs to be done,
// irrespective of whether it is being started or restarted
context. ...

// Start the context
context.start()
context.awaitTermination()

如果 checkpointDirectory 存在，上下文将会利用 checkpoint 数据重新创建。如果这个目录不存在，将会调用 functionToCreateContext 函数创建一个新的上下文，建立 DStream。 请看RecoverableNetworkWordCount 例子。

除了使用 getOrCreate，开发者必须保证在故障发生时，driver 处理自动重启。只能通过部署运行应用程序的基础设施来达到该目的。在部署章节将有更进一步的讨论。

注意，RDD 的 checkpointing 有存储成本。这会导致批数据（包含的 RDD 被 checkpoint）的处理时间增加。因此，需要小心的设置批处理的时间间隔。在最小的批容量（包含 1 秒的数据）情况下，checkpoint 每批数据会显著的减少 操作的吞吐量。相反，checkpointing 太少会导致谱系以及任务大小增大，这会产生有害的影响。因为有状态的 transformation 需要 RDD checkpoint。默认的间隔时间是批间隔时间的倍数，最少 10 秒。它可以通过 dstream.checkpoint 来设置。典型的情况下，设置 checkpoint 间隔是 DStream 的滑动间隔的 5-10 大小是一个好的尝试。

累加器和广播变量

原文链接 : http://spark.apache.org/docs/latest/streaming-programming-guide.html#accumulators-broadcast-variables-and-checkpoints

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2886483

贡献者 : 小瑶 漫步 那伊抹微笑 片刻

在Spark Streaming中，无法从checkpoint恢复累加器和广播变量。如果启用检查点并使用累加器 或广播变量 ，您必须为累加器 和广播变量创建延迟实例化的单例实例，以便在驱动程序重新启动失败后重新实例化。这在下面的示例中显示。

object WordBlacklist {

  @volatile private var instance: Broadcast[Seq[String]] = null

  def getInstance(sc: SparkContext): Broadcast[Seq[String]] = {
    if (instance == null) {
      synchronized {
        if (instance == null) {
          val wordBlacklist = Seq("a", "b", "c")
          instance = sc.broadcast(wordBlacklist)
        }
      }
    }
    instance
  }
}

object DroppedWordsCounter {

  @volatile private var instance: LongAccumulator = null

  def getInstance(sc: SparkContext): LongAccumulator = {
    if (instance == null) {
      synchronized {
        if (instance == null) {
          instance = sc.longAccumulator("WordsInBlacklistCounter")
        }
      }
    }
    instance
  }
}

wordCounts.foreachRDD { (rdd: RDD[(String, Int)], time: Time) =>
  // Get or register the blacklist Broadcast
  val blacklist = WordBlacklist.getInstance(rdd.sparkContext)
  // Get or register the droppedWordsCounter Accumulator
  val droppedWordsCounter = DroppedWordsCounter.getInstance(rdd.sparkContext)
  // Use blacklist to drop words and use droppedWordsCounter to count them
  val counts = rdd.filter { case (word, count) =>
    if (blacklist.value.contains(word)) {
      droppedWordsCounter.add(count)
      false
    } else {
      true
    }
  }.collect().mkString("[", ", ", "]")
  val output = "Counts at time " + time + " " + counts
})

完整代码（源代码）。

应用程序部署

原文链接 : http://spark.apache.org/docs/latest/streaming-programming-guide.html#deploying-applications

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2886597

贡献者 : 小瑶 漫步 那伊抹微笑 片刻

本节讨论部署 Spark Streaming 应用程序的步骤。

要求

要运行一个 Spark Streaming 应用，你需要有以下几点。

有管理器的集群 - 这是任何 Spark 应用程序都需要的需求，详见 部署指南。

将应用程序打为 jar 包 - 你必须编译你的应用程序为 jar 包。如果你用 spark-submit 启动应用程序，你不需要将 Spark 和 Spark Streaming 打包进这个 jar 包。 如果你的应用程序用到了高级源（如 kafka，flume），你需要将它们关联的外部 artifact 以及它们的依赖打包进需要部署的应用程序jar包中。例如，一个应用程序用到了TwitterUtils，那么就需要将 spark-streaming-twitter_2.10 以及它的所有依赖打包到应用程序 jar 中。

为 executors 配置足够的内存-因为接收的数据必须存储在内存中，executors 必须配置足够的内存用来保存接收的数据。注意，如果你正在做 10 分钟的窗口操作，系统的内存要至少能保存 10 分钟的数据。所以，应用程序的内存需求依赖于使用 它的操作。

配置 checkpointing - 如果 stream 应用程序需要 checkpointing，然后一个与 Hadoop API 兼容的容错存储目录必须配置为检查点的目录，流应用程序将 checkpoint 信息写入该目录用于错误恢复。

配置应用程序 driver 的自动重启 - 为了自动从 driver 故障中恢复，运行流应用程序的部署设施必须能监控 driver 进程，如果失败了能够重启它。不同的集群管理器，有不同的工具得到该功能

SparkStandalone : 一个 Spark 应用程序 driver 可以提交到 Spark 独立集群运行，也就是说 driver 运行在一个 worker 节点上。进一步来看，独立的集群管理器能够被指示用来监控 driver，并且在 driver 失败（或者是由于非零的退出代码如 exit(1)， 或者由于运行 driver 的节点的故障）的情况下重启 driver。

YARN : YARN 为自动重启应用程序提供了类似的机制。

Mesos : Mesos 可以用Marathon提供该功能

配置 write ahead logs - 在 Spark 1.2 中，为了获得极强的容错保证，我们引入了一个新的实验性的特性-预写日志（write ahead logs）。如果该特性开启，从 receiver 获取的所有数据会将预写日志写入配置的 checkpoint 目录。 这可以防止 driver 故障丢失数据，从而保证零数据丢失。这个功能可以通过设置配置参数 spark.streaming.receiver.writeAheadLogs.enable 为 true 来开启。然而，这些较强的语义可能以 receiver 的接收吞吐量为代价。这可以通过 并行运行多个 receiver 增加吞吐量来解决。另外，当预写日志开启时，Spark 中的复制数据的功能推荐不用，因为该日志已经存储在了一个副本在存储系统中。可以通过设置输入 DStream 的存储级别为 StorageLevel.MEMORY_AND_DISK_SER 获得该功能。

升级应用程序代码

如果运行的 Spark Streaming应用 程序需要升级，有两种可能的方法。

启动升级的应用程序，使其与未升级的应用程序并行运行。一旦新的程序（与就程序接收相同的数据）已经准备就绪，旧的应用程序就可以关闭。这种方法支持将数据发送到两个不同的目的地（新程序一个，旧程序一个）

首先，平滑的关闭（StreamingContext.stop(...) 或 JavaStreamingContext.stop(...)）现有的应用程序。在关闭之前，要保证已经接收的数据完全处理完。然后，就可以启动升级的应用程序，升级的应用程序会接着旧应用程序的点开始处理。这种方法仅支持具有源端缓存功能的输入源（如 flume，kafka），这是因为当旧的应用程序已经关闭，升级的应用程序还没有启动的时候，数据需要被缓存。

监控应用程序

除了 Spark 自己的 监控功能 之外，针对 Spark Streaming 它也有一些其他的功能。当使用 StreamingContext 时，Spark Web UI 显示了一个额外的 Streaming 标签，它显示了有关正在运行的 Receivers（接收器）（是否 Receivers 处于活动状态，记录接受数量，接收器误差，等等）和已完成的 Batches（批处理时间，查询延迟，等等）的统计信息。这可以用于监控 Spark 应用程序的进度。

在 Web UI 中以下两个指标特别重要 : 

Processing Time（处理时间）- 用来处理每批数据的时间。

Scheduling Delay（调度延迟）- 一批数据在队列中等待，直到上一批数据处理完成所需的时间。

如果批处理时间始终大于批的间隔 并且（或者）队列延迟不断增加，那么说明系统不能尽可能快的处理 Batches，它们（Batches）正在被生成并且落后（处理），在这种情况下，可以考虑 降低 批处理时间。

Spark Streaming 应用程序处理的进度，也可以使用 StreamingListener 接口监控，它允许你获取 Receiver 的状态以及批处理时间。注意，这是一个开发者 API 并且在将来它很可能被改进（即上报更多的信息）。

性能优化

降低批处理的时间

设置合理的批处理间隔

内存优化

为了在集群中获得 Spark Streaming 应用程序的最佳性能需要一些优化。这部分解释了一部分能够调整用来提升您应用程序性能的参数和配置。在一个较高的水平上，您需要考虑两件事情 : 

通过有效的利用群集资源来减少每批数据的处理时间。

设置正确的 Batch 大小，这样的话当它们被接受时 Batch 数据能够被尽量快的处理（换言之，数据处理能够赶得上数据摄取）。

降低批处理的时间

有许多优化可以在 Spark 中来完成，使每批数据的处理时间最小化。这些都在 优化指南 中详细讨论。本章介绍一些最重要的问题。

数据接收的并行级别

通过网络（像 Kafka，Flume，socket，等等）接受数据需要数据反序列化然后存在 Spark 中。如果数据接收成为了系统中的瓶颈，则需要考虑并行的数据接收。注意，每个 Input DStream（输入流）创建一个接受单个数据流的单独的 Receiver（接收器）（运行在一个 Worker 机器上）。接受多个数据流因此可以通过创建多个 Input DStreams 以及配置他们去从数据源（S）的不同分区接收数据流来实现。例如，一个单一的 Kafka Input DStream 接收两个 Topic（主题） 的数据能够被拆分成两个 Kafka 输入流，每个仅接收一个 Topic。这将运行两个 Receiver（接收器），使得数据可以被并行接受，因此将提高整体吞吐量。这些多个 DStream 可以合并在一起以创建一个单独的 DStream。然后被用于在一个单独的 Input DStream 上的 Transformations（转换）可以在统一的流上被应用。按照以下步骤进行 : 

val numStreams = 5
val kafkaStreams = (1 to numStreams).map { i => KafkaUtils.createStream(...) }
val unifiedStream = streamingContext.union(kafkaStreams)
unifiedStream.print()

应当考虑的另一个参数是 Receiver（接收器）的阻塞间隔，它通 过配置参数 spark.streaming.blockInterval 来决定。对于大部分 Receiver（接收器） 来说，在存储到 Spark 的 Memory（内存）之前时接收的数据被合并成数据 Block（块）。每批 Block（块）的数量确定了将用于处理在一个类似 Map Transformation（转换）的接收数据的任务数量。每个 Receiver（接收器）的每个 Batch 的任务数量大约为（Batch 间隔 / Block 间隔）。例如，200ms 的 Block（块）间隔和每 2s 的 Batch 将创建 10 个任务。如果任务的数量过低（即，小于每台机器的 Core（CPU）数量），那么效率会很低，因为所有可用的 Core（CPU）将不会用来处理数据。以增加给定的 Batch 间隔的任务数量，降低该 Block（间隔）。然而，Block（块）间隔推荐的最低值约为 50ms，低于该推荐值的任务的运行开销可能是一个问题。

与多个 Input Streams / Receivers 接受数据的另一种方法是明确的 Repartition （重分区）输入的数据流（使用 inputStream.repartition（<partition（分区）的数量>））。这样在进一步处理之前就分发接收数据的 Batch 到群集中指定数量的机器上去。

数据处理的并行级别

集群资源的利用率可能会很低，如果在任何计算阶段中并行任务数量的不是很多的话。例如，对于像 reduceByKey 和 reduceByKeyAndWindow 这样的分布式 Reduce 操作来说，默认的并行任务数量由 spark.default.parallelism 配置属性 控制。您可以传递并行的的参数（请看 PairDStreamFunctions 文档），或者设置 spark.default.parallelism 配置属性 来改变默认值。

数据序列化

可以通过调整序列化的格式来减少数据序列化的开销。在流式传输的情况下，有两种数据类型会被序列化。

Input Data（输入的数据）: 默认情况下，通过 Receivers（接收器）接收的输入数据被存储在 Executor 的内存与 StorageLevel.MEMORY_AND_DISK_SER_2 中。也就是说，数据被序列化成 Bytes（字节）以降低 GC 开销，以及被复制用于 Executor 的失败容错。此外，数据首先保存在 Memory（内存）中，如果内存不足已容纳所有用于流计算的输入数据将被溢出到硬盘上。这个序列化操作显然也需要开销 - Receiver（接收器）必须反序列化接收到的数据并且使用 Spark 的序列化格式重新序列化它们。

通过 Streaming 操作产生的持久的 RDDs : 通过流计算产生的 RDDs 可能被持久化在内存中。例如，Window（窗口）操作将数据持久化在内存中，因为他们可能被多次处理。然而，不像 Spark Core 默认的 StorageLevel.MEMORY_ONLY，通过流计产生的持久化 RDDs 被使用 StorageLevel.MEMORY_ONLY_SER（也就是序列化）存储，默认 GC 开销降至最低。

在这两种情况下，使用 Kryo 序列化能够减少 CPU 和 内存的开销。更多细节请看 Spark 优化指南。对于 Kyro 来说，考虑注册自定义的 Class，并且禁用 Object（对象）引用跟踪（在 配置指南 中看 Kyro 相关的配置）。

在特定的情况下，需要用于保留 Streaming 应用程序的数据量不是很大，这样也许是可行的，来保存反序列化的数据（两种类型）不用引起过度的 GC 开销。例如，如果您使用 Batch 的间隔有几秒钟并且没有 Window（窗口）操作，然后你可以通过显式地设置存储相应的级别来尝试禁用序列化保存数据。这将减少由于序列化的 CPU 开销，可能不需要太多的 GC 开销就能提升性能。

任务启动开销

如果每秒任务启动的数据很高（比如，每秒 50 个任务或者更多），那么发送 Task（任务） 到 Slave 的负载可能很大，将很难实现 亚秒级 延迟。可以通过下面的改变来降低负载 : 

Execution Mode（运行模式）: 以 Standlone 模式或者 coarse-grained（粗粒度的）Mesos 模式运行 Spark 会比 fine-grained（细粒度的）Mesos 模式运行 Spark 获得更佳的任务启动时间。更详细的信息请参考 Mesos 运行指南。

这些改变也许能减少批处理的时间（100s of milliseconds），因此亚秒级的 Batch 大小是可行的。

设置合理的批处理间隔

对于群集上运行的 Spark Streaming 应用程序来说应该是稳定的，系统应该尽可能快的处理接收到的数据。换句话说，批数据在生成后应该尽快被处理。对于应用程序来说无论这个是不是真的都可以通过在 Streaming Web UI 找到 监控 的处理时间，批处理时间应该小于批间隔。

取决于流计算的性质，所用的批间隔可能在通过群集中一组固定资源上的应用程序持续的数据速率有显著的影响。例如，让我们考虑下更早的 WordCountNetwork 例子。对于一个特定的数据速率。系统可能能够保持每 2 秒报告一次单词统计（即，批间隔是 2 秒），而不是每 500 毫秒一次。因此批间隔需要被设置，使得线上的预期数据速率可持续。

要为您的应用程序找出一个合理的批大小是去使用一个保守的批间隔（例如，5~10 秒）和一个较低的数据速率来测试它。为了验证系统是否能够保持数据速率，您可以通过每个被处理的 Batch 来检查端到端的延迟情况（或者在 Spark Driver log4j 日志中看 “Total delay”，或者使用 StreamingListener 接口）。如果延迟与批大小相比较处于一个稳定的状态，那么系统是稳定的。否则，如果延迟继续增加，它意味着系统不能保持下去，因此它是不稳定的。一旦你有了一个稳定的配置，你可以去试着增加数据速率 和/或者  降低批大小。注意一个短暂的延迟增加是由于临时的数据速率增加可能会变好，只要延迟降低到一个比较低的值。（即，小于批大小）。

内存优化

调优 Spark 应用程序的内存使用情况和 GC 行为已经在 优化指南 中详细讨论了。强烈推荐您阅读它。在这一章，我们讨论在 Spark Streaming 应用程序 Context 中指定的一些优化参数。

Spark Streaming 应用程序在群集中需要的 Memory（内存） 数量取决于使用的 Transformations（转换）上的类型行为。例如，如果您想要在最近 10 分钟的时候上使用 Window（窗口）函数，那么您的群集应有有足够的 Memory 以保存 10 分钟值的数据在内存中。或者您想要在大量的 keys 中使用 updateStateByKey，那么所需要的内存将会更高。与之相反，如果您想要去做一个简单的 map-filter-store 操作，那么所需的内存将会更少。

在一般情况下，从数据通过 Receiver（接收器）被接收时起使用 StorageLevel.MEMORY_AND_DISK_SER_2 存储，数据在内存中放不下时将拆分到硬盘中去。这样也许降低了 Streaming 应用程序的性能，因此建议为您的 Streaming 应用程序提供足够的内存。它最好去试一试，并且在小范围看看 Memory（内存）使用情况然后估算相应的值。

内存调优的另一个方面是垃圾收集。Streaming 应用程序需要低延迟，JVM 垃圾回收造成的大量暂停是不可取的。

这里有一些能够帮助你调整内存使用情况以及 GC 开销的参数 : 

Persistence Level of DStreams（DStream 的持久化级别）: 像前面所提到的 数据序列化 部分，输入的数据和 RDDs 默认持久化为序列化的字节。和反序列化持久性相比，这样减少了内存使用和 GC 开销。启用 Kryo 序列化进一步减少了序列化大小和内存使用。进一步减少内存使用可以用压缩实现（请看 Spark 配置 spark.rdd.compress），付出的是 CPU 时间。

Clearing old data（清除旧数据）: 默认情况下，所有的输入数据和通过 DStream transformation（转换）产生的持久化 RDDs 将被自动的清除。Spark Streaming 决定何时清除基于使用 transformation（转换）的数据。例如，如果你使用一个 10 分钟的 Window（窗口）操作，那么 Spark Streaming 将保存最近 10 分钟的数据，并主动扔掉旧的数据。数据也能够通过设置 streamingContext.remeber 保持更久（例如，交互式查询旧数据）。

CMS Garbage Collector（CMS 垃圾回收器）: 使用并发的 mark-sweep GC 是强烈推荐的用于保持 GC 相关的暂停更低。即使知道并发的 GC 降低了整个系统处理的吞吐量。仍然建议使用，以获得更一致的批处理时间。确定你在 Driver（在 spark-submit 中使用 --driver-java-options）和 Executor（使用 Spark 配置 spark.executor.extraJavaOptions）上设置的 CMS GC。

Other tips（其它建议）: 为了进一步降低 GC 开销，这里有些更多的建议可以尝试。

持久化 RDDs 使用 OFF_HEAP 存储界别。更多详情请看 Spark 编程指南

使用更多的 Executor 和更小的 heap size（堆大小）。这将在每个 JVM heap 内降低 GC 压力。

应该记住的要点

一个 DStream 和一个单独的 Receiver（接收器）关联。为了达到并行的读取多个 Receiver（接收器）。例如，多个 DStreams 需要被创建。一个 Receiver 运行在一个 Executor 内。它占有一个 Core（CPU）。确保在 Receiver Slot 被预定后有足够的 Core 。例如，spark.cores.max 应该考虑 Receiver Slot。Receiver 以循环的方式被分配到 Executor。

当数据从一个 Stream 源被接收时，Receiver（接收器） 创建了数据块。每个块间隔的毫秒内产生一个新的数据块。N 个数据块在批间隔（N = 批间隔 / 块间隔）的时候被创建。这些 Block（块）通过当前的 Executor 的 BlockManager 发布到其它 Executor 的 BlockManager。在那之后，运行在 Driver 上的  Network Input Tracker 获取 Block 位置用于进一步处理。

一个 RDD 创建在 Driver 上，因为 Block 创建在 batchInterval（批间隔）期间。Block 在 batchInterval 划分成 RDD 时生成。每个分区是 Spark 中的一个任务。blockInterval == batchInterval 将意味着那是一个单独的分区被创建并且可能它在本地被处理过了。

Block 上的 Map 任务在 Executor 中被处理（一个接收 Block，另一个 Block 被复制）无论块的间隔，除非非本地调度死亡。有更大的块间隔意味着更大的块。在本地节点上一个高的值 spark.locality.wait 增加处理 Blcok 的机会。需要发现一个平衡在这两个参数来确保更大的块被本都处理之间。

而不是依靠 batchInterval 和 blockInterval，你可以通过调用 inputDstream.repartition(n) 来定义分区的数量。这样会 reshuffles RDD 中的数据随机来创建 N 个分区。是的，为了更好的并行，虽然增加了 shuffle 的成本。一个 RDD 的处理通过 Driver 的 JobScheduler 作为一个 Job 来调度。在给定的时间点仅有一个 Job 是活跃的。所以，如果一个 Job 正在执行那么其它的 Job 会排队。

如果你有两个 DStream，那将有两个 RDDs 形成并且将有两个 Job 被创建，他们将被一个一个的调度。为了避免这个，你可以合并两个 DStream。这将确保两个 RDD 的 DStream 形成一个单独的 unionRDD。这个 unionRDD 被作为一个单独的 Job 考虑。然而分区的 RDDs 不受影响。

如果批处理时间超过了批间隔，那么显然 Receiver（接收器）的内存将开始填满，最走将抛出异常（最可能的是 BlockNotFoundException）。当前没有方法去暂停 Receiver。使用 SparkConf 配置  spark.streaming.receiver.maxRate，Receiver（接收器）的速率可以被限制。

容错语义

在这部分，我们将讨论 Spark Streaming 应用程序在发生故障时的行为。

背景

为了理解 Spark Streaming 提供的语义，让我们记住 Spark RDDs 最基本的容错语义。

RDD 是不可变的，确定重新计算的，分布式的 DataSet（数据集）。

如果任何分区的 RDD 由于 Worker 节点失败而丢失，那么这个分区能够从原来原始容错的 DataSet（数据集）使用操作的 lineage（血统）关系来重新计算。

假设所有的 RDD 转换是确定的，在最终转换的 RDD 中的数据总是相同的，无论 Spark 群集中的失败。

Spark 在数据上的操作像  HDFS 或者 S3 这样容错的文件系统一样。因此，所有从容错的数据中产生的 RDDs 也是容错的。然而，这种情况在 Spark Streaming 中不适用，因为在大多数情况下数据通过网络被接收（除非使用 fileStream）。为了对所有产生的 RDDs 实现相同的容错语义属性，接收的数据被复制到群集中 Worker 节点的多个 Spark Executor 之间（默认复制因子是 2）。这将会造成需要在发生故障时去恢复两种文件系统的数据类型 : 

Data received and replicated（数据接收并且被复制）- 这份数据幸存于一个单独的 Worker 节点故障中因为其它的节点复制了它。

Data received but buffered for replication（数据接收但是缓冲了副本）- 因为这份数据没有被复制，唯一恢复这份数据的方式是从 Source（源/数据源）再次获取它。

此外，我们应该关注两种类型的故障 : 

Failure of a Worker Node（Worker 节点的故障）- 任何运行 Executor 的 Worker 节点都是可以故障，并且这些节点所有在 Memory（内存）中的数据将会丢失。如果任何 Receiver（接收器）运行在故障的节点，那么他们缓存的数据将丢失。

Failure of the Driver Node（Driver 节点的故障）- 如果 Driver 节点运行的 Spark Streaming 应用程序发生故障，那么很显然 SparkContext 将会丢失，所有 Executor 与它们在内存中的数据都会丢失。

与这个基础知识一起，让我们理解 Spark Streaming 的容错语义。

定义

Streaming 处理系统经常会捕获系统异常并记录执行次数以保障系统容错，其中在这三种条件下可以保障服务，等等。

最多一次 : 每个记录将被处理一次或者根本不处理。

至少一次 : 每个记录将被处理一次或多次。这主要是在最后一次次，因为它确保数据不会丢失。但也有可能是重复处理的。

只有一次 : 每个记录将被处理一次，确保这一次数据完整。这显然是三者的最强保障。

基础语义

在任何流处理系统，从广义上讲，处理数据有三个步骤。

接收数据 : 接受数据或者从其他数据源接受数据。

转换数据 : 所接收的数据使用 DStream 和 RDD 变换转化。

输出数据 : 最后变换的数据被推送到外部系统，如文件系统，数据库，DashBoard 等。

如果一个 Stream 应用程序来实现端到端的数据传输，则每个环节都需要一次性完整保障。也就是说，每个记录都必须被接收正好一次，恰好转化一次，被推到下游系统一次。让我们了解在 Spark Streaming 的情况下这些步骤的语义。

接收数据 : 不同的输入源提供不同的容错。详细过程在下一小节中讨论。

转换数据 : 已经接收将被处理一次的所有数据，这要依靠于 RDDs 提供保证。即使有故障，只要接收到的输入数据是可读的，最终通过 RDDs 的转化将始终具有相同的内容。

输出数据 : 定义了数据至少一次输出，因为它依赖于输出数据操作类型（是否支持转换）和定义的下游系统（是否支持事务）。但是，用户可以实现自己的传输机制，以实现只要一次定义。会在后面小节里有更详细的讨论。

接收数据的语义

不同的输入源提供不同的保障，从至少一次到正好一次。阅读更多的细节。

关于文件

如果所有的输入数据已经存在如 HDFS 的容错文件系统，Spark Streaming 总是可以从任何故障中恢复所有数据。这种定义，在一次处理后就能恢复。

关于基于 Receiver（接收器） 的 Source（源）

对于基于 receiver 的输入源，容错的语义既依赖于故障的情形也依赖于 receiver 的类型。正如之前讨论的，有两种类型的 receiver : 

Reliable Receiver（可靠的接收器）: 这些 receivers 只有在确保数据复制之后才会告知可靠源。如果这样一个 receiver 失败了，缓冲（非复制）数据不会被源所承认。如果 receiver 重启，源会重发数 据，因此不会丢失数据。

Unreliable Receiver（不可靠的接收器）: 当 worker 或者 driver 节点故障，这种 receiver 会丢失数据。

选择哪种类型的 receiver 依赖于这些语义。如果一个 worker 节点出现故障，Reliable Receiver 不会丢失数据，Unreliable Receiver 会丢失接收了但是没有复制的数据。如果 driver 节点出现故障，除了以上情况下的数据丢失，所有过去接收并复制到内存中的数据都会丢失，这会影响有状态 transformation 的结果。

为了避免丢失过去接收的数据，Spark 1.2 引入了一个实验性的特征（预写日志机制）write ahead logs，它保存接收的数据到容错存储系统中。有了 write ahead logs 和 Reliable Receiver，我们可以做到零数据丢失以及 exactly-once 语义。

下表总结了根据故障的语义 : 

Deployment Scenario | Worker Failure | Driver Failure
Spark 1.1 或者更早, 没有 write ahead log 的 Spark 1.2 | 在 Unreliable Receiver 情况下缓冲数据丢失；在 Reliable Receiver 和文件的情况下，零数据丢失 | 在 Unreliable Receiver 情况下缓冲数据丢失；在所有 receiver 情况下，过去的数据丢失；在文件的情况下，零数据丢失
带有 write ahead log 的 Spark 1.2 | 在 Reliable Receiver 和文件的情况下，零数据丢失 | 在 Reliable Receiver 和文件的情况下，零数据丢失

Kafka Direct API

在 Spark 1.3 中，我们引入了一个新的 kafka Direct 的 API，它可以保证所有 kafka 数据由 spark stream 收到一次。如果实现仅一次输出操作，就可以实现保证终端到终端的一次。这种方法（版本 Spark2.0.2）中进一步讨论 kafka集成指南。

输出操作的语义

输出操作（例如 foreachRDD）至少被定义一次，即变换后的数据在一次人工故障的情况下可能会不止一次写入外部实体。虽然可以通过操作 saveAs***Files 保存文件到系统上（具有相同的数据将简单地被覆盖），额外的尝试可能是必要的，以实现一次准确的语义。有两种方法。

幂等更新 : 多次尝试写相同的数据。例如，saveAs***Files 总是写入相同数据生成的文件。

事务更新 : 所有更新事务作出这样的更新是恰好遵循原子性。这样做的方法如下：

使用批处理时间创建一个标识符（foreachRDD）和 RDD 的分区索引。给这个应用定义唯一标识符标识 blob 数据。

更新外部系统与当前事务（即原子性），如果已经被标识过得数据应用已经存在，那么就跳过更新。

dstream.foreachRDD { (rdd, time) =>
  rdd.foreachPartition { partitionIterator =>
    val partitionId = TaskContext.get.partitionId()
    val uniqueId = generateUniqueId(time.milliseconds, partitionId)
    // use this uniqueId to transactionally commit the data in partitionIterator
  }
}

迁移指南（从 0.9.1 或者更低版本至 1.x 版本）

在 Spark 0.9.1 和 Spark 1.0 之间，它们有一些 API 的改变以确保未来 API 的稳定性。这一章阐述需要迁移您已经在的代码到 1.0 版本的步骤。

Input DSteams（输入流）: 所有创建 Input Steam（输入流）的操作（例如，StreamingContext.socketStream，FlumeUtils.createStream，等等。）现在返回 InputDStream / ReceiverInputDStream (而不是 DStream) 的 Scala 版本，和 JavaInputDStream / JavaPairInputDStream /JavaReceiverInputDStream / JavaPairReceiverInputDStream（而不是 JavaDStream) 的 Java 版。这将确保特定输入流的功能可以添加到这些类中在未来不需要破坏二进制兼容性。注意您已经存在的 Spark Streaming 应用程序应该不需要任何改变（因为那些类是 DStream / JavaDStream 的子类），但可能需要使用 Spark 1.0 重新编译。

Custom Network Receivers（自定义网络接收器）: 从发布 Spark Streaming 以来，自定义网络接收器能够在 Scala 使用 NetworkReceiver 类自定义，该 API 在错误处理和报告中被限制了，并且不能够被 Java 使用。从 Spark 1.0 开始，自个类被 Receiver 替换了并且带来了下面的好处。

像 stop 和 restart 这样的方法已经被添加以更好的控制 Receiver（接收器）的生命周期。更多详细信息请看 自定义 Receiver 指南。

自定义 Receiver 能够使用 Scala 和 Java 实现。

为了迁移你已存在的自定义 Receiver，从更早的 NetworkReceiver 到新的 Receiver。您需要去做如下事情。

让您自定义的 Receiver 继承 org.apache.spark.streaming.receiver.Receiver 而不是 org.apache.spark.streaming.dstream.NetworkReceiver。

之前，一个 BlockGenerator 对象必须去通过自定义 Receiver 创建，它接收的数据被添加然后存在 Spark 中。它必须显示的从 onStart() 和 stop() 方法来启动和停止。新的 Receiver 类让这个是不必要的因为它添加了一组名为 store（<data>）的方法，能够在 Spark 中被调用以存储数据。所以，为了迁移你自定义的网络接收器，删除任何的 BlockGenerator 对象（在 Spark 1.0 中再也不存在），并且在接收数据时使用 store(...)方法。

Actor-base Receivers（基于 Actor 的接收器）: 这个基于 Actor 的接收器 API 已经被移到 DStream Akka 中去了。更多详细信息请参考该工程。

快速跳转

其它的指南

Kafka 集成指南

Kinesis 集成指南

自定义 Receiver 指南

外部 DStream 数据源

DStream MQTT

DStream Twitter

DStream Akka

DStream ZeroMQ

API 文档

Scala 文档

StreamingContext and DStream

KafkaUtils, FlumeUtils, KinesisUtils,

Java 文档

JavaStreamingContext, JavaDStream and JavaPairDStream

KafkaUtils, FlumeUtils, KinesisUtils

Python 文档

StreamingContext and DStream

KafkaUtils

Scala，Java 和 Python 中的更多示例。

描述 Spark Streaming 的 书籍 和 视频。

Kafka 集成指南

原文链接 : http://spark.apache.org/docs/2.1.0/streaming-kafka-integration.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=4882571

贡献者 : 胖哥， Roman，ApacheCN，Apache中文网

Apache Kafka is publish-subscribe messaging rethought as a distributed, partitioned, replicated commit log service. Please read the Kafka documentation thoroughly before starting an integration using Spark.

The Kafka project introduced a new consumer api between versions 0.8 and 0.10, so there are 2 separate corresponding Spark Streaming packages available. Please choose the correct package for your brokers and desired features; note that the 0.8 integration is compatible with later 0.9 and 0.10 brokers, but the 0.10 integration is not compatible with earlier brokers.

| spark-streaming-kafka-0-8 | spark-streaming-kafka-0-10
Broker Version | 0.8.2.1 or higher | 0.10.0 or higher
Api Stability | Stable | Experimental
Language Support | Scala, Java, Python | Scala, Java
Receiver DStream | Yes | No
Direct DStream | Yes | Yes
SSL / TLS Support | No | Yes
Offset Commit Api | No | Yes
Dynamic Topic Subscription | No | Yes

DataFrames，Datasets 和 SQL

原文链接 : http://spark.apache.org/docs/latest/sql-programming-guide.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2883736

贡献者 : 未知用户 (wulei)，风轻云淡，ping，Tiny_sun，geekidentity，那伊抹微笑

概述

Spark SQL 是 Spark 处理结构化数据的一个模块。与基础的 Spark RDD API 不同，Spark SQL 提供了查询结构化数据及计算结果等信息的接口。在内部，Spark SQL 使用这个额外的信息去执行额外的优化。有几种方式可以跟 Spark SQL 进行交互，包括 SQL 和 Dataset API。当使用相同执行引擎进行计算时，无论使用哪种 API / 语言都可以快速的计算。这种统一意味着开发人员能够在基于提供最自然的方式来表达一个给定的 transformation API 之间实现轻松的来回切换不同的 。

该页面所有例子使用的示例数据都包含在 Spark 的发布中，并且可以使用 spark-shell，pyspark，或者 sparkR 来运行。

SQL 

Spark SQL 的功能之一是执行 SQL 查询。Spark SQL 也能够被用于从已存在的 Hive 环境中读取数据。更多关于如何配置这个特性的信息，请参考 Hive 表 这部分。当以另外的编程语言运行 SQL 时，查询结果将以 Dataset / DataFrame 的形式返回。您也可以使用 命令行 或者通过 JDBC / ODBC 与 SQL 接口交互。

Datasets 和 DataFrames

一个 Dataset 是一个分布式的数据集合。Dataset 是在 Spark 1.6 中被添加的新接口，它提供了 RDD 的优点（强类型化，能够使用强大的 lambda 函数）与 Spark SQL 优化的执行引擎的好处。一个 Dataset 可以从 JVM 对象来构造并且使用转换功能（map，flatMap，filter，等等）。Dataset API 在 Scala 和 Java 中是可用的。Python 不支持 Dataset API。但是由于 Python 的动态特性，许多 Dataset API 的有点已经可用了（也就是说，你可能通过 name 天生的 row.columnName 属性访问一行中的字段）。这种情况和 R 相似。

一个 DataFrame 是一个 Dataset 组织成的指定列。它的概念与一个在关系型数据库或者在 R/Python 中的表是相等的，但是有更多的优化。DataFrame 可以从大量的 Source 中构造出来，像 : 结构化的数据文件，Hive 中的表，外部的数据库，或者已存在的 RDD。DataFrame API 在 Scala，Java，Python 和 R 中是可用的。在 Scala 和 Java 中，一个 DataFrame 所代表的是一个多个 Row（行）的 Dataset。在 Scala API 中，DataFrame 仅仅是一个 Dataset[Row] 类型的别名 。然而，在 Java API 中，用户需要去使用 Dataset<Row> 来表示 DataFrame。

在这个文档中，我们将常常会引用 Scala/Java  的 Dataset 的 Row（行）作为 DataFrame。

入门指南

起始点 : SparkSession

Spark 中所有功能的入口点是 SparkSession 类。去创建一个基本的 SparkSession，仅使用 SparkSession.builder() : 

import org.apache.spark.sql.SparkSession
val spark = SparkSession
  .builder()
  .appName("Spark SQL Example")
  .config("spark.some.config.option", "some-value")
  .getOrCreate()

// For implicit conversions like converting RDDs to DataFrames
import spark.implicits._

// 所有的示例代码可以在 Spark repo 的 “examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala” 中找到。

在 Spark 2.0 中 SparkSession 为 Hive 特性提供了内嵌的支持，包括使用 HiveQL 编写查询的能力，访问 Hive UDF，以及从 Hive 表中读取数据的能力。为了使用这些特性，你不需要去有一个已存在的 Hive 设置。

创建 DataFrames

与一个 SparkSession 一起，应用程序可以从一个 已存在的 RDD（   中从一个百度的 R data.frame），或者一个 Hive 表中，或者从 Spark 数据源 中创建 DataFrame。

举个例子，下面基于一个 JSON 文件的内容创建一个 DataFrame : 

val df = spark.read.json("examples/src/main/resources/people.json")

// Displays the content of the DataFrame to stdout
df.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

// 所有的示例代码可以在 Spark repo 的 “examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala” 中找到。

无类型 Dataset 操作（aka DataFrame 操作）

DataFrame 提供了一个 DSL（domain-specific language）用于在 Scala，Java，Python 或者 R 中的结构化数据操作。

正如上面提到的一样，在Spark 2.0 ，Scala 和 JavaAPI 中的 DataFrame 仅仅是多个 Row（行）的 Dataset 。这些操作也参考了与强类型的 Scala/Java Datasets 的 “类型转换” 相对应的 “无类型转换”。

这里包括一些使用 Dataset 进行结构化数据处理的示例 : 

// This import is needed to use the $-notation
import spark.implicits._
// Print the schema in a tree format
df.printSchema()
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)

// Select only the "name" column
df.select("name").show()
// +-------+
// |   name|
// +-------+
// |Michael|
// |   Andy|
// | Justin|
// +-------+

// Select everybody, but increment the age by 1
df.select($"name", $"age" + 1).show()
// +-------+---------+
// |   name|(age + 1)|
// +-------+---------+
// |Michael|     null|
// |   Andy|       31|
// | Justin|       20|
// +-------+---------+

// Select people older than 21
df.filter($"age" > 21).show()
// +---+----+
// |age|name|
// +---+----+
// | 30|Andy|
// +---+----+

// Count people by age
df.groupBy("age").count().show()
// +----+-----+
// | age|count|
// +----+-----+
// |  19|    1|
// |null|    1|
// |  30|    1|
// +----+-----+

// 所有的示例代码可以在 Spark repo 的 “examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala” 中找到。

能够在 DataFrame 上被执行的操作类型的完整列表请参考 API 文档。

除了简单的列引用和表达式之外，DataFrame 也有丰富的函数库，包括 string 操作，date 算术，常见的 math 操作以及更多。可用的完整列表请参考 DataFrame 函数参考。

以编程的方式运行 SQL 查询

// SparkSession 使应用程序的 SQL 函数能够以编程的方式运行 SQL 查询并且将查询结果以一个 DataFrame。

// Register the DataFrame as a SQL temporary view
df.createOrReplaceTempView("people")

val sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

// 所有的示例代码可以在 Spark repo 的 “examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala” 中找到。

创建 Datasets

Dataset 与 RDD 相似，然而，并不是使用 Java 序列化或者 Kryo，他们使用一个指定的 Encoder（编码器） 来序列化用于处理或者通过网络进行传输的对象。虽然编码器和标准的序列化都负责将一个对象序列化成字节，编码器是动态生成的代码，并且使用了一种允许 Spark 去执行许多像 filtering，sorting 以及 hashing 这样的操作，不需要将字节反序列化成对象的格式。

// Note: Case classes in Scala 2.10 can support only up to 22 fields. To work around this limit,
// you can use custom classes that implement the Product interface
case class Person(name: String, age: Long)

// Encoders are created for case classes
val caseClassDS = Seq(Person("Andy", 32)).toDS()
caseClassDS.show()
// +----+---+
// |name|age|
// +----+---+
// |Andy| 32|
// +----+---+

// Encoders for most common types are automatically provided by importing spark.implicits._
val primitiveDS = Seq(1, 2, 3).toDS()
primitiveDS.map(_ + 1).collect() // Returns: Array(2, 3, 4)

// DataFrames can be converted to a Dataset by providing a class. Mapping will be done by name
val path = "examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path).as[Person]
peopleDS.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

// 所有的示例代码可以在 Spark repo 的 “examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala” 中找到。

RDD 的互操作性

Spark SQL 支持两种不同的方法用于转换已存在的 RDD 成为 Dataset。

第一种方法是使用反射去推断一个包含指定的对象类型的 RDD 的 Schema。在你的 Spark 应用程序中当你已知 Schema 时这个基于方法的反射可以让你的代码更简洁。

第二种用于创建 Dataset 的方法是通过一个允许你构造一个 Schema 然后把它应用到一个已存在的 RDD 的编程接口。然而这种方法更繁琐，当列和它们的类型知道运行时都是未知时它允许你去构造 Dataset。

使用反射推断 Schema

// Spark SQL 的 Scala 接口支持自动转换一个包含 case classes 的 RDD 为 DataFrame。 Case class 定义了表的 Schema。Case class 的参数名使用反射读取并且成为了列名。
// Case class 也可以是嵌套的或者包含像 SeqS 或者 ArrayS 这样的复杂类型。这个 RDD 能够被隐式转换成一个 DataFrame 然后被注册为一个表。表可以用于后续的 SQL 语句。

import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder
import org.apache.spark.sql.Encoder

// For implicit conversions from RDDs to DataFrames
import spark.implicits._

// Create an RDD of Person objects from a text file, convert it to a Dataframe
val peopleDF = spark.sparkContext
  .textFile("examples/src/main/resources/people.txt")
  .map(_.split(","))
  .map(attributes => Person(attributes(0), attributes(1).trim.toInt))
  .toDF()
// Register the DataFrame as a temporary view
peopleDF.createOrReplaceTempView("people")

// SQL statements can be run by using the sql methods provided by Spark
val teenagersDF = spark.sql("SELECT name, age FROM people WHERE age BETWEEN 13 AND 19")

// The columns of a row in the result can be accessed by field index
teenagersDF.map(teenager => "Name: " + teenager(0)).show()
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+

// or by field name
teenagersDF.map(teenager => "Name: " + teenager.getAs[String]("name")).show()
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+

// No pre-defined encoders for Dataset[Map[K,V]], define explicitly
implicit val mapEncoder = org.apache.spark.sql.Encoders.kryo[Map[String, Any]]
// Primitive types and case classes can be also defined as
implicit val stringIntMapEncoder: Encoder[Map[String, Int]] = ExpressionEncoder()

// row.getValuesMap[T] retrieves multiple columns at once into a Map[String, T]
teenagersDF.map(teenager => teenager.getValuesMap[Any](List("name", "age"))).collect()
// Array(Map("name" -> "Justin", "age" -> 19))

// 所有的示例代码可以在 Spark repo 的 “examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala” 中找到。

以编程的方式指定 Schema

当 case class 不能够在执行之前被定义（例如，records 记录的结构在一个 string 字符串中被编码了，或者一个 text 文本 dataset 将被解析并且不同的用户投影的字段是不一样的）。一个 DataFrame 可以使用下面的三步以编程的方式来创建。

从原始的 RDD 创建 RDD 的 RowS（行）。

Step 1 被创建后，创建 Schema 表示一个 StructType 匹配 RDD 中的 Rows（行）的结构。

通过 SparkSession 提供的 createDataFrame 方法应用 Schema 到 RDD 的 RowS（行）。

例如 : 

import org.apache.spark.sql.types._
import org.apache.spark.sql.Row

// Create an RDD
val peopleRDD = spark.sparkContext.textFile("examples/src/main/resources/people.txt")

// The schema is encoded in a string
val schemaString = "name age"

// Generate the schema based on the string of schema
val fields = schemaString.split(" ")
  .map(fieldName => StructField(fieldName, StringType, nullable = true))
val schema = StructType(fields)

// Convert records of the RDD (people) to Rows
val rowRDD = peopleRDD
  .map(_.split(","))
  .map(attributes => Row(attributes(0), attributes(1).trim))

// Apply the schema to the RDD
val peopleDF = spark.createDataFrame(rowRDD, schema)

// Creates a temporary view using the DataFrame
peopleDF.createOrReplaceTempView("people")

// SQL can be run over a temporary view created using DataFrames
val results = spark.sql("SELECT name FROM people")

// The results of SQL queries are DataFrames and support all the normal RDD operations
// The columns of a row in the result can be accessed by field index or by field name
results.map(attributes => "Name: " + attributes(0)).show()
// +-------------+
// |        value|
// +-------------+
// |Name: Michael|
// |   Name: Andy|
// | Name: Justin|
// +-------------+

数据源

Spark SQL 支持通过 DataFrame 接口操作多种数据源。一个 DataFrame 可以通过关联转换来操作，也可以被创建为一个临时的 view。注册一个 DataFrame 作为一个临时的 view 就可以允许你在数据集上运行 SQL 查询。本节介绍了一些通用的方法通过使用 Spark Data Sources 来加载和保存数据以及一些可用的内置数据源的特定选项。

通用的 Load/Save 函数

在最简单的方式下，默认的数据源（parquet 除非另外配置通过spark.sql.sources.default）将会用于所有的操作。

val usersDF = spark.read.load("examples/src/main/resources/users.parquet")
usersDF.select("name", "favorite_color").write.save("namesAndFavColors.parquet")

完整的示例代码在 "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala"。

手动指定选项

你也可以手动的指定数据源，并且将与你想要传递给数据源的任何额外选项一起使用。数据源由其完全限定名指定（例如 : org.apache.spark.sql.parquet），不过对于内置数据源你也可以使用它们的缩写名（json, parquet, jdbc）。使用下面这个语法可以将从任意类型数据源加载的DataFrames 转换为其他类型。

val peopleDF = spark.read.format("json").load("examples/src/main/resources/people.json")
peopleDF.select("name", "age").write.format("parquet").save("namesAndAges.parquet")

完整的示例代码在 "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala"。

直接在文件上运行 SQL

你也可以直接在文件上运行 SQL 查询来替代使用 API 将文件加载到 DataFrame 再进行查询。

val sqlDF = spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`")

完整代码示例在 "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala"。

保存模式

Save 操作可以使用 SaveMode，可以指定如何处理已经存在的数据。这是很重要的要意识到这些保存模式没有利用任何锁并且也不是原子操作。另外，当执行 Overwrite，新数据写入之前会先将旧数据删除。

Scala / Java | Any Language | Meaning（含义）
SaveMode.ErrorIfExists(default) | "error" (default) | 在保存 DataFrame 到一个数据源时，如果数据已经存在，将会抛出异常。
SaveMode.Append | "append" | 在保存 DataFrame 到一个数据源时，如果数据/表已经存在，DataFrame 的内容将会追加到已存在的数据后面。
SaveMode.Overwrite | "overwrite" | Overwrite 模式意味着当保存 DataFrame 到一个数据源，如果数据/表已经存在，那么已经存在的数据将会被 DataFrame 的内容覆盖。
SaveMode.Ignore | "ignore" | Ignore 模式意味着当保存 DataFrame 到一个数据源，如果数据已经存在，save 操作不会将 DataFrame 的内容保存，也不会修改已经存在的数据。这个和 SQL 中的 'CREATE TABLE IF NOT EXISTS' 类似 。

保存为持久化的表

DataFrames 也可以通过 saveAsTable 命令来保存为一张持久表到 Hive metastore 中。值得注意的是对于这个功能来说已经存在的 Hive 部署不是必须的。Spark 将会为你创造一个默认的本地 Hive metastore（使用 Derby)。不像 createOrReplaceTempView 命令那样，saveAsTable 将会持久化 DataFrame 中的内容并在 Hive metastore 中创建一个指向数据的指针。持久化的表将会一直存在甚至当你的 Spark 应用已经重启，只要保持你的连接是和一个相同的 metastore。一个相对于持久化表的 DataFrame 可以通过在 SparkSession 中调用 table 方法创建。

默认情况下 saveAsTable 操作将会创建一个 “managed table”，意味着数据的位置将会被 metastore 控制。Managed tables 在表 drop 后也数据也会自动删除。

Parquet 文件

Parquet 是一个列式存储格式的文件，被许多其他数据处理系统所支持。Spark SQL 支持对 Parquet 文件的读写还可以自动的保存源数据的模式

以编程的方式加载数据

import spark.implicits._
val peopleDF = spark.read.json("examples/src/main/resources/people.json")
peopleDF.write.parquet("people.parquet")
val parquetFileDF = spark.read.parquet("people.parquet")
parquetFileDF.createOrReplaceTempView("parquetFile")
val namesDF = spark.sql("SELECT name FROM parquetFile WHERE age BETWEEN 13 AND 19")
namesDF.map(attributes => "Name: " + attributes(0)).show()
// +------------+
// | value|
// +------------+
// |Name: Justin|
// +------------+

分区发现

在系统中，比如 Hive，表分区是一个很常见的优化途径。在一个分区表中 ，数据通常存储在不同的文件目录中，对每一个分区目录中的途径按照分区列的值进行编码。Parquet 数据源现在可以自动地发现并且推断出分区的信息。例如，我们可以将之前使用的人口数据存储成下列目录结构的分区表，两个额外的列，gender 和 country 作为分区列 : 

path
└── to
    └── table
        ├── gender=male
        │   ├── ...
        │   │
        │   ├── country=US
        │   │   └── data.parquet
        │   ├── country=CN
        │   │   └── data.parquet
        │   └── ...
        └── gender=female
            ├── ...
            │
            ├── country=US
            │   └── data.parquet
            ├── country=CN
            │   └── data.parquet
            └── ...

通过向 SparkSession.read.parquet 或 SparkSession.read.load 中传入 path/to/table，Spark SQL 将会自动地从路径中提取分区信息。现在返回的 DataFrame 模式变成 : 

root
|-- name: string (nullable = true)
|-- age: long (nullable = true)
|-- gender: string (nullable = true)
|-- country: string (nullable = true)

需要注意的是分区列的数据类型是自动推导出的。当前，支持数值数据类型以及 string 类型。有些时候用户可能不希望自动推导出分区列的数据类型。对于这些使用场景，自动类型推导功能可以通过 spark.sql.sources.partitionColumnTypeInference.enabled 来配置，默认值是 true。当自动类型推导功能禁止，分区列的数据类型是 string。

从 Spark 1.6.0 开始，分区发现只能发现在默认给定的路径下的分区。对于上面那个例子，如果用户向 SparkSession.read.parquet 或 SparkSession.read.load, gender 传递 path/to/table/gender=male 将不会被当做分区列。如果用户需要指定发现的根目录，可以在数据源设置 basePath 选项。比如，将 path/to/table/gender=male 作为数据的路径并且设置 basePath 为 path/to/table/，gender 将会作为一个分区列。

Schema 合并

类似 ProtocolBuffer，Avro，以及 Thrift，Parquet 也支持 schema 演变。用户可以从一个简单的 schema 开始，并且根据需要逐渐地向 schema 中添加更多的列。这样，用户最终可能会有多个不同但是具有相互兼容 schema 的 Parquet 文件。Parquet 数据源现在可以自动地发现这种情况，并且将所有这些文件的 schema 进行合并。

Hive metastore Parquet 表转换

当从 Hive metastore 里读写 Parquet 表时，为了更好地提升新能 Spark SQL 会尝试用自己支持的 Parquet 替代 Hive SerDe。这个功能通过 spark.sql.hive.convertMetastoreParquet 选项来控制，默认是开启的。

Hive/Parquet Schema Reconciliation

从 Hive 和 Parquet 处理表 schema 过程的角度来看有两处关键的不同。

Hive 对大小写不敏感，而 Parquet 不是。

Hive 认为所有列都是 nullable 可为空的，在 Parquet 中为 nullability 是需要显示声明的。

由于这些原因，当我们将 Hive metastore Parquet table 转换为Spark SQLtable 时必须使 Hive metastore schema 与 Parquet schema 相兼容。兼容规则如下 : 

除了nullability，相同 schema 的字段的数据类型必须相同。要兼容的字段应该具有 Parquet 的数据类型，因此 nullability 是被推崇的。

reconciled schema 包含了这些 Hive metastore schema 里定义的字段。 

任何字段只出现在 Parquet schema 中会被 reconciled schema 排除。

任何字段只出现在 Hive metastore schema 中会被当做 nullable 字段来添加到 reconciled schema 中。

Metadata 刷新

为了提高性能 Spark SQL 缓存了 Parquet metadata。当 Hive metastore Parquet table 转换功能开启，这些转换后的元数据信息也会被缓存。如果这些表被 Hive 或者其他外部的工具更新，你需要手动刷新以确保元数据信息保持一致。

spark.catalog.refreshTable("my_table")

配置

Parquet 的配置可以使用 SparkSession 的 setConf 来设置或者通过使用 SQL 运行 SET key=value 命令。

属性名 | 默认值 | 含义
spark.sql.parquet.binaryAsString | false |  其他的一些产生 Parquet 的系统，特别是 Impala 和 SparkSQL 的老版本，当将 Parquet 模式写出时不会区分二进制数据和字符串。这个标志告诉 Spark SQL 将二进制数据解析成字符串，以提供对这些系统的兼容。
spark.sql.parquet.int96AsTimestamp | true | 其他的一些产生 Parquet 的系统，特别是 Impala ，将时间戳存储为 INT96 的形式。Spark 也将时间戳存储为 INT96，因为我们要避免纳秒级字段的精度的损失。这个标志告诉 Spark SQL 将 INT96 数据解析为一个时间戳，以提供对这些系统的兼容。
spark.sql.parquet.cacheMetadata | true | 打开 Parquet模式的元数据的缓存。能够加快对静态数据的查询。
spark.sql.parquet.compression.codec | gzip | 设置压缩编码解码器，当写入一个 Parquet文件时。可接收的值包括 : uncompressed，snappy，gzip，lzo。
spark.sql.parquet.filterPushdown | false | 打开 Parquet 过滤器的后进先出存储的优化。这个功能默认是被关闭的，因为一个 Parquet 中的一个已知的 bug 1.6.0rc3 (PARQUET-136)。然而，如果你的表中不包含任何的可为空的（nullable）字符串或者二进制列，那么打开这个功能是安全的。
spark.sql.hive.convertMetastoreParquet | true | 当设置成 false，Spark SQL 会为 parquet 表使用 Hive SerDe(Serialize/Deserilize)。
spark.sql.parquet.mergeSchema | false | 当设置 true 时，Parquet 数据源从所有的数据文件中合并 schemas，否则 schema 来自 summary file 或随机的数据文件当 summary file不可得时。

JSON Datasets

Spark SQL 可以自动的推断出 JSON 数据集的 schema 并且将它作为 DataFrame 进行加载。这个转换可以通过使用 SparkSession.read.json() 在字符串类型的 RDD 中或者 JSON 文件。

注意作为 json file 提供的文件不是一个典型的 JSON 文件。每一行必须包含一个分开的独立的有效 JSON 对象。因此，常规的多行 JSON 文件通常会失败。

 // A JSON dataset is pointed to by path.
// The path can be either a single text file or a directory storing text files
val path = "examples/src/main/resources/people.json"
val peopleDF = spark.read.json(path)

// The inferred schema can be visualized using the printSchema() method
peopleDF.printSchema()
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)

// Creates a temporary view using the DataFrame
peopleDF.createOrReplaceTempView("people")

// SQL statements can be run by using the sql methods provided by spark
val teenagerNamesDF = spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19")
teenagerNamesDF.show()
// +------+
// | name|
// +------+
// |Justin|
// +------+

// Alternatively, a DataFrame can be created for a JSON dataset represented by
// an RDD[String] storing one JSON object per string
val otherPeopleRDD = spark.sparkContext.makeRDD(
  """{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil)
val otherPeople = spark.read.json(otherPeopleRDD)
otherPeople.show()
// +---------------+----+
// | address|name|
// +---------------+----+
// |[Columbus,Ohio]| Yin|
// +---------------+----+

Hive 表

Spark SQL 还支持在 Apache Hive 中读写数据。然而，由于 Hive 依赖项太多，这些依赖没有包含在默认的 Spark 发行版本中。如果在 classpath 上配置了 Hive 依赖，那么 Spark 会自动加载它们。注意，Hive 依赖也必须放到所有的 worker 节点上，因为如果要访问 Hive 中的数据它们需要访问 Hive 序列化和反序列化库（SerDes)。

Hive 配置是通过将 hive-site.xml，core-site.xml（用于安全配置）以及 hdfs-site.xml（用于HDFS 配置）文件放置在conf/目录下来完成的。

如果要使用 Hive, 你必须要实例化一个支持 Hive 的 SparkSession，包括连接到一个持久化的 Hive metastore, 支持 Hive 序列化反序列化库以及 Hive 用户自定义函数。即使用户没有安装部署 Hive 也仍然可以启用 Hive 支持。如果没有在 hive-site.xml 文件中配置，Spark 应用程序启动之后，上下文会自动在当前目录下创建一个 metastore_db 目录并创建一个由 spark.sql.warehouse.dir 配置的、默认值是当前目录下的 spark-warehouse 目录的目录。请注意 : 从 Spark 2.0.0 版本开始，hive-site.xml 中的 hive.metastore.warehouse.dir 属性就已经过时了，你可以使用 spark.sql.warehouse.dir 来指定仓库中数据库的默认存储位置。你可能还需要给启动 Spark 应用程序的用户赋予写权限。

import org.apache.spark.sql.Row
import org.apache.spark.sql.SparkSession

case class Record(key: Int, value: String)

// warehouseLocation points to the default location for managed databases and tables
val warehouseLocation = "file:${system:user.dir}/spark-warehouse"

val spark = SparkSession
  .builder()
  .appName("Spark Hive Example")
  .config("spark.sql.warehouse.dir", warehouseLocation)
  .enableHiveSupport()
  .getOrCreate()

import spark.implicits._
import spark.sql

sql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING)")
sql("LOAD DATA LOCAL INPATH 'examples/src/main/resources/kv1.txt' INTO TABLE src")

// Queries are expressed in HiveQL
sql("SELECT * FROM src").show()
// +---+-------+
// |key|  value|
// +---+-------+
// |238|val_238|
// | 86| val_86|
// |311|val_311|
// ...

// Aggregation queries are also supported.
sql("SELECT COUNT(*) FROM src").show()
// +--------+
// |count(1)|
// +--------+
// |    500 |
// +--------+

// The results of SQL queries are themselves DataFrames and support all normal functions.
val sqlDF = sql("SELECT key, value FROM src WHERE key < 10 ORDER BY key")

// The items in DaraFrames are of type Row, which allows you to access each column by ordinal.
val stringsDS = sqlDF.map {
  case Row(key: Int, value: String) => s"Key: $key, Value: $value"
}
stringsDS.show()
// +--------------------+
// |               value|
// +--------------------+
// |Key: 0, Value: val_0|
// |Key: 0, Value: val_0|
// |Key: 0, Value: val_0|
// ...

// You can also use DataFrames to create temporary views within a HiveContext.
val recordsDF = spark.createDataFrame((1 to 100).map(i => Record(i, s"val_$i")))
recordsDF.createOrReplaceTempView("records")

// Queries can then join DataFrame data with data stored in Hive.
sql("SELECT * FROM records r JOIN src s ON r.key = s.key").show()
// +---+------+---+------+
// |key| value|key| value|
// +---+------+---+------+
// |  2| val_2|  2| val_2|
// |  2| val_2|  2| val_2|
// |  4| val_4|  4| val_4|
// ...

与不同版本的 Hive Metastore 交互

Spark SQL 对 Hive 最重要的一个支持就是可以和 Hive metastore 进行交互，这使得 Spark SQL 可以访问 Hive 表的元数据。从 Spark 1.4.0 版本开始，通过使用下面描述的配置，Spark SQL 一个简单的二进制编译版本可以用来查询不同版本的 Hive metastore。注意，不管用于访问 metastore 的 Hive 是什么版本，Spark SQL 内部都使用 Hive 1.2.1 版本进行编译，并且使用这个版本的一些类用于内部执行（serdes，UDFs，UDAFs等）。

下面的选项可用来配置用于检索元数据的Hive 版本 : 

属性名 | 默认值 | 含义
spark.sql.hive.metastore.version | 1.2.1 | Hive metastore 版本。可用选项从 0.12.0 到 1.2.1。

spark.sql.hive.metastore.jars

builtin

存放用于实例化 HiveMetastoreClient 的 jar 包位置。这个属性可以是下面三个选项之一 :

builtin : 使用 Hive 1.2.1 版本，当启用 -Phive 时会和 Spark 一起打包。如果使用了这个选项，那么 spark.sql.hive.metastore.version 要么是 1.2.1，要么就不定义。

maven : 使用从 Maven 仓库下载的指定版本的 Hive jar 包。生产环境部署通常不建议使用这个选项。

标准格式的 JVM classpath。这个 classpath 必须包含所有 Hive 及其依赖的 jar 包，并且包含正确版本的 hadoop。这些 jar 包只需要部署在 driver 节点上，但是如果你使用 yarn cluster 模式运行，那么你必须要确保这些 jar 包是和应用程序一起打包的。

spark.sql.hive.metastore.sharedPrefixes

com.mysql.jdbc,

org.postgresql,

com.microsoft.sqlserver,

oracle.jdbc

一个逗号分隔的类名前缀列表，这些类使用 classloader 加载，且可以在 Spark SQL 和特定版本的 Hive 间共享。一个共享类的示例就是用来访问 Hive metastore 的 JDBC driver。其它需要共享的类，是需要与已经共享的类进行交互的。例如，log4j 使用的自定义 appender 。

spark.sql.hive.metastore.barrierPrefixes | (empty) | 一个逗号分隔的类名前缀列表，这些类需要在 Spark SQL 访问的每个 Hive 版本中显式地重新加载。例如，在一个共享前缀列表 （org.apache.spark.*）中声明的 Hive UDF通常需要被共享。

JDBC 连接其它数据库

Spark SQL 还有一个能够使用 JDBC 从其他数据库读取数据的数据源。当使用 JDBC 访问其它数据库时，应该首选 JdbcRDD。这是因为结果是以数据框（DataFrame）返回的，且这样 Spark SQL操作轻松或便于连接其它数据源。因为这种 JDBC 数据源不需要用户提供 ClassTag，所以它也更适合使用 Java 或 Python 操作。（注意，这与允许其它应用使用 Spark SQL 执行查询操作的 Spark SQL JDBC 服务器是不同的）。

使用 JDBC 访问特定数据库时，需要在 spark classpath 上添加对应的 JDBC 驱动配置。例如，为了从 Spark Shell 连接 postgres，你需要运行如下命令 : 

bin/spark-shell --driver-class-path postgresql-9.4.1207.jar --jars postgresql-9.4.1207.jar

通过调用数据源API，远程数据库的表可以被加载为DataFrame 或Spark SQL临时表。支持的参数有 : 

属性名 | 含义
url | 要连接的 JDBC URL。
dbtable | 要读取的 JDBC 表。 注意，一个 SQL 查询的 From 分语句中的任何有效表都能被使用。例如，既可以是完整表名，也可以是括号括起来的子查询语句。
driver | 用于连接 URL 的 JDBC 驱动的类名。
partitionColumn, lowerBound, upperBound, numPartitions | 这几个选项，若有一个被配置，则必须全部配置。它们描述了当从多个 worker 中并行的读取表时，如何对它分区。partitionColumn 必须时所查询表的一个数值字段。注意，lowerBound 和 upperBound 都只是用于决定分区跨度的，而不是过滤表中的行。因此，表中的所有行将被分区并返回。
fetchSize | JDBC fetch size，决定每次读取多少行数据。 默认将它设为较小值（如，Oracle上设为 10）有助于 JDBC 驱动上的性能优化。

代码示例如下 : 

val jdbcDF = spark.read
  .format("jdbc")
  .option("url", "jdbc:postgresql:dbserver")
  .option("dbtable", "schema.tablename")
  .option("user", "username")
  .option("password", "password")
  .load()

CREATE TEMPORARY VIEW jdbcTable
USING org.apache.spark.sql.jdbc
OPTIONS (
  url "jdbc:postgresql:dbserver",
  dbtable "schema.tablename"
)

故障排除

在客户端会话（client session）中或者所有 executor 上，JDBC 驱动类必须可见于原生的类加载器。这是因为 Java 的驱动管理（DriverManager ）类在打开一个连接之前会做一个安全检查，这就导致它忽略了所有对原生类加载器不可见的驱动。一个方便的方法，就是修改所有 worker 节点上的 compute_classpath.sh 以包含你的驱动 Jar 包。

一些数据库，如 H2，会把所有的名称转为大写。在 Spark SQL 中你也需要使用大写来引用这些名称。

性能调优

对一些工作负载，可能的性能改进的方式，不是把数据缓存在内存里，就是调整一些试验选项。

缓存数据到内存

Spark SQL 可以通过调用 spark.cacheTable("tableName") 或者 dataFrame.cache() 以列存储格式缓存表到内存中。随后，Spark SQL 将会扫描必要的列，并自动调整压缩比例，以减少内存占用和 GC 压力。你可以调用 spark.uncacheTable("tableName") 来删除内存中的表。

你可以在 SparkSession 上使用 setConf 方法或在 SQL 语句中运行 SET key=value 命令，来配置内存中的缓存。

属性名 | 默认值 | 含义
spark.sql.inMemoryColumnarStorage.compressed | true | 当设置为 true 时，Spark SQL 将会基于数据的统计信息自动地为每一列选择单独的压缩编码方式。
spark.sql.inMemoryColumnarStorage.batchSize | 10000 | 控制列式缓存批量的大小。当缓存数据时，增大批量大小可以提高内存利用率和压缩率，但同时也会带来 OOM（Out Of Memory）的风险。

其它配置选项

下面的选项也可以用来提升查询执行的性能。随着 Spark 自动地执行越来越多的优化操作，这些选项在未来的发布版本中可能会过时。

属性名 | 默认值 | 含义
spark.sql.files.maxPartitionBytes | 134217728 (128 MB) | 读取文件时单个分区可容纳的最大字节数。
spark.sql.files.openCostInBytes | 4194304 (4 MB) | 打开文件的估算成本，按照同一时间能够扫描的字节数来测量。当往一个分区写入多个文件的时候会使用。高估更好, 这样的话小文件分区将比大文件分区更快 (先被调度)。
spark.sql.autoBroadcastJoinThreshold | 10485760 (10 MB) | 配置一个表在执行 join 操作时能够广播给所有 worker 节点的最大字节大小。通过将这个值设置为-1，可以禁用广播。注意，目前的数据统计仅支持已经运行了 ANALYZE TABLE <tableName> COMPUTE STATISTICS noscan 命令的 Hive metastore 表。
spark.sql.shuffle.partitions | 200 | 配置为连接或聚合操作混洗（shuffle）数据时使用的分区数。

分布式 SQL引擎

通过使用 Spark SQ L的 JDBC/ODBC 或者命令行接口，它还可以作为一个分布式查询引擎。在这种模式下，终端用户或应用程序可以运行 SQL 查询来直接与 Spark SQL 交互，而不需要编写任何代码。

运行 Thrift JDBC/ODBC server

这里实现的 Thrift JDBC/ODBC server 对应于 Hive 1.2.1 版本中的 HiveServer2。你可以使用 Spark 或者 Hive 1.2.1 自带的 beeline 脚本来测试这个 JDBC server。

要启动 JDBC/ODBC server， 需要在 Spark 安装目录下运行如下命令 : 

./sbin/start-thriftserver.sh

这个脚本能接受所有 bin/spark-submit 命令行选项，外加一个用于指定 Hive 属性的 --hiveconf 选项。你可以运行 ./sbin/start-thriftserver.sh --help 来查看所有可用选项的完整列表。默认情况下，这启动的 server 将会在 localhost:10000 上进行监听。你可以覆盖该行为，比如使用以下环境变量 : 

export HIVE_SERVER2_THRIFT_PORT=<listening-port>
export HIVE_SERVER2_THRIFT_BIND_HOST=<listening-host>
./sbin/start-thriftserver.sh \
  --master <master-uri> \
  ...

或者系统属性 : 

./sbin/start-thriftserver.sh \
  --hiveconf hive.server2.thrift.port=<listening-port> \
  --hiveconf hive.server2.thrift.bind.host=<listening-host> \
  --master <master-uri>
  ...

现在你可以使用 beeline 来测试这个Thrift JDBC/ODBC server : 

./bin/beeline

在 beeline 中使用以下命令连接到 JDBC/ODBC server : 

beeline> !connect jdbc:hive2://localhost:10000

Beeline 会要求你输入用户名和密码。在非安全模式下，只需要输入你本机的用户名和一个空密码即可。对于安全模式，请参考 beeline 文档中的指示。

将 hive-site.xml，core-site.xml 以及 hdfs-site.xml 文件放置在 conf 目录下可以完成 Hive 配置。

你也可以使用 Hive 自带的 beeline 的脚本。

Thrift JDBC server 还支持通过 HTTP 传输来发送 Thrift RPC 消息。使用下面的设置作为系统属性或者对 conf 目录中的 hive-site.xml 文件配置来启用 HTTP 模式 : 

hive.server2.transport.mode - Set this to value: http
hive.server2.thrift.http.port - HTTP port number fo listen on; default is 10001
hive.server2.http.endpoint - HTTP endpoint; default is cliservice

为了测试，在 HTTP 模式中使用 beeline 连接到 JDBC/ODBC server : 

beeline> !connect jdbc:hive2://<host>:<port>/<database>?hive.server2.transport.mode=http;hive.server2.thrift.http.path=<http_endpoint>

运行 Spark SQL CLI

Spark SQL CLI 是一个很方便的工具，它可以在本地模式下运行 Hive metastore 服务，并且执行从命令行中输入的查询语句。注意 : Spark SQL CLI 无法与 Thrift JDBC server 通信。

要启动 Spark SQL CLI，可以在 Spark 安装目录运行下面的命令 :

./bin/spark-sql

将 hive-site.xml，core-site.xml 以及 hdfs-site.xml 文件放置在 conf 目录下可以完成 Hive 配置。你可以运行 ./bin/spark-sql --help 来获取所有可用选项的完整列表。

迁移指南

从 Spark SQL 1.6 升级到 2.0

SparkSession 现在是 Spark 新的切入点, 它替代了老的 SQLContext 和 HiveContext。注意 : 为了向下兼容，老的 SQLContext 和 HiveContext 仍然保留。可以从 SparkSession 获取一个新的 catalog 接口—— 现有的访问数据库和表的 API，如 listTables，createExternalTable， dropTempView，cacheTable 都被移到该接口。

Dataset API 和 DataFrame API 进行了统一。在 Scala 中，DataFrame 变成了 Dataset[Row] 类型的一个别名，而 Java API 使用者必须将 DataFrame 替换成 Dataset<Row>。Dataset 类既提供了强类型转换操作（如 map，filter 以及 groupByKey）也提供了非强类型转换操作（如 select 和 groupBy）。由于编译期的类型安全不是 Python 和 R 语言的一个特性，Dataset 的概念并不适用于这些语言的 API。相反，DataFrame 仍然是最基本的编程抽象, 就类似于这些语言中单节点数据帧的概念。

Dataset 和 DataFrame API 中 unionAll 已经过时并且由 union 替代。

Dataset 和 DataFrame API 中 explode 已经过时，作为选择，可以结合 select 或 flatMap 使用 functions.explode() 。

Dataset 和 DataFrame API 中 registerTempTable 已经过时并且由 createOrReplaceTempView 替代。

从 Spark SQL 1.5 升级到 1.6

Spark 1.6 中，默认情况下服务器在多会话模式下运行。这意味着每个 JDBC / ODBC 连接拥有一份自己的 SQL 配置和临时注册表。缓存表仍在并共享。如果你想在单会话模式服务器运行，请设置选项 spark.sql.hive.thriftServer.singleSession 为 true。您既可以将此选项添加到 spark-defaults.conf，或者通过 --conf 将它传递给 start-thriftserver.sh。

./sbin/start-thriftserver.sh \
     --conf spark.sql.hive.thriftServer.singleSession=true \
     ...

从 1.6.1 开始，在 sparkR 中 withColumn 方法支持添加一个新列或更换数据框同名的现有列。

从 Spark 1.6 开始，LongType 强制转换为 TimestampType 秒，而不是微秒。这一变化是为了匹配 Hive 1.2 ，保证从数值类型转换到 TimestampType 的一致性。见 SPARK-11724 了解详情。

从 Spark SQL 1.4 升级到 1.5

使用手动管理的内存优化执行，现在是默认启用的，以及代码生成表达式求值。这些功能既可以通过设置 spark.sql.tungsten.enabled 为 false 来禁止使用。

Parquet 的模式合并默认情况下不再启用。它可以通过设置重新启用 spark.sql.parquet.mergeSchema 到 true。

字符串在 Python 列的分辨率现在支持使用点（.）来限定列或访问嵌套值。例如 df['table.column.nestedField']。但是，这意味着如果你的列名中包含任何圆点，你现在必须避免使用反引号（如 table.`column.with.dots`.nested）。

在内存中的列存储分区修剪默认是开启的。它可以通过设置 spark.sql.inMemoryColumnarStorage.partitionPruning 为 false 来禁用。

无限精度的小数列不再支持，而不是 Spark SQL 最大精度为 38 。当从 BigDecimal 对象推断模式时，现在使用（38，18）。在 DDL 没有指定精度时，则默认保留 Decimal(10, 0)。

时间戳现在存储在 1 微秒的精度，而不是 1 纳秒的。

在 sql 语句中，浮点数现在解析为十进制。HiveQL 解析保持不变。

SQL/DateFrame 数据帧功能的规范名称现在是小写（e.g. sum vs SUM）。

JSON 数据源不会自动加载由其他应用程序（未通过 Spark SQL 插入到数据集的文件）创建的新文件。对于 JSON 持久表（即表的元数据存储在 Hive Metastore），用户可以使用 REFRESH TABLE SQL 命令或 HiveContext 的 refreshTable 方法，把那些新文件列入到表中。对于代表一个 JSON 数据集的数据帧，用户需要重新创建数据框，同时数据框中将包括新的文件。

PySpark DataFrame 的 withColumn 方法支持添加新的列或替换现有的同名列。

从 Spark SQL 1.3 升级到 1.4

数据帧的数据读/写器接口

根据用户的反馈，我们创建了一个新的更快速的 API 中读取数据 (SQLContext.read）和写入数据(DataFrame.write)。同时废弃的过时的API（例如SQLContext.parquetFile，SQLContext.jsonFile）。

请参阅 API 文档 SQLContext.read(Scala, Java, Python )和 DataFrame.write(Scala , Java, Python)的更多信息。

DataFrame.groupBy 保留分组列

根据用户反馈，我们改变的默认行为 DataFrame.groupBy().agg() 保留在 DataFrame 的分组列。为了维持 1.3 的行为特征，设置 spark.sql.retainGroupColumns 为 false。

// 在 1.3.x 中, 为了让"department"列得到展示，
// 必须包括明确作为 gg 函数调用的一部分。
df.groupBy("department").agg($"department", max("age"), sum("expense"))

// 在 1.4 以上版本, "department" 列自动包含了.
df.groupBy("department").agg(max("age"), sum("expense"))

// 恢复到 1.3 版本（不保留分组列）
sqlContext.setConf("spark.sql.retainGroupColumns", "false")

在 DataFrame.withColumn 中的改变

之前 1.4 版本中，DataFrame.withColumn() 只支持添加列。该列将始终在 DateFrame 结果中被加入作为新的列，即使现有的列可能存在相同的名称。从 1.4 版本开始，DataFrame.withColumn() 支持添加与所有现有列的名称不同的列或替换现有的同名列。

请注意，这一变化仅适用于 Scala API，并不适用于 PySpark 和 SparkR。

从 Spark SQL 1.0~1.2 升级到 1.3

在 Spark 1.3 中，我们从 Spark SQL 中删除了 "Alpha" 的标签，作为一部分已经清理过的可用的 API 。从 Spark 1.3 版本以上，Spark SQL 将提供在 1.X 系列的其他版本的二进制兼容性。这种兼容性保证不包括被明确标记为不稳定的（即 DeveloperApi 类或 Experimental）的 API。

重命名 SchemaRDD 到 DateFrame

升级到 Spark SQL 1.3 版本时，用户会发现最大的变化是，SchemaRDD 已更名为 DataFrame。这主要是因为 DataFrames 不再从 RDD 直接继承，而是由 RDDS 自己来实现这些功能。DataFrames 仍然可以通过调用 .rdd 方法转换为 RDDS 。

在 Scala 中，有一个从 SchemaRDD 到 DataFrame 类型别名，可以为一些情况提供源代码兼容性。它仍然建议用户更新他们的代码以使用 DataFrame 来代替。Java 和 Python 用户需要更新他们的代码。

在 Java 和 Scala API 的统一

此前 Spark 1.3 有单独的Java兼容类（JavaSQLContext 和 JavaSchemaRDD），借鉴于 Scala API。在 Spark 1.3 中，Java API 和 Scala API 已经统一。两种语言的用户可以使用 SQLContext 和 DataFrame 。一般来说论文类尝试使用两种语言的共有类型（如 Array 替代了一些特定集合）。在某些情况下不通用的类型情况下，（例如，passing in closures 或 Maps）使用函数重载代替。

此外，该 Java 的特定类型的 API 已被删除。Scala 和 Java 的用户可以使用存在于 org.apache.spark.sql.types 类来描述编程模式。

隐式转换和 DSL 包的移除（仅限于 Scala）

许多 Spark 1.3 版本以前的代码示例都以 import sqlContext._ 开始，这提供了从 sqlContext 到 cope 的所有功能。在 Spark 1.3 中，我们移除了从 RDDs 到 DateFrame 再到 SQLContext 内部对象的隐式转换。

此外，隐式转换现在只是通过 toDF 方法增加 RDDs 所组成的一些类型（例如 classes 或 tuples），而不是自动应用。

当使用 DSL 的内部函数（现在由 DataFrame API 代替）的时候，用于一般会导入 org.apache.spark.sql.catalyst.dsl 来代替一些公有的 DataFrame 的 API 函数 : import org.apache.spark.sql.functions._。

删除在 org.apache.spark.sql 包中的一些 DataType 别名（仅限于 Scala）

Spark 1.3 移除存在于基本 SQL 包的 DataType 类型别名。开发人员应改为导入类 org.apache.spark.sql.types。

UDF 注册迁移到 sqlContext.udf 中 (针对Java和Scala)

用于注册 UDF 的函数，不管是 DataFrame DSL 还是 SQL 中用到的，都被迁移到 SQLContext 中的 udf对象中。

sqlContext.udf.register("strLen", (s: String) => s.length())

Python UDF 注册保持不变。

Python 的 DataType 不再是单例的

在 Python 中使用 DataTypes 时，你需要先构造它们（如：StringType()），而不是引用一个单例对象。

兼容 Apache Hive

Spark SQL 在设计时就考虑到了和 Hive metastore，SerDes 以及 UDF 之间的兼容性。目前 Hive SerDes 和 UDF 都是基于Hive 1.2.1版本，并且Spark SQL 可以连接到不同版本的Hive metastore（从 0.12.0 到 1.2.1，可以参考［与不同版本的Hive Metastore交互］）

在现有的 Hive 仓库中部署

Spark SQL Thrift JDBC server 采用了开箱即用的设计以兼容已有的 Hive 安装版本。你不需要修改现有的 Hive Metastore , 或者改变数据的位置和表的分区。

支持 Hive 的特性

Spark SQL 支持绝大部分的 Hive 功能，如 :  

Hive 查询语句, 包括 :

SELECT 

GROUP BY 

ORDER BY 

CLUSTER BY 

SORT BY 

所有的Hive运算符， 包括 :

关系运算符 (=, ⇔, ==, <>, <, >, >=, <=, etc) 

算术运算符 (+, -, *, /, %, etc) 

逻辑运算符 (AND, &&, OR, ||, etc) 

复杂类型构造器 - 数学函数 (sign, ln, cos等) 

String 函数 (instr, length, printf等) 

用户自定义函数（UDF） 

用户自定义聚合函数（UDAF） 

用户自定义序列化格式（SerDes） 

窗口函数 

Joins 

JOIN 

{LEFT|RIGHT|FULL} OUTER JOIN 

LEFT SEMI JOIN - CROSS JOIN 

Unions

子查询 

SELECT col FROM ( SELECT a + b AS col from t1) t2 

采样 

Explain 

分区表，包括动态分区插入 

视图 

所有 Hive DDL 功能, 包括 :

CREATE TABLE 

CREATE TABLE AS SELECT 

ALTER TABLE 

绝大多数Hive数据类型，包括

TINYINT 

SMALLINT 

INT 

BIGINT 

BOOLEAN 

FLOAT 

DOUBLE 

STRING

BINARY 

TIMESTAMP 

DATE 

ARRAY<> 

MAP<> 

STRUCT<>

不支持的 Hive 功能

以下是目前还不支持的Hive功能列表。在Hive部署中这些功能大部分都用不到。

Hive 核心功能

bucket : bucket 是 Hive 表分区内的一个哈希分区，Spark SQL目前还不支持 bucket。

Hive 高级功能

UNION 类型

Unique join

列统计数据收集 : Spark SQL目前不依赖扫描来收集列统计数据并且仅支持填充 Hive metastore 的 sizeInBytes 字段。

Hive 输入输出格式

CLI文件格式 : 对于回显到CLI中的结果，Spark SQL仅支持TextOutputFormat。

Hadoop archive

Hive 优化

有少数 Hive 优化还没有包含在 Spark 中。其中一些（比如索引）由于 Spark SQL 的这种内存计算模型而显得不那么重要。另外一些在 Spark SQL 未来的版本中会持续跟踪。

块级别位图索引和虚拟列（用来建索引）

自动为 join 和 groupBy 计算 reducer 个数 : 目前在 Spark SQL 中，你需要使用 ”SET spark.sql.shuffle.partitions=[num_tasks];” 

来控制后置混洗的并行程度。 

仅查询元数据 : 对于只需要使用元数据的查询请求，Spark SQL仍需要启动任务来计算结果。

数据倾斜标志 : Spark SQL 不遵循 Hive 中的数据倾斜标志

STREAMTABLE join操作提示 : Spark SQL 不遵循 STREAMTABLE 提示。 

对于查询结果合并多个小文件 : 如果返回的结果有很多小文件，Hive 有个选项设置，来合并小文件，以避免超过HDFS的文件数额度限制。Spark SQL 不支持这个。

参考

数据类型

Spark SQL 和 DataFrames 支持下面的数据类型 :  

数值类型 

ByteType : 表示 1 字节长的有符号整型，数值范围 : -128 到 127。

ShortType : 表示 2 字节长的有符号整型，数值范围 : -32768 到 32767。

IntegerType : 表示 4 字节长的有符号整型，数值范围 : -2147483648 到 2147483647。 

LongType : 表示 8 字节长的有符号整型，数值范围 : -9223372036854775808 到 9223372036854775807。

FloatType : 表示 4 字节长的单精度浮点数。 

DoubleType : 表示 8 字节长的双精度浮点数。

DecimalType : 表示任意精度有符号带小数的数值。内部使用 java.math.BigDecimal，一个BigDecimal 由一个任意精度的整数非标度值和一个 32 位的整数标度 (scale) 组成。

字符串类型 

StringType : 表示字符串值

二进制类型 

BinaryType : 表示字节序列值 

布尔类型 

BooleanType : 表示布尔值 

日期类型 

TimestampType : 表示包含年月日、时分秒等字段的日期值 

DateType : 表示包含年月日字段的日期值 

Complex types（复杂类型） 

 ArrayType(elementType, containsNull) : 数组类型，表示一个由类型为 elementType 的元素组成的序列，containsNull 用来表示 ArrayType 中的元素是否能为 null 值。

MapType(keyType, valueType, valueContainsNull) : 映射类型，表示一个键值对的集合。键的类型由 keyType 表示，值的类型则由 valueType 表示。对于一个 MapType 值，键是不允许为 null值。valueContainsNull 用来表示一个 MapType 的值是否能为 null 值。

StructType(fields) : 表示由 StructField 序列描述的结构。

StructField(name, datatype, nullable) : 表示 StructType 中的一个字段，name 表示字段名，datatype 是字段的数据类型，nullable 用来表示该字段是否可以为空值。

// Spark SQL的所有数据类型都放在 org.apache.spark.sql.types 这个包下。你可以这样获取它们 :
import org.apache.spark.sql.types._

完整示例代码参见 Spark 仓库中的 "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala”。

数据类型
Scala 中值的类型

用于获取或创建数据类型的 API

ByteType | Byte | ByteType
ShortType | Short | ShortType
IntegerType | Int | IntegerType
LongType | Long | LongType
FloatType | Float | FloatType
DoubleType | Double | DoubleType
DecimalType | java.math.BigDecimal | DecimalType
StringType | String | StringType
BinaryType | Array[Byte] | BinaryType
BooleanType | Boolean | BooleanType
TimestampType | java.sql.Timestamp | TimestampType
DateType | java.sql.Date | DateType
ArrayType | scala.collection.Seq | ArrayType(elementType, [containsNull]) 注意 : containsNull 的默认值是 true
MapType | scala.collection.Map | MapType(keyType, valueType, [valueContainsNull]) 注意 : valueContainsNull 的默认值是 true
StructType | org.apache.spark.sql.Row | StructType(fields) 注意 : fields 是一个 StructField 序列，另外不允许出现名称重复的字段。
StructField | Scala 中该字段的数据类型对应的值类型（例如，如果 StructField 的数据类型为 IntegerType，则 Scala 中其值类型为 Int） | list(name=name, type=dataType, nullable=nullable)

NaN 语义

当处理一些不符合标准浮点数语义的 float 或 double 类型时，对于 Not-a-Number(NaN) 需要做一些特殊处理。具体如下 : 

NaN = NaN 返回 true。 

在聚合操作中，所有 NaN 值都被分到同一组。 

在 join key 中 NaN 可以当做一个普通的值。 

NaN 值在升序排序中排到最后，比任何其他数值都大。

Spark SQL 概述

SQL

Datasets 和 DataFrames

Spark SQL 是 Spark 处理结构化数据的一个模块。与基础的 Spark RDD API 不同，Spark SQL 提供了查询结构化数据及计算结果等信息的接口。在内部，Spark SQL 使用这个额外的信息去执行额外的优化。有几种方式可以跟 Spark SQL 进行交互，包括 SQL 和 Dataset API。当使用相同执行引擎进行计算时，无论使用哪种 API / 语言都可以快速的计算。这种统一意味着开发人员能够在基于提供最自然的方式来表达一个给定的 transformation API 之间实现轻松的来回切换不同的 。

该页面所有例子使用的示例数据都包含在 Spark 的发布中，并且可以使用 spark-shell，pyspark，或者 sparkR 来运行。

SQL

Spark SQL 的功能之一是执行 SQL 查询。Spark SQL 也能够被用于从已存在的 Hive 环境中读取数据。更多关于如何配置这个特性的信息，请参考 Hive 表 这部分。当以另外的编程语言运行 SQL 时，查询结果将以 Dataset / DataFrame 的形式返回。您也可以使用 命令行 或者通过 JDBC / ODBC 与 SQL 接口交互。

Datasets 和 DataFrames

一个 Dataset 是一个分布式的数据集合。Dataset 是在 Spark 1.6 中被添加的新接口，它提供了 RDD 的优点（强类型化，能够使用强大的 lambda 函数）与Spark SQL执行引擎的优点。一个 Dataset 可以从 JVM 对象来构造并且使用转换功能（map，flatMap，filter，等等）。Dataset API 在 Scala 和 Java 中是可用的。Python 不支持 Dataset API。但是由于 Python 的动态特性，许多 Dataset API 的有点已经可用了（也就是说，你可能通过 name 天生的 row.columnName 属性访问一行中的字段）。这种情况和 R 相似。

一个 DataFrame 是一个 Dataset 组织成的指定列。它的概念与一个在关系型数据库或者在 R/Python 中的表是相等的，但是有更多的优化。DataFrame 可以从大量的 Source 中构造出来，像 : 结构化的数据文件，Hive 中的表，外部的数据库，或者已存在的 RDD。DataFrame API 在 Scala，Java，Python 和 R 中是可用的。在 Scala 和 Java 中，一个 DataFrame 所代表的是一个多个 Row（行）的 Dataset。在 Scala API 中，DataFrame 仅仅是一个 Dataset[Row] 类型的别名 。然而，在 Java API 中，用户需要去使用 Dataset<Row> 来表示 DataFrame。

在这个文档中，我们将常常会引用 Scala/Java  的 Dataset 的 Row（行）作为 DataFrame。

Spark SQL 入门指南

起始点 : SparkSession

创建 DataFrame

无类型 Dataset 操作（aka DataFrame 操作）

以编程的方式运行 SQL 查询

创建 Dataset

RDD 的互操作性

使用反射推断 Schema

以编程的方式指定 Schema

起始点 : SparkSession

Spark SQL中所有功能的入口点是 SparkSession 类。去创建一个基本的 SparkSession，仅使用 SparkSession.builder() : 

import org.apache.spark.sql.SparkSession
val spark = SparkSession
  .builder()
  .appName("Spark SQL Example")
  .config("spark.some.config.option", "some-value")
  .getOrCreate()

// For implicit conversions like converting RDDs to DataFrames
import spark.implicits._

// 所有的示例代码可以在 Spark repo 的 “examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala” 中找到。

在 Spark 2.0 中 SparkSession 为 Hive 特性提供了内嵌的支持，包括使用 HiveQL 编写查询的能力，访问 Hive UDF，以及从 Hive 表中读取数据的能力。为了使用这些特性，你不需要去有一个已存在的 Hive 设置。

创建 DataFrame

与一个 SparkSession 一起，应用程序可以从一个 已存在的 RDD（   中从一个百度的 R data.frame），或者一个 Hive 表中，或者从 Spark 数据源 中创建 DataFrame。

举个例子，下面基于一个 JSON 文件的内容创建一个 DataFrame : 

val df = spark.read.json("examples/src/main/resources/people.json")

// Displays the content of the DataFrame to stdout
df.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

// 所有的示例代码可以在 Spark repo 的 “examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala” 中找到。

无类型 Dataset 操作（aka DataFrame 操作）

DataFrame 提供了一个 DSL（domain-specific language）用于在 Scala，Java，Python 或者 R 中的结构化数据操作。

正如上面提到的一样，Spark 2.0 中 DataFrame 在 Scala 和 JavaAPI 中仅仅是多个 Row（行）的 Dataset 。这些操作也参考了与强类型的 Scala/Java Datasets 的 “类型转换” 相对应的 “无类型转换”。

这里包括一些使用 Dataset 进行结构化数据处理的示例 : 

// This import is needed to use the $-notation
import spark.implicits._
// Print the schema in a tree format
df.printSchema()
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)

// Select only the "name" column
df.select("name").show()
// +-------+
// |   name|
// +-------+
// |Michael|
// |   Andy|
// | Justin|
// +-------+

// Select everybody, but increment the age by 1
df.select($"name", $"age" + 1).show()
// +-------+---------+
// |   name|(age + 1)|
// +-------+---------+
// |Michael|     null|
// |   Andy|       31|
// | Justin|       20|
// +-------+---------+

// Select people older than 21
df.filter($"age" > 21).show()
// +---+----+
// |age|name|
// +---+----+
// | 30|Andy|
// +---+----+

// Count people by age
df.groupBy("age").count().show()
// +----+-----+
// | age|count|
// +----+-----+
// |  19|    1|
// |null|    1|
// |  30|    1|
// +----+-----+

// 所有的示例代码可以在 Spark repo 的 “examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala” 中找到。

能够在 DataFrame 上被执行的操作类型的完整列表请参考 API 文档。

除了简单的列引用和表达式之外，DataFrame 也有丰富的函数库，包括 string 操作，date 算术，常见的 math 操作以及更多。可用的完整列表请参考 DataFrame 函数参考。

以编程的方式运行 SQL 查询

// SparkSession 使应用程序的 SQL 函数能够以编程的方式运行 SQL 查询并且将查询结果以一个 DataFrame。

// Register the DataFrame as a SQL temporary view
df.createOrReplaceTempView("people")

val sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

// 所有的示例代码可以在 Spark repo 的 “examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala” 中找到。

创建 Dataset

Dataset 与 RDD 相似，然而，并不是使用 Java 序列化或者 Kryo，他们使用一个指定的 Encoder（编码器） 来序列化用于处理或者通过网络进行传输的对象。虽然编码器和标准的序列化都负责将一个对象序列化成字节，编码器是动态生成的代码，并且使用了一种允许 Spark 去执行许多像 filtering，sorting 以及 hashing 这样的操作，不需要将字节反序列化成对象的格式。

// Note: Case classes in Scala 2.10 can support only up to 22 fields. To work around this limit,
// you can use custom classes that implement the Product interface
case class Person(name: String, age: Long)

// Encoders are created for case classes
val caseClassDS = Seq(Person("Andy", 32)).toDS()
caseClassDS.show()
// +----+---+
// |name|age|
// +----+---+
// |Andy| 32|
// +----+---+

// Encoders for most common types are automatically provided by importing spark.implicits._
val primitiveDS = Seq(1, 2, 3).toDS()
primitiveDS.map(_ + 1).collect() // Returns: Array(2, 3, 4)

// DataFrames can be converted to a Dataset by providing a class. Mapping will be done by name
val path = "examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path).as[Person]
peopleDS.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

// 所有的示例代码可以在 Spark repo 的 “examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala” 中找到。

RDD 的互操作性

使用反射推断 Schema

以编程的方式指定 Schema

Spark SQL 支持两种不同的方法用于转换已存在的 RDD 成为 Dataset。

第一种方法是使用反射去推断一个包含指定的对象类型的 RDD 的 Schema。在你的 Spark 应用程序中当你已知 Schema 时这个基于方法的反射可以让你的代码更简洁。

第二种用于创建 Dataset 的方法是通过一个允许你构造一个 Schema 然后把它应用到一个已存在的 RDD 的编程接口。然而这种方法更繁琐，当列和它们的类型知道运行时都是未知时它允许你去构造 Dataset。

数据源

通用的 Load/Save 函数

手动指定选项

直接在文件上运行 SQL

保存模式

保存为持久化的表

Parquet文件

以编程的方式加载数据

分区发现

Schema 合并

Hive metastore Parquet 表转换

Hive/Parquet Schema Reconciliation

Metadata 刷新

Parquet 配置

JSON Datasets

Hive 表

与不同版本的 Hive Metastore 交互

JDBC 连接其它数据库

故障排除

Spark SQL 支持通过 DataFrame 接口操作多种数据源。一个 DataFrame 可以通过关联转换来操作，也可以被创建为一个临时的 view。注册一个 DataFrame 作为一个临时的 view 就可以允许你在数据集上运行 SQL 查询。本节介绍了一些通用的方法通过使用 Spark Data Sources 来加载和保存数据以及一些可用的内置数据源的特定选项。

通用的 Load/Save 函数

手动指定选项

直接在文件上运行 SQL

保存模式

保存为持久化的表

在最简单的方式下，默认的数据源（parquet 除非另外配置通过spark.sql.sources.default）将会用于所有的操作。

val usersDF = spark.read.load("examples/src/main/resources/users.parquet")
usersDF.select("name", "favorite_color").write.save("namesAndFavColors.parquet")

完整的示例代码在 "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala"。

Parquet文件

以编程的方式加载数据

分区发现

Schema 合并

Hive metastore Parquet 表转换

Hive/Parquet Schema Reconciliation

Metadata 刷新

Parquet 配置

Parquet 是一个列式存储格式的文件，被许多其他数据处理系统所支持。Spark SQL 支持对 Parquet 文件的读写还可以自动的保存源数据的模式

JSON Datasets

Spark SQL 可以自动的推断出 JSON 数据集的 schema 并且将它作为 DataFrame 进行加载。这个转换可以通过使用 SparkSession.read.json() 在字符串类型的 RDD 中或者 JSON 文件。

注意作为 json file 提供的文件不是一个典型的 JSON 文件。每一行必须包含一个分开的独立的有效 JSON 对象。因此，常规的多行 JSON 文件通常会失败。

 // A JSON dataset is pointed to by path.
// The path can be either a single text file or a directory storing text files
val path = "examples/src/main/resources/people.json"
val peopleDF = spark.read.json(path)

// The inferred schema can be visualized using the printSchema() method
peopleDF.printSchema()
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)

// Creates a temporary view using the DataFrame
peopleDF.createOrReplaceTempView("people")

// SQL statements can be run by using the sql methods provided by spark
val teenagerNamesDF = spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19")
teenagerNamesDF.show()
// +------+
// | name|
// +------+
// |Justin|
// +------+

// Alternatively, a DataFrame can be created for a JSON dataset represented by
// an RDD[String] storing one JSON object per string
val otherPeopleRDD = spark.sparkContext.makeRDD(
  """{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil)
val otherPeople = spark.read.json(otherPeopleRDD)
otherPeople.show()
// +---------------+----+
// | address|name|
// +---------------+----+
// |[Columbus,Ohio]| Yin|
// +---------------+----+

Hive 表

与不同版本的 Hive Metastore 交互

Spark SQL 还支持在 Apache Hive 中读写数据。然而，由于 Hive 依赖项太多，这些依赖没有包含在默认的 Spark 发行版本中。如果在 classpath 上配置了 Hive 依赖，那么 Spark 会自动加载它们。注意，Hive 依赖也必须放到所有的 worker 节点上，因为如果要访问 Hive 中的数据它们需要访问 Hive 序列化和反序列化库（SerDes)。

Hive 配置是通过将 hive-site.xml，core-site.xml（用于安全配置）以及 hdfs-site.xml（用于HDFS 配置）文件放置在conf/目录下来完成的。

如果要使用 Hive, 你必须要实例化一个支持 Hive 的 SparkSession，包括连接到一个持久化的 Hive metastore, 支持 Hive 序列化反序列化库以及 Hive 用户自定义函数。即使用户没有安装部署 Hive 也仍然可以启用 Hive 支持。如果没有在 hive-site.xml 文件中配置，Spark 应用程序启动之后，上下文会自动在当前目录下创建一个 metastore_db 目录并创建一个由 spark.sql.warehouse.dir 配置的、默认值是当前目录下的 spark-warehouse 目录的目录。请注意 : 从 Spark 2.0.0 版本开始，hive-site.xml 中的 hive.metastore.warehouse.dir 属性就已经过时了，你可以使用 spark.sql.warehouse.dir 来指定仓库中数据库的默认存储位置。你可能还需要给启动 Spark 应用程序的用户赋予写权限。

import org.apache.spark.sql.Row
import org.apache.spark.sql.SparkSession

case class Record(key: Int, value: String)

// warehouseLocation points to the default location for managed databases and tables
val warehouseLocation = "file:${system:user.dir}/spark-warehouse"

val spark = SparkSession
  .builder()
  .appName("Spark Hive Example")
  .config("spark.sql.warehouse.dir", warehouseLocation)
  .enableHiveSupport()
  .getOrCreate()

import spark.implicits._
import spark.sql

sql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING)")
sql("LOAD DATA LOCAL INPATH 'examples/src/main/resources/kv1.txt' INTO TABLE src")

// Queries are expressed in HiveQL
sql("SELECT * FROM src").show()
// +---+-------+
// |key|  value|
// +---+-------+
// |238|val_238|
// | 86| val_86|
// |311|val_311|
// ...

// Aggregation queries are also supported.
sql("SELECT COUNT(*) FROM src").show()
// +--------+
// |count(1)|
// +--------+
// |    500 |
// +--------+

// The results of SQL queries are themselves DataFrames and support all normal functions.
val sqlDF = sql("SELECT key, value FROM src WHERE key < 10 ORDER BY key")

// The items in DaraFrames are of type Row, which allows you to access each column by ordinal.
val stringsDS = sqlDF.map {
  case Row(key: Int, value: String) => s"Key: $key, Value: $value"
}
stringsDS.show()
// +--------------------+
// |               value|
// +--------------------+
// |Key: 0, Value: val_0|
// |Key: 0, Value: val_0|
// |Key: 0, Value: val_0|
// ...

// You can also use DataFrames to create temporary views within a HiveContext.
val recordsDF = spark.createDataFrame((1 to 100).map(i => Record(i, s"val_$i")))
recordsDF.createOrReplaceTempView("records")

// Queries can then join DataFrame data with data stored in Hive.
sql("SELECT * FROM records r JOIN src s ON r.key = s.key").show()
// +---+------+---+------+
// |key| value|key| value|
// +---+------+---+------+
// |  2| val_2|  2| val_2|
// |  2| val_2|  2| val_2|
// |  4| val_4|  4| val_4|
// ...

JDBC 连接其它数据库

Spark SQL 还有一个能够使用 JDBC 从其他数据库读取数据的数据源。当使用 JDBC 访问其它数据库时，应该首选 JdbcRDD。这是因为结果是以数据框（DataFrame）返回的，且这样 Spark SQL操作轻松或便于连接其它数据源。因为这种 JDBC 数据源不需要用户提供 ClassTag，所以它也更适合使用 Java 或 Python 操作。（注意，这与允许其它应用使用 Spark SQL 执行查询操作的 Spark SQL JDBC 服务器是不同的）。

使用 JDBC 访问特定数据库时，需要在 spark classpath 上添加对应的 JDBC 驱动配置。例如，为了从 Spark Shell 连接 postgres，你需要运行如下命令 : 

bin/spark-shell --driver-class-path postgresql-9.4.1207.jar --jars postgresql-9.4.1207.jar

通过调用数据源API，远程数据库的表可以被加载为DataFrame 或Spark SQL临时表。支持的参数有 : 

属性名 | 含义
url | 要连接的 JDBC URL。
dbtable | 要读取的 JDBC 表。 注意，一个 SQL 查询的 From 分语句中的任何有效表都能被使用。例如，既可以是完整表名，也可以是括号括起来的子查询语句。
driver | 用于连接 URL 的 JDBC 驱动的类名。
partitionColumn, lowerBound, upperBound, numPartitions | 这几个选项，若有一个被配置，则必须全部配置。它们描述了当从多个 worker 中并行的读取表时，如何对它分区。partitionColumn 必须时所查询表的一个数值字段。注意，lowerBound 和 upperBound 都只是用于决定分区跨度的，而不是过滤表中的行。因此，表中的所有行将被分区并返回。
fetchSize | JDBC fetch size，决定每次读取多少行数据。 默认将它设为较小值（如，Oracle上设为 10）有助于 JDBC 驱动上的性能优化。

代码示例如下 : 

val jdbcDF = spark.read
  .format("jdbc")
  .option("url", "jdbc:postgresql:dbserver")
  .option("dbtable", "schema.tablename")
  .option("user", "username")
  .option("password", "password")
  .load()

CREATE TEMPORARY VIEW jdbcTable
USING org.apache.spark.sql.jdbc
OPTIONS (
  url "jdbc:postgresql:dbserver",
  dbtable "schema.tablename"
)

故障排除

在客户端会话（client session）中或者所有 executor 上，JDBC 驱动类必须可见于原生的类加载器。这是因为 Java 的驱动管理（DriverManager ）类在打开一个连接之前会做一个安全检查，这就导致它忽略了所有对原生类加载器不可见的驱动。一个方便的方法，就是修改所有 worker 节点上的 compute_classpath.sh 以包含你的驱动 Jar 包。

一些数据库，如 H2，会把所有的名称转为大写。在 Spark SQL 中你也需要使用大写来引用这些名称。

性能调优

缓存数据到内存

其它配置选项

对一些工作负载，可能的性能改进的方式，不是把数据缓存在内存里，就是调整一些试验选项。

缓存数据到内存

Spark SQL 可以通过调用 spark.cacheTable("tableName") 或者 dataFrame.cache() 以列存储格式缓存表到内存中。随后，Spark SQL 将会扫描必要的列，并自动调整压缩比例，以减少内存占用和 GC 压力。你可以调用 spark.uncacheTable("tableName") 来删除内存中的表。

你可以在 SparkSession 上使用 setConf 方法或在 SQL 语句中运行 SET key=value 命令，来配置内存中的缓存。

属性名 | 默认值 | 含义
spark.sql.inMemoryColumnarStorage.compressed | true | 当设置为 true 时，Spark SQL 将会基于数据的统计信息自动地为每一列选择单独的压缩编码方式。
spark.sql.inMemoryColumnarStorage.batchSize | 10000 | 控制列式缓存批量的大小。当缓存数据时，增大批量大小可以提高内存利用率和压缩率，但同时也会带来 OOM（Out Of Memory）的风险。

其它配置选项

下面的选项也可以用来提升查询执行的性能。随着 Spark 自动地执行越来越多的优化操作，这些选项在未来的发布版本中可能会过时。

属性名 | 默认值 | 含义
spark.sql.files.maxPartitionBytes | 134217728 (128 MB) | 读取文件时单个分区可容纳的最大字节数。
spark.sql.files.openCostInBytes | 4194304 (4 MB) | 打开文件的估算成本，按照同一时间能够扫描的字节数来测量。当往一个分区写入多个文件的时候会使用。高估更好, 这样的话小文件分区将比大文件分区更快 (先被调度)。
spark.sql.autoBroadcastJoinThreshold | 10485760 (10 MB) | 配置一个表在执行 join 操作时能够广播给所有 worker 节点的最大字节大小。通过将这个值设置为-1，可以禁用广播。注意，目前的数据统计仅支持已经运行了 ANALYZE TABLE <tableName> COMPUTE STATISTICS noscan 命令的 Hive metastore 表。
spark.sql.shuffle.partitions | 200 | 配置为连接或聚合操作混洗（shuffle）数据时使用的分区数。

分布式 SQL引擎

运行 Thrift JDBC/ODBC server

运行 Spark SQL CLI

通过使用 Spark SQ L的 JDBC/ODBC 或者命令行接口，它还可以作为一个分布式查询引擎。在这种模式下，终端用户或应用程序可以运行 SQL 查询来直接与 Spark SQL 交互，而不需要编写任何代码。

运行 Thrift JDBC/ODBC server

这里实现的 Thrift JDBC/ODBC server 对应于 Hive 1.2.1 版本中的 HiveServer2。你可以使用 Spark 或者 Hive 1.2.1 自带的 beeline 脚本来测试这个 JDBC server。

要启动 JDBC/ODBC server， 需要在 Spark 安装目录下运行如下命令 : 

./sbin/start-thriftserver.sh

这个脚本能接受所有 bin/spark-submit 命令行选项，外加一个用于指定 Hive 属性的 --hiveconf 选项。你可以运行 ./sbin/start-thriftserver.sh --help 来查看所有可用选项的完整列表。默认情况下，这启动的 server 将会在 localhost:10000 上进行监听。你可以覆盖该行为，比如使用以下环境变量 : 

export HIVE_SERVER2_THRIFT_PORT=<listening-port>
export HIVE_SERVER2_THRIFT_BIND_HOST=<listening-host>
./sbin/start-thriftserver.sh \
  --master <master-uri> \
  ...

或者系统属性 : 

./sbin/start-thriftserver.sh \
  --hiveconf hive.server2.thrift.port=<listening-port> \
  --hiveconf hive.server2.thrift.bind.host=<listening-host> \
  --master <master-uri>
  ...

现在你可以使用 beeline 来测试这个Thrift JDBC/ODBC server : 

./bin/beeline

在 beeline 中使用以下命令连接到 JDBC/ODBC server : 

beeline> !connect jdbc:hive2://localhost:10000

Beeline 会要求你输入用户名和密码。在非安全模式下，只需要输入你本机的用户名和一个空密码即可。对于安全模式，请参考 beeline 文档中的指示。

将 hive-site.xml，core-site.xml 以及 hdfs-site.xml 文件放置在 conf 目录下可以完成 Hive 配置。

你也可以使用 Hive 自带的 beeline 的脚本。

Thrift JDBC server 还支持通过 HTTP 传输来发送 Thrift RPC 消息。使用下面的设置作为系统属性或者对 conf 目录中的 hive-site.xml 文件配置来启用 HTTP 模式 : 

hive.server2.transport.mode - Set this to value: http
hive.server2.thrift.http.port - HTTP port number fo listen on; default is 10001
hive.server2.http.endpoint - HTTP endpoint; default is cliservice

为了测试，在 HTTP 模式中使用 beeline 连接到 JDBC/ODBC server : 

beeline> !connect jdbc:hive2://<host>:<port>/<database>?hive.server2.transport.mode=http;hive.server2.thrift.http.path=<http_endpoint>

运行 Spark SQL CLI

Spark SQL CLI 是一个很方便的工具，它可以在本地模式下运行 Hive metastore 服务，并且执行从命令行中输入的查询语句。注意 : Spark SQL CLI 无法与 Thrift JDBC server 通信。

要启动 Spark SQL CLI，可以在 Spark 安装目录运行下面的命令 :

./bin/spark-sql

将 hive-site.xml，core-site.xml 以及 hdfs-site.xml 文件放置在 conf 目录下可以完成 Hive 配置。你可以运行 ./bin/spark-sql --help 来获取所有可用选项的完整列表。

迁移指南

从 Spark SQL 1.6 升级到 2.0

从 Spark SQL 1.5 升级到 1.6

从 Spark SQL 1.4 升级到 1.5

从 Spark SQL 1.3 升级到 1.4

从 Spark SQL 1.0~1.2 升级到 1.3

兼容 Apache Hive

从 Spark SQL 1.6 升级到 2.0

SparkSession 现在是 Spark 新的切入点, 它替代了老的 SQLContext 和 HiveContext。注意 : 为了向下兼容，老的 SQLContext 和 HiveContext 仍然保留。可以从 SparkSession 获取一个新的 catalog 接口—— 现有的访问数据库和表的 API，如 listTables，createExternalTable， dropTempView，cacheTable 都被移到该接口。

Dataset API 和 DataFrame API 进行了统一。在 Scala 中，DataFrame 变成了 Dataset[Row] 类型的一个别名，而 Java API 使用者必须将 DataFrame 替换成 Dataset<Row>。Dataset 类既提供了强类型转换操作（如 map，filter 以及 groupByKey）也提供了非强类型转换操作（如 select 和 groupBy）。由于编译期的类型安全不是 Python 和 R 语言的一个特性，Dataset 的概念并不适用于这些语言的 API。相反，DataFrame 仍然是最基本的编程抽象, 就类似于这些语言中单节点数据帧的概念。

Dataset 和 DataFrame API 中 unionAll 已经过时并且由 union 替代。

Dataset 和 DataFrame API 中 explode 已经过时，作为选择，可以结合 select 或 flatMap 使用 functions.explode() 。

Dataset 和 DataFrame API 中 registerTempTable 已经过时并且由 createOrReplaceTempView 替代。

从 Spark SQL 1.5 升级到 1.6

Spark 1.6 中，默认情况下服务器在多会话模式下运行。这意味着每个 JDBC / ODBC 连接拥有一份自己的 SQL 配置和临时注册表。缓存表仍在并共享。如果你想在单会话模式服务器运行，请设置选项 spark.sql.hive.thriftServer.singleSession 为 true。您既可以将此选项添加到 spark-defaults.conf，或者通过 --conf 将它传递给 start-thriftserver.sh。

./sbin/start-thriftserver.sh \
     --conf spark.sql.hive.thriftServer.singleSession=true \
     ...

从 1.6.1 开始，在 sparkR 中 withColumn 方法支持添加一个新列或更换数据框同名的现有列。

从 Spark 1.6 开始，LongType 强制转换为 TimestampType 秒，而不是微秒。这一变化是为了匹配 Hive 1.2 ，保证从数值类型转换到 TimestampType 的一致性。见 SPARK-11724 了解详情。

从 Spark SQL 1.4 升级到 1.5

使用手动管理的内存优化执行，现在是默认启用的，以及代码生成表达式求值。这些功能既可以通过设置 spark.sql.tungsten.enabled 为 false 来禁止使用。

Parquet 的模式合并默认情况下不再启用。它可以通过设置重新启用 spark.sql.parquet.mergeSchema 到 true。

字符串在 Python 列的分辨率现在支持使用点（.）来限定列或访问嵌套值。例如 df['table.column.nestedField']。但是，这意味着如果你的列名中包含任何圆点，你现在必须避免使用反引号（如 table.`column.with.dots`.nested）。

在内存中的列存储分区修剪默认是开启的。它可以通过设置 spark.sql.inMemoryColumnarStorage.partitionPruning 为 false 来禁用。

无限精度的小数列不再支持，而不是 Spark SQL 最大精度为 38 。当从 BigDecimal 对象推断模式时，现在使用（38，18）。在 DDL 没有指定精度时，则默认保留 Decimal(10, 0)。

时间戳现在存储在 1 微秒的精度，而不是 1 纳秒的。

在 sql 语句中，浮点数现在解析为十进制。HiveQL 解析保持不变。

SQL/DateFrame 数据帧功能的规范名称现在是小写（e.g. sum vs SUM）。

JSON 数据源不会自动加载由其他应用程序（未通过 Spark SQL 插入到数据集的文件）创建的新文件。对于 JSON 持久表（即表的元数据存储在 Hive Metastore），用户可以使用 REFRESH TABLE SQL 命令或 HiveContext 的 refreshTable 方法，把那些新文件列入到表中。对于代表一个 JSON 数据集的数据帧，用户需要重新创建数据框，同时数据框中将包括新的文件。

PySpark DataFrame 的 withColumn 方法支持添加新的列或替换现有的同名列。

从 Spark SQL 1.3 升级到 1.4

数据帧的数据读/写器接口

根据用户的反馈，我们创建了一个新的更快速的 API 中读取数据 (SQLContext.read）和写入数据(DataFrame.write)。同时废弃的过时的API（例如SQLContext.parquetFile，SQLContext.jsonFile）。

请参阅 API 文档 SQLContext.read(Scala, Java, Python )和 DataFrame.write(Scala , Java, Python)的更多信息。

DataFrame.groupBy 保留分组列

根据用户反馈，我们改变的默认行为 DataFrame.groupBy().agg() 保留在 DataFrame 的分组列。为了维持 1.3 的行为特征，设置 spark.sql.retainGroupColumns 为 false。

// 在 1.3.x 中, 为了让"department"列得到展示，
// 必须包括明确作为 gg 函数调用的一部分。
df.groupBy("department").agg($"department", max("age"), sum("expense"))

// 在 1.4 以上版本, "department" 列自动包含了.
df.groupBy("department").agg(max("age"), sum("expense"))

// 恢复到 1.3 版本（不保留分组列）
sqlContext.setConf("spark.sql.retainGroupColumns", "false")

在 DataFrame.withColumn 中的改变

之前 1.4 版本中，DataFrame.withColumn() 只支持添加列。该列将始终在 DateFrame 结果中被加入作为新的列，即使现有的列可能存在相同的名称。从 1.4 版本开始，DataFrame.withColumn() 支持添加与所有现有列的名称不同的列或替换现有的同名列。

请注意，这一变化仅适用于 Scala API，并不适用于 PySpark 和 SparkR。

从 Spark SQL 1.0~1.2 升级到 1.3

在 Spark 1.3 中，我们从 Spark SQL 中删除了 "Alpha" 的标签，作为一部分已经清理过的可用的 API 。从 Spark 1.3 版本以上，Spark SQL 将提供在 1.X 系列的其他版本的二进制兼容性。这种兼容性保证不包括被明确标记为不稳定的（即 DeveloperApi 类或 Experimental）的 API。

重命名 SchemaRDD 到 DateFrame

升级到 Spark SQL 1.3 版本时，用户会发现最大的变化是，SchemaRDD 已更名为 DataFrame。这主要是因为 DataFrames 不再从 RDD 直接继承，而是由 RDDS 自己来实现这些功能。DataFrames 仍然可以通过调用 .rdd 方法转换为 RDDS 。

在 Scala 中，有一个从 SchemaRDD 到 DataFrame 类型别名，可以为一些情况提供源代码兼容性。它仍然建议用户更新他们的代码以使用 DataFrame 来代替。Java 和 Python 用户需要更新他们的代码。

在 Java 和 Scala API 的统一

此前 Spark 1.3 有单独的Java兼容类（JavaSQLContext 和 JavaSchemaRDD），借鉴于 Scala API。在 Spark 1.3 中，Java API 和 Scala API 已经统一。两种语言的用户可以使用 SQLContext 和 DataFrame 。一般来说论文类尝试使用两种语言的共有类型（如 Array 替代了一些特定集合）。在某些情况下不通用的类型情况下，（例如，passing in closures 或 Maps）使用函数重载代替。

此外，该 Java 的特定类型的 API 已被删除。Scala 和 Java 的用户可以使用存在于 org.apache.spark.sql.types 类来描述编程模式。

隐式转换和 DSL 包的移除（仅限于 Scala）

许多 Spark 1.3 版本以前的代码示例都以 import sqlContext._ 开始，这提供了从 sqlContext 到 cope 的所有功能。在 Spark 1.3 中，我们移除了从 RDDs 到 DateFrame 再到 SQLContext 内部对象的隐式转换。

此外，隐式转换现在只是通过 toDF 方法增加 RDDs 所组成的一些类型（例如 classes 或 tuples），而不是自动应用。

当使用 DSL 的内部函数（现在由 DataFrame API 代替）的时候，用于一般会导入 org.apache.spark.sql.catalyst.dsl 来代替一些公有的 DataFrame 的 API 函数 : import org.apache.spark.sql.functions._。

删除在 org.apache.spark.sql 包中的一些 DataType 别名（仅限于 Scala）

Spark 1.3 移除存在于基本 SQL 包的 DataType 类型别名。开发人员应改为导入类 org.apache.spark.sql.types。

UDF 注册迁移到 sqlContext.udf 中 (针对Java和Scala)

用于注册 UDF 的函数，不管是 DataFrame DSL 还是 SQL 中用到的，都被迁移到 SQLContext 中的 udf对象中。

sqlContext.udf.register("strLen", (s: String) => s.length())

Python UDF 注册保持不变。

Python 的 DataType 不再是单例的

在 Python 中使用 DataTypes 时，你需要先构造它们（如：StringType()），而不是引用一个单例对象。

兼容 Apache Hive

Spark SQL 在设计时就考虑到了和 Hive metastore，SerDes 以及 UDF 之间的兼容性。目前 Hive SerDes 和 UDF 都是基于Hive 1.2.1版本，并且Spark SQL 可以连接到不同版本的Hive metastore（从 0.12.0 到 1.2.1，可以参考［与不同版本的Hive Metastore交互］）

在现有的 Hive 仓库中部署

Spark SQL Thrift JDBC server 采用了开箱即用的设计以兼容已有的 Hive 安装版本。你不需要修改现有的 Hive Metastore , 或者改变数据的位置和表的分区。

支持 Hive 的特性

Spark SQL 支持绝大部分的 Hive 功能，如 :  

Hive 查询语句, 包括 :

SELECT 

GROUP BY 

ORDER BY 

CLUSTER BY 

SORT BY 

所有的Hive运算符， 包括 :

关系运算符 (=, ⇔, ==, <>, <, >, >=, <=, etc) 

算术运算符 (+, -, *, /, %, etc) 

逻辑运算符 (AND, &&, OR, ||, etc) 

复杂类型构造器 - 数学函数 (sign, ln, cos等) 

String 函数 (instr, length, printf等) 

用户自定义函数（UDF） 

用户自定义聚合函数（UDAF） 

用户自定义序列化格式（SerDes） 

窗口函数 

Joins 

JOIN 

{LEFT|RIGHT|FULL} OUTER JOIN 

LEFT SEMI JOIN - CROSS JOIN 

Unions

子查询 

SELECT col FROM ( SELECT a + b AS col from t1) t2 

采样 

Explain 

分区表，包括动态分区插入 

视图 

所有 Hive DDL 功能, 包括 :

CREATE TABLE 

CREATE TABLE AS SELECT 

ALTER TABLE 

绝大多数Hive数据类型，包括

TINYINT 

SMALLINT 

INT 

BIGINT 

BOOLEAN 

FLOAT 

DOUBLE 

STRING

BINARY 

TIMESTAMP 

DATE 

ARRAY<> 

MAP<> 

STRUCT<>

不支持的 Hive 功能

以下是目前还不支持的Hive功能列表。在Hive部署中这些功能大部分都用不到。

Hive 核心功能

bucket : bucket 是 Hive 表分区内的一个哈希分区，Spark SQL目前还不支持 bucket。

Hive 高级功能

UNION 类型

Unique join

列统计数据收集 : Spark SQL目前不依赖扫描来收集列统计数据并且仅支持填充 Hive metastore 的 sizeInBytes 字段。

Hive 输入输出格式

CLI文件格式 : 对于回显到CLI中的结果，Spark SQL仅支持TextOutputFormat。

Hadoop archive

Hive 优化

有少数 Hive 优化还没有包含在 Spark 中。其中一些（比如索引）由于 Spark SQL 的这种内存计算模型而显得不那么重要。另外一些在 Spark SQL 未来的版本中会持续跟踪。

块级别位图索引和虚拟列（用来建索引）

自动为 join 和 groupBy 计算 reducer 个数 : 目前在 Spark SQL 中，你需要使用 ”SET spark.sql.shuffle.partitions=[num_tasks];” 

来控制后置混洗的并行程度。 

仅查询元数据 : 对于只需要使用元数据的查询请求，Spark SQL仍需要启动任务来计算结果。

数据倾斜标志 : Spark SQL 不遵循 Hive 中的数据倾斜标志

STREAMTABLE join操作提示 : Spark SQL 不遵循 STREAMTABLE 提示。 

对于查询结果合并多个小文件 : 如果返回的结果有很多小文件，Hive 有个选项设置，来合并小文件，以避免超过HDFS的文件数额度限制。Spark SQL 不支持这个。

参考

数据类型

NaN 语义

数据类型

Spark SQL 和 DataFrames 支持下面的数据类型 :  

数值类型 

ByteType : 表示 1 字节长的有符号整型，数值范围 : -128 到 127。

ShortType : 表示 2 字节长的有符号整型，数值范围 : -32768 到 32767。

IntegerType : 表示 4 字节长的有符号整型，数值范围 : -2147483648 到 2147483647。 

LongType : 表示 8 字节长的有符号整型，数值范围 : -9223372036854775808 到 9223372036854775807。

FloatType : 表示 4 字节长的单精度浮点数。 

DoubleType : 表示 8 字节长的双精度浮点数。

DecimalType : 表示任意精度有符号带小数的数值。内部使用 java.math.BigDecimal，一个BigDecimal 由一个任意精度的整数非标度值和一个 32 位的整数标度 (scale) 组成。

字符串类型 

StringType : 表示字符串值

二进制类型 

BinaryType : 表示字节序列值 

布尔类型 

BooleanType : 表示布尔值 

日期类型 

TimestampType : 表示包含年月日、时分秒等字段的日期值 

DateType : 表示包含年月日字段的日期值 

Complex types（复杂类型） 

 ArrayType(elementType, containsNull) : 数组类型，表示一个由类型为 elementType 的元素组成的序列，containsNull 用来表示 ArrayType 中的元素是否能为 null 值。

MapType(keyType, valueType, valueContainsNull) : 映射类型，表示一个键值对的集合。键的类型由 keyType 表示，值的类型则由 valueType 表示。对于一个 MapType 值，键是不允许为 null值。valueContainsNull 用来表示一个 MapType 的值是否能为 null 值。

StructType(fields) : 表示由 StructField 序列描述的结构。

StructField(name, datatype, nullable) : 表示 StructType 中的一个字段，name 表示字段名，datatype 是字段的数据类型，nullable 用来表示该字段是否可以为空值。

// Spark SQL的所有数据类型都放在 org.apache.spark.sql.types 这个包下。你可以这样获取它们 :
import org.apache.spark.sql.types._

完整示例代码参见 Spark 仓库中的 "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala”。

数据类型
Scala 中值的类型

用于获取或创建数据类型的 API

ByteType | Byte | ByteType
ShortType | Short | ShortType
IntegerType | Int | IntegerType
LongType | Long | LongType
FloatType | Float | FloatType
DoubleType | Double | DoubleType
DecimalType | java.math.BigDecimal | DecimalType
StringType | String | StringType
BinaryType | Array[Byte] | BinaryType
BooleanType | Boolean | BooleanType
TimestampType | java.sql.Timestamp | TimestampType
DateType | java.sql.Date | DateType
ArrayType | scala.collection.Seq | ArrayType(elementType, [containsNull]) 注意 : containsNull 的默认值是 true
MapType | scala.collection.Map | MapType(keyType, valueType, [valueContainsNull]) 注意 : valueContainsNull 的默认值是 true
StructType | org.apache.spark.sql.Row | StructType(fields) 注意 : fields 是一个 StructField 序列，另外不允许出现名称重复的字段。
StructField | Scala 中该字段的数据类型对应的值类型（例如，如果 StructField 的数据类型为 IntegerType，则 Scala 中其值类型为 Int） | list(name=name, type=dataType, nullable=nullable)

NaN 语义

当处理一些不符合标准浮点数语义的 float 或 double 类型时，对于 Not-a-Number(NaN) 需要做一些特殊处理。具体如下 : 

NaN = NaN 返回 true。 

在聚合操作中，所有 NaN 值都被分到同一组。 

在 join key 中 NaN 可以当做一个普通的值。 

NaN 值在升序排序中排到最后，比任何其他数值都大。

Structured Streaming

MLlib（机器学习）

机器学习库（MLlib）指南

ML Pipelines（ML管道）

Extracting, transforming and selecting features（特征的提取，转换和选择）

Classification and regression（分类和回归）

Clustering（聚类）

Collaborative Filtering（协同过滤）

ML Tuning: model selection and hyperparameter tuning（ML调优：模型选择和超参数调整）

Advanced topics（高级主题）

MLlib：基于RDD的API

Data Types - RDD-based API（数据类型）

Basic Statistics - RDD-based API（基本统计）

Classification and Regression - RDD-based API（分类和回归）

Linear Methods - RDD-based API（线性方法）

Naive Bayes - RDD-based API（朴素贝叶斯）

Decision Trees - RDD-based API（决策树）

Ensembles - RDD-based API（集成方法）

Regression - RDD-based API（回归）

Collaborative Filtering - RDD-based API（协同过滤）

Clustering - RDD-based API（聚类 - 基于RDD的API）

Dimensionality Reduction - RDD-based API（降维）

Feature Extraction and Transformation - RDD-based API（特征的提取和转换）

Frequent Pattern Mining - RDD-based API（频繁模式挖掘）

Evaluation metrics - RDD-based API（评估指标）

PMML model export - RDD-based API（PMML模型导出）

Optimization - RDD-based API（最优化）

机器学习库（MLlib）指南

原文链接 : http://spark.apache.org/docs/2.1.0/ml-guide.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2886337

贡献者 : 小白二号 片刻 ApacheCN Apache中文网

MLlib是Spark的机器学习（ML）库。其目标是使实用的机器学习具有可扩展性和容易性。在高水平上，它提供了以下工具：

ML算法：常用的学习算法，如分类，回归，聚类和协同过滤

特征：特征提取，变换，降维和选择

管道：构建，评估和调整ML管道的工具

持久性：保存和加载算法，模型和管道

实用程序：线性代数，统计，数据处理等

公告：基于DataFrame的API是主要的API

基于MLlib RDD的API现在处于维护模式。

从Spark 2.0开始，spark.mllib软件包中的基于RDD的API已进入维护模式。 Spark的主要学习API现在是spark.ml包中基于DataFrame的API。

有什么影响？

MLlib仍然会在spark.mllib中支持基于RDD的API，并提供错误修复。

MLlib不会为基于RDD的API添加新功能。

在Spark 2.x版本中，MLlib将向基于DataFrames的API添加功能，以实现与基于RDD的API的功能奇偶校验。

达到功能奇偶校验（大概估计为Spark 2.2）后，将不推荐使用基于RDD的API。

预计将在Spark 3.0中删除基于RDD的API。

为什么MLlib切换到基于DataFrame的API？

DataFrames提供比RDD更加用户友好的API。 DataFrames的许多好处包括Spark Datasources，SQL / DataFrame查询，Tungsten和Catalyst优化以及跨语言的统一API。

用于MLlib的基于DataFrame的API为ML算法和跨多种语言提供了统一的API。

DataFrames有助于实际的ML管道，特别是特征转换。有关详细信息，请参阅管道指南。

什么是“Spark ML”？

“Spark ML”不是官方名称，但偶尔用于引用基于MLlib DataFrame的API。这主要是由于基于DataFrame的API使用的org.apache.spark.ml Scala软件包名称以及我们最初用于强调管道概念的“Spark ML Pipelines”术语。

MLlib是否已弃用？

不，MLlib包括基于RDD的API和基于DataFrame的API。基于RDD的API现在处于维护模式。但是，API都不被弃用，也不是MLlib。

依赖关系

MLlib使用线性代数包Breeze，它取决于netlib-java进行优化的数值处理。如果native libraries1在运行时不可用，则会看到一条警告消息，而将使用纯JVM实现。

由于运行时专用二进制文件的许可问题，我们默认不包括netlib-java的本机代理。要将netlib-java/Breeze配置为使用系统优化的二进制文件，请将com.github.fommil.netlib：all：1.1.2（或构建Spark with -Pnetlib-lgpl）作为项目的依赖项，并读取netlib-java文档为您的平台的额外的安装说明。

要在Python中使用MLlib，您将需要NumPy 1.4或更高版本。

迁移指南

MLlib正在积极发展。标记为Experimental / DeveloperApi的API可能会在将来的版本中更改，下面的迁移指南将解释发行版之间的所有更改。

从2.0到2.1

突破变化

删除已弃用的方法

setLabelCol在feature.ChiSqSelectorModel中

numTrees在classification.RandomForestClassificationModel中（现在指的是Param叫numTrees）

numTrees在regression.RandomForestRegressionModel中（现在指的是Param叫numTrees）

model在regression.LinearRegressionSummary中

validateParams在PipelineStage中

validateParams在Evaluator中

废弃和行为变化

弃用

SPARK-18592：弃用所有Param setter方法，除了输入/输出列DecisionTreeClassificationModel的参数，GBTClassificationModel，RandomForestClassificationModel，DecisionTreeRegressionModel，GBTRegressionModel和RandomForestRegressionModel
行为变化

SPARK-17870：修正ChiSqSelector的错误，可能会改变其结果。现在，ChiSquareSelector使用pValue而不是raw statistic选择固定数量的顶级功能。
SPARK-3261：  如果k个不同的质心不可用或没有选择，KMeans会返回可能少于k个聚类中心。
SPARK-17389：K均值将k-means ||的默认步数从5减少到2初始化模式。

以前的Spark版本

此页面上存档了较早的迁移指南。 

1.要了解有关系统优化的本地人的好处和背景的更多信息，您可能希望观看Sam Halliday在Scala中的高性能线性代数的ScalaX演讲。 

ML Pipelines（ML管道）

在本节中，我们介绍了 ML Pipelines 的概念。ML Pipelines 提供了构建在 DataFrames 之上的一系列上层 API，帮助用户创建和调整切合实际的 ML Pipelines.

目录

原文链接 : http://spark.apache.org/docs/2.1.0/ml-pipeline.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2886339

贡献者 : 小白二号 片刻 程威 ApacheCN Apache中文网

Pipelines的主要概念

MLlib 将机器学习算法的API标准化，以便将多种算法更容易地组合成单个 Pipeline （管道）或者工作流。本节介绍Pipelines API 的关键概念,其中 Pipeline（管道）的概念主要是受到 scikit-learn 项目的启发.

DataFrame（数据模型）：ML API 将从Spark SQL查出来的 DataFrame 作为 ML 的数据集,数据集支持许多数据类型。例如,一个 DataFrame 可以有不同的列储存 text（文本）、feature（特征向量）、true labels（标注）、predictions（预测结果）等机器学习数据类型.

Transformer（转换器）：使用 Transformer 将一个 DataFrame 转换成另一个 DataFrame 的算法，例如，一个 ML Model 是一个 Transformer,它将带有特征的 DataFrame 转换成带有预测结果的 DataFrame.

Estimator（模型学习器）：Estimator 是一个适配 DataFrame 来生成 Transformer（转换器）的算法.例如,一个学习算法就是一个 Estimator 训练 DataFrame 并产生一个模型的过程.

Pipeline（管道）：Pipeline 将多个 Transformers 和 Estimators 绑在一起形成一个工作流.

Parameter（参数）：所有的 Transformers 和 Estimators 都已经使用标准的 API 来指定参数.

DataFrame

机器学习可以应用于各种各样的数据类型，比如向量，文本，图形和结构化数据.API 采用 Spark Sql 的 DataFrame 就是为了支持各种各样的数据类型.

DataFrame 支持许多基本的结构化的数据，参考Spark SQL datatype reference上的一系列支持的类型。另外除了 Spark SQL guide 列举的类型,DataFrame 还支持使用 ML Vector 类型.

DataFrame 可以用标准的 RDD 显式或者非显式创建。请参考下面的例子，或者前往 Spark SQL programming guide 查看例子

DataFrame 中的列是有名称的.下面的代码示例会使用名称如 "文本","特征"和"标签".

Pipelines 组件

Transformers（转换器）

 转换器是特征变换和机器学习模型的抽象。转换器必须实现transform方法，这个方法将一个 DataFrame 转换成另一个 DataFrame，通常是附加一个或者多个列。比如：

一个特征变换器是输入一个 DataFrame，读取一个列（比如：text），将其映射成一个新列（比如，特征向量），然后输出一个新的 DataFrame 并包含这个映射的列.

一个机器学习模型是输入一个 DataFrame，读取包含特征向量的列,预测每个特征向量的标签,并输出一个新的 DataFrame ，并附加预测标签作为一列.

Estimators（模型学习器）

Estimators 模型学习器是拟合和训练数据的机器学习算法或者其他算法的抽象。技术上来说, Estimator（模型学习器）实现 fit() 方法，这个方法输入一个 DataFrame 并产生一个 Model 即一个 Transformer（转换器）。举个例子，一个机器学习算法是一个 Estimator 模型学习器,比如这个算法是 LogisticRegression（逻辑回归），调用 fit() 方法训练出一个 LogisticRegressionModel,这是一个 Model,因此也是一个 Transformer（转换器）.

Pipeline组件的参数

Transformer.transform() 和 Estimator.fit() 都是无状态。以后,可以通过替换概念来支持有状态算法.

每一个 Transformer（转换器）和 Estimator （模型学习器）都有一个唯一的ID，这在指定参数上非常有用（将在下面进行讨论）.

Pipeline

在机器学习中,通常会执行一系列算法来处理和学习模型，比如，一个简单的文本文档处理流程可能包括这几个步骤：

把每个文档的文本分割成单词.

将这些单词转换成一个数值型特征向量.

使用特征向量和标签学习一个预测模型.

MLlib 代表一个流水线,就是一个 Pipeline（管道），Pipeline（管道） 包含了一系列有特定顺序的管道步骤（Transformers（转换器） 和 Estimators（模型学习器））。我们将使用这个简单的工作流作为本节的运行示例.

如何工作

一个 pipeline 由多个步骤组成，每一个步骤都是一个 Transformer（转换器）或者 Estimator（模型学习器）。这些步骤按顺序执行，输入的 DataFrame 在通过每个阶段时进行转换。在 Transformer （转换器）步骤中，DataFrame 会调用 transform() 方法；在 Estimator（模型学习器）步骤中，fit() 方法被调用并产生一个 Transformer（转换器）(会成为 PipelineModel（管道模型）的一部分,或者适配 Pipeline ),并且 DataFrame 会调用这个 Transformer’s（转换器）的transform()方法.

我们通过简单的文本文档工作流来说明.

下图是显示了使用管道的训练流程

以上,顶部的一行代表 Pipeline（管道）有三个步骤。第一第二个（Tokenizer（分词器）和 HashingTF（词频））是 Transformers（转换器）（蓝色的），第三个是 Estimator（模型学习器）（红色的）。底部的一行表示流经管道的数据,其中柱面表示 DataFrame。最初的 DataFrame 有少量的文本文档和标签, 会调用 Pipeline.fit() 方法.Tokenizer.transform()方法将原始文本分割成单词,并将这些单词作为一列添加到 DataFrame。接下HashingTF.transform() 方法将单词列转换成特征向量,并向 DataFrame 添加带有这些向量的新列。现在,由于LogisticRegression（逻辑回归）是一个模型学习器，Pipeline 会首先调用 LogisticRegression.fit() 来生成一个LogisticRegressionModel,然后 DataFrame 再调用 LogisticRegressionModel’s transform() 方法在 DataFrame传送到下一个步骤之前。

同时 Pipeline 也是一个模型学习器。因此,Pipeline’s fit() 运行完之后,会生成一个 PipelineModel,即一个Transformer（转换器）,PipelineModel 是用来在测试的时候使用。

下图说明了这种用法

在上图中，PipelineModel 有和原始的 Pipeline（管道）一样的步骤，但是在原始 Pipeline 所有的 Estimators （模型学习器）都会变成 Transformers（转换器）。当在测试集上调用 PipelineModel’s transform() 方法时,数据会按顺序通过装配的管道。每个步骤的 transform() 方法都会更新数据集并将 DataFrame 传递到下一个步骤.

Pipeline 和 PipelineModel 有助于确保了训练集和测试集经过相同的处理步骤.

细节

DAG（有向无环图）Pipelines：管道的步骤被定义为一个有序的数组。上面的例子是一个线性的管道即 Pipelines每个步骤使用的数据都产生于前一个步骤。只要数据流图能构成有向无环图，非线性 Pipelines 也可以创建出来。图当前是通过输每个步骤的输入输出列名来隐含指定的（通常指定为参数）.如果 Pipeline 构成了一个 DAG,则按照拓扑顺序指定阶段.

Runtime checking（运行时检查）：由于管道可以对具有不同类型的 DataFrames 进行操作,因此不能使用编译时类型检查。Pipelines 和PipelineModels 在真正运行 Pipeline 之前进行运行时检查。这个类型检查通过 DataFrame schema（描述了 DataFrame 列的数据类型）来完成的.

Unique Pipeline stages（唯一性管道阶段）：一个管道的阶段必须是一个唯一实例，比如，相同的实例 myHashingTF不能插入到 Pipeline 两次，因为管道的阶段的 ID 必须是唯一的。但是，不同的实例 myHashingTF1 和myHashingTF2（都是 HashingTF 类型）可以放进相同的 Pipeline,只要通过不同的 ID 创建不同的实例.

参数

MLib 的 Estimators（模型学习器）和 Transformers（转换器）使用统一的 API 来指定参数.

Param 是具有自包含定义的参数，ParamMap 是一组（参数,值）对.

将参数传递给算法主要有两种方式

给实例设置参数,例如,如果 lr 是一个 LogisticRegression实例,调用 lr.setMaxIter(10) 使 lr.fit() 最多10次迭代。这个 API 类似于 spark.mlib 包中的 API.

传递一个 ParamMap 给 fit() 函数和 transform() 函数。ParamMap 的任意参数将会覆盖前面调用实例通过 setter 方法指定的参数.

参数属于特定的 Estimators（模型学习器）和 Transformers（转换器）的实例。例如，如果我们有两个 LogisticRegression 实例 lr1 和 lr2，然后我们可以使用指定的 maxIter 参数来构建ParamMap：ParamMap(lr1.maxIter -> 10, lr2.maxIter -> 20)。如果 Pipeline 中存在两个 maxIter 参数的算法,这就非常有用.

保存和加载管道

通常情况下,将模型或管道保存到磁盘上以供将来使用是值得的。在 Spark 1.6，模型导入导出功能被加入到 Pipeline API。大多数基本的 transformers（转换器）是被支持的,包括一些更基本的ML Models。请根据算法 API 文档判断是否支持保存和加载.

示例代码

这部分将给出上述功能的代码示例。更多的信息,请参阅 API 文档 (Scala, Java, 和 Python).

例子：Estimator（模型学习器）,Transformer（转换器），Param（参数）

该例子涉及到 Estimator（训练器）、Transformer（转换器）和 Param（参数）的概念

Scala

import org.apache.spark.ml.classification.LogisticRegression
import org.apache.spark.ml.linalg.{Vector, Vectors}
import org.apache.spark.ml.param.ParamMap
import org.apache.spark.sql.Row

// Prepare training data from a list of (label, features) tuples.
val training = spark.createDataFrame(Seq(
  (1.0, Vectors.dense(0.0, 1.1, 0.1)),
  (0.0, Vectors.dense(2.0, 1.0, -1.0)),
  (0.0, Vectors.dense(2.0, 1.3, 1.0)),
  (1.0, Vectors.dense(0.0, 1.2, -0.5))
)).toDF("label", "features")

// Create a LogisticRegression instance. This instance is an Estimator.
val lr = new LogisticRegression()
// Print out the parameters, documentation, and any default values.
println("LogisticRegression parameters:\n" + lr.explainParams() + "\n")

// We may set parameters using setter methods.
lr.setMaxIter(10)
  .setRegParam(0.01)

// Learn a LogisticRegression model. This uses the parameters stored in lr.
val model1 = lr.fit(training)
// Since model1 is a Model (i.e., a Transformer produced by an Estimator),
// we can view the parameters it used during fit().
// This prints the parameter (name: value) pairs, where names are unique IDs for this
// LogisticRegression instance.
println("Model 1 was fit using parameters: " + model1.parent.extractParamMap)

// We may alternatively specify parameters using a ParamMap,
// which supports several methods for specifying parameters.
val paramMap = ParamMap(lr.maxIter -> 20)
  .put(lr.maxIter, 30)  // Specify 1 Param. This overwrites the original maxIter.
  .put(lr.regParam -> 0.1, lr.threshold -> 0.55)  // Specify multiple Params.

// One can also combine ParamMaps.
val paramMap2 = ParamMap(lr.probabilityCol -> "myProbability")  // Change output column name.
val paramMapCombined = paramMap ++ paramMap2

// Now learn a new model using the paramMapCombined parameters.
// paramMapCombined overrides all parameters set earlier via lr.set* methods.
val model2 = lr.fit(training, paramMapCombined)
println("Model 2 was fit using parameters: " + model2.parent.extractParamMap)

// Prepare test data.
val test = spark.createDataFrame(Seq(
  (1.0, Vectors.dense(-1.0, 1.5, 1.3)),
  (0.0, Vectors.dense(3.0, 2.0, -0.1)),
  (1.0, Vectors.dense(0.0, 2.2, -1.5))
)).toDF("label", "features")

// Make predictions on test data using the Transformer.transform() method.
// LogisticRegression.transform will only use the 'features' column.
// Note that model2.transform() outputs a 'myProbability' column instead of the usual
// 'probability' column since we renamed the lr.probabilityCol parameter previously.
model2.transform(test)
  .select("features", "label", "myProbability", "prediction")
  .collect()
  .foreach { case Row(features: Vector, label: Double, prob: Vector, prediction: Double) =>
    println(s"($features, $label) -> prob=$prob, prediction=$prediction")
  }

 参考: 

[`Estimator` Scala docs](api/scala/index.html#org.apache.spark.ml.Estimator)， 

[`Transformer` Scala docs](api/scala/index.html#org.apache.spark.ml.Transformer) 和

[`Params` Scala docs](api/scala/index.html#org.apache.spark.ml.param.Params)了解API的详细信息。

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/EstimatorTransformerParamExample.scala”中查找完整示例代码。

示例：Pipeline

该示例遵循上述附图中所示的简单文本文档管道。

Scala

import org.apache.spark.ml.{Pipeline, PipelineModel}
import org.apache.spark.ml.classification.LogisticRegression
import org.apache.spark.ml.feature.{HashingTF, Tokenizer}
import org.apache.spark.ml.linalg.Vector
import org.apache.spark.sql.Row

// Prepare training documents from a list of (id, text, label) tuples.
val training = spark.createDataFrame(Seq(
  (0L, "a b c d e spark", 1.0),
  (1L, "b d", 0.0),
  (2L, "spark f g h", 1.0),
  (3L, "hadoop mapreduce", 0.0)
)).toDF("id", "text", "label")

// Configure an ML pipeline, which consists of three stages: tokenizer, hashingTF, and lr.
val tokenizer = new Tokenizer()
  .setInputCol("text")
  .setOutputCol("words")
val hashingTF = new HashingTF()
  .setNumFeatures(1000)
  .setInputCol(tokenizer.getOutputCol)
  .setOutputCol("features")
val lr = new LogisticRegression()
  .setMaxIter(10)
  .setRegParam(0.001)
val pipeline = new Pipeline()
  .setStages(Array(tokenizer, hashingTF, lr))

// Fit the pipeline to training documents.
val model = pipeline.fit(training)

// Now we can optionally save the fitted pipeline to disk
model.write.overwrite().save("/tmp/spark-logistic-regression-model")

// We can also save this unfit pipeline to disk
pipeline.write.overwrite().save("/tmp/unfit-lr-model")

// And load it back in during production
val sameModel = PipelineModel.load("/tmp/spark-logistic-regression-model")

// Prepare test documents, which are unlabeled (id, text) tuples.
val test = spark.createDataFrame(Seq(
  (4L, "spark i j k"),
  (5L, "l m n"),
  (6L, "spark hadoop spark"),
  (7L, "apache hadoop")
)).toDF("id", "text")

// Make predictions on test documents.
model.transform(test)
  .select("id", "text", "probability", "prediction")
  .collect()
  .foreach { case Row(id: Long, text: String, prob: Vector, prediction: Double) =>
    println(s"($id, $text) --> prob=$prob, prediction=$prediction")
  }

有关API的详细信息，请参阅[`Pipeline` Scala docs](api/scala/index.html#org.apache.spark.ml.Pipeline) 

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/PipelineExample.scala”中查找完整示例代码。

模型选择（超参数调优）

使用ML管道的一大优点是超参数优化。有关自动模式选择的更多信息，请参阅“ML调优指南”。

Extracting, transforming and selecting features（特征的提取，转换和选择）

本节介绍使用功能的算法，大致分为以下几类：

提取：从“原始”数据中提取特征

转换：缩放，转换或修改特征

选择：从较大的一组特征中选择一个子集

局部敏感哈希（LSH）：这类算法将特征变换的方面与其他算法相结合。

目录

原文链接 : http://spark.apache.org/docs/2.1.0/ml-features.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=5505205

贡献者 : 子浪 祁洁 片刻 程威 ApacheCN Apache中文网

Feature Extractors（特征提取）

TF-IDF（词频-逆向文档频率）

词频（Term Frequency）- 逆向文档频率（Inverse Document Frequency） 是一种在文本挖掘中广泛使用的特征向量化方法，以反映一个单词在语料库中的重要性。定义：t 表示由一个单词，d 表示一个文档，D 表示语料库（corpus），词频 TF(t,d) 表示某一个给定的单词 t 出现在文档 d 中的次数， 而文档频率 DF(t,D) 表示包含单词 t  的文档次数。如果我们只使用词频 TF 来衡量重要性，则很容易过分强调出现频率过高并且文档包含少许信息的单词，例如，'a'，'the'，和 'of'。如果一个单词在整个语料库中出现的非常频繁，这意味着它并没有携带特定文档的某些特殊信息（换句话说，该单词对整个文档的重要程度低）。逆向文档频率是一个数字量度，表示一个单词提供了多少信息：

 其中，|D| 是在语料库中文档总数。由于使用对数，所以如果一个单词出现在所有的文件，其IDF值变为0。注意，应用平滑项以避免在语料库之外的项除以零（为了防止分母为0，分母需要加1）。因此，TF-IDF测量只是TF和IDF的产物：（对TF-IDF定义为TF和IDF的乘积）

 关于词频TF和文档频率DF的定义有多种形式。在MLlib，我们分离TF和IDF，使其灵活。

TF（词频Term Frequency）：HashingTF与CountVectorizer都可以用于生成词频TF向量。

HashingTF是一个需要特征词集的转换器（Transformer），它可以将这些集合转换成固定长度的特征向量。在文本处理中，“特征词集”有一系列的特征词构成。HashingTF利用hashing trick，原始特征（raw feature）通过应用哈希函数映射到索引（术语）中。这里使用的哈希函数是murmurHash 3。然后根据映射的索引计算词频。这种方法避免了计算全局特征词对索引映射的需要，这对于大型语料库来说可能是昂贵的，但是它具有潜在的哈希冲突，其中不同的原始特征可以在散列之后变成相同的特征词。为了减少碰撞的机会，我们可以增加目标特征维度，即哈希表的桶数。由于使用简单的模数将散列函数转换为列索引，建议使用两个幂作为特征维，否则不会将特征均匀地映射到列。默认功能维度为。可选的二进制切换参数控制词频计数。当设置为true时，所有非零频率计数设置为1。这对于模拟二进制而不是整数的离散概率模型尤其有用。

CountVectorizer将文本文档转换为关键词计数的向量。有关详细信息，请参阅CountVectorizer 。

IDF（逆向文档频率）：IDF是一个适合数据集并生成IDFModel的评估器（Estimator），IDFModel获取特征向量（通常由HashingTF或CountVectorizer创建）并缩放每列。直观地说，它下调了在语料库中频繁出现的列。

注： spark.ml不提供文本分割的工具。我们推荐用户参考Stanford NLP Group 和 scalanlp/chalk.

例子 

在下面的代码段中，我们从一组句子开始。我们使用Tokenizer将每个句子分成单词。对于每个句子（词袋，词集：bag of words），我们使用HashingTF将该句子哈希成特征向量。我们使用IDF来重新缩放特征向量；这通常会在使用文本作为功能时提高性能。然后，我们的特征向量可以被传递给学习算法。

Scala

import org.apache.spark.ml.feature.{HashingTF, IDF, Tokenizer}

val sentenceData = spark.createDataFrame(Seq(
  (0.0, "Hi I heard about Spark"),
  (0.0, "I wish Java could use case classes"),
  (1.0, "Logistic regression models are neat")
)).toDF("label", "sentence")

val tokenizer = new Tokenizer().setInputCol("sentence").setOutputCol("words")
val wordsData = tokenizer.transform(sentenceData)

val hashingTF = new HashingTF()
  .setInputCol("words").setOutputCol("rawFeatures").setNumFeatures(20)

val featurizedData = hashingTF.transform(wordsData)
// alternatively, CountVectorizer can also be used to get term frequency vectors

val idf = new IDF().setInputCol("rawFeatures").setOutputCol("features")
val idfModel = idf.fit(featurizedData)

val rescaledData = idfModel.transform(featurizedData)
rescaledData.select("label", "features").show()

有关API的更多详细信息，请参阅HashingTF Scala文档 和IDF Scala文档 

在Spark repo中"examples/src/main/scala/org/apache/spark/examples/ml/TfIdfExample.scala"下可以找到完整的示例代码。

Word2Vec

Word2Vec是一个Estimator(评估器)，它采用表示文档的单词序列，并训练一个Word2VecModel。 该模型将每个单词映射到一个唯一的固定大小向量。 Word2VecModel使用文档中所有单词的平均值将每个文档转换为向量; 该向量然后可用作预测，文档相似性计算等功能。有关更多详细信息，请参阅有关Word2Vec的MLlib用户指南。

在下面的代码段中，我们从一组文档开始，每一个文档都用一个单词序列表示。 对于每个文档，我们将其转换为特征向量。 然后可以将该特征向量传递给学习算法。

Scala

import org.apache.spark.ml.feature.Word2Vec
import org.apache.spark.ml.linalg.Vector
import org.apache.spark.sql.Row

// Input data: Each row is a bag of words from a sentence or document.
val documentDF = spark.createDataFrame(Seq(
  "Hi I heard about Spark".split(" "),
  "I wish Java could use case classes".split(" "),
  "Logistic regression models are neat".split(" ")
).map(Tuple1.apply)).toDF("text")

// Learn a mapping from words to Vectors.
val word2Vec = new Word2Vec()
  .setInputCol("text")
  .setOutputCol("result")
  .setVectorSize(3)
  .setMinCount(0)
val model = word2Vec.fit(documentDF)

val result = model.transform(documentDF)
result.collect().foreach { case Row(text: Seq[_], features: Vector) =>
  println(s"Text: [${text.mkString(", ")}] => \nVector: $features\n") }

有关API的更多详细信息，请参阅Word2Vec Scala文档。

在Spark repo中的"examples/src/main/scala/org/apache/spark/examples/ml/Word2VecExample.scala"中查找完整示例代码。

CountVectorizer

CountVectorizer和CountVectorizerModel旨在帮助将文本文档集合转换为标记数的向量。 当先验词典不可用时，CountVectorizer可以用作估计器来提取词汇表，并生成CountVectorizerModel。 该模型通过词汇生成文档的稀疏表示，然后可以将其传递给其他算法，如LDA。

在拟合过程中，CountVectorizer将选择通过语料库按术语频率排序的top前几vocabSize词。 可选参数minDF还通过指定术语必须出现以包含在词汇表中的文档的最小数量（或小于1.0）来影响拟合过程。 另一个可选的二进制切换参数控制输出向量。 如果设置为true，则所有非零计数都设置为1.对于模拟二进制而不是整数的离散概率模型，这是非常有用的。

Examples

假设我们有如下的DataFrame包含id和texts两列：

 id | texts
----|----------
 0  | Array("a", "b", "c")
 1  | Array("a", "b", "b", "c", "a")

文本中的每一行都是Array[String]类型的文档。调用CountVectorizer的拟合产生一个具有词汇表（a, b, c）的CountVectorizerModel。然后转换后的输出列 包含“向量”这一列：

 id | texts                           | vector
----|---------------------------------|---------------
 0  | Array("a", "b", "c")            | (3,[0,1,2],[1.0,1.0,1.0])
 1  | Array("a", "b", "b", "c", "a")  | (3,[0,1,2],[2.0,2.0,1.0])

每个向量表示文档在词汇表上的标记数。

Scala

import org.apache.spark.ml.feature.{CountVectorizer, CountVectorizerModel}

val df = spark.createDataFrame(Seq(
  (0, Array("a", "b", "c")),
  (1, Array("a", "b", "b", "c", "a"))
)).toDF("id", "words")

// fit a CountVectorizerModel from the corpus
val cvModel: CountVectorizerModel = new CountVectorizer()
  .setInputCol("words")
  .setOutputCol("features")
  .setVocabSize(3)
  .setMinDF(2)
  .fit(df)

// alternatively, define CountVectorizerModel with a-priori vocabulary
val cvm = new CountVectorizerModel(Array("a", "b", "c"))
  .setInputCol("words")
  .setOutputCol("features")

cvModel.transform(df).show(false)

有关API的更多详细信息，请参阅CountVectorizer Scala文档和CountVectorizerModel Scala文档

在Spark repo中的"examples/src/main/scala/org/apache/spark/examples/ml/CountVectorizerExample.scala"中查找完整示例代码。 

Feature Transformers（特征变换）

Tokenizer（分词器）

Tokenization（文本符号化）是将文本 （如一个句子）拆分成单词的过程。（在Spark ML中）Tokenizer（分词器）提供此功能。下面的示例演示如何将句子拆分为词的序列。

RegexTokenizer 提供了（更高级的）基于正则表达式 (regex) 匹配的（对句子或文本的）单词拆分。默认情况下，参数"pattern"(默认的正则表达式: "\\s+") 作为分隔符用于拆分输入的文本。或者，用户可以将参数“gaps”设置为 false ，指定正则表达式"pattern"表示"tokens"，而不是分隔符，这样作为划分结果找到的所有匹配项。

Scala

import org.apache.spark.ml.feature.{RegexTokenizer, Tokenizer}
import org.apache.spark.sql.functions._

val sentenceDataFrame = spark.createDataFrame(Seq(
  (0, "Hi I heard about Spark"),
  (1, "I wish Java could use case classes"),
  (2, "Logistic,regression,models,are,neat")
)).toDF("id", "sentence")

val tokenizer = new Tokenizer().setInputCol("sentence").setOutputCol("words")
val regexTokenizer = new RegexTokenizer()
  .setInputCol("sentence")
  .setOutputCol("words")
  .setPattern("\\W") // alternatively .setPattern("\\w+").setGaps(false)

val countTokens = udf { (words: Seq[String]) => words.length }

val tokenized = tokenizer.transform(sentenceDataFrame)
tokenized.select("sentence", "words")
    .withColumn("tokens", countTokens(col("words"))).show(false)

val regexTokenized = regexTokenizer.transform(sentenceDataFrame)
regexTokenized.select("sentence", "words")
    .withColumn("tokens", countTokens(col("words"))).show(false)

请参阅 Tokenizer Scala docs  和 RegexTokenizer Scala docs 了解相关的 API 的详细信息

在Spark repo中路径 "examples/src/main/scala/org/apache/spark/examples/ml/TokenizerExample.scala" 找到完整的示例代码。 

StopWordsRemover（去停用词）

Stop words （停用字）是（在文档中）频繁出现，但未携带太多意义的词语，它们不应该参与算法运算。

 StopWordsRemover（的作用是）将输入的字符串 （如分词器 Tokenizer 的输出）中的停用字删除（后输出）。停用字表由 stopWords 参数指定。对于某些语言的默认停止词是通过调用 StopWordsRemover.loadDefaultStopWords(language) 设置的，可用的选项为"丹麦"，"荷兰语"、"英语"、"芬兰语"，"法国"，"德国"、"匈牙利"、"意大利"、"挪威"、"葡萄牙"、"俄罗斯"、"西班牙"、"瑞典"和"土耳其"。布尔型参数 caseSensitive 指示是否区分大小写 （默认为否）。

Examples

假设有如下DataFrame，有id和raw两列：

 id | raw
----|----------
 0  | [I, saw, the, red, baloon]
 1  | [Mary, had, a, little, lamb]

通过对 raw 列调用 StopWordsRemover，我们可以得到筛选出的结果列如下：

 id | raw                         | filtered
----|-----------------------------|--------------------
 0  | [I, saw, the, red, baloon]  |  [saw, red, baloon]
 1  | [Mary, had, a, little, lamb]|  [Mary, little, lamb]

其中，“I”, “the”, “had”以及“a”被移除。

Scala

import org.apache.spark.ml.feature.StopWordsRemover

val remover = new StopWordsRemover()
  .setInputCol("raw")
  .setOutputCol("filtered")

val dataSet = spark.createDataFrame(Seq(
  (0, Seq("I", "saw", "the", "red", "baloon")),
  (1, Seq("Mary", "had", "a", "little", "lamb"))
)).toDF("id", "raw")

remover.transform(dataSet).show()

有关API的更多详细信息，请参阅StopWordsRemover Scala文档 

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/StopWordsRemoverExample.scala"可以找到完整的示例代码。  

n-gram（N元模型）

一个 n-gram是一个长度为n（整数）的字的序列。NGram可用于将输入特征转换成n-grams。

N-Gram 的输入为一系列的字符串（例如：Tokenizer分词器的输出）。参数 n 表示每个 n-gram 中单词（terms）的数量。输出将由 n-gram 序列组成，其中每个 n-gram 由空格分隔的 n 个连续词的字符串表示。如果输入的字符串序列少于n个单词，NGram 输出为空。

Scala

import org.apache.spark.ml.feature.NGram

val wordDataFrame = spark.createDataFrame(Seq(
  (0, Array("Hi", "I", "heard", "about", "Spark")),
  (1, Array("I", "wish", "Java", "could", "use", "case", "classes")),
  (2, Array("Logistic", "regression", "models", "are", "neat"))
)).toDF("id", "words")

val ngram = new NGram().setN(2).setInputCol("words").setOutputCol("ngrams")

val ngramDataFrame = ngram.transform(wordDataFrame)
ngramDataFrame.select("ngrams").show(false)

请参阅 NGram Scala docs了解相关的 API 的详细信息

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/NGramExample.scala"里可以找到完整的示例代码。  

Binarizer（二值化）

Binarization （二值化）是将数值特征阈值化为二进制（0/1）特征的过程。

Binarizer（ML提供的二元化方法）二元化涉及的参数有 inputCol（输入）、outputCol（输出）以及threshold（阀值）。（输入的）特征值大于阀值将二值化为1.0，特征值小于等于阀值将二值化为0.0。inputCol 支持向量（Vector）和双精度（Double）类型。

Scala

import org.apache.spark.ml.feature.Binarizer

val data = Array((0, 0.1), (1, 0.8), (2, 0.2))
val dataFrame = spark.createDataFrame(data).toDF("id", "feature")

val binarizer: Binarizer = new Binarizer()
  .setInputCol("feature")
  .setOutputCol("binarized_feature")
  .setThreshold(0.5)

val binarizedDataFrame = binarizer.transform(dataFrame)

println(s"Binarizer output with Threshold = ${binarizer.getThreshold}")
binarizedDataFrame.show()

请参阅  Binarizer Scala docs了解相关的 API 的详细信息

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/BinarizerExample.scala"里可以找到完整的示例代码 

PCA（主元分析）

PCA 是使用正交变换将可能相关变量的一组观察值转换为称为主成分的线性不相关变量的值的一组统计过程。 PCA 类训练使用 PCA 将向量投影到低维空间的模型。下面的例子显示了如何将5维特征向量投影到3维主成分中。

Scala

import org.apache.spark.ml.feature.PCA
import org.apache.spark.ml.linalg.Vectors

val data = Array(
  Vectors.sparse(5, Seq((1, 1.0), (3, 7.0))),
  Vectors.dense(2.0, 0.0, 3.0, 4.0, 5.0),
  Vectors.dense(4.0, 0.0, 0.0, 6.0, 7.0)
)
val df = spark.createDataFrame(data.map(Tuple1.apply)).toDF("features")

val pca = new PCA()
  .setInputCol("features")
  .setOutputCol("pcaFeatures")
  .setK(3)
  .fit(df)

val result = pca.transform(df).select("pcaFeatures")
result.show(false)

请参阅  PCA Scala docs了解相关的 API 的详细信息

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/PCAExample.scala"里可以找到完整的示例代码 

PolynomialExpansion（多项式扩展）

Polynomial expansion （多项式展开）是将特征扩展为多项式空间的过程，多项式空间由原始维度的n度组合组成。 PolynomialExpansion类提供此功能。 下面的例子显示了如何将您的功能扩展到3度多项式空间。

Scala

import org.apache.spark.ml.feature.PolynomialExpansion
import org.apache.spark.ml.linalg.Vectors

val data = Array(
  Vectors.dense(2.0, 1.0),
  Vectors.dense(0.0, 0.0),
  Vectors.dense(3.0, -1.0)
)
val df = spark.createDataFrame(data.map(Tuple1.apply)).toDF("features")

val polyExpansion = new PolynomialExpansion()
  .setInputCol("features")
  .setOutputCol("polyFeatures")
  .setDegree(3)

val polyDF = polyExpansion.transform(df)
polyDF.show(false)

请参阅  PolynomialExpansion Scala docs 了解相关的 API 的详细信息

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/PolynomialExpansionExample.scala"里可以找到完整的示例代码  

Discrete Cosine Transform（DCT 离散余弦变换）

Discrete Cosine Transform（离散余弦变换） 是将时域的N维实数序列转换成频域的N维实数序列的过程（有点类似离散傅里叶变换）。（ML中的）DCT类提供了离散余弦变换DCT-II的功能，将离散余弦变换后结果乘以 得到一个与时域矩阵长度一致的矩阵。没有偏移被应用于变换的序列（例如，变换的序列的第0个元素是第0个DCT系数，而不是第N / 2个），即输入序列与输出之间是一一对应的。

Scala

import org.apache.spark.ml.feature.DCT
import org.apache.spark.ml.linalg.Vectors

val data = Seq(
  Vectors.dense(0.0, 1.0, -2.0, 3.0),
  Vectors.dense(-1.0, 2.0, 4.0, -7.0),
  Vectors.dense(14.0, -2.0, -5.0, 1.0))

val df = spark.createDataFrame(data.map(Tuple1.apply)).toDF("features")

val dct = new DCT()
  .setInputCol("features")
  .setOutputCol("featuresDCT")
  .setInverse(false)

val dctDf = dct.transform(df)
dctDf.select("featuresDCT").show(false)

请参阅 DCT Scala docs  了解相关的 API 的详细信息

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/DCTExample.scala"里可以找到完整的示例代码  

StringIndexer（字符串-索引变换）

StringIndexer（字符串-索引变换）将标签的字符串列编号变成标签索引列。标签索引序列的取值范围是[0，numLabels（字符串中所有出现的单词去掉重复的词后的总和）]，按照标签出现频率排序，出现最多的标签索引为0。如果输入是数值型，我们先将数值映射到字符串，再对字符串进行索引化。如果下游的 pipeline（例如：Estimator 或者 Transformer）需要用到索引化后的标签序列，则需要将这个 pipeline 的输入列名字指定为索引化序列的名字。大部分情况下，通过 setInputCol 设置输入的列名。

Examples

假设我们有如下的 DataFrame ，包含有 id 和 category 两列

 id | category
----|----------
 0  | a
 1  | b
 2  | c
 3  | a
 4  | a
 5  | c

标签类别（category）是有3种取值的标签：“a”，“b”，“c”。使用 StringIndexer 通过 category 进行转换成 categoryIndex 后可以得到如下结果：

 id | category | categoryIndex
----|----------|---------------
 0  | a        | 0.0
 1  | b        | 2.0
 2  | c        | 1.0
 3  | a        | 0.0
 4  | a        | 0.0
 5  | c        | 1.0

“a”因为出现的次数最多，所以得到为0的索引（index）。第二多的“c”得到1的索引，“b”得到2的索引

另外，StringIndexer 在转换新数据时提供两种容错机制处理训练中没有出现的标签

StringIndexer 抛出异常错误（默认值）

跳过未出现的标签实例。

Examples

回顾一下上一个例子，这次我们将继续使用上一个例子训练出来的 StringIndexer 处理下面的数据集 

 id | category
----|----------
 0  | a
 1  | b
 2  | c
 3  | d

如果没有在 StringIndexer 里面设置未训练过（unseen）的标签的处理或者设置未 “error”，运行时会遇到程序抛出异常。当然，也可以通过设置 setHandleInvalid("skip")，得到如下的结果

 id | category | categoryIndex
----|----------|---------------
 0  | a        | 0.0
 1  | b        | 2.0
 2  | c        | 1.0

 注意：输出里面没有出现“d”

import org.apache.spark.ml.feature.StringIndexer

val df = spark.createDataFrame(
  Seq((0, "a"), (1, "b"), (2, "c"), (3, "a"), (4, "a"), (5, "c"))
).toDF("id", "category")

val indexer = new StringIndexer()
  .setInputCol("category")
  .setOutputCol("categoryIndex")

val indexed = indexer.fit(df).transform(df)
indexed.show()

请参阅  StringIndexer Scala docs了解相关的 API 的详细信息

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/StringIndexerExample.scala"里可以找到完整的示例代码   

IndexToString（索引-字符串变换）

与 StringIndexer 对应，IndexToString 将索引化标签还原成原始字符串。一个常用的场景是先通过 StringIndexer 产生索引化标签，然后使用索引化标签进行训练，最后再对预测结果使用 IndexToString来获取其原始的标签字符串。

Examples

假设我们有如下的DataFrame包含id和categoryIndex两列：

 id | categoryIndex
----|---------------
 0  | 0.0
 1  | 2.0
 2  | 1.0
 3  | 0.0
 4  | 0.0
 5  | 1.0

使用IndexToString我们可以获取其原始的标签字符串如下：

 id | categoryIndex | originalCategory
----|---------------|-----------------
 0  | 0.0           | a
 1  | 2.0           | b
 2  | 1.0           | c
 3  | 0.0           | a
 4  | 0.0           | a
 5  | 1.0           | c

Scala

import org.apache.spark.ml.attribute.Attribute
import org.apache.spark.ml.feature.{IndexToString, StringIndexer}

val df = spark.createDataFrame(Seq(
  (0, "a"),
  (1, "b"),
  (2, "c"),
  (3, "a"),
  (4, "a"),
  (5, "c")
)).toDF("id", "category")

val indexer = new StringIndexer()
  .setInputCol("category")
  .setOutputCol("categoryIndex")
  .fit(df)
val indexed = indexer.transform(df)

println(s"Transformed string column '${indexer.getInputCol}' " +
    s"to indexed column '${indexer.getOutputCol}'")
indexed.show()

val inputColSchema = indexed.schema(indexer.getOutputCol)
println(s"StringIndexer will store labels in output column metadata: " +
    s"${Attribute.fromStructField(inputColSchema).toString}\n")

val converter = new IndexToString()
  .setInputCol("categoryIndex")
  .setOutputCol("originalCategory")

val converted = converter.transform(indexed)

println(s"Transformed indexed column '${converter.getInputCol}' back to original string " +
    s"column '${converter.getOutputCol}' using labels in metadata")
converted.select("id", "categoryIndex", "originalCategory").show()

请参阅  IndexToString Scala docs 了解相关的 API 的详细信息

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/IndexToStringExample.scala"里可以找到完整的示例代码   

OneHotEncoder（独热编码）

独热编码（One-hot encoding）将一列标签索引映射到一列二进制向量，最多只有一个单值。 该编码允许期望连续特征（例如逻辑回归）的算法使用分类特征。

Scala

import org.apache.spark.ml.feature.{OneHotEncoder, StringIndexer}

val df = spark.createDataFrame(Seq(
  (0, "a"),
  (1, "b"),
  (2, "c"),
  (3, "a"),
  (4, "a"),
  (5, "c")
)).toDF("id", "category")

val indexer = new StringIndexer()
  .setInputCol("category")
  .setOutputCol("categoryIndex")
  .fit(df)
val indexed = indexer.transform(df)

val encoder = new OneHotEncoder()
  .setInputCol("categoryIndex")
  .setOutputCol("categoryVec")

val encoded = encoder.transform(indexed)
encoded.show()

 请参阅  OneHotEncoder Scala docs了解相关的 API 的详细信息

 在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/OneHotEncoderExample.scala"里可以找到完整的示例代码  

VectorIndexer(向量类型索引化)

VectorIndexer可以帮助指定向量数据集中的分类特征。它可以自动确定哪些功能是分类的，并将原始值转换为类别索引。具体来说，它执行以下操作：

取一个Vector类型的输入列和一个参数maxCategories。

根据不同值的数量确定哪些功能应分类，其中最多maxCategories的功能被声明为分类。

为每个分类功能计算基于0的类别索引。

索引分类特征并将原始特征值转换为索引。

索引分类功能允许诸如决策树和树组合之类的算法适当地处理分类特征，提高性能。

在下面的示例中，我们读取标注点的数据集，然后使用VectorIndexer来确定哪些功能应被视为分类。我们将分类特征值转换为其索引。然后，该转换的数据可以传递给诸如DecisionTreeRegressor之类的算法来处理分类特征。

Scala

import org.apache.spark.ml.feature.VectorIndexer

val data = spark.read.format("libsvm").load("data/mllib/sample_libsvm_data.txt")

val indexer = new VectorIndexer()
  .setInputCol("features")
  .setOutputCol("indexed")
  .setMaxCategories(10)

val indexerModel = indexer.fit(data)

val categoricalFeatures: Set[Int] = indexerModel.categoryMaps.keys.toSet
println(s"Chose ${categoricalFeatures.size} categorical features: " +
  categoricalFeatures.mkString(", "))

// Create new column "indexed" with categorical values transformed to indices
val indexedData = indexerModel.transform(data)
indexedData.show()

请参阅  VectorIndexer Scala docs 了解相关的 API 的详细信息

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/VectorIndexerExample.scala"里可以找到完整的示例代码    

Interaction（相互作用）

交互是一个变换器，它采用向量或双值列，并生成一个单个向量列，其中包含来自每个输入列的一个值的所有组合的乘积。

例如，如果您有2个向量类型的列，每个列具有3个维度作为输入列，那么您将获得一个9维向量作为输出列。

Examples

假设我们有如下DataFrame，列为“id1”, “vec1” 和 “vec2”:

 id1|vec1          |vec2
  ---|--------------|--------------
  1  |[1.0,2.0,3.0] |[8.0,4.0,5.0]
  2  |[4.0,3.0,8.0] |[7.0,9.0,8.0]
  3  |[6.0,1.0,9.0] |[2.0,3.0,6.0]
  4  |[10.0,8.0,6.0]|[9.0,4.0,5.0]
  5  |[9.0,2.0,7.0] |[10.0,7.0,3.0]
  6  |[1.0,1.0,4.0] |[2.0,8.0,4.0]

应用与这些输入列的交互，然后将交互作为输出列包含：

 id1|vec1          |vec2          |interactedCol
  ---|--------------|--------------|------------------------------------------------------
  1  |[1.0,2.0,3.0] |[8.0,4.0,5.0] |[8.0,4.0,5.0,16.0,8.0,10.0,24.0,12.0,15.0]
  2  |[4.0,3.0,8.0] |[7.0,9.0,8.0] |[56.0,72.0,64.0,42.0,54.0,48.0,112.0,144.0,128.0]
  3  |[6.0,1.0,9.0] |[2.0,3.0,6.0] |[36.0,54.0,108.0,6.0,9.0,18.0,54.0,81.0,162.0]
  4  |[10.0,8.0,6.0]|[9.0,4.0,5.0] |[360.0,160.0,200.0,288.0,128.0,160.0,216.0,96.0,120.0]
  5  |[9.0,2.0,7.0] |[10.0,7.0,3.0]|[450.0,315.0,135.0,100.0,70.0,30.0,350.0,245.0,105.0]
  6  |[1.0,1.0,4.0] |[2.0,8.0,4.0] |[12.0,48.0,24.0,12.0,48.0,24.0,48.0,192.0,96.0]

Scala

import org.apache.spark.ml.feature.Interaction
import org.apache.spark.ml.feature.VectorAssembler

val df = spark.createDataFrame(Seq(
  (1, 1, 2, 3, 8, 4, 5),
  (2, 4, 3, 8, 7, 9, 8),
  (3, 6, 1, 9, 2, 3, 6),
  (4, 10, 8, 6, 9, 4, 5),
  (5, 9, 2, 7, 10, 7, 3),
  (6, 1, 1, 4, 2, 8, 4)
)).toDF("id1", "id2", "id3", "id4", "id5", "id6", "id7")

val assembler1 = new VectorAssembler().
  setInputCols(Array("id2", "id3", "id4")).
  setOutputCol("vec1")

val assembled1 = assembler1.transform(df)

val assembler2 = new VectorAssembler().
  setInputCols(Array("id5", "id6", "id7")).
  setOutputCol("vec2")

val assembled2 = assembler2.transform(assembled1).select("id1", "vec1", "vec2")

val interaction = new Interaction()
  .setInputCols(Array("id1", "vec1", "vec2"))
  .setOutputCol("interactedCol")

val interacted = interaction.transform(assembled2)

interacted.show(truncate = false)

有关API的更多详细信息，请参阅交互式Scala文档

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/InteractionExample.scala”中查找完整示例代码。

Normalizer(范数p-norm规范化)

Normalizer是一个转换器，它可以将一组特征向量（通过计算p-范数）规范化。参数为p（默认值：2）来指定规范化中使用的p-norm。规范化操作可以使输入数据标准化，对后期机器学习算法的结果也有更好的表现。

下面的例子展示如何读入一个libsvm格式的数据，然后将每一行转换为以及形式。

Scala

import org.apache.spark.ml.feature.Normalizer
import org.apache.spark.ml.linalg.Vectors

val dataFrame = spark.createDataFrame(Seq(
  (0, Vectors.dense(1.0, 0.5, -1.0)),
  (1, Vectors.dense(2.0, 1.0, 1.0)),
  (2, Vectors.dense(4.0, 10.0, 2.0))
)).toDF("id", "features")

// Normalize each Vector using $L^1$ norm.
val normalizer = new Normalizer()
  .setInputCol("features")
  .setOutputCol("normFeatures")
  .setP(1.0)

val l1NormData = normalizer.transform(dataFrame)
println("Normalized using L^1 norm")
l1NormData.show()

// Normalize each Vector using $L^\infty$ norm.
val lInfNormData = normalizer.transform(dataFrame, normalizer.p -> Double.PositiveInfinity)
println("Normalized using L^inf norm")
lInfNormData.show()

请参阅  Normalizer Scala docs 了解相关的 API 的详细信息

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/NormalizerExample.scala"里可以找到完整的示例代码。   

StandardScaler（标准化）

StandardScaler转换Vector行的数据集，使每个要素标准化以具有单位标准偏差和 或 零均值。它需要参数：

withStd：默认为True。将数据缩放到单位标准偏差。

withMean：默认为false。在缩放之前将数据中心为平均值。它将构建一个密集的输出，所以在应用于稀疏输入时要小心。

StandardScaler是一个Estimator，可以适合数据集生成StandardScalerModel; 这相当于计算汇总统计数据。 然后，模型可以将数据集中的向量列转换为具有单位标准偏差和/或零平均特征。

请注意，如果特征的标准偏差为零，它将在该特征的向量中返回默认的0.0值。

以下示例演示如何以libsvm格式加载数据集，然后将每个要素归一化以具有单位标准偏差。

Scala

import org.apache.spark.ml.feature.StandardScaler

val dataFrame = spark.read.format("libsvm").load("data/mllib/sample_libsvm_data.txt")

val scaler = new StandardScaler()
  .setInputCol("features")
  .setOutputCol("scaledFeatures")
  .setWithStd(true)
  .setWithMean(false)

// Compute summary statistics by fitting the StandardScaler.
val scalerModel = scaler.fit(dataFrame)

// Normalize each feature to have unit standard deviation.
val scaledData = scalerModel.transform(dataFrame)
scaledData.show()

请参阅StandardScaler Scala docs了解相关的 API 的详细信息。

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/StandardScalerExample.scala"里可以找到完整的示例代码。

MinMaxScaler（最大-最小规范化） 

MinMaxScaler转换Vector行的数据集，将每个要素的重新映射到特定范围（通常为[0，1]）。它需要参数：

min：默认为0.0，转换后的下限，由所有功能共享。

max：默认为1.0，转换后的上限，由所有功能共享。

MinMaxScaler计算数据集的统计信息，并生成MinMaxScalerModel。然后，模型可以单独转换每个要素，使其在给定的范围内。

特征E的重新缩放值被计算为：

对于情况Emax == Emin，Rescaled(ei) = 0.5 *（max + min）

请注意，由于零值可能会转换为非零值，即使对于稀疏输入，变压器的输出也将为DenseVector。

以下示例演示如何以libsvm格式加载数据集，然后将每个要素重新缩放为[0，1]。

Scala

import org.apache.spark.ml.feature.MinMaxScaler
import org.apache.spark.ml.linalg.Vectors

val dataFrame = spark.createDataFrame(Seq(
  (0, Vectors.dense(1.0, 0.1, -1.0)),
  (1, Vectors.dense(2.0, 1.1, 1.0)),
  (2, Vectors.dense(3.0, 10.1, 3.0))
)).toDF("id", "features")

val scaler = new MinMaxScaler()
  .setInputCol("features")
  .setOutputCol("scaledFeatures")

// Compute summary statistics and generate MinMaxScalerModel
val scalerModel = scaler.fit(dataFrame)

// rescale each feature to range [min, max].
val scaledData = scalerModel.transform(dataFrame)
println(s"Features scaled to range: [${scaler.getMin}, ${scaler.getMax}]")
scaledData.select("features", "scaledFeatures").show()

请参阅MinMaxScaler Scala docs 和 MinMaxScalerModel Scala docs 了解相关的 API 的详细信息。

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/MinMaxScalerExample.scala"里可以找到完整的示例代码   

MaxAbsScaler（绝对值规范化）

MaxAbsScaler转换Vector行的数据集，通过划分每个要素中的最大绝对值，将每个要素的重新映射到范围[-1,1]。 它不会使数据移动/居中，因此不会破坏任何稀疏性。

MaxAbsScaler计算数据集的统计信息，并生成MaxAbsScalerModel。 然后，模型可以将每个要素单独转换为范围[-1,1]。

以下示例演示如何以libsvm格式加载数据集，然后将每个要素重新缩放为[-1,1]。

Scala

import org.apache.spark.ml.feature.MaxAbsScaler
import org.apache.spark.ml.linalg.Vectors

val dataFrame = spark.createDataFrame(Seq(
  (0, Vectors.dense(1.0, 0.1, -8.0)),
  (1, Vectors.dense(2.0, 1.0, -4.0)),
  (2, Vectors.dense(4.0, 10.0, 8.0))
)).toDF("id", "features")

val scaler = new MaxAbsScaler()
  .setInputCol("features")
  .setOutputCol("scaledFeatures")

// Compute summary statistics and generate MaxAbsScalerModel
val scalerModel = scaler.fit(dataFrame)

// rescale each feature to range [-1, 1]
val scaledData = scalerModel.transform(dataFrame)
scaledData.select("features", "scaledFeatures").show()

请参阅 MaxAbsScaler Scala docs 和 MaxAbsScalerModel Scala docs 了解相关的 API 的详细信息。

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/MaxAbsScalerExample.scala"里可以找到完整的示例代码 

Bucketizer（分箱器）

Bucketizer 将一列连续的特征转换为特征 buckets（区间），buckets（区间）由用户指定。Bucketizer 需要一个参数：

splits（分割）：这是个将连续的特征转换为 buckets（区间）的参数. n+1次分割时，将产生n个 buckets（区间）。一个bucket（区间）通过范围 [x,y) 中 x , y 来定义除了最后一个 bucket 包含 y 值。Splits（分割）应该是严格递增的。-inf, inf 之间的值必须明确提供来覆盖所有的 Double 值;另外,Double 值超出 splits（分割）指定的值将认为是错误的. 两个splits （拆分）的例子为 Array(Double.NegativeInfinity, 0.0, 1.0, Double.PositiveInfinity)以及Array(0.0, 1.0, 2.0)。

请注意,如果你不知道目标列的上线和下限,则应将 Double.NegativeInfinity 和  Double.PositiveInfinity 添加为splits（分割）的边界,以防止 Bucketizer 界限出现异常.

还请注意,你提供的 splits（分割）必须严格按照增加的顺序,即 s0 < s1 < s2 < ... < sn.

更多的细节可以查看 Bucketizer 的API文档.

下面这个例子演示了如何将包含 Doubles 的一列 bucketize （分箱）为另外一个索引列.

Scala

import org.apache.spark.ml.feature.Bucketizer

val splits = Array(Double.NegativeInfinity, -0.5, 0.0, 0.5, Double.PositiveInfinity)

val data = Array(-0.5, -0.3, 0.0, 0.2)
val dataFrame = spark.createDataFrame(data.map(Tuple1.apply)).toDF("features")

val bucketizer = new Bucketizer()
  .setInputCol("features")
  .setOutputCol("bucketedFeatures")
  .setSplits(splits)

// Transform original data into its bucket index.
val bucketedData = bucketizer.transform(dataFrame)
bucketedData.show()

请参阅 Bucketizer Scala docs了解相关的 API 的详细信息。

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/BucketizerExample.scala"里可以找到完整的示例代码。

ElementwiseProduct (Hadamard乘积)

ElementwiseProduct 将每个输入向量中乘以一个 weight（权重）向量，使用元素相乘的方法.换句话来说,就是通过scalar multiplier （标量乘法）对数据集中的每一列进行缩放。这表示输入向量 v 和转换向量 w 通过 Hadamard product（Hadamard积） 产生一个结果向量.

下面例子展示如何通过转换向量的值来调整向量。

Scala

import org.apache.spark.ml.feature.ElementwiseProduct
import org.apache.spark.ml.linalg.Vectors

// Create some vector data; also works for sparse vectors
val dataFrame = spark.createDataFrame(Seq(
  ("a", Vectors.dense(1.0, 2.0, 3.0)),
  ("b", Vectors.dense(4.0, 5.0, 6.0)))).toDF("id", "vector")

val transformingVector = Vectors.dense(0.0, 1.0, 2.0)
val transformer = new ElementwiseProduct()
  .setScalingVec(transformingVector)
  .setInputCol("vector")
  .setOutputCol("transformedVector")

// Batch transform the vectors to create new column:
transformer.transform(dataFrame).show()

请参阅 ElementwiseProduct Scala docs 了解相关的 API 的详细信息。

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/ElementwiseProductExample.scala"里可以找到完整的示例代码 。

SQLTransformer（SQL变换器）

SQLTransformer（SQL转换器）实现由 SQL 语句定义的转换 。目前我们只支持SQL语法如 "SELECT ... FROM __THIS__ ..." ，其中 "__THIS__" 代表输入数据集的基础表。选择语句指定输出中展示的字段、元素和表达式，支持Spark SQL 中的所有选择语句。用户还可以使用 Spark SQL 内置函数和U DFs（自定义函数）来对这些选定的列进行操作。SQLTransformer 支持如下语句：

SELECT a, a + b AS a_b FROM __THIS__

SELECT a, SQRT(b) AS b_sqrt FROM __THIS__ where a > 5

SELECT a, b, SUM(c) AS c_sum FROM __THIS__ GROUP BY a, b

例子:

假设我们有如下DataFrame包含 id，v1，v2列：

 id |  v1 |  v2
----|-----|-----
 0  | 1.0 | 3.0
 2  | 2.0 | 5.0

下面是使用  "SELECT *, (v1 + v2) AS v3, (v1 * v2) AS v4 FROM __THIS__" 的 SQLTransformer 的输出：

 id |  v1 |  v2 |  v3 |  v4
----|-----|-----|-----|-----
 0  | 1.0 | 3.0 | 4.0 | 3.0
 2  | 2.0 | 5.0 | 7.0 |10.0

Scala

import org.apache.spark.ml.feature.SQLTransformer

val df = spark.createDataFrame(
  Seq((0, 1.0, 3.0), (2, 2.0, 5.0))).toDF("id", "v1", "v2")

val sqlTrans = new SQLTransformer().setStatement(
  "SELECT *, (v1 + v2) AS v3, (v1 * v2) AS v4 FROM __THIS__")

sqlTrans.transform(df).show()

请参阅 SQLTransformer Scala docs  了解相关的 API 的详细信息

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/SQLTransformerExample.scala"里可以找到完整的示例代码。 

VectorAssembler（特征向量合并）

VectorAssembler 是将给定的一系列的列合并到单个向量列中的 transformer。它可以将原始特征和不同特征transformers（转换器）生成的特征合并为单个特征向量，来训练 ML 模型,如逻辑回归和决策树等机器学习算法。VectorAssembler 可接受以下的输入列类型：所有数值型、布尔类型、向量类型。输入列的值将按指定顺序依次添加到一个向量中。
Examples

假设我们有一个 DataFrame 包含 id, hour, mobile, userFeatures以及clicked 列：

 id | hour | mobile | userFeatures     | clicked
----|------|--------|------------------|---------
 0  | 18   | 1.0    | [0.0, 10.0, 0.5] | 1.0

userFeatures 是一个包含3个用户特征的特征列，我们希望将 hour, mobile 以及 userFeatures 组合为一个单一特征向量叫做 features，并将其用于预测是否点击。如果我们设置 VectorAssembler 的输入列为 hour , mobile 以及userFeatures，输出列为 features，转换后我们应该得到以下结果：

 id | hour | mobile | userFeatures     | clicked | features
----|------|--------|------------------|---------|-----------------------------
 0  | 18   | 1.0    | [0.0, 10.0, 0.5] | 1.0     | [18.0, 1.0, 0.0, 10.0, 0.5]

Scala

import org.apache.spark.ml.feature.VectorAssembler
import org.apache.spark.ml.linalg.Vectors

val dataset = spark.createDataFrame(
  Seq((0, 18, 1.0, Vectors.dense(0.0, 10.0, 0.5), 1.0))
).toDF("id", "hour", "mobile", "userFeatures", "clicked")

val assembler = new VectorAssembler()
  .setInputCols(Array("hour", "mobile", "userFeatures"))
  .setOutputCol("features")

val output = assembler.transform(dataset)
println(output.select("features", "clicked").first())

请参阅  VectorAssembler Scala docs了解相关的 API 的详细信息

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/VectorAssemblerExample.scala"里可以找到完整的示例代码。

QuantileDiscretizer（分位数离散化）

QuantileDiscretizer（分位数离散化）采用具有连续特征的列，并输出具有分类特征的列.bin（分级）的数量由numBuckets 参数设置。buckets（区间数）有可能小于这个值，例如,如果输入的不同值太少,就无法创建足够的不同的quantiles（分位数）.

NaN values：在 QuantileDiscretizer fitting 时，NaN值会从列中移除.这将产生一个 Bucketizer 模型进行预测.在转换过程中,Bucketizer 会发出错误信息当在数据集中找到 NaN 值，但用户也可以通过设置 handleInvalid 来选择保留或删除数据集中的 NaN 值.如果用户选择保留 NaN 值,那么它们将被特别处理并放入自己的 bucket（区间）中.例如,如果使用4个 buckets（区间）,那么非 NaN 数据将放入 buckets[0-3]，NaN将计数在特殊的 bucket[4] 中.

Algorithm：使用近似算法来选择 bin 的范围（有关详细说明可以参考 approxQuantile 的文档）。可以使用relativeError参数来控制近似的精度。当设置为零时,计算精确的 quantiles（分位数）（注意：计算 quantiles（分位数）是一项昂贵的操作.下边界和上边界将被 -Infinity（负无穷） 和 +Infinity（正无穷） 覆盖所有实际值）.

Examples

假设我们有一个 DataFrame 包含 id, hour 列：

 id | hour
----|------
 0  | 18.0
----|------
 1  | 19.0
----|------
 2  | 8.0
----|------
 3  | 5.0
----|------
 4  | 2.2

hour 是一个 Double 类型的连续特征，我们想要将连续的特征变成一个特征。将参数 numBuckets 设置为 3，我们应该得到以下 DataFrame ：

 id | hour | result
----|------|------
 0  | 18.0 | 2.0
----|------|------
 1  | 19.0 | 2.0
----|------|------
 2  | 8.0  | 1.0
----|------|------
 3  | 5.0  | 1.0
----|------|------
 4  | 2.2  | 0.0

Scala

import org.apache.spark.ml.feature.QuantileDiscretizer

val data = Array((0, 18.0), (1, 19.0), (2, 8.0), (3, 5.0), (4, 2.2))
var df = spark.createDataFrame(data).toDF("id", "hour")

val discretizer = new QuantileDiscretizer()
  .setInputCol("hour")
  .setOutputCol("result")
  .setNumBuckets(3)

val result = discretizer.fit(df).transform(df)
result.show()

请参阅  QuantileDiscretizer Scala docs了解相关的 API 的详细信息

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/QuantileDiscretizerExample.scala"里可以找到完整的示例代码。

Feature Selectors（特征选择）

VectorSlicer（向量切片机）

VectorSlicer是一个转换器，它采用特征向量，并输出一个新的特征向量与原始特征的子阵列。从向量列中提取特征很有用。

VectorSlicer接受具有指定索引的向量列，然后输出一个新的向量列，其值通过这些索引进行选择。有两种类型的指数：

代表向量中的索引的整数索引，setIndices()。

表示向量中特征名称的字符串索引，setNames()，此类要求向量列有AttributeGroup，因为实现在Attribute的name字段上匹配。

整数和字符串的规格都可以接受。此外，您可以同时使用整数索引和字符串名称。必须至少选择一个特征。重复的功能是不允许的，所以选择的索引和名称之间不能有重叠。请注意，如果选择了功能的名称，则会遇到空的输入属性时会抛出异常。

输出向量将首先（按照给定的顺序）对所选索引的特征进行排序，其次是所选择的名称（按照给定的顺序）。

Examples

假设我们有一个含有userFeatures列的DataFrame：

 userFeatures
------------------
 [0.0, 10.0, 0.5]

userFeatures是一个包含三个用户功能的向量列。

假设userFeature的第一列全部为0，因此我们要删除它并仅选择最后两列。

VectorSlicer使用setIndices（1,2）选择最后两个元素，然后生成一个名为features的新向量列：

 userFeatures     | features
------------------|-----------------------------
 [0.0, 10.0, 0.5] | [10.0, 0.5]

假设我们对userFeatures具有潜在的输入属性，即["f1", "f2", "f3"]，那么我们可以使用setNames("f2", "f3")来选择它们。

 userFeatures     | features
------------------|-----------------------------
 [0.0, 10.0, 0.5] | [10.0, 0.5]
 ["f1", "f2", "f3"] | ["f2", "f3"]

Scala

import java.util.Arrays

import org.apache.spark.ml.attribute.{Attribute, AttributeGroup, NumericAttribute}
import org.apache.spark.ml.feature.VectorSlicer
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.StructType

val data = Arrays.asList(
  Row(Vectors.sparse(3, Seq((0, -2.0), (1, 2.3)))),
  Row(Vectors.dense(-2.0, 2.3, 0.0))
)

val defaultAttr = NumericAttribute.defaultAttr
val attrs = Array("f1", "f2", "f3").map(defaultAttr.withName)
val attrGroup = new AttributeGroup("userFeatures", attrs.asInstanceOf[Array[Attribute]])

val dataset = spark.createDataFrame(data, StructType(Array(attrGroup.toStructField())))

val slicer = new VectorSlicer().setInputCol("userFeatures").setOutputCol("features")

slicer.setIndices(Array(1)).setNames(Array("f3"))
// or slicer.setIndices(Array(1, 2)), or slicer.setNames(Array("f2", "f3"))

val output = slicer.transform(dataset)
output.show(false)

请参阅  VectorSlicer Scala docs了解相关的 API 的详细信息

 在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/VectorSlicerExample.scala"里可以找到完整的示例代码。

RFormula（R模型公式） 

RFormula选择由R模型公式（R model formula）指定的列。目前，我们支持R运算符的有限子集，包括‘~’, ‘.’, ‘:’, ‘+’以及‘-‘，基本操作如下：

 ~分隔目标和对象

 +合并对象，“+ 0”表示删除截距

 - 删除对象，“ - 1”表示删除截距

 :交互（数字乘法或二值化分类值）

 . 除了目标外的全部列

假设a和b是double列，我们使用以下简单的例子来说明RFormula的效果：

y ~ a + b表示模型y ~ w0 + w1 * a +w2 * b其中w0为截距，w1和w2为相关系数。

y ~a + b + a:b – 1表示模型y ~ w1* a + w2 * b + w3 * a * b，其中w1，w2，w3是相关系数。

RFormula产生一个特征向量列和一个标签的double列或label列。 像R在线性回归中使用公式时，字符型的输入将转换成one-hot编码，数字列将被转换为双精度。 如果label列是类型字符串，则它将首先使用StringIndexer转换为double。 如果DataFrame中不存在label列，则会从公式中指定的响应变量创建输出标签列。

Examples

假设我们有一个具有列id，country，hour和clicked的DataFrame：

id | country | hour | clicked
---|---------|------|---------
 7 | "US"    | 18   | 1.0
 8 | "CA"    | 12   | 0.0
 9 | "NZ"    | 15   | 0.0

如果我们使用具有clicked ~ country + hour的公式字符串的RFormula，这表示我们想要基于country 和hour预测clicked，转换后我们应该得到以下DataFrame：

id | country | hour | clicked | features         | label
---|---------|------|---------|------------------|-------
 7 | "US"    | 18   | 1.0     | [0.0, 0.0, 18.0] | 1.0
 8 | "CA"    | 12   | 0.0     | [0.0, 1.0, 12.0] | 0.0
 9 | "NZ"    | 15   | 0.0     | [1.0, 0.0, 15.0] | 0.0

Scala

import org.apache.spark.ml.feature.RFormula

val dataset = spark.createDataFrame(Seq(
  (7, "US", 18, 1.0),
  (8, "CA", 12, 0.0),
  (9, "NZ", 15, 0.0)
)).toDF("id", "country", "hour", "clicked")

val formula = new RFormula()
  .setFormula("clicked ~ country + hour")
  .setFeaturesCol("features")
  .setLabelCol("label")

val output = formula.fit(dataset).transform(dataset)
output.select("features", "label").show()

请参阅 RFormula Scala docs 了解相关的 API 的详细信息

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/RFormulaExample.scala"里可以找到完整的示例代码。

ChiSqSelector（卡方特征选择器）

ChiSqSelector代表卡方特征选择。它适用于带有类别特征的标签数据。ChiSqSelector使用卡方独立测试来决定选择哪些特征。它支持三种选择方法：numTopFeatures, percentile, fpr：

numTopFeatures根据卡方检验选择固定数量的顶级功能。这类似于产生具有最大预测能力的功能。

percentile类似于numTopFeatures，但选择所有功能的一部分，而不是固定数量。

fpr选择p值低于阈值的所有特征，从而控制选择的假阳性率。

默认情况下，选择方法是numTopFeatures，默认的顶级功能数量设置为50.用户可以使用setSelectorType选择一种选择方法。

Examples

假设我们有一个具有列id, features和clicked的DataFrame，这被用作我们预测的目标：

id | features              | clicked
---|-----------------------|---------
 7 | [0.0, 0.0, 18.0, 1.0] | 1.0
 8 | [0.0, 1.0, 12.0, 0.0] | 0.0
 9 | [1.0, 0.0, 15.0, 0.1] | 0.0

如果我们使用ChiSqSelector并设置numTopFeatures=1，根据我们所有的特征，其中最后一列标签clicked是认为最有用的特征：

id | features              | clicked | selectedFeatures
---|-----------------------|---------|------------------
 7 | [0.0, 0.0, 18.0, 1.0] | 1.0     | [1.0]
 8 | [0.0, 1.0, 12.0, 0.0] | 0.0     | [0.0]
 9 | [1.0, 0.0, 15.0, 0.1] | 0.0     | [0.1]

Scala

import org.apache.spark.ml.feature.ChiSqSelector
import org.apache.spark.ml.linalg.Vectors

val data = Seq(
  (7, Vectors.dense(0.0, 0.0, 18.0, 1.0), 1.0),
  (8, Vectors.dense(0.0, 1.0, 12.0, 0.0), 0.0),
  (9, Vectors.dense(1.0, 0.0, 15.0, 0.1), 0.0)
)

val df = spark.createDataset(data).toDF("id", "features", "clicked")

val selector = new ChiSqSelector()
  .setNumTopFeatures(1)
  .setFeaturesCol("features")
  .setLabelCol("clicked")
  .setOutputCol("selectedFeatures")

val result = selector.fit(df).transform(df)

println(s"ChiSqSelector output with top ${selector.getNumTopFeatures} features selected")
result.show()

请参阅 ChiSqSelector Scala docs 了解相关的 API 的详细信息

在Spark repo中路径"examples/src/main/scala/org/apache/spark/examples/ml/ChiSqSelectorExample.scala"里可以找到完整的示例代码。

Locality Sensitive Hashing（局部敏感哈希）

LSH是哈希技术中重要的一种，通常用于集群，近似最近邻搜索和大型数据集的孤立点检测。

LSH的大致思路是用一系列函数（LSH families）将数据哈希到桶中，这样彼此接近的数据点处于相同的桶中可能性就会很高，而彼此相距很远的数据点很可能处于不同的桶中。一个LSH family 正式定义如下。

在度量空间(M,d)中，M是一个集合，d是M上的一个距离函数，LSH family是一系列能满足以下属性的函数h：

满足以上条件的LSH family被称为(r1, r2, p1, p2)-sensitive。

在Spark中，不同的LSH families实现在不同的类中（例如：MinHash ），并且在每个类中提供了用于特征变换的API，近似相似性连接和近似最近邻。

在LSH中，我们将一个假阳性定义为一对相距大的输入特征（当 d(p,q)≥r2 时），它们被哈希到同一个桶中，并且将一个假阴性定义为一对相邻的特征（当  d(p,q)≤r1 时 ），它们被分散到不同的桶中。

LSH Operations（LSH运算）

我们称使用LSH的主要操作类型为LSH 运算。 一个合适的LSH模型对每一个操作中都有对应的方法。

Feature Transformation（特征变换）

特征变换是将哈希值添加为新列的基本功能。 这可以有助于降低维数。 用户可以通过设置 inputCol 和 outputCol  参数来指定输入和输出列名。

LSH 还支持多个LSH哈希表。 用户可以通过设置 numHashTables 来指定哈希表的数量。 这也用于近似相似性连接和近似最近邻的 OR-amplification（或放大器）放大。 增加哈希表的数量将增加准确性，但也会增加通信成本和运行时间。

outputCol 的类型是 Seq [Vector]，其中数组的维数等于 numHashTables ，并且向量的维度当前设置为1。在将来的版本中，我们将实现 AND-amplification（与放大器），以便用户可以指定这些向量的维度 。

Approximate Similarity Join（近似相似度连接）

近似相似度连接采用两个数据集，并且近似返回距离小于用户定义阈值的数据集中的行对。 近似相似度连接支持两个不同的数据集连接和自连接。 Self-joinin （自连接）会产生一些重复的对。

近似相似度连接接受已转换和未转换的数据集作为输入。 如果使用未转换的数据集，它将自动转换。 在这种情况下，哈希签名将被创建为outputCol。

在加入的数据集中，可以在数据集A和数据集B中查询原始数据集。 距离列将被添加到输出数据集，以显示返回的每对行之间的真实距离。

Approximate Nearest Neighbor Search（近似最邻近搜索）

近似最近邻搜索采用数据集（特征向量）和密钥（单个特征向量），并且它近似返回数据集中最接近向量的指定数量的行。

近似最近邻搜索接受已转换和未转换的数据集作为输入。 如果使用未转换的数据集，它将自动转换。 在这种情况下，哈希签名将被创建为outputCol。

距离列将被添加到输出数据集，以显示每个输出行和搜索的键之间的真实距离。

注意：当哈希桶中没有足够的候选项时，近似最近邻搜索将返回少于k行。

LSH Algorithms（LSH算法）

Bucketed Random Projection for Euclidean Distance（ 欧几里得度量的随机投影）

随机桶投影是用于欧几里德距离的 LSH family。 欧氏度量的定义如下：

其LSH family将向量x特征向量映射到随机单位矢量v，并将映射结果分为哈希桶中：

其中r是用户定义的桶长度，桶长度可用于控制哈希桶的平均大小（因此也可用于控制桶的数量）。 较大的桶长度（即，更少的桶）增加了将特征哈希到相同桶的概率（增加真实和假阳性的数量）。

桶随机投影接受任意向量作为输入特征，并支持稀疏和密集向量。

Scala

import org.apache.spark.ml.feature.BucketedRandomProjectionLSH
import org.apache.spark.ml.linalg.Vectors

val dfA = spark.createDataFrame(Seq(
  (0, Vectors.dense(1.0, 1.0)),
  (1, Vectors.dense(1.0, -1.0)),
  (2, Vectors.dense(-1.0, -1.0)),
  (3, Vectors.dense(-1.0, 1.0))
)).toDF("id", "keys")

val dfB = spark.createDataFrame(Seq(
  (4, Vectors.dense(1.0, 0.0)),
  (5, Vectors.dense(-1.0, 0.0)),
  (6, Vectors.dense(0.0, 1.0)),
  (7, Vectors.dense(0.0, -1.0))
)).toDF("id", "keys")

val key = Vectors.dense(1.0, 0.0)

val brp = new BucketedRandomProjectionLSH()
  .setBucketLength(2.0)
  .setNumHashTables(3)
  .setInputCol("keys")
  .setOutputCol("values")

val model = brp.fit(dfA)

// Feature Transformation
model.transform(dfA).show()
// Cache the transformed columns
val transformedA = model.transform(dfA).cache()
val transformedB = model.transform(dfB).cache()

// Approximate similarity join
model.approxSimilarityJoin(dfA, dfB, 1.5).show()
model.approxSimilarityJoin(transformedA, transformedB, 1.5).show()
// Self Join
model.approxSimilarityJoin(dfA, dfA, 2.5).filter("datasetA.id < datasetB.id").show()

// Approximate nearest neighbor search
model.approxNearestNeighbors(dfA, key, 2).show()
model.approxNearestNeighbors(transformedA, key, 2).show()

如需了解API中的更多详细信息，请参阅 BucketedRandomProjectionLSH Scala 文档

在Spark repo 路径“examples/src/main/scala/org/apache/spark/examples/ml/BucketedRandomProjectionLSHExample.scala”中查找完整示例代码。

MinHash for Jaccard Distance

MinHash 是一个用于Jaccard 距离的 LSH family，它的输入特征是自然数的集合。 两组的Jaccard距离由它们的交集和并集的基数定义：

MinHash 将随机哈希函数g应用于集合中的每个元素，并取得所有哈希值中的最小值。

MinHash 的输入集合表示为二进制向量，其中向量索引表示元素本身，向量中的非零值表示该元素在集合中存在。尽管支持稠密和稀疏向量，但通常推荐使用稀疏向量来提高效率。 例如，Vectors.sparse（10，Array [（2，1.0），（3，1.0），（5，1.0）]）表示空间中有10个元素。 该集合包含 elem 2，elem 3 和 elem 5。所有非零值都被视为二进制“1”值。

注意：空集不能被MinHash转换，这意味着任何输入向量必须至少有一个非零条目。

Scala

import org.apache.spark.ml.feature.MinHashLSH
import org.apache.spark.ml.linalg.Vectors

val dfA = spark.createDataFrame(Seq(
  (0, Vectors.sparse(6, Seq((0, 1.0), (1, 1.0), (2, 1.0)))),
  (1, Vectors.sparse(6, Seq((2, 1.0), (3, 1.0), (4, 1.0)))),
  (2, Vectors.sparse(6, Seq((0, 1.0), (2, 1.0), (4, 1.0))))
)).toDF("id", "keys")

val dfB = spark.createDataFrame(Seq(
  (3, Vectors.sparse(6, Seq((1, 1.0), (3, 1.0), (5, 1.0)))),
  (4, Vectors.sparse(6, Seq((2, 1.0), (3, 1.0), (5, 1.0)))),
  (5, Vectors.sparse(6, Seq((1, 1.0), (2, 1.0), (4, 1.0))))
)).toDF("id", "keys")

val key = Vectors.sparse(6, Seq((1, 1.0), (3, 1.0)))

val mh = new MinHashLSH()
  .setNumHashTables(3)
  .setInputCol("keys")
  .setOutputCol("values")

val model = mh.fit(dfA)

// Feature Transformation
model.transform(dfA).show()
// Cache the transformed columns
val transformedA = model.transform(dfA).cache()
val transformedB = model.transform(dfB).cache()

// Approximate similarity join
model.approxSimilarityJoin(dfA, dfB, 0.6).show()
model.approxSimilarityJoin(transformedA, transformedB, 0.6).show()
// Self Join
model.approxSimilarityJoin(dfA, dfA, 0.6).filter("datasetA.id < datasetB.id").show()

// Approximate nearest neighbor search
model.approxNearestNeighbors(dfA, key, 2).show()
model.approxNearestNeighbors(transformedA, key, 2).show()

如需了解API中的更多详细信息，请参阅 MinHashLSH Scala 文档

在Spark repo 路径“examples/src/main/scala/org/apache/spark/examples/ml/MinHashLSHExample.scala”中查找完整示例代码。

Classification and regression（分类和回归）

本页面介绍了分类和回归的算法。它还包括讨论特定类别的算法部分，如：线性方法，树和集成。

目录如下：

原文链接 : http://spark.apache.org/docs/2.1.0/ml-classification-regression.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=9406543

贡献者 : Sehriff 片刻 一生逍遥 ApacheCN Apache中文网

Classification（分类）

Logistic Regression（逻辑回归）

逻辑回归是预测分类反应的流行方法。 广义线性模型的一个特例是预测结果的可能性。 在spark.ml逻辑回归中可以使用二项式逻辑回归来预测二进制结果，也可以通过使用多项Logistic回归来预测多类结果。 使用系列参数在这两种算法之间进行选择，或者将其设置为未设置，Spark将推断出正确的变体。

通过将家族参数设置为“多项式”，可以将多项Logistic回归用于二进制分类。它将产生两组系数和两个截距。

当在具有常量非零列的数据集上对LogisticRegressionModel进行拟合时，Spark MLlib为常数非零列输出零系数。此行为与R glmnet相同，但与LIBSVM不同。

Binomial logistic regression（二项式逻辑回归）

有关二项式逻辑回归实现的更多背景和更多细节，请参阅spark.mllib中逻辑回归的文档。

示例

以下示例显示了如何用弹性网络正则化来训练二项分类的二项式和多项Logistic回归模型。 elasticNetParam对应于αα，regParam对应于λ。

Scala

import org.apache.spark.ml.classification.LogisticRegression

// Load training data
val training = spark.read.format("libsvm").load("data/mllib/sample_libsvm_data.txt")

val lr = new LogisticRegression()
  .setMaxIter(10)
  .setRegParam(0.3)
  .setElasticNetParam(0.8)

// Fit the model
val lrModel = lr.fit(training)

// Print the coefficients and intercept for logistic regression
println(s"Coefficients: ${lrModel.coefficients} Intercept: ${lrModel.intercept}")

// We can also use the multinomial family for binary classification
val mlr = new LogisticRegression()
  .setMaxIter(10)
  .setRegParam(0.3)
  .setElasticNetParam(0.8)
  .setFamily("multinomial")

val mlrModel = mlr.fit(training)

// Print the coefficients and intercepts for logistic regression with multinomial family
println(s"Multinomial coefficients: ${mlrModel.coefficientMatrix}")
println(s"Multinomial intercepts: ${mlrModel.interceptVector}")

有关参数的更多详细信息，请参见Scala API文档 

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/LogisticRegressionWithElasticNetExample.scala”中查找完整示例代码。

逻辑回归的spark.ml实现也支持在训练集中提取模型的摘要。 请注意，在BinaryLogisticRegressionSummary中存储为DataFrame的预测和度量标注为@transient，因此仅在驱动程序上可用。

LogisticRegressionTrainingSummary为LogisticRegressionModel提供了一个摘要。 目前，只支持二进制分类，必须将摘要显式转换为BinaryLogisticRegressionTrainingSummary。 当支持多类分类时，这可能会发生变化。

继续前面的例子：

Scala

import org.apache.spark.ml.classification.{BinaryLogisticRegressionSummary, LogisticRegression}

// Extract the summary from the returned LogisticRegressionModel instance trained in the earlier
// example
val trainingSummary = lrModel.summary

// Obtain the objective per iteration.
val objectiveHistory = trainingSummary.objectiveHistory
println("objectiveHistory:")
objectiveHistory.foreach(loss => println(loss))

// Obtain the metrics useful to judge performance on test data.
// We cast the summary to a BinaryLogisticRegressionSummary since the problem is a
// binary classification problem.
val binarySummary = trainingSummary.asInstanceOf[BinaryLogisticRegressionSummary]

// Obtain the receiver-operating characteristic as a dataframe and areaUnderROC.
val roc = binarySummary.roc
roc.show()
println(s"areaUnderROC: ${binarySummary.areaUnderROC}")

// Set the model threshold to maximize F-Measure
val fMeasure = binarySummary.fMeasureByThreshold
val maxFMeasure = fMeasure.select(max("F-Measure")).head().getDouble(0)
val bestThreshold = fMeasure.where($"F-Measure" === maxFMeasure)
  .select("threshold").head().getDouble(0)
lrModel.setThreshold(bestThreshold)

 在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/LogisticRegressionSummaryExample.scala”中查找完整示例代码。

Multinomial logistic regression（多项Logistic回归）

通过多项Logistic（softmax）回归支持多类分类。 在多项Logistic回归中，该算法产生K个系数集，或K×J矩阵，其中K是结果类的数量，J是特征数。 如果算法与截距项拟合，则截距的长度K向量是可用的。

多项式系数可用作系数矩阵，截距可作为interceptVector使用。
不支持用多项式族训练的逻辑回归模型的系数和截距方法。 改用系数矩阵和interceptVector。
使用softmax函数对结果类k∈1,2，...，K的条件概率进行建模。

我们使用多项式响应模型将加权负对数似然值最小化，并用弹性网络惩罚来控制过拟合。

有关详细的推导，请参见这里。

示例

以下示例展示了如何使用弹性网络正则化来训练多元逻辑回归模型。

Scala

import org.apache.spark.ml.classification.LogisticRegression

// Load training data
val training = spark
  .read
  .format("libsvm")
  .load("data/mllib/sample_multiclass_classification_data.txt")

val lr = new LogisticRegression()
  .setMaxIter(10)
  .setRegParam(0.3)
  .setElasticNetParam(0.8)

// Fit the model
val lrModel = lr.fit(training)

// Print the coefficients and intercept for multinomial logistic regression
println(s"Coefficients: \n${lrModel.coefficientMatrix}")
println(s"Intercepts: ${lrModel.interceptVector}")

 查找Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/MulticlassLogisticRegressionWithElasticNetExample.scala”的完整示例代码。

Decision tree classifier（决策树分类器）

决策树是一种流行的分类和回归方法。有关spark.ml实现的更多信息，请参见决策树部分。

 示例

以下示例以LibSVM格式加载数据集，将其拆分为训练和测试集，在第一个数据集上训练，然后对所保留的测试集进行评估。 我们使用两个特征变压器来准备数据; 这些帮助索引类别的标签和分类功能，添加元数据到决策树算法可以识别的DataFrame。

Scala

import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.classification.DecisionTreeClassificationModel
import org.apache.spark.ml.classification.DecisionTreeClassifier
import org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator
import org.apache.spark.ml.feature.{IndexToString, StringIndexer, VectorIndexer}

// Load the data stored in LIBSVM format as a DataFrame.
val data = spark.read.format("libsvm").load("data/mllib/sample_libsvm_data.txt")

// Index labels, adding metadata to the label column.
// Fit on whole dataset to include all labels in index.
val labelIndexer = new StringIndexer()
  .setInputCol("label")
  .setOutputCol("indexedLabel")
  .fit(data)
// Automatically identify categorical features, and index them.
val featureIndexer = new VectorIndexer()
  .setInputCol("features")
  .setOutputCol("indexedFeatures")
  .setMaxCategories(4) // features with > 4 distinct values are treated as continuous.
  .fit(data)

// Split the data into training and test sets (30% held out for testing).
val Array(trainingData, testData) = data.randomSplit(Array(0.7, 0.3))

// Train a DecisionTree model.
val dt = new DecisionTreeClassifier()
  .setLabelCol("indexedLabel")
  .setFeaturesCol("indexedFeatures")

// Convert indexed labels back to original labels.
val labelConverter = new IndexToString()
  .setInputCol("prediction")
  .setOutputCol("predictedLabel")
  .setLabels(labelIndexer.labels)

// Chain indexers and tree in a Pipeline.
val pipeline = new Pipeline()
  .setStages(Array(labelIndexer, featureIndexer, dt, labelConverter))

// Train model. This also runs the indexers.
val model = pipeline.fit(trainingData)

// Make predictions.
val predictions = model.transform(testData)

// Select example rows to display.
predictions.select("predictedLabel", "label", "features").show(5)

// Select (prediction, true label) and compute test error.
val evaluator = new MulticlassClassificationEvaluator()
  .setLabelCol("indexedLabel")
  .setPredictionCol("prediction")
  .setMetricName("accuracy")
val accuracy = evaluator.evaluate(predictions)
println("Test Error = " + (1.0 - accuracy))

val treeModel = model.stages(2).asInstanceOf[DecisionTreeClassificationModel]
println("Learned classification tree model:\n" + treeModel.toDebugString)

有关参数的更多详细信息，请参见Scala API文档

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/DecisionTreeClassificationExample.scala”中查找完整示例代码。

Random forest classifier（随机森林分类）

随机森林是一类受欢迎的分类和回归方法。有关spark.ml实现的更多信息，请参见随机林部分。

 示例

以下示例以LibSVM格式加载数据集，将其拆分为训练和测试集，在第一个数据集上训练，然后对所保留的测试集进行评估。 我们使用两个特征变压器来准备数据; 这些帮助索引类别的标签和分类功能，添加元数据到基于树的算法可以识别的DataFrame。

Scala

import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.classification.{RandomForestClassificationModel, RandomForestClassifier}
import org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator
import org.apache.spark.ml.feature.{IndexToString, StringIndexer, VectorIndexer}

// Load and parse the data file, converting it to a DataFrame.
val data = spark.read.format("libsvm").load("data/mllib/sample_libsvm_data.txt")

// Index labels, adding metadata to the label column.
// Fit on whole dataset to include all labels in index.
val labelIndexer = new StringIndexer()
  .setInputCol("label")
  .setOutputCol("indexedLabel")
  .fit(data)
// Automatically identify categorical features, and index them.
// Set maxCategories so features with > 4 distinct values are treated as continuous.
val featureIndexer = new VectorIndexer()
  .setInputCol("features")
  .setOutputCol("indexedFeatures")
  .setMaxCategories(4)
  .fit(data)

// Split the data into training and test sets (30% held out for testing).
val Array(trainingData, testData) = data.randomSplit(Array(0.7, 0.3))

// Train a RandomForest model.
val rf = new RandomForestClassifier()
  .setLabelCol("indexedLabel")
  .setFeaturesCol("indexedFeatures")
  .setNumTrees(10)

// Convert indexed labels back to original labels.
val labelConverter = new IndexToString()
  .setInputCol("prediction")
  .setOutputCol("predictedLabel")
  .setLabels(labelIndexer.labels)

// Chain indexers and forest in a Pipeline.
val pipeline = new Pipeline()
  .setStages(Array(labelIndexer, featureIndexer, rf, labelConverter))

// Train model. This also runs the indexers.
val model = pipeline.fit(trainingData)

// Make predictions.
val predictions = model.transform(testData)

// Select example rows to display.
predictions.select("predictedLabel", "label", "features").show(5)

// Select (prediction, true label) and compute test error.
val evaluator = new MulticlassClassificationEvaluator()
  .setLabelCol("indexedLabel")
  .setPredictionCol("prediction")
  .setMetricName("accuracy")
val accuracy = evaluator.evaluate(predictions)
println("Test Error = " + (1.0 - accuracy))

val rfModel = model.stages(2).asInstanceOf[RandomForestClassificationModel]
println("Learned classification forest model:\n" + rfModel.toDebugString)

请参阅Scala的API文档的更多细节

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/RandomForestClassifierExample.scala”中查找完整示例代码。

Gradient-boosted tree classifier（梯度增强树分类器）

梯度增强树（GBT）是使用决策树组合的流行分类和回归方法。 有关spark.ml实现的更多信息，请参见GBT部分。

Example

以下示例以LibSVM格式加载数据集，将其分解为训练和测试集，在第一个数据集上训练，然后在被测试的集合上进行训练。 我们使用两个特征变压器来准备数据; 这些帮助索引类别的标签和分类功能，添加元数据到基于树的算法可以识别的DataFrame

Scala

import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.classification.{GBTClassificationModel, GBTClassifier}
import org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator
import org.apache.spark.ml.feature.{IndexToString, StringIndexer, VectorIndexer}

// Load and parse the data file, converting it to a DataFrame.
val data = spark.read.format("libsvm").load("data/mllib/sample_libsvm_data.txt")

// Index labels, adding metadata to the label column.
// Fit on whole dataset to include all labels in index.
val labelIndexer = new StringIndexer()
  .setInputCol("label")
  .setOutputCol("indexedLabel")
  .fit(data)
// Automatically identify categorical features, and index them.
// Set maxCategories so features with > 4 distinct values are treated as continuous.
val featureIndexer = new VectorIndexer()
  .setInputCol("features")
  .setOutputCol("indexedFeatures")
  .setMaxCategories(4)
  .fit(data)

// Split the data into training and test sets (30% held out for testing).
val Array(trainingData, testData) = data.randomSplit(Array(0.7, 0.3))

// Train a GBT model.
val gbt = new GBTClassifier()
  .setLabelCol("indexedLabel")
  .setFeaturesCol("indexedFeatures")
  .setMaxIter(10)

// Convert indexed labels back to original labels.
val labelConverter = new IndexToString()
  .setInputCol("prediction")
  .setOutputCol("predictedLabel")
  .setLabels(labelIndexer.labels)

// Chain indexers and GBT in a Pipeline.
val pipeline = new Pipeline()
  .setStages(Array(labelIndexer, featureIndexer, gbt, labelConverter))

// Train model. This also runs the indexers.
val model = pipeline.fit(trainingData)

// Make predictions.
val predictions = model.transform(testData)

// Select example rows to display.
predictions.select("predictedLabel", "label", "features").show(5)

// Select (prediction, true label) and compute test error.
val evaluator = new MulticlassClassificationEvaluator()
  .setLabelCol("indexedLabel")
  .setPredictionCol("prediction")
  .setMetricName("accuracy")
val accuracy = evaluator.evaluate(predictions)
println("Test Error = " + (1.0 - accuracy))

val gbtModel = model.stages(2).asInstanceOf[GBTClassificationModel]
println("Learned classification GBT model:\n" + gbtModel.toDebugString)

请参阅Scala的API文档的更多细节

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/GradientBoostedTreeClassifierExample.scala”中查找完整示例代码。

Multilayer perceptron classifier（多层感知器分类器）

多层感知器分类器（MLPC）是基于前馈人工神经网络的分类器。 MLPC由多层节点组成。 每个层完全连接到网络中的下一层。 输入层中的节点表示输入数据。 所有其他节点通过输入与节点权重ww和偏差bb的线性组合将输入映射到输出，并应用激活功能。 这可以用矩阵形式写入具有K + 1层的MLPC，如下所示：

中间层节点使用Sigmoid（logistic）函数：

输出层节点使用softmax函数：

输出层中的节点数N对应于类的数量。

MLPC采用反向传播来学习模型。我们使用物流损失函数进行优化和L-BFGS作为优化程序。

Example

Scala

import org.apache.spark.ml.classification.MultilayerPerceptronClassifier
import org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator

// Load the data stored in LIBSVM format as a DataFrame.
val data = spark.read.format("libsvm")
  .load("data/mllib/sample_multiclass_classification_data.txt")

// Split the data into train and test
val splits = data.randomSplit(Array(0.6, 0.4), seed = 1234L)
val train = splits(0)
val test = splits(1)

// specify layers for the neural network:
// input layer of size 4 (features), two intermediate of size 5 and 4
// and output of size 3 (classes)
val layers = Array[Int](4, 5, 4, 3)

// create the trainer and set its parameters
val trainer = new MultilayerPerceptronClassifier()
  .setLayers(layers)
  .setBlockSize(128)
  .setSeed(1234L)
  .setMaxIter(100)

// train the model
val model = trainer.fit(train)

// compute accuracy on the test set
val result = model.transform(test)
val predictionAndLabels = result.select("prediction", "label")
val evaluator = new MulticlassClassificationEvaluator()
  .setMetricName("accuracy")

println("Test set accuracy = " + evaluator.evaluate(predictionAndLabels))

请参阅Scala的API文档的更多细节

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/MultilayerPerceptronClassifierExample.scala”中查找完整示例代码。

One-vs-Rest classifier (a.k.a. One-vs-All)（ 一对一休息分类器（a.k.a.一对全） ）

OneVsRest是用于执行多类别分类的机器学习简化的示例，给定可以有效地执行二进制分类的基本分类器。它也被称为“一对一”。

OneVsRest作为估计器实现。对于基类分类器，它需要分类器的实例，并为每个k类创建二进制分类问题。对i类的分类器进行训练，以预测标签是否为我，将i类与所有其他类区分开来。

预测是通过评估每个二进制分类器来完成的，最可靠的分类器的索引作为标签输出。

Example

下面的示例演示如何加载Iris数据集，将其解析为DataFrame，并使用OneVsRest执行多类分类。计算出测试误差来测量算法的准确性。

Scala

import org.apache.spark.ml.classification.{LogisticRegression, OneVsRest}
import org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator

// load data file.
val inputData = spark.read.format("libsvm")
  .load("data/mllib/sample_multiclass_classification_data.txt")

// generate the train/test split.
val Array(train, test) = inputData.randomSplit(Array(0.8, 0.2))

// instantiate the base classifier
val classifier = new LogisticRegression()
  .setMaxIter(10)
  .setTol(1E-6)
  .setFitIntercept(true)

// instantiate the One Vs Rest Classifier.
val ovr = new OneVsRest().setClassifier(classifier)

// train the multiclass model.
val ovrModel = ovr.fit(train)

// score the model on test data.
val predictions = ovrModel.transform(test)

// obtain evaluator.
val evaluator = new MulticlassClassificationEvaluator()
  .setMetricName("accuracy")

// compute the classification error on test data.
val accuracy = evaluator.evaluate(predictions)
println(s"Test Error = ${1 - accuracy}")

请参阅Scala的API文档的更多细节

查找Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/OneVsRestExample.scala”的完整示例代码。

Naive Bayes（朴素贝叶斯）

朴素贝叶斯分类器是一个简单概率分类器的家族，基于贝叶斯定理与特征之间的强烈（天真）独立假设。spark.ml实现目前支持多项朴素贝叶斯和伯努利天真贝叶斯。有关更多信息，请参阅MLlib中Naive Bayes部分。

Example

Scala

import org.apache.spark.ml.classification.NaiveBayes
import org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator

// Load the data stored in LIBSVM format as a DataFrame.
val data = spark.read.format("libsvm").load("data/mllib/sample_libsvm_data.txt")

// Split the data into training and test sets (30% held out for testing)
val Array(trainingData, testData) = data.randomSplit(Array(0.7, 0.3), seed = 1234L)

// Train a NaiveBayes model.
val model = new NaiveBayes()
  .fit(trainingData)

// Select example rows to display.
val predictions = model.transform(testData)
predictions.show()

// Select (prediction, true label) and compute test error
val evaluator = new MulticlassClassificationEvaluator()
  .setLabelCol("label")
  .setPredictionCol("prediction")
  .setMetricName("accuracy")
val accuracy = evaluator.evaluate(predictions)
println("Test set accuracy = " + accuracy)

请参阅Scala的API文档的更多细节

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/NaiveBayesExample.scala”中查找完整示例代码。

Regression（回归）

Linear regression（线性回归）

使用线性回归模型和模型摘要的界面类似于逻辑回归案例。 

在使用“l-bfgs”求解器的常量非零列的数据集上对LinearRegressionModel进行拟合时，Spark MLlib为常数非零列输出零系数。此行为与R glmnet相同，但与LIBSVM不同。

Example

以下示例演示了训练弹性网络正则化线性回归模型并提取模型汇总统计量。

Scala

import org.apache.spark.ml.regression.LinearRegression

// Load training data
val training = spark.read.format("libsvm")
  .load("data/mllib/sample_linear_regression_data.txt")

val lr = new LinearRegression()
  .setMaxIter(10)
  .setRegParam(0.3)
  .setElasticNetParam(0.8)

// Fit the model
val lrModel = lr.fit(training)

// Print the coefficients and intercept for linear regression
println(s"Coefficients: ${lrModel.coefficients} Intercept: ${lrModel.intercept}")

// Summarize the model over the training set and print out some metrics
val trainingSummary = lrModel.summary
println(s"numIterations: ${trainingSummary.totalIterations}")
println(s"objectiveHistory: [${trainingSummary.objectiveHistory.mkString(",")}]")
trainingSummary.residuals.show()
println(s"RMSE: ${trainingSummary.rootMeanSquaredError}")
println(s"r2: ${trainingSummary.r2}")

有关参数的更多详细信息，请参见Scala API文档

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/LinearRegressionWithElasticNetExample.scala”中查找完整示例代码。

Generalized linear regression（广义线性回归）

与线性回归相比，输出被假设为跟随高斯分布，广义线性模型（GLM）是线性模型的规范，其中响应变量Yi遵循指数族分布的一些分布。 Spark的GeneralizedLinearRegression界面允许灵活的GLM规范，可用于各种类型的预测问题，包括线性回归，泊松回归，逻辑回归等。 目前在spark.ml中，仅支持指数族分布的一部分，下面列出它们。

NOTE: Spark目前仅通过其GeneralizedLinearRegression接口支持多达4096个功能，如果超出此约束，则会抛出异常。 有关详细信息，请参阅高级部分。 然而，对于线性和逻辑回归，可以使用线性回归和逻辑回归估计器来训练具有增加的特征数量的模型。

GLM需要指数族分布，可以用他们的“canonical（规范）”或“natural（自然）”形式，即自然指数族分布。 自然指数族分布的形式如下：

其中θ是感兴趣的参数，τ是色散参数。在GLM中，假设响应变量YiYi是从自然指数族分布中得出的：

其中感兴趣的参数θi与响应变量μi的预期值相关

这里，A'(θi) 由所选分布的形式定义。 GLM还允许指定链接功能，其定义响应变量μi的期望值与所谓的线性预测器ηi之间的关系：

通常，选择链接函数使得A'= g-1，其产生关注参数θθ和线性预测器η之间的简化关系。 在这种情况下，链接函数 g(μ) 被称为“canonical（规范）”链接功能。

GLM找到使似然函数最大化的回归系数。

其中感兴趣的参数θi与回归系数相关

Spark的广义线性回归界面还提供诊断GLM模型拟合的汇总统计量，包括残差，p值，偏差，Akaike信息准则等。

参见这里，对GLM及其应用进行更全面的综述。

Available families（有用家族）

Family | Response Type | Supported Links
Gaussian | Continuous | Identity*, Log, Inverse
Binomial | Binary | Logit*, Probit, CLogLog
Poisson | Count | Log*, Identity, Sqrt
Gamma | Continuous | Inverse*, Idenity, Log

* 规范链接

Example

以下示例演示了使用高斯响应和身份链接功能训练GLM并提取模型汇总统计信息。

Scala

import org.apache.spark.ml.regression.GeneralizedLinearRegression

// Load training data
val dataset = spark.read.format("libsvm")
  .load("data/mllib/sample_linear_regression_data.txt")

val glr = new GeneralizedLinearRegression()
  .setFamily("gaussian")
  .setLink("identity")
  .setMaxIter(10)
  .setRegParam(0.3)

// Fit the model
val model = glr.fit(dataset)

// Print the coefficients and intercept for generalized linear regression model
println(s"Coefficients: ${model.coefficients}")
println(s"Intercept: ${model.intercept}")

// Summarize the model over the training set and print out some metrics
val summary = model.summary
println(s"Coefficient Standard Errors: ${summary.coefficientStandardErrors.mkString(",")}")
println(s"T Values: ${summary.tValues.mkString(",")}")
println(s"P Values: ${summary.pValues.mkString(",")}")
println(s"Dispersion: ${summary.dispersion}")
println(s"Null Deviance: ${summary.nullDeviance}")
println(s"Residual Degree Of Freedom Null: ${summary.residualDegreeOfFreedomNull}")
println(s"Deviance: ${summary.deviance}")
println(s"Residual Degree Of Freedom: ${summary.residualDegreeOfFreedom}")
println(s"AIC: ${summary.aic}")
println("Deviance Residuals: ")
summary.residuals().show()

 请参阅Scala的API文档的更多细节 

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/GeneralizedLinearRegressionExample.scala”中查找完整示例代码。

Decision tree regression（决策树回归）

决策树是一种流行的分类和回归方法。有关spark.ml实现的更多信息，请参见决策树部分。

Example

以下示例以LibSVM格式加载数据集，将其拆分为训练和测试集，在第一个数据集上训练，然后对所保留的测试集进行评估。我们使用特征变换器来对分类特征进行索引，将元数据添加到决策树算法可以识别的DataFrame中。

Scala

import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.evaluation.RegressionEvaluator
import org.apache.spark.ml.feature.VectorIndexer
import org.apache.spark.ml.regression.DecisionTreeRegressionModel
import org.apache.spark.ml.regression.DecisionTreeRegressor

// Load the data stored in LIBSVM format as a DataFrame.
val data = spark.read.format("libsvm").load("data/mllib/sample_libsvm_data.txt")

// Automatically identify categorical features, and index them.
// Here, we treat features with > 4 distinct values as continuous.
val featureIndexer = new VectorIndexer()
  .setInputCol("features")
  .setOutputCol("indexedFeatures")
  .setMaxCategories(4)
  .fit(data)

// Split the data into training and test sets (30% held out for testing).
val Array(trainingData, testData) = data.randomSplit(Array(0.7, 0.3))

// Train a DecisionTree model.
val dt = new DecisionTreeRegressor()
  .setLabelCol("label")
  .setFeaturesCol("indexedFeatures")

// Chain indexer and tree in a Pipeline.
val pipeline = new Pipeline()
  .setStages(Array(featureIndexer, dt))

// Train model. This also runs the indexer.
val model = pipeline.fit(trainingData)

// Make predictions.
val predictions = model.transform(testData)

// Select example rows to display.
predictions.select("prediction", "label", "features").show(5)

// Select (prediction, true label) and compute test error.
val evaluator = new RegressionEvaluator()
  .setLabelCol("label")
  .setPredictionCol("prediction")
  .setMetricName("rmse")
val rmse = evaluator.evaluate(predictions)
println("Root Mean Squared Error (RMSE) on test data = " + rmse)

val treeModel = model.stages(1).asInstanceOf[DecisionTreeRegressionModel]
println("Learned regression tree model:\n" + treeModel.toDebugString)

有关参数的更多详细信息，请参见Scala API文档 

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/DecisionTreeRegressionExample.scala”中查找完整示例代码。

Random forest regression（随机森林回归）

随机森林是一类受欢迎的分类和回归方法。有关spark.ml实现的更多信息，请参见随机林部分。

Example

以下示例以LibSVM格式加载数据集，将其拆分为训练和测试集，在第一个数据集上训练，然后对所保留的测试集进行评估。我们使用特征变换器对分类特征进行索引，将元数据添加到基于树的算法可以识别的DataFrame中。我们使用特征变换器对分类特征进行索引，将元数据添加到基于树的算法可以识别的DataFrame中。

Scala

import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.evaluation.RegressionEvaluator
import org.apache.spark.ml.feature.VectorIndexer
import org.apache.spark.ml.regression.{RandomForestRegressionModel, RandomForestRegressor}

// Load and parse the data file, converting it to a DataFrame.
val data = spark.read.format("libsvm").load("data/mllib/sample_libsvm_data.txt")

// Automatically identify categorical features, and index them.
// Set maxCategories so features with > 4 distinct values are treated as continuous.
val featureIndexer = new VectorIndexer()
  .setInputCol("features")
  .setOutputCol("indexedFeatures")
  .setMaxCategories(4)
  .fit(data)

// Split the data into training and test sets (30% held out for testing).
val Array(trainingData, testData) = data.randomSplit(Array(0.7, 0.3))

// Train a RandomForest model.
val rf = new RandomForestRegressor()
  .setLabelCol("label")
  .setFeaturesCol("indexedFeatures")

// Chain indexer and forest in a Pipeline.
val pipeline = new Pipeline()
  .setStages(Array(featureIndexer, rf))

// Train model. This also runs the indexer.
val model = pipeline.fit(trainingData)

// Make predictions.
val predictions = model.transform(testData)

// Select example rows to display.
predictions.select("prediction", "label", "features").show(5)

// Select (prediction, true label) and compute test error.
val evaluator = new RegressionEvaluator()
  .setLabelCol("label")
  .setPredictionCol("prediction")
  .setMetricName("rmse")
val rmse = evaluator.evaluate(predictions)
println("Root Mean Squared Error (RMSE) on test data = " + rmse)

val rfModel = model.stages(1).asInstanceOf[RandomForestRegressionModel]
println("Learned regression forest model:\n" + rfModel.toDebugString)

请参阅Scala的API文档的更多细节

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/RandomForestRegressorExample.scala”中查找完整示例代码。

Gradient-boosted tree regression（梯度增强树回归）

梯度增强树（GBT）是使用决策树组合的流行回归方法。有关spark.ml实现的更多信息，请参见GBT部分。

Example

注意：对于此示例数据集，GBTRegressor实际上只需要1次迭代，但这一般不会是真的。

Scala

import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.evaluation.RegressionEvaluator
import org.apache.spark.ml.feature.VectorIndexer
import org.apache.spark.ml.regression.{GBTRegressionModel, GBTRegressor}

// Load and parse the data file, converting it to a DataFrame.
val data = spark.read.format("libsvm").load("data/mllib/sample_libsvm_data.txt")

// Automatically identify categorical features, and index them.
// Set maxCategories so features with > 4 distinct values are treated as continuous.
val featureIndexer = new VectorIndexer()
  .setInputCol("features")
  .setOutputCol("indexedFeatures")
  .setMaxCategories(4)
  .fit(data)

// Split the data into training and test sets (30% held out for testing).
val Array(trainingData, testData) = data.randomSplit(Array(0.7, 0.3))

// Train a GBT model.
val gbt = new GBTRegressor()
  .setLabelCol("label")
  .setFeaturesCol("indexedFeatures")
  .setMaxIter(10)

// Chain indexer and GBT in a Pipeline.
val pipeline = new Pipeline()
  .setStages(Array(featureIndexer, gbt))

// Train model. This also runs the indexer.
val model = pipeline.fit(trainingData)

// Make predictions.
val predictions = model.transform(testData)

// Select example rows to display.
predictions.select("prediction", "label", "features").show(5)

// Select (prediction, true label) and compute test error.
val evaluator = new RegressionEvaluator()
  .setLabelCol("label")
  .setPredictionCol("prediction")
  .setMetricName("rmse")
val rmse = evaluator.evaluate(predictions)
println("Root Mean Squared Error (RMSE) on test data = " + rmse)

val gbtModel = model.stages(1).asInstanceOf[GBTRegressionModel]
println("Learned regression GBT model:\n" + gbtModel.toDebugString)

请参阅Scala的API文档的更多细节

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/GradientBoostedTreeRegressorExample.scala”中查找完整示例代码。

Survival regression（生存回归）

在spark.ml中，我们实现了加速失效时间（AFT）模型，该模型是用于截尾数据的参数生存回归模型。 它描述了生存时间对数的模型，因此通常将其称为生存分析的对数线性模型。 与为同一目的设计的比例危害模型不同，AFT模型更容易并行化，因为每个实例独立地有助于目标函数。

给定协变量x'的值，对于主体i = 1，...，n的随机寿命 ti，具有可能的右检查，AFT模型下的似然函数给出为：

其中 是事件发生的指示符，即未被审查。使用 ，对数似然函数假设为：

其中  和基线幸存者功能，和 和相应的密度函数。

最常用的AFT模型是基于Weibull分布的生存时间。寿命的威布尔分布对应于寿命日志的极值分布，函数为：

函数为：

具有威布尔寿命分布的AFT模型的对数似然函数为：

由于最小化与最大后验概率相等的负对数似然，我们用于优化的损失函数为。 β和log⁡σ的梯度函数分别为：

AFT模型可以表示为凸优化问题，即根据系数向量ββ和比例参数对数，找到凸函数的最小值的任务log⁡σ。实现的优化算法是L-BFGS。该实现与R的存活功能的结果相匹配

当在固定非零列的数据集上对AFTSurvivalRegressionModel进行拟合时，Spark MLlib将为常数非零列输出零系数。这种行为与R存活::幸存者不同。

Example

Scala

import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.ml.regression.AFTSurvivalRegression

val training = spark.createDataFrame(Seq(
  (1.218, 1.0, Vectors.dense(1.560, -0.605)),
  (2.949, 0.0, Vectors.dense(0.346, 2.158)),
  (3.627, 0.0, Vectors.dense(1.380, 0.231)),
  (0.273, 1.0, Vectors.dense(0.520, 1.151)),
  (4.199, 0.0, Vectors.dense(0.795, -0.226))
)).toDF("label", "censor", "features")
val quantileProbabilities = Array(0.3, 0.6)
val aft = new AFTSurvivalRegression()
  .setQuantileProbabilities(quantileProbabilities)
  .setQuantilesCol("quantiles")

val model = aft.fit(training)

// Print the coefficients, intercept and scale parameter for AFT survival regression
println(s"Coefficients: ${model.coefficients}")
println(s"Intercept: ${model.intercept}")
println(s"Scale: ${model.scale}")
model.transform(training).show(false)

请参阅Scala的API文档的更多细节

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/AFTSurvivalRegressionExample.scala”中查找完整示例代码。

Isotonic regression（保序回归）

保序回归属于回归算法族。 正则等式回归是一个问题，其中给出了一个有限的实数集Y = y1，y2，...，表示观察到的响应，X = x1，x2，...，xn 要拟合的未知响应值，找到最小化的函数

对于满足x1≤x2≤...≤xn的完整订单，其中wiwi是正权重。所得到的函数称为等渗回归，它是唯一的。它可以被视为订单限制下的最小二乘问题。基本等渗回归是最接近原始数据点的单调函数。

我们实施一个相邻违反算法的池，它使用一种方法来平行等渗回归。训练输入是一个DataFrame，它包含三列标签，特征和重量。另外，IsotonicRegression算法有一个可选参数，称为等渗偏差默认为true。这个参数指定等渗回归是等渗的（单调递增）还是反对（单调递减）。

训练返回一个可以用于预测已知和未知特征的标签的等渗回归模型。等渗回归的结果被视为分段线性函数。因此，预测规则是：

如果预测输入与训练特征完全匹配，则返回相关预测。 如果有多个具有相同特征的预测，则返回其中一个。 哪一个是未定义的（与java.util.Arrays.binarySearch相同）。

如果预测输入低于或高于所有训练特征，则返回具有最低或最高特征的预测。 如果存在具有相同特征的多个预测，则分别返回最低或最高。

如果预测输入落在两个训练特征之间，则预测被视为分段线性函数，并且根据两个最接近的特征的预测来计算内插值。 如果有多个具有相同特征的值，则使用与前一点相同的规则。

Examples

Scala

import org.apache.spark.ml.regression.IsotonicRegression

// Loads data.
val dataset = spark.read.format("libsvm")
  .load("data/mllib/sample_isotonic_regression_libsvm_data.txt")

// Trains an isotonic regression model.
val ir = new IsotonicRegression()
val model = ir.fit(dataset)

println(s"Boundaries in increasing order: ${model.boundaries}\n")
println(s"Predictions associated with the boundaries: ${model.predictions}\n")

// Makes predictions.
model.transform(dataset).show()

有关API的详细信息，请参阅IsotonicRegression Scala文档 

查找Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/IsotonicRegressionExample.scala”的完整示例代码。

Linear methods（线性方法）

我们通过L1、L2正则化实现了逻辑回归和线性最小二乘法等常用的线性方法。关于实现和调优的详细信息请参考线性方法指南（RDD-based API ）。

我们也提供了Elastic net的DataFrame API。它发表在Zou et al, Regularization and variable selection via the elastic net，是L1、L2正则化的混合。在数学上定义为L1和L2正则项的凸组合：

我们实现流行的线性方法，如逻辑回归和L1或L2正则化的线性最小二乘法。 有关实现和调优的详细信息，请参考基于RDD的API的线性方法指南; 这个信息仍然是有关系的。

我们还包括一个用于elastic net的DataFrame API，Zou等人提出的L1和L2正则化的混合，通过elastic net进行正则化和可变选择。 在数学上，它被定义为L1和L2正则化项的凸组合：

通过正确设置α，elastic net包含L1和L2正则化作为特殊情况。 例如，如果使用elastic net参数α设置为1来训练线性回归模型，则相当于Lasso模型。 另一方面，如果α设定为0，训练模型减少到  岭回归模型。 我们通过elastic net正规化来实现线性回归和逻辑回归的管道API。

Decision Tree（决策树）

决策树及其合奏是分类和回归机器学习任务的流行方法。决策树被广泛使用，因为它们易于解释，处理分类特征，扩展到多类分类设置，不需要特征缩放，并且能够捕获非线性和特征交互。诸如随机森林和提升等树的集成算法是分类和回归任务的最佳表现者之一。

spark.ml实现支持二分类、多分类和回归的决策树，使用连续和分类特征。该实现按行分隔数据，允许分布式培训，数百万甚至数十亿个实例。

用户可以在MLlib决策树指南中找到有关决策树算法的更多信息。该API和原始MLlib决策树API之间的主要区别是：

支持ML管道

决策树的分离与回归分离

使用DataFrame元数据来区分连续和分类特征

决策树的管道API提供比原始API更多的功能。特别地，对于分类，用户可以得到每个类的预测概率（a.k.a.类条件概率）;为了回归，用户可以获得预测的偏差样本方差。

树的集成方法【Random Forests （随机森林）和  Gradient-Boosted Trees（渐变树）】将在下面的 树的集成方法部分 中进行描述。

Inputs and Outputs（输入与输出）

我们在这里列出输入和输出（预测）列类型。所有输出列都是可选的;要排除输出列，将其对应的Param设置为空字符串。

Input Columns（输入列）

参数名称 | 类型 | 默认值 | 描述
labelCol | Double | "label" | label预测
featuresCol | Vector | "features" | 特征向量

Output Columns（输出列）

参数名称 | 类型 | 默认值 | 描述 | 笔记
predictionCol | Double | "prediction" | 预测的 label |
rawPredictionCol | Vector | "rawPrediction" | 向量的长度＃类，在树节点处的训练实例标签的计数进行预测 | 仅限分类
probabilityCol | Vector | "probability" | 向量的长度＃类 等于rawPrediction归一化为多项分布 | 仅限分类
varianceCol | Double |  | 预测的偏差样本方差 | 仅限回归

Tree Ensambles（树集合）

DataFrame API 支持两种主要的数集合算法：Random Forests(随机森林)和(Gradient-Boosted Trees ,GBTs)(迭代树)。两者都用spark.ml决策树作为自己的基础模型。

用户可以在机器学习库-集合指南(mllib-ensembles guide)找到更多的集合算法。在这里，我们演示用于 Ensambles(集合) 的 DataFrame API 。

该 API 和原始MLlib集合API(original MLlib ensembles API)的差异如下：

支持 DataFrame 和机器学习管道( ML pipeline )。

分类(classification )与回归(regression)的分离。

使用DataFrame元数据( DataFrame metadata )来分辨特征( features )是持续的(continus)还是分类的(categorical)。

随机森林有更多功能：: 评估特征( feature )重要性和分类( classification )中各类的预测概率(即条件概率)。

Random Forests（随机森林）

随机森林是 决策树的集合. 随机森林结合多多个决策树来避免 过拟合(overfitting) 带来的风险。 spark机器学习(spark.ml) 的实现支持持续的(continus)还是分类的(categorical)的特征，支持二元分类和多元分类的随机森林及回归。

更多关于算法本身的资料，可以查看链接 spark.mllib documentation on random forests.

Inputs and Outputs（输入与输出）

我们在这里列举了输入和输出(预测)列类型。所有的输出列都是可选的；要排除输出列，就把对应参数设为空字符串。

Input Columns（输入列）

参数名称 | 类型 | 默认值 | 描述
labelCol | Double | "label" | 要预测的标签(Label to predict)
featuresCol | Vector | "features" | 特征向量(Feature vector)

Output Columns（Predictions）输出列（预测）

参数名称 | 类型 | 默认值 | 描述 | 注意
predictionCol | Double | "prediction" | 预测后的标签(Predicted label) |
rawPredictionCol | Vector | "rawPrediction" | 长度向量，类及用于预测的树节点上的训练实例标签数量。 | 只用于分类
probabilityCol | Vector | "probability" | 长度向量，与rawPrediction 相等的类被正则化为多元分布。 | 只用于分类

Gradient-Boosted Trees（GBTs） 

Gradient-Boosted Trees (GBTs) 是 决策树的集合. GBTs 迭代决策树以最小化 loss function(损失函数) 。spark机器学习库 的实现使用 持续的(continus) 和 分类的(categorical) 的特征来支持 GBTs ，以用于 二元分类(binary classification) 和 回归(regression) 。 

更多关于算法本身的资料，可以查看链接 spark.mllib documentation on GBTs.

Inputs and Outputs（输入和输出）

我们在这里列举了输入和输出(预测)列类型。所有的输出列都是可选的；要排除输出列，就把对应参数设为空字符串。

Input Columns（输入列）

参数名称 | 类型(s) | 默认值 | 描述
labelCol | Double | "label" | 要预测的标签(Label to predict)
featuresCol | Vector | "features" | 特征向量(Feature vector)

注意：GBTClassifier目前只支持 二元标签( binary labels.) 

Output Columns（Predictions）输出列（预测）

参数名称 | 类型(s) | 默认值 | 描述 | 注意点
predictionCol | Double | "prediction" | Predicted label |

在以后，GBTClassifier将会支持 rawPrediction 和 probability 的输出列，跟 RandomForestClassifier 一样。

Clustering（聚类）

本页介绍MLlib中的聚类算法。在基于RDD的API中聚类的指南还具有关于这些算法的相关信息。

目录

原文链接 : http://spark.apache.org/docs/2.1.0/ml-clustering.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=9406765

贡献者 : yangjifei 片刻 ApacheCN Apache中文网

k - means 

k - means 是其中一个最常用的聚类算法,点到一个集群数据 预定义的集群。 MLlib实现包括并行 变体的 k - means + + 方法 被称为 kmeans | | 。

KMeans 被实现为一个估计量并生成一个 KMeansModel 基本模型。

输入列 

参数名称 | 类型(s) | 默认的 | 描述
featuresCol | Vector | "features" | Feature vector

输出列 

参数名称 | 类型(s) | 默认的 | 描述
predictionCol | Int | "prediction" | Predicted cluster center

例子 (scala)

Scala

import org.apache.spark.ml.clustering.KMeans

// 加载数据
val dataset = spark.read.format("libsvm").load("data/mllib/sample_kmeans_data.txt")

// 训练k-means模型
val kmeans = new KMeans().setK(2).setSeed(1L)
val model = kmeans.fit(dataset)

// 评估聚类通过计算在平方误差的总和。
val WSSSE = model.computeCost(dataset)
println(s"Within Set Sum of Squared Errors = $WSSSE")

// 显示结果
println("Cluster Centers: ")
model.clusterCenters.foreach(println)

更多的细节参考Scala API文档

在Spark repo中可以找到"examples/src/main/scala/org/apache/spark/examples/ml/KMeansExample.scala"完整的例子代码。

Latent Dirichlet allocation (LDA)

LDA实现为一个支持EMLDAOptimizer和OnlineLDAOptimizer的估计器，并生成一个LDAModel作为基本模型。 如果需要，专家用户可以将EMLDAOptimizer生成的LDAModel转换为DistributedLDAModel。

Scala

import org.apache.spark.ml.clustering.LDA

// 加载数据
val dataset = spark.read.format("libsvm")
  .load("data/mllib/sample_lda_libsvm_data.txt")

// 训练LDA模型
val lda = new LDA().setK(10).setMaxIter(10)
val model = lda.fit(dataset)

val ll = model.logLikelihood(dataset)
val lp = model.logPerplexity(dataset)
println(s"The lower bound on the log likelihood of the entire corpus: $ll")
println(s"The upper bound bound on perplexity: $lp")

// 描述topics
val topics = model.describeTopics(3)
println("The topics described by their top-weighted terms:")
topics.show(false)

// 显示结果
val transformed = model.transform(dataset)
transformed.show(false)

有关更多详细信息，请参阅Scala API文档

在Spark repo中可以找到"examples/src/main/scala/org/apache/spark/examples/ml/LDAExample.scala"的完整代码。

角平分线k - means 

角平分线k - means是一种分层聚类使用一个 分裂的(或“自上而下”)的方法:所有观测开始在一个集群,将递归地执行 沿着层次结构。

平分k - means通常可以比常规的k - means要快得多,但是它通常会产生不同的集群。

BisectingKMeans 被实现为一个 估计量 并生成一个 BisectingKMeansModel 基本模型。

例子 

Scala

import org.apache.spark.ml.clustering.BisectingKMeans
// 加载数据
val dataset = spark.read.format("libsvm").load("data/mllib/sample_kmeans_data.txt")

// 训练bisecting k-means模型
val bkm = new BisectingKMeans().setK(2).setSeed(1)
val model = bkm.fit(dataset)

// 评估聚类。
val cost = model.computeCost(dataset)
println(s"Within Set Sum of Squared Errors = $cost")

// 显示结果。
println("Cluster Centers: ")
val centers = model.clusterCenters
centers.foreach(println)

更多细节请参考Scala API 文档

可以在Spark repo中找到 "examples/src/main/scala/org/apache/spark/examples/ml/BisectingKMeansExample.scala"的完整代码。

高斯混合模型(GMM) 

高斯混合模型表示复合分布，其中点从k个高斯子分布中的一个绘出，每个具有其自身的概率。 spark.ml实现使用期望最大化算法来给出给定一组样本的最大似然模型。

GaussianMixture作为估计器实现，并生成GaussianMixtureModel 作为基本模型。

输入列 

参数名称 | 类型(s) | 默认的 | 描述
featuresCol | Vector | "features" | Feature vector

输出列 

参数名称 | 类型(s) | 默认的 | 描述
predictionCol | Int | "prediction" | 预测集群中心
probabilityCol | Vector | "probability" | 每个集群的概率

例子 

Scala

import org.apache.spark.ml.clustering.GaussianMixture

// 加载数据
val dataset = spark.read.format("libsvm").load("data/mllib/sample_kmeans_data.txt")

// 训练Gaussian Mixture模型
val gmm = new GaussianMixture()
  .setK(2)
val model = gmm.fit(dataset)

// 输出混合模型参数模型
for (i <- 0 until model.getK) {
  println(s"Gaussian $i:\nweight=${model.weights(i)}\n" +
      s"mu=${model.gaussians(i).mean}\nsigma=\n${model.gaussians(i).cov}\n")
}

更多细节请参考Scala API 文档

在Spark repo中可以找到"examples/src/main/scala/org/apache/spark/examples/ml/GaussianMixtureExample.scala" 的完整代码。

Collaborative Filtering（协同过滤）

原文链接 : http://spark.apache.org/docs/2.1.0/ml-collaborative-filtering.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=9406772

贡献者 : kagura 片刻 ApacheCN Apache中文网

Collaborative filtering（协同过滤）

协作过滤通常用于推荐系统。这些技术旨在填补用户-项目关联矩阵中丢失的条目。spark.ml 目前支持基于模型的协同过滤，其中用户和项目通过一小组潜在因素来描述，可用于预测缺失的条目。spark.ml 使用 ALS（交替最小二乘法）算法来学习这些潜在因素。spark.ml 中的实现具有以下参数：

numBlocks 是用户和项目将被分区以便并行化计算的块数（默认值为10）。

rank 是模型中潜在因素的数量（默认为10）。

maxIter 是要运行的最大迭代次数（默认为10）。

implicitPrefs 指定是使用显式反馈ALS的版本还是用适用于隐式反馈数据集的版本（默认值为 false，这意味着使用显式反馈）。

alpha 是适用于ALS的隐式反馈版本的参数，用于控制偏好观察值的基线置信度（默认为1.0）。

nonnegative 指定是否对最小二乘使用非负约束（默认为 false ）。

注意：用于 ALS 的基于 DataFrame（数据框）的 API 目前仅支持整数的用户和项目id。 用户和项目id的列支持其他数字类型，但是列中id的取值必须在整数值范围内。

Explicit vs. implicit feedback（显式与隐式反馈）

基于矩阵分解的协同过滤的标准方法是将用户-项目关联矩阵中的元素视为用户对项目的显式偏好，例如用户对电影的评分。在现实世界中的许多用例中，通常只能接触到隐式的反馈（例如浏览，点击，购买，喜欢，分享等）。在 spark.ml 中使用基于隐式反馈数据集的协同过滤的方法来处理这些数据。实际上，这种方法不是直接对数据矩阵进行建模，而是将数据视为代表用户行为意愿强度的数字（例如点击的次数或某人累积观看电影的时间）。然后，这些数字与观察到的用户偏好的置信水平相关，而不是给予项目的明确评级。 然后，该模型尝试找到可用于预测用户对项目的预期偏好的潜在因素。

Scaling of the regularization parameter（正则化参数的换算）

我们通过用户在更新用户因素中产生的评分，或产品在更新产品因素中收到的评分来求解每个最小二乘问题的规则化参数 regParam 。 这种方法被命名为“ ALS-WR ”（加权正则化交替最小二乘法），并在论文“ Large-Scale Parallel Collaborative Filtering for the Netflix Prize ”中进行了讨论。 它使 regParam 对数据集的规模依赖较少，因此我们可以将从采样子集学到的最佳参数应用于完整数据集，并期望能有相似的表现。

Examples（示例）

在以下示例中，我们从 MovieLens 数据集加载评分数据，每行由用户，电影，评分和时间戳组成。 然后，我们训练一个 ALS 模型，默认情况下假定评分是显式的（ implicitPrefs 是 false ）。 我们通过测量评级预测的 root-mean-square error（均方根误差）来评估推荐模型。

有关API的更多详细信息，请参阅 ALS Scala 文档。

Scala

import org.apache.spark.ml.evaluation.RegressionEvaluator
import org.apache.spark.ml.recommendation.ALS

case class Rating(userId: Int, movieId: Int, rating: Float, timestamp: Long)
def parseRating(str: String): Rating = {
  val fields = str.split("::")
  assert(fields.size == 4)
  Rating(fields(0).toInt, fields(1).toInt, fields(2).toFloat, fields(3).toLong)
}

val ratings = spark.read.textFile("data/mllib/als/sample_movielens_ratings.txt")
  .map(parseRating)
  .toDF()
val Array(training, test) = ratings.randomSplit(Array(0.8, 0.2))

// Build the recommendation model using ALS on the training data
val als = new ALS()
  .setMaxIter(5)
  .setRegParam(0.01)
  .setUserCol("userId")
  .setItemCol("movieId")
  .setRatingCol("rating")
val model = als.fit(training)

// Evaluate the model by computing the RMSE on the test data
val predictions = model.transform(test)

val evaluator = new RegressionEvaluator()
  .setMetricName("rmse")
  .setLabelCol("rating")
  .setPredictionCol("prediction")
val rmse = evaluator.evaluate(predictions)
println(s"Root-mean-square error = $rmse")

可以在 Spark repo 中的“examples/src/main/scala/org/apache/spark/examples/ml/ALSExample.scala”中查找完整的示例代码。
如果评分矩阵是来自于另一个信息来源（即从其他信号推断出来），您可以将 implicitPrefs 设置为 true 以获得更好的结果：

val als = new ALS()
  .setMaxIter(5)
  .setRegParam(0.01)
  .setImplicitPrefs(true)
  .setUserCol("userId")
  .setItemCol("movieId")
  .setRatingCol("rating")

ML Tuning: model selection and hyperparameter tuning（ML调优：模型选择和超参数调整）

本节介绍如何使用MLlib的工具来调整ML算法和管道。

内置的交叉-验证和其他工具允许用户优化算法和流水线中的超参数。

目录

原文链接 : http://spark.apache.org/docs/2.1.0/ml-tuning.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=10027400

贡献者 : 片刻 ApacheCN Apache中文网

模型选择（a.k.a.超参数调整）

ML中的一个重要任务是模型选择，或使用数据找到给定任务的最佳模型或参数。这也叫调优。可以针对个体估算器（如Logistic回归）或包括多个算法，特征化和其他步骤的整个管道完成调整。用户可以一次调整整个流水线，而不是单独调整管道中的每个元素。

MLlib支持使用 CrossValidator和TrainValidationSplit等工具进行模型选择。这些工具需要以下项目：

Estimator（估算器）：算法或管道调整

ParamMaps集：可供选择的参数，有时称为“参数网格”进行搜索

Evaluator（评估者）：衡量拟合模型对延伸测试数据有多好的度量

在高层次上，这些模型选择工具的工作如下：

他们将输入数据分成单独的培训和测试数据集。

对于每个（训练，测试）对，他们遍历一组ParamMaps：

对于每个ParamMap，它们使用这些参数适合Estimator，获得拟合的Model，并使用Evaluator评估Model的性能。

他们选择由最佳性能参数组合生成的模型。

评估者可以是回归问题的RegressionEvaluator，二进制数据的BinaryClassificationEvaluator或多类问题的MulticlassClassificationEvaluator。用于选择最佳ParamMap的默认度量可以被这些评估器中的每一个的setMetricName方法覆盖。

为了帮助构建参数网格，用户可以使用ParamGridBuilder实用程序。

交叉-验证

CrossValidator首先将数据集分成一组折叠，这些折叠用作单独的训练和测试数据集。 例如，k = 3倍，CrossValidator将生成3个（训练，测试）数据集对，每个数据集使用2/3的数据进行训练，1/3进行测试。 为了评估一个特定的ParamMap，CrossValidator通过在3个不同的（训练，测试）数据集对上拟合Estimator来计算3个模型的平均评估度量。

在确定最佳ParamMap之后，CrossValidator最终使用最好的ParamMap和整个数据集重新拟合Estimator。

示例：通过交叉-验证进行模型选择

以下示例演示如何使用CrossValidator从参数网格中进行选择。

请注意，通过参数网格的交叉验证是昂贵的。 例如，在下面的示例中，参数网格具有3个值，用于hashingTF.numFeatures，2个值用于lr.regParam，CrossValidator使用2个折叠。 这被乘以 (3×2)×2 = 12个不同的模型被训练。 在现实的设置中，尝试更多的参数并使用更多的折叠（k = 3和k = 10是常见的）是常见的。 换句话说，使用CrossValidator可能是非常昂贵的。 然而，它也是一种成熟的方法，用于选择比启发式手动调谐更具统计学意义的参数。

有关API的详细信息，请参阅[`CrossValidator` Scala docs](api/scala/index.html#org.apache.spark.ml.tuning.CrossValidator)。

Scala

import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.classification.LogisticRegression
import org.apache.spark.ml.evaluation.BinaryClassificationEvaluator
import org.apache.spark.ml.feature.{HashingTF, Tokenizer}
import org.apache.spark.ml.linalg.Vector
import org.apache.spark.ml.tuning.{CrossValidator, ParamGridBuilder}
import org.apache.spark.sql.Row

// Prepare training data from a list of (id, text, label) tuples.
val training = spark.createDataFrame(Seq(
  (0L, "a b c d e spark", 1.0),
  (1L, "b d", 0.0),
  (2L, "spark f g h", 1.0),
  (3L, "hadoop mapreduce", 0.0),
  (4L, "b spark who", 1.0),
  (5L, "g d a y", 0.0),
  (6L, "spark fly", 1.0),
  (7L, "was mapreduce", 0.0),
  (8L, "e spark program", 1.0),
  (9L, "a e c l", 0.0),
  (10L, "spark compile", 1.0),
  (11L, "hadoop software", 0.0)
)).toDF("id", "text", "label")

// Configure an ML pipeline, which consists of three stages: tokenizer, hashingTF, and lr.
val tokenizer = new Tokenizer()
  .setInputCol("text")
  .setOutputCol("words")
val hashingTF = new HashingTF()
  .setInputCol(tokenizer.getOutputCol)
  .setOutputCol("features")
val lr = new LogisticRegression()
  .setMaxIter(10)
val pipeline = new Pipeline()
  .setStages(Array(tokenizer, hashingTF, lr))

// We use a ParamGridBuilder to construct a grid of parameters to search over.
// With 3 values for hashingTF.numFeatures and 2 values for lr.regParam,
// this grid will have 3 x 2 = 6 parameter settings for CrossValidator to choose from.
val paramGrid = new ParamGridBuilder()
  .addGrid(hashingTF.numFeatures, Array(10, 100, 1000))
  .addGrid(lr.regParam, Array(0.1, 0.01))
  .build()

// We now treat the Pipeline as an Estimator, wrapping it in a CrossValidator instance.
// This will allow us to jointly choose parameters for all Pipeline stages.
// A CrossValidator requires an Estimator, a set of Estimator ParamMaps, and an Evaluator.
// Note that the evaluator here is a BinaryClassificationEvaluator and its default metric
// is areaUnderROC.
val cv = new CrossValidator()
  .setEstimator(pipeline)
  .setEvaluator(new BinaryClassificationEvaluator)
  .setEstimatorParamMaps(paramGrid)
  .setNumFolds(2)  // Use 3+ in practice

// Run cross-validation, and choose the best set of parameters.
val cvModel = cv.fit(training)

// Prepare test documents, which are unlabeled (id, text) tuples.
val test = spark.createDataFrame(Seq(
  (4L, "spark i j k"),
  (5L, "l m n"),
  (6L, "mapreduce spark"),
  (7L, "apache hadoop")
)).toDF("id", "text")

// Make predictions on test documents. cvModel uses the best model found (lrModel).
cvModel.transform(test)
  .select("id", "text", "probability", "prediction")
  .collect()
  .foreach { case Row(id: Long, text: String, prob: Vector, prediction: Double) =>
    println(s"($id, $text) --> prob=$prob, prediction=$prediction")
  }

 查找Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/ModelSelectionViaCrossValidationExample.scala”的完整示例代码。

火车-验证 拆分

除了CrossValidator Spark，还提供了用于超参数调整的TrainValidationSplit。 TrainValidationSplit仅对参数的每个组合进行一次评估，而在CrossValidator的情况下，则不是k次。 因此，它较便宜，但在训练数据集不够大时不会产生可靠的结果。

与CrossValidator不同，TrainValidationSplit创建一个（训练，测试）数据集对。 它使用trainRatio参数将数据集分成这两个部分。 例如，trainRatio = 0.75，TrainValidationSplit将生成训练和测试数据集对，其中75％的数据用于培训，25％用于验证。

像CrossValidator一样，TrainValidationSplit最终适合使用最好的ParamMap和整个数据集的Estimator。

示例：通过列车验证拆分模型选择

有关API的详细信息，请参阅TrainValidationSplit Scala文档。

Scala

import org.apache.spark.ml.evaluation.RegressionEvaluator
import org.apache.spark.ml.regression.LinearRegression
import org.apache.spark.ml.tuning.{ParamGridBuilder, TrainValidationSplit}

// Prepare training and test data.
val data = spark.read.format("libsvm").load("data/mllib/sample_linear_regression_data.txt")
val Array(training, test) = data.randomSplit(Array(0.9, 0.1), seed = 12345)

val lr = new LinearRegression()
    .setMaxIter(10)

// We use a ParamGridBuilder to construct a grid of parameters to search over.
// TrainValidationSplit will try all combinations of values and determine best model using
// the evaluator.
val paramGrid = new ParamGridBuilder()
  .addGrid(lr.regParam, Array(0.1, 0.01))
  .addGrid(lr.fitIntercept)
  .addGrid(lr.elasticNetParam, Array(0.0, 0.5, 1.0))
  .build()

// In this case the estimator is simply the linear regression.
// A TrainValidationSplit requires an Estimator, a set of Estimator ParamMaps, and an Evaluator.
val trainValidationSplit = new TrainValidationSplit()
  .setEstimator(lr)
  .setEvaluator(new RegressionEvaluator)
  .setEstimatorParamMaps(paramGrid)
  // 80% of the data will be used for training and the remaining 20% for validation.
  .setTrainRatio(0.8)

// Run train validation split, and choose the best set of parameters.
val model = trainValidationSplit.fit(training)

// Make predictions on test data. model is the model with combination of parameters
// that performed best.
model.transform(test)
  .select("features", "label", "prediction")
  .show()

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/ml/ModelSelectionViaTrainValidationSplitExample.scala”中查找完整示例代码。

Advanced topics（高级主题）

原文链接 : http://spark.apache.org/docs/2.1.0/ml-advanced.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=9406565

贡献者 : channingzong 片刻 ApacheCN Apache中文网

Optimization of linear methods (developer)（线性方法的优化（开发者））

Limited-memory BFGS (L-BFGS)（有限内存BFGS算法）

L-BFGS 是拟牛顿法（Quasi-Newton Methods）家族中的一种优化算法，它用来解决  形式的最优化问题。L-BFGS 方法局部地将目标函数近似为一个二次函数，而不去评估目标函数的二阶偏导来构造海森矩阵（Hession matrix）。海森矩阵是通过上一次迭代的梯度评估近似得来的，所以它不会像用牛顿法显式地计算海森矩阵那样有垂直扩展性的问题（训练特征数量）。在效果上，L-BFGS 相比于其他一阶优化算法经常能够获得更快的收敛速度。

Orthant-Wise Limited-memory Quasi-Newton（OWL-QN）是 L-BFGS 方法的一种扩展，它能够有效地处理 L1 范式和 elastic net（弹性网络）正则项的问题。

L-BFGS 被用来作为 LinearRegression（线性回归模型）、LogisticRegression（逻辑回归模型）、AFTSurvivalRegression（加速失效时间生存回归模型）和 MultilayerPerceptronClassifier（多层感知器分类模型）的一种解决方法。

MLlib 中的 L-BFGS 方法调用的是 breeze 中相应的方法来实现的。

Normal equation solver for weighted least squares（加权最小二乘法的矩阵方程求解）

MLlib 是通过代码 WeightedLeastSquares 来实现 normal equation 从而解决加权最小二乘法问题的。

给出 n 个加权观察值：

 为第 i 个观察值的权值

 为第 i 个观察值的特征向量

 为第 i 个观察值的标签

每一个观察值的特征数量为 m。我们使用以下加权最小二乘法方程：

这里是正则化参数，是 elastic-net 混合参数，是标签的总体标准差， 是第 j 个特征列的总体标准差。

该目标方程要求只能经过数据一次来收集需要的统计值去求解它。对于一个的数据矩阵，这些统计值只需要的空间存储，所以当 m（特征数量）相对较小时，他们可以被存放到单一机器节点上。于是我们解决 normal equation 就可以使用单一节点和本地方法如乔里斯基分解（Cholesky factorization）或者迭代优化程序来处理。

Spark MLlib 目前支持两种类型的 normal equation 解决方法：乔里斯基分解法和拟牛顿法（L-BFGS/OWL-QN）。乔里斯基分解法依赖于一个正定协方差矩阵（如数据矩阵的列一定是线性独立的）并且如果违背这一条件将会失败。拟牛顿法在协方差矩阵为非正定时依然可以提供一个合理的解，所以 normal equation 的解在这种情况下也可以自动回落到用拟牛顿法解决。这种回落在 LinearRegression 和 GeneralizedLinearRegression 评估器上是总被启用的。

WeightedLeastSquares 支持 L1、L2 范式以及弹性网络的正则化并且提供选项以启动或禁用正则化和标准化。在 L1 正则化不被应用的场景中（如）存在一种解析方法，乔里斯基分解法或拟牛顿法可能会被使用。当时没有解析方法存在，我们只能使用拟牛顿法来通过迭代找到系数。

为了使 normal equation 方法有效，WeightedLeastSquares 要求特征的数量要不多于4096个。对于更大型的场景，可以使用 L-BFGS 来替代。

Iteratively reweighted least squares (IRLS)（迭代重加权最小二乘法）

MLlib 通过代码 IterativelyReweightedLeastSquares 实现了迭代重加权最小二乘法（IRLS）。它可以被用来找到广义线性模型（GLM）的极大似然估计值，找到稳健回归模型的 M 估计值，以及解决其他优化问题。可以参考 Iteratively Reweighted Least Squares for Maximum Likelihood Estimation, and some Robust and Resistant Alternatives 获取更多信息。

它通过以下过程来迭代地解决特定的最优化问题：

在当前的解决方法上将目标线性化并且更新相应的权值。

通过 WeightedLeastSquares 解决加权最小二乘法（WLS）问题

重复以上步骤直到收敛

正因为它涉及到在每一次迭代中使用 WeightedLeastSquares 来解决加权最小二乘法（WLS）问题，它也要求特征数量要不多于4096个。目前 IRLS 在 GeneralizedLinearRegression 中被用做默认的解法。

MLlib：基于RDD的API

原文链接 : http://spark.apache.org/docs/2.1.0/mllib-guide.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=9406009

贡献者 : 片刻 ApacheCN Apache中文网

本页介绍了基于RDD的API（spark.mllib软件包）的 机器学习库（MLlib）指南 的部分。请参阅基于DataFrame的API（spark.ml 软件包）的 机器学习库（MLlib）指南 ，该软件包现在是MLlib的主要API。

继续阅读

Data Types - RDD-based API（数据类型）

Basic Statistics - RDD-based API（基本统计）

Classification and Regression - RDD-based API（分类和回归）

Linear Methods - RDD-based API（线性方法）

Naive Bayes - RDD-based API（朴素贝叶斯）

Decision Trees - RDD-based API（决策树）

Ensembles - RDD-based API（集成方法）

Regression - RDD-based API（回归）

Collaborative Filtering - RDD-based API（协同过滤）

Clustering - RDD-based API（聚类 - 基于RDD的API）

Dimensionality Reduction - RDD-based API（降维）

Feature Extraction and Transformation - RDD-based API（特征的提取和转换）

Frequent Pattern Mining - RDD-based API（频繁模式挖掘）

Evaluation metrics - RDD-based API（评估指标）

PMML model export - RDD-based API（PMML模型导出）

Optimization - RDD-based API（最优化）

Data Types - RDD-based API（数据类型）

原文链接 : http://spark.apache.org/docs/2.1.0/mllib-data-types.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=9406290

贡献者 :小茗同学 片刻 ApacheCN Apache中文网

MLlib支持存储在单个机器上的本地向量和矩阵，以及由一个或多个RDD支持的分布式矩阵。 局部向量和局部矩阵是用作公共接口的简单数据模型。 底层线性代数运算由Breeze提供。 在监督学习中使用的训练示例在MLlib中被称为“标记点”。

Local vector（局部向量）

局部向量具有整数类型和基于0的索引和双类型值，存储在单个机器上。  MLlib支持两种类型的局部向量：密集和稀疏。 密集向量由表示其条目值的双数组支持，而稀疏向量由两个并行数组支持：索引和值。 例如，向量（1.00.03.0）可以密集格式表示为1.00.03.0，或以稀疏格式表示为（3，02，1.03.0），其中3是 矢量的大小。

本地向量的基类是Vector，我们提供了两个实现：DenseVector 和 SparseVector。 我们建议使用 Vectors 中实现的工厂方法来创建本地向量。

有关API的详细信息，请参阅  Vector Scala docs  和 Vectors Scala docs 。

Scala

import org.apache.spark.mllib.linalg.{Vector, Vectors}

// Create a dense vector (1.0, 0.0, 3.0).
val dv: Vector = Vectors.dense(1.0, 0.0, 3.0)
// Create a sparse vector (1.0, 0.0, 3.0) by specifying its indices and values corresponding to nonzero entries.
val sv1: Vector = Vectors.sparse(3, Array(0, 2), Array(1.0, 3.0))
// Create a sparse vector (1.0, 0.0, 3.0) by specifying its nonzero entries.
val sv2: Vector = Vectors.sparse(3, Seq((0, 1.0), (2, 3.0)))

注意：Scala默认导入scala.collection.immutable.Vector，因此您必须明确导入org.apache.spark.mllib.linalg.Vector才能使用MLlib的Vector。

Labeled point（标示点）

标记点是与标签/响应相关联的局部矢量，密集或稀疏。 在MLlib中，标注点用于监督学习算法。 我们使用双重存储标签，所以我们可以在回归和分类中使用标记点。 对于二进制分类，标签应为0（负）或1（正）。 对于多类分类，标签应该是从零开始的类索引：0，1，2，....

标记点由事例类  LabeledPoint 表示。

有关 api 的详细信息, 请参阅 LabeledPoint Scala docs 。

Scala

import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint

// Create a labeled point with a positive label and a dense feature vector.
val pos = LabeledPoint(1.0, Vectors.dense(1.0, 0.0, 3.0))

// Create a labeled point with a negative label and a sparse feature vector.
val neg = LabeledPoint(0.0, Vectors.sparse(3, Array(0, 2), Array(1.0, 3.0)))

稀疏数据

在实践中, 有稀疏的训练数据是很常见的。MLlib 支持以 LIBSVM 格式存储的阅读培训示例, 这是  LIBSVM 和 LIBLINEAR 使用的默认格式。它是一种文本格式, 每个行都使用以下格式表示一个标记的稀疏特征向量:

label index1:value1 index2:value2 ...

其中指数是 one-based, 按升序排列。加载后, 功能索引将转换为从零开始。

Scala

import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.util.MLUtils
import org.apache.spark.rdd.RDD

val examples: RDD[LabeledPoint] = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")

MLUtils.loadLibSVMFile 读取以LIBSVM格式存储的训练示例。

有关API的详细信息，请参阅  MLUtils Scala docs

Local matrix（本地矩阵）

本地矩阵具有整数类型的行和列索引和双类型值，存储在单个机器上。 MLlib支持密集矩阵，其入口值以列主序列存储在单个双阵列中，稀疏矩阵的非零入口值以列主要顺序存储在压缩稀疏列（CSC）格式中。 例如，以下密集矩阵

存储在具有矩阵大小  (3, 2) 的一维数组  [1.0, 3.0, 5.0, 2.0, 4.0, 6.0]  中。

Scala

import org.apache.spark.mllib.linalg.{Matrix, Matrices}

// Create a dense matrix ((1.0, 2.0), (3.0, 4.0), (5.0, 6.0))
val dm: Matrix = Matrices.dense(3, 2, Array(1.0, 3.0, 5.0, 2.0, 4.0, 6.0))

// Create a sparse matrix ((9.0, 0.0), (0.0, 8.0), (0.0, 6.0))
val sm: Matrix = Matrices.sparse(3, 2, Array(0, 1, 3), Array(0, 2, 1), Array(9, 6, 8))

局部矩阵的基类是Matrix，我们提供了两个实现： DenseMatrix 和 SparseMatrix。 我们建议使用 Matrices 中实现的工厂方法来创建本地矩阵。 记住，MLlib中的局部矩阵以列主要顺序存储。

有关API的详细信息，请参阅 Matrix Scala docs 和 Matrices Scala docs 。

Distributed matrix（分布矩阵）

分布式矩阵具有长类型的行和列索引和双类型值，分布式存储在一个或多个RDD中。选择正确的格式来存储大型和分布式矩阵是非常重要的。将分布式矩阵转换为不同的格式可能需要全局shuffle，这是相当昂贵的。到目前为止已经实现了四种类型的分布式矩阵。

基本类型称为RowMatrix。 RowMatrix是没有有意义的行索引的行向分布式矩阵，例如特征向量的集合。它由其行的RDD支持，其中每行是局部向量。我们假设RowMatrix的列数不是很大，因此单个本地向量可以合理地传递给驱动程序，也可以使用单个节点进行存储/操作。 IndexedRowMatrix与RowMatrix类似，但具有行索引，可用于标识行和执行连接。 CoordinateMatrix是以坐标 list(COO) 格式存储的分布式矩阵，由其条目的RDD支持。 BlockMatrix是由MatrixBlock的RDD支持的分布式矩阵，它是（Int，Int，Matrix）的元组。

Note

分布式矩阵的底层RDD必须是确定性的，因为我们缓存矩阵大小。一般来说，使用非确定性RDD可能会导致错误。

RowMatrix

RowMatrix是一个面向行的分布式矩阵，没有有意义的行索引，由其行的RDD支持，其中每一行都是一个局部向量。 由于每行由局部向量表示，所以列数受到整数范围的限制，但实际应该要小得多。

Scala

import org.apache.spark.mllib.linalg.Vector
import org.apache.spark.mllib.linalg.distributed.RowMatrix

val rows: RDD[Vector] = ... // an RDD of local vectors
// Create a RowMatrix from an RDD[Vector].
val mat: RowMatrix = new RowMatrix(rows)

// Get its size.
val m = mat.numRows()
val n = mat.numCols()

// QR decomposition
val qrResult = mat.tallSkinnyQR(true)

可以从RDD [Vector]实例创建RowMatrix。 然后我们可以计算其列汇总统计和分解。 QR分解形式为A = QR，其中Q是正交矩阵，R是上三角矩阵。 对于奇异值分解（SVD）和主分量分析（PCA），请参考降维。

有关API的详细信息，请参阅RowMatrix Scala文档。

IndexedRowMatrix

IndexedRowMatrix 与RowMatrix类似，但具有有意义的行索引。它由索引行的RDD支持，因此每行都由其索引（长类型）和局部向量表示。

Scala

import org.apache.spark.mllib.linalg.distributed.{IndexedRow, IndexedRowMatrix, RowMatrix}

val rows: RDD[IndexedRow] = ... // an RDD of indexed rows
// Create an IndexedRowMatrix from an RDD[IndexedRow].
val mat: IndexedRowMatrix = new IndexedRowMatrix(rows)

// Get its size.
val m = mat.numRows()
val n = mat.numCols()

// Drop its row indices.
val rowMat: RowMatrix = mat.toRowMatrix()

IndexedRowMatrix可以从RDD [IndexedRow]实例创建，其中 IndexedRow 是一个包装器（Long，Vector）。 IndexedRowMatrix可以通过删除其行索引来转换为RowMatrix。

有关API的详细信息，请参阅IndexedRowMatrix Scala文档。

CoordinateMatrix

CoordinateMatrix 是由其条目的RDD支持的分布式矩阵。 每个条目是 (i: Long, j: Long, value: Double) 的元组，其中i是行索引，j是列索引，value是条目值。 只有当矩阵的两个维度都很大并且矩阵非常稀疏时才应使用Coordinate矩阵。

Scala

import org.apache.spark.mllib.linalg.distributed.{CoordinateMatrix, MatrixEntry}

val entries: RDD[MatrixEntry] = ... // an RDD of matrix entries
// Create a CoordinateMatrix from an RDD[MatrixEntry].
val mat: CoordinateMatrix = new CoordinateMatrix(entries)

// Get its size.
val m = mat.numRows()
val n = mat.numCols()

// Convert it to an IndexRowMatrix whose rows are sparse vectors.
val indexedRowMatrix = mat.toIndexedRowMatrix()

可以从RDD [MatrixEntry]实例创建一个CoordinateMatrix，其中 MatrixEntry 是一个包装器 (Long, Long, Double) 。 可以通过调用toIndexedRowMatrix将CoordinateMatrix转换为具有稀疏行的IndexedRowMatrix。 目前不支持CoordinateMatrix的其他计算。

有关API的详细信息，请参阅CoordinateMatrix Scala文档。

BlockMatrix

BlockMatrix是由MatrixBlocks的RDD支持的分布式矩阵，其中MatrixBlock是  ((Int, Int), Matrix) 的元组，其中 (Int, Int)  是块的索引，Matrix是子矩阵， 在给定索引处的矩阵大小为rowsPerBlock x colsPerBlock。 BlockMatrix支持诸如添加和乘以另一个BlockMatrix的方法。 BlockMatrix还有一个帮助函数validate，可用于检查BlockMatrix是否正确设置。

Scala

import org.apache.spark.mllib.linalg.distributed.{BlockMatrix, CoordinateMatrix, MatrixEntry}

val entries: RDD[MatrixEntry] = ... // an RDD of (i, j, v) matrix entries
// Create a CoordinateMatrix from an RDD[MatrixEntry].
val coordMat: CoordinateMatrix = new CoordinateMatrix(entries)
// Transform the CoordinateMatrix to a BlockMatrix
val matA: BlockMatrix = coordMat.toBlockMatrix().cache()

// Validate whether the BlockMatrix is set up properly. Throws an Exception when it is not valid.
// Nothing happens if it is valid.
matA.validate()

// Calculate A^T A.
val ata = matA.transpose.multiply(matA)

通过调用toBlockMatrix，可以通过IndexedRowMatrix或CoordinateMatrix创建 BlockMatrix  。 toBlockMatrix默认创建大小为1024 x 1024的块。 用户可以通过toBlockMatrix（rowsPerBlock，colsPerBlock）提供值来更改块大小。

有关API的详细信息，请参阅BlockMatrix Scala文档。

Basic Statistics - RDD-based API（基本统计）

原文链接 : http://spark.apache.org/docs/2.1.0/mllib-statistics.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=9406599

贡献者 : Le-Mon 片刻 ApacheCN Apache中文网

Summary statistics/概要统计

我们提供对 RDD[Vector] 中的向量的统计,采用 Statistics 里的函数 colStats 

 colStats() 返回一个 MultivariateStatisticalSummary 的实例, 包含列的最大值、最小值、均值、方差和非零的数量以及总数量。

阅读  MultivariateStatisticalSummary Scala 文档查看 API 细节

import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.stat.{MultivariateStatisticalSummary, Statistics}

val observations = sc.parallelize(
  Seq(
    Vectors.dense(1.0, 10.0, 100.0),
    Vectors.dense(2.0, 20.0, 200.0),
    Vectors.dense(3.0, 30.0, 300.0)
  )
)

// 计算概要统计.
val summary: MultivariateStatisticalSummary = Statistics.colStats(observations)
println(summary.mean)  //  每一列的均值
println(summary.variance)  // 所有向量的方差
println(summary.numNonzeros)  // 每列中的非零数

更完整的代码请在 Spark 项目查看"examples/src/main/scala/org/apache/spark/examples/mllib/SummaryStatisticsExample.scala".

Correlations/相关性

 计算两个系列（series）数据之间的相关性的数据是在统计学一种常见的操作。在 spark.mllib 我们提供灵活的计算两两之间的相关性的方法。支持计算相关性的方法目前有 Pearson’s and Spearman’s (皮尔森和斯皮尔曼) 的相关性.

Statistics类提供了计算系列之间相关性的方法。 根据输入类型，两个RDD [Double]或RDD [Vector]，输出分别为Double或相关矩阵。

阅读 Statistics Scala 文档查看 API 细节

import org.apache.spark.mllib.linalg._
import org.apache.spark.mllib.stat.Statistics
import org.apache.spark.rdd.RDD

val seriesX: RDD[Double] = sc.parallelize(Array(1, 2, 3, 3, 5))  // a series
// 必须具有相同数量的分区和基数作为一个series
val seriesY: RDD[Double] = sc.parallelize(Array(11, 22, 33, 33, 555))

// 计算 Pearson's 相关性.输入"spearman" 调用 Spearman's 的计算方法.
// 默认使用 Pearson's 方法.
val correlation: Double = Statistics.corr(seriesX, seriesY, "pearson")
println(s"Correlation is: $correlation")

val data: RDD[Vector] = sc.parallelize(
  Seq(
    Vectors.dense(1.0, 10.0, 100.0),
    Vectors.dense(2.0, 20.0, 200.0),
    Vectors.dense(5.0, 33.0, 366.0))
)  // 注意每个向量为行而不是列
// 计算 Pearson's 相关性的矩阵.输入"spearman" 调用 Spearman's 的计算方法.
// 默认使用 Pearson's 方法.
val correlMatrix: Matrix = Statistics.corr(data, "pearson")
println(correlMatrix.toString)

更完整的代码请在 Spark 项目查看"examples/src/main/scala/org/apache/spark/examples/mllib/CorrelationsExample.scala".

Stratified sampling/分层采样

与spark.mllib中的其他统计功能不同，sampleByKey和sampleByKeyExact可以对键值对的RDD执行分层采样方法。 对于分层采样，键可以被认为是一个标签，该值作为一个特定属性。 例如，key 可以是男人或女人或文档ID，并且相应的 value 可以是人的年龄列表或文档中的单词列表。 sampleByKey方法将类似掷硬币方式来决定观察是否被采样，因此需要一次遍历数据，并提供期望的样本大小。 sampleByKeyExact需要比sampleByKey中使用的每层简单随机抽样花费更多的资源，但将提供99.99％置信度的确切抽样大小。 python当前不支持sampleByKeyExact。

sampleByKeyExact() 允许用户精确地采样⌈fk⋅nk⌉∀k∈K⌈fk⋅nk⌉∀k∈K项，其中fk fk是关键kk的期望分数，nk nk是关键kk的键值对的数量， 而KK是一组键。 无需更换的采样需要额外通过RDD以保证采样大小，而替换的采样需要两次额外的通过。

allows users to sample exactly ⌈f  k  ⋅n k  ⌉ ∀k ∈K   ⌈fk⋅nk⌉∀k∈K items, where f  k    fk is the desired fraction for key k   k, n k    nk is the number of key-value pairs for key k   k, and K   K is the set of keys. Sampling without replacement requires one additional pass over the RDD to guarantee sample size, whereas sampling with replacement requires two additional passes.

//  RDD[(K, V)]  任何键值对
val data = sc.parallelize(
  Seq((1, 'a'), (1, 'b'), (2, 'c'), (2, 'd'), (2, 'e'), (3, 'f')))

// 指明每个Key的权重
val fractions = Map(1 -> 0.1, 2 -> 0.6, 3 -> 0.3)

// 从每层获取一个近似的样本
val approxSample = data.sampleByKey(withReplacement = false, fractions = fractions)
// 从每层获取一个确切的样本
val exactSample = data.sampleByKeyExact(withReplacement = false, fractions = fractions)

更完整的代码请在 Spark 项目查看"examples/src/main/scala/org/apache/spark/examples/mllib/StratifiedSamplingExample.scala".

Hypothesis testing/假设检验

假设检验是统计学中强大的工具，用于确定结果是否具有统计学意义，无论该结果是否偶然发生。 spark.mllib目前支持Pearson的卡方（χ2χ2）测试，以获得拟合优度和独立性。 输入数据类型确定是否进行拟合优度或独立性测试。 拟合优度测试需要输入类型的Vector，而独立性测试需要一个 Matrix作为输入。

spark.mllib还支持输入类型 RDD[LabeledPoint] 通过卡方独立测试启用特征选择。

Statistics 提供运行Pearson卡方检验的方法。 以下示例演示如何运行和解释假设检验。

mport org.apache.spark.mllib.linalg._
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.stat.Statistics
import org.apache.spark.mllib.stat.test.ChiSqTestResult
import org.apache.spark.rdd.RDD

// 由事件频率组成的向量
val vec: Vector = Vectors.dense(0.1, 0.15, 0.2, 0.3, 0.25)

// 计算适合度。 如果没有提供第二个测试向量
// 作为参数，针对均匀分布运行测试。
val goodnessOfFitTestResult = Statistics.chiSqTest(vec)
// 测试包括p值，自由度，检验统计量，使用方法,和零假设
println(s"$goodnessOfFitTestResult\n")

// a contingency matrix. Create a dense matrix ((1.0, 2.0), (3.0, 4.0), (5.0, 6.0))
val mat: Matrix = Matrices.dense(3, 2, Array(1.0, 3.0, 5.0, 2.0, 4.0, 6.0))

// conduct Pearson's independence test on the input contingency matrix
// 对输入的矩阵进行Pearson独立性测试
val independenceTestResult = Statistics.chiSqTest(mat)
// summary of the test including the p-value, degrees of freedom
// 测试:包括p值，自由度
println(s"$independenceTestResult\n")

val obs: RDD[LabeledPoint] =
  sc.parallelize(
    Seq(
      LabeledPoint(1.0, Vectors.dense(1.0, 0.0, 3.0)),
      LabeledPoint(1.0, Vectors.dense(1.0, 2.0, 0.0)),
      LabeledPoint(-1.0, Vectors.dense(-1.0, 0.0, -0.5)
      )
    )
  ) // (feature, label) pairs.

// The contingency table is constructed from the raw (feature, label) pairs and used to conduct
// the independence test. Returns an array containing the ChiSquaredTestResult for every feature
// against the label.
///应急表由原始的（特征，标签）对构成，用于进行独立性测试。 返回一个包含针对标签的每个功能的ChiSquaredTestResult的数组。
val featureTestResults: Array[ChiSqTestResult] = Statistics.chiSqTest(obs)
featureTestResults.zipWithIndex.foreach { case (k, v) =>
  println("Column " + (v + 1).toString + ":")
  println(k)
}  // summary of the test

更完整的代码请在 Spark 项目查看"examples/src/main/scala/org/apache/spark/examples/mllib/HypothesisTestingExample.scala".

此外， spark.mllib 提供了对于概率分布相等的Kolmogorov-Smirnov（KS）测试的单样本双侧实现。 通过提供理论分布（目前仅为正态分布支持）及其参数的名称，或根据给定理论分布计算累积分布的函数，用户可以测试其假设，即样本服从该分布。 在用户根据正态分布（distName =“norm”）进行测试但不提供分发参数的情况下，测试将初始化为标准正态分布并记录适当的消息。

Statistics 提供了运行单样本，双侧Kolmogorov-Smirnov检验的方法。 以下示例演示如何运行和解释假设检验。

阅读 Statistics Scala 文档查看 API 细节

import org.apache.spark.mllib.stat.Statistics
import org.apache.spark.rdd.RDD

val data: RDD[Double] = sc.parallelize(Seq(0.1, 0.15, 0.2, 0.3, 0.25))  // an RDD of sample data

// run a KS test for the sample versus a standard normal distribution
val testResult = Statistics.kolmogorovSmirnovTest(data, "norm", 0, 1)
// summary of the test including the p-value, test statistic, and null hypothesis if our p-value
// indicates significance, we can reject the null hypothesis.
println(testResult)
println()

// perform a KS test using a cumulative distribution function of our making
val myCDF = Map(0.1 -> 0.2, 0.15 -> 0.6, 0.2 -> 0.05, 0.3 -> 0.05, 0.25 -> 0.1)
val testResult2 = Statistics.kolmogorovSmirnovTest(data, myCDF)
println(testResult2)

更完整的代码请在 Spark 项目查看 "examples/src/main/scala/org/apache/spark/examples/mllib/HypothesisTestingKolmogorovSmirnovTestExample.scala"

Streaming Significance Testing/流式测试

spark.mllib 提供了一些测试的在线实现，以支持A / B测试用例等。 这些测试可以在Spark Streaming DStream [（Boolean，Double）]上执行，其中每个元组的第一个元素指示控制组（false）或处理组（true），第二个元素是观察值。

流式测试显示支持以下参数：

peacePeriod - 从流中忽略的初始数据点数，用于减轻新奇效应.

windowSize - - 执行假设测试的过去批次数。 设置为0将使用所有之前的批次执行累积处理。

StreamingTest 提供流式假设检验.

val data = ssc.textFileStream(dataDir).map(line => line.split(",") match {
  case Array(label, value) => BinarySample(label.toBoolean, value.toDouble)
})

val streamingTest = new StreamingTest()
  .setPeacePeriod(0)
  .setWindowSize(0)
  .setTestMethod("welch")

val out = streamingTest.registerStream(data)
out.print()

更完整的代码请在 Spark 项目查看 "examples/src/main/scala/org/apache/spark/examples/mllib/StreamingTestExample.scala".

Random data generation/随机数据生成

随机数据生成对于随机算法，原型设计和性能测试很有用。 spark.mllib支持使用i.i.d.生成随机RDD。 从给定分布绘制的值：均匀，标准正态或泊松分布。

RandomRDDs 提供工厂方法来生成随机double型RDD或向量RDD。 以下示例生成随机double型RDD，其值遵循标准正态分布N（0,1），然后映射到N（1,4）。

阅读  RandomRDDs Scala docs 文档查看 API 细节

import org.apache.spark.SparkContext
import org.apache.spark.mllib.random.RandomRDDs._

val sc: SparkContext = ...

// Generate a random double RDD that contains 1 million i.i.d. values drawn from the
// standard normal distribution `N(0, 1)`, evenly distributed in 10 partitions.
val u = normalRDD(sc, 1000000L, 10)
// Apply a transform to get a random double RDD following `N(1, 4)`.
val v = u.map(x => 1.0 + 2.0 * x)

Kernel density estimation/核密度估计

核密度估计  是一种用于可视化经验概率分布的技术，而不需要对所观察到的样本的特定分布进行假设。 它计算在给定集合点评估的随机变量的概率密度函数的估计。 它通过在特定点表达PDF的经验分布来实现这一估计，这是以每个样本为中心的正态分布的PDF平均值.

KernelDensity 提供了从RDD样本计算核密度估计的方法。 以下示例演示如何执行此操作。 

阅读  KernelDensity 文档查看 API 细节

import org.apache.spark.mllib.stat.KernelDensity
import org.apache.spark.rdd.RDD

// an RDD of sample data
val data: RDD[Double] = sc.parallelize(Seq(1, 1, 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 9))

// Construct the density estimator with the sample data and a standard deviation
// for the Gaussian kernels
val kd = new KernelDensity()
  .setSample(data)
  .setBandwidth(3.0)

// Find density estimates for the given values
val densities = kd.estimate(Array(-1.0, 2.0, 5.0))

更完整的代码请在 Spark 项目查看 "examples/src/main/scala/org/apache/spark/examples/mllib/KernelDensityEstimationExample.scala" .

Classification and Regression - RDD-based API（分类和回归）

原文链接 : http://spark.apache.org/docs/2.1.0/mllib-classification-regression.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=9406490

贡献者 : 片刻 ApacheCN Apache中文网

spark.mllib包支持binary classification（二分类），multiclass classification（多分类）和regression analysis（回归分析）的各种方法。

下表列出了每种类型问题支持的算法。

问题类型 | 支持方法
Binary Classification（二分类） | linear SVMs, logistic regression, decision trees, random forests, gradient-boosted trees, naive Bayes
Multiclass Classification（多分类） | logistic regression, decision trees, random forests, naive Bayes
Regression（回归） | linear least squares, Lasso, ridge regression, decision trees, random forests, gradient-boosted trees, isotonic regression

这些方法的更多细节可以在这里找到：

Linear Methods - RDD-based API（线性方法）

Naive Bayes - RDD-based API（朴素贝叶斯）

Decision Trees - RDD-based API（决策树）

Ensembles - RDD-based API（集成方法）

Regression - RDD-based API（回归）

Collaborative Filtering - RDD-based API（协同过滤）

原文链接 : http://spark.apache.org/docs/2.1.0/mllib-collaborative-filtering.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=10027362

贡献者 : kagura ApacheCN Apache中文网

Collaborative filtering（协同过滤）

协作过滤通常用于推荐系统。这些技术旨在填补用户-项目关联矩阵中丢失的条目。spark.mllib 目前支持基于模型的协同过滤，其中用户和项目通过一小组潜在因素来描述，可用于预测缺失的条目。spark.mllib 使用 ALS（交替最小二乘法）算法来学习这些潜在因素。spark.mllib 中的实现具有以下参数：

numBlocks 是用于并行化计算的块数（设置为-1到自动配置）。

rank 是模型中潜在因素的数量。

iterations 是运行 ALS 的迭代次数。 ALS 通常在20次或更少次迭代中收敛到合理的解决方案。

lambda 指定 ALS 中的正则化参数。

implicitPrefs 指定是使用显式反馈ALS的版本还是用适用于隐式反馈数据集的版本。

alpha 是适用于控制偏好观察的基线置信度的ALS的隐式反馈版本的参数。

Explicit vs. implicit feedback（显式与隐式反馈）

基于矩阵分解的协同过滤的标准方法是将用户-项目关联矩阵中的元素视为用户对项目的显式偏好，例如用户对电影的评分。在现实世界中的许多用例中，通常只能接触到隐式的反馈（例如浏览，点击，购买，喜欢，分享等）。在 spark.mllib 中使用基于隐式反馈数据集的协同过滤的方法来处理这些数据。实际上，这种方法不是直接对数据矩阵进行建模，而是将数据视为代表用户行为意愿强度的数字（例如点击的次数或某人累积观看电影的时间）。然后，这些数字与观察到的用户偏好的置信水平相关，而不是给予项目的明确评级。 然后，该模型尝试找到可用于预测用户对项目的预期偏好的潜在因素。

Scaling of the regularization parameter（正则化参数的换算）

自 V1.1 起，我们通过用户在更新用户因素中产生的评分，或产品在更新产品因素中收到的评分来求解每个最小二乘问题的规则化参数 lambda 。 这种方法被命名为“ ALS-WR ”（加权正则化交替最小二乘法），并在论文“ Large-Scale Parallel Collaborative Filtering for the Netflix Prize ”中进行了讨论。 它使 lambda  对数据集的规模依赖较少，因此我们可以将从采样子集学到的最佳参数应用于完整数据集，并期望能有相似的表现。

Examples（示例）

在以下示例中，我们加载评估数据。 每行由用户，产品和评级组成。 我们使用默认的 ALS.train() 方法，该方法假设评级是显式的。 我们通过测量评级预测的均方误差来评估推荐模型。

有关API的更多详细信息，请参阅 ALS Scala 文档。

Scala

import org.apache.spark.mllib.recommendation.ALS
import org.apache.spark.mllib.recommendation.MatrixFactorizationModel
import org.apache.spark.mllib.recommendation.Rating

// Load and parse the data
val data = sc.textFile("data/mllib/als/test.data")
val ratings = data.map(_.split(',') match { case Array(user, item, rate) =>
  Rating(user.toInt, item.toInt, rate.toDouble)
})

// Build the recommendation model using ALS
val rank = 10
val numIterations = 10
val model = ALS.train(ratings, rank, numIterations, 0.01)

// Evaluate the model on rating data
val usersProducts = ratings.map { case Rating(user, product, rate) =>
  (user, product)
}
val predictions =
  model.predict(usersProducts).map { case Rating(user, product, rate) =>
    ((user, product), rate)
  }
val ratesAndPreds = ratings.map { case Rating(user, product, rate) =>
  ((user, product), rate)
}.join(predictions)
val MSE = ratesAndPreds.map { case ((user, product), (r1, r2)) =>
  val err = (r1 - r2)
  err * err
}.mean()
println("Mean Squared Error = " + MSE)

// Save and load model
model.save(sc, "target/tmp/myCollaborativeFilter")
val sameModel = MatrixFactorizationModel.load(sc, "target/tmp/myCollaborativeFilter")

可以在 Spark repo 中的“examples/src/main/scala/org/apache/spark/examples/mllib/RecommendationExample.scala”中查找完整的示例代码。
如果评分矩阵是来自于另一个信息来源（即从其他信号推断出来），您可以使用 implicitPrefs 方法 以获得更好的结果：

val alpha = 0.01
val lambda = 0.01
val model = ALS.trainImplicit(ratings, rank, numIterations, lambda, alpha)

为了运行上述应用程序，请按照“Spark快速入门指南”的“自包含应用程序”部分中提供的说明进行操作。 确保将spark-mllib作为依赖并包含在您的构建文件中。

Tutorial（教程）

2014年 Spark Summit 的培训活动包括用 spark.mllib 进行个性化电影推荐的实践教程。

Clustering - RDD-based API（聚类 - 基于RDD的API）

聚类是一个无监督的学习问题，我们的目标是基于一些相似性的概念将实体的子集相互组合。聚类经常用于探索性分析和/或作为分层 监督学习流水线的组成部分（其中针对每个群集训练不同的分类器或回归模型）。

 该spark.mllib 软件包支持以下模型：

原文链接 : http://spark.apache.org/docs/2.1.0/mllib-clustering.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=9406661

贡献者 : @yangjifei 片刻 ApacheCN Apache中文网

K-means

K-means是将数据点聚类到预定数量的聚类中最常用的聚类算法之一。该spark.mllib实现包括一个称为kmeans ||的k-means ++方法的并行变体 。在spark.mllib中，这个实现具有以下参数：

k是所需簇的数量。请注意，可以返回少于k个集群，例如，如果有少于k个不同的集群点。

maxIterations是要运行的最大迭代次数。

initializationMode通过k-means ||指定随机初始化或初始化。

run这个参数从Spark 2.0.0起没有效果。

initializationSteps确定k-means ||中的步数 算法。

epsilon决定了我们认为k-means收敛的距离阈值。

initialModel是用于初始化的一组可选集群。如果提供此参数，则只执行一次运行。

例子(Scala)

         可以使用spark-shell执行以下代码片段。

         在加载和解析数据后的以下示例中，我们使用该 KMeans对象将数据集群到两个集群中。所需的簇的数量被传递给算法。然后我们计算平方误差的集合和（WSSSE）。

         你可以通过增加k来减少此误差度量。实际上，最优的k通常是WSSSE图中有一个“elbow”。

Scala

import org.apache.spark.mllib.clustering.{KMeans, KMeansModel}
import org.apache.spark.mllib.linalg.Vectors

//加载和解析数据
val data = sc.textFile("data/mllib/kmeans_data.txt")
val parsedData = data.map(s => Vectors.dense(s.split(' ').map(_.toDouble))).cache()

// 使用KMeans将数据集成到2个类中
val numClusters = 2
val numIterations = 20
val clusters = KMeans.train(parsedData, numClusters, numIterations)

// 通过计算在平方误差的总和中评估聚类
val WSSSE = clusters.computeCost(parsedData)
println("Within Set Sum of Squared Errors = " + WSSSE)

// 保存和加载模型
clusters.save(sc, "target/org/apache/spark/KMeansExample/KMeansModel")
val sameModel = KMeansModel.load(sc, "target/org/apache/spark/KMeansExample/KMeansModel")

 有关API的详细信息，请参阅KMeansScala文档和KMeansModelScala文档

在Spark repo中的"examples/src/main/scala/org/apache/spark/examples/mllib/KMeansExample.scala"中查找完整示例代码。

 注：kmeans_data.txt 链接   

高斯混合

高斯混合模型代表一个复合分布，由此点是从一个绘制ķ高斯子分布，每个具有其自己的概率。该spark.mllib实现使用 期望最大化算法来给出给定一组样本的最大似然模型。这个实现具有以下参数：

k是所需簇的数量。

convergenceTol是我们考虑收敛的对数似然的最大变化。

maxIterations是不达到收敛的最大迭代次数。

initialModel是启动EM算法的可选起始点。如果省略此参数，将从数据中构建一个随机起始点。

例子(Scala)

在加载和解析数据之后的以下示例中，我们使用 GaussianMixture对象将数据集群到两个集群中。所需的簇的数量被传递给算法。然后我们输出混合模型的参数。

Scala

import org.apache.spark.mllib.clustering.{GaussianMixture, GaussianMixtureModel}
import org.apache.spark.mllib.linalg.Vectors

// 加载和解析数据
val data = sc.textFile("data/mllib/gmm_data.txt")
val parsedData = data.map(s => Vectors.dense(s.trim.split(' ').map(_.toDouble))).cache()

// 使用GaussianMixture将数据分成两类
val gmm = new GaussianMixture().setK(2).run(parsedData)

// 保存和加载模型
gmm.save(sc, "target/org/apache/spark/GaussianMixtureExample/GaussianMixtureModel")
val sameModel = GaussianMixtureModel.load(sc,
  "target/org/apache/spark/GaussianMixtureExample/GaussianMixtureModel")

// 输出最大似然模型的参数
for (i <- 0 until gmm.k) {
  println("weight=%f\nmu=%s\nsigma=\n%s\n" format
    (gmm.weights(i), gmm.gaussians(i).mu, gmm.gaussians(i).sigma))
}

有关API的详细信息，请参阅GaussianMixtureScala文档和GaussianMixtureModelScala文档。

可以在Spark repo中找到"examples/src/main/scala/org/apache/spark/examples/mllib/GaussianMixtureExample.scala"完整的示例代码  
注：gmm_data.txt 链接      

幂迭代聚类（PIC）

       幂迭代聚类（PIC）是一种可扩展和高效的算法，用于将图中的顶点聚类为给定的边缘属性的成对相似性，Lin和Cohen，Power Iteration Clustering中描述。它通过功率迭代计算图的归一化亲和度矩阵的伪特征向量，并将 其用于聚类顶点。spark.mllib包括使用GraphX作为其后端的PIC的实现。它需要一个RDD的(srcId, dstId, similarity)元组，并输出与该聚类分配的模型。相似之处必须是非负的。PIC假定相似性度量是对称的。(srcId, dstId)输入数据中最多只能出现一对，而不管排序如何。如果输入中缺少一对，则将其相似性视为零。 spark.mllib

k：簇数

maxIterations：最大功率迭代次数

initializationMode：初始化模型 这可以是使用随机向量作为顶点属性的“随机”，它是默认的，或者“度”使用归一化的相似度。

例子(Scala)

下面我们来看一下代码片段演示如何在spark.mllib使用PIC 。

   PowerIterationClustering 实现PIC算法。它需要代表亲和度矩阵RDD的(srcId: Long, dstId: Long, similarity: Double)元组。

       调用PowerIterationClustering.run返回a PowerIterationClusteringModel，其中包含计算的聚类分配。

Scala

import org.apache.spark.mllib.clustering.PowerIterationClustering

val circlesRdd = generateCirclesRdd(sc, params.k, params.numPoints)
val model = new PowerIterationClustering()
  .setK(params.k)
  .setMaxIterations(params.maxIterations)
  .setInitializationMode("degree")
  .run(circlesRdd)

val clusters = model.assignments.collect().groupBy(_.cluster).mapValues(_.map(_.id))
val assignments = clusters.toList.sortBy { case (k, v) => v.length }
val assignmentsStr = assignments
  .map { case (k, v) =>
    s"$k -> ${v.sorted.mkString("[", ",", "]")}"
  }.mkString(", ")
val sizesStr = assignments.map {
  _._2.length
}.sorted.mkString("(", ",", ")")
println(s"Cluster assignments: $assignmentsStr\ncluster sizes: $sizesStr")

有关API的详细信息，请参阅PowerIterationClusteringScala文档和PowerIterationClusteringModelScala文档 

可以在Spark repo中找到 "examples/src/main/scala/org/apache/spark/examples/mllib/PowerIterationClusteringExample.scala"完整的示例代码。

潜在Dirichlet分配（LDA）

 潜在的Dirichlet分配（LDA） 是一个主题模型，从一组文本文档推断主题。LDA可以被认为是聚类算法如下：

 主题对应于集群中心，文档对应于数据集中的示例（行）。

 主题和文档都存在于特征空间中，其中特征向量是字计数（单词包）的向量。

 LDA不是使用传统距离来估计聚类，而是使用基于如何生成文本文档的统计模型的函数。

               LDA通过setOptimizer功能支持不同的推理算法。 EMLDAOptimizer使用 期望最大化 对似然函数进行聚类，并产生综合结果，

               同时 OnlineLDAOptimizer使用迭代小批量抽样进行在线变分推理 ，通常内存友好。

LDA将文档集合作为字计数的向量和以下参数（使用构建器模式设置）：

k：主题数量（即集群中心）

optimizer：用于学习LDA模型的优化器，或者 EMLDAOptimizer或OnlineLDAOptimizer

docConcentration：Dirichlet参数，用于事先通过主题分发的文档。较大的值会鼓励更平稳的推断分布。

topicConcentration：Dirichlet参数，用于事先通过术语（词）分配主题。较大的值会鼓励更平稳的推断分布。

maxIterations：限制迭代次数。

checkpointInterval：如果使用检查点（在Spark配置中设置），则此参数指定将创建检查点的频率。如果maxIterations大，使用检查点可以帮助减少磁盘上的随机文件大小，并帮助恢复故障。

所有的spark.mllib的LDA型号都支持：

describeTopics：将主题作为最重要术语和术语权重的数组返回

topicsMatrix：返回一个vocabSize由k矩阵，其中各列是一个主题

       注意：LDA仍然是积极开发的实验功能。因此，某些功能仅在优化程序生成的两个优化器/模型之一中可用。目前，分布式模型可以转换为本地模型，但反之亦然。

       以下讨论将分别描述每个优化器/模型对。

期望最大化

       在 EMLDAOptimizer 和 DistributedLDAModel中的实现。

       为LDA提供的参数：

docConcentration：只支持对称先验，所以提供的k维度向量中的所有值必须相同。所有值也必须为$ 1.0 $。
                               提供默认行为的结果（均值维矢量，值为$（50 / k）+ 1 $>1.0  >1.0Vector(-1)k(50/k)+1  (50/k)+1

topicConcentration：只支持对称先验。值必须为$ 1.0 $。提供结果，默认值为$ 0.1 + 1 $。>1.0  >1.0-10.1+1  0.1+1

maxIterations：EM迭代的最大数量。

注意：重复执行足够的迭代。在早期迭代中，EM通常有无用的主题，但是这些主题在更多的迭代之后会显着改善。使用至少20次和可能的50-100次迭代通常是合理的，具体取决于您的数据集。

       EMLDAOptimizer产生一个DistributedLDAModel，它不仅存储推断的主题，而且存储训练语料库中每个文档的完整的训练语料库和主题分布。一个 DistributedLDAModel的支持：

topTopicsPerDocument：训练语料库中每个文档的主题及其权重

topDocumentsPerTopic：每个主题的顶部文档以及文档中主题的相应权重。

logPrior：考虑到超参数的估计主题和文档的主题分布数概率 docConcentration和topicConcentration

logLikelihood：训练语料库的对数可能性，给出推断的主题和文档主题分布

在线变异贝叶斯

       在 OnlineLDAOptimizer 和 LocalLDAModel中的实现。

       为LDA提供的参数：

docConcentrationk>=0  >=0Vector(-1)k(1.0/k)  (1.0/k)

topicConcentration>=0  >=0-1(1.0/k)  (1.0/k)

maxIterations：要提交的最大批量数量。

        另外，OnlineLDAOptimizer接受以下参数：

miniBatchFraction：每次迭代采样和使用语料库的分数

optimizeDocConcentration：如果设置为true，则在每个minibatch之后执行超参数docConcentration（aka alpha）的最大似然估计docConcentration，并将返回的优化LocalLDAModel

tau0kappa(τ  0 +iter) −κ   (τ0+iter)−κiter  iter

   OnlineLDAOptimizer产生一个LocalLDAModel，它只存储推断的主题。一个 LocalLDAModel支持：

logLikelihood(documents)：计算所提供documents给定推断主题的下限 。

logPerplexity(documents)：计算提供的documents给定推断主题的困惑度的上限。

例子(Scala)

            在以下示例中，我们加载表示文档语料库的字数向量。然后，我们使用LDA 从文档中推断出三个主题。所需的簇的数量被传递给算法。然后我们输出主题，表示为词的概率分布。

Scala

import org.apache.spark.mllib.clustering.{DistributedLDAModel, LDA}
import org.apache.spark.mllib.linalg.Vectors

// 加载和解析数据
val data = sc.textFile("data/mllib/sample_lda_data.txt")
val parsedData = data.map(s => Vectors.dense(s.trim.split(' ').map(_.toDouble)))
// 具有唯一ID的索引文档
val corpus = parsedData.zipWithIndex.map(_.swap).cache()

// 使用LDA将文档集成到三个主题中
val ldaModel = new LDA().setK(3).run(corpus)

// 输出主题。每个都是一个分配的单词（匹配的单词向量）
println("Learned topics (as distributions over vocab of " + ldaModel.vocabSize + " words):")
val topics = ldaModel.topicsMatrix
for (topic <- Range(0, 3)) {
  print("Topic " + topic + ":")
  for (word <- Range(0, ldaModel.vocabSize)) { print(" " + topics(word, topic)); }
  println()
}

// 保存和加载模型
ldaModel.save(sc, "target/org/apache/spark/LatentDirichletAllocationExample/LDAModel")
val sameModel = DistributedLDAModel.load(sc,
  "target/org/apache/spark/LatentDirichletAllocationExample/LDAModel")

有关API的详细信息，请参阅LDAScala文档和DistributedLDAModelScala文档

可以在Spark repo中找到"examples/src/main/scala/org/apache/spark/examples/mllib/LatentDirichletAllocationExample.scala" 的完整示例代码。

注：sample_lda_data.txt 链接

平分k-means

          平均K均值通常比常规K均值快得多，但通常会产生不同的聚类。

          平分k均值是一种层次聚类。分层聚类是集群分析中最常用的方法之一，它旨在构建集群的层次结构。层次聚类的策略通常分为两种：

集合：这是一种“自下而上”的方法：每个观察在自己的集群中开始，并且一组聚类在层次结构上移动时被合并。

分裂：这是一种“自上而下”的方法：所有观察都从一个群集中开始，并且分层在层级结构向下移动时递归执行。

          二分法k均值算法是一种分裂算法。MLlib中的实现具有以下参数：

k：所需的叶簇数（默认值：4）。如果没有可分割的叶簇，实际数量可能更小。

maxIterations：拆分集群的k-means迭代的最大数量（默认值：20）

minDivisibleClusterSize：可分组的最小点数（如果> = 1.0）或点的最小比例（如果<1.0）（默认值：1）

种子：一个随机种子（默认值：类名的哈希值）

例子(Scala)

Scala

import org.apache.spark.mllib.clustering.BisectingKMeans
import org.apache.spark.mllib.linalg.{Vector, Vectors}

// 加载和解析数据
def parse(line: String): Vector = Vectors.dense(line.split(" ").map(_.toDouble))
val data = sc.textFile("data/mllib/kmeans_data.txt").map(parse).cache()

// 通过BisectingKMeans将数据聚类成6个簇。
val bkm = new BisectingKMeans().setK(6)
val model = bkm.run(data)

// 显示计算成本和集群中心
println(s"Compute Cost: ${model.computeCost(data)}")
model.clusterCenters.zipWithIndex.foreach { case (center, idx) =>
  println(s"Cluster Center ${idx}: ${center}")
}

 有关API的详细信息，请参阅BisectingKMeansScala文档和BisectingKMeansModelScala文档

可以在Spark repo中找到"examples/src/main/scala/org/apache/spark/examples/mllib/BisectingKMeansExample.scala"完整的示例代码。

注：kmeans_data.txt 链接

Streaming k-means

       当数据到达流时，我们可能需要动态地估计集群，并在新数据到达时进行更新。spark.mllib提供对流式k均值聚类的支持，其中包含用于控制估计的衰减（或“健忘”）的参数。该算法使用小批量k-means更新规则的泛化。对于每批数据，我们将所有点分配给最近的集群，计算新的集群中心，然后使用以下方式更新每个集群：

例子

       此示例显示如何估计流数据上的集群。

Scala

import org.apache.spark.mllib.clustering.StreamingKMeans
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.streaming.{Seconds, StreamingContext}

val conf = new SparkConf().setAppName("StreamingKMeansExample")
val ssc = new StreamingContext(conf, Seconds(args(2).toLong))

val trainingData = ssc.textFileStream(args(0)).map(Vectors.parse)
val testData = ssc.textFileStream(args(1)).map(LabeledPoint.parse)

val model = new StreamingKMeans()
  .setK(args(3).toInt)
  .setDecayFactor(1.0)
  .setRandomCenters(args(4).toInt, 0.0)

model.trainOn(trainingData)
model.predictOnValues(testData.map(lp => (lp.label, lp.features))).print()

ssc.start()
ssc.awaitTermination()

      可以在Spark repo中找到"examples/src/main/scala/org/apache/spark/examples/mllib/StreamingKMeansExample.scala" 完整的示例代码。

     当您添加具有数据的新文本文件时，集群中心将更新。 每个训练点应该格式化为[x1，x2，x3]，每个测试数据点应该被格式化为（y，[x1，x2，x3]），其中y是一些有用的标签或标识符（例如真正的类别分配 ）。 任何时候，文本文件放在/training/data/dir中，模型将会更新。 任何时候，文本文件都放在/testing/data/dir中，您将看到预测。 使用新的数据，集群中心将改变！

Dimensionality Reduction - RDD-based API（降维）

原文链接 : https://spark.apache.org/docs/latest/mllib-dimensionality-reduction.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=10027073

贡献者 : 程威 片刻 ApacheCN Apache中文网

降维是减少所考虑的变量数量的过程.它可以用于从原始和噪声特征中提取潜在特征,或者在保持结构的同时压缩数据.spark.mlib 提供对 RowMatrix class 降维的支持.

Singular value decomposition (SVD) （奇异值分解）

Singular value decomposition (SVD)（奇异值分解）将矩阵分解为三个矩阵: U, Σ, 和 V,例如

解释 :

U是一个正交矩阵（方阵）,其列被称为左奇异向量.

Σ是一个对角线元素的值为非负数的对角矩阵,其对角线元素被称为奇异值.

V是一个正交矩阵,其列称为右奇异向量.

对于大型的矩阵,通常我们不需要完整的因式分解,而只需要顶点奇异值及相关的奇异向量.这可以节省存储,去噪和恢复矩阵的低阶结构.

如果我们保留k个奇异值（将n维空间映射到k维空间）,则所得到的低阶矩阵的维数将是 :

U: m×k

Σ: k×k

V: n×k

Performance（性能）

我们假设 n 的数量低于 m 的数量.奇异值和右奇异向量派生于 Gramian（格拉姆） 矩阵的特征值和特征向量.如果用户通过 computeU 参数请求,矩阵储存左奇异向量U,通过矩阵相乘计算得到.实际使用的方法是通过计算成本来自动确定 ：

如果 n 是较小的（n<100）或者 k 是较大的(k>n/2)，我们先计算 Gramian（格拉姆）矩阵,然后在驱动程序上本地计算 top（明显的）特征值和特征向量.这需要一次运算伴随着在执行器和驱动程序上的的空间复杂度,和在驱动程序上的的时间复杂度.

- 否则,我们以分发的方式计算并发送到 ARPACK 来计算在驱动节点上的 top（明显的）特征值和特征向量.这需要运算O(k)次,消耗执行器上的O(n)的空间复杂度和驱动程序上的O(nk)的空间复杂度.

SVD Example（SVD例子）

spark.mllib 为面向行的矩阵提供SVD功能,在RowMatrix类中提供. 

Scala

import org.apache.spark.mllib.linalg.Matrix
import org.apache.spark.mllib.linalg.SingularValueDecomposition
import org.apache.spark.mllib.linalg.Vector
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.linalg.distributed.RowMatrix
val data = Array(
  Vectors.sparse(5, Seq((1, 1.0), (3, 7.0))),
  Vectors.dense(2.0, 0.0, 3.0, 4.0, 5.0),
  Vectors.dense(4.0, 0.0, 0.0, 6.0, 7.0))
val dataRDD = sc.parallelize(data, 2)
val mat: RowMatrix = new RowMatrix(dataRDD)
// 计算最明显的5个奇异值和相应的奇异变量
val svd: SingularValueDecomposition[RowMatrix, Matrix] = mat.computeSVD(5, computeU = true)
val U: RowMatrix = svd.U  // U是一个RowMatrix类.
val s: Vector = svd.s  // 奇异值存储在局部密集矢量中.
val V: Matrix = svd.V  // V是一个局部密集矩阵.

有关API的详细信息，请参阅SingularValueDecomposition Scala文档

在 Spark repo "examples/src/main/scala/org/apache/spark/examples/mllib/SVDExample.scala" 中查找完整示例代码.

如果 U 被定义为 IndexedRowMatrix ,则相同的代码适用于 IndexedRowMatrix.

Principal component analysis (PCA)

Principal component analysis (PCA) 是一种统计方法来找到一个旋转，使得第一个坐标具有最大的方差可能，每个后续坐标依次具有最大的方差。旋转矩阵的列称为主要组件。 PCA广泛用于降维。

spark.mllib 支持 PCA，用于以行为导向的格式和任何向量存储的高和低密度矩阵。

以下代码演示如何计算 RowMatrix 上的主要组件，并使用它们将向量投影到低维空间中

Scala

import org.apache.spark.mllib.linalg.Matrix
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.linalg.distributed.RowMatrix

val data = Array(
  Vectors.sparse(5, Seq((1, 1.0), (3, 7.0))),
  Vectors.dense(2.0, 0.0, 3.0, 4.0, 5.0),
  Vectors.dense(4.0, 0.0, 0.0, 6.0, 7.0))

val dataRDD = sc.parallelize(data, 2)

val mat: RowMatrix = new RowMatrix(dataRDD)

// Compute the top 4 principal components.
// Principal components are stored in a local dense matrix.
val pc: Matrix = mat.computePrincipalComponents(4)

// Project the rows to the linear space spanned by the top 4 principal components.
val projected: RowMatrix = mat.multiply(pc)

有关API的详细信息，请参阅 RowMatrix Scala docs

完整的示例代码在Spark repo 的"examples/src/main/scala/org/apache/spark/examples/mllib/PCAOnRowMatrixExample.scala" 文件中.

以下代码演示如何计算源向量中的主成分，并使用它们将向量投影到低维空间中，同时保留相关标签：

Scala

import org.apache.spark.mllib.feature.PCA
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.rdd.RDD

val data: RDD[LabeledPoint] = sc.parallelize(Seq(
  new LabeledPoint(0, Vectors.dense(1, 0, 0, 0, 1)),
  new LabeledPoint(1, Vectors.dense(1, 1, 0, 1, 0)),
  new LabeledPoint(1, Vectors.dense(1, 1, 0, 0, 0)),
  new LabeledPoint(0, Vectors.dense(1, 0, 0, 0, 0)),
  new LabeledPoint(1, Vectors.dense(1, 1, 0, 0, 0))))

// Compute the top 5 principal components.
val pca = new PCA(5).fit(data.map(_.features))

// Project vectors to the linear space spanned by the top 5 principal
// components, keeping the label
val projected = data.map(p => p.copy(features = pca.transform(p.features)))

有关API的详细信息，请参阅 PCA Scala docs文档

完整的代码在Spark repo 的'examples/src/main/scala/org/apache/spark/examples/mllib/PCAOnSourceVectorExample.scala'文件中.

为了运行上述应用程序，请按照Spark快速入门指南的 Self-Contained Applications 部分中提供的说明进行操作。确保将spark-mllib包含在您的构建文件中作为依赖。

Feature Extraction and Transformation - RDD-based API（特征的提取和转换）

原文链接 : http://spark.apache.org/docs/2.1.0/mllib-feature-extraction.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=10027716

贡献者 : ping 片刻 ApacheCN Apache中文网

TF-IDF

注意  我们建议使用基于 DataFrame 的 API，这在 ML 的关于 TF-IDF 的用户指南中有详细介绍。

词频-逆文档频率（TF-IDF）是一种在文本挖掘中广泛使用的特征向量化方法，以反映字词在语料库中的重要性。用 t 表示词频，d 表示文档，和 D 表示语料库。词频 TF(t,d) 是指字词 t 在文档 d 中出现的次数，而文档频率 DF(t,D) 是指语料库 D 含有包含字词 t 的文档数。如果我们只使用词频来衡量其重要性，就会很容易过分强调重出现次数多但携带信息少的单词，例如 "a"，“the” 和 "of"。 如果某个字词在语料库中高频出现，这意味着它不包含关于特定文档的特殊信息。 逆文档频率是单词携带信息量的数值度量：

其中 |D|是语料中的文档总数。由于使用了log计算，如果单词在所有文档中出现，那么IDF就等于0。注意这里做了平滑处理（+1操作），防止字词没有在语料中出现时IDF计算中除0。 TF-IDF度量是TF和IDF的简单相乘：

事实上词频和文档频率的定义有多重变体。在 spark.mllib 中，为了灵活性我们将TF和IDF分开处理。

MLlib中词频统计的实现使用了hashing trick(散列技巧)，也就是使用哈希函数将原始特征映射到一个索引（字词）。然后基于这个索引来计算词频。这个方法避免了全局的单词到索引的映射，全局映射对于大量语料有非常昂贵的计算/存储开销；但是该方法也带来了潜在哈希冲突的问题，不同原始特征可能会被映射到相同的索引。为了减少冲突率，我们可以提升目标特征的维度，换句话说，哈希表中桶的数量。默认特征维度是220 = 1048576。

注意：spark.mllib 没有提供文本分段（例如分词）的工具。用户可以参考 Stanford NLP Group 和 scalanlp/chalk。

示例

TF和IDF分别在类HashingTF and IDF中实现。HashingTF 以 RDD[Iterable[_]] 为输入。每条记录是可遍历的字符串或者其他类型。

Scala

import org.apache.spark.mllib.feature.{HashingTF, IDF}
import org.apache.spark.mllib.linalg.Vector
import org.apache.spark.rdd.RDD

// 加载文件（每行一个）。
val documents: RDD[Seq[String]] = sc.textFile("data/mllib/kmeans_data.txt")
  .map(_.split(" ").toSeq)

val hashingTF = new HashingTF()
val tf: RDD[Vector] = hashingTF.transform(documents)

// 在应用 HashingTF 时，只需要单次传递数据，应用 IDF 需要两次：
// 首先计算 IDF 向量，其次用 IDF 来缩放字词频率。
tf.cache()
val idf = new IDF().fit(tf)
val tfidf: RDD[Vector] = idf.transform(tf)

// spark.mllib IDF 实现提供了忽略少于最少文档数量的字词的选项。
//在这种情况下，这些条款的 IDF 设置为0。
// 通过将minDocFreq值传递给IDF构造函数，可以使用此功能。
val idfIgnore = new IDF(minDocFreq = 2).fit(tf)
val tfidfIgnore: RDD[Vector] = idfIgnore.transform(tf)

API 详情参见 HashingTF Scala 文档

完整样例代码在 Spark repo 的 "examples/src/main/scala/org/apache/spark/examples/mllib/TFIDFExample.scala" 中。

Word2Vec

Word2Vec 计算单词的向量表示。这种表示的主要优点是相似的词在向量空间中离得近，这使得向新模式的泛化更容易并且模型估计更健壮。分布式的向量表示在诸如命名实体识别、歧义消除、句子解析、打标签以及机器翻译等自然语言处理程序中比较有用。

模型

在我们实现 Word2Vec 时，使用的是 skip-gram 模型。skip-gram 的目标函数是学习擅长预测同一个句子中词的上下文的词向量表示。用数学语言表达就是，给定一个训练单词序列：w₁, w₂, ..., wT, skip-gram 模型的目标是最大化平均 log 似然函数(log-likelihood):

其中 k 是训练窗口的大小，也就是给定一个词，需要分别查看前后 k 个词。

在 skip-gram 模型中，每个词 w 跟两个向量 uw 和 vw 关联：uw 是 w 的词向量表示，是 vw 上下文。给定单词 wj，正确预测单词 wi 的概率取决于 softmax 模型：

 其中 V 是词汇量大小。

使用 softmax 的 skip-gram 模型开销很大，因为 log p(wi|wj) 的计算量跟 V 成比例，而 V 很可能在百万量级。为了加速 Word2Vec 的训练，我们引入了层次 softmax，该方法将计算 log p(wi|wj) 时间复杂度降低到了 O(log(V))。

示例

在下面的例子中，首先导入文本文件，然后将数据解析为 RDD[Seq[String]]，接着构造 Word2Vec 实例并使用输入数据拟合出 Word2VecModel 模型。最后，显示了指定单词的 40 个同义词。要运行这段程序，需要先下载 text8 数据并解压到本地目录。这里假设我们抽取的文件是 text8，并在相同目录下运行 spark shell。

Scala

import org.apache.spark.mllib.feature.{Word2Vec, Word2VecModel}

val input = sc.textFile("data/mllib/sample_lda_data.txt").map(line => line.split(" ").toSeq)

val word2vec = new Word2Vec()

val model = word2vec.fit(input)

val synonyms = model.findSynonyms("1", 5)

for((synonym, cosineSimilarity) <- synonyms) {
  println(s"$synonym $cosineSimilarity")
}

// 保存和加载模型
model.save(sc, "myModelPath")
val sameModel = Word2VecModel.load(sc, "myModelPath")

API 详情参见 Word2Vec Scala 文档

完整样例代码在 Spark repo 的 "examples/src/main/scala/org/apache/spark/examples/mllib/Word2VecExample.scala" 中。

StandardScaler（标准化）

标准化是指：对于训练集中的样本，基于列统计信息将数据除以方差或（且）者将数据减去其均值（结果是方差等于1，数据在 0 附近）。这是很常用的预处理步骤。

例如，当所有的特征具有值为 1 的方差且/或值为 0 的均值时，SVM 的径向基函数（RBF）核或者 L1 和 L2 正则化线性模型通常有更好的效果。

标准化可以提升模型优化阶段的收敛速度，还可以避免方差很大的特征对模型训练产生过大的影响。

Model Fitting（模型拟合）

类 StandardScaler 的构造函数具有下列参数：

withMean 默认值False. 在尺度变换（除方差）之前使用均值做居中处理（减去均值）。这会导致密集型输出，所以在稀疏数据上无效。

withStd 默认值True. 将数据缩放（尺度变换）到单位标准差。

StandardScaler.fit()方法以RDD[Vector]为输入，计算汇总统计信息，然后返回一个模型，该模型可以根据StandardScaler配置将输入数据转换为标准差为1，均值为0的特征。

模型中还实现了VectorTransformer，这个类可以对 Vector 和 RDD[Vector] 做转化。

注意：如果某特征的方差是 0，那么标准化之后返回默认的 0.0 作为特征值。                                                                                                                                                                                                                                                                                    

示例

在下面的例子中，首先导入 libsvm 格式的数据，然后做特征标准化，标准化之后新的特征值有单位长度的标准差和/或均值。

Scala

import org.apache.spark.mllib.feature.{StandardScaler, StandardScalerModel}
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.util.MLUtils

val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")

val scaler1 = new StandardScaler().fit(data.map(x => x.features))
val scaler2 = new StandardScaler(withMean = true, withStd = true).fit(data.map(x => x.features))
// scaler3 is an identical model to scaler2, and will produce identical transformations
val scaler3 = new StandardScalerModel(scaler2.std, scaler2.mean)

// data1 will be unit variance.
val data1 = data.map(x => (x.label, scaler1.transform(x.features)))

// data2 will be unit variance and zero mean.
val data2 = data.map(x => (x.label, scaler2.transform(Vectors.dense(x.features.toArray))))

API 详情参见  StandardScaler Scala 文档 

完整样例代码在 Spark repo 的 "examples/src/main/scala/org/apache/spark/examples/mllib/StandardScalerExample.scala" 中。

Normalizer（归一化）

归一化是指将每个独立样本做尺度变换从而是该样本具有单位  Lp 范数。这是文本分类和聚类中的常用操作。例如，两个做了L2归一化的TF-IDF向量的点积是这两个向量的cosine（余弦）相似度。

Normalizer 的构造函数有以下参数：

在Lp空间的p范数, 默认 p=2。

Normlizer 实现了 VectorTransformer ，这个类可以对 Vector 和 RDD[Vector] 做归一化。

注意：如果输入的范数是 0，会返回原来的输入向量。

示例

在下面的例子中，首先导入libsvm格式的数据，然后使用 L2 范数和 L∞ 范数归一化。

Scala版本：

API 详情参见 Normalizer Scala 文档 

完整样例代码在 Spark repo 的 "examples/src/main/scala/org/apache/spark/examples/mllib/NormalizerExample.scala" 中。

Scala

import org.apache.spark.mllib.feature.Normalizer
import org.apache.spark.mllib.util.MLUtils

val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")

val normalizer1 = new Normalizer()
val normalizer2 = new Normalizer(p = Double.PositiveInfinity)

// Each sample in data1 will be normalized using $L^2$ norm.
val data1 = data.map(x => (x.label, normalizer1.transform(x.features)))

// Each sample in data2 will be normalized using $L^\infty$ norm.
val data2 = data.map(x => (x.label, normalizer2.transform(x.features)))

ChiSqSelector（卡方选择）

ChiSqSelector是指使用卡方（Chi-Squared）做特征选择。该方法操作的是有标签的类别型数据。ChiSqSelector基于卡方检验来排序数据，然后选出卡方值较大(也就是跟标签最相关)的特征（topk)。

模型拟合

ChiSqSelector 的构造函数有如下特征：

numTopFeatures 保留的卡方较大的特征的数量。

ChiSqSelector.fit() 方法以具有类别特征的RDD[LabeledPoint]为输入，计算汇总统计信息，然后返回ChiSqSelectorModel，这个类将输入数据转化到降维的特征空间。

模型实现了 VectorTransformer，这个类可以在Vector和RDD[Vector]上做卡方特征选择。

注意：也可以手工构造一个ChiSqSelectorModel，需要提供升序排列的特征索引。

示例

下面的例子说明了ChiSqSelector的基本用法。

Scala

import org.apache.spark.mllib.feature.ChiSqSelector
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.util.MLUtils

// Load some data in libsvm format
val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")
// Discretize data in 16 equal bins since ChiSqSelector requires categorical features
// Even though features are doubles, the ChiSqSelector treats each unique value as a category
val discretizedData = data.map { lp =>
  LabeledPoint(lp.label, Vectors.dense(lp.features.toArray.map { x => (x / 16).floor }))
}
// Create ChiSqSelector that will select top 50 of 692 features
val selector = new ChiSqSelector(50)
// Create ChiSqSelector model (selecting features)
val transformer = selector.fit(discretizedData)
// Filter the top 50 features from each feature vector
val filteredData = discretizedData.map { lp =>
  LabeledPoint(lp.label, transformer.transform(lp.features))
}

API 详情参见 ChiSqSelector Scala 文档 。

完整样例代码在 Spark repo 的  "examples/src/main/scala/org/apache/spark/examples/mllib/ChiSqSelectorExample.scala" 中。

ElementwiseProduct（Hadamard 乘积）

ElementwiseProduct multiplies each input vector by a provided “weight” vector, using element-wise multiplication. In other words, it scales each column of the dataset by a scalar multiplier. This represents the Hadamard product between the input vector, v and transforming vector, scalingVec, to yield a result vector. Qu8T948*1# Denoting the scalingVec as “w,” this transformation may be written as:

ElementwiseProduct 对输入向量的每个元素乘以一个给定的权重向量的每个元素，对输入向量每个元素逐个进行放缩。这个称为对输入向量 v 和变换向量 scalingVec 使用 Hadamard product (阿达玛积)进行变换，最终产生一个新的向量。Qu8T948*1#  用向量 w 表示 scalingVec ，则 Hadamard product 可以表示为

Hamard 乘积需要配置一个权向量 scalingVec

 scalingVec 变换向量

ElementwiseProduct 实现 VectorTransformer 方法，就可以对向量乘以权向量，得到新的向量，或者对 RDD[Vector] 乘以权向量得到 RDD[Vector]

示例

下例展示如何对向量进行 ElementwiseProduct 变换

Scala

import org.apache.spark.mllib.feature.ElementwiseProduct
import org.apache.spark.mllib.linalg.Vectors

// Create some vector data; also works for sparse vectors
val data = sc.parallelize(Array(Vectors.dense(1.0, 2.0, 3.0), Vectors.dense(4.0, 5.0, 6.0)))

val transformingVector = Vectors.dense(0.0, 1.0, 2.0)
val transformer = new ElementwiseProduct(transformingVector)

// Batch transform and per-row transform give the same results:
val transformedData = transformer.transform(data)
val transformedData2 = data.map(x => transformer.transform(x))

API 详情参见 ElementwiseProduct Scala 文档 

完整样例代码在 Spark repo 的  "examples/src/main/scala/org/apache/spark/examples/mllib/ElementwiseProductExample.scala" 中。

PCA

使用PCA将向量投影到低维空间的特征变换器。 您可以阅读的细节在降低维度。

示例

下例展示如何计算特征向量空间的主要组件，并使用主要组件将向量投影到低维空间中，同时保留向量的类标签用于计算的关联标签线性回归。

Scala

import org.apache.spark.mllib.feature.PCA
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.{LabeledPoint, LinearRegressionWithSGD}

val data = sc.textFile("data/mllib/ridge-data/lpsa.data").map { line =>
  val parts = line.split(',')
  LabeledPoint(parts(0).toDouble, Vectors.dense(parts(1).split(' ').map(_.toDouble)))
}.cache()

val splits = data.randomSplit(Array(0.6, 0.4), seed = 11L)
val training = splits(0).cache()
val test = splits(1)

val pca = new PCA(training.first().features.size / 2).fit(data.map(_.features))
val training_pca = training.map(p => p.copy(features = pca.transform(p.features)))
val test_pca = test.map(p => p.copy(features = pca.transform(p.features)))

val numIterations = 100
val model = LinearRegressionWithSGD.train(training, numIterations)
val model_pca = LinearRegressionWithSGD.train(training_pca, numIterations)

val valuesAndPreds = test.map { point =>
  val score = model.predict(point.features)
  (score, point.label)
}

val valuesAndPreds_pca = test_pca.map { point =>
  val score = model_pca.predict(point.features)
  (score, point.label)
}

val MSE = valuesAndPreds.map { case (v, p) => math.pow((v - p), 2) }.mean()
val MSE_pca = valuesAndPreds_pca.map { case (v, p) => math.pow((v - p), 2) }.mean()

println("Mean Squared Error = " + MSE)
println("PCA Mean Squared Error = " + MSE_pca)

API 详情参见 PCA Scala 文档

完整样例代码在 Spark repo 的  "examples/src/main/scala/org/apache/spark/examples/mllib/PCAExample.scala" 中。

Frequent Pattern Mining - RDD-based API（频繁模式挖掘）

原文链接 : http://spark.apache.org/docs/2.1.0/mllib-frequent-pattern-mining.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=9406527

贡献者 : 孙上上 片刻 ApacheCN Apache中文网

近些年来，分析大规模数据集的第一步，就是挖掘频繁项，频繁项集，子序列或者是子结构，并且在数据挖掘领域，这已经是一项热点研究了。关于更多的相关的信息，用户可以去维基百科上了解association rule learning(关联规则的学习)这部分。

FP-growth

FP-growth 算法是由韩家炜在 Han et al., Mining frequent patterns without candidate generation 提出的，“FP”代表频繁模式的意思。该算法的基本思路如下，给予一个事务数据库，FP-Growth 算法的第一步是每一项出现的次数，通过最小支持度进行筛选确定频繁项。不同于另一种关联算法Apriori 算法，FP-growth 算法第二步是通过产生后缀树( FP-tree )来存储所有的事务数据库中的项，而不是像 Apriori 算法那样花费大量的内存去产生候选项集。然后，通过遍历 FP-Tree 可以挖掘出频繁项集。在 spark.mllib 中，我们实现了并行的 FP-growth 算法叫做 PFP，正如论文Li et al., PFP: Parallel FP-growth for query recommendation 中描述的，PFP 将基于相同后缀的事务分发到相同的机器上，因此相比的单台机器的实现，这样有更好的扩展性。我们推荐用户读 Li et al., PFP: Parallel FP-growth for query recommendation这篇论文去理解更多的信息。
spark mllib 中 FP-growth 算法的实现要使用到以下两个超参数:
      • minSupport (最小支持度):一个项集被认为是频繁项集的最小支持度。例如，如果一个项总共有5个事务的数据集中，出现了3次，那么它的支持度为3/5=0.6
      • numPartitions (分区数):分区的个数，同于将事务分配到几个分区。

例子

Scala

import org.apache.spark.mllib.fpm.FPGrowth
import org.apache.spark.rdd.RDD

//读取文件
val data = sc.textFile("data/mllib/sample_fpgrowth.txt")

//预处理并转换为RDD[Array[String]]存储
val transactions: RDD[Array[String]] = data.map(s => s.trim.split(' '))

//调用FPGrowth()方法，设定最小支持度与分区数
val fpg = new FPGrowth()
  .setMinSupport(0.2)
  .setNumPartitions(10)

//得到FPGrowthModel
val model = fpg.run(transactions)

//遍历频繁项集
model.freqItemsets.collect().foreach { itemset =>
  println(itemset.items.mkString("[", ",", "]") + ", " + itemset.freq)
}

//设定最小置信度
val minConfidence = 0.8

//产生关联规则
model.generateAssociationRules(minConfidence).collect().foreach { rule =>
  println(
    rule.antecedent.mkString("[", ",", "]")
      + " => " + rule.consequent .mkString("[", ",", "]")
      + ", " + rule.confidence)
}

有关 API 的详细信息，请参考 FPGrowth 的 Scala 文档

整个例子的源码在spark源码包"examples/src/main/scala/org/apache/spark/examples/mllib/SimpleFPGrowth.scala"下

关联规则

AssociationRules(关联规则)类实现了一个并行的规则生成算法去构建产生一个单项作为结果的规则。

Scala

import org.apache.spark.mllib.fpm.AssociationRules
import org.apache.spark.mllib.fpm.FPGrowth.FreqItemset

val freqItemsets = sc.parallelize(Seq(
  new FreqItemset(Array("a"), 15L),
  new FreqItemset(Array("b"), 35L),
  new FreqItemset(Array("a", "b"), 12L)
))

val ar = new AssociationRules()
  .setMinConfidence(0.8)
val results = ar.run(freqItemsets)

results.collect().foreach { rule =>
  println("[" + rule.antecedent.mkString(",")
    + "=>"
    + rule.consequent.mkString(",") + "]," + rule.confidence)
}

有关 API 的详细信息，请参考AssociationRules的 Scala 文档

整个例子的源码在 spark 源码包"examples/src/main/scala/org/apache/spark/examples/mllib/AssociationRulesExample.scala"下

PrefixSpan

PrefixSpan 是一种序列模式挖掘算法， 具体描述见论文 Pei et al., Mining Sequential Patterns by Pattern-Growth: The PrefixSpan Approach。我们推荐读者去读相关的论文去更加深入的理解的序列模式挖掘问题。

spark.mllib 中的 PrefixSpan 要使用到以下的参数：
     • minSupport (最小支持度):最小支持度要求能够反映频繁序列模式。
     • maxPatternLength (最大频繁序列的长度):频繁序列模式的最大长度。结果中将不包含任何频繁序列长度如果超过这个长度的频繁序列。

     •maxLocalProjDBSize (最大单机投影数据库的项数):这个参数应该与你的 spark 集群中 executors 参数设置一致。

例子：

下面的例子介绍了 PrefixSpan 在序列上运行情况(与Pei et al的论文中使用相同的标记)

<(12)3>
<1(32)(12)>
<(12)5>
<6>

PrefixSpan 类实现了 PrefixSpan 算法。调用 PrefixSpan.run 返回存储带有频率的频繁序列的模型 PrefixSpanModel。

Scala

import org.apache.spark.mllib.fpm.PrefixSpan

val sequences = sc.parallelize(Seq(
  Array(Array(1, 2), Array(3)),
  Array(Array(1), Array(3, 2), Array(1, 2)),
  Array(Array(1, 2), Array(5)),
  Array(Array(6))
), 2).cache()
val prefixSpan = new PrefixSpan()
  .setMinSupport(0.5)
  .setMaxPatternLength(5)
val model = prefixSpan.run(sequences)
model.freqSequences.collect().foreach { freqSequence =>
  println(
    freqSequence.sequence.map(_.mkString("[", ", ", "]")).mkString("[", ", ", "]") +
      ", " + freqSequence.freq)
}

有关 API 的详细信息，请参考 PrefixScan 的 Scala 文档和 PrefixScanModel 的 Scala 文档

整个例子的源码在spark源码包 "examples/src/main/scala/org/apache/spark/examples/mllib/PrefixSpanExample.scala" 下。

Evaluation metrics - RDD-based API（评估指标）

原文链接 : http://spark.apache.org/docs/2.1.0/mllib-evaluation-metrics.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=9406579

贡献者 : channingzong 片刻 ApacheCN Apache中文网

spark.mllib 提供了一系列的机器学习算法可以用来从数据中挖掘信息和进行预测。当这些算法被用来建立机器学习模型时，我们同样需要根据实际应用和需求去基于某些标准评估模型的效果。spark.mllib 同样提供了一整套指标用来满足评估机器学习模型效果的要求。

一些机器学习算法可被归类于更广泛的机器学习应用类型，比如分类问题、回归问题、聚类问题等。这里面每一种类型都有成熟的效果评估指标并且这些指标可以从 spark.mllib 中使用，在本章中将会仔细讲述。

Classification model evaluation（分类模型评估）

虽然分类算法的种类有很多，但是分类模型的评估方法的原理都比较相似。在一个有监督分类问题中，对于每一个数据点都会存在一个真实的分类值（标签）和一个模型生成的预测分类值。基于这两个值，每个数据点的结果都可以被归纳为以下四个类别中的一种：

True Positive（TP）- 真实标签为正并且预测正确

True Negative（TN）- 真实标签为负并且预测正确

False Positive（FP）- 真实标签为负但是预测为正

False Negative（FN）- 真实标签为正但是预测为负

上面四种值是大多数分类器评估指标的基础。如果仅仅依靠最基本的准确率（预测是对的还是错的）去评估一个分类器的效果的话，这往往不是一个好的指标，原因在于一个数据集可能各个类别的分布非常不平衡。举例来说，如果一个预测欺诈的模型是根据这样的数据集设计出来的：95%的数据点是非欺诈数据，5%的数据点是欺诈数据，那么一个简单的分类器去预测非欺诈时，不考虑输入的影响，它的准确率将会是95%。因此，我们经常会使用 precision 和 recall 这样的指标，因为他们会将“错误”的类型考虑进去。在大多数应用中，precision 和 recall 会被合并为一个指标来在他们之间进行一些平衡，我们称这个指标为 F-measure。

Binary classification（二项分类）

二项分类器可以用来区分数据集中的元素到两个可能的组中（如欺诈和非欺诈），而且它是多项分类器的一种特殊情况。多数的二项分类评估指标可以被推广使用到多项分类评估中。

Threshold tuning（阈值调优）

很多分类模型对于每一个类实际上会输出一个“分数”（经常是一个概率值），其中较高的分数表示更高的可能性，理解这一点非常重要。在二项分类场景中，模型会为每个类别输出一个概率： 和 。并不是所有场景都会取更高概率的类别，有些场景下模型可能需要进行调整来使得它只有在某个概率非常高的情况下才去预测这个类别（如只有在模型预测的欺诈概率高于90%的情况下才屏蔽这笔信用卡交易）。所以，根据模型输出的概率来确定预测的类别是由预测的阈值来决定的。

调整预测阈值将会改变模型的 precision 和 recall，而且它是模型调优的重要环节。为了能够将 precision、recall 和其他评估指标根据阈值的改变是如何变化的这一情况可视化出来，一种常见的做法是通过阈值的参数化来绘制相互作对比的指标。一种图叫做 P-R 曲线，它会将 (precision, recall) 组成的点根据不同的阈值绘制出来，而另一种叫做 receiver operating characteristic 或者 ROC 曲线，它会绘制出 (recall, false positive rate) 组成的点。

可用的评估指标

指标 | 定义
Precision（精确率） | [picture]
Recall（召回率） | [picture]
F-measure | [picture]
Receiver Operating Characteristic（ROC） | [picture]
Area Under ROC Curve（ROC 曲线下面积 | [picture]
Area Under Precision-Recall Curve（P-R 曲线下面积） | [picture]

举例

下面代码片段阐释了如何加载一个数据集样本，并对数据训练一个二项分类模型，最后通过几个二项评估指标来评价该模型的效果。

Scala

import org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS
import org.apache.spark.mllib.evaluation.BinaryClassificationMetrics
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.util.MLUtils

// 以 LIBSVM 格式加载训练数据
val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_binary_classification_data.txt")

// 分割数据为训练集（60%）和测试集（40%）
val Array(training, test) = data.randomSplit(Array(0.6, 0.4), seed = 11L)
training.cache()

// 运行训练算法来建模
val model = new LogisticRegressionWithLBFGS()
  .setNumClasses(2)
  .run(training)

// 清空预测阈值来使模型返回概率值
model.clearThreshold

// 使用测试集计算原始分数
val predictionAndLabels = test.map { case LabeledPoint(label, features) =>
  val prediction = model.predict(features)
  (prediction, label)
}

// 实例化指标对象
val metrics = new BinaryClassificationMetrics(predictionAndLabels)

// 按阈值分别求 precision
val precision = metrics.precisionByThreshold
precision.foreach { case (t, p) =>
  println(s"Threshold: $t, Precision: $p")
}

// 按阈值分别求 recall
val recall = metrics.recallByThreshold
recall.foreach { case (t, r) =>
  println(s"Threshold: $t, Recall: $r")
}

// Precision-Recall 曲线
val PRC = metrics.pr

// F-measure
val f1Score = metrics.fMeasureByThreshold
f1Score.foreach { case (t, f) =>
  println(s"Threshold: $t, F-score: $f, Beta = 1")
}

val beta = 0.5
val fScore = metrics.fMeasureByThreshold(beta)
f1Score.foreach { case (t, f) =>
  println(s"Threshold: $t, F-score: $f, Beta = 0.5")
}

// AUPRC
val auPRC = metrics.areaUnderPR
println("Area under precision-recall curve = " + auPRC)

// 计算 ROC 和 PR 曲线中使用的阈值
val thresholds = precision.map(_._1)

// ROC 曲线
val roc = metrics.roc

// AUROC
val auROC = metrics.areaUnderROC
println("Area under ROC = " + auROC)

 请参考 LogisticRegressionWithLBFGS Scala docs 和 BinaryClassificationMetrics Scala docs 以获取更多 API 相关细节

在Spark开源项目的“examples/src/main/scala/org/apache/spark/examples/mllib/BinaryClassificationMetricsExample.scala”路径下可以找到完整的例子代码。

Multiclass classification（多项分类）

多项分类描述了这样一种分类问题，对于每个数据点都有种可能的分类标签（当时为二项分类问题）。举例来说，区分手写样本为0到9的数字就是一种有10个可能分类的多项分类问题。

对于多类别评估指标，积极和消极的概念跟二项分类略有不同。预测值和实际分类仍然可以是积极的或消极的，但是它们必须被归类到某种特定的类别中。每一个类别标签和预测值都会分配给多个类别中的一个，所以它对于分配的类别来说是积极的，但对于其他类别则是消极的。因此，true positive 发生在预测值和实际分类相吻合的时候，而 true negative 发生在预测值和实际分类都不属于一个类别的时候。由于这个认识，对于一个数据样本而言存在多个 true negative。根据之前对于 positive 和 negative 的定义扩展到 false negative 和 false positive 时就相对直接了。

Label based metrics（基于标签的指标）

相对于只有两种可能分类的二项分类问题，多项分类问题有多种可能的类别，所以基于类别的评估指标概念则被引入。基于所有类别的 precision 的准确率计算方式为，任何类别被预测正确的次数（true positive）除以数据点的数量。基于一个类别的 precision 的计算方式为，某个特定的类别被预测正确的次数除以这个类别在结果中出现的次数。

可用的评估指标 

定义类别或标签序列为：

真实的输出向量包含个元素：

对于个元素，一个多类别预测算法会产生一个预测向量：

一个修正的 delta 函数将会证明是有用的：

指标 | 定义
Confusion Matrix（混淆矩阵） | [picture]
Accuracy（准确率） | [picture]
Precision by label（标签 precision） | [picture]
Recall by label（标签 recall） | [picture]
F-measure by label（标签 F-measure） | [picture]
Weighted precision（加权 precision） | [picture]
Weighted recall（加权 recall） | [picture]
Weighted F-measure（加权 F-measure） | [picture]

举例

下面代码片段阐释了如何加载一个数据集样本，并对数据训练一个多项分类模型，最后通过多项分类评估指标评估模型效果。

Scala

import org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS
import org.apache.spark.mllib.evaluation.MulticlassMetrics
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.util.MLUtils

// 以 LIBSVM 格式加载训练数据
val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_multiclass_classification_data.txt")

// 分割数据为训练集（60%）和测试集（40%）
val Array(training, test) = data.randomSplit(Array(0.6, 0.4), seed = 11L)
training.cache()

// 运行训练算法来建模
val model = new LogisticRegressionWithLBFGS()
  .setNumClasses(3)
  .run(training)

// 使用测试集计算原始分数
val predictionAndLabels = test.map { case LabeledPoint(label, features) =>
  val prediction = model.predict(features)
  (prediction, label)
}

// 实例化指标对象
val metrics = new MulticlassMetrics(predictionAndLabels)

// 混淆矩阵
println("Confusion matrix:")
println(metrics.confusionMatrix)

// 总体统计结果
val accuracy = metrics.accuracy
println("Summary Statistics")
println(s"Accuracy = $accuracy")

// 基于类别计算 Precision
val labels = metrics.labels
labels.foreach { l =>
  println(s"Precision($l) = " + metrics.precision(l))
}

// 基于类别计算 Recall
labels.foreach { l =>
  println(s"Recall($l) = " + metrics.recall(l))
}

// 基于类别计算 False positive rate
labels.foreach { l =>
  println(s"FPR($l) = " + metrics.falsePositiveRate(l))
}

// 基于类别计算 F-measure
labels.foreach { l =>
  println(s"F1-Score($l) = " + metrics.fMeasure(l))
}

// 加权统计结果
println(s"Weighted precision: ${metrics.weightedPrecision}")
println(s"Weighted recall: ${metrics.weightedRecall}")
println(s"Weighted F1 score: ${metrics.weightedFMeasure}")
println(s"Weighted false positive rate: ${metrics.weightedFalsePositiveRate}")

参考 MulticlassMetrics Scala docs 来获取更多 API 相关细节

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/mllib/BinaryClassificationMetricsExample.scala”中查找完整示例代码。

Multilabel classification （多标签分类）

多标签分类问题指的是将一个数据集中的每一个样本映射到一组分类标签中去。在这一类分类问题中，各个标签所包含的样本并不是互斥的。举例来说，将一组新闻稿分类到不同的主题中去，一篇稿子可能既属于科技类，又属于政治类。

由于标签不是彼此互斥，预测值和真实的分类标签就成为了标签集合的向量，而非标签的向量。于是多标签评估指标就可以从基本的 precision、recall 等概念扩展到对集合的操作上。例如，对于某个类的 true positive 就发生在当这个类别存在于某个数据点的预测值集合中，并且也存在于它的真实类别集合中时。

可用指标

我们定义一个集合包含个文档

定义为全体标签集合，为全体预测结果集合，L_i 和 P_i 分别为文档 d_i 的标签集合和预测值集合。

那么所有独立标签的集合则表示为

对于集合 A 的标识函数将会被使用

指标 | 定义
Precision | [picture]
Recall | [picture]
Accuracy | [picture]
Precision by label | [picture]
Recall by label | [picture]
F1-measure by label | [picture]
Hamming Loss | [picture]
Subset Accuracy | [picture]
F1 Measure | [picture]
Micro precision | [picture]
Micro recall | [picture]
Micro F1 Measure | [picture]

举例

以下代码片段阐释了如何去评估一个多标签分类器的效果。下面的多标签分类例子中使用了伪造的预测数据和标签数据。

对于每一篇文章的预测结果为：

文章 0 - 预测值为 0, 1 - 类别为 0, 2

文章 1 - 预测值为 0, 2 - 类别为 0, 1

文章 2 - 预测值为空 - 类别为 0

文章 3 - 预测值为 2 - 类别为 2

文章 4 - 预测值为 2, 0 - 类别为 2, 0

文章 5 - 预测值为 0, 1, 2 - 类别为 0, 1

文章 6 - 预测值为 1 - 类别为 1, 2

对于每一类别的预测结果为：

类别 0 - 文章 0, 1, 4, 5（一共 4 篇）

类别 1 - 文章 0, 5, 6（一共 3 篇）

类别 2 - 文章 1, 3, 4, 5（一共 4 篇）

每一类别实际包含的文章为：

类别 0 - 文章 0, 1, 2, 4, 5（一共 5 篇）

类别 1 - 文章 1, 5, 6（一共 3 篇）

类别 2 - 文章 0, 3, 4, 6（一共 4 篇）

Scala

import org.apache.spark.mllib.evaluation.MultilabelMetrics
import org.apache.spark.rdd.RDD

val scoreAndLabels: RDD[(Array[Double], Array[Double])] = sc.parallelize(
  Seq((Array(0.0, 1.0), Array(0.0, 2.0)),
    (Array(0.0, 2.0), Array(0.0, 1.0)),
    (Array.empty[Double], Array(0.0)),
    (Array(2.0), Array(2.0)),
    (Array(2.0, 0.0), Array(2.0, 0.0)),
    (Array(0.0, 1.0, 2.0), Array(0.0, 1.0)),
    (Array(1.0), Array(1.0, 2.0))), 2)

// 实例化指标对象
val metrics = new MultilabelMetrics(scoreAndLabels)

// 总体统计结果
println(s"Recall = ${metrics.recall}")
println(s"Precision = ${metrics.precision}")
println(s"F1 measure = ${metrics.f1Measure}")
println(s"Accuracy = ${metrics.accuracy}")

// 基于每一标签的统计结果
metrics.labels.foreach(label =>
  println(s"Class $label precision = ${metrics.precision(label)}"))
metrics.labels.foreach(label => println(s"Class $label recall = ${metrics.recall(label)}"))
metrics.labels.foreach(label => println(s"Class $label F1-score = ${metrics.f1Measure(label)}"))

// Micro stats
println(s"Micro recall = ${metrics.microRecall}")
println(s"Micro precision = ${metrics.microPrecision}")
println(s"Micro F1 measure = ${metrics.microF1Measure}")

// Hamming loss
println(s"Hamming loss = ${metrics.hammingLoss}")

// Subset accuracy
println(s"Subset accuracy = ${metrics.subsetAccuracy}")

参考  MultilabelMetrics Scala docs 以获取更多 API 相关细节

在 Spark 项目的“examples/src/main/scala/org/apache/spark/examples/mllib/MultiLabelMetricsExample.scala”路径下可以找到完整示例代码。

Ranking system（排名系统）

排名算法（通常被作为推荐系统算法）的作用是基于一些训练数据给用户返回一个相关物品或文章的集合。相关性的定义会根据不同的应用场景而有所差异。排名系统评估指标则用于在不同的场景中量化排名或者推荐的效果。某些指标会把系统推荐的文章集合跟实际相关的文章集合做对比，而另外一些指标会显式地结合数值打分。

可用的指标

一个排名系统通常会处理一个由个用户组成的集合

每一个用户有一个由个真正相关的文章组成的集合

并且还有一个由个推荐文章组成的有序集合，按照相关度降序排序

排序系统的目标就是给每个用户提供与其相关度最高的若干个文章集合。集合的相关度以及算法的有效性可以用下列评估指标进行度量。

度量 | 定义 | 说明
Precision at k | [picture] | Precision at k 是用来度量对所有用户来说，平均前k个推荐的文章里有几个在真正相关的文章集合中出现。这个指标不考虑推荐的顺序问题。
Mean Average Precision | [picture] | MAP 是用来度量推荐的文章集合里有几个出现在真正相关的文章集合中，并且考虑推荐的顺序性（比如文章的相关性越高，它出错的惩罚就越大）。
Normalized Discounted Cumulative Gain | [picture] | NDCG at k 是用来度量对所有用户来说，平均前k个推荐的文章里有几个在真正相关的文章集合中出现。跟 Precision at k 不同，这个指标考虑推荐的顺序性（假设文章按照相关性降序排序）。

举例

下面代码片段阐释了如何加载一个数据集样本，并使用数据训练一个交替最小二乘法推荐算法模型，最后通过几个排名评估指标来评价推荐系统效果。下面简单总结了一下使用的方法。

对于影片的打分在 1 - 5 分区间：

5: 必看

4: 会喜欢

3: 还可以

2: 不怎么样

1: 烂片

那么如果预测的打分小于3分，我们将不会推荐这部影片。我们使用下面映射关系来表示置信分数：

5 -> 2.5

4 -> 1.5

3 -> 0.5

2 -> -0.5

1 -> -1.5

这个映射关系表示没有被用户察觉到的影片通常在“还可以”和“不怎么样”之间。权重0在这里的语义表示“跟这个影片从来没有过任何互动”。

Scala

import org.apache.spark.mllib.evaluation.{RankingMetrics, RegressionMetrics}
import org.apache.spark.mllib.recommendation.{ALS, Rating}

// 读取打分数据
val ratings = spark.read.textFile("data/mllib/sample_movielens_data.txt").rdd.map { line =>
  val fields = line.split("::")
  Rating(fields(0).toInt, fields(1).toInt, fields(2).toDouble - 2.5)
}.cache()

// 将打分映射到0或者1，1表示影片应该被推荐
val binarizedRatings = ratings.map(r => Rating(r.user, r.product,
  if (r.rating > 0) 1.0 else 0.0)).cache()

// 评分的统计结果
val numRatings = ratings.count()
val numUsers = ratings.map(_.user).distinct().count()
val numMovies = ratings.map(_.product).distinct().count()
println(s"Got $numRatings ratings from $numUsers users on $numMovies movies.")

// 建模
val numIterations = 10
val rank = 10
val lambda = 0.01
val model = ALS.train(ratings, rank, numIterations, lambda)

// 定义一个函数将打分映射到0和1区间
def scaledRating(r: Rating): Rating = {
  val scaledRating = math.max(math.min(r.rating, 1.0), 0.0)
  Rating(r.user, r.product, scaledRating)
}

// 获取排序后对于每个用户前十个预测值，并映射到0和1区间
val userRecommended = model.recommendProductsForUsers(10).map { case (user, recs) =>
  (user, recs.map(scaledRating))
}

// 假设任何被用户打分为3分或更高（映射到1）的影片为一个相关的影片
// 并与前十个最相关的影片做对比
val userMovies = binarizedRatings.groupBy(_.user)
val relevantDocuments = userMovies.join(userRecommended).map { case (user, (actual,
predictions)) =>
  (predictions.map(_.product), actual.filter(_.rating > 0.0).map(_.product).toArray)
}

// 实例化一个评估指标模型
val metrics = new RankingMetrics(relevantDocuments)

// Precision at K
Array(1, 3, 5).foreach { k =>
  println(s"Precision at $k = ${metrics.precisionAt(k)}")
}

// Mean average precision
println(s"Mean average precision = ${metrics.meanAveragePrecision}")

// Normalized discounted cumulative gain
Array(1, 3, 5).foreach { k =>
  println(s"NDCG at $k = ${metrics.ndcgAt(k)}")
}

// 获得每个数据点的 Prediction
val allPredictions = model.predict(ratings.map(r => (r.user, r.product))).map(r => ((r.user,
  r.product), r.rating))
val allRatings = ratings.map(r => ((r.user, r.product), r.rating))
val predictionsAndLabels = allPredictions.join(allRatings).map { case ((user, product),
(predicted, actual)) =>
  (predicted, actual)
}

// 使用回归评估指标获得 RMSE
val regressionMetrics = new RegressionMetrics(predictionsAndLabels)
println(s"RMSE = ${regressionMetrics.rootMeanSquaredError}")

// R-squared
println(s"R-squared = ${regressionMetrics.r2}")

参考 RegressionMetrics Scala docs 和 RankingMetrics Scala docs 获取更多 API 相关细节

在 Spark 项目的“examples/src/main/scala/org/apache/spark/examples/mllib/RankingMetricsExample.scala”路径下可以找到完整示例代码。

Regression model evaluation（回归模型评估）

Regression analysis（回归分析）被用于从一系列自变量中预测连续的因变量的场景中。

可用的指标

指标 | 定义
Mean Squared Error (MSE) | [picture]
Root Mean Squared Error (RMSE) | [picture]
Mean Absolute Error (MAE) | [picture]
Coefficient of Determination | [picture]
Explained Variance | [picture]

举例

下面代码片段阐释了如何加载一个数据集样本，并使用数据训练一个线性回归算法模型，最后通过几个回归评估指标来对模型效果进行评价的过程。

Scala

import org.apache.spark.mllib.evaluation.RegressionMetrics
import org.apache.spark.mllib.linalg.Vector
import org.apache.spark.mllib.regression.{LabeledPoint, LinearRegressionWithSGD}

// 加载数据
val data = spark
  .read.format("libsvm").load("data/mllib/sample_linear_regression_data.txt")
  .rdd.map(row => LabeledPoint(row.getDouble(0), row.get(1).asInstanceOf[Vector]))
  .cache()

// 建模
val numIterations = 100
val model = LinearRegressionWithSGD.train(data, numIterations)

// 获取预测值
val valuesAndPreds = data.map{ point =>
  val prediction = model.predict(point.features)
  (prediction, point.label)
}

// 实例化一个回归指标对象
val metrics = new RegressionMetrics(valuesAndPreds)

// Squared error
println(s"MSE = ${metrics.meanSquaredError}")
println(s"RMSE = ${metrics.rootMeanSquaredError}")

// R-squared
println(s"R-squared = ${metrics.r2}")

// Mean absolute error
println(s"MAE = ${metrics.meanAbsoluteError}")

// Explained variance
println(s"Explained variance = ${metrics.explainedVariance}")

请参考 RegressionMetrics Scala docs 来获取更多 API 相关细节

在 Spark 项目的“examples/src/main/scala/org/apache/spark/examples/mllib/RegressionMetricsExample.scala”路径下可以找到完整示例代码。

PMML model export - RDD-based API（PMML模型导出）

原文链接 : http://spark.apache.org/docs/2.1.0/mllib-pmml-model-export.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=10027702

贡献者 : 羊三 片刻 ApacheCN Apache中文网

spark.mllib支持的模型

Spark.mllib支持模型导出到Predictive Model Markup Language（预测模型标记语言）。

下表列出了可以导出到PMML的spark.mllib模型及其等效的PMML模型。

Spark.mllib模型 | PMML模型
KMeansModel | ClusteringModel
LinearRegressionModel | RegressionModel (functionName="regression")
RidgeRegressionModel | RegressionModel (functionName="regression")
LassoModel | RegressionModel (functionName="regression")
SVMModel | RegressionModel (functionName="classification" normalizationMethod="none")
Binary LogisticRegressionModel | RegressionModel (functionName="classification" normalizationMethod="logit")

例子

将支持的模型（见上表）导出到PMML，只需调用model.toPMML。

除了将PMML模型导出为String（model.toPMML，如上例所示），您也可以将PMML模型导出为其他格式。

这里是一个建立KMeansModel并以PMML格式打印出来的完整示例：

Scala

import org.apache.spark.mllib.clustering.KMeans
import org.apache.spark.mllib.linalg.Vectors

// 加载并解析数据
val data = sc.textFile("data/mllib/kmeans_data.txt")
val parsedData = data.map(s =>Vectors.dense(s.split(' ').map(_.toDouble))).cache()

// 使用KMeans将数据分成两类
val numClusters = 2
val numIterations = 20
val clusters =KMeans.train(parsedData, numClusters, numIterations)

// 以PMML格式导出一个字符串
println("PMML Model:\n" + clusters.toPMML)
// 将模型导出为PMML格式的本地文件
clusters.toPMML("/tmp/kmeans.xml")
// 以PMML格式将模型导出到分布式文件系统的目录上
clusters.toPMML(sc, "/tmp/kmeans")
// 将模型导出为PMML格式的输出流
clusters.toPMML(System.out)

有关API的详细信息，请参阅KMeans Scala文档和Vectors Scala文档

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/mllib/PMMLModelExportExample.scala”中查找完整示例代码。

对于不支持的模型，您将找不到.toPMML方法，否则将抛出IllegalArgumentException异常。

Optimization - RDD-based API（最优化）

原文链接 : http://spark.apache.org/docs/2.1.0/mllib-optimization.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=10027710

贡献者 : 羊三 片刻 ApacheCN Apache中文网

Mathematical description（数学描述）

Gradient descent（梯度下降）

解决格式为的优化问题的最简单的方法是梯度下降。这种一阶优化方法（包括梯度下降及其随机变量）非常适合于大规模分布式计算。

梯度下降方法旨在通过迭代，沿最陡下降的方向迭代地获得函数的局部最小值，该最陡下降的方向是当前点处的函数的导数（称为梯度）的负值，即当前参数值 。

如果目标函数f在所有参数上都不可微分，但仍然是凸的，则子梯度是梯度的自然泛化，并且承担步骤方向的作用。 在任何情况下，计算f的梯度或子梯度的成本是很高的 - 它需要完整的遍历完整的数据集，以便计算所有损失项的权重。

Stochastic gradient descent （SGD-随机梯度下降）

将目标函数f写为和的优化问题特别适合于使用随机梯度下降（SGD）求解。 在我们的例子中，是在监督机器学习中常用的优化方案。

（1）

这是很正常的，因为损失是作为每个数据点的个人损失的平均值得出的。

随机子梯度是一个向量的随机选择，使得在预期中，我们获得了原始目标函数的真实子梯度。 随机抽取一个数据点，我们得到关于w的（1）的随机子梯度，如下：

其中是由第i个数据点确定的损失函数的一部分的子梯度，即。 此外，是正则化器的子梯度。

不依赖于选择哪个随机数据点。 显然，期望的随机选择。 我们有是原始目标f的子梯度，即是。

运行SGD现在只是走向负随机子梯度f'w的方向 ，即

Step-size（步长）： 参数 是步长，在默认实现中，选择与迭代计数器的平方根相减，即  在第t次迭代中，输入参数  s= stepSize。请注意，选择SGD方法的最佳步长通常在实践中是微妙的，并且是积极研究的主题。

Gradients（梯度）：在spark.mllib中实现的机器学习方法的（子）梯度表在分类和回归部分可用。

Proximal Updates（更新近端）：作为在步进方向上仅使用正则化器的子梯度R'(w)的替代方案，可以通过使用近端算子来获得一些改进的更新。 对于L1正则化器，近端运算符由软阈值给出，如L1Updater中实现的。

Update schemes for distributed SGD（分布式SGD更新方案）

GradientDescent 中的SGD实现使用数据示例的简单（分布式）抽样。 我们记得优化问题 (1) 的损失部分是，因此将是真（子）梯度。 由于这将需要访问完整的数据集，参数miniBatchFraction指定要使用的完整数据的哪一部分。 该子集上的梯度的平均值，即：

是随机梯度。 这里SS是大小 | S | = miniBatchFraction⋅n 的采样子集。
在每次迭代中，通过分布式数据集(RDD) 的采样以及每个工作机器的部分结果的总和的计算由标准火花程序执行。

如果将小分数分数的分数设置为1（默认值），则每次迭代中生成的步骤都是精确（子）梯度下降。 在这种情况下，在使用的步骤方向上没有随机性和无变化。 另一方面，如果选择非常小的miniBatchFraction，使得仅对一个点进行采样，即  | S | = miniBatchFraction⋅n=1，则算法等同于标准SGD。 在这种情况下，步长方向取决于点的均匀随机抽样。

Limited-memory BFGS (L-BFGS)

L-BFGS 是用于解决  形式的优化问题的准牛顿法中的优化算法。 L-BFGS方法将本地目标函数近似为二次，而不用评估目标函数的第二偏导数来构造Hessian矩阵。 Hessian矩阵由先前的梯度评估近似，因此在牛顿方法中明确计算Hessian矩阵时，没有垂直可伸缩性问题（训练特征的数量）。 因此，与其他一阶优化相比，L-BFGS经常实现rapider融合。

Choosing an Optimization Method（选择优化方法）

线性方法在内部使用优化，而spark.mllib中的一些线性方法支持SGD和L-BFGS。 根据目标函数的属性，不同的优化方法可以有不同的收敛保证，我们无法涵盖这里的文献。 一般来说，当L-BFGS可用时，我们建议使用它代替SGD，因为L-BFGS趋向于更快地收敛（以较少的迭代）。

在MLlib中的实现

Gradient descent and stochastic gradient descent（梯度下降和随机梯度下降）

包括作为MLlib中的低级原函数的随机子梯度下降（SGD）的梯度下降方法，其中开发各种ML算法，参见线性方法部分。

SGD类GradientDescent可以设置以下参数：

Gradient 渐变是计算正在优化的功能的随机梯度的类，即相对于单个训练示例，以当前参数值计算。 MLlib包括常见损失函数的梯度类，例如hinge, logistic, least-squares（最小二乘法）。梯度类将训练样本，其标签和当前参数值作为输入。

更新器是对于给定的损耗部分的梯度，执行实际的梯度下降步骤，即更新每个迭代中的权重。更新者还负责从正规化部分执行更新。 MLlib包括没有正则化的情况的更新程序，以及L1和L2正则化程序。

stepSize是一个标量值，表示梯度下降的初始步长。 MLlib中的所有更新者都使用等于  的第t步的步长。

numIterations是要运行的迭代次数。

regParam是使用L1或L2正则化时的正则化参数。

miniBatchFraction是在每次迭代中采样的总数据的一部分，以计算梯度方向。

采样仍然需要遍历整个RDD，因此减少miniBatchFraction可能无法加快优化。当梯度计算成本高时，用户将会看到最大的加速，因为只有选定的样本用于计算梯度。

L-BFGS

L-BFGS目前只是MLlib中的一个低级优化原语。 如果要在各种ML算法（如线性回归和逻辑回归）中使用L-BFGS，则必须将目标函数的渐变和更新器传递给优化器，而不是使用诸如LogisticRegressionWithSGD之类的培训API。 参见下面的例子。 将在下一个版本中解决。

使用L1Updater的L1正则化将不起作用，因为L1Updater中的软阈值逻辑被设计用于梯度下降。 请参阅开发人员的说明。

L-BFGS方法LBFGS.runLBFGS具有以下参数：

Gradient是一个类，它以当前参数值计算正在优化的目标函数的梯度，即相对于单个训练示例。 MLlib包括常见损失函数的梯度类，例如铰链，后勤，最小二乘法。梯度类将训练样本，其标签和当前参数值作为输入。

更新器是一种计算L-BFGS正则化部分的目标函数的梯度和损耗的类。 MLlib包括没有正则化的情况的更新程序，以及L2正则化程序。

numCorrections是L-BFGS更新中使用的更正次数。建议10。

maxNumIterations是L-BFGS可以运行的最大迭代次数。

regParam是使用正则化时的正则化参数。

converTol控制当L-BFGS被认为收敛时仍然允许多少相对变化。这必须是非负的。较低的值较不容忍，因此通常会导致更多的迭代运行。该值考察了Breeze LBFGS中的平均改善度和梯度范数。

返回是包含两个元素的元组。 第一个元素是包含每个要素权重的列矩阵，第二个元素是包含为每次迭代计算的损失的数组。

这是使用L-BFGS优化器来训练二元逻辑回归与L2正则化的一个例子。

Scala

import org.apache.spark.mllib.classification.LogisticRegressionModel
import org.apache.spark.mllib.evaluation.BinaryClassificationMetrics
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.optimization.{LBFGS, LogisticGradient, SquaredL2Updater}
import org.apache.spark.mllib.util.MLUtils

val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")
val numFeatures = data.take(1)(0).features.size

// Split data into training (60%) and test (40%).
val splits = data.randomSplit(Array(0.6, 0.4), seed = 11L)

// Append 1 into the training data as intercept.
val training = splits(0).map(x => (x.label, MLUtils.appendBias(x.features))).cache()

val test = splits(1)

// Run training algorithm to build the model
val numCorrections = 10
val convergenceTol = 1e-4
val maxNumIterations = 20
val regParam = 0.1
val initialWeightsWithIntercept = Vectors.dense(new Array[Double](numFeatures + 1))

val (weightsWithIntercept, loss) = LBFGS.runLBFGS(
  training,
  new LogisticGradient(),
  new SquaredL2Updater(),
  numCorrections,
  convergenceTol,
  maxNumIterations,
  regParam,
  initialWeightsWithIntercept)

val model = new LogisticRegressionModel(
  Vectors.dense(weightsWithIntercept.toArray.slice(0, weightsWithIntercept.size - 1)),
  weightsWithIntercept(weightsWithIntercept.size - 1))

// Clear the default threshold.
model.clearThreshold()

// Compute raw scores on the test set.
val scoreAndLabels = test.map { point =>
  val score = model.predict(point.features)
  (score, point.label)
}

// Get evaluation metrics.
val metrics = new BinaryClassificationMetrics(scoreAndLabels)
val auROC = metrics.areaUnderROC()

println("Loss of each step in training process")
loss.foreach(println)
println("Area under ROC = " + auROC)

有关API的详细信息，请参阅  LBFGS Scala docs 和 SquaredL2Updater Scala docs 。

在Spark repo中的“examples/src/main/scala/org/apache/spark/examples/mllib/LBFGSExample.scala”中查找完整示例代码。

Developer’s notes（开发者笔记）

由于Hessian近似来自先前的梯度评估，所以在优化过程中不能改变目标函数。 结果，随机L-BFGS不会通过使用miniBatch天真地工作; 因此，直到我们有了更深入的了解，我们才不提供这个。

Updater是一个最初设计用于梯度的类，它计算实际的梯度下降步长。 然而，我们可以忽略L-BFGS正则化的目标函数的梯度和损失，忽略仅适用于梯度的逻辑部分，例如自适应步长的东西。 我们将把它重构为正则化程序，以替换更新程序，以便稍后在正则化和步骤更新之间分离逻辑。

GraphX（图形处理）

原文链接 : http://spark.apache.org/docs/latest/graphx-programming-guide.html

译文链接 : http://cwiki.apachecn.org/pages/viewpage.action?pageId=2883742

贡献者 : 漫步 片刻 ApacheCN Apache中文网

概述

GraphX 是 Spark 图表和图形并行计算的新组件。GraphX 延伸 Spark RDD 通过引入新的图形的抽象：计算与连接到每个顶点和边缘性的向量。以支持图形计算，GraphX 公开了一组基本的操作符（例如  subgraph, joinVertices 和 aggregateMessages）以及一个优化高阶API。此外，GraphX 包括的图形越来越多的收集 algorithms 和 builders ，以简化图形分析任务。

从 Spark 1.1 的升级

GraphX 在Spark 1.2 中的API面向用户做很少改变：

为了提高性能，我们已经推出了新版本  mapReduceTriplets 叫做 aggregateMessages这需要从前面返回的消息 mapReduceTriplets（通过回调EdgeContext返回值），而不是。我们不支持 mapReduceTriplets并鼓励用户详细的阅读变更指南。

在 Spark 1.0 和 1.1 中 EdgeRDD 从 EdgeRDD[ED]至 EdgeRDD[ED, VD]优化了一些缓存。我们已经发现，因为一个更优雅的解决方案，并已更自然的恢复类型签名到 EdgeRDD[ED]类型。

开始

开始时，首先需要在项目中导入 Spark和 GraphX 的依赖，具体如下：

import org.apache.spark._
import org.apache.spark.graphx._
// To make some of the examples work we will also need RDD
import org.apache.spark.rdd.RDD

如果不使用 Spark shell，你还需要一个 SparkContext。要了解更多关于 Spark 参考 Spark 快速入门指南。

Graph 的属性

该属性是一个每个顶点和边缘用户定义的对象的有向图。一个向图是一个由边缘和点计算组成的关系图。支持平行边缘简化了建模场景，其中可以有相同的顶点之间的多个关系（例如，同事和朋友）。每个顶点由唯一的一个64 位标识符键值（VertexID）。GraphX 不会对顶点标识符的任何排序约束。同样，边缘具有相应的源和目的地顶点标识符。

该物理图形参数是顶点（VD）和边缘（ED）类型。这些类型是分别与每个顶点和边相关联的对象

GraphX 优化顶点和边的类型的表达，当他们的原始数据类型（例如，int,double 等等）通过将它们存储在专有的阵列,可以减小内存占用。

在某些情况下，可能希望具有不同的属性类型的顶点在相同的图中。这可以通过继承来完成。例如，以用户和产品型号为两个图，我们不妨做到以下几点：

class VertexProperty()
case class UserProperty(val name: String) extends VertexProperty
case class ProductProperty(val name: String, val price: Double) extends VertexProperty
// The graph might then have the type:
var graph: Graph[VertexProperty, String] = null

类似 RDD数据集合，图的产生是通过分布式和容错，并且不可变的。更改值或图形的结构是由生产具有所期望的变化的新图形来实现的。注意原始图主要部分（即，未受影响的结构，属性和索引）被重用，在新图形减少这个固有功能数据结构的属性。图表跨采用一系列顶点划分发执行。与RDD数据集合不同，图中的每个分区可以在不同的机器上在发生故障的情况下重新创建。

按道理属性图对应的一对类型的集合（RDD）的编码为每个顶点和边的属性。其结果是，该图类包含的成员访问该图的顶点和边：

class Graph[VD, ED] {
  val vertices: VertexRDD[VD]
  val edges: EdgeRDD[ED]
}

这些类 VertexRDD[VD]和 EdgeRDD[ED]延伸，并分别经过优化的 RDD[(VertexID, VD)]和 RDD[Edge[ED]]。二者 VertexRDD[VD] 和 EdgeRDD[ED]提供每个图的计算和优化内部内置附加功能。我们在上一节更详细的讨论 VertexRDD 和 EdgeRDDAPI ( 顶点和边RDDS) ,现在他们可以被看作仅仅是形式的RDD集合： RDD[(VertexID, VD)]和 RDD[Edge[ED]]。

Graph 属性示例

假设我们想要构建每一个 GraphX 并组成一个图。 顶点属性可能包含用户名和职业。 我们可以注明用户和职业之间的关系:

生成的 graph 类型申明:

val userGraph: Graph[(String, String), String]

从原始文件有许多方法来构造一个属性图,抽样,甚至综合的,这些部分在 graph builders  里有更详细地讨论 。 最通用的方法是使用 Graph对象 。 例如下面的代码是从抽样的集合构造图:

// Assume the SparkContext has already been constructed
val sc: SparkContext
// Create an RDD for the vertices
val users: RDD[(VertexId, (String, String))] =
  sc.parallelize(Array((3L, ("rxin", "student")), (7L, ("jgonzal", "postdoc")),
                       (5L, ("franklin", "prof")), (2L, ("istoica", "prof"))))
// Create an RDD for edges
val relationships: RDD[Edge[String]] =
  sc.parallelize(Array(Edge(3L, 7L, "collab"),    Edge(5L, 3L, "advisor"),
                       Edge(2L, 5L, "colleague"), Edge(5L, 7L, "pi")))
// Define a default user in case there are relationship with missing user
val defaultUser = ("John Doe", "Missing")
// Build the initial Graph
val graph = Graph(users, relationships, defaultUser)

在上面的例子中,我们使用 edge类。 边 (Edge) 有一个 srcId 和一个 dstId 源和目标顶点对应标识符。 此外, attr是一个边缘属性。 

我们可以把图分解为相应的顶点和边 graph.vertices 和 graph.edges 

val graph: Graph[(String, String), String] // Constructed from above
// Count all users which are postdocs
graph.vertices.filter { case (id, (name, pos)) => pos == "postdoc" }.count
// Count all the edges where src > dst
graph.edges.filter(e => e.srcId > e.dstId).count

注意 graph.vertices 返回一个 VertexRDD[(字符串,字符串)] 延伸 ((VertexID(String,String))) 所以我们使用 scala  解析元组。 另一方面, graph.edges 返回一个 EdgeRDD 包含 边缘(字符串) 对象。 我们也可以使用案例类型构造函数如以下: 

graph.edges.filter { case Edge(src, dst, prop) => src > dst }.count

除了属性图的顶点和边,GraphX 也暴露了三个一组的观点。 通过点、边、边属性，这三种属性抽样(EdgeTriplet[VD ED]] 包含的实例 EdgeTriplet 类。 也可以用SQL表达式表示: 

SELECT src.id, dst.id, src.attr, e.attr, dst.attr
FROM edges AS e LEFT JOIN vertices AS src, vertices AS dst
ON e.srcId = src.Id AND e.dstId = dst.Id

或图形:

EdgeTriplet 类通过添加 srcAttr 和 dstAttr 成员扩展了 边缘类，分别包含源和目标属性。 我们可以使用 三个一组的一个图形来呈现一个字符串集合描述用户之间的关系。

val graph: Graph[(String, String), String] // Constructed from above
// Use the triplets view to create an RDD of facts.
val facts: RDD[String] =
  graph.triplets.map(triplet =>
    triplet.srcAttr._1 + " is the " + triplet.attr + " of " + triplet.dstAttr._1)
facts.collect.foreach(println(_))

graph 操作

正如 rdds 有基本的map, filter, reduceByKey操作 ,操作图的属性也是对集合的操作,改变性质和结构,通过操作用户自定义函数产生新的图形。对图的属性的操作定义为 GraphOps 。 然而,由于值得一提的是Scala 对 GraphOps 会自动提供的成员属性图。 例如,我们可以 计算每个顶点的入度(在GraphOps中定义)如以下: 

val graph: Graph[(String, String), String]
// Use the implicit GraphOps.inDegrees operator
val inDegrees: VertexRDD[Int] = graph.inDegrees

区分核心图形操作的原因 GraphOps 是 在未来能够支持不同的图形表示。 每个图表示必须 提供核心业务和重用的实现中定义的许多有用的操作 GraphOps 。

操作摘要

下面是 Graph和 GraphOps的快速摘要以及 graph的简单成员。请注意，某些功能的签名已被简化（如默认参数和去掉了类型限制）和一些更高级的功能已被删除，请参考正式的API文档操作名单。

/** Summary of the functionality in the property graph */
class Graph[VD, ED] {
  // Information about the Graph ===================================================================
  val numEdges: Long
  val numVertices: Long
  val inDegrees: VertexRDD[Int]
  val outDegrees: VertexRDD[Int]
  val degrees: VertexRDD[Int]
  // Views of the graph as collections =============================================================
  val vertices: VertexRDD[VD]
  val edges: EdgeRDD[ED]
  val triplets: RDD[EdgeTriplet[VD, ED]]
  // Functions for caching graphs ==================================================================
  def persist(newLevel: StorageLevel = StorageLevel.MEMORY_ONLY): Graph[VD, ED]
  def cache(): Graph[VD, ED]
  def unpersistVertices(blocking: Boolean = true): Graph[VD, ED]
  // Change the partitioning heuristic  ============================================================
  def partitionBy(partitionStrategy: PartitionStrategy): Graph[VD, ED]
  // Transform vertex and edge attributes ==========================================================
  def mapVertices[VD2](map: (VertexID, VD) => VD2): Graph[VD2, ED]
  def mapEdges[ED2](map: Edge[ED] => ED2): Graph[VD, ED2]
  def mapEdges[ED2](map: (PartitionID, Iterator[Edge[ED]]) => Iterator[ED2]): Graph[VD, ED2]
  def mapTriplets[ED2](map: EdgeTriplet[VD, ED] => ED2): Graph[VD, ED2]
  def mapTriplets[ED2](map: (PartitionID, Iterator[EdgeTriplet[VD, ED]]) => Iterator[ED2])
    : Graph[VD, ED2]
  // Modify the graph structure ====================================================================
  def reverse: Graph[VD, ED]
  def subgraph(
      epred: EdgeTriplet[VD,ED] => Boolean = (x => true),
      vpred: (VertexID, VD) => Boolean = ((v, d) => true))
    : Graph[VD, ED]
  def mask[VD2, ED2](other: Graph[VD2, ED2]): Graph[VD, ED]
  def groupEdges(merge: (ED, ED) => ED): Graph[VD, ED]
  // Join RDDs with the graph ======================================================================
  def joinVertices[U](table: RDD[(VertexID, U)])(mapFunc: (VertexID, VD, U) => VD): Graph[VD, ED]
  def outerJoinVertices[U, VD2](other: RDD[(VertexID, U)])
      (mapFunc: (VertexID, VD, Option[U]) => VD2)
    : Graph[VD2, ED]
  // Aggregate information about adjacent triplets =================================================
  def collectNeighborIds(edgeDirection: EdgeDirection): VertexRDD[Array[VertexID]]
  def collectNeighbors(edgeDirection: EdgeDirection): VertexRDD[Array[(VertexID, VD)]]
  def aggregateMessages[Msg: ClassTag](
      sendMsg: EdgeContext[VD, ED, Msg] => Unit,
      mergeMsg: (Msg, Msg) => Msg,
      tripletFields: TripletFields = TripletFields.All)
    : VertexRDD[A]
  // Iterative graph-parallel computation ==========================================================
  def pregel[A](initialMsg: A, maxIterations: Int, activeDirection: EdgeDirection)(
      vprog: (VertexID, VD, A) => VD,
      sendMsg: EdgeTriplet[VD, ED] => Iterator[(VertexID,A)],
      mergeMsg: (A, A) => A)
    : Graph[VD, ED]
  // Basic graph algorithms ========================================================================
  def pageRank(tol: Double, resetProb: Double = 0.15): Graph[Double, Double]
  def connectedComponents(): Graph[VertexID, ED]
  def triangleCount(): Graph[Int, ED]
  def stronglyConnectedComponents(numIter: Int): Graph[VertexID, ED]
}

属性操作

类似操作RDD 的 map 一样，graphx 的属性包含以下内容：

class Graph[VD, ED] {
  def mapVertices[VD2](map: (VertexId, VD) => VD2): Graph[VD2, ED]
  def mapEdges[ED2](map: Edge[ED] => ED2): Graph[VD, ED2]
  def mapTriplets[ED2](map: EdgeTriplet[VD, ED] => ED2): Graph[VD, ED2]
}

这里每一个操作产生一个新图，其顶点和边被用户定义的map函数修改了。
注意：在每一个实例图结构不受影响。这是这些操作的关键特征，这允许结果图重复利用原始图的结构索引。下面的代码片段逻辑上是等同的，但是第一个没有保存结构索引，其不会从GraphX系统优化中获益：

val newVertices = graph.vertices.map { case (id, attr) => (id, mapUdf(id, attr)) }
val newGraph = Graph(newVertices, graph.edges)

代替，使用mapVertices保护结构索引：
val newGraph = graph.mapVertices((id, attr) => mapUdf(id, attr))这些操作经常用来初始化图为了进行特殊计算或者排除不需要的属性。例如，给定一个图，它的出度作为顶点属性（之后描述如何构建这样一个图），我们初始化它为PageRank：

// Given a graph where the vertex property is the out degree
val inputGraph: Graph[Int, String] =
  graph.outerJoinVertices(graph.outDegrees)((vid, _, degOpt) => degOpt.getOrElse(0))
// Construct a graph where each edge contains the weight
// and each vertex is the initial PageRank
val outputGraph: Graph[Double, Double] =
  inputGraph.mapTriplets(triplet => 1.0 / triplet.srcAttr).mapVertices((id, _) => 1.0)

Spark R

部署

集群模式概述

提交应用

Spark Standalone 模式

Spark on Mesos

Spark on YARN

Spark on YARN 上运行

准备

Spark on YARN 配置

调试应用

Spark 属性

重要提示

在一个安全的集群中运行

用 Apache Oozie 来运行应用程序

Kerberos 故障排查

最近的更新

那伊抹微笑

Apache Spark 2.2.0 中文文档在2017-07-26更新查看变动

片刻

MLlib：基于RDD的API 在2017-07-21更新查看变动

GraphX（图形处理）在2017-07-20更新查看变动

小瑶

Spark Standalone 模式在2017-07-18更新查看变动

片刻

Scala的类：变量／方法／构造方法／继承在2017-07-05更新查看变动

陈留锁

RDD 持久化在2017-07-04发表了评论

无尴尬不青春

优化指南在2017-06-19更新查看变动

Spark 配置在2017-06-19更新查看变动

容错语义在2017-06-13更新查看变动

那伊抹微笑

初始化 StreamingContext 在2017-06-11更新查看变动

Spark Streaming 在2017-06-11更新查看变动

Spark 配置在2017-06-06更新查看变动

片刻

累加器和广播变量在2017-06-05更新查看变动

DStreams 上的 Transformations（转换）在2017-06-01更新查看变动

Input DStreams 和 Receivers 在2017-06-01更新查看变动

依赖在2017-06-01更新查看变动

基本概念在2017-06-01更新查看变动

Spark Streaming 在2017-06-01更新查看变动

应用程序部署在2017-06-01更新查看变动

MLlib 操作在2017-06-01更新查看变动

显示更多请等待

集群模式概述

原文链接 : http://spark.apache.org/docs/latest/cluster-overview.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2887940

贡献者 : 那伊抹微笑

该文档给出了 Spark 如何在集群上运行、使之更容易来理解所涉及到的组件的简短概述。通过阅读 应用提交指南 来学习关于在集群上启动应用。

组件

Spark 应用在集群上作为独立的进程组来运行，在您的 main 程序中通过 SparkContext 来协调（称之为 driver 程序）。

具体的说，为了运行在集群上，SparkContext 可以连接至几种类型的 Cluster Manager（既可以用 Spark 自己的 Standlone Cluster Manager，或者 Mesos，也可以使用 YARN），它们会分配应用的资源。一旦连接上，Spark 获得集群中节点上的 Executor，这些进程可以运行计算并且为您的应用存储数据。接下来，它将发送您的应用代码（通过 JAR 或者 Python 文件定义传递给 SparkContext）至 Executor。最终，SparkContext 将发送 Task 到 Executor 以运行。

这里有几个关于这个架构需要注意的地方 : 

每个应用获取到它自己的 Executor 进程，它们会保持在整个应用的生命周期中并且在多个线程中运行 Task（任务）。这样做的优点是把应用互相隔离，在调度方面（每个 driver 调度它自己的 task）和 Executor 方面（来自不同应用的 task 运行在不同的 JVM 中）。然而，这也意味着若是不把数据写到外部的存储系统中的话，数据就不能够被不同的 Spark 应用（SparkContext 的实例）之间共享。

Spark 是不知道底层的 Cluster Manager 到底是什么类型的。只要它能够获得 Executor 进程，并且它们可以和彼此之间通信，那么即使是在一个也支持其它应用的 Cluster Manager（例如，Mesos / YARN）上来运行它也是相对简单的。

Driver 程序必须在自己的生命周期内（例如，请看 在网络中配置 spark.driver.port 章节）监听和接受来自它的 Executor 的连接请求。同样的，driver 程序必须可以从 worker 节点上网络寻址（就是网络没问题）。

因为 driver 调度了集群上的 task（任务），更好的方式应该是在相同的局域网中靠近 worker 的节点上运行。如果您不喜欢发送请求到远程的集群，倒不如打开一个 RPC 至 driver 并让它就近提交操作而不是从很远的 worker 节点上运行一个 driver。

Cluster Manager 类型

该系统当前支持三种 Cluster Manager : 

Standalone – 包含在 Spark 中使得它更容易来安装集群的一个简单的 Cluster Manager。

Mesos – 一个通用的 Cluster Manager，它也可以运行 Hadoop MapReduce 和其它服务应用。

Hadoop YARN – Hadoop 2 中的 resource manager（资源管理器）。

提交应用

使用 spark-submit 脚本可以提交应用至任何类型的集群。在 应用提交指南 中介绍了如何来做到这一点。

监控

每个 driver 程序有一个 Web UI，通常在端口 4040 上，它展示了关于运行 task，executor，和存储使用情况的信息。在网页浏览器中访问这个 UI : http://<driver-node>:4040。监控指南 也描述了其它的监控选项。

Job 调度

Spark 即可以在应用间（Cluster Manager 级别），也可以在应用内（如果多个计算发生在相同的 SparkContext 上时）控制资源分配。Job 调度指南 描述的更详细。

术语

下表中总结了您将会看到用于涉及到集群时的术语 : 

Term | Meaning
Application | 用户构建在 Spark 上的程序。由集群上的一个 driver 程序和多个 executor 组成。
Application jar | 一个包含用户 Spark 应用的 Jar。有时候用户会想要去创建一个包含他们应用以及它的依赖的 “uber jar”。用户的 Jar 应该没有包括 Hadoop 或者 Spark 库，然而，它们将会在运行时被添加。
Driver program | 该进程运行应用的 main() 方法并且创建了 SparkContext。
Cluster manager | 一个外部的用于获取集群上资源的服务。（例如，Standlone Manager，Mesos，YARN）
Deploy mode | 根据 driver 程序运行的地方区别。在 “Cluster” 模式中，框架在群集内部启动 driver。在 “Client” 模式中，submitter（提交者）在 Custer 外部启动 driver。
Worker node | 任何在集群中可以运行应用代码的节点。
Executor | 一个为了在 worker 节点上的应用而启动的进程，它运行 task 并且将数据保持在内存中或者硬盘存储。每个应用有它自己的 Executor。
Task | 一个将要被发送到 Executor 中的工作单元。
Job | 一个由多个 task 组成的并行计算，并且能从 Spark action 中获取响应（例如，save，collect），您将在 driver 的日志中看到这个术语。
Stage | 每个 Job 被拆分成更小的被称作 stage（阶段） 的 task（任务） 组，stage 彼此之间是相互依赖的（与 MapReduce 中的 map 和 reduce stage 相似）。您将在 driver 的日志中看到这个术语。

提交应用

原文链接 : http://spark.apache.org/docs/latest/submitting-applications.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=3539265

贡献者 : 那伊抹微笑

在 Spark 的 bin 目录中的 spark-submit 脚本用与在集群上启动应用程序。它可以通过一个统一的接口使用所有 Spark 支持的 Cluster Manager，所以您不需要专门的为每个 Cluster Manager 来配置您的应用程序。

打包应用依赖

如果您的代码依赖了其它的项目，为了分发代码到 Spark 集群中您将需要将它们和您的应用程序一起打包。为此，创建一个包含您的代码以及依赖的 assembly jar（或者 “uber” jar）。无论是 sbt 还是 Maven 都有 assembly 插件。在创建 assembly jar 时，列出 Spark 和 Hadoop 提供的依赖。它们不需要被打包，因为在运行时它们已经被 Cluster Manager 提供了。如果您有一个 assembled jar 您就可以调用 bin/spark-submit 脚本（如下所示）来传递您的 jar。

对于 Python 来说，您可以使用 spark-submit 的 --py-files 参数来添加 .py，.zip 和 .egg 文件以与您的应用程序一起分发。如果您依赖了多个 Python 文件我们推荐将它们打包成一个 .zip 或者 .egg 文件。

用 spark-submit 启动应用

如果用户的应用程序被打包好了，它可以使用 bin/spark-submit 脚本来启动。这个脚本负责设置 Spark 和它的依赖的 classpath，并且可以支持 Spark 所支持的不同的 Cluster Manager 以及 deploy mode（部署模式）: 

./bin/spark-submit \
  --class <main-class> \
  --master <master-url> \
  --deploy-mode <deploy-mode> \
  --conf <key>=<value> \
  ... # other options
  <application-jar> \
  [application-arguments]

一些常用的 options（选项）有 : 

--class : 您的应用程序的入口点（例如。org.apache.spark.examples.SparkPi）。

--master : 集群的 Master URL（例如。spark://23.195.26.187:7077）。

--deploy-mode : 是在 worker 节点（cluster）上还是在本地作为一个外部的客户端（client）部署您的 driver（默认 : client）†。

--conf : 按照 key=value 格式任意的 Spark 配置属性。对于包含空格的 value（值）使用引号包 “key=value” 起来。

application-jar : 包括您的应用以及所有依赖的一个打包的 Jar 的路径。该 URL 在您的集群上必须是全局可见的，例如，一个 hdfs:// path 或者一个 file:// path 在所有节点是可见的。

application-arguments : 传递到您的 main class 的 main 方法的参数，如果有的话。

† 常见的部署策略是从一台 gateway 机器物理位置与您 worker 在一起的机器（比如，在 standalone EC2 集群中的 Master 节点上）来提交您的应用。在这种设置中，client 模式是合适的。在 client 模式中，driver 直接运行在一个充当集群 client 的 spark-submit 进程内。应用程序的输入和输出直接连到控制台。因此，这个模式特别适合那些设计 REPL（例如，Spark shell）的应用程序。

另外，如果您从一台远离 worker 机器的机器（例如，本地的笔记本电脑上）提交应用程序，通常使用 cluster 模式来降低 driver 和 executor 之间的延迟。目前，Standalone 模式不支持 Cluster 模式的 Python 应用。

对于 Python 应用，在 <application-jar> 的位置简单的传递一个 .py 文件而不是一个 JAR，并且可以用 --py-files 添加 Python .zip，.egg 或者 .py 文件到 search path（搜索路径）。

这里有一些选项可用于特定的 Cluster Manager 中。例如，Spark standalone cluster 用 cluster 部署模式，您也可以指定 --supervise 来确保 driver 在 non-zero exit code 失败时可以自动重启。为了列出所有 spark-submit 可用的选项，用 --help 来运行它。这里是一些常见选项的例子 : 

# Run application locally on 8 cores
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master local[8] \
  /path/to/examples.jar \
  100

# Run on a Spark standalone cluster in client deploy mode
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master spark://207.184.161.138:7077 \
  --executor-memory 20G \
  --total-executor-cores 100 \
  /path/to/examples.jar \
  1000

# Run on a Spark standalone cluster in cluster deploy mode with supervise
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master spark://207.184.161.138:7077 \
  --deploy-mode cluster \
  --supervise \
  --executor-memory 20G \
  --total-executor-cores 100 \
  /path/to/examples.jar \
  1000

# Run on a YARN cluster
export HADOOP_CONF_DIR=XXX
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master yarn \
  --deploy-mode cluster \  # can be client for client mode
  --executor-memory 20G \
  --num-executors 50 \
  /path/to/examples.jar \
  1000

# Run a Python application on a Spark standalone cluster
./bin/spark-submit \
  --master spark://207.184.161.138:7077 \
  examples/src/main/python/pi.py \
  1000

# Run on a Mesos cluster in cluster deploy mode with supervise
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master mesos://207.184.161.138:7077 \
  --deploy-mode cluster \
  --supervise \
  --executor-memory 20G \
  --total-executor-cores 100 \
  http://path/to/examples.jar \
  1000

Master URL

传递给 Spark 的 master URL 可以使用下列格式中的一种 : 

Master URL | Meaning
local | 使用一个线程本地运行 Spark（即，没有并行性）。
local[K] | 使用 K 个 worker 线程本地运行 Spark（理想情况下，设置这个值的数量为您机器的 core 数量）。
local[*] | 使用更多的 worker 线程作为逻辑的 core 在您的机器上来本地的运行 Spark。
spark://HOST:PORT | 连接至给定的 Spark standalone cluster master。该 port（端口）必须有一个作为您的 master 配置来使用，默认是 7077.
mesos://HOST:PORT | 连接至给定的 Mesos cluster。该 port（端口）必须有一个作为您的配置来使用，默认是 5050。或者，对于使用了 ZooKeeper 的 Mesos cluster 来说，使用 mesos://zk://...。使用 --deploy-mode cluster 来提交，该 HOST:PORT 应该被配置以连接到 MesosClusterDispatcher。
yarn | 连接至一个 YARN cluster，取决于 --deploy-mode 的值在 client 或者 cluster 模式中。该 cluster 的位置将根据 HADOOP_CONF_DIR 或者 YARN_CONF_DIR 变量来找到。

从文件中加载配置

spark-submit 脚本可以从一个 properties 文件加载默认的 Spark configuration values 并且传递它们到您的应用中去。默认情况下，它将从 Spark 目录下的 conf/spark-defaults.conf 读取配置。更多详细信息，请看 加载默认配置 部分。

加载默认的 Spark 配置，这种方式可以消除某些标记到 spark-submit 的必要性。例如，如果 spark.master 属性被设置了，您可以在 spark-submit 中安全的省略。一般情况下，明确设置在 SparkConf 上的配置值的优先级最高，然后是传递给 spark-submit 的值，最后才是 default value（默认文件）中的值。

如果您不是很清楚其中的配置设置来自哪里，您可以通过使用 --verbose 选项来运行 spark-submit 打印出细粒度的调试信息。

先进的依赖管理

在使用 spark-submit 时，使用 --jars 选项包括的应用程序的 jar 和任何其它的 jar 都将被自动的传输到集群。在 --jars 后面提供的 URL 必须用逗号分隔。该列表传递到 driver 和 executor 的 classpath 中 --jars 不支持目录的形式。

Spark 使用下面的 URL 方案以允许传播 jar 时使用不同的策略 : 

file: - 绝对路径和 file:/ URI 通过 driver 的 HTTP file server 提供服务，并且每个 executor 会从 driver 的 HTTP server 拉取这些文件。

hdfs:，http:，https:，ftp: - 如预期的一样拉取下载文件和 JAR。

local: - 一个用 local:/ 开头的 URL 预期作在每个 worker 节点上作为一个本地文件存在。这样意味着没有网络 IO 发生，并且非常适用于那些通过 NFS，GlusterFS，等推倒每个 worker 或共享大型的 file/JAR。

注意，那些 JAR 和文件被复制到 working directory（工作目录）用于在 executor 节点上的每个 SparkContext。这可以使用最多的空间显著量随着时间的推移，将需要清理。在 Spark On YARN 模式中，自动执行清理操作。在 Spark standalone 模式中，可以通过配置 spark.worker.cleanup.appDataTtl 属性来执行自动清理。

用户也可以通过使用 --packages 来提供一个逗号分隔的 maven coordinates（maven 坐标）以包含任何其它的依赖。在使用这个命令时所有可传递的依赖将被处理。其它的 repository（或者在 SBT 中被解析的）可以使用 --repositoies 该标记添加到一个逗号分隔的样式中。这些命令可以与 pyspark，spark-shell 和 spark-submit 配置会使用以包含 Spark Packages（Spark 包）。

对于 Python 来说，也可以使用 --py-files 选项用于分发 .egg，.zip 和 .py libraries 到 executor 中。

更多信息

如果您已经部署了您的应用程序，集群模式概述 描述了在分布式执行中涉及到的组件，以及如何去监控和调试应用程序。

Spark Standalone 模式

原文链接 : http://spark.apache.org/docs/latest/spark-standalone.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2888473

贡献者 : 漫步，那伊抹微笑

Spark 除了运行在 Mesos 或者 Yarn 上以外还可以独立运行，可以单独运行 master 和 worker，或者使用我们提供的脚本启动集群。也可以为了测试在一台机器上运行这些进程。

独立安装 Spark 集群

独立安装 Spark 集群，只需要把编译好的版本部署在每个节点上，然后启动，或者也可以自己编译指定版本。

手动启动集群

通过下面脚本启动集群的主节点:

./sbin/start-master.sh

通过上面脚本启动 master ，master 会通过 spark://host:portURL 生成链接，你可以手动访问，或者通过 master 的 SparkContext，来创建访问，或者通过浏览器访问 spark 的 Web UI ,默认情况 : HTTP：//主机ip:8080

同样，你可以启动一个和多个 worker，并通过 主节点把他们连接到一起 : 

./sbin/start-slave.sh <master-spark-URL>

在您启动一个 worker 之后，就可以通过URL : HTTP://主机ip:8080 看到新添加的节点，你可以看到新的节点列表以及节点的 CPU 数量以及内存。

最后，下面的配置参数可以传递给 master 和 worker

参数 | 说明
-h HOST， --host HOST | 监听的主机名
-i HOST， --ip HOST | 同上，不建议使用
-p PORT， --port PORT | 监听的服务的端口（master 默认是 7077，worker 随机）
--webui-port PORT | web UI 的端口（master 默认是 8080，worker 默认是 8081）
-c CORES， --cores CORES | Spark 应用程序可以使用的 CPU 核数（默认是所有可用）；这个选项仅在 worker 上可用
-m MEM， --memory MEM | Spark 应用程序可以使用的内存数（默认情况是你的机器内存数减去 1g）；这个选项仅在 worker 上可用
-d DIR， --work-dir DIR | 用于暂存空间和工作输出日志的目录（默认是 SPARK_HOME/work）；这个选项仅在 worker 上可用
--properties-file FILE | 自定义的 Spark 配置文件的加载目录（默认是 conf/spark-defaults.conf）

集群启动脚本

为了使用启动脚本启动 Spark 独立集群，你应该在你的 Spark 目录下建立一个名为 conf/slaves 的文件，这个文件必须包含所有你要启动的 Spark worker 所在机器的主机名，一行一个。如果 conf/slaves 不存在，启动脚本默认为单个机器（localhost），这台机器对于测试是有用的。注意，master 机器通过 ssh 访问所有的 worker。在默认情况下，SSH 是并行运行，需要设置无密码（采用私有密钥）的访问。 如果你没有设置为无密码访问，你可以设置环境变量 SPARK_SSH_FOREGROUND，为每个 worker 提供密码。

一旦你设置了这个文件，你就可以通过下面的 shell 脚本启动或者停止你的集群。

sbin/start-master.sh : 在机器上启动一个 master 实例。

sbin/start-slaves.sh : 在每台机器上启动一个 slave 实例。

sbin/start-slave.sh : 在机器上启动一个 slave 实例。

sbin/start-all.sh : 同时启动一个 master 实例和所有 slave 实例。

sbin/stop-master.sh : 停止 master 实例。

sbin/stop-slaves.sh : 停止所有 slave 实例。

sbin/stop-all.sh : 停止 master 实例和所有 slave 实例。

注意，这些脚本必须在你的 Spark master 运行的机器上执行，而不是在你的本地机器上面。

你可以在 conf/spark-env.sh 中设置环境变量进一步配置集群。利用 conf/spark-env.sh.template 创建这个文件，然后将它复制到所有的 worker 机器上使设置有效。下面的设置可以起作用 : 

环境变量 | 说明
SPARK_MASTER_HOST | 绑定 master 到一个指定的 ip 地址
SPARK_MASTER_PORT | 不同的端口上启动主（默认值 : 7077）
SPARK_MASTER_WEBUI_PORT | master web UI 的端口（默认是 8080）
SPARK_MASTER_OPTS | 应用到 master 的配置属性，格式是 "-Dx=y"（默认是 none），查看下面的表格的选项以组成一个可能的列表
SPARK_LOCAL_DIRS | Spark 中暂存空间的目录。包括 map 的输出文件和存储在磁盘上的 RDDs(including map output files and RDDs that get stored on disk)。这必须在一个快速的、可靠的系统本地磁盘上。它可以是一个逗号分隔的列表，代表不同磁盘的多个目录
SPARK_WORKER_CORES | Spark 应用程序可以用到的核心数（默认是所有可用）
SPARK_WORKER_MEMORY | Spark 应用程序用到的内存总数（默认是内存总数减去 1G）。注意，每个应用程序个体的内存通过 spark.executor.memory 设置
SPARK_WORKER_PORT | 在指定的端口上启动 Spark worker（默认是随机）
SPARK_WORKER_WEBUI_PORT | worker UI 的端口（默认是 8081）
SPARK_WORKER_DIR | Spark worker 运行目录，该目录包括日志和暂存空间（默认是 SPARK_HOME/work）
SPARK_WORKER_OPTS | 应用到 worker 的配置属性，格式是 "-Dx=y"（默认是 none），查看下面表格的选项以组成一个可能的列表
SPARK_DAEMON_MEMORY | 分配给 Spark master 和 worker 守护进程的内存（默认是 512m）
SPARK_DAEMON_JAVA_OPTS | Spark master 和 worker 守护进程的 JVM 选项，格式是 "-Dx=y"（默认为 none）
SPARK_PUBLIC_DNS | Spark master 和 worker 公共的 DNS 名（默认是 none）

注意，启动脚本还不支持 windows。为了在 windows 上启动 Spark 集群，需要手动启动 master 和 workers。

SPARK_MASTER_OPTS 支持以下系统属性 : 

参数 | 默认 | 说明
spark.deploy.retainedApplications | 200 | 展示完成的应用程序的最大数目。老的应用程序会被删除以满足该限制
spark.deploy.retainedDrivers | 200 | 展示完成的drivers 的最大数目。老的应用程序会被删除以满足该限制
spark.deploy.spreadOut | true | 这个选项控制独立的集群管理器是应该跨节点传递应用程序还是应努力将程序整合到尽可能少的节点上。在 HDFS 中，传递程序是数据本地化更好的选择，但是，对于计算密集型的负载，整合会更有效率。
spark.deploy.defaultCores | （infinite） | 在 Spark 独立模式下，给应用程序的默认核数（如果没有设置 spark.cores.max）。如果没有设置，应用程序总数获得所有可用的核，除非设置了 spark.cores.max。在共享集群上设置较低的核数，可用防止用户默认抓住整个集群
spark.deploy.maxExecutorRetries | 10 | 限制上的独立的集群管理器中删除有问题的应用程序之前可能发生的后端到回执行人失败的最大数量。应用程序将永远不会被删除，如果有任何正在运行的执行人。如果一个应用程序的经验超过 spark.deploy.maxExecutorRetries 连续的失败，没有遗嘱执行人成功启动这些失败之间运行，并且应用程序没有运行执行人那么独立的集群管理器将删除该应用程序并将其标记为失败。要禁用此自动删除，设置spark.deploy.maxExecutorRetries 为 -1
spark.worker.timeout | 60 | 独立部署的 master 认为 worker 失败（没有收到心跳信息）的间隔时间

SPARK_WORKER_OPTS 支持以下系统属性 : 

参数 | 默认 | 说明
spark.worker.cleanup.enabled | false | 周期性的清空 worker 应用程序目录。注意，这仅仅影响独立部署模式。不管应用程序是否还在执行，用于程序目录都会被清空
spark.worker.cleanup.interval | 1800（30 分钟） | 在本地机器上，worker 清空老的应用程序工作目录的时间间隔
spark.worker.cleanup.appDataTtl | 7 * 24 * 3600（7天） | 每个 worker 中应用程序工作目录的保留时间。这个时间依赖于你可用磁盘空间的大小。应用程序日志和jar包上传到每个应用程序的工作目录。随着时间的推移，工作目录会很快的填满磁盘空间，特别是如果你运行的作业很频繁

提交应用程序到集群

为了简单的运行 Spark 的应用程序，只需要通过 SparkContext 构造 spark:IP:port 这个 URL。

集群的交互式运行可以通过 Spark shell，运行下面命名 : 

./bin/spark-shell --master spark://ip:port

你也可以通过选项 --total-executor-cores <numCores> 传递参数去控制 spark-shell 的核数。

启动 Spark 应用程序

spark-submit 脚本支持最直接的提交一个 Spark 应用程序到集群。对于独立部署的集群，Spark 目前支持两种部署模式。在 client 模式中，driver 启动进程与客户端提交应用程序所在的进程是同一个进程。然而，在 cluster 模式中，driver 在集群的某个 worker 进程中启动，只有客户端进程完成了提交任务，它不会等到应用程序完成就会退出。

如果你的应用程序通过 Spark submit 启动，你的应用程序jar包将会自动分发到所有的 worker 节点。对于你的应用程序依赖的其它 jar 包，你应该用 --jars 符号指定（如 --jars jar1,jar2）。

另外，如果你的应用程序以非 0 状态退出，独立集群模式支持重启程序。要支持自动重启，需要向 spark-submit 传递 --supervise 标志。如果你想杀掉一个重复失败的应用程序，你可以使用如下方式 : 

./bin/spark-class org.apache.spark.deploy.Client kill <master url> <driver ID>

你可以在 Master 的 web UI 上 http://<master url>:8080 找到 driver ID。

资源调度

独立部署的集群模式仅仅支持简单的 FIFO 调度器。然而，为了允许多个并行的用户，你能够控制每个应用程序能用的最大资源数。在默认情况下，它将获得集群的所有核，这只有在某一时刻只允许一个应用程序才有意义。你可以通过 spark.cores.max 在 SparkConf 中设置核数。

val conf = new SparkConf()
  .setMaster(...)
  .setAppName(...)
  .set("spark.cores.max", "10")
val sc = new SparkContext(conf)

另外，你可以在集群的 master 进程中配置 spark.deploy.defaultCores 来改变默认的值。在 conf/spark-env.sh 添加下面的行 : 

export SPARK_MASTER_OPTS="-Dspark.deploy.defaultCores=<value>"

这在用户没有配置最大核数的共享集群中是有用的。

监控和日志

Spark 的独立模式提供了一个基于 Web 的用户界面来监控集群。Master 和 每个 Worker 都有自己的 web 用户界面，显示集群和作业统计信息。默认情况下你可以在 8080 端口访问 Web UI 的主端口也可以在配置文件中或通过命令行选项进行更改。

此外，对于每个作业详细的日志也会输出每个 worker 目录下（默认情况工作目录 SPARK_HOME/work）。你会看到两个文件为每个作业，stdout 和 stderr 与所有输出它写信给其控制台。

与 hadoop 的集成

你可以在 hadoop 上单独启动一个 Spark 服务。若要从 Spark 访问 Hadoop 的数据，只需使用一个 HDFS：// URL（通常是 hdfs://<namenode>:9000/path，也可以找到 Hadoop 的 Web UI 找到 Namenode 的 URL）。另外，您也可以单独启动 Spark 集群，而且还有它通过网络进行访问 HDFS; 这将是比磁盘，本地访问速度较慢，最好是在一个局域网之内（例如，Hadoop 各项服务或者各个节点在同一个机架上）。

端口配置和网络安全

Spark 对网络的需求比较高，对网络环境有紧密联系，需要严谨设置防火墙。端口配置列表，请参阅 安全网页。

高可用性

默认情况下，独立的调度集群对 worker 失败是有弹性的（在 Spark 本身的范围内是有弹性的，对丢失的工作通过转移它到另外的 worker 来解决）。然而，调度器通过 master 去执行调度决定， 这会造成单点故障 : 如果 master 死了，新的应用程序就无法创建。为了避免这个，我们有两个高可用的模式。

用 ZooKeeper 的备用 master

概述

利用 ZooKeeper 去支持领导选举以及一些状态存储，你能够在你的集群中启动多个 master，这些 master 连接到同一个 ZooKeeper 实例上。一个被选为 “Leader”，其它的保持备用模式。如果当前 的领导死了，另一个 master 将会被选中，恢复老 master 的状态，然后恢复调度。整个的恢复过程大概需要 1 到 2 分钟。注意，这个恢复时间仅仅会影响调度新的应用程序-运行在失败 master 中的 应用程序不受影响。

了解更多关于如何开始使用的 ZooKeeper 这里。

配置

为了开启这个恢复模式，你可以用下面的属性在 spark-env 中设置 SPARK_DAEMON_JAVA_OPTS  spark.deploy.recoveryMode 及相关 spark.deploy.zookeeper.* 的配置。有关这些配置的详细信息，请参阅配置文档

可能的陷阱 : 如果你在集群中有多个 masters，但是没有用 zookeeper 正确的配置这些 masters，这些 masters 不会发现彼此，会认为它们都是 leaders。这将会造成一个不健康的集群状态（因为所有的 master 都会独立的调度）。

细节

zookeeper 集群启动之后，开启高可用是简单的。在相同的 zookeeper 配置（zookeeper URL 和目录）下，在不同的节点上简单地启动多个 master 进程。master 可以随时添加和删除。

为了调度新的应用程序或者添加 worker 到集群，它需要知道当前 leader 的 IP 地址。这可以通过简单的传递一个 master 列表来完成。例如，你可能启动你的 SparkContext 指向 spark://host1:port1,host2:port2。 这将造成你的 SparkContext 同时注册这两个master - 如果 host1 死了，这个配置文件将一直是正确的，因为我们将找到新的 leader - host2。

"registering with a Master" 和正常操作之间有重要的区别。当启动时，一个应用程序或者 worker 需要能够发现和注册当前的 leader master。一旦它成功注册，它就在系统中了。如果 错误发生，新的 leader 将会接触所有之前注册的应用程序和 worker，通知他们领导关系的变化，所以它们甚至不需要事先知道新启动的 leader 的存在。

由于这个属性的存在，新的 master 可以在任何时候创建。你唯一需要担心的问题是新的应用程序和 workers 能够发现它并将它注册进来以防它成为 leader master。

用本地文件系统做单节点恢复

概述

zookeeper 是生产环境下最好的选择，但是如果你想在 master 死掉后重启它，FILESYSTEM（文件系统）模式可以解决。当应用程序和 worker 注册，它们拥有足够的状态写入提供的目录，以至于在重启 master 进程时它们能够恢复。

配置

为了开启这个恢复模式，你可以用下面的属性在 spark-env 中设置 SPARK_DAEMON_JAVA_OPTS。

系统属性 | 说明
spark.deploy.recoveryMode | 设置为 FILESYSTEM 开启单节点恢复模式（默认为 none）
spark.deploy.recoveryDirectory | 用来恢复状态的目录

细节

这个解决方案可以和监控器/管理器（如 monit）相配合，或者仅仅通过重启开启手动恢复。

虽然文件系统的恢复似乎比没有做任何恢复要好，但对于特定的开发或实验目的，这种模式可能是次优的。特别是，通过 stop-master.sh 杀掉 master 不会清除它的恢复状态，所以，不管你何时启动一个新的 master，它都将进入恢复模式。这可能使启动时间增加到 1 分钟。

虽然它不是官方支持的方式，你也可以创建一个 NFS 目录作为恢复目录。如果原始的 master 节点完全死掉，你可以在不同的节点启动 master，它可以正确的恢复之前注册的所有应用程序和 workers。未来的应用程序会发现这个新的 master。

Spark on Mesos

原文链接 : http://spark.apache.org/docs/latest/running-on-mesos.html

译文链接 : http://www.apache.wiki/display/Spark/Spark+on+Mesos

贡献者 : 片刻，那伊抹微笑

Spark 可以在 Apache Mesos 管理的硬件集群上运行。

使用 Mesos 部署 Spark 的优点包括 : 

Spark 和其他框架 frameworks 之间的 dynamic partitioning（动态分区）。

Spark 的多个实例之间的 scalable partitioning（可扩展分区）。

运行原理

在一个独立集群部署中，下图的集群管理器是 Spark master 的一个实例。当使用 Mesos 管理时，Mesos master 会替代 Spark master 作为集群的管理器。

现在，Driver 程序创建一个 job 并开始分发调度任务时，Mesos 会决定什么机器处理什么任务。 因为 Mesos 调度这些短期任务时会考虑到其他的框架，许多框架将会在同一个集群上共存，而不是借助资源的静态分区。

开始，请按照以下步骤安装 Mesos 并通过 Mesos 部署 Spark 作业。

安装 Mesos

Spark 2.1.0 设计用于 Mesos1.0.0 或更新版本，不需要任何特殊的 Mesos 补丁。

如果你早已经有一个 Mesos 集群在运行，你可以跳过这个 Mesos 的安装步骤。

否则，安装 Mesos for Spark 与安装 Mesos for 其他的框架没有什么不同。你可以通过源码或者预构建软件安装包来安装 Mesos。

从源码安装

通过源码安装 Apache Mesos，按照以下步骤 : 

从镜像 mirror 下载 Mesos 版本。

按照 Mesos 开始页面 Getting Started 来编译和安装 Mesos。

注意 :

 如果你希望运行 Mesos 又不希望安装在系统的默认位置（例如 : 如果你没有默认路径的管理权限），传递 --prefix 选项进行配置来告诉它安装在什么地方。例如： 传递 --prefix=/home/me/mesos。默认情况下，前缀是 : /usr/local 。

第三方包

Apache Mesos 项目只发布了源码的版本，而不是二进制包。但是其他的第三方项目发布的二进制版本，可能对设置 Mesos 有帮助。

其中之一是中间层。用使用中间层提供的二进制版本安装 Mesos。

从下载页面下载 Mesos 的安装包。

按照说明进行安装和配置。

中间层的安装文档建议设置 zookeeper 来处理 Mesos Master 的故障转移，但是 Mesos 通过使用 Single Master 模式，可以在没有 zookeeper 的情况下运行。

验证

要验证 Mesos 集群是否可用于 Spark，导航到 Mesos Master 的 Web UI 界面，端口是 : 5050 来确认所有预期的机器都会与从属选项卡中。

连接 Spark 到 Mesos

通过使用 Spark 中的 Mesos，你需要一个 Spark 的二进制包放到 Mesos 可以访问的地方，然后配置 Spark driver 程序来连接 Mesos。

或者你也可以在所有 Mesos slaves 位置安装 Spark，然后配置 spark.mesos.executor.home（默认是 SPARK_HOME）来指向这个位置。

上传Spark包

当 Mesos 第一次在 Mesos slave上运行任务时，该从服务器必须有一个 Spark 二进制包来运行 Spark Mesos 执行器后端。 Spark 包可以在任何 Hadoop 可访问的 URI 上托管，包括 HTTP 通过 http://，Amazon Simple Storage Service 通过 s3n:// 或 HDFS 通过 hdfs://。

要使用预编译包: 

从 Spark download page 下载 Spark 二进制包。

上传到 hdfs/http/s3。

要在 HDFS 上主机，请使用 Hadoop fs put 命令 : hadoop fs -put spark-2.1.0.tar.gz /path/to/spark-2.1.0.tar.gz

或者如果您使用的是自定义编译版本的 Spark，则需要使用包含在 Spark 源代码 tarball / checkout 中的 dev / make-distribution.sh 脚本创建一个包。

使用 here 的说明下载并构建 Spark

使用 ./dev/make-distribution.sh --tgz 创建二进制包。

将归档文件上传到 http / s3 / hdfs

使用 Mesos Master 的 URL

Mesos 的 Master URL 以 mesos://host:5050 形式表示 single-master Mesos 集群，或者 mesos://zk://host1:2181,host2:2181,host3:2181/mesos 为一个 multi-master Mesos 集群使用 ZooKeeper。

客户端模式

在客户端模式下，Spark Mesos 框架直接在客户端计算机上启动，并等待驱动程序输出。

驱动程序需要在 spark-env.sh 中进行一些配置才能与Mesos正常交互 : 

在 spark-env.sh 中设置一些环境变量 : 

export MESOS_NATIVE_JAVA_LIBRARY=<path to libmesos.so>的路径。此路径通常为 <prefix>/lib/libmesos.so，其中前缀默认为 /usr/local。请参阅上面的 Mesos 安装说明。在 Mac OS X 上，库称为 libmesos.dylib，而不是 libmesos.so。

export SPARK_EXECUTOR_URI=<URL of spark-2.1.0.tar.gz上传>。

还将 spark.executor.uri 设置为 <URL of spark-2.1.0.tar.gz>。

现在，当针对集群启动 Spark 应用程序时，在创建 SparkContext 时传递一个 mesos:// URL 作为主服务器。例如 : 

Scala

val conf = new SparkConf()
  .setMaster("mesos://HOST:5050")
  .setAppName("My app")
  .set("spark.executor.uri", "<path to spark-2.1.0.tar.gz uploaded above>")
val sc = new SparkContext(conf)

（您还可以在 conf/spark-defaults.conf 文件中使用 spark-submit 并配置 spark.executor.uri。）

运行 shell 时，spark.executor.uri 参数从 SPARK_EXECUTOR_URI 继承，因此不需要作为系统属性冗余传递。

./bin/spark-shell --master mesos://host:5050

集群模式

Spark on Mesos 还支持集群模式，其中驱动程序在集群中启动，客户端可以从 Mesos Web UI 中查找驱动程序的结果。

要使用集群模式，必须通过 sbin/start-mesos-dispatcher.sh 脚本启动集群中的 MesosClusterDispatcher，并传递 Mesos 主 URL（例如 ：mesos://host:5050）。这将启动 MesosClusterDispatcher 作为在主机上运行的守护程序。

如果你喜欢用 Marathon 运行 MesosClusterDispatcher，你需要在前台运行 MesosClusterDispatcher（即 : bin/spark-class org.apache.spark.deploy.mesos.MesosClusterDispatcher）。请注意，MesosClusterDispatcher 尚不支持 HA 的多个实例。

MesosClusterDispatcher 还支持将恢复状态写入 Zookeeper。这将允许 MesosClusterDispatcher 能够在重新启动时恢复所有提交和运行的容器。为了启用此恢复模式，您可以通过配置 spark.deploy.recoveryMode 和相关的 spark.deploy.zookeeper.* 配置在 spark-env 中设置SPARK_DAEMON_JAVA_OPTS。有关这些配置的更多信息，请参阅配置 (doc)[configurations.html#deploy]。

从客户端，您可以通过运行 spark-submit 并指定到 MesosClusterDispatcher 的 URL（例如 : mesos://dispatcher:7077）的主 URL 将作业提交到 Mesos 集群。您可以在 Spark 集群 Web UI 上查看驱动程序状态。

例如 : 

./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master mesos://207.184.161.138:7077 \
  --deploy-mode cluster \
  --supervise \
  --executor-memory 20G \
  --total-executor-cores 100 \
  http://path/to/examples.jar \
  1000

请注意，传递给 spark-submit 的 jar 或 python 文件应该是 Mesos 从设备可达的 URI，因为 Spark 驱动程序不会自动上传本地 jar。

Mesos 运行模式

Spark 可以在两种模式下运行 Mesos : “粗粒度”（默认）和“细粒度”（已过时）。

Coarse-Grained（粗粒度）

在 “粗粒度” 模式下，每个 Spark 执行程序作为单个 Mesos 任务运行。 Spark 执行程序的大小根据以下配置变量确定 : 

执行器内存 : spark.executor.memory。

执行器核心 : spark.executor.cores。

执行器数 : spark.cores.max / spark.executor.cores。

有关详细信息和默认值，请参阅 配置页面 。

应用程序启动时，热切地启动执行器，直到达到 spark.cores.max。如果不设置 spark.cores.max，Spark 应用程序将保留 Mesos 提供给它的所有资源，因此我们当然敦促您在任何类型的多租户集群中设置此变量，包括运行多个并发 Spark 应用程序。

调度程序将启动执行者循环提供 Mesos 给它，但没有传播保证，因为 Mesos 不提供这样的保证提供流。

粗粒度模式的好处是启动开销低得多，但是在应用程序的整个持续时间内保留 Mesos 资源的代价。要配置作业以动态调整其资源要求，请查看 Dynamic Allocation 。

Fine-Grained （细粒度，已过时）

注意 :

细粒度模式自 Spark 2.0.0 起已过时。考虑使用 Dynamic Allocation 有一些好处。有关完整说明，请参阅 SPARK-11857

在 “细粒度” 模式下，Spark 执行程序中的每个 Spark 任务作为单独的 Mesos 任务运行。这允许 Spark（和其他框架）的多个实例以非常精细的粒度共享核心，其中每个应用程序随着其上升和下降而获得更多或更少的核心，但是它在启动每个任务时带来额外的开销。此模式可能不适合低延迟要求，如交互式查询或服务 Web 请求。

请注意，虽然细粒度的 Spark 任务会在核心终止时放弃核心，但它们不会放弃内存，因为 JVM 不会将内存回馈给操作系统。执行器在空闲时也不会终止。

要以细粒度模式运行，请在 SparkConf 中将 spark.mesos.coarse 属性设置为 false : 

conf.set("spark.mesos.coarse", "false")

您还可以使用 spark.mesos.constraints 在 Mesos 资源提供上设置基于属性的约束。默认情况下，所有资源优惠都将被接受。

conf.set("spark.mesos.constraints", "os:centos7;us-east-1:false")

例如，假设 spark.mesos.constraints 设置为 os:centos7;us-east-1:false，那么将检查资源提供以查看它们是否满足这两个约束，然后才会被接受以启动新的执行程序。

Mesos Docker 的支持

Spark 可以通过在你的 SparkConf 中 设置 spark.mesos.executor.docker.image 的属性，从而来使用 Mesos Docker 容器。

所使用的 Docker 镜像必须有一个合适的版本的 Spark 已经是映像的一部分，或者您可以通过通常的方法让 Mesos 下载 Spark。

需要 Mesos 版本 0.22 或更高版本。

独立于 Hadoop 运行

您可以在现有的 Hadoop 集群上运行 Spark 和 Mesos，只需将它们作为计算机上的单独服务启动即可。 要从 Spark 访问 Hadoop 数据，需要一个完整的 hdfs:// URL（通常为 hdfs://<namenode>:9000/path，但您可以在 Hadoop Namenode Web UI 上找到正确的URL）。

此外，还可以在 Mesos 上运行 Hadoop MapReduce，以便在两者之间实现更好的资源隔离和共享。 在这种情况下，Mesos 将作为统一的调度程序，将 Core 核心分配给 Hadoop 或 Spark，而不是通过每个节点上的 Linux 调度程序共享资源。 请参考 Hadoop on Mesos 。

在任一情况下，HDFS 与 Hadoop MapReduce 分开运行，而不通过 Mesos 调度。

使用 Mesos 来进行动态分配资源

Mesos 仅支持使用粗粒度模式的动态分配，这可以基于应用程序的统计信息调整执行器的数量。 有关一般信息，请参阅 Dynamic Resource Allocation 。

要使用的外部 Shuffle 服务是 Mesos Shuffle 服务。 它在 Shuffle 服务之上提供 shuffle 数据清理功能，因为 Mesos 尚不支持通知另一个框架的终止。 要启动它，在所有从节点上运 $SPARK_HOME/sbin/start-mesos-shuffle-service.sh，并将 spark.shuffle.service.enabled 设置为true。

这也可以通过 Marathon，使用唯一的主机约束和以下命令实现 : bin/spark-class org.apache.spark.deploy.mesos.MesosExternalShuffleService。

配置

有关 Spark 配置的信息，请参阅 配置页面 。 以下配置特定于 Mesos 上的 Spark。

Spark属性

属性名称 | 默认值 | 含义
spark.mesos.coarse | true | 如果设置为 true，则以 “粗粒度” 共享模式在 Mesos 集群上运行，其中 Spark 在每台计算机上获取一个长期存在的 Mesos 任务。 如果设置为 false，则以 “细粒度” 共享模式在 Mesos 集群上运行，其中每个 Spark 任务创建一个 Mesos 任务。 'Mesos Run Modes' 中的详细信息。
spark.mesos.extra.cores | 0 | 设置执行程序公布的额外核心数。 这不会导致分配更多的内核。 它代替意味着执行器将“假装”它有更多的核心，以便驱动程序将发送更多的任务。 使用此来增加并行度。 此设置仅用于 Mesos 粗粒度模式。
spark.mesos.mesosExecutor.cores | 1.0 | （仅限细粒度模式）给每个 Mesos 执行器的内核数。 这不包括用于运行 Spark 任务的核心。 换句话说，即使没有运行 Spark 任务，每个 Mesos 执行器将占用这里配置的内核数。 该值可以是浮点数。
spark.mesos.executor.docker.image | (none) | 设置 Spark 执行器将运行的 docker 映像的名称。所选映像必须安装 Spark，以及兼容版本的 Mesos 库。 Spark 在图像中的安装路径可以通过 spark.mesos.executor.home来指定; 可以使用 spark.executorEnv.MESOS_NATIVE_JAVA_LIBRARY 指定 Mesos 库的安装路径。

spark.mesos.executor.docker.volumes
(none)

设置要装入到 Docker 镜像中的卷列表，这是使用 spark.mesos.executor.docker.image 设置的。 此属性的格式是以逗号分隔的映射列表，后面的形式传递到 docker run -v。 这是他们采取的形式 :

[host_path:]container_path[:ro|:rw]

spark.mesos.executor.docker.portmaps
(none)

设置由 Docker 镜像公开的入站端口的列表，这是使用 spark.mesos.executor.docker.image 设置的。

此属性的格式是以逗号分隔的映射列表，格式如下 :

host_port:container_port[:tcp|:udp]

spark.mesos.executor.home | driver side SPARK_HOME | 在 Mesos 中的执行器上设置 Spark 安装目录。 默认情况下，执行器将只使用驱动程序的 Spark 主目录，它们可能不可见。 请注意，这只有当 Spark 二进制包没有通过 spark.executor.uri 指定时才是有意义的。
spark.mesos.executor.memoryOverhead | executor memory * 0.10, with minimum of 384 | 以每个执行程序分配的额外内存量（以 MB 为单位）。 默认情况下，开销将大于 spark.executor.memory 的 384 或 10%。 如果设置，最终开销将是此值。
spark.mesos.uris | (none) | 当驱动程序或执行程序由 Mesos 启动时，要下载到沙箱的 URI 的逗号分隔列表。 这适用于粗粒度和细粒度模式。
spark.mesos.principal | (none) | 设置 Spark 框架将用来与 Mesos 进行身份验证的主体。
spark.mesos.secret | (none) | 设置 Spark 框架将用来与 Mesos 进行身份验证的机密。
spark.mesos.role | * | 设置这个 Spark 框架对 Mesos 的作用。 角色在 Mesos 中用于预留和资源权重共享。

spark.mesos.constraints
(none)

基于属性的约束对 mesos 资源提供。 默认情况下，所有资源优惠都将被接受。

有关属性的更多信息，请参阅 Mesos Attributes & Resources 。

标量约束与 “小于等于” 语义匹配，即约束中的值必须小于或等于资源提议中的值。

范围约束与 “包含” 语义匹配，即约束中的值必须在资源提议的值内。

集合约束与语义的 “子集” 匹配，即约束中的值必须是资源提供的值的子集。

文本约束与 “相等” 语义匹配，即约束中的值必须完全等于资源提议的值。

如果没有作为约束的一部分存在的值，则将接受具有相应属性的任何报价（没有值检查）。

spark.mesos.driver.webui.url | (none) | 设置 Spark Mesos 驱动程序 Web UI URL 以与框架交互。 如果取消设置，它将指向 Spark 的内部 Web UI。
spark.mesos.dispatcher.webui.url | (none) | 设置 Spark Mesos 分派器 Web UI URL 以与框架交互。 如果取消设置，它将指向 Spark 的内部 Web UI。

故障排查和调试

在调试中可以看的地方 : 

Mesos Master 的端口 : 5050

Slaves 应该出现在 Slaves 那一栏

Spark 应用应该出现在框架那一栏

任务应该出现在在一个框架的详情

检查失败任务沙箱的输出和错误

Mesos 的日志

Master 和 Slave 的日志默认在 : /var/log/mesos  目录

常见的陷阱 : 

Spark assembly 不可达 / 不可访问

Slave 必须可以从你给的 http://，hdfs:// 或者 s3n:// URL 地址下载的到 Spark 的二进制包

防火墙拦截通讯

检查信息是否是连接失败

临时禁用防火墙来调试，然后戳出适当的漏洞

Spark on YARN

原文链接 : http://spark.apache.org/docs/latest/running-on-yarn.html

译文链接 : http://www.apache.wiki/display/Spark/Spark+on+YARN

贡献者 : 小瑶，多啦a梦 ，那伊抹微笑

支持在 YARN （Hadoop NextGen） 上运行是在 Spark 0.6.0 版本中加入到 Spark 中的，并且在后续的版本中得到改进的。

Spark on YARN 运行

确保 HADOOP_CONF_DIR 或者 YARN_CONF_DIR 指向包含 Hadoop 集群的（客户端）配置文件的目录。这些配置被用于写入 HDFS 并连接到 YARN ResourceManager 。此目录中包含的配置将被分发到 YARN 集群，以便应用程序（application）使用的所有的所有容器（containers）都使用相同的配置。如果配置引用了 Java 系统属性或者未由 YARN 管理的环境变量，则还应在 Spark 应用程序的配置（驱动程序（driver），执行程序（executors），和在客户端模式下运行时的 AM ）。

有两种部署模式可以用于在 YARN 上启动 Spark 应用程序。在集群模式下， Spark 驱动程序 （Spark driver） 运行在集群上由 YARN 管理的应用程序主进程 （master process） 内，并且客户端可以在初始化应用程序后离开。在客户端模式下，驱动程序在客户端进程中运行，并且应用程序主服务器仅用于从 YARN 请求资源。

与 Spark 独立模式 和 Mesos 模式不同的是，在这两种模式中，master 的地址在 --master 参数中指定，在 YARN 模式下， ResourceManager 的地址从 Hadoop 配置中选取。因此， --master 参数是 yarn 。

在集群模式下启动 Spark 应用程序 : 

$ ./bin/spark-submit --class path.to.your.Class --master yarn --deploy-mode cluster [options] <app jar> [app options]

例如 : 

$ ./bin/spark-submit --class org.apache.spark.examples.SparkPi \
    --master yarn \
    --deploy-mode cluster \
    --driver-memory 4g \
    --executor-memory 2g \
    --executor-cores 1 \
    --queue thequeue \
    lib/spark-examples*.jar \
    10

以上启动一个 YARN 客户端程序，启动默认的主应用程序（Application Master）。然后 SparkPi 将作为 Application Master 的子进程运行。客户端将定期轮询 Application Master 以获取状态的更新并在控制台中显示它们。一旦您的应用程序完成运行后，客户端将退出。请参阅下面的 "调试您的应用程序（Debugging your Application）" 部分，了解如何查看驱动程序（driver） 和 执行程序日志（executor logs）。

要在客户端模式下启动 Spark 应用程序，请执行相同的操作，但是将 client 替换 cluster 。下面展示了如何在客户端模式下运行 spark-shell : 

$ ./bin/spark-shell --master yarn --deploy-mode client

添加其他的 JARs

在集群模式下，驱动程序（driver）在与客户端不同的机器上运行，因此 SparkContext.addJar 将不会立即使用客户端本地的文件运行。要使客户端上的文件可用于 SparkContext.addJar ，请在启动命令中使用 --jars 选项来包含这些文件。

$ ./bin/spark-submit --class my.main.Class \
    --master yarn \
    --deploy-mode cluster \
    --jars my-other-jar.jar,my-other-other-jar.jar \
    my-main-jar.jar \
    app_arg1 app_arg2

准备

在 YARN 上运行 Spark 需要使用 YARN 支持构建的二进制分布式的 Spark （a binary distribution of Spark）。二进制文件（binary distributions）可以从项目网站的 下载页面 下载。要自己构建 Spark ，请参考 构建 Spark 。

要使 Spark 运行时 jars 可以从 YARN 端访问，您可以指定 spark.yarn.archive 或者 spark.yarn.jars 。有关详细的信息，请参阅 Spark 属性 。如果既没有指定 spark.yarn.archive 也没有指定 spark.yarn.jars ，Spark 将在$SPARK_HOME/jars 目录下创建一个包含所有 jar 的 zip 文件，并将其上传到分布式缓存（distributed cache）中。

Spark on YARN 配置

对于 Spark on YARN 和其他的部署模式，大多数的配置是相同的。有关这些的更多信息，请参阅 Spark 配置 。这些是特定于 YARN 上的 Spark 的配置。

调试应用

在 YARN 术语中，executors（执行器）和 application masters 在 “containers（容器）” 中运行时。 YARN 有两种模式用于在应用程序完成后处理容器日志（container logs）。如果启用日志聚合（aggregation）（使用 yarn.log-aggregation-enable 配置），容器日志（container logs）将复制到 HDFS 并在本地计算机上删除。可以使用 yarn logs 命令从集群中的任何位置查看这些日志。

yarn logs -applicationId <app ID>

将打印来自给定的应用程序的所有容器（containers）的所有的日志文件的内容。你还可以使用 HDFS shell 或者 API 直接在 HDFS 中查看容器日志文件（container log files）。可以通过查看您的 YARN 配置（yarn.nodemanager.remote-app-log-dir 和 yarn.nodemanager.remote-app-log-dir-suffix）找到它们所在的目录。日志还可以在 Spark Web UI 的 “执行程序（Executors）”选项卡下找到。您需要同时运行 Spark 历史记录服务器（Spark history server） 和 MapReduce 历史记录服务器（MapReduce history server），并在 yarn-site.xml 文件中正确配置 yarn.log.server.url 。 Spark 历史记录服务器 UI 上的日志将重定向您到 MapReduce 历史记录服务器以显示聚合日志（aggregated logs）。

当未启用日志聚合时，日志将在每台计算机上的本地保留在 YARN_APP_LOGS_DIR 目录下，通常配置为 /tmp/logs 或者 $HADOOP_HOME/logs/userlogs ，具体取决于 Hadoop 版本和安装。查看容器（container）的日志需要转到包含它们的主机并在此目录中查看它们。子目录根据应用程序 ID （application ID）和 容器 ID （container ID）组织日志文件。日志还可以在 Spark Web UI 的 “执行程序（Executors）”选项卡下找到，并且不需要运行 MapReduce 历史记录服务器。

要查看每个容器的启动环境，请将 yarn.nodemanager.delete.debug-delay-sec 增加到一个较大的值（例如 36000），然后通过 yarn.nodemanager.local-dirs 访问应用程序缓存，在容器启动的节点上。此目录包含启动脚本（launch script）， JARs ，和用于启动每个容器的所有的环境变量。这个过程对于调试 classpath 问题特别有用。（请注意，启用此功能需要集群设置的管理员权限并且还需要重新启动所有的 node managers，因此这不适用于托管集群）。

要为 application master 或者 executors 使用自定义的 log4j 配置，请选择以下选项 : 

使用 spark-submit 上传一个自定义的 log4j.properties ，通过将 spark-submit 添加到要与应用程序一起上传的文件的 --files 列表中。

添加 -Dlog4j.configuration=<配置文件的位置（location of configuration file）> 到 spark.driver.extraJavaOptions （对于驱动程序）或者 containers （对于执行者）。请注意，如果使用文件，文件 : 协议（protocol ）应该被显式提供，并且该文件需要在所有节点的本地存在。

更新 $SPARK_CONF_DIR/log4j.properties 文件，并且它将与其他配置一起自动上传。请注意，如果指定了多个选项，其他 2 个选项的优先级高于此选项。

请注意，对于第一个选项，执行器（executors）和 主应用程序（application master）将共享相同的 log4j 配置，这当它们在同一个节点上运行的时候，可能会导致问题（例如，试图写入相同的日志文件）。

如果你需要引用正确的位置将日志文件放在 YARN 中，以便 YARN 可以正确显示和聚合它们，请在您的 log4j.properties 中使用 spark.yarn.app.container.log.dir 。例如，log4j.appender.file_appender.File=${spark.yarn.app.container.log.dir}/spark.log 。对于流应用程序（streaming applications），配置 RollingFileAppender 并将文件位置设置为 YARN 的日志目录将避免由于大型日志文件导致的磁盘溢出，并且可以使用 YARN 的日志实用程序（YARN’s log utility）访问日志。

要为主应用程序（application master）和 执行器（executors），请更新 $SPARK_CONF_DIR/metrics.properties 文件。它将自动与其他配置一起上传，因此您不需要使用 --files 手动指定它。

Spark 属性

属性名称 | 默认值 | 含义
spark.yarn.am.memory | 512m | 在客户端模式下用于 YARN Application Master 的内存量，与 JVM 内存字符串格式相同（例如，512m，2g）。在集群模式下， 请改用 spark.driver.memory。 使用小写字母尾后缀，例如，k，m，g，t 和 p，分别表示 kibi-，mebi-，gibi-，tebi-，和 pebibytes。
spark.driver.memory | 1g | 用于驱动程序进程（driver process）的内存量，即初始化 SparkContext 的位置。（例如 1g，2g）。 注意：在客户端模式下，不能通过 SparkConf 直接在应用程序中设置此配置，因为驱动程序 JVM 已在此时启动。相反， 请通过 --driver-memory 命令选项或者在默认属性文件中进行设置。
spark.driver.cores | 1 | 驱动程序在 YARN 集群模式下使用的核数。由于驱动程序在集群模式下在与 YARN Application Master 相同的 JVM 中 运行，因此还会控制 YARN Application Master 使用的核数。在客户端模式下，使用 spark.yarn.am.cores 来控制 YARN Application Master 使用的核数。
spark.yarn.am.cores | 1 | 在客户端模式下用于 YARN Application Master 的核数。在集群模式下，请改用 spark.driver.cores 。
spark.yarn.am.waitTime | 100s | 在集群模式下，YARN Application Master 等待 SparkContext 初始化的时间。在客户端模式下，YARN Application Master 等待驱动程序（driver）连接到它的时间。
spark.yarn.submit.file.replication | HDFS 默认的备份数 （通常为 3） | 用于应用程序上传到 HDFS 的文件的 HDFS 副本级别。这些包括诸如 Spark jar ，app jar， 和任何分布式缓存文件/归档 之类的东西。
spark.yarn.stagingDir | 文件系统中当前用户的 主目录 | 提交应用程序时使用的临时目录。
spark.yarn.preserve.staging.files | false | 设置为 true 以便在作业结束保留暂存文件（Spark jar， app jar，分布式缓存文件），而不是删除它们。
spark.yarn.scheduler.heartbeat.interval-ms | 3000 | Spark application master 心跳到 YARN ResourceManager 中的间隔（以毫秒为单位）。该值的上限为到期时间间隔的 YARN 配置值的一半，即 yarn.am.liveness-monitor.expiry-interval-ms 。
spark.yarn.scheduler.initial-allocation.interval | 200ms | 当存在未决容器分配请求时，Spark application master 心跳到 YARN ResourceManager 的初始间隔。它不应大于 spark.yarn .scheduler.heartbeat.interval-ms。如果挂起的容器仍然存在，直到达到 spark.yarn.scheduler.heartbeat.interval-ms，则连续 的心跳的分配间隔将加倍。
spark.yarn.max.executor.failures | numExecutors * 2，最小值为 3 | 在应用程序失败（failing the application）之前，执行器失败（executor failures）的最大数量。

spark.yarn.historyServer.address
(none)

Spark 历史记录服务器（history server）的地址，例如 host.com:18080 。地址不应包含 scheme （http://）。默认为未设置，因为历史记录服务器是可选服务。当 Spark 应用程序完成将应用程序从 ResourceManager UI 链接到 Spark 历史记录服务器 UI 时，此地址将提供给 YARN

ResourceManager 。对于此属性， YARN 属性可用作变量，这些属性在运行时由 Spark 替换。例如，如果 Spark 历史记录服务器在与 YARN ResourceManager 相同的节点上运行，则可以将其设置为 ${hadoopconf-yarn.resourcemanager.hostname}:18080。

spark.yarn.dist.archives | (none) | 逗号分隔的要提取到每个执行器（executor）的工作目录（working directory）中的归档列表。
spark.yarn.dist.files | (none) | 要放到每个执行器（executor）的工作目录（working directory）中的以逗号分隔的文件列表。
spark.yarn.dist.jars | (none) | 要放到每个执行器（executor）的工作目录（working directory）中的以逗号分隔的 jar 文件列表。
spark.executor.cores | 在 YARN 模式下是 1 ，在独立模式下 worker 机器上的所有的可用的核。 | 每个执行器（executor）上使用的核数。仅适用于 YARN 和独立（standalone ）模式。
spark.executor.instances | 2 | 静态分配的执行器（executor）数量。使用 spark.dynamicAllocation.enabled ，初始的执行器（executor）集至少会是这么大。
spark.executor.memory | 1g | 每个执行器（executor）进程使用的内存量（例如 2g ，8g）。
spark.yarn.executor.memoryOverhead | 执行器内存（executorMemory）* 0.10 ，最小值为 384 | 要为每个执行器（executor）分配的堆外（off-heap）内存量（以兆字节为单位）。这是内存，例如 VM 开销，内部字符串，其他本机开销等。这往往随着执行器（executor）大小（通常为 6-10%）增长。
spark.yarn.driver.memoryOverhead | 驱动程序内存（driverMemory）* 0.10，最小值为 384 | 在集群模式下为每个驱动程序（driver）分配的堆外（off-heap）内存量（以兆字节为单位）。这是内存，例如 VM 开销，内部字符串，其他本机开销等。这往往随着容器（container）大小（通常为 6- 10%）增长。
spark.yarn.am.memoryOverhead | AM 内存（AM Memory） * 0.10，最小 384 | 与 spark.yarn.driver.memoryOverhead 相同，但是适用于客户端模式下的 YARN Application Master 。
spark.yarn.am.port | （random） | 被 YARN Application Master 监听的端口。在 YARN 客户端模式下，这用于在网关（gateway）上运行的 Spark 驱动程序（driver）和在 YARN 上运行的 YARN Application Master 之间进行通信。在 YARN 集群模式下，这用于动态执行器（executor）功能，其中它处理从调度程序后端的 kill 。
spark.yarn.queue | default | 提交应用程序（application）的 YARN 队列名称。
spark.yarn.jars | (none) | 包含要分发到 YARN 容器（container）的 Spark 代码的库（libraries）列表。默认情况下， Spark on YARN 将使用本地安装的 Spark jar，但是 Spark jar 也可以在 HDFS 上的一个任何位置都可读的位置。这允许 YARN 将其缓存在节点上，使得它不需要在每次运行应用程序时分发。例如，要指向 HDFS 上的 jars ，请将此配置设置为 hdfs:///some/path。允许使用 globs 。
spark.yarn.archive | (none) | 包含所需的 Spark jar 的归档（archive），以分发到 YARN 高速缓存。如果设置，此配置将替换 spark.yarn.jars，并且归档（archive）在所有的应用程序（application）的容器（container）中使用。归档（archive）应该在其根目录中包含 jar 文件。与以前的选项一样，归档也可以托管在 HDFS 上以加快文件分发速度。

spark.yarn.access.namenodes
(none)
以逗号分隔的 Spark 应用程序要访问的安全的 HDFS namenodes 列表。例如 : spark.yarn.access.namenodes=hdfs://nn1.com:8032,hdfs://nn2.com:8032，webhdfs://nn3.com:50070。Spark 应用程序必须具有对列出的 namenode 的访问权限，并且 Kerberos 必须正确配置为能够访问它们（在同一领域（realm）或者在可信领域（trusted realm））。 Spark 为每个 namenode 获取安全令牌（security tokens），以便 Spark application 可以访问这些远程的 HDFS 集群。

spark.yarn.appMasterEnv.[EnvironmentVariableName] | (none) | 将由 EnvironmentVariableName 指定的环境变量添加到在 YARN 上启动的 Application Master 进程。用户可以指定其中的多个并设置多个环境变量。在集群模式下，这控制 Spark 驱动程序（driver）的环境，在客户端模式下，它只控制执行器（executor）启动器（launcher）的环境。
spark.yarn.containerLauncherMaxThreads | 25 | 在 YARN Application Master 中用于启动执行器容器（executor containers）的最大线程（thread）数。
spark.yarn.am.extraJavaOptions | (none) | 在客户端模式下传递到 YARN Application Master 的一组额外的 JVM 选项。在集群模式下，请改用 spark.driver.extraJavaOptions。请注意，使用此选项设置最大堆大小（-Xmx）设置是非法的。最大堆大小设置可以使用 spark.yarn.am.memory。
spark.yarn.am.extraLibraryPath | (none) | 设置在客户端模式下启动 YARN Application Master 时要使用的特殊库路径（special library path）。
spark.yarn.maxAppAttempts | 在 YARN 中 是 yarn.resourcemanager.am.max-attempts | 将要提交应用程序的最大的尝试次数。它应该不大于 YARN 配置中的全局最大尝试次数。
spark.yarn.am.attemptFailuresValidityInterval | (none) | 定义 AM 故障跟踪（failure tracking）的有效性间隔。如果 AM 已经运行至少所定义的间隔，则 AM 故障计数将被重置。如果未配置此功能，则不启用此功能，并且仅在 Hadoop 2.6 + 版本中支持此功能。
spark.yarn.executor.failuresValidityInterval | (none) | 定义执行器（executor）故障跟踪的有效性间隔。超过有效性间隔的执行器故障（executor failures）将被忽略。
spark.yarn.submit.waitAppCompletion | true | 在 YARN 集群模式下，控制客户端是否等待退出，直到应用程序完成。如果设置为 true ，客户端将保持报告应用程序的状态。否则，客户端进程将在提交后退出。
spark.yarn.am.nodeLabelExpression | (none) | 一个将调度限制节点 AM 集合的 YARN 节点标签表达式。只有大于或等于 2.6 版本的 YARN 版本支持节点标签表达式，因此在对较早版本运行时，此属性将被忽略。
spark.yarn.executor.nodeLabelExpression | (none) | 一个将调度限制节点执行器（executor）集合的 YARN 节点标签表达式。只有大于或等于 2.6 版本的 YARN 版本支持节点标签表达式，因此在对较早版本运行时，此属性将被忽略。
spark.yarn.tags | (none) | 以逗号分隔的字符串列表，作为 YARN application 标记中显示的 YARN application 标记传递，可用于在查询 YARN apps 时进行过滤（filtering ）。
spark.yarn.keytab | (none) | 包含上面指定的主体（principal）的 keytab 的文件的完整路径。此 keytab 将通过安全分布式缓存（Secure Distributed Cache）复制到运行 YARN Application Master 的节点，以定期更新 login tickets 和 delegation tokens。（运行也使用 “local” master）。
spark.yarn.principal | (none) | 在安全的 HDFS 上运行时用于登录 KDC 的主体（Principal ）。（运行也使用 “local” master）。

spark.yarn.config.gatewayPath
(none)

在网关主机（gateway host）（启动 Spark application 的 host）上有效的路径，但对于集群中其他节点中相同资源的路径可能不同。结合 spark.yarn.config.replacementPath，者用于支持具有异构配置的集群，以便 Spark 可以正确启动远程进程。替换路径（replacement path）通常将包含对由 YARN 导出的某些环境变量（以及，因此对于 Spark 容器可见）的引用。

例如，如果网关节点（gateway node）在 /disk1/hadoop 上安装了 Hadoop 库，并且 Hadoop 安装的位置由 YARN 作为 HADOOP_HOME 环境变量导出，则将此值设置为 /disk1/hadoop ，将替换路径（replacement path）设置为 $HADOOP_HOME 将确保用于启动远程进程的路径正确引用本地 YARN 配置。

spark.yarn.config.replacementPath | (none) | 查看 spark.yarn.config.gatewayPath。
spark.yarn.security.tokens.${service}.enabled | true | 控制是否在启用安全性时检索非 HDFS 服务的 delegation tokens 。默认情况下，当配置了这些服务时，将检索所有支持的服务的 delegation tokens ，但是如果它以某种方式与正在运行的应用程序冲突，则可以禁用该行为。 目前支持的服务有 : hive，hbase 。

重要提示

核心请求在调度决策中是否得到执行取决于使用的调度程序及其配置方式。

在集群模式下，Spark executors 和 Spark dirver 使用的本地目录是为 YARN（Hadoop YARN 配置 yarn.nodemanager.local-dirs）配置的本地目录。如果用户指定 spark.local.dir，它将被忽略。在客户端模式下，Spark executors 将使用为 YARN 配置的本地目录，而 Spark dirver 将使用 spark.local.dir 中定义的目录。这是因为 Spark dirver 不在客户端模式下在 YARN 集群上运行，只有 Spark executors。

--files 和 --archives 选项支持用 # 指定文件名，类似于 Hadoop。 例如，你可以指定 : --files localtest.txt＃appSees.txt，这会将你在本地命名为 localtest.txt 的文件上传到 HDFS，但这将通过名称 appSees.txt 链接，当你的应用程序在 YARN 上运行时，你应该使用名称 appSees.txt 引用它。

--jars 选项允许你在集群模式下使用本地文件时运行 SparkContext.addJar 函数。 如果你使用 HDFS，HTTP，HTTPS 或 FTP 文件，则不需要使用它。

在一个安全的集群中运行

如 security 所讲的，Kerberos 被应用在安全的 Hadoop 集群中去验证与服务和客户端相关联的 principals。 这允许客户端请求这些已验证的服务; 向授权的 principals 授予请求服务的权利。

Hadoop 服务发出 hadoop tokens 去授权访问服务和数据。 客户端必须首先获取它们将要访问的服务的 tokens，当启动应用程序时，将它和应用程序一起发送到 YAYN 集群中。

如果 Spark 应用程序与 HDFS，HBase 和 Hive 进行交互，它必须使用启动应用程序的用户的 Kerberos 凭据获取相关 tokens，也就是说身份将成为已启动的 Spark 应用程序的 principal。

这通常在启动时完成 : 在安全集群中，Spark 将自动为集群的 HDFS 文件系统获取 tokens，也可能为 HBase 和 Hive 获取。

如果 HBase 在类路径中，HBase 配置声明应用程序是安全的（即 hbase-site.xml 将 hbase.security.authentication 设置为 kerberos），并且 spark.yarn.security.tokens.hbase.enabled 未设置为 false，HBase tokens 将被获得。

类似地，如果 Hive 在类路径上，其配置包括元数据存储的 URI（hive.metastore.uris），并且 spark.yarn.security.tokens.hive.enabled 未设置为 false，则将获得 Hive token（令牌）。

如果应用程序需要与其他安全 HDFS 集群交互，则在启动时必须显式请求访问这些集群所需的 tokens。 这是通过将它们列在 spark.yarn.access.namenodes 属性中来实现的。

spark.yarn.access.namenodes hdfs://ireland.example.org:8020/,hdfs://frankfurt.example.org:8020/

Spark 支持通过 Java Services 机智（请看 java.util.ServiceLoader）与其它的具有安全性的服务来进行集成。为了实现该目标，通过在 jar 的 META-INF/services 目录中列出相应 org.apache.spark.deploy.yarn.security.ServiceCredentialProvider 的实现的名字就可应用到 Spark。这些插件可以通过设置 spark.yarn.security.tokens.{service}.enabled 为 false 来禁用，这里的 {service} 是 credential provider（凭证提供者）的名字。

用 Apache Oozie 来运行应用程序

Apache Oozie 可以将启动 Spark 应用程序作为工作流的一部分。在安全集群中，启动的应用程序将需要相关的 tokens 来访问集群的服务。如果 Spark 使用 keytab 启动，这是自动的。但是，如果 Spark 在没有 keytab 的情况下启动，则设置安全性的责任必须移交给 Oozie。

有关配置 Oozie 以获取安全集群和获取作业凭据的详细信息，请参阅 Oozie web site 上特定版本文档的 “Authentication” 部分。

对于 Spark 应用程序，必须设置 Oozie 工作流以使 Oozie 请求应用程序需要的所有 tokens，包括：

YARN 资源管理器。

本地 HDFS 文件系统。

任何用作 I/O 的源或目标的远程 HDFS 文件系统。

Hive -如果使用。

HBase - 如果使用。

YARN 时间轴服务器，如果应用程序与此交互。

为了避免 Spark 尝试 - 然后失败 - 获取 Hive，HBase 和远程 HDFS 令牌，必须将 Spark 配置收集这些服务 tokens 的选项设置为禁用。

Spark 配置必须包含以下行 : 

spark.yarn.security.tokens.hive.enabled   false
spark.yarn.security.tokens.hbase.enabled  false

必须取消设置配置选项 spark.yarn.access.namenodes。

Kerberos 故障排查

调试 Hadoop / Kerberos 问题可能是 “困难的”。 一个有用的技术是通过设置 HADOOP_JAAS_DEBUG 环境变量在 Hadoop 中启用对 Kerberos 操作的额外记录。

bash export HADOOP_JAAS_DEBUG=true

JDK 类可以配置为通过系统属性 sun.security.krb5.debug 和 sun.security.spnego.debug = true 启用对 Kerberos 和 SPNEGO / REST 认证的额外日志记录。

-Dsun.security.krb5.debug=true -Dsun.security.spnego.debug=true

所有这些选项都可以在 Application Master 中启用 : 

spark.yarn.appMasterEnv.HADOOP_JAAS_DEBUG true
spark.yarn.am.extraJavaOptions -Dsun.security.krb5.debug=true -Dsun.security.spnego.debug=true

最后，如果 org.apache.spark.deploy.yarn.Client 的日志级别设置为 DEBUG，日志将包括获取的所有 tokens 的列表，以及它们的到期详细信息。

Spark on YARN 上运行

支持在 YARN （Hadoop NextGen） 上运行是在 Spark 0.6.0 版本中加入到 Spark 中的，并且在后续的版本中得到改进的。

Spark on YARN 运行

确保 HADOOP_CONF_DIR 或者 YARN_CONF_DIR 指向包含 Hadoop 集群的（客户端）配置文件的目录。这些配置被用于写入 HDFS 并连接到 YARN ResourceManager 。此目录中包含的配置将被分发到 YARN 集群，以便应用程序（application）使用的所有的所有容器（containers）都使用相同的配置。如果配置引用了 Java 系统属性或者未由 YARN 管理的环境变量，则还应在 Spark 应用程序的配置（驱动程序（driver），执行程序（executors），和在客户端模式下运行时的 AM ）。

有两种部署模式可以用于在 YARN 上启动 Spark 应用程序。在集群模式下， Spark 驱动程序 （Spark driver） 运行在集群上由 YARN 管理的应用程序主进程 （master process） 内，并且客户端可以在初始化应用程序后离开。在客户端模式下，驱动程序在客户端进程中运行，并且应用程序主服务器仅用于从 YARN 请求资源。

与 Spark 独立模式 和 Mesos 模式不同的是，在这两种模式中，master 的地址在 --master 参数中指定，在 YARN 模式下， ResourceManager 的地址从 Hadoop 配置中选取。因此， --master 参数是 yarn 。

在集群模式下启动 Spark 应用程序 : 

$ ./bin/spark-submit --class path.to.your.Class --master yarn --deploy-mode cluster [options] <app jar> [app options]

例如 : 

$ ./bin/spark-submit --class org.apache.spark.examples.SparkPi \
    --master yarn \
    --deploy-mode cluster \
    --driver-memory 4g \
    --executor-memory 2g \
    --executor-cores 1 \
    --queue thequeue \
    lib/spark-examples*.jar \
    10

以上启动一个 YARN 客户端程序，启动默认的主应用程序（Application Master）。然后 SparkPi 将作为 Application Master 的子进程运行。客户端将定期轮询 Application Master 以获取状态的更新并在控制台中显示它们。一旦您的应用程序完成运行后，客户端将退出。请参阅下面的 "调试您的应用程序（Debugging your Application）" 部分，了解如何查看驱动程序（driver） 和 执行程序日志（executor logs）。

要在客户端模式下启动 Spark 应用程序，请执行相同的操作，但是将 client 替换 cluster 。下面展示了如何在客户端模式下运行 spark-shell : 

$ ./bin/spark-shell --master yarn --deploy-mode client

添加其他的 JARs

在集群模式下，驱动程序（driver）在与客户端不同的机器上运行，因此 SparkContext.addJar 将不会立即使用客户端本地的文件运行。要使客户端上的文件可用于 SparkContext.addJar ，请在启动命令中使用 --jars 选项来包含这些文件。

$ ./bin/spark-submit --class my.main.Class \
    --master yarn \
    --deploy-mode cluster \
    --jars my-other-jar.jar,my-other-other-jar.jar \
    my-main-jar.jar \
    app_arg1 app_arg2

准备

在 YARN 上运行 Spark 需要使用 YARN 支持构建的二进制分布式的 Spark （a binary distribution of Spark）。二进制文件（binary distributions）可以从项目网站的 下载页面 下载。要自己构建 Spark ，请参考 构建 Spark 。

要使 Spark 运行时 jars 可以从 YARN 端访问，您可以指定 spark.yarn.archive 或者 spark.yarn.jars 。有关详细的信息，请参阅 Spark 属性 。如果既没有指定 spark.yarn.archive 也没有指定 spark.yarn.jars ，Spark 将在 $SPARK_HOME/jars 目录下创建一个包含所有 jar 的 zip 文件，并将其上传到分布式缓存（distributed cache）中。

Spark on YARN 配置

对于 Spark on YARN 和其他的部署模式，大多数的配置是相同的。有关这些的更多信息，请参阅 Spark 配置 。这些是特定于 YARN 上的 Spark 的配置。

调试应用

在 YARN 术语中，executors（执行器）和 application masters 在 “containers（容器）” 中运行时。 YARN 有两种模式用于在应用程序完成后处理容器日志（container logs）。如果启用日志聚合（aggregation）（使用 yarn.log-aggregation-enable 配置），容器日志（container logs）将复制到 HDFS 并在本地计算机上删除。可以使用 yarn logs 命令从集群中的任何位置查看这些日志。

yarn logs -applicationId <app ID>

将打印来自给定的应用程序的所有容器（containers）的所有的日志文件的内容。你还可以使用 HDFS shell 或者 API 直接在 HDFS 中查看容器日志文件（container log files）。可以通过查看您的 YARN 配置（yarn.nodemanager.remote-app-log-dir 和 yarn.nodemanager.remote-app-log-dir-suffix）找到它们所在的目录。日志还可以在 Spark Web UI 的 “执行程序（Executors）”选项卡下找到。您需要同时运行 Spark 历史记录服务器（Spark history server） 和 MapReduce 历史记录服务器（MapReduce history server），并在 yarn-site.xml 文件中正确配置 yarn.log.server.url 。 Spark 历史记录服务器 UI 上的日志将重定向您到 MapReduce 历史记录服务器以显示聚合日志（aggregated logs）。

当未启用日志聚合时，日志将在每台计算机上的本地保留在 YARN_APP_LOGS_DIR 目录下，通常配置为 /tmp/logs 或者 $HADOOP_HOME/logs/userlogs ，具体取决于 Hadoop 版本和安装。查看容器（container）的日志需要转到包含它们的主机并在此目录中查看它们。子目录根据应用程序 ID （application ID）和 容器 ID （container ID）组织日志文件。日志还可以在 Spark Web UI 的 “执行程序（Executors）”选项卡下找到，并且不需要运行 MapReduce 历史记录服务器。

要查看每个容器的启动环境，请将 yarn.nodemanager.delete.debug-delay-sec 增加到一个较大的值（例如 36000），然后通过 yarn.nodemanager.local-dirs 访问应用程序缓存，在容器启动的节点上。此目录包含启动脚本（launch script）， JARs ，和用于启动每个容器的所有的环境变量。这个过程对于调试 classpath 问题特别有用。（请注意，启用此功能需要集群设置的管理员权限并且还需要重新启动所有的 node managers，因此这不适用于托管集群）。

要为 application master 或者 executors 使用自定义的 log4j 配置，请选择以下选项 : 

使用 spark-submit 上传一个自定义的 log4j.properties ，通过将 spark-submit 添加到要与应用程序一起上传的文件的 --files 列表中。

添加 -Dlog4j.configuration=<配置文件的位置（location of configuration file）> 到 spark.driver.extraJavaOptions （对于驱动程序）或者 containers （对于执行者）。请注意，如果使用文件，文件 : 协议（protocol ）应该被显式提供，并且该文件需要在所有节点的本地存在。

更新 $SPARK_CONF_DIR/log4j.properties 文件，并且它将与其他配置一起自动上传。请注意，如果指定了多个选项，其他 2 个选项的优先级高于此选项。

请注意，对于第一个选项，执行器（executors）和 主应用程序（application master）将共享相同的 log4j 配置，这当它们在同一个节点上运行的时候，可能会导致问题（例如，试图写入相同的日志文件）。

如果你需要引用正确的位置将日志文件放在 YARN 中，以便 YARN 可以正确显示和聚合它们，请在您的 log4j.properties 中使用 spark.yarn.app.container.log.dir 。例如，log4j.appender.file_appender.File=${spark.yarn.app.container.log.dir}/spark.log 。对于流应用程序（streaming applications），配置 RollingFileAppender 并将文件位置设置为 YARN 的日志目录将避免由于大型日志文件导致的磁盘溢出，并且可以使用 YARN 的日志实用程序（YARN’s log utility）访问日志。

要为主应用程序（application master）和 执行器（executors），请更新 $SPARK_CONF_DIR/metrics.properties 文件。它将自动与其他配置一起上传，因此您不需要使用 --files 手动指定它。

Spark 属性

属性名称 | 默认值 | 含义
spark.yarn.am.memory | 512m | 在客户端模式下用于 YARN Application Master 的内存量，与 JVM 内存字符串格式相同（例如，512m，2g）。在集群模式下， 请改用 spark.driver.memory。 使用小写字母尾后缀，例如，k，m，g，t 和 p，分别表示 kibi-，mebi-，gibi-，tebi-，和 pebibytes。
spark.driver.memory | 1g | 用于驱动程序进程（driver process）的内存量，即初始化 SparkContext 的位置。（例如 1g，2g）。 注意：在客户端模式下，不能通过 SparkConf 直接在应用程序中设置此配置，因为驱动程序 JVM 已在此时启动。相反， 请通过 --driver-memory 命令选项或者在默认属性文件中进行设置。
spark.driver.cores | 1 | 驱动程序在 YARN 集群模式下使用的核数。由于驱动程序在集群模式下在与 YARN Application Master 相同的 JVM 中 运行，因此还会控制 YARN Application Master 使用的核数。在客户端模式下，使用 spark.yarn.am.cores 来控制 YARN Application Master 使用的核数。
spark.yarn.am.cores | 1 | 在客户端模式下用于 YARN Application Master 的核数。在集群模式下，请改用 spark.driver.cores 。
spark.yarn.am.waitTime | 100s | 在集群模式下，YARN Application Master 等待 SparkContext 初始化的时间。在客户端模式下，YARN Application Master 等待驱动程序（driver）连接到它的时间。
spark.yarn.submit.file.replication | HDFS 默认的备份数 （通常为 3） | 用于应用程序上传到 HDFS 的文件的 HDFS 副本级别。这些包括诸如 Spark jar ，app jar， 和任何分布式缓存文件/归档 之类的东西。
spark.yarn.stagingDir | 文件系统中当前用户的 主目录 | 提交应用程序时使用的临时目录。
spark.yarn.preserve.staging.files | false | 设置为 true 以便在作业结束保留暂存文件（Spark jar， app jar，分布式缓存文件），而不是删除它们。
spark.yarn.scheduler.heartbeat.interval-ms | 3000 | Spark application master 心跳到 YARN ResourceManager 中的间隔（以毫秒为单位）。该值的上限为到期时间间隔的 YARN 配置值的一半，即 yarn.am.liveness-monitor.expiry-interval-ms 。
spark.yarn.scheduler.initial-allocation.interval | 200ms | 当存在未决容器分配请求时，Spark application master 心跳到 YARN ResourceManager 的初始间隔。它不应大于 spark.yarn .scheduler.heartbeat.interval-ms。如果挂起的容器仍然存在，直到达到 spark.yarn.scheduler.heartbeat.interval-ms，则连续 的心跳的分配间隔将加倍。
spark.yarn.max.executor.failures | numExecutors * 2，最小值为 3 | 在应用程序失败（failing the application）之前，执行器失败（executor failures）的最大数量。

spark.yarn.historyServer.address
(none)

Spark 历史记录服务器（history server）的地址，例如 host.com:18080 。地址不应包含 scheme （http://）。默认为未设置，因为历史记录服务器是可选服务。当 Spark 应用程序完成将应用程序从 ResourceManager UI 链接到 Spark 历史记录服务器 UI 时，此地址将提供给 YARN

ResourceManager 。对于此属性， YARN 属性可用作变量，这些属性在运行时由 Spark 替换。例如，如果 Spark 历史记录服务器在与 YARN ResourceManager 相同的节点上运行，则可以将其设置为 ${hadoopconf-yarn.resourcemanager.hostname}:18080。

spark.yarn.dist.archives | (none) | 逗号分隔的要提取到每个执行器（executor）的工作目录（working directory）中的归档列表。
spark.yarn.dist.files | (none) | 要放到每个执行器（executor）的工作目录（working directory）中的以逗号分隔的文件列表。
spark.yarn.dist.jars | (none) | 要放到每个执行器（executor）的工作目录（working directory）中的以逗号分隔的 jar 文件列表。
spark.executor.cores | 在 YARN 模式下是 1 ，在独立模式下 worker 机器上的所有的可用的核。 | 每个执行器（executor）上使用的核数。仅适用于 YARN 和独立（standalone ）模式。
spark.executor.instances | 2 | 静态分配的执行器（executor）数量。使用 spark.dynamicAllocation.enabled ，初始的执行器（executor）集至少会是这么大。
spark.executor.memory | 1g | 每个执行器（executor）进程使用的内存量（例如 2g ，8g）。
spark.yarn.executor.memoryOverhead | 执行器内存（executorMemory）* 0.10 ，最小值为 384 | 要为每个执行器（executor）分配的堆外（off-heap）内存量（以兆字节为单位）。这是内存，例如 VM 开销，内部字符串，其他本机开销等。这往往随着执行器（executor）大小（通常为 6-10%）增长。
spark.yarn.driver.memoryOverhead | 驱动程序内存（driverMemory）* 0.10，最小值为 384 | 在集群模式下为每个驱动程序（driver）分配的堆外（off-heap）内存量（以兆字节为单位）。这是内存，例如 VM 开销，内部字符串，其他本机开销等。这往往随着容器（container）大小（通常为 6- 10%）增长。
spark.yarn.am.memoryOverhead | AM 内存（AM Memory） * 0.10，最小 384 | 与 spark.yarn.driver.memoryOverhead 相同，但是适用于客户端模式下的 YARN Application Master 。
spark.yarn.am.port | （random） | 被 YARN Application Master 监听的端口。在 YARN 客户端模式下，这用于在网关（gateway）上运行的 Spark 驱动程序（driver）和在 YARN 上运行的 YARN Application Master 之间进行通信。在 YARN 集群模式下，这用于动态执行器（executor）功能，其中它处理从调度程序后端的 kill 。
spark.yarn.queue | default | 提交应用程序（application）的 YARN 队列名称。
spark.yarn.jars | (none) | 包含要分发到 YARN 容器（container）的 Spark 代码的库（libraries）列表。默认情况下， Spark on YARN 将使用本地安装的 Spark jar，但是 Spark jar 也可以在 HDFS 上的一个任何位置都可读的位置。这允许 YARN 将其缓存在节点上，使得它不需要在每次运行应用程序时分发。例如，要指向 HDFS 上的 jars ，请将此配置设置为 hdfs:///some/path。允许使用 globs 。
spark.yarn.archive | (none) | 包含所需的 Spark jar 的归档（archive），以分发到 YARN 高速缓存。如果设置，此配置将替换 spark.yarn.jars，并且归档（archive）在所有的应用程序（application）的容器（container）中使用。归档（archive）应该在其根目录中包含 jar 文件。与以前的选项一样，归档也可以托管在 HDFS 上以加快文件分发速度。

spark.yarn.access.namenodes
(none)
以逗号分隔的 Spark 应用程序要访问的安全的 HDFS namenodes 列表。例如 : spark.yarn.access.namenodes=hdfs://nn1.com:8032,hdfs://nn2.com:8032，webhdfs://nn3.com:50070。Spark 应用程序必须具有对列出的 namenode 的访问权限，并且 Kerberos 必须正确配置为能够访问它们（在同一领域（realm）或者在可信领域（trusted realm））。 Spark 为每个 namenode 获取安全令牌（security tokens），以便 Spark application 可以访问这些远程的 HDFS 集群。

spark.yarn.appMasterEnv.[EnvironmentVariableName] | (none) | 将由 EnvironmentVariableName 指定的环境变量添加到在 YARN 上启动的 Application Master 进程。用户可以指定其中的多个并设置多个环境变量。在集群模式下，这控制 Spark 驱动程序（driver）的环境，在客户端模式下，它只控制执行器（executor）启动器（launcher）的环境。
spark.yarn.containerLauncherMaxThreads | 25 | 在 YARN Application Master 中用于启动执行器容器（executor containers）的最大线程（thread）数。
spark.yarn.am.extraJavaOptions | (none) | 在客户端模式下传递到 YARN Application Master 的一组额外的 JVM 选项。在集群模式下，请改用 spark.driver.extraJavaOptions。请注意，使用此选项设置最大堆大小（-Xmx）设置是非法的。最大堆大小设置可以使用 spark.yarn.am.memory。
spark.yarn.am.extraLibraryPath | (none) | 设置在客户端模式下启动 YARN Application Master 时要使用的特殊库路径（special library path）。
spark.yarn.maxAppAttempts | 在 YARN 中 是 yarn.resourcemanager.am.max-attempts | 将要提交应用程序的最大的尝试次数。它应该不大于 YARN 配置中的全局最大尝试次数。
spark.yarn.am.attemptFailuresValidityInterval | (none) | 定义 AM 故障跟踪（failure tracking）的有效性间隔。如果 AM 已经运行至少所定义的间隔，则 AM 故障计数将被重置。如果未配置此功能，则不启用此功能，并且仅在 Hadoop 2.6 + 版本中支持此功能。
spark.yarn.executor.failuresValidityInterval | (none) | 定义执行器（executor）故障跟踪的有效性间隔。超过有效性间隔的执行器故障（executor failures）将被忽略。
spark.yarn.submit.waitAppCompletion | true | 在 YARN 集群模式下，控制客户端是否等待退出，直到应用程序完成。如果设置为 true ，客户端将保持报告应用程序的状态。否则，客户端进程将在提交后退出。
spark.yarn.am.nodeLabelExpression | (none) | 一个将调度限制节点 AM 集合的 YARN 节点标签表达式。只有大于或等于 2.6 版本的 YARN 版本支持节点标签表达式，因此在对较早版本运行时，此属性将被忽略。
spark.yarn.executor.nodeLabelExpression | (none) | 一个将调度限制节点执行器（executor）集合的 YARN 节点标签表达式。只有大于或等于 2.6 版本的 YARN 版本支持节点标签表达式，因此在对较早版本运行时，此属性将被忽略。
spark.yarn.tags | (none) | 以逗号分隔的字符串列表，作为 YARN application 标记中显示的 YARN application 标记传递，可用于在查询 YARN apps 时进行过滤（filtering ）。
spark.yarn.keytab | (none) | 包含上面指定的主体（principal）的 keytab 的文件的完整路径。此 keytab 将通过安全分布式缓存（Secure Distributed Cache）复制到运行 YARN Application Master 的节点，以定期更新 login tickets 和 delegation tokens。（运行也使用 “local” master）。
spark.yarn.principal | (none) | 在安全的 HDFS 上运行时用于登录 KDC 的主体（Principal ）。（运行也使用 “local” master）。

spark.yarn.config.gatewayPath
(none)

在网关主机（gateway host）（启动 Spark application 的 host）上有效的路径，但对于集群中其他节点中相同资源的路径可能不同。结合 spark.yarn.config.replacementPath，者用于支持具有异构配置的集群，以便 Spark 可以正确启动远程进程。替换路径（replacement path）通常将包含对由 YARN 导出的某些环境变量（以及，因此对于 Spark 容器可见）的引用。

例如，如果网关节点（gateway node）在 /disk1/hadoop 上安装了 Hadoop 库，并且 Hadoop 安装的位置由 YARN 作为 HADOOP_HOME 环境变量导出，则将此值设置为 /disk1/hadoop ，将替换路径（replacement path）设置为 $HADOOP_HOME 将确保用于启动远程进程的路径正确引用本地 YARN 配置。

spark.yarn.config.replacementPath | (none) | 查看 spark.yarn.config.gatewayPath。
spark.yarn.security.tokens.${service}.enabled | true | 控制是否在启用安全性时检索非 HDFS 服务的 delegation tokens 。默认情况下，当配置了这些服务时，将检索所有支持的服务的 delegation tokens ，但是如果它以某种方式与正在运行的应用程序冲突，则可以禁用该行为。 目前支持的服务有 : hive，hbase 。

重要提示

核心请求在调度决策中是否得到执行取决于使用的调度程序及其配置方式。

在集群模式下，Spark executors 和 Spark dirver 使用的本地目录是为 YARN（Hadoop YARN 配置 yarn.nodemanager.local-dirs）配置的本地目录。如果用户指定 spark.local.dir，它将被忽略。在客户端模式下，Spark executors 将使用为 YARN 配置的本地目录，而 Spark dirver 将使用 spark.local.dir 中定义的目录。这是因为 Spark dirver 不在客户端模式下在 YARN 集群上运行，只有 Spark executors。

--files 和 --archives 选项支持用 # 指定文件名，类似于 Hadoop。 例如，你可以指定 : --files localtest.txt＃appSees.txt，这会将你在本地命名为 localtest.txt 的文件上传到 HDFS，但这将通过名称 appSees.txt 链接，当你的应用程序在 YARN 上运行时，你应该使用名称 appSees.txt 引用它。

--jars 选项允许你在集群模式下使用本地文件时运行 SparkContext.addJar 函数。 如果你使用 HDFS，HTTP，HTTPS 或 FTP 文件，则不需要使用它。

在一个安全的集群中运行

如 security 所讲的，Kerberos 被应用在安全的 Hadoop 集群中去验证与服务和客户端相关联的 principals。 这允许客户端请求这些已验证的服务; 向授权的 principals 授予请求服务的权利。

Hadoop 服务发出 hadoop tokens 去授权访问服务和数据。 客户端必须首先获取它们将要访问的服务的 tokens，当启动应用程序时，将它和应用程序一起发送到 YAYN 集群中。

如果 Spark 应用程序与 HDFS，HBase 和 Hive 进行交互，它必须使用启动应用程序的用户的 Kerberos 凭据获取相关 tokens，也就是说身份将成为已启动的 Spark 应用程序的 principal。

这通常在启动时完成 : 在安全集群中，Spark 将自动为集群的 HDFS 文件系统获取 tokens，也可能为 HBase 和 Hive 获取。

如果 HBase 在类路径中，HBase 配置声明应用程序是安全的（即 hbase-site.xml 将 hbase.security.authentication 设置为 kerberos），并且 spark.yarn.security.tokens.hbase.enabled 未设置为 false，HBase tokens 将被获得。

类似地，如果 Hive 在类路径上，其配置包括元数据存储的 URI（hive.metastore.uris），并且 spark.yarn.security.tokens.hive.enabled 未设置为 false，则将获得 Hive token（令牌）。

如果应用程序需要与其他安全 HDFS 集群交互，则在启动时必须显式请求访问这些集群所需的 tokens。 这是通过将它们列在 spark.yarn.access.namenodes 属性中来实现的。

spark.yarn.access.namenodes hdfs://ireland.example.org:8020/,hdfs://frankfurt.example.org:8020/

Spark 支持通过 Java Services 机智（请看 java.util.ServiceLoader）与其它的具有安全性的服务来进行集成。为了实现该目标，通过在 jar 的 META-INF/services 目录中列出相应 org.apache.spark.deploy.yarn.security.ServiceCredentialProvider 的实现的名字就可应用到 Spark。这些插件可以通过设置 spark.yarn.security.tokens.{service}.enabled 为 false 来禁用，这里的 {service} 是 credential provider（凭证提供者）的名字。

用 Apache Oozie 来运行应用程序

Apache Oozie 可以将启动 Spark 应用程序作为工作流的一部分。在安全集群中，启动的应用程序将需要相关的 tokens 来访问集群的服务。如果 Spark 使用 keytab 启动，这是自动的。但是，如果 Spark 在没有 keytab 的情况下启动，则设置安全性的责任必须移交给 Oozie。

有关配置 Oozie 以获取安全集群和获取作业凭据的详细信息，请参阅 Oozie web site 上特定版本文档的 “Authentication” 部分。

对于 Spark 应用程序，必须设置 Oozie 工作流以使 Oozie 请求应用程序需要的所有 tokens，包括：

YARN 资源管理器。

本地 HDFS 文件系统。

任何用作 I/O 的源或目标的远程 HDFS 文件系统。

Hive -如果使用。

HBase - 如果使用。

YARN 时间轴服务器，如果应用程序与此交互。

为了避免 Spark 尝试 - 然后失败 - 获取 Hive，HBase 和远程 HDFS 令牌，必须将 Spark 配置收集这些服务 tokens 的选项设置为禁用。

Spark 配置必须包含以下行 : 

spark.yarn.security.tokens.hive.enabled   false
spark.yarn.security.tokens.hbase.enabled  false

必须取消设置配置选项 spark.yarn.access.namenodes。

Kerberos 故障排查

调试 Hadoop / Kerberos 问题可能是 “困难的”。 一个有用的技术是通过设置 HADOOP_JAAS_DEBUG 环境变量在 Hadoop 中启用对 Kerberos 操作的额外记录。

bash export HADOOP_JAAS_DEBUG=true

JDK 类可以配置为通过系统属性 sun.security.krb5.debug 和 sun.security.spnego.debug = true 启用对 Kerberos 和 SPNEGO / REST 认证的额外日志记录。

-Dsun.security.krb5.debug=true -Dsun.security.spnego.debug=true

所有这些选项都可以在 Application Master 中启用 : 

spark.yarn.appMasterEnv.HADOOP_JAAS_DEBUG true
spark.yarn.am.extraJavaOptions -Dsun.security.krb5.debug=true -Dsun.security.spnego.debug=true

最后，如果 org.apache.spark.deploy.yarn.Client 的日志级别设置为 DEBUG，日志将包括获取的所有 tokens 的列表，以及它们的到期详细信息。

更多

Spark 配置

Spark 监控

优化指南

作业调度

Spark 安全

硬件配置

构建 Spark

Spark 配置

原文链接 : http://spark.apache.org/docs/latest/configuration.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2887898

贡献者 : Josh ，琴剑蓝天，风轻云淡，ping，coco，Tiny_sun，漫步，那伊抹微笑

Spark 提供了三种地方来配置系统 :  

Spark 属性 - 控制着常见的应用参数，并且可以通过使用一个 SparkConf 对象，或者通过 Java 系统属性来设置。

环境变量 - 可以在每个节点上通过 conf/spark-env.sh 脚本来配置每台机器的设置，例如 IP 地址。

日志 - 可以通过 log4j.properties 来配置。

Spark 属性

Spark 属性可以控制大多数的应用程序设置，并且每个应用的设置都是分开的。这些属性可以通过 SparkConf 对象直接设定。SparkConf 为一些常用的属性定制了专用方法（如，master URL 和 application name），其他属性都可以用键值对做参数，调用 set() 方法来设置。例如，我们可以初始化一个包含 2 个本地线程的 Spark 应用，代码如下 :  

注意，local[2] 代表 2 个本地线程 – 这是最小的并发方式，可以帮助我们发现一些只有在分布式上下文才能复现的 bug。

val conf = new SparkConf()
             .setMaster("local[2]")
             .setAppName("CountingSheep")
val sc = new SparkContext(conf)

注意，本地模式下，我们可以使用多个线程。而且在像 Spark Streaming 这样的场景下，我们可能需要多个线程来防止类似线程饿死这样的问题。

配置时间段的属性应该写明时间单位，如下格式都是可接受的 :  

25ms (milliseconds)
5s (seconds)
10m or 10min (minutes)
3h (hours)
5d (days)
1y (years)

配置大小的属性也应该写明单位，如下格式都是可接受的 :  

1b (bytes)
1k or 1kb (kibibytes = 1024 bytes)
1m or 1mb (mebibytes = 1024 kibibytes)
1g or 1gb (gibibytes = 1024 mebibytes)
1t or 1tb (tebibytes = 1024 gibibytes)
1p or 1pb (pebibytes = 1024 tebibytes)

动态加载 Spark 属性

在某些场景下，你可能想避免将属性值写死在 SparkConf 中。例如，你可能希望在同一个应用上使用不同的 master 或不同的内存总量。Spark 允许你简单地创建一个空的 SparkConf 对象 :  

val sc = new SparkContext(new SparkConf())

然后在运行时设置这些属性 : 

./bin/spark-submit --name "My app" --master local[4] --conf spark.eventLog.enabled=false
  --conf "spark.executor.extraJavaOptions=-XX:+PrintGCDetails -XX:+PrintGCTimeStamps" myApp.jar

Spark shell 和 spark-submit 工具支持两种动态加载配置的方法。第一种，通过命令行选项，如 : 上面提到的 --master。spark-submit 可以在启动 Spark 应用时，通过 --conf 标志接受任何属性配置，同时有一些特殊配置参数同样可用。运行 ./bin/spark-submit --help 可以展示这些选项的完整列表。

同时，bin/spark-submit 也支持从 conf/spark-defaults.conf 中读取配置选项，在该文件中每行是一个键值对，并用空格分隔，如下 : 

spark.master            spark://5.6.7.8:7077
spark.executor.memory   4g
spark.eventLog.enabled  true
spark.serializer        org.apache.spark.serializer.KryoSerializer

这些通过参数或者属性配置文件传递的属性，最终都会在 SparkConf 中合并。其优先级是 : 首先是 SparkConf 代码中写的属性值，其次是 spark-submit 或 spark-shell 的标志参数，最后是 spark-defaults.conf 文件中的属性。早期版本spark的一些配置项被重命名过，这种情形下，老的名字仍然是可以接受的，只是优先级比新名字优先级低。

查看 Spark 属性

在应用程序 http://<driver>:4040 的 web UI 上的 “Environment” 标签页可以查看 Spark 属性。如果你真的想确认一下属性设置是否正确的话，这个功能就非常有用了。注意，只有显式地通过 SparkConf 对象、在命令行参数、或者 spark-defaults.conf 设置的参数才会出现在页面上。其他属性，你可以认为都是默认值。

可用的属性

绝大多数属性都有合理的默认值。这里是部分常用的选项 :

应用程序属性

属性名 | 默认值 | 含义
spark.app.name | (none) | Spark 应用的名字。会在 SparkUI 和日志中出现。
spark.dirver.cores | 1 | 在 cluster 模式下，用几个 core 运行 driver 进程。
spark.driver.maxResultSize | 1g | Spark action 算子返回的结果集的最大数量。至少要 1M，可以设为 0 表示无限制。如果结果超过这一大小，Spark job 会直接中断退出。但是，设得过高有可能导致 driver 出现 out-of-memory 异常（取决于 spark.driver.memory 设置，以及驱动器 JVM 的内存限制）。设一个合理的值，以避免 driver 出现 out-of-memory 异常。
spark.driver.memory | 1g | driver进程可以使用的内存总量（如 : 1g，2g）。注意，在 client 模式下，这个配置不能在 SparkConf 中直接设置，应为在那个时候 driver 进程的 JVM 已经启动了。因此需要在命令行里用 --driver-memory 选项 或者在默认属性配置文件里设置。
spark.executor.memory | 1g | 每个 executor 进程使用的内存总量（如，2g，8g）。
spark.extraListeners | (none) | 逗号分隔的实现 SparkListener 接口的类名列表；初始化 SparkContext 时，这些类的实例会被创建出来，并且注册到 Spark 的监听器上。如果这些类有一个接受 SparkConf 作为唯一参数的构造函数，那么这个构造函数会被调用；否则，就调用无参构造函数。如果没有合适的构造函数，SparkContext 创建的时候会抛异常。
spark.local.dir | /tmp | Spark 的”草稿“目录，包括 map 输出的临时文件以及 RDD 存在磁盘上的数据。这个目录最好在本地文件系统中。这个配置可以接受一个以逗号分隔的多个挂载到不同磁盘上的目录列表。注意 : Spark-1.0 及以后版本中，这个属性会被 cluster manager 设置的环境变量覆盖 : SPARK_LOCAL_DIRS（Standalone，Mesos）或者 LOCAL_DIRS（YARN）。
spark.logConf | false | SparkContext 启动时是否把生效的 SparkConf 属性以 INFO 日志打印到日志里。
spark.master | (none) | 要连接的 cluster manager。参考Cluster Manager 类型。
spark.submit.deployMode | (none) | Spark driver 程序的部署模式，可以是 "client" 或 "cluster"，意味着部署 dirver 程序本地（"client"）或者远程（"cluster"）在 Spark 集群的其中一个节点上。

运行时环境

属性名 | 默认值 | 含义
spark.driver.extraClassPath | (none) | 额外的classpath条目需预先添加到驱动程序 classpath中。 注意 : 在客户端模式下，这一套配置不能通过 SparkConf 直接在应用在应用程序中，因为 JVM 驱动已经启用了。相反，请在配置文件中通过设置 --driver-class-path 选项或者选择默认属性。
spark.driver.extraJavaOptions | (none) | 一些额外的 JVM 属性传递给驱动。例如，GC 设置或其他日志方面设置。注意，设置最大堆大小（-Xmx）是不合法的。最大堆大小设置可以通过在集群模式下设置 spark.driver.memory 选项，并且可以通过 --driver-memory 在客户端模式设置。注意 : 在客户端模式下，这一套配置不能通过 SparkConf 直接应用在应用程序中，因为 JVM 驱动已经启用了。相反，请在配置文件中通过设置 --driver-java-options 选项或者选择默认属性。
spark.driver.extraLibraryPath | (none) | 当启动 JVM 驱动程序时设置一个额外的库路径。注意 : 在客户端模式下，这一套配置不能通过 SparkConf 直接在应用在应用程序中，因为 JVM 驱动已经启用了。相反，请在配置文件中通过设置 --driver-library-path 选项或者选择默认属性。
spark.driver.userClassPathFirst | (none) | （实验）在驱动程序加载类库时，用户添加的 Jar 包是否优先于 Spark 自身的 Jar 包。这个特性可以用来缓解冲突引发的依赖性和用户依赖。目前只是实验功能。这是仅用于集群模式。
spark.executor.extraClassPath | (none) | 额外的类路径要预先考虑到 executor 的 classpath。这主要是为与旧版本的 Spark 向后兼容。用户通常不应该需要设置这个选项。
spark.executor.extraJavaOptions | (none) | 一些额外的 JVM 属性传递给 executor。例如，GC 设置或其他日志方面设置。注意，设置最大堆大小（-Xmx）是不合法的。Spark 应该使用 SparkConf 对象或 Spark 脚本中使用的 spark-defaults.conf 文件中设置。最大堆大小设置可以在 spark.executor.memory 进行设置。
spark.executor.extraLibraryPath | (none) | 当启动 JVM 的可执行程序时设置额外的类库路径。
spark.executor.logs.rolling.maxRetainedFiles | (none) | 最新回滚的日志文件将被系统保留。旧的日志文件将被删除。默认情况下禁用。
spark.executor.logs.rolling.maxSize | (none) | 设置最大文件的大小,以字节为单位日志将被回滚。默认禁用。见 spark.executor.logs.rolling.maxRetainedFiles 旧日志的自动清洗。
spark.executor.logs.rolling.strategy | (none) | 设置 executor 日志的回滚策略。它可以被设置为 “时间”（基于时间的回滚）或 “大小”（基于大小的回滚）。对于 “时间”，使用 spark.executor.logs.rolling.time.interval 设置回滚间隔。用 spark.executor.logs.rolling.maxSize 设置最大文件大小回滚。
spark.executor.logs.rolling.time.interval | daily | 设定的时间间隔，executor 日志将回滚。默认情况下是禁用的。有效值是每天，每小时，每分钟或任何时间间隔在几秒钟内。见 spark.executor.logs.rolling.maxRetainedFiles 旧日志的自动清洗。
spark.executor.userClassPathFirst | false | （实验）与 spark.driver.userClassPathFirst 相同的功能，但适用于执行程序的实例。
spark.executorEnv.[EnvironmentVariableName] | (none) | 通过添加指定的环境变量 EnvironmentVariableName 给 executor 进程。用户可以设置多个环境变量。
spark.python.profile | false | 启用在 python 中的 profile。结果将由 sc.show_profiles() 显示,或者它将会在驱动程序退出后显示。它还可以通过 sc.dump_profiles dump 到磁盘。如果一些 profile 文件的结果已经显示，那么它们将不会再驱动程序退出后再次显示。默认情况下，pyspark.profiler.BasicProfiler 将被使用，但这可以通过传递一个 profile 类作为一个参数到 SparkContext 中进行覆盖。
spark.python.profile.dump | (none) | 这个目录是在驱动程序退出后，proflie 文件 dump 到磁盘中的文件目录。结果将为每一个 RDD dump 为分片文件。它们可以通过 ptats.Stats() 加载。如果指定，profile 结果将不会自动显示。
spark.python.worker.memory | 512m | 在聚合过程中，每个 python 进程所用的内存大小，和 JVM 内存相同的格式。如果内存中超过设定值，就会溢出到磁盘。
spark.python.worker.reuse | true | 重用 python worker。如果为 true，它将使用固定数量的 worker 数量。不需要为每一个任务分配 python 进程。如果是大型的这将是非常有用。

Shuffle 行为（Behavior）

属性名 | 默认值 | 含义
spark.reducer.maxSizeInFlight | 48m | 从每个 Reduce 任务中并行的 fetch 数据的最大大小。因为每个输出都要求我们创建一个缓冲区，这代表要为每一个 Reduce 任务分配一个固定大小的内存。除非内存足够大否则尽量设置小一点。
spark.reducer.maxReqsInFlight | Int.MaxValue | 在集群节点上，这个配置限制了远程 fetch 数据块的连接数目。当集群中的主机数量的增加时候，这可能导致大量的到一个或多个节点的主动连接，导致负载过多而失败。通过限制获取请求的数量，可以缓解这种情况。
spark.shuffle.compress | true | 是否要对 map 输出的文件进行压缩。默认为 true，使用 spark.io.compression.codec。
spark.shuffle.file.buffer | 32k | 每个 shuffle 文件输出流的内存大小。这些缓冲区的数量减少了磁盘寻道和系统调用创建的 shuffle 文件。
spark.shuffle.io.maxRetries | 3 | （仅适用于 Netty）如果设置了非 0 值，与 IO 异常相关失败的 fetch 将自动重试。在遇到长时间的 GC 问题或者瞬态网络连接问题时候，这种重试有助于大量 shuffle 的稳定性。
spark.shuffle.io.numConnectionsPerPeer | 1 | （仅Netty）重新使用主机之间的连接，以减少大型集群的连接建立。 对于具有许多硬盘和少量主机的群集，这可能导致并发性不足以使所有磁盘饱和，因此用户可考虑增加此值。
spark.shuffle.io.preferDirectBufs | true | （仅适用于 Netty）堆缓冲区用于减少在 shuffle 和缓存块传输中的垃圾回收。对于严格限制的堆内存环境中，用户可能希望把这个设置关闭，以强制Netty的所有分配都在堆上。。
spark.shuffle.io.retryWait | 5s | （仅适用于 Netty）fetch 重试的等待时长。默认 15s。计算公式是 maxRetries * retryWait。
spark.shuffle.service.enabled | false | 使用外部 shuffle。这个服务保存了由 executor 写出的 shuffle 文件所以 executor 可以被安全移除。 如果 spark.dynamicAllocation.enabled 设置为 true 那么这个选项一定是 true。外部 shuffle 服务必须通过设置来启用它。通过动态分配配置和设置文档了解更多信息。
spark.shuffle.service.port | 7337 | 外部 shuffle 的运行端口。
spark.shuffle.sort.bypassMergeThreshold | 200 | 在基于排序的 shuffle 管理中，如果没有在 map 端的数据聚集并且如果有这个数据量的 reduce partition，可以避免归并排序。
spark.shuffle.spill.compress | true | shuffle 过程中对溢出的文件是否压缩。使用 spark.io.compression.codec.

Spark UI

属性名 | 默认值 | 含义
spark.eventLog.compress | false | 是否压缩日志文件。如果设置为 ture 即为压缩。
spark.eventLog.dir | file : ///tmp/spark-events | Spark 事件日志的文件路径。如果 spark.eventLog.enabled 为 true。在这个基本目录下，Spark 为每个应用程序创建一个二级目录，日志事件特定于应用程序的目录。用户可能希望设置一个统一的文件目录像一个 HDFS 目录那样，所以历史文件可以从历史文件服务器中读取。
spark.eventLog.enabled | false | 是否对 Spark 事件记录日志。在应用程序启动后有助于重建 Web UI。
spark.ui.killEnabled | true | 允许从 Web UI 中结束相应的工作进程。
spark.ui.port | 4040 | 应用 UI 的端口，用于显示内存和工作负载数据。
spark.ui.retainedJobs | 1000 | 在垃圾回收前，Spark UI 和 API 有多少 Job 可以留存。
spark.ui.retainedStages | 1000 | 在垃圾回收前，Spark UI 和 API 有多少 Stage 可以留存。
spark.ui.retainedTasks | 100000 | 在垃圾回收前，Spark UI 和 API 有多少 Task 可以留存。
spark.worker.ui.retainedExecutors | 1000 | 在垃圾回收前，Spark UI 和 API 有多少 executor 已经完成。
spark.worker.ui.retainedDrivers | 1000 | 在垃圾回收前，Spark UI 和 API 有多少 driver 已经完成。
spark.sql.ui.retainedExecutions | 1000 | 在垃圾回收前，Spark UI 和 API 有多少 execution 已经完成。
spark.streaming.ui.retainedBatches | 1000 | 在垃圾回收前，Spark UI 和 API 有多少 batch 已经完成。
spark.ui.retainedDeadExecutors | 1000 | 在垃圾回收前，Spark UI 和 API 有多少 dead executors。

压缩和序列化（Compression and Serialization） 

属性名 | 默认值 | 含义
spark.broadcast.compress | true | 是否在发送广播变量前压缩。通常是个好主意。
spark.io.compression.codec | lz4 | 内部数据使用的压缩编解码器，如 RDD 分区，广播变量和混洗输出。 默认情况下，Spark 提供三种编解码器 : lz4，lzf 和 snappy。您还可以使用完全限定类名来指定编码解码器，例如 : org.apache.spark.io.LZ4CompressionCodec，org.apache.spark.io.LZFCompressionCodec 和 org.apache.spark.io.SnappyCompressionCodec。
spark.io.compression.lz4.blockSize | 32k | 在采用 LZ4 压缩编解码器的情况下，LZ4 压缩使用的块大小。减少块大小还将降低采用 LZ4 时的混洗内存使用。
spark.io.compression.snappy.blockSize | 32k | 在采用 Snappy 压缩编解码器的情况下，Snappy 压缩使用的块大小。减少块大小还将降低采用 Snappy 时的混洗内存使用。
spark.kryo.classesToRegister | (none) | 如果你采用 Kryo 序列化，给一个以逗号分隔的自定义类名列以注册 Kryo。有关详细信息，请参阅调优指南。
spark.kryo.referenceTracking | true（当使用 Spark SQL Thrift Server 为false） | 是否在采用 Kryo 序列化数据时跟踪对同一对象的引用，如果你的对象图形包含循环则是有必要的，并且如果它们包含同一对象的多个副本则对效率有用。 如果你知道这不是这样，可以禁用提高性能。
spark.kryo.registrationRequired | false | 是否需要注册 Kryo。 如果设置为 'true'，如果未注册的类被序列化，Kryo 将抛出异常。如果设置为 false（默认值），Kryo 将与每个对象一起写入未注册的类名。 编写类名可能会导致显著的性能开销，因此启用此选项可以严格强制用户没有从注册中省略类。
spark.kryo.registrator | (none) | 如果你采用 Kryo 序列化，则给一个逗号分隔的类列表，以使用 Kryo 注册你的自定义类。 如果你需要以自定义方式注册你的类，则此属性很有用，例如以指定自定义字段序列化程序。 否则，使用 spark.kryo.classesToRegisteris 更简单。 它应该设置为 KryoRegistrator 的子类。 详见 : 调整指南。
spark.kryoserializer.buffer.max | 64m | Kryo 序列化缓冲区的最大允许大小。 这必须大于你需要序列化的任何对象。 如果你在 Kryo 中得到一个 “buffer limit exceeded” 异常，你就需要增加这个值。
spark.kryoserializer.buffer | 64k | Kryo 序列化缓冲区的初始大小。 注意，每个 worker上每个 core 会有一个缓冲区。 如果需要，此缓冲区将增长到 spark.kryoserializer.buffer.max。
spark.rdd.compress | false | 是否压缩序列化的 RDD 分区（例如，在 Java 和 Scala 中为 StorageLevel.MEMORY_ONLY_SER 或在 Python 中为 StorageLevel.MEMORY_ONLY）。能节省大量空间，但多消耗一些 CPU 时间。
spark.serializer | org.apache.spark.serializer. JavaSerializer（在使用 Spark SQL Thrift Server 时为 org.apache.spark.serializer. KryoSerializer） | 用于序列化将通过网络发送或需要以序列化形式缓存的对象的类。 Java 序列化的默认值适用于任何可序列化的 Java 对象，但是速度相当慢，因此我们建议使用org.apache.spark.serializer.KryoSerializer 并在需要速度时配置 Kryo 序列化。当然你可以通过继承 org.apache.spark.Serializer 类自定义一个序列化器 。
spark.serializer.objectStreamReset | 100 | 当正使用 org.apache.spark.serializer.JavaSerializer 序列化时, 序列化器缓存对象虽然可以防止写入冗余数据，但是却停止这些缓存对象的垃圾回收。通过调用 'reset' 你从序列化程序中清除该信息，并允许收集旧的对象。 要禁用此周期性重置，请将其设置为 -1。 默认情况下，序列化器会每过 100 个对象被重置一次。

内存管理（Memory Management）

属性名 | 默认值 | 含义
spark.memory.fraction | 0.6 | 用于执行和存储的（堆空间 - 300MB）的分数。这个值越低，溢出和缓存数据逐出越频繁。 此配置的目的是在稀疏、异常大的记录的情况下为内部元数据，用户数据结构和不精确的大小估计预留内存。推荐使用默认值。 有关更多详细信息，包括关于在增加此值时正确调整 JVM 垃圾回收的重要信息，请参阅 this description。
spark.memory.storageFraction | 0.5 | 不会被逐出内存的总量，表示为 spark.memory.fraction 留出的区域大小的一小部分。 这个越高，工作内存可能越少，执行和任务可能更频繁地溢出到磁盘。 推荐使用默认值。有关更多详细信息，请参阅 this description。
spark.memory.offHeap.enabled | false | 如果为 true，Spark 会尝试对某些操作使用堆外内存。 如果启用了堆外内存使用，则 spark.memory.off Heap.size 必须为正值。
spark.memory.offHeap.size | 0 | 可用于堆外分配的绝对内存量（以字节为单位）。 此设置对堆内存使用没有影响，因此如果您的执行器的总内存消耗必须满足一些硬限制，那么请确保相应地缩减JVM堆大小。 当 spark.memory.offHeap.enabled = true 时，必须将此值设置为正值。
spark.memory.useLegacyMode | false | 是否启用 Spark 1.5 及以前版本中使用的传统内存管理模式。 传统模式将堆空间严格划分为固定大小的区域，如果未调整应用程序，可能导致过多溢出。 必须启用本参数，以下选项才可用 : spark.shuffle.memoryFraction spark.storage.memoryFraction spark.storage.unrollFraction
spark.shuffle.memoryFraction | 0.2 | （过时）只有在启用 spark.memory.useLegacyMode 时，此属性才是可用的。 混洗期间用于聚合和 cogroups 的 Java 堆的分数。 在任何给定时间，用于混洗的所有内存映射的集合大小不会超过这个上限，超过该限制的内容将开始溢出到磁盘。 如果溢出频繁，请考虑增加此值，但这以 spark.storage.memoryFraction 为代价。
spark.storage.memoryFraction | 0.6 | （过时）只有在启用 spark.memory.useLegacyMode 时，此属性才是可用的。 Java 堆的分数，用于 Spark 的内存缓存。 这个值不应该大于 JVM 中老生代（old generation) 对象所占用的内存，默认情况下，它提供 0.6 的堆，但是如果配置你所用的老生代对象大小，你可以增加它。
spark.storage.unrollFraction | 0.2 | （过时）只有在启用 spark.memory.useLegacyMode 时，此属性才是可用的。 spark.storage.memoryFraction 用于在内存中展开块的分数。 当没有足够的空闲存储空间来完全展开新块时，通过删除现有块来动态分配。

执行器行为（Execution Behavior）

属性名 | 默认值 | 含义
spark.broadcast.blockSize | 4m | TorrentBroadcastFactory 的一个块的每个分片大小。 过大的值会降低广播期间的并行性（更慢了）; 但是，如果它过小，BlockManager 可能会受到性能影响。
spark.executor.cores | 在 YARN 模式下默认为 1，standlone 和 Mesos 粗粒度模型中的 worker 节点的所有可用的 core。 | 在每个 executor（执行器）上使用的 core 数。 在 standlone 和 Mesos 的粗粒度模式下，设置此参数允许应用在相同的 worker 上运行多个 executor（执行器），只要该 worker 上有足够的 core。 否则，每个 application（应用）在单个 worker 上只会启动一个 executor（执行器）。

spark.default.parallelism

对于分布式混洗（shuffle）操作，如 reduceByKey 和 join，父 RDD 中分区的最大数量。 对于没有父 RDD 的并行操作，它取决于集群管理器 :

本地模式 : 本地机器上的 core 数

Mesos 细粒度模式 : 8

其他 : 所有执行器节点上的 core 总数或者 2，以较大者为准

如果用户没有指定参数值，则这个属性是 join，reduceByKey 和 parallelize 等转换返回的 RDD 中的默认分区数。

spark.executor.heartbeatInterval | 10s | 每个执行器的心跳与驱动程序之间的间隔。 心跳让驱动程序知道执行器仍然存活，并用正在进行的任务的指标更新它。
spark.files.fetchTimeout | 60s | 获取文件的通讯超时，所获取的文件是从驱动程序通过 SparkContext.addFile() 添加的。
spark.files.useFetchCache | true | 如果设置为 true（默认），文件提取将使用由属于同一应用程序的执行器共享的本地缓存，这可以提高在同一主机上运行许多执行器时的任务启动性能。 如果设置为 false，这些缓存优化将被禁用，所有执行器将获取它们自己的文件副本。 如果使用驻留在 NFS 文件系统上的 Spark 本地目录，可以禁用此优化（有关详细信息，请参阅 SPARK-6313 ）。
spark.files.overwrite | false | 当目标文件存在且其内容与源不匹配的情况下，是否覆盖通过 SparkContext.addFile() 添加的文件。
spark.hadoop.cloneConf | false | 如果设置为 true，则为每个任务克隆一个新的 Hadoop 配置对象。 应该启用此选项以解决配置线程安全问题（有关详细信息，请参阅 SPARK-2546 ）。 默认情况下禁用此功能，以避免不受这些问题影响的作业的意外性能回退。
spark.hadoop.validateOutputSpecs | true | 如果设置为 true，则验证 saveAsHadoopFile 和其他变体中使用的输出规范（例如，检查输出目录是否已存在）。 可以禁用此选项以静默由于预先存在的输出目录而导致的异常。 我们建议用户不要禁用此功能，除非需要实现与以前版本的 Spark 的兼容性。 可以简单地使用 Hadoop 的 FileSystem API 手动删除输出目录。 对于通过 Spark Streaming 的StreamingContext 生成的作业会忽略此设置，因为在检查点恢复期间可能需要将数据重写到预先存在的输出目录。
spark.storage.memoryMapThreshold | 2m | 当从磁盘读取块时，Spark 内存映射的块大小。 这会阻止 Spark 从内存映射过小的块。 通常，存储器映射对于接近或小于操作系统的页大小的块具有高开销。

网络（Networking）

属性名 | 默认值 | 含义
spark.rpc.message.maxSize | 128 | 在 “control plane” 通信中允许的最大消息大小（以 MB 为单位）; 一般只适用于在 executors 和 driver 之间发送的映射输出大小信息。 如果您正在运行带有数千个 map 和 reduce 任务的作业，并查看有关 RPC 消息大小的消息，请增加此值。
spark.blockManager.port | (random) | 所有块管理器监听的端口。 这些都存在于 driver 和 executors 上。
spark.driver.host | (local hostname) | 要监听的 driver 的主机名或 IP 地址。 这用于与 executors 和 standalone Master 进行通信。
spark.driver.port | (random) | 要监听的 driver 的端口。这用于与 executors 和 standalone Master 进行通信。
spark.network.timeout | 120s | 所有网络交互的默认超时。 如果未配置此项，将使用此配置替换 spark.core.connection.ack.wait.timeout，spark.storage.blockManagerSlaveTimeoutMs，spark.shuffle.io.connectionTimeout，spark.rpc.askTimeout或spark.rpc.lookupTimeout。
spark.port.maxRetries | 16 | 在绑定端口放弃之前的最大重试次数。 当端口被赋予特定值（非 0）时，每次后续重试将在重试之前将先前尝试中使用的端口增加 1。 这本质上允许它尝试从指定的开始端口到端口 + maxRetries 的一系列端口。
spark.rpc.numRetries | 3 | 在 RPC 任务放弃之前重试的次数。 RPC 任务将在此数字的大多数时间运行。
spark.rpc.retry.wait | 3s | RPC 请求操作在重试之前等待的持续时间。
spark.rpc.askTimeout | 120s | RPC 请求操作在超时前等待的持续时间。
spark.rpc.lookupTimeout | 120s | RPC 远程端点查找操作在超时之前等待的持续时间。

调度（Scheduling）

属性名 | 默认值 | 含义
spark.cores.max | (not set) | 当以 “coarse-grained（粗粒度）” 共享模式在 standalone deploy 集群或 Mesos 集群上运行时，从集群（而不是每台计算机）请求应用程序的最大 CPU 内核数量。 如果未设置，默认值将是 Spar k的 standalone deploy 管理器上的 spark.deploy.defaultCores，或者 Mesos上的无限（所有可用核心）。
spark.locality.wait | 3s | 等待启动本地数据任务多长时间，然后在较少本地节点上放弃并启动它。 相同的等待将用于跨越多个地点级别（process-local，node-local，rack-local 等所有）。 也可以通过设置 spark.locality.wait.node 等来自定义每个级别的等待时间。如果任务很长并且局部性较差，则应该增加此设置，但是默认值通常很好。
spark.locality.wait.node | spark.locality.wait | 自定义 node locality 等待时间。 例如，您可以将其设置为 0 以跳过 node locality，并立即搜索机架位置（如果群集具有机架信息）。
spark.locality.wait.process | spark.locality.wait | 自定义 process locality 等待时间。这会影响尝试访问特定执行程序进程中的缓存数据的任务。
spark.locality.wait.rack | spark.locality.wait | 自定义 rack locality 等待时间。
spark.scheduler.maxRegisteredResourcesWaitingTime | 30s | 在调度开始之前等待资源注册的最大时间量。
spark.scheduler.minRegisteredResourcesRatio | 0.8 for YARN mode; 0.0 for standalone mode and Mesos coarse-grained mode | 注册资源（注册资源/总预期资源）的最小比率（资源是 yarn 模式下的执行程序，standalone 模式下的 CPU 核心和 Mesos coarsed-grained 模式 'spark.cores.max' 值是 Mesos  coarse-grained 模式下的总体预期资源]）在调度开始之前等待。 指定为 0.0 和 1.0 之间的双精度。 无论是否已达到资源的最小比率，在调度开始之前将等待的最大时间量由配置spark.scheduler.maxRegisteredResourcesWaitingTime 控制。
spark.scheduler.mode | FIFO | 作业之间的调度模式提交到同一个 SparkContext。 可以设置为 FAIR 使用公平共享，而不是一个接一个排队作业。 对多用户服务有用。
spark.scheduler.revive.interval | 1s | 调度程序复活工作资源去运行任务的间隔长度。
spark.speculation | false | 如果设置为 “true”，则执行任务的推测执行。 这意味着如果一个或多个任务在一个阶段中运行缓慢，则将重新启动它们。
spark.speculation.interval | 100ms | Spark 检查要推测的任务的时间间隔。
spark.speculation.multiplier | 1.5 | 一个任务的速度可以比推测的平均值慢多少倍。
spark.speculation.quantile | 0.75 | 对特定阶段启用推测之前必须完成的任务的分数。
spark.task.cpus | 1 | 要为每个任务分配的核心数。
spark.task.maxFailures | 4 | 放弃作业之前任何特定任务的失败次数。 分散在不同任务中的故障总数不会导致作业失败; 一个特定的任务允许失败这个次数。 应大于或等于 1。允许重试次数=此值 - 1。

动态分配（Dynamic Allocation）

属性名 | 默认值 | 含义
spark.dynamicAllocation.enabled | false | 是否使用动态资源分配，它根据工作负载调整为此应用程序注册的执行程序数量。 有关更多详细信息，请参阅此处的说明。 这需要设置 spark.shuffle.service.enabled。 以下配置也相关 : spark.dynamicAllocation.minExecutors，spark.dynamicAllocation.maxExecutors 和spark.dynamicAllocation.initialExecutors。
spark.dynamicAllocation.executorIdleTimeout | 60s | 如果启用动态分配，并且执行程序已空闲超过此持续时间，则将删除执行程序。 有关更多详细信息，请参阅此描述.
spark.dynamicAllocation.cachedExecutorIdleTimeout | infinity | 如果启用动态分配，并且已缓存数据块的执行程序已空闲超过此持续时间，则将删除执行程序。 有关详细信息，请参阅此描述。
spark.dynamicAllocation.initialExecutors | spark.dynamicAllocation.minExecutors | 启用动态分配时要运行的执行程序的初始数。 如果 `--num-executors`（或 `spark.executor.instances`）被设置并大于此值，它将被用作初始执行器数。
spark.dynamicAllocation.maxExecutors | infinity | 启用动态分配的执行程序数量的上限。
spark.dynamicAllocation.minExecutors | 0 | 启用动态分配的执行程序数量的下限。
spark.dynamicAllocation.schedulerBacklogTimeout | 1s | 如果启用动态分配，并且有超过此持续时间的挂起任务积压，则将请求新的执行者。 有关更多详细信息，请参阅此描述。
spark.dynamicAllocation.sustainedSchedulerBacklogTimeout  | schedulerBacklogTimeout | 与 spark.dynamicAllocation.schedulerBacklogTimeout 相同，但仅用于后续执行者请求。 有关更多详细信息，请参阅此描述。

安全性（Security）

属性名 | 默认值 | 含义
spark.acls.enable | false | 是否开启 Spark acls。如果开启了，它检查用户是否有权限去查看或修改 job。 Note this requires the user to be known, so if the user comes across as null no checks are done。UI 利用使用过滤器验证和设置用户。
spark.admin.acls | Empty | 逗号分隔的用户或者管理员列表，列表中的用户或管理员有查看和修改所有 Spark job 的权限。如果你运行在一个共享集群，有一组管理员或开发者帮助 debug，这个选项有用。
spark.admin.acls.groups | Empty | 逗号分隔的用户或者管理员列表，列表中的用户或管理员有查看和修改所有 Spark job 的权限。如果你运行在一个共享集群，有一组管理员或开发者帮助 debug，这个是针对组管理员组用户 设置 spark.user.groups.mapping 和 spark.user.groups.mapping。

spark.user.groups.mapping
org.apache.spark.security.ShellBasedGroupsMappingProvider
用户组列表是由一组地图服务定义的特征 org.apache.spark.security。GroupMappingServiceProvider 可以由这个属性配置。 提供一个默认的基于 unix shell 实现 org.apache.spark.security.ShellBasedGroupsMappingProvider。 可以指定解决用户组列表。 注意 : 这个实现只支持基于 Unix / Linux 环境。 Windows 环境目前不支持。 然而，一个新的平台/协议可以支持的实现的特征 org.apache.spark.security.GroupMappingServiceProvider。

spark.authenticate | false | 是否 Spark 验证其内部连接。如果不是运行在 YARN 上，请看 spark.authenticate.secret。
spark.authenticate.secret | None | 设置密钥用于 spark 组件之间进行身份验证。 这需要设置 不启用运行在 yarn 和身份验证。
spark.authenticate.enableSaslEncryption | false | 身份验证时启用加密通信。 这是块传输服务和支持 RPC 的端点。
spark.network.sasl.serverAlwaysEncrypt | false | 禁用未加密的连接服务，支持 SASL 验证。 这是目前支持的外部转移服务。
spark.core.connection.ack.wait.timeout | 60s | 连接等待回答的时间。单位为秒。为了避免不希望的超时，你可以设置更大的值。
spark.core.connection.auth.wait.timeout | 30s | 连接时等待验证的实际。单位为秒。
spark.modify.acls | Empty | 逗号分隔的用户列表，列表中的用户有查看 Spark web UI的权限。默认情况下，只有启动 Spark job 的用户有查看权限。
spark.modify.acls.groups | Empty | 针对 spark.modify.acls 设置组权限。
spark.ui.filters | None | 应用到 Spark web UI 的用于过滤类名的逗号分隔的列表。过滤器必须是标准的ja。vax servlet Filter。通过设置java系统属性也可以指定每个过滤器的参数。spark.<class name of filter>.params='param1=value1,param2=value2'。例如 -Dspark.ui.filters=com.test.filter1、-Dspark.com.test.filter1.params='param1=foo,param2=testing'。
spark.ui.view.acls | Empty | 逗号分隔的用户列表，列表中的用户有查看 Spark web UI 的权限。默认情况下，只有启动 Spark job 的用户有查看权限。
spark.ui.view.acls.groups | Empty | 针对 spark.ui.view.acls 设置组权限。

加密（Encryption）

属性名 | 默认值 | 含义
spark.ssl.enabled | false | 所有支持的协议是否启用 SSL 连接 所有的 SSL设置 spark.ssl.xxx 在哪里 xxx 是一个 特定的配置属性,表示全局所有支持的配置协议。 为了覆盖全局配置特定的协议, 属性必须被覆盖特定于协议的名称空间 使用 spark.ssl.YYY.XXX 覆盖全局配置设置特定的协议用。 示例值多包括 fs , 用户界面 , standlone , historyServer。查看SSL 配置 为服务细节层次 SSL 配置
spark.ssl.enabledAlgorithms | Empty | 密码的逗号分隔列表。 指定的密码必须由JVM支持。 协议的参考列表可以查看这个 页面。 注意 : 如果没有设置,它将使用 JVM 的缺省密码
spark.ssl.keyPassword | None | 私钥的密钥存储库的密码。
spark.ssl.keyStore | None | 密钥存储库文件的路径。 可以绝对或相对路径的目录 组件开始。
spark.ssl.keyStorePassword | None | 密钥存储密码。
spark.ssl.keyStoreType | JKS | 密钥存储库的类型。
spark.ssl.protocol | None | 协议名称。 协议必须由 JVM 支持。 协议的参考列表 一个可以找到这个页面。
spark.ssl.needClientAuth | false | 设置真实如果 SSL 客户机身份验证需求。
spark.ssl.trustStore | None | 信任存储文件的路径。 可以绝对或相对路径的目录组件是开始的地方。
spark.ssl.trustStorePassword | None | 信任存储密码。
spark.ssl.trustStoreType | JKS | 信任存储库的类型。

Spark SQL

运行 SET -v 命令将显示 SQL 配置的完整列表。

// spark 是一个现有的 SparkSession
spark.sql("SET -v").show(numRows = 200, truncate = false)

Spark Streaming

属性名 | 默认值 | 含义
spark.streaming.backpressure.enabled | false | 开启或关闭 Spark Streaming 内部的 backpressure mecheanism（自1.5 开始）。基于当前批次调度延迟和处理时间，这使得 Spark Streaming 能够控制数据的接收率，因此，系统接收数据的速度会和系统处理的速度一样快。从内部来说，这动态地设置了系统的最大接收率。这个速率上限通过 spark.streaming.receiver.maxRate 和 spark.streaming.kafka.maxRatePerPartition 两个参数设定（如下）。
spark.streaming.backpressure.initialRate | not set | 当 backpressure mecheanism 开启时，每个 receiver 接受数据的初始最大值。
spark.streaming.blockInterval | 200ms | 在这个时间间隔（ms）内，通过 Spark Streaming receivers 接收的数据在保存到 Spark 之前，chunk 为数据块。推荐的最小值为 50ms。具体细节见 Spark Streaming 指南的 performance tuning 一节。
spark.streaming.receiver.maxRate | not set | 每秒钟每个 receiver 将接收的数据的最大速率（每秒钟的记录数目）。有效的情况下，每个流每秒将最多消耗这个数目的记录。设置这个配置为 0 或者 -1 将会不作限制。细节参见 Spark Streaming 编程指南的 deployment guide 一节。
spark.streaming.receiver.writeAheadLog.enable | false | 为 receiver 启用 write ahead logs。所有通过接收器接收输入的数据将被保存到 write ahead logs，以便它在驱动程序故障后进行恢复。见星火流编程指南部署指南了解更多详情。细节参见 Spark Streaming 编程指南的 deployment guide 一节。
spark.streaming.unpersist | true | 强制通过 Spark Streaming 生成并持久化的 RDD 自动从 Spark 内存中非持久化。通过 Spark Streaming 接收的原始输入数据也将清除。设置这个属性为 false 允许流应用程序访问原始数据和持久化 RDD，因为它们没有被自动清除。但是它会造成更高的内存花费
spark.streaming.stopGracefullyOnShutdown | false | 如果为 true，Spark 将缓慢地（gracefully）关闭在 JVM 运行的 StreamingContext ，而非立即执行。
spark.streaming.kafka.maxRatePerPartition | not set | 在使用新的 Kafka direct stream API 时，从每个 kafka 分区读到的最大速率（每秒的记录数目）。详见 Kafka Integration guide。
spark.streaming.kafka.maxRetries | 1 | driver 连续重试的最大次数，以此找到每个分区 leader 的最近的（latest）的偏移量（默认为 1 意味着 driver 将尝试最多两次）。仅应用于新的 kafka direct stream API。
spark.streaming.ui.retainedBatches | 1000 | 在垃圾回收之前，Spark Streaming UI 和状态API 所能记得的 批处理（batches）数量。
spark.streaming.driver.writeAheadLog.closeFileAfterWrite | false | 在写入一条 driver 中的 write ahead log 记录 之后，是否关闭文件。如果你想为 driver 中的元数据 WAL 使用 S3（或者任何文件系统而不支持 flushing），设定为 true。
spark.streaming.receiver.writeAheadLog.closeFileAfterWrite | false | 在写入一条 reveivers 中的 write ahead log 记录 之后，是否关闭文件。如果你想为 reveivers 中的元数据 WAL 使用 S3（或者任何文件系统而不支持 flushing），设定为 true。

SparkR

属性名 | 默认值 | 含义
spark.r.numRBackendThreads | 2 | 使用 RBackend 处理来自 SparkR 包中的 RPC 调用的线程数。
spark.r.command | Rscript | 在 driver 和 worker 两种集群模式下可执行的 R 脚本。
spark.r.driver.command | spark.r.command | 在 driver 的 client 模式下可执行的R脚本。在集群模式下被忽略。

部署

属性名 | 默认值 | 含义
spark.deploy.recoveryMode | NONE | 集群模式下，Spark jobs 执行失败或者重启时，恢复提交 Spark jobs 的恢复模式设定。
spark.deploy.zookeeper.url | None | 当 `spark.deploy.recoveryMode` 被设定为 ZOOKEEPER，这一配置被用来连接 zookeeper URL。
spark.deploy.zookeeper.dir | None | 当 `spark.deploy.recoveryMode` 被设定为 ZOOKEEPER，这一配置被用来设定 zookeeper 目录为 store recovery state。

Cluster Manager（集群管理器）

Spark 中的每个集群管理器都有额外的配置选项，这些配置可以在每个模式的页面中找到。

YARN

Mesos

Spark Standalone

环境变量

通过环境变量配置特定的 Spark 设置。环境变量从 Spark 安装目录下的 conf/spark-env.sh 脚本读取（或者是 window 环境下的 conf/spark-env.cmd）。在 Standalone 或 Mesos 模式下，这个文件可以指定机器的特定信息，比如主机名。它也可以为正在运行的 Spark Application 或者提交脚本提供来源。 

注意，当 Spark 被安装，默认情况下 conf/spark-env.sh 是不存在的。但是，你可以通过拷贝 conf/spark-env.sh.template 来创建它。确保你的拷贝文件时可执行的。

spark-env.sh : 中有有以下变量可以被设置 :

环境变量 | 含义
JAVA_HOME | Java 的安装路径（如果不在你的默认路径下）
PYSPARK_PYTHON | 在 driver 和 worker 中 PySpark 用到的 Python 二进制可执行文件（如何有默认为 Python2.7，否则为 python）
PYSPARK_DRIVER_PYTHON | 只在 driver 中 PySpark 用到的 Python 二进制可执行文件（默认为 PySpark_Python）
PYSPARK_DRIVER_R | SparkR shell 用到的 R 二进制可执行文件（默认为 R）
SPARK_DRIVER_IP | 机器绑定的 IP 地址
SPARK_PUBLIC_DNS | 你的 Spark 程序通知其他机器的主机名

除了以上参数，standalone cluster scripts 也可以设置其他选项，比如每个机器使用的 CPU 核数和最大内存。

因为 spark-env.sh 是 shell 脚本，一些可以通过程序的方式来设置，比如你可以通过特定的网络接口来计算 SPARK_LOCAL_IP。

注意 : 当以集群模式运行 Spark on YARN 时，环境变量需要通过 spark.yarn.appMasterEnv 来设定。在你的 conf/spark-defaults.conf 文件中的 [EnvironmentVariableName] 属性。集群模式下，spark-env.sh 中设定的环境变量将不会在 YARN Application Master 过程中反应出来。详见 YARN-related Spark PropertiesProperties。

配置 Logging（日志）

Spark 用 log4j 生成日志，你可以通过在 conf 目录下添加 log4j.properties 文件来配置。一种方法是拷贝 log4j.properties.template 文件。

覆盖配置目录

如果你想指定不同的配置目录，而不是默认的 SPARK_HOME/conf，你可以设置 SPARK_CONF_DIR。Spark 将从这一目录下读取（spark-defaults.conf，spark-env.sh，log4j.properties 等）

继承 Hadoop 集群配置

如果你想用 Spark 来读写 HDFS，在 Spark 的 classpath 就需要包括两个 Hadoop 配置文件。

hdfs-site.xml，为 HDFS client 提供默认的行为。

core-site.xml，设定默认的文件系统名称。

这两个配置文件的位置视 CDH 和 HDP 两个版本而不同，不过一般来说在 /etc/hadoop/conf 下。一些工具，如 Cloudera Manager，创建动态的配置，并且提供一个来下载它们的副本机制。

为了使这些文件对 Spark 可见，需要设定 $SPARK_HOME/spark-env.sh 中的 HADOOP_CONF_DIR 到一个包含配置文件的位置。

Spark 监控

原文链接 : http://spark.apache.org/docs/latest/monitoring.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2887795

贡献者 : Le-Mon，那伊抹微笑

有几种方法可以监视 Spark 应用 : Web UI，metrics 和其他扩展工具。

Web 接口

每一个 SparkContext 启动一个 web UI，默认情况下使用端口 4040，可以显示关于运行程序的有用信息。这包括 : 

调度器阶段和任务的列表 　　

RDD 大小和内存使用的概要信息 　　

环境信息　

正在运行的程序的信息 

您只需打开 http://<driver-node>:4040 的 web 浏览器就可以访问。如果在同一主机上运行多个 SparkContexts，他们将开始连续绑定到端口 4040（4041、4042、等）。

注意，默认情况下这些信息仅在有程序的执行时显示。你可以在启动 Spark 之前修改配置，设置 spark.eventLog.enabled 为 true。让 Spark 记录并持久化存储 Spark 事件使其可以在 UI 中显示。

历史信息

如果 Spark 在 Mesos 或者 YARN 上运行，它仍有可能用已存在的程序日志通过 Spark history server（历史信息记录服务）来显示该程序运行时的详细信息。启动命令如下: 

./sbin/start-history-server.sh

这个会默认创建一个 web 接口 : http://<server-url>:18080，显示未完成、完成以及其他尝试的任务信息。

当指定使用一个文件系统提供 class 类（具体见下 spark.history.provider），那么基本的日志存储路径应该在 spark.history.fs.logDirectory 这个配置中指定，并且会有子目录，每个都表示某个程序信息的日志 log。

Spark 任务本身必须配置启用日志，并用相同的、共享的、可写的目录记录他们。例如，如果服务器配置的日志目录为

spark.eventLog.enabled true
spark.eventLog.dir hdfs://namenode/shared/spark-logs

那么 history server 的配置信息可以如下 : 

环境变量

环境变量 | 含义
SPARK_DAEMON_MEMORY | history server 分配内存（默认 : 1g）.
SPARK_DAEMON_JAVA_OPTS | history server JVM 配置（默认 : none）.
SPARK_PUBLIC_DNS | history server 的公用地址. 如果未配置， 可能会使用服务器的内部地址， 导致连接失效 （默认 : none）.
SPARK_HISTORY_OPTS | spark.history.*  history server 的相关配置选项 （默认 : none）.

Spark 配置选项

属性名称 | 默认 | 含义
spark.history.provider | org.apache.spark.deploy.history.FsHistoryProvider | 实现了 history backend 的 class 类的名称。目前只有一个实现，Spark 本身提供，用于检测存在文件系统中的程序的日志文件。
spark.history.fs.logDirectory | http://file/tmp/spark-events | 提供历史日志文件存储路径，URL地址（包含可加载的程序日志的目录）。可以配置本地路径 file://，或者 HDFS 路径 hdfs://namenode/shared/spark-logs 或其他 Hadoop API 支持的文件系统。
spark.history.fs.update.interval | 10s | 以秒为单位，更新日志相关信息的时间间隔，更短的时间间隔帮助检测到新的程序更快，但是牺牲更多的服务器负载。一旦更新完成，完成和未完成的程序的都会发生变化。
spark.history.retainedApplications | 50 | 保存在 UI 缓存中的程序数量。如果超过这个上限，那么时间最老的程序将从缓存中移除。如果一个程序未被缓存，它就必须从磁盘加载。
spark.history.ui.maxApplications | Int.MaxValue | 显示在总历史页面中的程序的数量。如果总历史页面未显示，程序 UI 仍可通过访问其 URL 来显示。
spark.history.ui.port | 18080 | UI 端口设置.
spark.history.kerberos.enabled | false | 表明 history server 是否应该使用 kerberos 登录。如果历史服务器访问 HDFS 文件安全的 Hadoop 集群，这是必需的。如果设置为 true，将会使用 spark.history.kerberos.principal 和 spark.history.kerberos.keytab 两个配置。
spark.history.kerberos.principal | （none） | Kerberos 凭证名称。
spark.history.kerberos.keytab | （none） | kerberos keytab 文件位置。
spark.history.ui.acls.enable | false | 指定是否通过 acl 授权限制用户查看程序。如果启用，程序运行时会强制执行访问控制检查，检测程序是否设置 spark.ui.acls.enable，程序所有者总能够查看自己的应用程序和任何指定 spark.ui.view.acls 和 spark.ui.view.acls.groups 的程序。当程序运行也将要授权查看。如果禁用，就没有访问控制检查。
spark.history.fs.cleaner.enabled | false | 是否定期清理历史日志文件。
spark.history.fs.cleaner.interval | 1d | 清理历史日志文件的间隔，只会清理比 spark.history.fs.cleaner.maxAge 更老的日志文件。
spark.history.fs.cleaner.maxAge | 7d | 日志文件的最大年龄，超过就会被清理。
spark.history.fs.numReplayThreads | 25% of available cores | histroy sever 操作日志的最大线程数。

请注意，在所有这些 UI，表是可排序的点击他们的表头，让它容易更容易识别任务缓慢，数据倾斜的任务。

注意 : 

history server 会显示完成和未完成的 Spark 任务。如果一个程序多次尝试失败后，这时会显示失败的尝试，或任何正在进行的却未完成的尝试，或者最终成功的尝试。

未完成的程序只会间歇性地更新。更新之间的时间间隔定义在的检查文件变化间隔（spark.history.fs.update.interval）。在更大的集群上更新间隔可能被设置为更大值。可以通过 web UI 查看正在运行的程序。

程序如果退出时没有表明注册已经完成，将会被列入未完成的一栏，尽管他们不再运行。如果一个应用程序崩溃可能导致该情况发生，。

一种表明 Spark 工作完成方法是显式调用停止 Spack Context 的 sc.stop() 方法，或者在 Python 中使用 with SparkContext() as sc : 构造方法来加载和去除 Spark Context。

REST API

除了 UI 中查看这些指标，也可以得到任务信息的 JSON ，这个能够是开发者更方便的创造新的 Spark 可视化和监控。 正在运行的程序和历史的程序都可以得到他们的 JSON 信息。挂载在 /api/v1，比如，对于 histroy server，他们通常会访问 http://<server-url>:18080/api/v1 ，对于运行的程序，则是 http://localhost:4040/api/v1。

API 中，程序拥有一个程序 ID， [app-id]。在 YARN 上执行时，每个程序可能进行有多次尝试，但尝试的 ID（attempt IDs）只有在集群模式下有，客户端模式的程序没有。在 YARN 集群模式上执行的程序会有 [attempt-id]。在下面列出的 API 中，是在 YARN 集群模式下运行时， [app-id] 就是 [base-app-id]/[attempt-id]，[base-app-id] 是 YARN 的程序 ID。

位置 | 含义

/applications
所有的应用程序的列表。
?status=[completed|running] 显示特定状态的程序。
?minDate=[date] 显示的最早时间.。
示例 :
?minDate=2015-02-10
?minDate=2015-02-03T16:42:40.000GMT
?maxDate=[date] 显示的最新的时间; 格式和 minDate 相同。
?limit=[limit] 程序显示数量的限制。

/applications/[app-id]/jobs | 指定应用的所有 Job（作业）列表 : ?status=[complete|succeeded|failed] 显示特定状态的信息。
/applications/[app-id]/jobs/[job-id] | 某个 job 的详情。
/applications/[app-id]/stages | 显示某个程序的 stages 列表。
/applications/[app-id]/stages/[stage-id] | 显示给定的 stages-id 状态。 ?status=[active|complete|pending|failed] 显示特定状态的信息。
/applications/[app-id]/stages/[stage-id]/[stage-attempt-id] | 指定 stage attempt 的详情。
/applications/[app-id]/stages/[stage-id]/[stage-attempt-id]/taskSummary | stage attempt 的指标集合统计。 ?quantiles 统计指定的 quantiles。 示例 : ?quantiles=0.01，0.5，0.99

/applications/[app-id]/stages/[stage-id]/[stage-attempt-id]/taskList
指定 stage attempt 的 task 列表。
?offset=[offset]&length=[len] 显示指定范围的task.。
?sortBy=[runtime|-runtime] task 排序.。
示例 : ?offset=10&length=50&sortBy=runtime

/applications/[app-id]/executors | 程序的 executors。
/applications/[app-id]/storage/rdd | 程序的 RDDs。
/applications/[app-id]/storage/rdd/[rdd-id] | 指定 RDD 详情。
/applications/[base-app-id]/logs | 以 zip 文件形式下载指定程序的 log。
/applications/[base-app-id]/[attempt-id]/logs | 以 zip 文件形式下载指定程序 atempt 的 log。

可恢复的 jobs 和 stages 的数量被独立的 Spark UI 的相同保留机制所约束;  "spark.ui.retainedJobs" 定义触发回收垃圾 jobs 阈值，和 spark.ui.retainedStages 限定 stages。注意，配置需要重启才能生效:通过增加这些值和重新启动服务器才可以保存获取更多的信息。

API 版本政策

这些 endpoints 都已经版本化以便在其之上开发，Spark 保证 : 

endpoints 不会被移除

对于任何给定的 endpoint，不会删除个别 fields

可能新增 endpoints

可能新增已有 endpoints 的 fields

将来可能在单独的 endpoint（例如，api / v2）添加 api 的新版本。 新版本不需要向后兼容。

API 版本可能被删除，但之前至少有版本老的 API 与新的 API 版本共存。

注意，即使检查正在运行的程序的 UI，applications/[app-id] 部分仍然是必需的，尽管只有一个程序可用。如。查看正在运行的应用程序的工作列表，你会去 http://localhost:4040/api/v1/applications/[app-id]/jobs. 。这是保持在两种模式下的路径一致。

Metrics

Spark 拥有可配置的 metrics system （度量系统） 其基于 Dropwizard Metrics Library. 这使用户可以通过多种 sinks 比如 HTTP，JMX，CSV 文件报告 Spark 的各项指标。Metrics system 是通过一个配置文件配置， Spark 需要其在路径 $SPARK_HOME/conf/metrics.properties 下。 可以通过 spark.metrics.conf configuration property 指定自定义文件的位置 。Spark 的指标拥有不同实例，对于不同的 Spark 组件是解耦的。在每个实例中，您可以配置一组需要的 Metrics 。目前支持以下 : 

master :  Spark 独立的 master 进程。

applications : 一个 master 组件用于报告各种程序。

worker : Spark 独立的 worker 进程。

executor :  Spark executor。

driver : Spark 驱动进程，（创建 SparkContext 的进程）。

每个例子都可以报告 0 项以上的 sinks，包含在 org.apache.spark.metrics.sink 之中

ConsoleSink : 记录指标信息到控制台。

CSVSink : 周期记录到 CSV 文件。

JmxSink : JMX 监控。

MetricsServlet : 启动 Servlet 向 Spark UI 提供 JSON 格式信息。

GraphiteSink : 传送到 Graphite 节点。

Slf4jSink : 存到 slf4j 。

Spark 同样支持 Ganglia sink ，因为版权原因无法默认安装 : 

GangliaSink : 发到 Ganglia 节点或者组播组（multicast group）。

安装 GangliaSink 需要构建一个定制的 Spark。注意，通过嵌入这个插件库 Spark 将包括 LGPL-licensed 授权的代码。对于 sbt 用户，构建前设置 SPARK_GANGLIA_LGPL 环境变量。对于 Maven 用户，要使 -Pspark-ganglia-lgpl 生效，除了修改 Spark 集群构建的环境配置，用户程序也需要加入 spark-ganglia-lgpl 组件。标准配置的语法在示例文件中，$SPARK_HOME/conf/metrics.properties.template。

高级工具

一些扩展工具可以用来帮助监控 Spark 任务性能 : 

集群监控工具， 比如 Ganglia， 可以对整个集群的利用率和性能瓶颈监控。比如， 一个 Ganglia 监控表盘可以很快地展示一个特定任务是否占用磁盘，网络，cpu。

操作系统分析工具如 dstat， iostat， 和 iotop 可以在单个节点上提供细粒度分析。

JVM 工具例如 jstack 生成 stack traces（线程堆栈信息）， jmap 生成 heap-dump（内存信息）， jstat 报告运行状态信息，并且 jconsole 提供对帮助理解 JVM 核心的各项属性可视化监视、管理。

优化指南

原文链接 : http://spark.apache.org/docs/latest/tuning.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2887785

贡献者 : 鱼刺，Sehriff，那伊抹微笑

大多数 Saprk 计算本质是内存，Spark  程序可以碰到集群中的 CPU、网络带宽或存储 资源上的瓶颈。大多数情况下，如果数据加载到内存中，网络带宽就是瓶颈。但有时候，还需要做一些调整，比如序列化形式存储 RDD （storing RDDs in serialized），以减少内存使用情况。还涉及两个主要方面 : 原始数据的系列化，良好的网络性能，以减少内存使用情况。下面做几个主要的讨论。

数据序列化

序列化在任何分布式应用程序提高性能方面都是至关重要的思路。格式对象序列化对降低消耗大量的字节数，将大大减少计算量。通常情况下，这将是 Spark 调整以优化应用程序的最先考虑的。

Spark 开发目的就在方便（可以让您在操作中与任何的 Java Type 类型一起工作）和性能之间最大化。它提供了两个序列化库 : 

Java serialization : 默认情况下，Spark 序列化使用 Java 对象 ObjectOutputStream 框架，你创建了java.io.Serializable.实现类就可以工作，可以更有效的延伸序列化的性能 java.io.Externalizable 中。Java 序列化优点灵活，但速度慢，序列化，并导致许多格式 class。

Kryo serialization : Spark 同样可以使用 Kryo 序列库（版本2），Kryo 序列化比 java 序列化在速度上更快（一般在10倍左右） 缺点就是不支持所有 Serializable 类 ，并且要求您提前在程序中注册您所需的类以获得最佳性能。

你可以通过设置 conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer") 初始化 SparkConf  来转化成 Kryo 序列化。此设置配置串行器不仅用在工作节点之间进行数据shuffle，而且还用于将RDD序列化到磁盘。Kryo 不能成为默认方式的唯一原因是需要用户进行注册 ，但建议在任何"网络密集"应用中进行尝试。自从Spark 2.0.0以来，我们在使用简单类型，简单类型的数组或字符串类型对RDD进行shuffle时，内部使用Kryo serializer。。

Spark自动包含Kryo序列化器，用于Twitter chill库中AllScalaRegistrar涵盖的许多常用的核心Scala类。

通过 registerKryoClasses 类的方法，用Kryo 注册的自定义类。

val conf = new SparkConf().setMaster(...).setAppName(...)
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))
val sc = new SparkContext(conf)

Kryo  documentation  提供更多高效的注册选项，比如添加自定义序列化。

针对对象是很大，你可能还需要增加的 spark.kryoserializer.buffer config。该值对大量序列化有带来一定个性能提升最后，如果您没有注册自定义类，Kryo 仍然可以使用，但它有完整的类名称存储每个对象时，这是一种浪费...

内存优化

内存优化有三个方面的考虑 : 对象所占用的内存，访问对象的消耗以及垃圾回收所占用的开销。

默认情况下，Java 对象存取速度快，但可以很容易地比内部 “raw” 数据的字段的消耗的 2-5 倍以及更多空间。这是由于以下几个原因 : 

每个不同的 Java 对象都有一个“对象头”大约是 16 个字节，并包含诸如一个指向它的类。并且包含了指向对象所对应的类的指针等信息。如果对象本身包含的数据非常少（比如就一个Int字段），那么对象头可能会比对象数据还要大。

Java String 在实际的 raw 字符串数据之外，还需要大约 40 字节的额外开销（因为 String 使用一个 Char 数组来保存字符串，而且需要保存长度等额外数据）；同时，因为 String 在内部使用 UTF-16 编码，每一个字符需要占用两个字节，所以，一个长度为 10 字节的字符串需要占用60 个字节。

常见的集合类，如 LinkedList 和 HashMap，使用 LinkedList 的数据结构，每条是一个 “warpper” 的对象（例如为 Map.Entry）。每一个条目不仅包含对象头，还包含了一个指向下一条目的指针（通常每个指针占 8 字节）。

基本类型的集合常常将它们存储为 “boxed” 对象 比如 java.lang.Integer 中的对象。

先介绍 Spark 内存管理的概述，然后再讨论具体策略使"用户"更有效地在应用去申请内存。特别是，我们将介绍对象的内存使用情况在哪里寻找，以及从哪些方面提升 - 改变你的数据结构，或存储的数据就是序列化格式。以及调整 Spark 缓存、Java 的垃圾收集器。

内存管理概述

Spark中的内存使用大部分属于两类：execution 和storage。 execution 内存是指用于以shuffles, joins, sorts and aggregations计算的内存，而storage内存是指用于在集群中缓存和传播内部数据的内存。 在Spark中，execution 和storage共享一个统一的区域（M）。 当没有使用execution 内存时，storage可以获取所有可用的内存，反之亦然。 如果需要，execution 可以驱逐storage，但只有在总存储内存使用量到达某个阈值（R）之前。 换句话说，R描述了M内的一个子区域，其中缓存块永远不会被驱逐。 由于实施的复杂性，storage不得驱逐execution 。

这种设计保证几个显著的特性。首先，应用程序不需要缓存整个执行到空间中，避免不必要的磁盘溢出。其次，使用缓存的应用程序可以保留最小的存储空间（R），其中的数据块不会被移除。最后，这种方法为各种工作负载提供了合理的开箱即用性能，而不需要用户内部如何分配内存的专业知识。

虽然有两种相关配置，但典型用户不需要调整它们，因为默认值适用于大多数工作负载：: 

spark.memory.fraction : 表示配置当前的内存管理器的最大内存使用比例，（默认 0.6）剩余 40% 部分被保留用于用户数据的结构，在 Spark 内部元数据，保障 OOM 错误，在异常大而稀疏的记录情况。

spark.memory.storageFraction : 表示配置用于配置 rdd 的 storage 与 cache 的默认分配的内存池大小（默认值 0.5）。

spark.memory.fraction 值的配置不仅仅以调试的 JVM 堆空间或 “tenured” 设置。还有一些 GC 优化。

确定内存消耗

计算数据集所需的内存消耗量的最佳方式是创建RDD，将其放入缓存中，并查看Web UI中的“Storage”页面。 该页面将告诉您RDD占用多少内存。要估计特定对象的内存消耗，请使用SizeEstimator的估计方法这对于尝试使用不同的数据布局来调整内存使用情况以及确定广播变量在每个执行程序堆中占用的空间量非常有用。

数据结构优化

减少内存消耗的首要方式是避免了 Java 特性开销，如基于指针的数据结构和二次封装对象。有几个方法可以做到这一点 : 

使用对象数组以及原始类型数组来设计数据结构，以替代 Java 或者 Scala 集合类（eg : HashMap）fastutil  库提供了原始数据类型非常方便的集合类，同时兼容 Java 标准类库。

尽可能地避免使用包含大量小对象和指针的嵌套数据结构。

采用数字 ID 或者枚举类型以便替代 String 类型的主键。

假如内存少于 32GB，设置 JVM 参数 -XX:+UseCompressedOops 以便将 8 字节指针修改成4字节。将这个配置添加到 spark-env.sh 中。

序列化RDD存储

当上面的优化都尝试过了对象同样很大。那么，还有一种减少内存的使用方法“以序列化形式存储数据”，在 RDD 持久化 API 中（RDD persistence API）使用序列化的 StorageLevel 例如 MEMORY_ONLY_SER 。 Spark 将每个 RDD 分区都保存为 byte 数组。序列化带来的唯一不足就是会降低访问速度，因为需要将对象反序列化（using Kryo）。如果需要采用序列化的方式缓存数据，我们强烈建议采用 Kryo，Kryo 序列化结果比 Java 标准序列化的更小（某种程度，甚至比对象内部的 raw 数据都还要小）。

垃圾回收优化

如果你需要不断的“翻动”程序保存的 RDD 数据，JVM 内存回收就可能成为问题（通常，如果只需进行一次 RDD 读取然后进行操作是不会带来问题的）。当需要回收旧对象以便为新对象腾内存空间时，JVM 需要跟踪所有的 Java 对象以确定哪些对象是不再需要的。需要记住的一点是，内存回收的代价与对象的数量正相关；因此，使用对象数量更小的数据结构（例如使用int数组而不是 LinkedList）能显著降低这种消耗。另外一种更好的方法是采用对象序列化，如上面所描述的一样；这样，RDD 的每一个 partition 都会保存为唯一一个对象（一个 byte 数组）。如果内存回收存在问题，在尝试其他方法之前，首先尝试使用 序列化缓存（serialized caching） 。

每项任务（task）的工作内存（运行task所需要的空间）以及缓存在节点的 RDD 之间会相互影响，这种影响也会带来内存回收问题。下面我们讨论如何为 RDD 分配空间以便减轻这种影响。

估算GC的影响

优化内存回收的第一步是获取一些统计信息，包括内存回收的频率、内存回收耗费的时间等。为了获取这些统计信息，我们可以把参数 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps 添加到 java 选项中（ 配置向导里面有关于传 java 选项参数到 Spark job 的信息）。设置完成后，Spark 作业运行时，我们可以在日志中看到每一次内存回收的信息。注意，这些日志保存在集群的工作节点（在他们工作目录下的 stout 文件中）而不是你的驱动程序（driver program )。

高级GC优化

为了进一步优化内存回收，我们需要了解 JVM 内存管理的一些基本知识。

Java 堆（heap）空间分为两部分 : 新生代和老生代。新生代用于保存生命周期较短的对象；老生代用于保存生命周期较长的对象。

新生代进一步划分为三部分[ Eden，Survivor1，Survivor2 ]

内存回收过程的简要描述 : 如果 Eden 区域已满则在 Eden 执行 minor GC 并将 Eden 和 Survivor1 中仍然活跃的对象拷贝到 Survivor2 。然后将 Survivor1 和 Survivor2 对换。如果对象活跃的时间已经足够长或者 Survivor2 区域已满，那么会将对象拷贝到 Old 区域。最终，如果 Old 区域消耗殆尽，则执行 full GC 。

Spark 内存回收优化的目标是确保只有长时间存活的 RDD 才保存到老生代区域；同时，新生代区域足够大以保存生命周期比较短的对象。这样，在任务执行期间可以避免执行 full GC 。下面是一些可能有用的执行步骤 : 

通过收集 GC 信息检查内存回收是不是过于频繁。如果在任务结束之前执行了很多次 full GC ，则表明任务执行的内存空间不足。

如果有过多的 minor GC 而不是 full GC，那么为 Eden 分配更大的内存是有益的。你可以为 Eden 分配大于任务执行所需要的内存空间。如果 Eden 的大小确定为 E，那么可以通过 -Xmn=4/3*E 来设置新生代的大小（将内存扩大到 4/3 是考虑到 survivor 所需要的空间)。

在打印的内存回收信息中，如果老生代接近消耗殆尽，那么减少用于缓存的内存空间。可这可以通过属性 spark.storage.memoryFraction 来完成。减少缓存对象以提高执行速度是非常值得的。

尝试设置 -XX:+UseG1GC 来使用垃圾回收器 G1GC 。在垃圾回收是瓶颈的场景使用它有助改善性能。当 executor 的 heap 很大时，使用 -XX:G1HeapRegionSize 增大 G1区大小很有必要。

举一个例子，如果任务从 HDFS 读取数据，那么任务需要的内存空间可以从读取的 block 数量估算出来。注意，解压后的 blcok 通常为解压前的 2-3 倍。所以，如果我们需要同时执行 3 或 4 个任务，block 的大小为 64M，我们可以估算出 Eden 的大小为 4*3*64MB。

监控内存回收的频率以及消耗的时间并修改相应的参数设置。

我们的经历表明有效的内存回收优化取决于你的程序和内存大小。 在网上还有很多更多其他调优选项 ， 总体而言有效控制内存回收的频率非常有助于降低额外开销。

executor 的 GC 调优标志位可以在 job 的配置中设置 spark.executor.extraJavaOptions 来指定。

其他优化

并行度级别

除非你为每步操作设置的 并行度 足够大，否则集群的资源是无法被充分利用的。Spark 自动根据文件大小设置运行在每个文件上的 map 任务的数量（虽然你可以通过 SparkContext.textFile 的可选参数来控制数量，等等），而且对于分布式 reduce 操作，例如 groupByKey 和 reduceByKey ，它使用最大父 RDD 的分区数。你可以通过第二个参数传入并行度（阅读文档 spark.PairRDDFunctions  ）或者通过设置系统参数 spark.default.parallelism 来改变默认值。通常来讲，在集群中，我们建议为每一个 CPU 核（ core ）分配 2-3 个任务。

Reduce Tasks的内存使用

有时，你会碰到 OutOfMemory 错误，这不是因为你的 RDD 不能加载到内存，而是因为 task 执行的数据集过大，例如正在执行 groupByKey 操作的 reduce 任务。Spark 的 shuffle 操作（sortByKey 、groupByKey 、reduceByKey 、join 等）为了实现 group 会为每一个任务创建哈希表，哈希表有可能非常大。最简单的修复方法是增加并行度，这样，每一个 task 的输入会变小。Spark 能够非常有效的支持短的 task（例如 200ms)，因为他会复用一个 executor 的 JVM 来执行多个 task，这样能减小 task 启动的消耗，所以你可以放心的增加任务的并行度到大于集群的 CPU 核数。

广播大的变量

使用 SparkContext 的  广播功能 可以有效减小每个序列化的 task 的大小以及在集群中启动 job 的消耗。如果 task 使用 driver program 中比较大的对象（例如静态查找表），考虑将其变成广播变量。Spark 会在 master 打印每一个 task 序列化后的大小，所以你可以通过它来检查 task 是不是过于庞大。通常来讲，大于 20KB 的 task 可能都是值得优化的。

数据本地性

数据本地性会对 Spark jobs 造成重大影响。如果数据和操作数据的代码在一起，计算就会变快。但如果代码和数据是分离的，其中一个必须移动到另一个。一般来说，移动序列化的代码比移动数据块来的快，因为代码大小远小于数据大小。Spark 根据该数据本地性的统一法则来构建 scheduling 计划。

数据本地性是指数据和操作该数据的代码有多近。根据数据的当前路径，有以下几个本地性级别。根据从近到远的数据排列 : 

PROCESS_LOCAL  数据跟代码在同一个JVM。这个是最好的本地性。

NODE_LOCAL  所有数据都在同一个节点。例如在同一个节点的 HDFS，或在同一个节点的另一个 executor 上。这个级别比 PROCESS_LOCAL 慢一点因为数据需要在多个 process（进程）间移动。

NO_PREF  从任何地方访问数据都一样快，没有本地性的偏好。

RACK_LOCAL  数据在同样的服务器机架上。数据在同一个机架的不同服务器上，所以需要通过网络传输，典型场景是通过单个交换机。

ANY  数据在网络的不同地方，不在同一个机架。

Spark 倾向于调度所有的 task 在最好的本地性级别，但未必总是行得通。如果所有空闲的 executor 都没有未处理的数据，Spark 就会切换到更低的本地性级别。

这样就有两个选择 : 

一直等待直到忙碌的CPU 释放下来对同一个服务器的数据启动 task 。

马上在远的、需要传输数据的地方启动一个 task。

Spark 典型做法是等待一段 timeout（超时）时间直到 CPU 释放资源。一旦 timeout 结束，它就开始移动数据到远处、有空闲 CPU 的地方。各个级别切换所等待的 timeout 时间可以单独配置或统一通过一个参数配置，需要更多细节可以查看配置页面的 spark.locality  参数。如果你的 task 看起来很长而且本地性差，就要考虑增加这些设置值，但默认设置一般都运行良好。

总结

该文指出了 Spark 程序优化所需要关注的几个关键点 - 最主要的是 数据序列化 和 内存优化 。对于大多数程序而言，采用 Kryo 序列化以及以序列化方式存储数据能够解决大部分性能问题。非常欢迎在 Spark mailing list 提问优化相关的问题。

作业调度

原文链接 : http://spark.apache.org/docs/latest/security.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2887905

贡献者 : breeze，那伊抹微笑

概述

Spark 有好几种计算资源调度的方式。首先，回忆一下集群模式概述中每个 Spark 应用（包含一个 SparkContext 实例）中运行了一些其独占的执行器（executor）进程。集群管理器提供了 Spark 应用之间的资源调度（scheduling across applications）。其次，在各个Spark 应用内部，各个线程可能并发地通过 action 算子提交多个 Spark 作业（job）。如果你的应用服务于网络请求，那这种情况是很常见的。在 Spark 应用内部（对应同一个 SparkContext）各个作业之间，Spark 默认 FIFO 调度，同时也可以支持公平调度（fair scheduler）。

跨应用调度

如果在集群上运行，每个 Spark 应用都会 SparkContext 获得一批独占的执行器 JVM，来运行其任务并存储数据。如果有多个用户共享集群，那么会有很多资源分配相关的选项，如何设置还取决于具体的集群管理器。 

对 Spark 所支持的各个集群管理器而言，最简单的资源分配，就是对资源静态划分。这种方式就意味着，每个 Spark 应用都是设定一个最大可用资源总量，并且该应用在整个生命周期内都会占住这些资源。这种方式在 Spark 独立部署和 YARN 调度，以及 Mesos 粗粒度模式（coarse-grained Mesos mode）下都可用。

Standalone mode : 默认情况下，Spark 应用在独立部署的集群中都会以 FIFO（first-in-first-out）模式顺序提交运行，并且每个 Spark 应用都会占用集群中所有可用节点。不过你可以通过设置 spark.cores.max 或者 spark.deploy.defaultCores 来限制单个应用所占用的节点个数。最后，除了可以控制对 CPU 的使用数量之外，还可以通过 spark.executor.memory 来控制各个应用的内存占用量。

Mesos : 在 Mesos 中要使用静态划分的话，需要将 spark.mesos.coarse 设为 true，同样，你也需要设置 spark.cores.max 来控制各个应用的 CPU 总数，以及 spark.executor.memory 来控制各个应用的内存占用。

YARN : 在 YARN 中需要使用 --num-executors 选项来控制 Spark 应用在集群中分配的执行器的个数，对于单个执行器（executor）所占用的资源，可以使用 --executor-memory 和 --executor-cores 来控制。

Mesos 上还有一种动态共享 CPU 的方式。在这种模式下，每个 Spark 应用的内存占用仍然是固定且独占的（仍由 spark.executor.memory 决定），但是如果该 Spark 应用没有在某个机器上执行任务的话，那么其他应用可以占用该机器上的 CPU。这种模式对集群中有大量不是很活跃应用的场景非常有效，例如 : 集群中有很多不同用户的 Spark shell session。但这种模式不适用于低延迟的场景，因为当 Spark 应用需要使用 CPU 的时候，可能需要等待一段时间才能取得 CPU 的使用权。要使用这种模式，只需要在 mesos:// URL 上设置 spark.mesos.coarse 属性为 false 即可。

值得注意的是，目前还没有任何一种资源分配模式能支持跨 Spark 应用的内存共享。如果你想通过这种方式共享数据，我们建议你可以单独使用一个服务（例如 alluxio），这样就能实现多应用访问同一个 RDD 的数据。

动态资源分配

Spark 提供了一种基于负载来动态调节 Spark 应用资源占用的机制。这意味着，你的应用会在资源空闲的时候将其释放给集群，需要时再重新申请。这一特性在多个应用共享 Spark 集群资源的情况下特别有用。

这个特性默认是禁用的，但是在所有的粗粒度集群管理器上都是可用的，如 : 独立部署模式，YARN 模式以及 Mesos 粗粒度模式（Mesos coarse-grained mode）。

配置和部署

要使用这一特性有两个前提条件。首先，你的应用必须设置 spark.dynamicAllocation.enabled 为 true。其次，你必须在每个节点上启动 external shuffle service，并在你的应用中将 spark.shuffle.service.enabled 设为 true。external shuffle service 的目的是在移除 executor 的时候，能够保留 executor 输出的 shuffle 文件（本文后续有更详细的描述）。启用 external shuffle service 的方式在各个集群管理器上各不相同 : 

在 Spark 独立部署的集群中，你只需要在 worker 启动前设置 spark.shuffle.server.enabled 为 true 即可。

在 Mesos 粗粒度模式下，你需要在各个节点上运行 ${SPARK_HOME}/sbin/start-mesos-shuffle-service.sh 并设置 spark.shuffle.service.enabled 为 true 即可。例如，你可以用 Marathon 来启用这一功能。

在 YARN 模式下，需要按以下步骤在各个 NodeManager 上启动 : 

首先按照YARN profile 构建 Spark。如果你已经有打好包的 Spark，可以忽略这一步。

找到 spark-<version>-yarn-shuffle.jar。如果你是自定义编译，其位置应该在 ${SPARK_HOME}/network/yarn/target/scala-<version>，否则应该可以在 lib 目录下找到这个 jar 包。

将该 jar 包添加到 NodeManager 的 classpath 路径中。

配置各个节点上的 yarn-site.xml，将 spark_shuffle 添加到 yarn.nodemanager.aux-services 中，然后将 yarn.nodemanager.aux-services.spark_shuffle.class 设为 org.apache.spark.network.yarn.YarnShuffleService，并将 spark.shuffle.service.enabled 设为 true。

最后重启各节点上的 NodeManager。 

所有相关的配置都是可选的，并且都在 spark.dynamicAllocation.* 和 spark.shuffle.service.* 命名空间下。更详细请参考 : 配置页面。

资源分配策略

总体上来说，Spark 应该在执行器空闲时将其关闭，而在后续要用时再次申请。因为没有一个固定的方法，可以预测一个执行器在后续是否马上回被分配去执行任务，或者一个新分配的执行器实际上是空闲的，所以我们需要一些试探性的方法，来决定是否申请或移除一个执行器。

请求策略

一个启用了动态分配的 Spark 应用会在有等待任务需要调度的时候，申请额外的执行器。这种情况下，必定意味着已有的执行器已经不足以同时执行所有未完成的任务。

Spark 会分轮次来申请执行器。实际的资源申请，会在任务挂起 spark.dynamicAllocation.schedulerBacklogTimeout 秒后首次触发，其后如果等待队列中仍有挂起的任务，则每过 spark.dynamicAlloction.sustainedSchedulerBacklogTimeout 秒触发一次资源申请。另外，每一轮所申请的执行器个数以指数形式增长。例如，一个 Spark 应用可能在首轮申请1个执行器，后续的轮次申请个数可能是 2个、4个、8个… … 。

采用指数级增长策略的原因有两个 : 第一，对于任何一个 Spark 应用如果只是需要多申请少数几个执行器的话，那么必须非常谨慎地启动资源申请，这和 TCP 慢启动有些类似；第二，如果一旦 Spark 应用确实需要申请很多个执行器的话，那么可以确保其所需的计算资源及时地增长。

移除策略

移除执行器的策略就简单多了。Spark 应用会在某个执行器空闲超过 spark.dynamicAllocation.executorIdleTimeout 秒后将其删除。在绝大多数情况下，执行器的移除条件和申请条件都是互斥的，也就是说，执行器在有待执行任务挂起时，不应该空闲。

优雅地关闭 Executor（执行器）

非动态分配模式下，执行器可能的退出原因有执行失败或者相关 Spark 应用已经退出。不管是那种原因，执行器的所有状态都已经不再需要，可以丢弃掉。但在动态分配的情形下，执行器有可能在 Spark 应用运行期间被移除。这时候，如果 Spark 应用尝试去访问该执行器存储的状态，就必须重算这一部分数据。因此，Spark 需要一种机制，能够优雅地关闭执行器，同时还保留其状态数据。

这种需求对于混洗操作尤其重要。混洗过程中，Spark 执行器首先将 map 输出写到本地磁盘，同时执行器本身又是一个文件服务器，这样其他执行器就能够通过该执行器获得对应的 map 结果数据。一旦有某些任务执行时间过长，动态分配有可能在混洗结束前移除任务异常的执行器，而这些被移除的执行器对应的数据将会被重新计算，但这些重算其实是不必要的。

要解决这一问题，就需要用到 external shuffle service ，该服务在 Spark 1.2 引入。该服务在每个节点上都会启动一个不依赖于任何 Spark 应用或执行器的独立进程。一旦该服务启用，Spark 执行器不再从各个执行器上获取 shuffle 文件，转而从这个 service 获取。这意味着，任何执行器输出的混洗状态数据都可能存留时间比对应的执行器进程还长。

除了混洗文件之外，执行器也会在磁盘或者内存中缓存数。一旦执行器被移除，其缓存数据将无法访问。这个问题目前还没有解决。或许在未来的版本中，可能会采用外部混洗服务类似的方法，将缓存数据保存在堆外存储中以解决这一问题。 

应用内调度

在指定的 Spark 应用内部（对应同一 SparkContext 实例），多个线程可能并发地提交 Spark 作业（job）。在本节中，作业（job）是指，由 Spark action 算子（如 : collect）触发的一系列计算任务的集合。Spark 调度器是完全线程安全的，并且能够支持 Spark 应用同时处理多个请求（比如 : 来自不同用户的查询）。 

默认，Spark 应用内部使用 FIFO 调度策略。每个作业被划分为多个阶段（stage）（例如 : map 阶段和 reduce 阶段），第一个作业在其启动后会优先获取所有的可用资源，然后是第二个作业再申请，再第三个……。如果前面的作业没有把集群资源占满，则后续的作业可以立即启动运行，否则，后提交的作业会有明显的延迟等待。

不过从 Spark 0.8 开始，Spark 也能支持各个作业间的公平（Fair）调度。公平调度时，Spark 以轮询的方式给每个作业分配资源，因此所有的作业获得的资源大体上是平均分配。这意味着，即使有大作业在运行，小的作业再提交也能立即获得计算资源而不是等待前面的作业结束，大大减少了延迟时间。这种模式特别适合于多用户配置。

要启用公平调度器，只需设置一下 SparkContext 中 spark.scheduler.mode 属性为 FAIR 即可 : 

val conf = new SparkConf().setMaster(...).setAppName(...)
conf.set("spark.scheduler.mode", "FAIR")
val sc = new SparkContext(conf)

公平调度资源池

公平调度器还可以支持将作业分组放入资源池（pool），然后给每个资源池配置不同的选项（如 : 权重）。这样你就可以给一些比较重要的作业创建一个“高优先级”资源池，或者你也可以把每个用户的作业分到一组，这样一来就是各个用户平均分享集群资源，而不是各个作业平分集群资源。Spark 公平调度的实现方式基本都是模仿 Hadoop Fair Scheduler 来实现的。 

默认情况下，新提交的作业都会进入到默认资源池中，不过作业对应于哪个资源池，可以在提交作业的线程中用 SparkContext.setLocalProperty 设定 spark.scheduler.pool 属性。示例代码如下 : 

// 假设 sc 是您的 SparkContext 变量
sc.setLocalProperty("spark.scheduler.pool", "pool1")

一旦设好了局部属性，所有该线程所提交的作业（即 : 在该线程中调用action算子，如 : RDD.save/count/collect 等）都会使用这个资源池。这个设置是以线程为单位保存的，你很容易实现用同一线程来提交同一用户的所有作业到同一个资源池中。同样，如果需要清除资源池设置，只需在对应线程中调用如下代码 : 

sc.setLocalProperty("spark.scheduler.pool", null)

资源池默认行为 

默认地，各个资源池之间平分整个集群的资源（包括 default 资源池），但在资源池内部，默认情况下，作业是 FIFO 顺序执行的。举例来说，如果你为每个用户创建了一个资源池，那么久意味着各个用户之间共享整个集群的资源，但每个用户自己提交的作业是按顺序执行的，而不会出现后提交的作业抢占前面作业的资源。

配置资源池属性 

资源池的属性需要通过配置文件来指定。每个资源池都支持以下3个属性 : 

schedulingMode : 可以是 FIFO 或 FAIR，控制资源池内部的作业是如何调度的。

weight : 控制资源池相对其他资源池，可以分配到资源的比例。默认所有资源池的 weight 都是 1。如果你将某个资源池的 weight 设为 2，那么该资源池中的资源将是其他池子的2倍。如果将 weight 设得很高，如 1000，可以实现资源池之间的调度优先级 – 也就是说，weight=1000 的资源池总能立即启动其对应的作业。

minShare : 除了整体 weight 之外，每个资源池还能指定一个最小资源分配值（CPU 个数），管理员可能会需要这个设置。公平调度器总是会尝试优先满足所有活跃（active）资源池的最小资源分配值，然后再根据各个池子的 weight 来分配剩下的资源。因此，minShare 属性能够确保每个资源池都能至少获得一定量的集群资源。minShare 的默认值是 0。

资源池属性是一个 XML 文件，可以基于 conf/fairscheduler.xml.template 修改，然后在 SparkConf 的 spark.scheduler.allocation.file 属性指定文件路径：

conf.set("spark.scheduler.allocation.file", "/path/to/file")

资源池 XML 配置文件格式如下，其中每个池子对应一个 <pool> 元素，每个资源池可以有其独立的配置 : 

<?xml version="1.0"?>
<allocations>
  <pool name="production">
    <schedulingMode>FAIR</schedulingMode>
    <weight>1</weight>
    <minShare>2</minShare>
  </pool>
  <pool name="test">
    <schedulingMode>FIFO</schedulingMode>
    <weight>2</weight>
    <minShare>3</minShare>
  </pool>
</allocations>

完整的例子可以参考 conf/fairscheduler.xml.template。注意，没有在配置文件中配置的资源池都会使用默认配置（schedulingMode : FIFO，weight : 1，minShare : 0）。

Spark 安全

原文链接 : http://spark.apache.org/docs/latest/security.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2887905

贡献者 : 装X帝，那伊抹微笑

Spark 当前支持使用共享密钥来认证。可以通过配置 spark.authenticate 选项来开启认证。这个参数用来控制 Spark 的通讯协议是否使用共享密钥来执行认证。这个认证是一个基本的握手，用来保证两侧具有相同的共享密钥然后允许通讯。如果共享密钥无法识别，他们将不能通讯。创建共享密钥的方法如下 : 

对于 Spark on YARN 的部署方式，配置 spark.authenticate 为 true，会自动生成和分发共享密钥。每个 application 会使用一个单独的共享密钥

对于 Spark 的其他部署方式，需要在每一个 nodes 配置 spark.authenticate.secret 参数。这个密码将会被所有的 Master/Workers 和 aplication 使用

WEB UI

Spark UI 可以通过 spark.ui.filters 配置使用 javax servlet filters、通过 spark.ui.https.enabled 配置使用 https/SSL 来进行安全保护。

认证

当 UI 中有一些不允许其他用户看到的数据时，用户可能想对 UI 进行安全防护。用户指定的 javax servlet filter 可以对用户进行身份认证，用户一旦登入，Spark 可以比较与这个用户相对应的视图 ACLs 来确认是否授权用户访问 UI。配置项 spark.acls,enable,spark.ui.view.acls 和 spark.ui.view.acls.groups 控制 ACLs 的行为。注意，启动 application 的用户具有使用 UI 视图的访问权限。在 YARN 上，Spark UI 使用标准的 YARN web application 代理机制可以通过任何已安装的 Hadoop filters 进行认证。

Spark 也支持修改 ACLs 来控制谁具有修改一个正在运行的 Spark application 的权限。这些权限包括 kill 这个 application 或者一个 task。以上功能通过选项 spark.acls.enable，spark.modify.acls 和 spark.modify.acls.groups 来控制。注意如果你已经登入 UI，为了使用 UI 上的 kill 按钮，必须先添加这个用户到 modify acls 和 view acls。在 YARN 上，modify acls 通过 YARN 接口传入并控制谁具有 modify 权限。Spark 允许在 acls 中指定多个对所有的 application 都具有 view 和 modify 权限的管理员，通过选项 spark.admin.acls 和 spark.admin.acls.groups 来控制。这样做对于一个有帮助用户调试 applications 的管理员或者支持人员的共享集群，非常有帮助。

事件日志

如果你的 applications 使用事件日志，事件日志的的位置必须手动创建同时具有适当的访问权限（spark.eventLog.dir）。如果想对这些日志文件做安全保护，目录的权限应该设置为 drwxrwxrwxt。目录的属主应该是启动 history server 的高级用户，同时组权限应该仅限于这个高级用户组。这将允许所有的用户向目录中写入文件，同时防止删除不是属于自己的文件（权限字符t为防删位）。事件日志将会被 Spark 创建，只有特定的用户才就有读写权限。

加密

Spark 支持 HTTP SSL 协议。SASL 加密用于块传输服务和 RPC 端。

Spark 本地临时存储的数据（如 shuffle 文件，缓存的数据，其他和 application 相关文件）当前不支持加密。如果有这种需求，一个变通的方式是配置 cluster manager 将 application 数据存储到加密磁盘上。

SSL 配置

SSL 配置是分级组织的。用户可以对所有的通讯协议配置默认的 SSL 设置，除非被特定协议的设置覆盖掉。这样用户可以很容易的为所有的协议提供通用的设置，无需禁用每个单独配置的能力。通用的 SSL 设置在 Spark 配置文件的 spark.ssl 命名空间中。下表描述了用于覆盖特定组件默认配置的命名空间

config Namespace（配置命名空间） | Component（组件）
spark.ssl.fs | HTTP file server and broadcast server
spark.ssl.ui | Spark application Web UI
spark.ssl.standalone | Standalone Master / Worker Web UI
spark.ssl.historyServer | History Server Web UI

所有的SSL选项可以在 配置页面 里查询到。SSL 必须在所有的节点进行配置，对每个使用特定协议通讯的组件生效。

YARN模式

key-store 可以在客户端侧准备好，然后作为 application 的一部分通过 executor 进行分发和使用。因为用户能够通过使用 spark.yarn.dist.files 或者 spark.yarn.dis.archives 配置选项，在 YARN上 启动 application 之前部署文件。加密和传输这些文件的责任在 YARN 一侧，与 Spark 无关。

为了一个长期运行的应用比如 Spark Streaming 应用能够写 HDFS，需要通过分别设置 --principal 和 --keytab 参数传递 principal 和 keytab 给 spark-submit。传入的 keytab 会通过 Hadoop Distributed Cache 拷贝到运行 Application 的 Master（这是安全的 - 如果 YARN 使用 SSL 配置同时启用了 HDFS 加密）。Kerberos 会使用这个 principal 和 keytab 周期的更新登陆信息，同时 HDFS 需要的授权 tokens 会周期生成，因此应用可以持续的写入 HDFS。

STANDALONE 模式

用户需要为 master 和 slaves 提供 key-stores 和相关配置选项。必须通过附加恰当的 Java 系统属性到 SPARK_MASTER_OPTS 和 SPARK_WORKER_OPTS 环境变量中，或者只在 SPARK_DAEMON_JAVA_OPTS 中。在这种模式下，用户可以允许 executors 使用从派生它的 worker 继承来的 SSL 设置。可以通过设置 spark.ssl.useNodeLocalConf 为 true 来完成。如果设置这个参数，用户在客户端侧提供的设置不会被 executors 使用。

准备 key-store

可以使用 keytool 程序生成 key-stores。这个工具的使用说明在这里。为 standalone 部署方式配置 key-stores 和 trust-store 最基本的步骤如下 : 

为每个 node 生成 keys pair

在每个 node 上导出 key pair 到一个文件

导入以上生成的所有的公钥到一个单独的 trust-store

在所有的节点上分发 trust-store

配置SASL加密

当认证（sprk.authenticate）开启的时候，块传输支持 SASL 加密。通过在 application 配置中设置 spark.authenticate.enableSaslEncryption 为 true 来开启 SASL 加密。

当使用外部 shuffle 服务，需要在 shuffle 服务配置中通过设置 spark.network.sasl.serverAlwaysEncrypt 为 true 来禁用未加密的连接。如果这个选项开启，未设置使用 SASL 加密的 application 在链接到 shuffer 服务的时候会失败。

针对网络安全配置端口

Spark 严重依赖 network，同时一些环境对使用防火墙有严格的要求。下面使一些 Spark 网络通讯使用的主要的端口和配置方式。

仅对 Standalone 模式

From | To | Default Port（默认端口） | Purpose（目的） | Configuration Setting（配置设置） | Notes（注意）
Browser | Standalone Master | 8080 | Web UI | spark.master.ui.port / SPARK_MASTER_WEBUI_PORT | Jetty-based.   Standalone mode only.
Browser | Standalone Worker | 8081 | Web UI | spark.worker.ui.port / SPARK_WORKER_WEBUI_PORT | Jetty-based. Standalone mode only.
Driver / Standalone Worker | Standalone Master | 7077 | Submit job to cluster / Join cluster | spark.worker.ui.port / SPARK_WORKER_WEBUI_PORT | Set to "0" to choose a port randomly. Standalone mode only.
Standalone Master | Standalone Worker | (random) | Schedule executors | SPARK_WORKER_PORT | Set to "0" to choose a port randomly. Standalone mode only.

所有的集群管理器（cluster managers)

From | To | Default Port（默认端口） | Purpose（目的） | Configuration Setting（配置设置） | Notes（注意）
Browser | Application | 4040 | Web UI | spark.ui.port | Jetty-based
Browser | History Server | 18080 | Web UI | spark.history.ui.port | Jetty-based
Executor / Standalone Master | Driver | (random) | Connect to application / Notify executor state changes | spark.driver.port | Set to "0" to choose a port randomly.
Executor / Driver | Executor / Driver | (random) | Block Manager port | spark.blockManager.port | Raw socket via ServerSocketChannel

更多关于安全配置参数方面的细节请参考配置页面。安全方面的实施细节参考 org.apache.spark.SecurityManager

硬件配置

原文链接 : http://spark.apache.org/docs/latest/hardware-provisioning.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2887869

贡献者 : 飞师傅，那伊抹微笑

Spark 开发者都会遇到一个常见问题，那就是如何为 Spark 配置硬件。然而正确的硬件配置取决于使用的场景，我们提出以下建议。

存储系统

因为大多数 Spark 作业都很可能必须从外部存储系统（例如 Hadoop 文件系统或者 HBase）读取输入的数据，所以部署 Spark 时尽可能靠近这些系统是很重要的。我们建议如下 : 

如果可以，在 HDFS 相同的节点上运行 Spark 。最简单的方法是在相同节点上设置 Spark 独立集群模式，并且配置 Spark 和 Hadoop 的内存和 CPU 的使用以避免干扰（Hadoop 的相关选项为 : 设置每个任务内存大小的选项 mapred.child.java.opts 以及设置任务数量的选项 mapred.tasktracker.map.tasks.maximum 和 mapred.tasktracker.reduce.tasks.maximum）。当然您也可以在常用的集群管理器（比如 Mesos 或者 YARN）上运行  Hadoop 和 Spark。

如果不可以在相同的节点上运行，建议在与 HDFS 相同的局域网中的不同节点上运行 Spark 。

对于像 HBase 这样的低延时数据存储系统，在与这些存储系统不同的节点上运行计算作业来可能更有利于避免干扰。

本地磁盘

虽然 Spark 可以在内存中执行大量计算，但是他仍然使用本地磁盘来存储不适合内存存储的数据以及各个阶段的中间结果。我们建议每个节点配置 4-8 个磁盘，并且不使用 RAID 配置(只作为单独挂载点)。在 Linux 中,使用 noatime选项 挂载磁盘以减少不必要的写入。在 Spark 中，配置 spark.local.dir 变量为逗号分隔的本地磁盘列表。如果您正在运行 HDFS，可以使用与 HDFS 相同的磁盘。

内存

一般来说，Spark 可以在每台机器 8GB 到数百 GB 内存的任何地方正常运行。在所有情况下，我们建议只为 Spark 分配最多75% 的内存；其余部分供操作系统和缓存区高速缓存存储器使用。

您需要多少内存取决于您的应用程序。如果您需要确定的应用程序中某个特定数据集占用内存的大小，您可以把这个数据集加载到一个 Spark RDD 中，然后在 Spark 监控 UI 页面（http://<driver-node>:4040）中的 Storage 选项卡下查看它在内存中的大小。需要注意的是，存储级别和序列化格式对内存使用量有很大的影响 - 如何减少内存使用量的建议，请参阅调优指南。

最后,需要注意的是 Java 虚拟机在超过 200GB 的 RAM 时表现得并不好。如果您购置的机器有比这更多的 RAM ，您可以在每个节点上运行多个 Worker 的 JVM 实例。在 Spark 的独立模式下,您可以通过 conf/spark-env.sh 中的 SPARK_WORKER_INSTANCES 和 SPARK_WORKER_CORES 两个参数来分别设置每个节点的 Worker 数量和每个 Worker 使用的 Core 数量。

网络

根据我们的经验，当数据在内存中时，很多 Spark 应用程序跟网络有密切的关系。使用 10 千兆位以太网或者更快的网络是让这些应用程序变快的最佳方式。这对于 “distributed reduce” 类的应用程序来说尤其如此，例如 group-by 、reduce-by 和 SQL join。任何程序都可以在应用程序监控UI 页面（http://<driver-node>:4040）中查看 Spark 通过网络传输的数据量。

CPU Core 数量

因为 Spark 实行线程之间的最小共享，所以 Spark 可以很好地在每台机器上扩展数十个 CPU Core。您应该为每台机器至少配置 8-16 个 Core。根据您工作负载的 CPU 成本，您可能还需要更多 : 当数据都在内存中时，大多数应用程序就只跟 CPU 或者网络有关了。

构建 Spark

原文链接 : http://spark.apache.org/docs/latest/building-spark.html

译文链接 : http://www.apache.wiki/pages/viewpage.action?pageId=2888437

贡献者 : 王鑫，就问你屌不屌，那伊抹微笑

构建 Apache Spark

Apache Maven

构建 Apache Spark 参考了基于 Maven 的构建。构建 Spark 会使用 Maven ，而且需要 3.3.9 或者更新版本的 Maven 以及 Java 7 及以上版本。

设置 Maven 的内存使用率

想要使用更多内存，需要配置 Maven 设置中的 MAVEN_OPTS 项

export MAVEN_OPTS="-Xmx2g -XX : ReservedCodeCacheSize=512m"

在使用 Java 7 来进行编译时，您需要添加额外选项 “-XX : MaxPermSize=512M” 到 MAVEN_OPTS 项

如果不添加这些参数，那么您可能看到如下错误和警告 :

[INFO] Compiling 203 Scala sources and 9 Java sources to /Users/me/Development/spark/core/target/scala-2.11/classes...
[ERROR] PermGen space -> [Help 1]

[INFO] Compiling 203 Scala sources and 9 Java sources to /Users/me/Development/spark/core/target/scala-2.11/classes...
[ERROR] Java heap space -> [Help 1]

[INFO] Compiling 233 Scala sources and 41 Java sources to /Users/me/Development/spark/sql/core/target/scala-{site.SCALA_BINARY_VERSION}/classes...
OpenJDK 64-Bit Server VM warning :  CodeCache is full. Compiler has been disabled.
OpenJDK 64-Bit Server VM warning :  Try increasing the code cache size using -XX : ReservedCodeCacheSize=

您可以通过以上方法设置 MAVEN_OPTS 项来解决这一问题。

注意 :

如果使用 build/mvn 但是没有设置 MAVEN_OPTS，那么脚本会自动的将以上选项添加到 MAVEN_OPTS 环境变量

构建 Spark 的测试阶段将自动把这些选项添加到 MAVEN_OPTS ，即使不使用 build/mvn

在您使用 Java 8 和 build/mvn 来构建和运行测试时，您可能会看到类似于 "ignoring option MaxPermSize=1g; support was removed in 8.0" 的警告。这些警告无伤大雅。

build / mvn

Spark 现在与一个独立的 Maven 安装包封装到了一起，使从源目录 build/ 下的原始位置来构建和部署 Spark 更加容易。这个脚本能自动在本地的 build/ 目录下载和安装所有需要的构建需求包，例如 Maven、Scala、Zinc。其尊重任何已经存在的 mvn 二进制文件，但是却无论如何将自己的 Scala 和 Zinc 的副本摧毁，来确保需求包的版本是合适的。build/mvn 的执行通过 mvn 调用允许过渡到之前的构建方法。例如，可以使用如下方法构建一个版本的 Spark :

./build/mvn -Pyarn -Phadoop-2.4 -Dhadoop.version=2.4.0 -DskipTests clean package

其他的构建例子能在下文找到

构建一个可运行的 Distribution 版本

想要创建一个像是那些 Spark 下载页中的 Spark 的分布式版本，并且能使其运行，使用项目中 root 目录下的 ./dev/make-distribution.sh 脚本。它可以使用 Maven 的配置文件进行配置，如直接使用 Maven 构建。例如 :

./dev/make-distribution.sh --name custom-spark --tgz -Psparkr -Phadoop-2.4 -Phive -Phive-thriftserver -Pyarn

想要获取更详细的信息，可以运行

./dev/make-distribution.sh --help

指定 Hadoop 版本

因为 HDFS 并非跨版本协议兼容的，如果您想从 HDFS 读数据，那么您就需要构建与您的环境中 HDFS 版本相符的 Spark 。您可以通过设置属性 hadoop.version 来实现。如果不设置，那么 Spark 将默认按照 Hadoop 2.2.0 来进行构建。注意对于特定的 Hadoop 版本，需要某些特定的构造文件。

Hadoop version | Profile required
2.2.x | hadoop-2.2
2.3.x | hadoop-2.3
2.4.x | hadoop-2.4
2.6.x | hadoop-2.6
2.7.x and later 2.x | hadoop-2.7

您可以配置 yarn 的配置文件和可选设置 yarn.version 属性，如果其与 hadoop.version 不同的时候。 Spark 只支持 Yarn2.2.0 或者之后版本。

例如 :

# Apache Hadoop 2.2.X
./build/mvn -Pyarn -Phadoop-2.2 -DskipTests clean package

# Apache Hadoop 2.3.X
./build/mvn -Pyarn -Phadoop-2.3 -Dhadoop.version=2.3.0 -DskipTests clean package

# Apache Hadoop 2.4.X or 2.5.X
./build/mvn -Pyarn -Phadoop-2.4 -Dhadoop.version=VERSION -DskipTests clean package

# Apache Hadoop 2.6.X
./build/mvn -Pyarn -Phadoop-2.6 -Dhadoop.version=2.6.0 -DskipTests clean package

# Apache Hadoop 2.7.X and later
./build/mvn -Pyarn -Phadoop-2.7 -Dhadoop.version=VERSION -DskipTests clean package

# Different versions of HDFS and YARN.
./build/mvn -Pyarn -Phadoop-2.3 -Dhadoop.version=2.3.0 -Dyarn.version=2.2.0 -DskipTests clean package

构建支持 Hive 和 JDBC 的支持

想要使 Spark SQL 集成 Hive 以及 JDBC 服务和 CLI ，请增加 -Phive 和 Phive-thriftserver 配置到您已经存在的配置选项。默认 Spark 绑定 Hive 1.2.1

# Apache Hadoop 2.4.X with Hive 1.2.1 support
./build/mvn -Pyarn -Phadoop-2.4 -Dhadoop.version=2.4.0 -Phive -Phive-thriftserver -DskipTests clean package

打包时排除针对 YARN 的 Hadoop 依赖

mvn package 生成的汇编目录，默认情况下将包括所有 Spark 的依赖，包括 Hadoop 和一些生态系统项目。在 YARN 的部署上，这会导致这些项目的许多版本都出现在执行目录中 : 包装在 Spark 组建在没个结点的版本，包括在属性 yarn.application.classpath 中。配置 hadoop-provided 可以在构建时不包括 Hadoop 生态项目，例如 ZooKeeper 和 Hadoop 本身。

构建适用于 Scala 2.10

如果希望使用 Scala 2.10 来编译 Spark 包，使用属性 -Dscala-2.10 

./dev/change-scala-version.sh 2.10
./build/mvn -Pyarn -Phadoop-2.4 -Dscala-2.10 -DskipTests clean package

构建单个子模块

可以使用选项 mvn -pl来构建 Spark 的子模块。

例如，您可以构建 Spark Streaming 通过 :

./build/mvn -pl  : spark-streaming_2.11 clean install

这里 spark-streaming_2.11 是在文件 streaming/pom.xml 中定义的 artifactId 

连续的编译

我们使用插件 scala-maven-plugin 来支持增量和连续编译。例如 :

./build/mvn scala : cc

这里应该运行连续编译(即等待变化)。然而这并没得到广泛的测试，有一些需要注意的陷阱 :

它只扫描路径 src/main 和 src/test ，所以其只对含有该结构的某些子模块起作用

您通常需要在工程的根目录运行 mvn install 在编译某些特定子模块时。这是因为依赖于其他子模块的子模块需要通过 spark-parent 模块实现。

因此，运行 core 子模块的连续编译总流程如下 :

$ ./build/mvn install
$ cd core
$ ../build/mvn scala : cc

用 Zinc 加速编译

Zinc 是 SBT 增量编译器的长期运行的服务器版本，当作为后台进程在本地运行时，它可有加速编译基于 Scala 的项目 : 如 Spark 。定期使用 Maven 重新编译 Spark 的开发人员会对 Zinc 最感兴趣。此章节将说明如何构建和运行 Zinc.OS X 用户可有通过 brew 安装Zinc。

如果使用 bulid/mvn 软件包，ZInc 将会自动下载并且用于所有版本。 此进程将会在第一次 build/mvn 编译后自动开启并绑定到 3030 端口，除非自定义设置了环境变量 ZINC_PORT。可有在任何时候通过运行 build/zinc-<version>/bin/zinc -shutdown 来停止 Zinc 进程，但每当通过 build/mvn 编译的时候它又会重新启动。

通过 SBT 编译

Maven 是 Spark 官方推荐的编译工具。但是当SBT能够提供快速迭代编译的功能时，它逐渐被应用于日常开发。更多高级的开发者希望能够运用 SBT 。

SBT 通过 Maven POM 文件进行编译，因此也可以设置相同的 Maven 配置文件和变量来控制 SBT 的构建。 例如 :

./build/sbt -Pyarn -Phadoop-2.3 package

为了避免每次启动 SBT 都需要重新编译，可以通过运行 build/sbt 以交互模式来启动 SBT，然后在命令提示符下运行所有 build 命令。 有关减少构建时间的更多建议，请参阅 wiki 页面。

加密的文件系统

当构建在加密的文件系统上（例如，如果您的主目录被加密），那么 Spark 构建可能会失败，并出现 “Filename too long”错误。 作为解决方法，在项目的 pom.xml 中的 scala-maven-plugin 的配置参数中添加以下代码 :

<arg>-Xmax-classfile-name</arg>
<arg>128</arg>

 并在 project/SparkBuild.scala 中添加 :

scalacOptions in Compile ++= Seq("-Xmax-classfile-name", "128"),

关于 sharedSettings val，如果您不确定在哪里添加这些行，请参阅此 PR。

IntelliJ IDEA 或者 Eclipse

有关设置 IntelliJ IDEA 或 Eclipse 以用来进行 Spark 开发和故障排除的帮助文档，请参阅 IDE 设置的 wiki 页面。

运行测试

默认情况下通过 ScalaTest Maven 组件进行测试。

一些测试需要首先打包 Spark ，因此，第一次总是使用 -DskipTests 运行 mvn package。 以下是正确（构建，测试）顺序的示例 :

./build/mvn -Pyarn -Phadoop-2.3 -DskipTests -Phive -Phive-thriftserver clean package
./build/mvn -Pyarn -Phadoop-2.3 -Phive -Phive-thriftserver test

ScalaTest 组件还支持运行特定的 Scala test 套件。如下 :

./build/mvn -P... -Dtest=none -DwildcardSuites=org.apache.spark.repl.ReplSuite test
./build/mvn -P... -Dtest=none -DwildcardSuites=org.apache.spark.repl.* test

 或者一个 java 测试用例 :

./build/mvn test -P... -DwildcardSuites=none -Dtest=org.apache.spark.streaming.JavaAPISuite

使用 SBT 测试

一些测试需要首先打包 Spark，所以总是第一次总是运行 build/sbt。 以下是正确（构建，测试）顺序的示例 :

./build/sbt -Pyarn -Phadoop-2.3 -Phive -Phive-thriftserver package
./build/sbt -Pyarn -Phadoop-2.3 -Phive -Phive-thriftserver test

要运行特定的测试套件

./build/sbt -Pyarn -Phadoop-2.3 -Phive -Phive-thriftserver "test-only org.apache.spark.repl.ReplSuite"
./build/sbt -Pyarn -Phadoop-2.3 -Phive -Phive-thriftserver "test-only org.apache.spark.repl.*"

要运行特定子项目的测试套件，如下所示 :

./build/sbt -Pyarn -Phadoop-2.3 -Phive -Phive-thriftserver core/test

运行 JAVA 8 测试组件

只能运行 JAVA 8 测试组件，不能运行其他版本

./build/mvn install -DskipTests
./build/mvn -pl  : java8-tests_2.11 test

 或者

./build/sbt java8-tests/test

当检测到 Java 8 JDK 时，将自动启用 Java 8 测试。 如果您安装了JDK 8，但它不是系统默认值，则可以在运行测试之前将 JAVA_HOME 设置为指向 JDK 8 。

用 Maven 做 PySpark 测试

 如果您正在构建 PySpark 并希望运行 PySpark 测试，您将需要使用 Hive 支持构建 Spark 。

./build/mvn -DskipTests clean package -Phive
./python/run-tests

运行测试脚本也可以限于特定的 Python 版本或特定的模块

./python/run-tests --python-executables=python --modules=pyspark-sql

运行 R 测试

要运行 SparkR 测试，您需要安装 R 包 testthat（从 R shell 运行 install.packages（testthat））。 您可以使用命令只运行 SparkR 测试 :

./R/run-tests.sh

注意 : 您还可以使用 SBT 构建运行 Python 测试，前提是您使用 Hive 支持构建 Spark 。

运行基于 Docker 集成的测试组件

为了运行 Docker 集成测试，您必须在您的盒子上安装 docker 引擎。 有关安装的说明，请访问 Docker 站点。 一旦安装，docker 服务需要启动，如果还没有运行。 在 Linux 上，这可以通过 sudo service docker start 来完成。

./build/mvn install -DskipTests
./build/mvn -Pdocker-integration-tests -pl  : spark-docker-integration-tests_2.11

或者

./build/sbt docker-integration-tests/test