Spark 围绕RDD的概念(pyspark学习一 中已经讲了rdd的概念)展开,RDD是可并行操作的容错性元素集合。创建RDD有两种方式:并行化驱动程序中的现有集合,或在外部系统中引用数据集。
1. 获得RDD
创建rdd方式主要有两种
- 处理现有集合 Parallelizing Collections
- 读取外部数据 External Datasets
1.1 处理现有集合
使用SparkContext的parallelize方法对iterable或collection处理得到RDD。复制集合中的元素得到可并行操作的RDD
data = [1, 2, 3, 4, 5]
distData = sc.parallelize(data)
该方法还有一个参数是将集合划分的分区数。集群中每个分区partition运行一个任务task。1.2 读取外部数据
distFile = sc.textFile(path)
path为文件路径
2. rdd操作
RDD支持两种操作:转换操作transformations,从存在的RDD中创建一个新的RDD;行动操作Actions,在运行完数据集上的计算,返回给驱动程序一个值。
Spark 所有的转换操作都是懒操作lazy,并不会立刻计算他们的结果,而是仅仅几下操作的对象。转换操作只有在行动操作需要return一个结果时才会被执行(rdd只有遇到action时,前面的transformation才会执行)。Why?这让spark更加高效。
默认的,你的每次action操作,transformed RDD都会被重新计算。也可以在内存中持久化RDD,使用persist或cache方法。
2.1 常见操作
常见的转换操作有 map,flatMap,filter等
常见的行动操作有 reduce,collection,count,foreach等, 详情后面文章介绍
例子
lines = sc.textFile("data.txt")
lineLengths = lines.map(lambda s: len(s))
totalLength = lineLengths.reduce(lambda a, b: a + b)
lineLengths.persist()
如果我们想多次使用lineLengths,可以在reduce之前将其persist
2.2 传递函数
Spark 的API和依赖于传递函数来在集群中运行。三点建议方法做到这些:
- lambda表达式可以写简单函数(它不支持多语句函数或无返回值函数)
data.map(lambda word:(word,1)
- 调用Spark的函数内部的局部定义,用于更长的代码。
def myFunc(s):
words = s.split(" ")
return len(words)
sc = SparkContext(...)
sc.textFile("file.txt").map(myFunc)
- module中的顶层函数
2.3 理解spark中的闭包Closures
Spark的难点之一是理解在集群上执行代码时变量和方法的范围和生命周期。经常会出现在变量范围外修改变量的操作。
比如下面,使用使用foreach() 来增加变量counter
counter = 0
rdd = sc.parallelize(data)
# Wrong: Don't do this!!
def increment_counter(x):
global counter
counter += x
rdd.foreach(increment_counter)
print("Counter value: ", counter)
上面代码的行为是不稳定的,可能无法正常工作。为了执行作业(job),Spark将RDD操作的处理分解成任务task,每个任务由执行者excutor执行。在执行之前spark计算任务闭包closure。闭包是excutor在RDD执行计算(本例中是foreach)时必须可见的那些变量和方法。这个闭包进行序列化并发给每个excutor。发送给每个excutor的闭包中的那些变量是driver中变量的副本,因此当在foreach中引用的counter不再是driver node中的counter。Driver节点的内存中仍然有counter,但是excutor不能对其改变。Excutor只能访问闭包中的副本。因此,计数器的最终值仍然是零,因为计数器上的所有操作都引用了序列化闭包内的值。 在local模式中,某些环境中,foreach函数实际上和driver在一个jvm中执行,将引用同一个counter,并且可能实际更新他。
为了确保这种情况下明确定义,应该使用累加器。Spark中的Accumulator专门用于提供一种在集群中的工作节点之间执行拆分时安全的更新变量的机制。
一般来说,闭包-像循环或本地定义的方法这样的结构不应该被用来改变全局状态。Spark 并没有定义或保证对从闭包外引用的对象的突变行为。这样的代码可能在本地模式中正常工作但不会按照预期在分布式模式下运行。如果需要全局聚合使用累加器accumulator。
另一个常见的习惯是试图使用rdd.foreach(println)或rdd.map(println)打印RDD的元素。 在一台机器上,这将产生预期的输出并打印所有RDD的元素。 但是,在集群模式下,执行程序调用的stdout输出是excutor执行程序的stdout,而不是驱动程序的stdout,因此驱动程序上的stdout不会显示这些! 要打印驱动程序中的所有元素,可以使用collect()方法首先将RDD带到驱动程序节点:rdd.collect().foreach(println)。 但是,这可能会导致驱动程序内存不足,因为collect()会将整个RDD提取到一台计算机; 如果您只需要打印RDD的一些元素,则更安全的方法是使用
take():rdd.take(100).foreach(println)。
3. pair RDD
大多数RDD操作可用于任何对象,但是有一些特殊的只适用于pair rdd(pair RDD 指的是数据是键值对类型的rdd)。最常见的一个是分布式的shuffle操作,比如按键分组或汇总元素。
Python中,这些操作在包含内置Python元祖的RDD上工作,如(1,2)。简单的创建这样的元祖然后调用操作。
lines = sc.textFile("data.txt")
pairs = lines.map(lambda s: (s, 1))
counts = pairs.reduceByKey(lambda a, b: a + b)
先写这么多,下一张介绍rdd常用的转换操作和行动操作