使用广播变量时,我面临一种奇怪的行为。每次我使用广播变量时,内容都会为每个节点复制一次,并且永远不会被重复使用。
这是spark-shell中的一个例子--master local [32]: (当然,它是无用且愚蠢的代码,但确实显示了这种行为)
case class Test(a:String)
val test = Test("123")
val bc = sc.broadcast(test)
// On my 32 core machine, I get 33 copies of Test (expected)
// Yourkit profiler shows 33 instances of my object (32 are unreachable)
sc.parallelize((1 to 100)).map(x => bc.value.a).count
// Doing it again, Test copies are not reused and serialized again (now 65 copies, 64 are unreachable)
sc.parallelize((1 to 100)).map(x => bc.value.a).count
在我的例子中,我广播的变量是几百兆字节,由数百万个小对象(几个哈希映射和向量)组成。
每次我在使用RDD的RDD上运行操作时,都会浪费几千兆字节的内存,垃圾收集器会越来越成为瓶颈!
是否设计为每次执行新闭包重新广播变量,或者它是一个错误,我应该重复使用我的副本?
为什么它们在使用后立即无法接触?
本地模式中的火花壳是特别的吗?
注意:我使用spark-1.3.1-hadoop2.6
UPDATE1: 根据这篇文章:http://apache-spark-user-list.1001560.n3.nabble.com/How-to-share-a-NonSerializable-variable-among-tasks-in-the-same-worker-node-td11048.html Singleton对象在Spark 1.2.x +上不再起作用 所以这种解决方法也不起作用:
val bcModel = sc.broadcast(bigModel)
object ModelCache {
@transient lazy private val localModel = { bcModel.value }
def getModel = localModel
}
sc.parallelize((1 to 100)).map(x => ModelCache.getModel.someValue)
UPDATE2: 我也试图重用累加器模式而没有成功:
class VoidAccumulatorParams extends AccumulatorParam[BigModel] {
override def addInPlace(r1: BigModel, r2: BigModel): BigModel= { r1 }
override def zero(initialValue: BigModel): BigModel= { initialValue }
}
val acc = sc.accumulator(bigModel, "bigModel")(new VoidAccumulableParams())
sc.parallelize((1 to 100)).map(x => acc.localValue.someValue)
UPDATE3: 在使用spark-submit而不是scala shell运行作业时,看起来单例对象有效。
答案 0 :(得分:0)
看一下广播测试(BroadcastSuite.scala),看看会发生什么。
当您运行第一个作业时,您的对象被序列化,切成块并且块被发送到执行程序(通过BlockManager机制)。它们从块中反序列化对象并将其用于处理任务。完成后,它们会丢弃反序列化对象,但BlockManager会保留缓存的序列化数据块。
对于第二个作业,不需要序列化和传输对象。它只是从缓存中反序列化并使用。
注意事项:首先,这并不能帮助您避免过多的GC。另一件事是,我尝试通过使用可变状态(class Test(var a: String) extends Serializable
)并在运行之间对其进行变更来验证上述理论。令我惊讶的是,第二轮看到了变异的状态!所以我在local
模式下完全错了,或者说错了。我希望有人能分辨出哪一个。 (如果我明天记得的话,我会尝试进一步测试。)