当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)。
答案 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进程上生成相同的哈希码:
enum
的sealed trait
- 基于枚举:sealed trait TraitEnum
object TEA extends TraitEnum
object TEB extends TraitEnum
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(…)
其他信息: