我们主要介绍两种共享变量类型:accumulators聚合信息,broadcast有效的分发large values。
当我们的任务涉及到了需要大量的设置时间(比如创建数据库连接或者随机数生成),我们可以把这个设置时间share到多个数据items上面。
除了Spark直接支持的语言外,我们还可以使用pipe()方法来与别的编程语言进行沟通,例如使用pipe()方法来访问R语言的库。
当我们把函数传递给Spark,例如map()的函数或者filter()的条件,它们可以在driver program之外使用变量定义,但是每个task是运行在cluster上面的,cluster会得到每个变量的副本,如果对这个变量进行了更新是不会返回到driver上面的。
Spark的共享变量,accumulate和broadcast放宽了这一限制。accumulate提供一个简单的语法从worker nodes聚合值然后返回到driver program。它最常用的一个情景就是在执行job的时候count events。
例如:我们想要得到一个list里面,num的值为2的obj个数。
data = sc.parallelize([
{"name": "panda", "num": 3}, {"name": "snail", "num": 2},
{"name": "dog", "num": 2}, {"name": "cat", "num": 4},
{"name": "zhexiao", "num": 2}
])
accumu_num = sc.accumulator(0)
normal_num = 0
def culculate_who_have_2(obj):
global accumu_num
global normal_num
if obj['num'] == 2:
accumu_num += 1
normal_num += 1
return obj
call = data.flatMap(culculate_who_have_2)
call.collect()
print(accumu_num) # 3
print(normal_num) # 0
注意:如我们预想的,accumulator的变量在Partition里面共享,所有值是3;而正常的变量不能在Partition里面共享,所以值是0。
为什么需要调用collect()这个Actions,是因为map()是lazy的Transformation方法,必须要调用一个Actions来激活它。
Worker nodes的Task是不能访问accumulate的value(),accumulate是write-only变量,这样提高了执行效率。
例如:我们现在可以使用accumulate来计算一个文件中有多少个错误数据。
# Create Accumulators for validating call signs
validSignCount = sc.accumulator(0)
invalidSignCount = sc.accumulator(0)
def validateSign(sign):
global validSignCount, invalidSignCount
if re.match(r"\A\d?[a-zA-Z]{1,2}\d{1,4}[a-zA-Z]{1,3}\Z", sign):
validSignCount += 1
return True
else:
invalidSignCount += 1
return False
# Count the number of times we contacted each call sign
validSigns = callSigns.filter(validateSign)
contactCount = validSigns.map(lambda sign: (sign, 1)).reduceByKey(lambda (x, y): x + y)
# Force evaluation so the counters are populated
contactCount.count()
if invalidSignCount.value < 0.1 * validSignCount.value:
contactCount.saveAsTextFile(outputDir + “/contactCount”)
else:
print “Too many errors: %d in %d” % (invalidSignCount.value, validSignCount.value)
Spark会自动重新处理失败或者机器比较慢的tasks。比如一个partition上面的一个map()操作在某个node运行失败,Spark会自动选择其余的node来重新运行,即使是因为这个node运行比较慢而不是crash,Spark也会自动选择新的高质量的node。
从这里我们会发现一个问题,因为accumulate如果是在Transformation执行的时候运行,那么当一个node更新了accumulate的变量,但是后面又发生了错误转移到了其他的node执行这个Transformation,就会造成accumulate变量的值不准确。
因此,比较建议是将accumulate变量放在Action里面执行,比如foreach()。
data = sc.parallelize([
{"name": "panda", "num": 3}, {"name": "snail", "num": 2},
{"name": "dog", "num": 2}, {"name": "cat", "num": 4},
{"name": "zhexiao", "num": 2}
])
accumu_num = sc.accumulator(0)
normal_num = 0
def culculate_who_have_2(obj):
global accumu_num, normal_num
if obj['num'] == 2:
accumu_num += 1
normal_num += 1
dd = data.foreach(culculate_who_have_2)
# (Accumulator, 0)
print(accumu_num, normal_num)
Broadcast Variable(broadcast变量)允许程序发送一个large,read-only的值到所有的worker nodes使用。比如你的应用需要用到表的数据,则不需要在node里面进行费时查找,直接在外面查找后发送给worker nodes。
broadcast变量可以通过调用value来得到Broadcast对象的值,这个值仅发送给each node一次,使用有效率的BitTorrent-like通讯机制。
data = sc.parallelize([
{"name": "panda", "num": 3}, {"name": "snail", "num": 2},
{"name": "dog", "num": 2}, {"name": "cat", "num": 4},
{"name": "zhexiao", "num": 2}
])
rows = ['panda', 'zhexiao']
bd_val = sc.broadcast(rows)
def process_dt(obj):
if obj['name'] in bd_val.value:
return (obj['name'], 1)
return (obj['name'], 0)
dt = data.map(process_dt)
# [('panda', 1), ('snail', 0), ('dog', 0), ('cat', 0), ('zhexiao', 1)]
print(dt.collect())
总结:
1. 在SparkContext上面调用broadcast创建对象
2. 调用value属性获得值
3. 变量会仅会发送到each node一次,记住这个变量是read-only
在broadcast比较大的值的时候,我们有必要对数据进行序列化,这样可以减少网络之间的传输瓶颈。我们可以使用spark.serializer属性(Kryo)来序列化数据。
基于某个partition上面处理数据可以使我们避免重复的给每个数据做初始化工作。比如打开数据库连接或者创建随机数等例子。我们只需要在partition打开即可,不需要每次操作数据的时候都打开数据库连接。
mapPartitions,mapPartitionsWithIndex,foreachPartition是常用的Per-partition operators。
data = sc.parallelize([2, 3, 5, 2])
def avg(c1, c2):
return (c1[0]+c2[0], c1[1]+c2[1])
d = data.map(lambda x: (x, 1)).reduce(avg)
# (12, 4)
print(d)
data = sc.parallelize([2, 3, 5, 2])
def par_func(nums):
sumCount = [0, 0]
for n in nums:
sumCount[0] += n
sumCount[1] += 1
return [sumCount]
d = data.mapPartitions(par_func)
# [[5, 2], [7, 2]]
print(d.collect())
注:[[5, 2], [7, 2]]代表什么呢?代表我们是2个partition在做操作。第一个partition的值是[5, 2],第二个partition计算后的值是[7, 2]
Spark提供了几个专门用来做统计操作的operations。
data = sc.parallelize([2, 3, 5, 2])
# (count: 4, mean: 3.0, stdev: 1.22474487139, max: 5, min: 2)
print(data.stats())
# 4
print(data.count())
# 5
print(data.max())