介绍

我们主要介绍两种共享变量类型:accumulators聚合信息,broadcast有效的分发large values。

当我们的任务涉及到了需要大量的设置时间(比如创建数据库连接或者随机数生成),我们可以把这个设置时间share到多个数据items上面。

除了Spark直接支持的语言外,我们还可以使用pipe()方法来与别的编程语言进行沟通,例如使用pipe()方法来访问R语言的库。

Accumulators

当我们把函数传递给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)

Accumulators and Fault Tolerance

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<id=0, value=3>, 0)
print(accumu_num, normal_num)

Broadcast

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

Optimizing Broadcasts

在broadcast比较大的值的时候,我们有必要对数据进行序列化,这样可以减少网络之间的传输瓶颈。我们可以使用spark.serializer属性(Kryo)来序列化数据。

Working on a Per-Partition Basis

基于某个partition上面处理数据可以使我们避免重复的给每个数据做初始化工作。比如打开数据库连接或者创建随机数等例子。我们只需要在partition打开即可,不需要每次操作数据的时候都打开数据库连接。

mapPartitions,mapPartitionsWithIndex,foreachPartition是常用的Per-partition operators。

spark林子雨课后答案_数据库

不使用mapPartitions做平均值

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)

使用mapPartitions做平均值

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]

数值型RDD操作

Spark提供了几个专门用来做统计操作的operations。

spark林子雨课后答案_spark林子雨课后答案_02

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())