Spark专家的一个很好的问题。
我正在map
操作(RDD)中处理数据。在mapper函数中,我需要查找类A
的对象,以用于处理RDD中的元素。
由于这将在执行程序上执行,因此创建类型为A
的元素(将被查找)恰好是一项昂贵的操作,我想在每个执行程序上预加载和缓存这些对象。这样做的最佳方式是什么?
一个想法是广播查找表,但类A
不可序列化(无法控制其实现)。
另一个想法是将它们加载到单个对象中。但是,我想控制加载到查找表中的内容(例如,可能是不同Spark作业上的不同数据)。
理想情况下,我想指定一次将在执行程序上加载的内容(包括Streaming的情况,以便查找表在批处理之间保留在内存中),通过在启动期间驱动程序上可用的参数,在处理任何数据之前。
是否有干净优雅的方式或无法实现?
答案 0 :(得分:5)
这正是broadcast.
的目标用例。广播变量传输一次并使用种子有效地移动到所有执行器,并保留在内存/本地磁盘中,直到您不再需要它们为止。
使用其他人的界面时,序列化经常会出现问题。如果您可以强制执行您使用的对象是可序列化的,那么这将是最佳解决方案。如果这是不可能的,那么你的生活会变得更加复杂。如果无法序列化A
对象,则必须在每个任务的执行程序上创建它们。如果它们存储在某个文件中,则看起来像是:
rdd.mapPartitions { it =>
val lookupTable = loadLookupTable(path)
it.map(elem => fn(lookupTable, elem))
}
请注意,如果您正在使用此模型,那么您必须为每个任务加载一次查找表 - 您无法从广播变量的跨任务持久性中受益。
编辑:这是另一个模型,我相信可以让你在每个JVM的任务中共享查找表。
class BroadcastableLookupTable {
@transient val lookupTable: LookupTable[A] = null
def get: LookupTable[A] = {
if (lookupTable == null)
lookupTable = < load lookup table from disk>
lookupTable
}
}
这个类可以广播(没有实质内容传输),第一次按JVM调用,你将加载查找表并返回它。
答案 1 :(得分:3)
如果序列化变得不可能,那么如何将查找对象存储在数据库中?它不是最简单的解决方案,但应该可以正常工作。我可以建议检查,例如spark-redis,但我相信有更好的解决方案。
答案 2 :(得分:0)
由于A
不可序列化,因此最简单的解决方案是创建自己的可序列化类型A1
,其中包含计算所需的所有A
数据。然后在广播中使用新的查找表。