如何在spark

时间:2016-10-13 08:20:19

标签: scala apache-spark weka partitioning

我有一个存储在S3中的weka模型,大小约为400MB。 现在,我有一些记录,我想在其上运行模型并执行预测。

为了进行预测,我试过的是,

  1. 在驱动程序上下载并加载模型作为静态对象,将其广播给所有执行程序。对预测RDD执行映射操作。 ---->不工作,如在Weka中执行预测,模型对象需要修改,广播需要只读副本。

  2. 在驱动程序上下载并加载模型作为静态对象,并在每个映射操作中将其发送给执行程序。 ----->工作(效率不高,如在每个地图操作中,我传递400MB对象)

  3. 在驱动程序上下载模型并将其加载到每个执行程序上并将其缓存在那里。 (不知道该怎么做)

  4. 有人知道如何在每个执行程序上加载模型一次并将其缓存,以便其他记录我不再加载它?

4 个答案:

答案 0 :(得分:26)

您有两种选择:

1。使用表示数据的惰性val创建单个对象:

    object WekaModel {
        lazy val data = {
            // initialize data here. This will only happen once per JVM process
        }
    }       

然后,您可以在map函数中使用lazy val。 lazy val确保每个工作者JVM初始化他们自己的数据实例。不会对data执行序列化或广播。

    elementsRDD.map { element =>
        // use WekaModel.data here
    }

<强>优点

  • 更高效,因为它允许您为每个JVM实例初始化一次数据。例如,当需要初始化数据库连接池时,此方法是一个不错的选择。

<强>缺点

  • 对初始化的控制较少。例如,如果需要运行时参数,初始化对象会很棘手。
  • 如果需要,您无法释放或释放对象。通常,这是可以接受的,因为操作系统会在进程退出时释放资源。

2。在RDD上使用mapPartition(或foreachPartition)方法,而不仅仅是map

这允许您初始化整个分区所需的任何内容。

    elementsRDD.mapPartition { elements =>
        val model = new WekaModel()

        elements.map { element =>
            // use model and element. there is a single instance of model per partition.
        }
    }

<强>优点

  • 为对象的初始化和取消初始化提供更大的灵活性。

<强>缺点

  • 每个分区都将创建并初始化对象的新实例。根据每个JVM实例的分区数量,它可能是也可能不是问题。

答案 1 :(得分:1)

这对我来说比懒惰的初始化程序更有用。我创建了一个初始化为null的对象级指针,让每个执行器初始化它。在初始化块中,您可以使用一次性运行代码。请注意,每个处理批处理将重置局部变量,但不会重置对象级变量。

object Thing1 {
  var bigObject : BigObject = null

  def main(args: Array[String]) : Unit = {
    val sc = <spark/scala magic here>
    sc.textFile(infile).map(line => {
      if (bigObject == null) {
         // this takes a minute but runs just once
         bigObject = new BigObject(parameters)  
      }
      bigObject.transform(line)
    })
  }
}

这种方法每个执行器只创建一个大对象,而不是其他方法的每个分区的一个大对象。

如果将 var bigObject:BigObject = null 放在主函数名称空间中,它的行为会有所不同。在这种情况下,它在每个分区的开头运行bigObject构造函数(即批处理)。如果你有内存泄漏,那么这最终会杀死执行程序。垃圾收集还需要做更多的工作。

答案 2 :(得分:0)

这是我们通常要做的

  1. 定义执行此类工作的单例客户端,以确保每个执行器中仅存在一个客户端

  2. 有一个getorcreate方法来创建或获取客户信息,通常让您拥有一个要为多个不同模型提供服务的通用服务平台,然后我们就可以像并发映射一样使用以确保线程安全和可计算性

  3. getorcreate方法将在RDD级别(如transform或foreachpartition)中调用,因此请确保init在执行程序级别发生

答案 3 :(得分:0)

您可以通过广播带有惰性val的case对象来实现此目的,如下所示:

case object localSlowTwo {lazy val value: Int = {Thread.sleep(1000); 2}}
val broadcastSlowTwo = sc.broadcast(localSlowTwo)
(1 to 1000).toDS.repartition(100).map(_ * broadcastSlowTwo.value.value).collect

在具有三个线程的三个执行器上的事件时间表如下:

Event Timeline for Stage 1

在同一spark-shell会话中再次运行最后一行不会再初始化:

Event Timeline for Stage 3