Scala实践Spark(四)Spark进阶

累加器

共享变量,将工作节点的值聚合到驱动器程序中。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值