Spark编程指南(一)

翻译Spark Programming Guide,Spark2.2.0. 之前有博文也翻译,Spark版本1.3.0.,本文翻译过程中,有所参考。

概述


每个含一个驱动程序组成的Spark应用,可以高效地运行用户的主程序(main function)以及在集群上执行各种并行的操作。Spark提出的抽象概念主要是一个弹性分布式数据集(resilient distributed datasets, RDDs),它是一个可以并行操作的分布于集群节点上元素集合。RDDs可通过在Hadoop文件系统(或者任何Hadoop支持的文件系统)打开文件来创建,或在驱动程序中通过转化存在的Scala集。用户可以让Spark持续保留RDD在内存中,允许数据可以被分布式并行操作高效地重新利用。另外,RDDs可因节点错误自动恢复。

另一方面,Spark一种可在并行操作中应用的共享变量。默认情况下,当Spark在不同的节点上多任务并行运行一个函数,它会把函数中所用到的各个变量拷贝传到每个子任务中。有时候,一个变量需要被跨任务、或是任务与驱动程序间共享。Spark支持两种共享变量类型:广播变量,它可以在所有节点内存中缓存;累加器(accumulators),仅可被叠加,例如计数和求和。

本指南用Spark支持的各语言分别介绍了这些特征。如果你打开Spark交互终端(spark-shell for Scala/ pyspark for Python)跟随下面教程那将是非常方便的。

连接Spark(Python case)


Spark 2.2.0 兼容Python 2.6+, Python 3.4+。它可以利用标准的CPython解释器,这样可以利用C语言库如NumPy。当然,它也兼容PyPy 2.3+.

注意,Spark2.0.0中移除了支持Python2.6,Spark2.2.0中也将可能移除。

为了用Python运行Spark应用,请用位于Spark目录下的bin/spark-submit脚本。这个脚本将会载入Spark的Java/Scala库,并允许你提交应用到集群上。你也可以用bin/pyspark打开交互式Python终端。

如果你希望访问HDFS数据,你需要为你使用的HDFS版本建立一个PySpark链接。对于常见的HDFS版本,预建包(Prebuilt packages)可在Spark主页下载。

最后,你需要导入一些Spark类到你的程序中。添加如下命令:

from pyspark import SparkContext, SparkConf

在驱动和工作组,PySpark需要相同的Python小版本(minor version)。它利用PATH中默认的python版本,你可以通过PYSPARK_PATH明确你想用的Python版本,例如:

$ PYSPARK_PYTHON=python3.4 bin/pyspark
$ PYSPARK_PYTHON=/opt/pypy-2.5/bin/pypy bin/spark-submit examples/src/main/python/pi.py

初始化Spark


一个Spark程序首先必须做的是创建一个SparkContext对象,这会告诉Spark如何进入一个集群。为了创建SparkContext首先你需要建立一个包含你的应用的信息的SparkConf对象。

conf = SparkConf().setAppName(appName).setMaster(master)
sc = SparkContext(conf=conf)

参数appName是展示在集群UI上你的应用的名字。master是一个Spark,Mesos或YARN集群的URL,亦或是运行在本地模式下的一个特殊的”local”字符串。事实上,当在一个集群上运行程序,你一般不会把master参数写死在代码中,而是通过spark-submit提交应用,然后来获得。然而,在本地测试以及单元测试,你可以在进程内(in-process)传递”local”来运行Spark。

使用Shell


在PySpark终端中,一个特殊的集成在解释器中的SparkContext已经为你创建好,变量名sc。创建你自己的SparkContext将不会起作用。你可以使用--master参数来设置环境连接哪种主机(which master),可以通过传递一个逗号隔开的列表--py-files添加Python .zip, .egg 或者.py文件到运行的路径。任何依赖的额外资源(如Sonatype)可以通过--repositories参数传递。Spark包的所有Python依赖(罗列在包的requirements.txt)必要的时候必须手动用pip安装。例如,用4核来跑bin/pyspark:

$ ./bin/pyspark --master local[4]

再或者,添加code.py到搜索路径(为了在后面能够import code):

$ ./bin/pyspark --master local[4] --py-files code.py

更多的选项列表,输入命令pyspark --help。在后台,pyspark唤起更一般的spark-submit脚本。

也可以用IPython来启动PySpark。PySpark兼容IPython1.0.0及更新版。如用IPython,设置变量PYSPARK_DRIVER_PYTHON 为ipython当运行bin/pyspark 时:

$ PYSPARK_DRIVER_PYTHON=ipython ./bin/pyspark

用Jupyter notebook的话:

$ PYSPARK_DRIVER_PYTHON=jupyter PYSPARK_DRIVER_PYTHON_OPTS=notebook ./bin/pyspark

你可以定制ipython 或 jupyter命令来设置PYSPARK_DRIVER_PYTHON_OPTS.

在Jupyter Notebook服务启用后,你可以从“Files”中创建一个新的”Python 2”笔记本。在笔记本中,在你开始用从Jupyter notebook中用Spark前,你可以输入命令%pylab inline作为你的笔记本的部分。

弹性分布式数据集(RDDs)


Spark 是以RDD概念为中心运行的,RDD是一个可并行操作的容错元素集。有两种方法来生成RDDs:在你的驱动程序中并行化一个已经存在的集合;从外部存储系统中引用一个数据集,例如一个共享的文件系统, HDFS, HBase, 或是任何支持Hadoop输入格式的数据源。

并行集合


并行集合是通过在驱动程序中一个现有的迭代器或集合上调用 SparkContext的 parallelize方法来创建。集合的元素被拷贝从而形成可并行操作的分布式数据集。例如,下面是如何创建一个存着1到5的并行数据集:

data = [1, 2, 3, 4, 5]
distData = sc.parallelize(data)

一旦创建,分布式数据集(distData)可以并行操作。例如,我们可以调用distData.reduce(lambda a, b: a + b)来叠加列表中的元素。稍后我们再描述操作分布式数据集。

一个并行集合的重要参数是将数据集分成分区的数量。Spark将会在集群的各自分区运行一个任务。例如你想在你的集群中每个CPU负责2-4个分区。通常,Spark会根据你的集群自动设置分区数目。然而,你也可以手动地传递分区数目作为第二个参数给parallelize(例如sc.parallelize(data, 10))。注意:有些代码中会使用切片(slice,分片的同义词)这个术语来保持向下兼容性。

外部数据集(External Datasets)


PySpark可以从任何Hadoop支持的存储源中创建分布式数据集,包括你的本地文件系统,HDFS,Cassandra,HBase,Amazon S3等。Spark支持文本文件(Text file),序列文件(SequenceFiles)及任何支持Hadoop输入格式文件。

文本文件RDDs可以利用SparkContext的textFile方法来创建。这种方法会
用到文件的URI(本地路径或是hdfs://,s3n://等),并读取作为行的集合。下面是一个例子:

>>> distFile = sc.textFile("data.txt")

一旦创建,distFile可以开始数据集操作。例如,我们可以利用mapreduce叠加所有行的尺寸:distFile.map(lambda s: len(s)).reduce(lambda a, b: a + b)

用Spark读取文件的几点事项:

  • 如果用本地文件的路径,要确保在worker节点上也可以通过相同路径访问。可通过将这个文件拷贝到所有worker上或者使用网络挂载的共享文件系统。

  • 所有Spark的基于文件的输入方法,包含textFile,都支持将文件夹、压缩文件、包含通配符的路径作为参数。例如,你可以用textFile("/my/directory"),textFile("/my/directory/*.txt"),textFile("/my/directory/*.gz")

  • textFile方法也接受一个可选的第二个参数来控制文件分区数。默认情况下,Spark为文件的每个块创建一个分区(在HDFS中默认块的大小为128MB),但是你也可以通过传入一个更大的值来要求Spark建立更多的分区。注意,分区的数量绝不能小于文件块的数量。

除了文本文件,Spark的Python API也提供了几种另外的数据格式:

  • SparkContext.wholeTextFiles可以读取含多个小文本文件的路径,并各自返回(文件名,目录)对。这与textFile中不一致,后者在每个文件中返回
    每行一个记录。

  • RDD.saveAsPickleFileSparkContext.pickleFile支持将RDD以串行化的Python对象格式存储起来。串行化的过程中会以默认10个一批的数量批量处理。

  • 序列文件和其他Hadoop输入输出格式。

注意这个特性目前标记为 Experimental ,为高级用户准备。这个特性在未来可能会被基于Spark SQL的read/write支持所取代,因为Spark SQL是更好的方式。

可写类型支持


PySpark序列文件支持利用Java作为中介载入一个键值对RDD,将可写类型转化成Java的基本类型,然后使用 Pyrolite将java结果对象串行化。当将一个键值对RDD储存到一个序列文件中时PySpark将会运行上述过程的相反过程。首先将Python对象反串行化成Java对象,然后转化成可写类型。以下可写类型会自动转换:

可写类型 Python类型
Text unicode str
IntWritable int
FloatWritable float
DoubleWritable float
BooleanWritable bool
BytesWritable bytearray
NullWritable None
MapWritable dict

数组是不能自动转换的。用户需要在读写时指定ArrayWritable的子类型.在读入的时候,默认的转换器会把自定义的ArrayWritable子 类型转化成Java的Object[],之后串行化成Python的元组。为了获得Python的array.array类型来使用主要类型的数组,用户 需要自行指定转换器。

保存、载入序列文件


和文本文件类似,序列文件可以通过指定路径来保存与读取。键值类型都可以自行指定,但是对于标准可写类型可以不指定。

>>> rdd = sc.parallelize(range(1, 4)).map(lambda x: (x, "a" * x ))  
>>> rdd.saveAsSequenceFile("path/to/file")  
>>> sorted(sc.sequenceFile("path/to/file").collect())  
[(1, u'a'), (2, u'aa'), (3, u'aaa')]  

保存、载入其他Hadoop 输入输出格式文件


PySpark同样支持写入和读出其他Hadoop输入输出格式,包括’新’和’旧’两种Hadoop MapReduce API。如果有必要,一个Hadoop配置可以以Python字典的形式传入。以下是一个例子,使用了Elasticsearch ESInputFormat:

$ ./bin/pyspark --jars /path/to/elasticsearch-hadoop.jar
>>> conf = {"es.resource" : "index/type"}  # assume Elasticsearch is running on localhost defaults
>>> rdd = sc.newAPIHadoopRDD("org.elasticsearch.hadoop.mr.EsInputFormat",
                             "org.apache.hadoop.io.NullWritable",
                             "org.elasticsearch.hadoop.mr.LinkedMapWritable",
                             conf=conf)
>>> rdd.first()  # the result is a MapWritable that is converted to a Python dict
(u'Elasticsearch ID',
 {u'field1': True,
  u'field2': u'Some Text',
  u'field3': 12345})

注意,如果这个读入格式仅仅依赖于一个Hadoop配置和/或输入路径,而且键值类型都可以根据前面的表格直接转换,那么刚才提到的这种方法非常合适。

如果你有一些自定义的序列化二进制数据(比如从Cassandra/HBase中读取数据),那么你需要首先在Scala/Java端将这些数据转化成可以被Pyrolite的串行化器处理的数据类型。一个转换器特质已经提供好了。简单地拓展这个特质同时在convert方法中实现你自己的转换代码即可。记住,要确保这个类以及访问你的输入格式所需的依赖都被打到了Spark作业包中,并且确保这个包已经包含到了PySpark的classpath中。

这里有一些通过自定义转换器来使用Cassandra/HBase输入输出格式的Python样例和转换器样例。

你可能感兴趣的:(python)