迭代RDD并更新可变集合将返回空集合

时间:2016-12-16 23:29:45

标签: scala apache-spark bigdata

我是Scala和Spark的新手,希望能够帮助理解为什么下面的代码没有产生我想要的结果。

我正在比较两个表

我想要的输出架构是:

case class DiscrepancyData(fieldKey:String, fieldName:String, val1:String, val2:String, valExpected:String)

当我手动逐步运行下面的代码时,我实际上最终得到了我想要的结果。这是List[DiscrepancyData]完全填充了我想要的输出。但是,我必须在下面的代码中遗漏一些东西,因为它返回一个空列表(在调用此代码之前,还有其他代码从HIVE读取表,映射,分组,过滤等等):

val compareCols  = Set(year, nominal, adjusted_for_inflation, average_private_nonsupervisory_wage)

val key = "year"

def compare(table:RDD[(String, Iterable[Row])]): List[DiscrepancyData] = {
    var discs: ListBuffer[DiscrepancyData] = ListBuffer()
    def compareFields(fieldOne:String, fieldTwo:String, colName:String, row1:Row, row2:Row): DiscrepancyData = {
        if (fieldOne != fieldTwo){
            DiscrepancyData(
                row1.getAs(key).toString, //fieldKey
                colName, //fieldName
                row1.getAs(colName).toString, //table1Value
                row2.getAs(colName).toString, //table2Value
                row2.getAs(colName).toString) //expectedValue
        }
        else null
    }
    def comparison() {
        for(row <- table){
            var elem1 = row._2.head //gets the first element in the iterable
            var elem2 = row._2.tail.head //gets the second element in the iterable

            for(col <- compareCols){
                var value1 = elem1.getAs(col).toString
                var value2 = elem2.getAs(col).toString

                var disc = compareFields(value1, value2, col, elem1, elem2)

                if (disc != null) discs += disc
            }
        }
    }

    comparison()

    discs.toList
}

我正在调用上述函数:

var outcome = compare(groupedFiltered)

以下是groupsFiltered:

中的数据
(1991,CompactBuffer([1991,7.14,5.72,39%], [1991,4.14,5.72,39%]))
(1997,CompactBuffer([1997,4.88,5.86,39%], [1997,3.88,5.86,39%]))
(1999,CompactBuffer([1999,5.15,5.96,39%], [1999,5.15,5.97,38%]))
(1947,CompactBuffer([1947,0.9,2.94,35%], [1947,0.4,2.94,35%]))
(1980,CompactBuffer([1980,3.1,6.88,45%], [1980,3.1,6.88,48%]))
(1981,CompactBuffer([1981,3.15,6.8,45%], [1981,3.35,6.8,45%]))

groupsFiltered的表格架构:

(year String, 
nominal Double,
adjusted_for_inflation Double, 
average_provate_nonsupervisory_wage String)

2 个答案:

答案 0 :(得分:2)

迭代RDD并更新循环外定义的可变结构 Spark反模式

想象一下这个RDD分布在200台机器上。这些机器如何更新相同的缓冲区?他们不能。每个JVM都会看到自己的discs: ListBuffer[DiscrepancyData]。最后,您的结果将不是您所期望的。

总而言之,这是一个完全有效的(不是惯用的)Scala代码,但不是有效的Spark代码。如果用数组替换RDD,它将按预期工作。

尝试在这些方面实现更多功能:

val finalRDD: RDD[DiscrepancyData] = table.map(???).filter(???) 

答案 1 :(得分:2)

Spark是一种分布式计算引擎。在经典单节点计算的“代码正在做什么”旁边,使用Spark我们还需要考虑“代码运行的位置”

让我们检查上面表达式的简化版本:

val records: RDD[List[String]] = ??? //whatever data
var list:mutable.List[String] = List()
for {record <- records
     entry <- records } 
    { list += entry }

scala for-comprehension使该表达式看起来像一个自然的局部计算,但实际上RDD操作被序列化并“运送”到执行程序,其中内部操作将在本地执行。我们可以像这样重写上面的内容:

records.foreach{ record =>     //RDD.foreach => serializes closure and executes remotely
     record.foreach{entry =>   //record.foreach => local operation on the record collection
        list += entry          // this mutable list object is updated in each executor but never sent back to the driver. All updates are lost  
     }
}

在分布式计算中,可变对象通常是不可行的。想象一下,一个执行器添加一个记录而另一个执行器删除它,正确的结果是什么?或者每个执行者都得到一个不同的值,这是正确的值?

要实现上述操作,我们需要将数据转换为我们想要的结果。

我首先应用另一种最佳做法:不要使用null作为返回值。我还将行操作移动到了函数中。让我们记住重写比较操作:

def compareFields(colName:String, row1:Row, row2:Row): Option[DiscrepancyData] = {
    val key = "year"
    val v1 = row1.getAs(colName).toString
    val v2 = row2.getAs(colName).toString
    if (v1 != v2){
        Some(DiscrepancyData(
            row1.getAs(key).toString, //fieldKey
            colName, //fieldName
            v1, //table1Value
            v2, //table2Value
            v2) //expectedValue
        )
    } else None
}

现在,我们可以将差异计算重写为初始table数据的转换:

val discrepancies = table.flatMap{case (str, row) =>
    compareCols.flatMap{col => compareFields(col, row.next, row.next) }   
}

我们也可以使用for-comprehension表示法,现在我们了解了运行的位置:

val discrepancies = for {
    (str,row) <- table
    col <- compareCols
    dis <- compareFields(col, row.next, row.next)
} yield dis

请注意,discrepancies的类型为RDD[Discrepancy]。如果我们想要获得驱动程序的实际值,我们需要:

val materializedDiscrepancies = discrepancies.collect()