- Apache Spark:
- 分布式计算框架:简化 运行于 计算机集群上 的并行程序编写
- 对资源调度,
- 任务提交、执行和跟踪,
- 节点间通信
- 及数据并行处理的内在底层操作都抽象
- 提供更高级别的API来处理分布式数据
- 从这方面说,它与Apache Hadoop等分布式处理框架类似。
- 但底层架构上,与它们不同
- 起源UCB
- 当时关注分布式机器学习算法的应用
- Spark一开始便为应对迭代式应用的高性能需求设计
- 在这类应用中,相同数据被多次访
- 该设计主要靠
- 数据集内存缓存
- 启动任务时低延迟
- 低系统开销来
- 实现高性能
- 容错性、
- 灵活的分布式数据结构
- 强大函数式编程接口
- Spark在各类基于机器学习和
- 迭代分析的大规模数据处理任务上有应用
- 支持四种
- 本地单机:所有Spark进程都运行在同一个JVM
- 集群单机:使用Spark自己内置的任务调度框架
- 基于 Mesos: Mesos: 开源集群计算框架
- 基于YARN:即Hadoop2,它是一个与 Hadoop关联的集群计算和资源调度框架
- 本章包括
- 下载Spark二进制版本
- 搭建本地单机模式
- 示例都在该环境下运行
- 通过Spark的交互式终端
- 来了解它的编程模型
- 及其API
- Scala、Java和 Python编写第一个Spark程序
- 在Amazon的EC2
- 架设一个Spark集群
- 可应对数据量更大、计算更复杂的任务
1.1 Spark本地安装与配置
- Spark能通过内置的单机集群调度器来在本地运行。
- 此时,所有的Spark进程
- 运行在同一个
- Java虚拟机中
- 这实际上构造了
- 一个独立
- 多线程版本的Spark环境
- 本地模式适合
- 程序原型设计、
- 开发、调试及测试
- 也适应于在单机上
- 多核并行计算
- Spark本地与集群兼容,
- 本地程序
- 仅需增加少许设置便能在集群
- 下载(本书时为1.2.0)
- 版本包及源代码Github地址
- Spark项目的下载页面找到:http://spark.apache.org/downloads.html
- Spark在线文档:http://spark.apache.org/docs/latest
- 为访问HDFS及标准或定制的Hadoop输入源
- Spark的编译需要与Hadoop版本对应
- 上述下载页面提供了针对
- Hadoop1
- CDH4( Cloudera的 Hadoop发行版)
- MapR的 Hadoop发行版和
- Hadoop2(YARN)的预编译二进制包
- 建议你通过如下链接从Apache镜像下载
- Spark运行依赖Scala(本书写作时为2.10.4版)
- 预编译的二进制包中已包含Scala运行环境
- JRE(Java运行时环境)或JDK(Java开发套件)是要安装的
- (安装指南见本书代码包中的软硬件列表)
-
tar xfvz spark-1.2.0-bin-hadoop2.4.tgz
-
cd spark-1.2.0-bin-hadoop2.4
-
Spark的脚本在bin目录
-
运行Spark附带示例来测试是否正常
-
./bin/run-example org.apache.spark.examples.SparkPi
-
本地单机模式下执行Sparkpi这示例
-
所有Spark进程均运行于同一个JVM中
- 并行处理通过多线程
-
默认该例启用
- 与本地的CPU核心数的线程
-
运行完
- 看到如下

- 如只用两线程时,
- 输如下
-
MASTER=local[2] ./bin/run-example org.apache.spark.examples.SparkPi
1.2 Spark集群
- Spark集群由两类程序构成:一个驱动和多个执行
- 本地模式时所有的处理都运行在同一JVM内,
- 集群模式时它们通常运行在不同节点。
- 单机模式的Spark集群(即用Spark内置的集群管理模块)包括
- 一个运行 Spark单机主进程和驱动程序的主节点;
- 各自运行一个执行程序进程的多个工作节点。
- 本书用 Spark的本地单机模式讲解,所用代码也可运行在Spark集群
- 如在一个Spark单机集群上运行上述示例,只需传入主节点的URL即可
-
MASTER=spark://IP:PORT ./bin/run-example org.apache.spark.examples.Sparkpi
- 告诉Sparki让示例程序运行在主节点所对应的集群上
- Spark集群管理和部署的不在本书
- 但本章后面对 Amazon EC2集群的设置和使用简要说明。

1.3 Spark编程模型
- 先介绍SparkContext对象和Spark shell

1.3.1 SparkContext类与SparkConf类
- Spark程序的编写都从
- SparkContext(Java为JavaSparkContext)
- 开始。
- SparkContext的初始化要一个SparkConf对象,
- 后者包含Spark集群配置的参数
- (如主节点的URL)
- 初始化后,便可用SparkContext的各种方法来
- 创建和操作分布式数据集
- 和共享变量
- Spark shell(在 Scala和 Python下可以,但不Java)
- 能自动完成上述初始化
- 用Scala来实现的话,如下:
val conf =new SparkConf ()
.setAppName("Test Spark App")
.setMaster("local[4]")
val sc =new SparkContext(conf)
- 代码创建4线程的SparkContext对象,任务名为 Test Spark APP。
- 也可如下
val sc =new SparkContext("local[4]","Test Spark App")

1.3.2 Spark shell
- Spark支持Scala或Python REPL(Read-Eval-Print-loop,交互式 shell)。
- 输入代码被立即计算,给出实时反馈
- Scala shell里
- 命令执行结果的值与类型在代码执行完后也显示
- 想通过Scala来用Spark shell,从Spark的主目录执行./bin/spark-she11
- 它启动Scala shell并初始化一个SparkContext
- 通过sc这个Scala值来调用这个对象
- 该命令的终端输出应该如下图所示
![在这里插入图片描述]()
- 想在Python shell中用Spark,运行./bin/ pysparkt即可
- Python下的SparkContext对象可通过变量sc来调用
- 上述命令的终端输出应该如下图所示

1.3.3 弹性分布式数据集
- Resilient Distributed Dataset是Spark的核概
- 一个RDD代表一系列的“记录”(严格来说,某种类型的对象)
- 记录被分配或分区到集群的多个节点上(本地模式理解为单进程的多线程上)
- Spark中的RDD具备容错性,当某节点或任务失败时(非代码错误的而引起,如硬件故障、网络不通等)
- RDD会在余下的节点自动重建,以便任务完成
1.创RDD
- 可从现有集合创建。
- Scala shell中:
val collection=List("a","b","c","d","e")
val rddFromCollection=sc.parallelize(collection)
- RDD也可基于Hadoop的输入源创建,如本地文件系统、HDFS和Amazon S3
- 基于Hadoop的RDD可使用任何实现Hadoop InputFormat接口的输入格式
- 包括文本文件
- 其他Hadoop标准格式
- HBase
- Cassandra等
- 举例:如何用本地文件系统里的文件创RDD
val rddFromTextFile=sc.textFile("LICENSE")
- textFile返回一个RDD对象
- 该对象的每一条记录是
- 文本文件中
- 某一行文字的String对象
2 Spark操作
- 创RDD后,便有 可供操作的分布式记录集
- Spark下操作分:转换(transformation)和执行(action)
- 转换是对一个数据集里的所有记录执行某种函数,从而使记录改变;
- 执行通常是运行某些计算或聚合操作,并将结果返回运行 Sparkcontext的那个驱动程序
- Spark常用函数式风格
- Spark API易上手,无函数式编程经验的人也不担心
- Spark最常用的转换:map
- 对一个RDD里的每条记录都执行某函数,将输入映射成为新的输出
- 对一个从本地文本文件创建的RDD(由若干String构成的RDD对象)
- 每条记录都执行size
- map将每个字符串都转换为整数,返回个由若干Int构成的RDD
-
val intsFromStringsRDD=rddFromTextFile.map(line=>line.size)
- 输出如下,提示了RDD的类型:

- =>是Scala下:匿名函数的语法
- 匿名函数指:没指定函数名的函数(Scala或Python中用def关键字定义的函数)

- 常见的执行操作count,返回记录数目
-
intsFromStringsRDD.count
- 执行结果

- 计算文本文件里每行字符串的平均长度
- 先用sum函数来对所有记录的长度求和
- 再除以总的记录数
-
val sumOfRecords = intsFromStringsRDD.sum
-
val numRecords = intsFromStringsRDD.count
-
val aveLengthOfRecord = sumOfRecords/ numRecords
- 结果
-
aveLengthOfRecord: Double =52.06030150753769
- Spark的大多数操作都返回一个新RDD
- 但多数的执行操作则返回计算的结果(上例中, count返Long,sum返Double)。
- 意味:多个操作可自然地前后连接,从而代码简洁
- 下面的代码可得到和上例相同的结果
-
val aveLengthOfRecordChained=rddFromTextFile.map(line=> linesize).sum / rddfromtextfile.count
- Spark中的转换操作是延后的
- 在RDD上调用一个转换操作并不立即触发相应计算
- 这些转换操作会链接起来,只在有执行操作被调用时才被高效计算
- 大部分操作可在集群上并行执行,只有必要时才计算结果并将其返回给驱动程序,从而提高Spark效率
- 若Spark程序从未调用一个执行操作
- 就不触发实际计算,也不得到任何结果
- 下面代码只返回一个表示一系列转换操作的新RDD
-
val transformedRDD=rddFromTextFile.map(line => line.size).filter(size => size>10).map(size=> size * 2)
- 输出如下

- 这里实际没触发任何计算,也无结果返回
- 若在新的RDD上调用一个执行操作,如sum,该计算将被触发
-
val computation =transformedRDD.sum
- 现在可看到一个Spark任务被启动,终端输出如下
![在这里插入图片描述]()

3 RDD缓存策略
- Spark功能:能把数据缓存在集群的内存里
- 调用RDD的cache
-
rddFromTextFile.cache
- 调用RDD的cache会告诉Spark将这个RDD缓存在内存中
- RDD首次调用一个执行时,此操作对应的计算立即执行,数据从数据源里读出并保存到内存。
- 因此,首次调用cache所需要的时间会部分取决于 Spark从输入源读取数据所需要的时间。
- 但下一次访问该数据集时,数据直接从内存中读出从而减少I/O
- 这会取得数倍的速度。
- 若在已缓存了的RDD上调用count或sum,可感觉到RDD的确已经载入到了内存中
-
val avelengthofrecordchained=rddfromtextfilemap(line=> line size).sum /rddFromTextFile.count
- 实际上,从下方的输出可看到,数据在第一次调用cache时便已缓存到内存,并占用约62KB,余下270MB可用:

-
再次求平均长度:
-
val aveLengthOfRecordChainedFromCached =rddFromTextFile.map(line=> line.size).sum / rddfromrextfile.count
-
从如下的输出中应该可以看出缓存的数据是从内存直接读出的:
![在这里插入图片描述]()

1.3.4 广播变量和累加器
-
Sparkl能创俩特殊变量:广播变量和累加器
-
广播变量为只读,由运行Sparkcontext的驱动程序创建后发送给
- 会参与计算的节点
-
对那些需要让各工作节点高效地访问相同数据的应用场景,如机器学习,这很有用。
-
创建广播变量
-
val broadcastAlist =sc.broadcast(List("a","b","c","d","e"))
-
-
终端的输出表明,广播変量存储在内存中,占用的空间大概是48字节,余下270MB可用

广播变量也可被非驱动程序所在的节点(即工作节点)访问,访问的方法是调用该变量的value方法
-
sc.parallelize(List(1", "2", "3")).map(x => broadcastAList.value ++ x).collect
- 这段代码会从{"1","2","3"}这个集合(一个 Scala List)里,新建一个带有三条记录的RDD。
- map函数里的代码会返回一个新的List对象。
- 这个对象里的记录由之前创建的那个broadcastAList里的记录与新建的RDD里的三条记录分別拼接而成。
上述代码用了collect
- 是Spark执行函数,它将整个RDD以Scala(Python或Java)集合的形式返回驱动程序。
- 通常只在需将结果返回到驱动程序所在节点以供本地处理时,才调用它

- 新生成的RDD含3条记录,
- 每条包含
- 一个由原来被广播的List変量
- 附加一个新的元素所构成的新记录
- (新记录分别以1、2、3结尾)。
![在这里插入图片描述]()
累加器也是一种被广播到工作节点的变量。
- 累加器与广播变量不同:后者只读而前者可累加。
- 但支持的累加操作有一定的限制。
- 这种累加必须是种有关联的操作,
- 即它得能保证在全局范围内累加起来的值能被正确地并行计算以及返回驱动程序。
- 每个工作节点只能访问和操作本地的累加器,
- 全局累加器只允许驱动程序访问。
- 累加器通过valuel访问。

1.4 Spark Scale编程入门
- 简单的Spark数据处理程序。
- 依次用Scala、 Java和 Python
- 数据存在名为UserPurchaseHistory.csv
- 每行对应一条购买记录,客户名称、商品名及价格
![在这里插入图片描述]()
- 对Scala,要创俩文件:Scala代码文件及项目的构建配置文件。
- 项目用SBT(Scala Build Tool)来构建
- 建议下载示例scala-spark-app。
- data目录下包含CSV
- 运行示例项目要系统中
- 安装好SBT(写书时用的版本0.13.1)

- SBT配置文件build.sbt,内容如下(的空行是必需)

- 最后一行:添加Spark到本项目的依赖
- Scala程序在Scalaapp.scala文件。
- 接下来逐一讲解代码的各部分。
- 先导入所需的Spak类
![在这里插入图片描述]()
object ScalaApp

主函数里,
- 初始化所需的Sparkcontext
- textfile访问CSV
- 对每行字符串以逗号为分隔符分割,提取用户名、产品和价格,
![在这里插入图片描述]()
RDD每条记录由三个字段
-
算如下指标:
- 购买总次数、客户总个数、总收入
-
如下:
![在这里插入图片描述]()
-
最后那段计算最畅销产品的代码演示了Map/Reduce模式的计算,
- 该模式随Hadoop而流行
- 先将(user, product, price)映射为( product,1)
- 然后执行reduceByKey,对各个产品的1值求和
-
转换后的RDD含各个商品的购买次数
- 有了这个RDD后,调用collect,将其计算结果以Scala集合的形式返回驱动程序
- 之后驱动程序的本地对记录按照买次排序
- 实际大数据常用sortbykey对并排
最后可在终端上打印出

可在项目的主目录下执行sbt run来运行此程序。
- 如果你使用IDE,也可从 Scala IDE直接运行
- 输出与下面相似

- 4客户的5交易,总收入39.91。
- 最畅销iPhone Cover,共买2次
1.5 Spark Java编程入门
- Java API与Scala API本质相似。
- Scala可调Java,但某些scala却无法在Java调用,
- 特别是那些用隐式类型转换、默认参数
- 和采用了某些Scala反射机制的代码
- 这些特性在Scala中会被广泛使用。
- 就有必要另外为那些常见的类编写相应的Java版本
- SparkContext对应的Java版本JavaSparkContext,
- RDD对应JavaRDD。
- 1.8及之前的Java不支持匿名函数,
- 在函数式编程上无严格的语法规范。
- 于是,套用到 Spark的 Java API上的函数必须要实现一个带有call函数的 WrappedFunction接口。
- 这使代码冗长,所以常创建临时类来传递给Spark操作。
- 这些类会实现操作所需的接口以及call函数,以取得和用 Scala编写时相同效果。
- Spark提供对Java8匿名函数( lambda)语法的支持。
- 使用该语法能让Java8书写的代码看上去很像等效的 Scala版。
- 用Scala编写时,键/值对记录的RDD能支持(如reduceByKey和saveAsSequenceFile)。
- 这些操作可通过隐式类型转换而自动被调用。
- 用Java编写时,则需特别类型的JavaRDD来支持这些操作。
- 包括用于键值对的JavaPairRDD,
- 及用于数值记录的 JavaDoubleRDD

- 后面的Java程序中,可看到大部分差异。
- 示例代码包含在本章示例代码的java-spark-app目录
- data子目录含CSV
- 用Maven构建工具来编译和运行此项目。
- 设读者已在其系统上安装该工具。

- 项目包含名为JavaApp.java的Java源文件

- 如Scala一样,先初始化一个上下文对象
- 用JavaSparkContext类而不是Sparkcontext。
- 类似地、调用 Javasparkcontext对象,用textfi1e函数来访问数据,将各行输入分割成多个字段。
- 下面代码的高亮部分是如何使用匿名类来定义一个分割函数的。
- 该函数确定了如何对各行字符串进行分割

- 现在可算一下用Scala时计算过的指标。
- 有两点注意,
- 一是下面 Java API中有些函数(比如 distinct和 count)实际上和在 Scala Apl中一样,
- 二是我们定义了一个匿名类并将其传给map函数。
- 置名类的定义方式可参见代码的高亮部分。

- 下面求出最畅销的产品,步骤与Scala相同。
- 多出的代码看似复杂,但它们大多与Java中创建匿名函数有关,实际功能与Scala一样:

- Java和 Scala相比虽然多了通过内部类来声明变量和函数的引用代码,
- 但两者的基本结构类似。
- 分别练习这两种版本,
- 比较计算同一个指标时表达上的异同。
- 该程序可以通过在项目主目录下执行如下命令运行
-
mvn exec: java -Dexec.mainClass="JavaApp"
- 输出和Scala版的类似,计算结果一样:

1.6 Spark Python编程入门
- Spark的Python API几乎覆盖了所有Scala API所能提供的功能,但有些特性,
- 如Spark Streaming和个别的API方法,暂不支持。
- 具体可见《Spark编程指南》的Python部分:
http://spark.apache.org/docs/latest/programming-guide.html。
- 与上两节类似,这里将编写Python版。
- 设读者系统中已安装2.6或更高版本的Python
- (多数Linux系统和Mac OS X预装Python)。
- 如下示例代码可以在本章的python-spark-app目录下找到。
- CSV文件也在该目录的data子目录中。
- 代码名为pythonapp.py里,内容如下:

- Scala和Python语法大致同。
- 匿名函数在Python中亦称lambda函数,lambda是语法表达上的关键字。
- Scala将x映射为y的匿名函数为 x => y ,
- Python是 lambda x : y
- 上面代码高亮部分,
- 定义了一个将两个输入映射为一个输出的匿名函数。
- 这两个输入的类型一般相同,这里调用的是相加函数,故写成 lambda a, b : a + b 。
- 运行该脚本的最好方法是在脚本目录下运行:
-
$SPARK_HOME/bin/spark-submit pythonapp.py
- $SPARK_HOME变量应换为Spark的主目录,
- 即本章开始Spark预编译包解压生成的那个目录。
- 输出和Scala、Java版时类似

1.7 在Amazon EC2 上运行 Spark
写到这儿了!!!!!!!!!!!!!!
1.8 小结
- 自己电脑及Amazon EC2的云端上配置Spark
- 通过Scala交互式终端,学习Spark编程模型的基础知识并了解了它的APl。
- 用Scala、Java和 Python,写Spark程序。
- 下章用Spark创建一个机器学习系统








浙公网安备 33010602011771号