枚举是Spark PairRDD导致问题的关键

时间:2017-05-02 20:56:00

标签: java scala apache-spark enums

当RDD的键是或包含枚举时,Spark PairRDD上的某些操作无法正常工作。

例如,下面的Spark代码需要两个星期的工作日,并按工作日计算:

import java.time.DayOfWeek
val weekdays: Seq[(DayOfWeek, Int)] = DayOfWeek.values().map(dow => (dow, 1))
val numPartitions = 2 * weekdays.size
val result = sc
  .parallelize(weekdays ++ weekdays, numPartitions)
  .reduceByKey(_ + _)
  .collect
  .toSeq
println(result)

在输出中,我希望每个工作日(例如,MONDAY)都有计数2,但是,在我的群集中,我得到:

WrappedArray(
  (THURSDAY,1), (SATURDAY,1), (WEDNESDAY,2), (SATURDAY,1),
  (MONDAY,2), (TUESDAY,2), (THURSDAY,1), (FRIDAY,2), (SUNDAY,2)
)

如果在具有单个节点的群集上运行此操作(或将numPartitions设置为1),则结果是正确的(即,所有计数均为2)。

1 个答案:

答案 0 :(得分:2)

Spark PairRDD的操作如aggregateByKey()reduceByKey()combineByKey()采用可选参数来指定Spark要使用的Partitioner。如果未明确指定分区程序,则使用Spark的HashPartitioner,它调用行的密钥hashCode()方法并使用它将行分配给分区。但是,不能保证枚举的hashCode()在不同的JVM进程上是相同的 - 即使它们运行在相同的Java版本上。因此,Spark xyzByKey()操作无法正常工作。

在上面的例子中,输入中有两对(THURSDAY, 1),每一对都在不同的执行器上处理。该示例使用带有14(= HashPartitioner)个分区的numPartitions。由于(THURSDAY, 1).hashCode() % 14在这两个执行器上产生不同的结果,因此这两行会被发送到不同的执行器以减少,导致结果不正确。

底线:不要将HashPartitioner用于哈希码在不同JVM进程上不一致的对象。特别是,不保证以下对象在不同的​​JVM进程上生成相同的哈希码:

  • Java enum
  • Scala sealed trait - 基于枚举:
sealed trait TraitEnum
object TEA extends TraitEnum
object TEB extends TraitEnum
  • Scala abstract class - 基于枚举:
sealed abstract class AbstractClassEnum
object ACA extends AbstractClassEnum
object ACB extends AbstractClassEnum
  • 包含上述类型之一的嵌套对象的任何键(并且没有自定义hashCode()实现)。

但是,基于Scala case class的枚举,具有一致的哈希码,因此可以安全使用:

sealed case class CaseClassEnum(…) # “…" must be a non-empty list of parameters
object CCA extends CaseClassEnum(…)
object CCB extends CaseClassEnum(…)

其他信息: