【问题标题】:Why Mutable map becomes immutable automatically in UserDefinedAggregateFunction(UDAF) in Spark为什么可变映射在 Spark 的 UserDefinedAggregateFunction(UDAF) 中自动变为不可变
【发布时间】:2016-08-06 09:49:39
【问题描述】:

我正在尝试在 Spark 中定义一个 UserDefinedAggregateFunction(UDAF),它计算组的列中每个唯一值的出现次数。

这是一个例子: 假设我有一个这样的数据框df

+----+----+
|col1|col2|
+----+----+
|   a|  a1|
|   a|  a1|
|   a|  a2|
|   b|  b1|
|   b|  b2|
|   b|  b3|
|   b|  b1|
|   b|  b1|
+----+----+

我会有一个 UDAF DistinctValues

val func = new DistinctValues

然后我将它应用到数据框 df

val agg_value = df.groupBy("col1").agg(func(col("col2")).as("DV"))

我希望有这样的东西:

+----+--------------------------+
|col1|DV                        |
+----+--------------------------+
|   a|  Map(a1->2, a2->1)       |
|   b|  Map(b1->3, b2->1, b3->1)|
+----+--------------------------+

所以我想出了这样的 UDAF,

import org.apache.spark.sql.expressions.MutableAggregationBuffer
import org.apache.spark.sql.expressions.UserDefinedAggregateFunction
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.StructType
import org.apache.spark.sql.types.StructField
import org.apache.spark.sql.types.DataType
import org.apache.spark.sql.types.ArrayType
import org.apache.spark.sql.types.StringType
import org.apache.spark.sql.types.MapType
import org.apache.spark.sql.types.LongType
import Array._

class DistinctValues extends UserDefinedAggregateFunction {
  def inputSchema: org.apache.spark.sql.types.StructType = StructType(StructField("value", StringType) :: Nil)

  def bufferSchema: StructType = StructType(StructField("values", MapType(StringType, LongType))::Nil)

  def dataType: DataType =  MapType(StringType, LongType)
  def deterministic: Boolean = true

  def initialize(buffer: MutableAggregationBuffer): Unit = {
    buffer(0) = scala.collection.mutable.Map()
  }

  def update(buffer: MutableAggregationBuffer, input: Row) : Unit = {
    val str = input.getAs[String](0)
    var mp = buffer.getAs[scala.collection.mutable.Map[String, Long]](0)
    var c:Long = mp.getOrElse(str, 0)
    c = c + 1
    mp.put(str, c)
    buffer(0) = mp
  }

  def merge(buffer1: MutableAggregationBuffer, buffer2: Row) : Unit = {
    var mp1 = buffer1.getAs[scala.collection.mutable.Map[String, Long]](0)
    var mp2 = buffer2.getAs[scala.collection.mutable.Map[String, Long]](0)
    mp2 foreach {
        case (k ,v) => {
            var c:Long = mp1.getOrElse(k, 0)
            c = c + v
            mp1.put(k ,c)
        }
    }
    buffer1(0) = mp1
  }

  def evaluate(buffer: Row): Any = {
      buffer.getAs[scala.collection.mutable.Map[String, LongType]](0)
  }
}

然后我的数据框上有这个功能,

val func = new DistinctValues
val agg_values = df.groupBy("col1").agg(func(col("col2")).as("DV"))

它给出了这样的错误,

func: DistinctValues = $iwC$$iwC$DistinctValues@17f48a25
org.apache.spark.SparkException: Job aborted due to stage failure: Task 1 in stage 32.0 failed 4 times, most recent failure: Lost task 1.3 in stage 32.0 (TID 884, ip-172-31-22-166.ec2.internal): java.lang.ClassCastException: scala.collection.immutable.Map$EmptyMap$ cannot be cast to scala.collection.mutable.Map
at $iwC$$iwC$DistinctValues.update(<console>:39)
at org.apache.spark.sql.execution.aggregate.ScalaUDAF.update(udaf.scala:431)
at org.apache.spark.sql.execution.aggregate.AggregationIterator$$anonfun$12.apply(AggregationIterator.scala:187)
at org.apache.spark.sql.execution.aggregate.AggregationIterator$$anonfun$12.apply(AggregationIterator.scala:180)
at org.apache.spark.sql.execution.aggregate.SortBasedAggregationIterator.processCurrentSortedGroup(SortBasedAggregationIterator.scala:116)
at org.apache.spark.sql.execution.aggregate.SortBasedAggregationIterator.next(SortBasedAggregationIterator.scala:152)
at org.apache.spark.sql.execution.aggregate.SortBasedAggregationIterator.next(SortBasedAggregationIterator.scala:29)
at scala.collection.Iterator$$anon$11.next(Iterator.scala:328)
at scala.collection.Iterator$$anon$11.next(Iterator.scala:328)
at org.apache.spark.shuffle.sort.BypassMergeSortShuffleWriter.write(BypassMergeSortShuffleWriter.java:149)
at org.apache.spark.scheduler.ShuffleMapTask.runTask(ShuffleMapTask.scala:73)
at org.apache.spark.scheduler.ShuffleMapTask.runTask(ShuffleMapTask.scala:41)
at org.apache.spark.scheduler.Task.run(Task.scala:89)
at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:213)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)

看起来在update(buffer: MutableAggregationBuffer, input: Row)方法中,变量buffer是一个immutable.Map,程序累了把它转换成mutable.Map

但我使用mutable.Mapinitialize(buffer: MutableAggregationBuffer, input:Row) 方法中初始化buffer 变量。它是传递给update 方法的同一个变量吗?还有buffermutableAggregationBuffer,所以它应该是可变的,对吧?

为什么我的 mutable.Map 变得不可变了?有谁知道发生了什么?

我确实需要这个函数中的可变 Map 来完成任务。我知道有一种解决方法可以从不可变映射创建一个可变映射,然后对其进行更新。但是我真的很想知道为什么程序中可变的会自动转换为不可变的,这对我来说没有意义。

【问题讨论】:

    标签: scala apache-spark mutable user-defined-aggregate


    【解决方案1】:

    相信它是您的StructType 中的MapType。因此buffer 拥有一个Map,它是不可变的。

    你可以转换它,但你为什么不让它不可变并这样做:

    mp = mp + (k -> c)
    

    向不可变的Map添加一个条目?

    下面的工作示例:

    class DistinctValues extends UserDefinedAggregateFunction {
      def inputSchema: org.apache.spark.sql.types.StructType = StructType(StructField("_2", IntegerType) :: Nil)
    
      def bufferSchema: StructType = StructType(StructField("values", MapType(StringType, LongType))::Nil)
    
      def dataType: DataType =  MapType(StringType, LongType)
      def deterministic: Boolean = true
    
      def initialize(buffer: MutableAggregationBuffer): Unit = {
        buffer(0) = Map()
      }
    
      def update(buffer: MutableAggregationBuffer, input: Row) : Unit = {
        val str = input.getAs[String](0)
        var mp = buffer.getAs[Map[String, Long]](0)
        var c:Long = mp.getOrElse(str, 0)
        c = c + 1
        mp = mp  + (str -> c)
        buffer(0) = mp
      }
    
      def merge(buffer1: MutableAggregationBuffer, buffer2: Row) : Unit = {
        var mp1 = buffer1.getAs[Map[String, Long]](0)
        var mp2 = buffer2.getAs[Map[String, Long]](0)
        mp2 foreach {
            case (k ,v) => {
                var c:Long = mp1.getOrElse(k, 0)
                c = c + v
                mp1 = mp1 + (k -> c)
            }
        }
        buffer1(0) = mp1
      }
    
      def evaluate(buffer: Row): Any = {
          buffer.getAs[Map[String, LongType]](0)
      }
    }
    

    【讨论】:

    • 不错的收获!嗯,StructType中的MapyType可能就是这样。但是spark.sql.types 中没有其他可变映射类型,除非我自己定义。
    • 就像我说的,不要——只使用不可变的Map。不可变的Map 上的mp = mp + (k -&gt; c) 提供与可变Map 上的mp.put(k, c) 相同的功能
    • mp = mp + (k -&gt; c) 有效!我是 scala 的新手,不知道您可以像这样操作不可变的数据类型。非常感谢!
    • 你不是在操作它,而是基于前一个实例创建一个全新的实例,然后丢弃前一个实例。但是,是的,我现在几乎只使用不可变集合——可变集合真的没有太多理由。
    • 找到你了!所以mp 必须是var,你正在为变量mp 重新分配一个新映射。答案已接受
    【解决方案2】:

    聚会迟到了。我刚刚发现可以使用

    override def bufferSchema: StructType = StructType(List(
        StructField("map", ObjectType(classOf[mutable.Map[String, Long]]))
    ))
    

    在缓冲区中使用mutable.Map

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-04-25
      • 1970-01-01
      • 1970-01-01
      • 2017-01-13
      • 2017-03-21
      • 2017-05-02
      • 2020-10-14
      相关资源
      最近更新 更多