累加器
共享变量,将工作节点的值聚合到驱动器程序中。
val sc = new SparkContext(...)
val file = sc.textFile("file.txt")
val blanklines = sc.accmulator(0) //创建Accumulator[Int]并初始化为0
val callSigns = flat.flatMap(line => {
if(line==""){
blanklines += 1
}
line.split(" ")
})
callSigns.saveAsTextFile("output.txt")
println("Blank lines:" + blanklines.value)
注意:只有运行saveAsTextFile()行动才能看到正确的计数,因为flatMap()是惰性的。
总结以下几点:
- 1.通过驱动器
SparkContext.accumulator(initiaValue)
创建有初始值的累加器 - 2.使用累加器的 +=方法
- 3.调用value属性取值
- 工作节点的任务不能访问累加器的值,其相对于工作节点是一个只写变量
由于Spark会自动重新执行失败的或较慢的任务来应对错误的或比较慢的机器,可能导致同一个函数对同一个数据执行了多次,累加器该如何处理呢?对于action操作使用的累加器,Spark只会把每个任务对各累加器的修改应用一次,我们需要把它放到foreach()这样的action操作。
对于RDD转化操作的累加器,上述情况则不能保证。因而在转化操作中,累加器通常只用于调试目的。
自定义累加器,需要满足交换律和结合律。
广播变量
通过呼号的前缀来查询对应的国家。
signPrefixes = loadCallSignTable()
def processSignCount(sign_count,signPrefixes):
country = lookupCountry(sign_count[0],signPrefixes)
count = sign_count[1]
return (country,count)
countryContactCounts = (contactCounts.map(processSignCount).reduceByKey((lambda x,y:x+y)))
上述代码是可以运行的,但是有两个问题:一是signPrefixes可能会很大,从主节点为每个任务发送一个这样的数组代价很大。二是如果之后还要再次使用signPrefixes还需要向每个节点再发送一遍。
我们可以通过广播变量来解决这样的问题,广播变量使用的是一种高效的类似BitTorrentde的通信机制。
val signPrefixes = sc.broadcast(loadCallSignTable())
val coutryContaceCounts = contactCounts.map{case (sign,count) =>
val country = lookupInArray(sign,signPrefixes.value)
(country,count)
}.reduceBykey((x,y)=>x+y)
countryContactCounts.saveAsTextFile(outputDir + "/countries.txt")
使用广播变量:
- 1.对一个类型T的对象调用SparkContext.broadcast创建一个Broadcast对象
- 2.通过value属性访问对象的值
- 3.变量只发送到各个节点一次,作为只读值处理
基于分区操作
通过基于分区的操作,每个分区内共享一个数据库连接池,避免太多连接,还可以重用JSON解析器。使用mapPartitions函数获得输入RDD的每个分区中的元素迭代器。
val contactsContactLists = validSigns.distinct().mapPartitions{
signs =>
val mapper = createMapper()
val client = new HttpClient()
client.start()
signs.map{sign => createExchangeForsign(sign)
}.map{ case(sign,exchange) =>
(sign,readExchangeCalllog(mapper,excahnge))
}.filter(x => x._2!=null)
}
当基于分区操作RDD时,Spark会为函数提供该分区中的元素的迭代器。此外还有mapPartitionWithInedex()
和foreachPartitions()
。
除了上述避免重复的配置,还可以使用mapPartitions()
避免创建对象的开销,之前我们计算平均值一种方法是将数值RDD转化为二元组,在归约中用于计算整个元素的个数,而实际上我们也可以只为每个分区创建一个二元组,而不需为每个元素执行创建。
以下是通过mapPartitions()
来节省创建对象的开销(python)
def combineCtrs(c1,c2):
return (c1[0]+c2[0],c1[1]+c2[1])
def partitionCtr(nums):
sumCount = [0,0]
for num in nums:
sumCount[0] += num
sumCount[1] += 1
return [sumCount]
def fastAvg(nums):
sumCount = nums.mapPartitions(partitionCtr).reduce(combineCtrs)
与外部程序的管道
Spark支持通过管道将数据传给其他语言程序,这里暂时略。
RDD的操作
Spark的数值操作通过流式算法实现的,允许每次一个元素的方式构建出模型。这些统计数据会在调用stats()
时一次遍历数据计算出来,并以StatsCounter对象返回。
下面是一个移除异常值的例子
val distanceDouble = distance.map(string => string.toDouble)
val stats = distanceDoubles.stats()
val stddev = stats.stdev
val mean = stats.mean
val reasonableDistances = distanceDouble.filter(x => math.abs(x-mean)<3*stddev)
println(reasonableDistance.collect().tolist)
以上是进阶的第一部分,下一部分学习集群上运行Spark