文章目录
Spark编程基础-搭配Jupyter
上节我们说道了Spark的基础知识和原理,这一节我们具体说一下Spark的编程基础
1.1 RDD编程
1.1.1 RDD创建
RDD的两种创建方式:
- 读取一个外部数据。比如:
* 本地加载文本文件;
* 从HDFS文件系统加载数据集
* SequenceFile文件(Hadoop提供的,由二进制序列化过的键值的字节流组成的文本存储文件)
* 其他符合Hadoop InputFormat格式的文件 - 调用SparkContext的parallelize方法,在Driver中已经存在的集合上创建
1.1.2 文件系统中加在数据集
在jupyter中,可以使用以下代码读取本地文件:
from pyspark import SparkContext
import os
os.environ['JAVA_HOME'] # 此处查看自己的java_home 路径是否配置正确
sc = SparkContext( 'local', 'test')
textFile = sc.textFile("./word.txt") # 也可以使用绝对路径
# sc.textFile("file:///你的路径/word.txt")
textFile.first()
textFile.saveAsTextFile('./writeback') # 保存你的文件 练习
logData = sc.textFile(logFile, 2).cache()
numAs = logData.filter(lambda line: 'a' in line).count()
numBs = logData.filter(lambda line: 'b' in line).count()
print('Lines with a: %s, Lines with b: %s' % (numAs, numBs))
- 接下来我们看下
sc.textFile
详细讲解 - 在你代码里面输入
help(sc.textFile)
Help on method textFile in module pyspark.context: textFile(name, minPartitions=None, use_unicode=True) method of pyspark.context.SparkContext instance Read a text file from HDFS, a local file system (available on all nodes), or any Hadoop-supported file system URI, and return it as an RDD of Strings. If use_unicode is False, the strings will be kept as `str` (encoding as `utf-8`), which is faster and smaller than unicode. (Added in Spark 1.2) >>> path = os.path.join(tempdir, "sample-text.txt") >>> with open(path, "w") as testFile: ... _ = testFile.write("Hello world!") >>> textFile = sc.textFile(path) >>> textFile.collect() ['Hello world!']
- 上面这段代码是对 sc.textFile 的解释,这里我们看到use_unicode 这个参数假设是False的时候 可以让我更快更小的运行,而且是UTF-8的格式,而unicode和string的区别这个熟悉python的应该都很清楚了,我们可以根据自己需求进行定制
在使用spark读取文件时,需要说明几个点:
(1)如果使用了本地文件系统的路径,那么,必须要保证在所有的worker节点上,也都能够采用相同的路径访问到该文件,比如,可以把该文件拷贝到每个worker节点上,或者也可以使用网络挂载共享文件系统。
(2)textFile()方法的输入参数,可以是文件名,也可以是目录,也可以是压缩文件等。比如,textFile(“/my/directory”), textFile(“/my/directory/.txt”), and textFile(“/my/directory/.gz”).
(3)textFile()方法也可以接受第2个输入参数(可选),用来指定分区的数目。默认情况下,Spark会为HDFS的每个block创建一个分区(HDFS中每个block默认是128MB)。你也可以提供一个比block数量更大的值作为分区数目,但是,你不能提供一个小于block数量的值作为分区数目。
1.1.3 通过并行集合创建RDD
直接上例子:
ary = [1, 2, 3, 4, 5]
s_ary = sc.parallelize(ary)
s_ary.variance
输出如下:
<bound method RDD.variance of ParallelCollectionRDD[6] at parallelize at PythonRDD.scala:195>
从执行结果来看,生成了一个collect的RDD
1.1.4 RDD操作
RDD被创建好以后,在后续使用过程中一般会发生两种操作:
- 转换(Transformation): 基于现有的数据集创建一个新的数据集。
- 行动(Action):在数据集上进行运算,返回计算值。
1.1.4.1 转换操作
- 下面列出一些常见的转换操作(Transformation API):
- filter(func):筛选出满足函数func的元素,并返回一个新的数据集
- map(func):将每个元素传递到函数func中,并将结果返回为一个新的数据集
- flatMap(func):与map()相似,但每个输入元素都可以映射到0或多个输出结果
- groupByKey():应用于(K,V)键值对的数据集时,返回一个新的(K, Iterable)形式的数据集
- reduceByKey(func):应用于(K,V)键值对的数据集时,返回一个新的(K, V)形式的数据集,其中的每个值是将每个key传递到函数func中进行聚合
1.1.4.2 行动操作
行动操作是真正触发计算的地方。Spark程序执行到行动操作时,才会执行真正的计算,从文件中加载数据,完成一次又一次转换操作,最终,完成行动操作得到结果。
下面列出一些常见的行动操作(Action API):
- count() 返回数据集中的元素个数
- collect() 以数组的形式返回数据集中的所有元素
- first() 返回数据集中的第一个元素
- take(n) 以数组的形式返回数据集中的前n个元素
- reduce(func) 通过函数func(输入两个参数并返回一个值)聚合数据集中的元素
- foreach(func) 将数据集中的每个元素传递到函数func中运行*
1.2 键值对RDD
- 从文件中加载
在jupyter对应路径新建一个txt文件,此处使用map()函数实现
lines = sc.textFile("file:///usr/local/spark/mycode/pairrdd/word.txt")
output: org.apache.spark.rdd.RDD[String] = file:///usr/local/spark/mycode/pairrdd/word.txt MapPartitionsRDD[1] at textFile at <console>:27
pairRDD = lines.flatMap(line => line.split(" ")).map(word => (word,1))
pairRDD: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[3] at map at <console>:29
pairRDD.foreach(println)
(i,1)
(love,1)
(hadoop,1)
(i,1)
(love,1)
(Spark,1)
(Spark,1)
(is,1)
(fast,1)
(than,1)
(hadoop,1)
我们之前在“第一个Spark应用程序:WordCount”章节已经详细解释过类似代码,所以,上面代码不再做细节分析。从代码执行返回信息:pairRDD: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[3] at map at :29,可以看出,返回的结果是键值对类型的RDD,即RDD[(String, Int)]。从pairRDD.foreach(println)执行的打印输出结果也可以看到,都是由(单词,1)这种形式的键值对。
- 通过并行集合创建RDD
scala> val list = List("Hadoop","Spark","Hive","Spark")
list: List[String] = List(Hadoop, Spark, Hive, Spark)
scala> val rdd = sc.parallelize(list)
rdd: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[11] at parallelize at <console>:29
scala> val pairRDD = rdd.map(word => (word,1))
pairRDD: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[12] at map at <console>:31
scala> pairRDD.foreach(println)
(Hadoop,1)
(Spark,1)
(Hive,1)
(Spark,1)
1.3 共享变量(分布式)
在默认情况下,当Spark在集群的多个不同节点的多个任务上并行运行一个函数时,它会把函数中涉及到的每个变量,在每个任务上都生成一个副本。但是,有时候,需要在多个任务之间共享变量,或者在任务(Task)和任务控制节点(Driver Program)之间共享变量。为了满足这种需求,Spark提供了两种类型的变量:广播变量(broadcast variables)和累加器(accumulators)。广播变量用来把变量在所有节点的内存之间进行共享。累加器则支持在所有不同节点之间进行累加计算(比如计数或者求和)。
- 广播变量
- 广播变量(broadcast variables)允许程序开发人员在每个机器上缓存一个只读的变量,而不是为机器上的每个任务都生成一个副本。通过这种方式,就可以非常高效地给每个节点(机器)提供一个大的输入数据集的副本。Spark的“动作”操作会跨越多个阶段(stage),对于每个阶段内的所有任务所需要的公共数据,Spark都会自动进行广播。通过广播方式进行传播的变量,会经过序列化,然后在被任务使用时再进行反序列化。这就意味着,显式地创建广播变量只有在下面的情形中是有用的:当跨越多个阶段的那些任务需要相同的数据,或者当以反序列化方式对数据进行缓存是非常重要的。
- 累加器
- 累加器是仅仅被相关操作累加的变量,通常可以被用来实现计数器(counter)和求和(sum)。Spark原生地支持数值型(numeric)的累加器,程序开发人员可以编写对新类型的支持。如果创建累加器时指定了名字,则可以在Spark UI界面看到,这有利于理解每个执行阶段的进程。
一个数值型的累加器,可以通过调用SparkContext.longAccumulator()或者SparkContext.doubleAccumulator()来创建。运行在集群中的任务,就可以使用add方法来把数值累加到累加器上,但是,这些任务只能做累加操作,不能读取累加器的值,只有任务控制节点(Driver Program)可以使用value方法来读取累加器的值。
1.4 数据读写
1.4.1 文件数据读写
这里只讲解本地文件读写,不同格式文件读写基本一致,只是表现形式上有所不同,有需要的同学参考:文件读写参考
- 在我们已经搭建好的jupyter和spark环境下,新建一个 ****.ipynb文件,新建一个word.txt,随便输入一些内容
- 先上代码
from pyspark import SparkContext import os sc = SparkContext( 'local', 'test') # help(SparkContext) textFile = sc.textFile("./word.txt") textFile.first() textFile.saveAsTextFile('./writeback') # 保存新文件到writeback路径 # logData = sc.textFile(logFile, 2).cache() # numAs = logData.filter(lambda line: 'a' in line).count() # numBs = logData.filter(lambda line: 'b' in line).count() # print('Lines with a: %s, Lines with b: %s' % (numAs, numBs))
- 之后在目录中 输入
cd /writeback ls
- 执行结果如下:
part-00000 _SUCCESS
cat part-00000
查看文件内容可以看出和word.txt中的数据是一模一样