全文共10887字,预计阅读时间70分钟。

第二章  Spark入门介绍与基础案例

1.  第一步:下载Apache Spark安装包

    1.1  Spark的目录和文件

2.  第二步:使用Scale或者PySpark Shell

    2.1  使用本地机器

3.  第三步:理解Spark应用的概念

    3.1  Spark jobs

    3.2  Spark Stages

    3.3  Spark Tasks

4.  转换、操作和懒加载(transformation、action、Lasy Evaluation)

    4.1  窄依赖和宽依赖的转换

5.  Spark UI界面

6.  运行第一个Spark应用程序

    6.1  统计巧克力豆的数量

    6.2  用Scale构建独立的Spark应用程序

7.  总结

在本章中,你可以收获如何设置Spark,并通过三个简单步骤来启动你编写的第一个独立的应用程序。

我们将使用本地模式,在单节点服务上安装Spark服务,本章内容中所有数据处理脚本会通过Spark Shell实现,这是学习框架的简单方法,这样的好处是能够为执行Spark迭代操作快速得到结果。使用Spark shell的优点是简单快速,而且可以在编写复杂的Spark应用程序之前使用小数据集进行Spark计算和验证测试,而不需要去编写复杂的应用程序,打包提交再执行,但是在实际的工作中,对于大型数据集还是需要依赖分布式处理的并行计算能力,本地模式就显得不适合,通常你需要使用YARN或Kubernetes部署模式。

虽然Spark shell只支持Scala、Python和R,但是你可以使用其他任何受支持的语言(包括Java)编写Spark应用程序,并在Spark SQL中发出查询。我们确实希望你能熟悉你所选择的语言。

1.  第一步:下载Apache Spark安装包

在开始之前,请转到Spark下载页面,从步骤2的下拉菜单中选择“预构建的Apache Hadoop2.7”安装包,然后在步骤3中点击“下载Spark”链接(图2-1)。

spark  教程 spark实战教程_spark  教程

然后就能够下载到spark-3.0.0-preview2-bin-hadoop2.7.tgz文件,它包含了在本地模式上运行Spark所需要的所有与hadoop相关的二进制文件。或者,如果要将其安装在已经具有HDFS或Hadoop的环境上,则可以从下拉菜单中选择和Hadoop版本适配的部署包。至于如何从源代码进行构建这个问题不在本书讨论的范围,但你可以在文档中阅读更多关于它的内容。

当这本书快要出版时,Apache Spark3.0仍然处于预览模式,但是你可以使用相同的下载方法和说明下载最新的Spark3.0。

自从Apache Spark2.2发布以来,开发人员只关心在Python中学习Spark,通过从PyPI 仓库中安装PySpark。如果你纯粹使用Python编写程序,则不必安装运行Scala、Java或R所需的所有其他软件库;这样可以使得二进制文件更加轻量化,想要通过PyPI安装PySpark,只要运行pip install pyspark 指令即可。

还可以为SQL、ML和MLlib安装一些额外的依赖项,通过pip install pyspark[sql、ml、mllib]或者pip install pyspark pyspark[sql](如果只需要SQL依赖项的话)安装依赖。

你需要在你的计算机上安装Java 8或更高版本,并设置JAVA_HOME环境变量。有关如何下载和安装Java的说明,请参阅本文档。

如果你想要在shell模式下运行R,则必须安装R依赖,然后运行sparkR脚本。此外你还可以使用R社区的开源项目sparklyr从而使用R进行分布式计算。

1.1  Spark的目录和文件

我们假设你在你的本地计算机或集群上运行了Linux或macOS操作系统,本书中所有关于命令的使用和说明都是在上面运行的。一旦你完成了Spark安装包下载,cd 到下载的目录,用tar -xf spark-3.0.0-preview2-binhadoop2.7.tgz 解压成安装目录,然后cd到该目录,并查看内容:

spark  教程 spark实战教程_大数据_02

让我们简要说明一下这些文件和目录的用途和目的。在Spark 2.x和3.0中添加了新项目,并且还对一些现有文件和目录的内容进行了更改:

README.md:

使用说明,文件包含有关如何使用Spark Shell、从源代码构建Spark、运行独立的Spark示例、Spark文档详细链接和配置指南以及贡献Spark的详细说明。

bin:

顾名思义,此目录包含用于与Spark交互的大多数可执行脚本,包括Spark Shell(spark-sql、  pyspark、spark shell和sparkR)。在本章后面,我们将使用此目录中的这些shell和可执行文件,使用spark提交一个独立的Spark应用程序,并编写脚本构建和推送Docker映像,在支持Spark运行在Kubernetes上。

sbin:

此目录中的大多数脚本都是管理集群的,用于在各种部署模式下启动和停止集群中的Spark 服务。有关部署模式的详细信息,请参见第1章表1-1中的表格。

Kubernetes:

自Spark2.4发布以来,此目录包含用于在Kubernetes上创建Spark分布式服务的DockerFile镜像文件。同时它还包含一个说明文件,提供关于如何在构建Docker镜像之前构建分布式Spark的说明。

data:

此目录下包含了许多以*.txt结尾的数据文件,这些文件可以作为Spark组件的测试数据:MLlib、Structured Streaming和GraphX。

examples:

对于任何开发人员来说,快速学习一个新的平台需要冲两个地方入手,第一个就是示例代码,还有就是比较全面的使用文档,具备这两个条件能够让我们更快的上手使用。Spark提供了Java、Python、R和Scala的示例,你完全可以在学习框架时使用它们。我们将在本章和后续章节中介绍其中的一些例子。

 

2.  第二步:使用Scala或者PySpark Shell

如前所述,Spark提供了四个广泛使用的解释器,类似于交互式“ Shell”,并实现即席查询数据分析,包括pyspark、Spark shell、spark sql和spark R。在许多方面,如果你能够使用Python、Scala、R、SQL、Unix操作系统 Shell(如bash或Bourneshell),它们的交互性会类似于你已经熟悉的shell。

这些shell已经得到增强以支持连接到集群,并允许你将分布式数据加载到Spark Executor的内存中。无论你处理的是GB数据还是小型数据集,Spark Shell都有助于快速学习Spark。

要启动PySpark,cd到bin目录,并通过pySpark启动shell。如果你已经从PyPI安装了PySpark,那么只需输入pySpark就足够了:

$ pyspark
Python 3.7.3 (default, Mar 27 2019, 09:23:15)
[Clang 10.0.1 (clang-1001.0.46.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
20/02/16 19:28:48 WARN NativeCodeLoader: Unable to load native-hadoop library 
for your platform... using builtin-java classes where applicable
Welcome to
 ____ __
 / __/__ ___ _____/ /__
 _\ \/ _ \/ _ `/ __/ '_/
 /__ / .__/\_,_/_/ /_/\_\ version 3.0.0-preview2
 /_/
Using Python version 3.7.3 (default, Mar 27 2019 09:23:15)
SparkSession available as 'spark'.
>>> spark.version
'3.0.0-preview2'
>>>

要使用Scala启动类似的Spark shell,cd到bin目录执行spark-shell指令.

$ spark-shell
20/05/07 19:30:26 WARN NativeCodeLoader: Unable to load native-hadoop library 
for your platform... using builtin-java classes where applicable
Spark context Web UI available at http://10.0.1.7:4040
Spark context available as 'sc' (master = local[*], app id = local-1581910231902)
Spark session available as 'spark'.
Welcome to
 ____ __
 / __/__ ___ _____/ /__
 _\ \/ _ \/ _ `/ __/ '_/
 /___/ .__/\_,_/_/ /_/\_\ version 3.0.0-preview2
 /_/
Using Scala version 2.12.10 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_241)
Type in expressions to have them evaluated.
Type :help for more information.
scala> spark.version
res0: String = 3.0.0-preview2
scala>

2.1  使用本地机器

现在你已经在本地计算机上下载并安装了Spark,在本章的其余部分中,你将在本地使用Spark Shell。也就是说,Spark将以本地模式运行。

有关在本地模式中运行的提醒,请参见第1章的表1-1。

如上一章所述,Spark计算被表示为算子操作。然后将这些操作转换为基于RDD的任务集,并分发给Spark的Executor执行计算。

让我们看看一个简短的例子,读取文本文件返回一个DataFrame,显示已读取的字符串,并计算文件中的总行数。这个简单的示例说明了高级结构化API的使用,我们将在下一章中介绍它。DataFrame上的show(10,false)操作仅显示前10行而不进行截断;默认情况下,截断布尔标志为true。以下是Scala Shell的语法结构:

scala> val strings = spark.read.text("../README.md")
strings: org.apache.spark.sql.DataFrame = [value: string]
scala> strings.show(10, false)
+------------------------------------------------------------------------------+
|value |
+------------------------------------------------------------------------------+
|# Apache Spark |
| |
|Spark is a unified analytics engine for large-scale data processing. It |
|provides high-level APIs in Scala, Java, Python, and R, and an optimized |
|engine that supports general computation graphs for data analysis. It also |
|supports a rich set of higher-level tools including Spark SQL for SQL and |
|DataFrames, MLlib for machine learning, GraphX for graph processing, |
| and Structured Streaming for stream processing. |
| |
|<https://spark.apache.org/> |
+------------------------------------------------------------------------------+
only showing top 10 rows
scala> strings.count()
res2: Long = 109
scala>

非常简单,让我们看看使用Python交互式shell pyspark的一个类似示例:

$ pyspark
Python 3.7.3 (default, Mar 27 2019, 09:23:15)
[Clang 10.0.1 (clang-1001.0.46.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.apache.spark.unsafe.Platform 
WARNING: Use --illegal-access=warn to enable warnings of further illegal 
reflective access operations
WARNING: All illegal access operations will be denied in a future release
20/01/10 11:28:29 WARN NativeCodeLoader: Unable to load native-hadoop library 
for your platform... using builtin-java classes where applicable
Using Spark's default log4j profile: org/apache/spark/log4j-defaults.properties
Setting default log level to "WARN".
To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use 
setLogLevel(newLevel).
Welcome to
 ____ __
 / __/__ ___ _____/ /__
 _\ \/ _ \/ _ `/ __/ '_/
 /__ / .__/\_,_/_/ /_/\_\ version 3.0.0-preview2
 /_/
Using Python version 3.7.3 (default, Mar 27 2019 09:23:15)
SparkSession available as 'spark'.
>>> strings = spark.read.text("../README.md")
>>> strings.show(10, truncate=False)
+------------------------------------------------------------------------------+
|value |
+------------------------------------------------------------------------------+
|# Apache Spark |
| |
|Spark is a unified analytics engine for large-scale data processing. It |
|provides high-level APIs in Scala, Java, Python, and R, and an optimized |
|engine that supports general computation graphs for data analysis. It also |
|supports a rich set of higher-level tools including Spark SQL for SQL and |
|DataFrames, MLlib for machine learning, GraphX for graph processing, |
|and Structured Streaming for stream processing. |
| |
|<https://spark.apache.org/> |
+------------------------------------------------------------------------------+
only showing top 10 rows
>>> strings.count()
109
>>>

要退出任何Spark Shell,请按Ctrl-D。如所见,与Spark shell的快速交互不仅有利于快速学习,而且有利于快速原型化,测试数据非常方便。

在前面的示例中,请注意跨Scala和Python之间的API语法和签名奇偶校验。在Spark从1.x演变的整个过程中,这是持续改进的功能点之一。

还要注意,我们使用高级结构化API将文本文件读取到Spark DataFrame中,而不是RDD。在整本书中,我们将更加关注这些结构化的API;因为到了Spark2.x,rdd被封装成低级API使用,更加通用。

在高级结构化API中表示的每个计算都被拆分为低级且优化后并生成RDD操作,然后转换为Executor的JVM的Scala字节码。由此生成的RDD操作代码不提供用户访问,它和面向用户的RDD应用程序也不一样。

3.  第三步:理解Spark应用的概念

现在你已经下载了Spark,以独立模式安装在本地电脑上,同时启动了Spark shell,并以交互方式执行了一些简短的代码示例,接下来就是需要将这些内容整合成完整的应用程序。

为了更好地了解我们的示例代码中涉及的一起操作和原理,你需要熟悉Spark应用程序的一些关键概念和如何跨Spark Executor作为任务将代码逻辑做转换和执行。我们将首先定义一些重要的术语:

Application(应用):

也就是用户编写的Spark应用程序/代码,包含了Driver功能代码和分布在集群中多个节点上运行的Executor代码。

SparkSession

它为用户提供了一个统一的切入点来使用Spark的各项功能,并允许使用它的API对Spark进行编程。在交互式Spark shell中,Spark驱动程序会为你实例化一个SparkSession,而在Spark应用程序中,你需要自己创建一个SparkSession对象。

Job

作业,由多个 task 组成的一个并行计算, 这些 task 产生自一个 Spark action (比如, save, collect) 操作. 在 driver 的日志中可以看到 job 这个术语.。

Stage

阶段,每个 job 被分解为多个 stage, 每个 stage 其实就是一些 task 的集合, 这些 stage 之间相互依赖 (与 MapReduce 中的 map 与 reduce stage 类似)。

Task

任务,stage根据数据的分区个数又可以具体分为多个task,task可以并行执行,所以stage又称为taskSet(工作集合)。让我们更详细地了解这些概念。

 

Spark应用程序和SparkSession

Spark驱动程序(Driver)是每个Spark应用程序的核心,它负责创建SparkSession对象。使用Spark Shell时,驱动程序是shell的一部分,并为你创建SparkSession对象(通过变量Spark访问),正如你在启动shell之前的示例中看到的那样。

在这些示例中,因为你在本地电脑上启动了Spark shell,所以所有操作都在本地一个JVM中运行。但是你可以基于本地模式轻松地启动Spark shell继而并行分析数据。spark shell --help命令或pyspark --help命令将向你介绍如何连接到Spark集群管理器。图2-2显示了在完成此操作后,Spark在集群上的执行方式。

spark  教程 spark实战教程_大数据_03

一旦有了SparkSession,你就可以使用API编程Spark以执行Spark操作。

3.1  Spark Jobs

在与Spark shell的交互式Session期间,驱动程序会将Spark应用程序转换为一个或多个Spark作业(图2-3)。然后,它会将每个作业转换为一个DAG图。这本质上是Spark的执行计划,其中DAG中的每个节点都可以是单个或多个Spark阶段。

spark  教程 spark实战教程_人工智能_04

3.2  Spark Stages 

作为DAG节点的一部分,根据RDD的依赖关系,可以分为宽依赖和窄依赖,根据宽依赖将DAG图分为多个stage。其中宽依赖一般涉及shuffle操作,所以也可以认为shuffle是stage的一个划分依据,stage是根据每个可以序列或并行形成的操作来创建(如图2-4)。并非所有的Spark操作都可以在一个阶段进行,因此它们可以分为多个阶段。通常在算子的计算边界上划分,在Spark Executor之间控制数据传输。

spark  教程 spark实战教程_编程语言_05

3.3  Spark Tasks

每个阶段由Spark Task(执行单元)组成,然后跨Spark Executor联合计算;每个任务映射到一个核心,在一个数据分片上计算(图2-5)。因此,具有16个核的Executor可以有16个或更多任务并行处理16个或更多分区,使得Spark任务可以并行执行!

spark  教程 spark实战教程_编程语言_06

4.  转换、操作和懒加载(transformation、action、Lazy Evaluation)

对分布式数据集来说Spark操作可以分为两种类型:转换和操作。转换,顾名思义,可以将一个Spark DataFrame转换为一个新的DataFrame而不改变原始数据,从而赋予它不可变性的特性。换句话说,select() 或filter()等操作不会更改原始DataFrame,而是将转换后的操作结果返回为新的DataFrame。

值得一提的是所有的转换都是懒性计算的,也就是说懒加载,它们并不会直接计算出来结果,而是作为一种血缘关系被记录或记住。记录的血缘关系允许Spark在以后的执行计划中重排某些转换、控制分区数量或将优化stage转换操作,以实现更高效的执行。懒加载是Spark延迟执行的策略,所有的转换操作只有当调用action操作时才会触发结果计算(从磁盘读取或写入磁盘)。

一个操作会触发对所有记录的转换的惰性计算。在图2-6中,记录所有转换T,直到调用动作A。每个转换T都会产生一个新的DataFrame。

spark  教程 spark实战教程_spark  教程_07

虽然懒加载策略允许Spark通过查看链式转换来优化查询,但血缘关系和数据不变性提供了容错能力。因为Spark记录了其血缘关系中的每个转换操作,而DataFrame在转换之间是不可变的,所以它可以通过重放记录的血缘关系来重现其原始状态,使它在发生故障时具有弹性,也就是容错性。

表2-1列出了一些转换和操作的示例。

spark  教程 spark实战教程_编程语言_08

这些操作和转换有助于创建Spark查询计划,我们将在下一章中介绍。在调用action操作之前,不会执行查询计划中的任何操作。在下面Python和Scala的示例中,有两个转换操作-read()和filter(),还有一个count()操作,count操作将触发对作为查询执行计划的一部分而记录的转换操作。在本示例中,在shell中执行filtered.count()之前不会发生结果计算:

# In Python
>>> strings = spark.read.text("../README.md")
>>> filtered = strings.filter(strings.value.contains("Spark"))
>>> filtered.count()


// In Scala
scala> import org.apache.spark.sql.functions._
scala> val strings = spark.read.text("../README.md")
scala> val filtered = strings.filter(col("value").contains("Spark"))
scala> filtered.count()
res5: Long = 20

4.1  窄依赖和宽依赖的转换

如前所述,转换是指对Spark进行惰性计算的操作。懒加载方案的一个巨大提升是,Spark可以检查你的计算执行计划,并确定如何优化它。这种优化可以通过连接或流水线化某些操作并将它们分配到一个阶段,或者通过确定哪些操作需要跨集群进行洗牌或交换数据,将它们分成各个阶段来实现。

转换可以被归类为具有窄依赖或宽依赖关系。任何可以从单个输入分区计算出单个输出分区的转换都是一个窄依赖的转换。例如,在前面的代码段中,filter()和contains()表示窄依赖转换操作,因为它们可以在单个分区上操作并产生结果输出分区,而无需任何数据交换。

但是,groupBy()或者orderBy()在spark中属于宽依赖操作,对来自其他分区的数据进行读取、组合最后写入磁盘。由于每个分区都有自己的计数,即在其数据行中包含“Spark”单词,因此一个count(groupBy())操作会强制对来自集群中每个Executor分区的数据洗牌。在此转换中,orderBy()操作需要其他分区的输出才能完成最终聚合计算。

图2-7说明了这两种类型的依赖关系。

spark  教程 spark实战教程_spark  教程_09

5.  Spark UI 界面

Spark包括一个图形用户界面,你可以用于检查或监视Spark应用程序的各个分解阶段,即作业、阶段和任务。根据Spark的部署方式,驱动程序将启动默认在端口4040上启动WebUI,在页面上你可以查看各个指标项以及详细信息,例如:

  • 调度程序的阶段和任务的列表
  • RDD大小和内存的摘要
  • 有关运行环境的信息
  • 有关正在运行的Executor的信息
  • 所有的Spark SQL查询

启动spark-shell时,部分输出日志会显示要在端口4040访问的本地主机URL。

让我们检查上一节中的Python示例是如何转换为作业、阶段和任务的。要查看DAG的可视化界面,请单击Web用户界面中的“DAG Visualization”。如图2-8所示,驱动程序创建了一个作业和一个阶段。

spark  教程 spark实战教程_编程语言_10

请注意,因为只有一个阶段,所以Executor之间的数据不需要进行数据交换。Spark的各个操作都会显示在蓝色的方框中。

阶段0由一个任务组成。如果你有多个任务,则它们将被并行执行。你可以在“Stages”选项卡中查看每个阶段的详细信息,如图2-9所示。

spark  教程 spark实战教程_spark  教程_11

我们将在第7章中更详细地介绍SparkUI。现在,请注意,UI界面为Spark的内部工作提供了一个显微透镜,作为调试和检查的工具。

Databricks是一家在云中提供托管的Apache Spark平台的公司。除了使用本地机器在本地模式下运行Spark外,你还可以使用免费的Databricks社区版来尝试本章和其他章节中的一些示例(图2-10)。作为Apache Spark的学习工具,社区版有许多值得注意的教程和示例。除了使用Python、R、Scala或SQL编写自己的notebook指外,还可以导入其他notebook,包括Jupyter notebook。

要获取帐户,请访问https://databricks.com/try并按照说明免费试用社区版。注册后,你可以从其GitHub仓库中导入本书的notebook。

 

spark  教程 spark实战教程_hadoop_12

6.  运行第一个Spark应用程序

为了便于学习和探索,Spark包为Spark的每个组件提供了一组样本应用程序。欢迎你浏览安装位置的示例目录,了解可用内容。

从本地计算机上的安装目录中,提供了几个简单的Java和Scala样例,可以使用命令运行bin/run example <class> [params]。例如:

$ ./bin/run-example JavaWordCount README.md

运行之后控制台上显示输出消息,以及readme.md文件中每个单词的列表及其计数(统计单词数量是分布式计算领域的“Hello,World”)。

6.1  统计巧克力豆的数量

在上一个示例中,我们计算了一个文件中的单词。如果文件很大,它会被划分成小的数据块分发到集群各个节点中,我们的Spark应用程序将计算任务进行分发,在每个分区中计算每个单词的数量,并返回最终的聚合计数结果。但这个例子已经有点陈词滥调了。

如果让我们解决类似的问题,但是使用更大的数据集并使用更多的Spark的分布式功能和DataFrame API。我们将在后面的章节中介绍在这个Spark应用程序中使用的API,但现在我们先继续往下阅读。

这本书的作者中有一位数据科学家,他喜欢烤饼干,并加入巧克力豆,然后经常奖励她在美国不同洲际的学生,她经常用一批这些饼干形象类比机器学习和数据科学课程。但她显然是通过数据驱动的,很明显,她希望确保自己为不同州的学生在分配到对应颜色巧克力豆的饼干(如图2-11),每种颜色代表一个州的学生。

spark  教程 spark实战教程_大数据_13

接下来让我们编写一个Spark程序,用于读取超过100000个条目(每行都具有<state、mnm_color、count>),并计算和聚合每种颜色和洲际的计数。这些汇总的计数结果告诉我们每个州学生喜欢的巧克力豆颜色。在示例2-1中提供了完整的Python实现逻辑。

Example 2-1. Counting and aggregating M&Ms (Python version)
# Import the necessary libraries.
# Since we are using Python, import the SparkSession and related functions
# from the PySpark module.
import sys
from pyspark.sql import SparkSession
from pyspark.sql.functions import count
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: mnmcount <file>", file=sys.stderr)
sys.exit(-1)
# Build a SparkSession using the SparkSession APIs.
# If one does not exist, then create an instance. There
# can only be one SparkSession per JVM.
spark = (SparkSession
.builder
.appName("PythonMnMCount")
.getOrCreate())
# Get the M&M data set filename from the command-line arguments
mnm_file = sys.argv[1]
# Read the file into a Spark DataFrame using the CSV
# format by inferring the schema and specifying that the
# file contains a header, which provides column names for comma-
# separated fields.
mnm_df = (spark.read.format("csv")
.option("header", "true")
.option("inferSchema", "true")
.load(mnm_file))
# We use the DataFrame high-level APIs. Note
# that we don't use RDDs at all. Because some of Spark's
# functions return the same object, we can chain function calls.
# 1. Select from the DataFrame the fields "State", "Color", and "Count"
# 2. Since we want to group each state and its M&M color count,
# we use groupBy()
# 3. Aggregate counts of all colors and groupBy() State and Color
# 4 orderBy() in descending order
count_mnm_df = (mnm_df
.select("State", "Color", "Count")
.groupBy("State", "Color")
.agg(count("Count").alias("Total"))
.orderBy("Total", ascending=False))
# Show the resulting aggregations for all the states and colors;
# a total count of each color per state.
# Note show() is an action, which will trigger the above
# query to be executed.
count_mnm_df.show(n=60, truncate=False)
print("Total Rows = %d" % (count_mnm_df.count()))
36 | Chapter 2: Downloading Apache Spark and Getting Started # While the above code aggregated and counted for all
# the states, what if we just want to see the data for
# a single state, e.g., CA?
# 1. Select from all rows in the DataFrame
# 2. Filter only CA state
# 3. groupBy() State and Color as we did above
# 4. Aggregate the counts for each color
# 5. orderBy() in descending order
# Find the aggregate count for California by filtering
ca_count_mnm_df = (mnm_df
.select("State", "Color", "Count")
.where(mnm_df.State == "CA")
.groupBy("State", "Color")
.agg(count("Count").alias("Total"))
.orderBy("Total", ascending=False))
# Show the resulting aggregation for California.
# As above, show() is an action that will trigger the execution of the
# entire computation.
ca_count_mnm_df.show(n=10, truncate=False)
# Stop the SparkSession
spark.stop()

你可以使用你最喜欢的工具将此代码写成一个名为mnmcount.py的Python文件中,从本书的GitHub 仓库中下载mnn_dataset.csv文件,并使用安装箱目录中的submit-Spark脚本将其作为Spark作业提交。将SPARK_HOME环境变量设置为在本地计算机上安装Spark的根目录。

前面的代码使用DataFrame API,它读取起来类似于高级DSL查询。我们将在下一章中介绍这个和其他API;现在,你可以指示Spark做什么,而不是如何做,不像RDD API。真是太酷了!

为了避免将详细的INFO信息打印到控制台,请将log4j.properties.template文件复制到log4j.properties,并在conf/log4j.properties文件中设置log4j.rootCategory=WARN。

让我们使用Python API提交我们的第一个Spark作业(有关代码的说明,请阅读示例2-1中的内联注释):

$SPARK_HOME/bin/spark-submit mnmcount.py data/mnm_dataset.csv
+-----+------+-----+
|State|Color |Total|
+-----+------+-----+
|CA |Yellow|1807 |
|WA |Green |1779 |
|OR |Orange|1743 |
| 37|TX |Green |1737 |
|TX |Red |1725 |
|CA |Green |1723 |
|CO |Yellow|1721 |
|CA |Brown |1718 |
|CO |Green |1713 |
|NV |Orange|1712 |
|TX |Yellow|1703 |
|NV |Green |1698 |
|AZ |Brown |1698 |
|CO |Blue |1695 |
|WY |Green |1695 |
|NM |Red |1690 |
|AZ |Orange|1689 |
|NM |Yellow|1688 |
|NM |Brown |1687 |
|UT |Orange|1684 |
|NM |Green |1682 |
|UT |Red |1680 |
|AZ |Green |1676 |
|NV |Yellow|1675 |
|NV |Blue |1673 |
|WA |Red |1671 |
|WY |Red |1670 |
|WA |Brown |1669 |
|NM |Orange|1665 |
|WY |Blue |1664 |
|WA |Yellow|1663 |
|WA |Orange|1658 |
|NV |Brown |1657 |
|CA |Orange|1657 |
|CA |Red |1656 |
|CO |Brown |1656 |
|UT |Blue |1655 |
|AZ |Yellow|1654 |
|TX |Orange|1652 |
|AZ |Red |1648 |
|OR |Blue |1646 |
|UT |Yellow|1645 |
|OR |Red |1645 |
|CO |Orange|1642 |
|TX |Brown |1641 |
|NM |Blue |1638 |
|AZ |Blue |1636 |
|OR |Green |1634 |
|UT |Brown |1631 |
|WY |Yellow|1626 |
|WA |Blue |1625 |
|CO |Red |1624 |
|OR |Brown |1621 |
|TX |Blue |1614 |
|OR |Yellow|1614 |
|NV |Red |1610 |
|CA |Blue |1603 |
|WY |Orange|1595 |
|UT |Green |1591 |
|WY |Brown |1532 |
+-----+------+-----+
Total Rows = 60
+-----+------+-----+
|State|Color |Total|
+-----+------+-----+
|CA |Yellow|1807 |
|CA |Green |1723 |
|CA |Brown |1718 |
|CA |Orange|1657 |
|CA |Red |1656 |
|CA |Blue |1603 |
+-----+------+-----+

首先,我们将看到每个洲际的每个巧克力豆颜色的所有聚合结果,下面是仅针对CA的聚合(其首选颜色为黄色)。

如果你想使用这个相同的Spark程序的Scala版本,该怎么办呢?API相似;在Spark中,在支持的语言中有很好的同等性,很多都是相通的,但是会有细微的语法差异。示例2-2是该程序的Scala版本。可以先看一看,在下一节中,我们将向你展示如何构建和运行该应用程序。

Example 2-2. Counting and aggregating M&Ms (Scala version)
package main.scala.chapter2
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions._
/**
* Usage: MnMcount <mnm_file_dataset>
*/
object MnMcount {
def main(args: Array[String]) {
val spark = SparkSession
.builder
.appName("MnMCount")
.getOrCreate()
if (args.length < 1) {
print("Usage: MnMcount <mnm_file_dataset>")
sys.exit(1)
}
// Get the M&M data set filename
val mnmFile = args(0)
// Read the file into a Spark DataFrame
val mnmDF = spark.read.format("csv")
.option("header", "true")
.option("inferSchema", "true")
.load(mnmFile)
// Aggregate counts of all colors and groupBy() State and Color
// orderBy() in descending order
val countMnMDF = mnmDF
.select("State", "Color", "Count")
.groupBy("State", "Color")
.agg(count("Count").alias("Total"))
.orderBy(desc("Total"))
// Show the resulting aggregations for all the states and colors
countMnMDF.show(60)
println(s"Total Rows = ${countMnMDF.count()}")
println()
// Find the aggregate counts for California by filtering
val caCountMnMDF = mnmDF
.select("State", "Color", "Count")
.where(col("State") === "CA")
.groupBy("State", "Color")
.agg(count("Count").alias("Total"))
.orderBy(desc("Total"))
// Show the resulting aggregations for California
caCountMnMDF.show(10)
// Stop the SparkSession
spark.stop()
}
}

6.2  用Scala构建独立的Spark应用程序

现在我们将向你展示如何使用Scala构建工具(sbt)构建第一个Scala Spark程序。

因为Python是一种声明式语言,并且没有像先编译这样的步骤(尽管可以在pyc中将Python代码编译成字节码),我们不会在这里执行此步骤。有关如何使用Maven构建JavaSpark程序的详情,请参考Apache Spark网站上的指南。为了简洁起见,在这本书中,我们主要介绍了Python和Scala中的例子。

build.sbt是编译构建文件,类似于makefile,描述并指示Scala编译器构建与Scala相关的任务,如jars、package、依赖关系以及在哪里查找它们的规范文件。在我们的例子中,我们有一个简单的sbt文件用于构建巧克力豆代码(示例2-3)。

Example 2-3. sbt build file
// Name of the package
name := "main/scala/chapter2"
// Version of our package
version := "1.0"
// Version of Scala
scalaVersion := "2.12.10"
// Spark library dependencies
libraryDependencies ++= Seq(
"org.apache.spark" %% "spark-core" % "3.0.0-preview2",
"org.apache.spark" %% "spark-sql" % "3.0.0-preview2"
)

假设你已经安装了Java开发工具包(JDK)和sbt,并设置了JAVA_HOME和SPARK_HOME,仅使用一个命令就可以构建Spark应用程序:

$ sbt clean package
[info] Updated file /Users/julesdamji/gits/LearningSparkV2/chapter2/scala/
project/build.properties: set sbt.version to 1.2.8
[info] Loading project definition from /Users/julesdamji/gits/LearningSparkV2/
chapter2/scala/project
[info] Updating
[info] Done updating.
...
[info] Compiling 1 Scala source to /Users/julesdamji/gits/LearningSparkV2/
chapter2/scala/target/scala-2.12/classes ...
[info] Done compiling.
[info] Packaging /Users/julesdamji/gits/LearningSparkV2/chapter2/scala/target/
scala-2.12/main-scala-chapter2_2.12-1.0.jar ...
[info] Done packaging.
[success] Total time: 6 s, completed Jan 11, 2020, 4:11:02 PM

成功构建后,可以运行巧克力豆计数实例的Scala版本,如下示例:

$SPARK_HOME/bin/spark-submit --class main.scala.chapter2.MnMcount \
jars/main-scala-chapter2_2.12-1.0.jar data/mnm_dataset.csv
...
...
20/01/11 16:00:48 INFO TaskSchedulerImpl: Killing all running tasks in stage 4:
Stage finished
20/01/11 16:00:48 INFO DAGScheduler: Job 4 finished: show at MnMcount.scala:49,
took 0.264579 s
+-----+------+-----+
|State| Color|Total|
+-----+------+-----+
| CA|Yellow| 1807|
| CA| Green| 1723|
| CA| Brown| 1718|
| CA|Orange| 1657|
Your First Standalone Application | 41| CA| Red| 1656|
| CA| Blue| 1603|
+-----+------+-----+

输出的结果与Python程序时相同。可以动手试试!

正如上面你所看到的——我们的数据科学家作者会非常乐意使用这些数据来决定为她教的不同洲际的学生提供什么颜色的巧克力豆饼干。

7.  总结

在本章中,我们介绍了你开始使用Apache Spark时需要采用的三个简单步骤:下载安装包,熟悉Scala或PySpark交互式shell,以及掌握高级Spark应用程序的概念和术语。我们简要概述了使用转换(transformation)和操作(action)编写Spark应用程序的过程,并简要介绍了使用SparkUI来检查创建的作业、阶段和任务。

最后,通过一个简短的示例,我们向你展示如何使用高级结构化API来告诉Spark该做什么,这将在下一章做进一步介绍,我们将更详细地检查这些API。