Scala类lazy val变量与Spark的奇怪行为

时间:2016-01-18 16:25:39

标签: scala apache-spark

我注意到使用Spark为项目使用scala 2.10时的奇怪行为,我正在读取属性文件并将所有内容写入Map(loadConfig)中,我还创建了一个返回给定键值的简单方法

问题是,当我在lazy val类变量中获取所有blackListed名称时,namesBlackList似乎为空,因为我的所有Person都拥有“完全访问权限”标记,这是不正确的

然而,当我在<{strong> namesBlackList内写filterAccess 时,一切都运作良好。

ConfigManager.scala

object ConfigManager extends Serializable {

  private var configMap = Map.empty[String, String]

  def loadConfig(configPath:String) = {
    // Reads a key/value properties file and writes it in the configmap
  }

  def getParameter(parameter: String): String = configMap.getOrElse(parameter, s"${parameter}=>UNKNOWN")
}

AnalyseData.scala

object AnalyseData extends Serializable {

    private lazy val namesBlackList = ConfigManager.getParameter("names.blacklist").toSet

    def filterAccess(rdd:RDD[Person]) : RDD[Person] = {
        rdd.map {person => 
          if (namesBlackList.contains(person.firstName))
            (person.firstName,person.lastName,"limited access")
          else
            (person.firstName,person.lastName,"full Access")  
       }
    }
}

AnalyseService.scala

object AnalyseService extends Serializable {
    def main(path:String) {
        ConfigManager.loadConfig(path)

        val datas = createNameRdd // reads from a db and create a RDD[Person]

        val filteredData = AnalyseData.filterAccess(datas)

    }
}

我尝试调整代码中的所有内容,看起来,因为Spark以map方式执行lazy方法,所以在lazy val类中设置Singleton对象的结果变量不会产生正确的结果。 我无法理解为什么它不起作用,更重要的是,除了在方法中调用namesBlackList之外我无法真正找到解决方法

感谢您的评论。

2 个答案:

答案 0 :(得分:3)

有关所需的一些术语和概念的说明,请参阅https://spark.apache.org/docs/latest/programming-guide.html#understanding-closures-a-nameclosureslinka。你的情况会发生什么(我认为):

  1. ConfigManager.loadConfig(path)在驱动程序节点上运行。 configMap在那里初始化。

  2. filterAccess中,namesBlackList实际上是一种方法调用。因此,当在工作节点上执行map内的代码时,此调用就会在那里进行,并在同一节点上访问configMap,该节点为空。

  3. 但是,当你在filterAccess&#34;中写下名字BlackBList然后它是一个局部变量, 成为闭包的一部分,并被序列化。

  4. 要解决此问题,您需要为configMap使用broadcast variable。像

    这样的东西
    object ConfigManager extends Serializable {
    
      private var configMap: Broadcast[Map[String, String]] = _
    
      def loadConfig(configPath:String) = {
        // Reads a key/value properties file and writes it in the configmap
      }
    
      def getParameter(parameter: String): String = configMap.value.getOrElse(parameter, s"${parameter}=>UNKNOWN")
    }
    

    最好避免var

    def main(path:String) {
        val configMap = ConfigManager.loadConfig(path)
    
        val datas = createNameRdd(configMap) // reads from a db and create a RDD[Person]
    
        val filteredData = AnalyseData.filterAccess(datas, configMap)
    }
    

答案 1 :(得分:2)

也许您可以尝试在lazy val方法中强制filterAccess(但在闭包之外),如下所示:

object AnalyseData extends Serializable {

  private lazy val namesBlackList = ConfigManager.getParameter("names.blacklist").toSet

  def filterAccess(rdd:RDD[Person]) : RDD[Person] = {
      val localNamesBlackList = namesBlackList       //force the lazy val...
      rdd.map {person => 
        if (localNamesBlackList.contains(person.firstName))
          (person.firstName,person.lastName,"limited access")
        else
          (person.firstName,person.lastName,"full Access")  
     }
  }
}