共享变量
共享变量有两种:累加器和广播变量
累加器用来对信息进行聚合,广播变量用来高效分发较大的对象
累加器
累加器提供了将工作节点中的值聚合到驱动器程序中的简单方法。累加器一个常见的用途就是在调试时对作业执行过程中的事件进行计数。
例如:我们需要读取文件中的日志信息,并且记录输入有多少空行
val files = sc.textFile(file.txt)
val blankLines = sc.accumulator(0)//创建accumulator[0]并初始化为0
val callSigns = files.flatMap(line => {
if(line == ""){
blankLines +=1
}
line.split()
})
scala> callSigns.saveAsTextFile("out.txt")
scala> println("Blank lines:"+ blankLines.value)
Blank lines:4
注意flatMap
是惰性操作,只有当执行saveAsTextFile
时行动操作后才会看到正确的计数
总结累加器的用法如下:
- 通过在驱动器中调用
SparkContext.accumulator[initialvalue]
方法,创建出有初识值的累加器。返回值为org.apache.spark.Accumulator[T]
对象,其中T为初始值initialvalue
的类型 - spark闭包里的执行器代码可以使用累加器的
+=
方法来增加累加器的值 - 驱动器程序可以调用累加器的
value
属性来访问累加器的值
从这些任务来看,累加器是一个只写变量。在这种模式下,累加器的实现可以更加高效,不需要为每次更新操作进行复杂的通信。
累加器与容错性
Spark会自动重新执行失败的或者较慢的任务来应对出错的或者较慢的机器。例如,如果对某分区执行map()操作的节点失败了,Spark会在另一个节点上重新运行该任务。即使该节点没有崩溃,只是运行速度慢了一些,Spark也会抢占式的在另一个节点上启动一个“投机式”的任务副本,如果该任务更多结束就可以提前获得结果。
因此最终结果就是同一个函数可能对同一个数据运行了多次。
这种情况下累加器怎么处理呢?
对于要在行动操作中使用的累加器,Spark只会把每个任务对各累加器的修改应用一次
如果想要一个在无论失败还是重复计算中都绝对可靠的累加器,我们必须把它放在foreach()这样的行动操作里。对于RDD中的转化操作,就不能保证有这种情况了。因此转化操作中的累加器最好只在调试时使用。
自定义累加器
只要操作满足交换律和结合律,就可以用任意操作来替代数值上的加操作,这需要扩展AccumulatorParam
广播变量
广播变量可以让程序高效的向所有的工作节点发送一个较大的只读值,以供一个或多个spark操作使用。
Spark会自动把闭包中所有引用到的变量发送到工作节点上。很方便也很低效。如果要对不同的数据文件执行相同的操作,每次都会将该变量从主节点发送到每一个任务。我们可以将其变为广播变量来解决这一问题。
广播变量就是类型为spark.broadcast.Broadcast[T]
的一个对象,其中存放着类型为T的值。可以再任务中通过对Broadcast
对象调用value
来获取该对象的值。这个值只会被发送到各节点一次,使用的是一种类似于BitTorrent的通信机制
//查询RDD contactCounts中的呼号的对应位置
//将呼号前缀读取为国家代码来进行查询
val signPrefixes = sc.broadcast(loadCallSignTable())
val countryContactCounts = contactCounts.map{ case (sign,count) => val country = lookuoInArray(sign,signPrefixes.value)
(country,count)
}.reduceByKey((x,y) => x+y)
countryContactCounts.saveAsTextFile(outputDir + "/countries.txt")
使用广播变量的过程
- 通过对一个类型T的对象调用SparkContext.broadcast创建出一个Broadcast[T]对象。任何可以序列化的对象都能这么做
- 通过
value
属性访问该对象的值 - 变量只会被发到各个节点一次,应作为只读值处理。(修改这个值不会影响到别的节点)
满足只读要求最容易的使用方式就是广播基本类型或引用不可变对象
广播的优化
当广播一个比较大的值的时候,选择一个既快又好的序列化格式非常重要。java和scala里都使用Java序列化库,它对除基本类型的数组以外的任何对象序列化都比较低效。
可以使用别的序列化库来优化序列的过程,比如spark.serializer