[英]Spark Kryo deserialization of EMR-produced files fails locally
在將 EMR 版本升級到 6.2.0(我們之前使用 5.0 beta - ish)和 Spark 3.0.1 后,我們注意到我們無法在本地讀取從 EMR 集群寫入的 Kryo 文件(這在以前顯然是可能的)。 嘗試讀取此類文件時,拋出的異常如下:
com.esotericsoftware.kryo.KryoException: java.lang.ClassCastException: scala.Tuple3 cannot be cast to scala.Tuple2
我們使用 spark 3.0.1 和 Kryo 4.0.2(捆綁)並使用 Kryo::readClassAndObject 讀取 Kryo 文件,使用 SparkContext::sequenceFile 對 RDD 重新讀取進行操作。
TL;DR:AWS EMR 6.2.0(可能更早)導致從 EMR 集群寫入的 Kryo 文件的本地反序列化失敗(由於集群運行 AWS Spark 分叉)。 要修復的代碼附在帖子末尾。
最近,Amazon EMR 集群運行了自己的 Apache Spark 的 fork(即對於 EMR 6.2.0 集群,Spark 版本為 3.0.1.amzn-0),其中包含了 Kryo 作為默認的序列化框架,我們自己使用。 自從升級到 6.2.0 后,我們注意到我們無法在本地讀取從 EMR 6.2.0 集群寫入的 Kryo 文件,它們會失敗並顯示如下消息:
com.esotericsoftware.kryo.KryoException: java.lang.ClassCastException: scala.Tuple3 cannot be cast to scala.Tuple2
我們試圖讀取的 RDD 確實是 Tuple2 類型的 RDD,但顯然在反序列化時,Kryo 認為它出於某種原因被編碼為 Tuple3 的 RDD。
現在,在內部,Kryo 擁有 ID <-> class 的映射,該映射在運行時構建,預計在讀寫 JVM 之間保持一致(用於確定要反序列化到哪個 class)。 此注冊表建立在 Kryo 實例的實例化之上(我們使用 org.apache.spark.serializer.KryoSerializer::newKryo)。 經過檢查,我們注意到 Tuple2 的 ID 在執行序列化的 EMR 集群和我們的本地機器之間確實不同,並且差異歸因於 EMR 設置中存在而不是本地的單個 class - 這個 class 是 org.ZB62ECCD606D1419D03 .spark.scheduler.HighlyCompressedMapStatus$CompressedSizes 在任何公開可用的 Spark 代碼中都不存在,因此我們將其歸因於 Amazon spark fork。 這實際上意味着我們無法在本地讀取幾乎任何由 EMR 集群編寫的 class,因為無法在本地使用 Spark 的分支,並且 class 在創建 Kryo 實例時注冊在 ID 13 上(以后可能會明顯改變),導致幾乎所有類都無法反序列化。
這里丑陋的解決方法是使用 Kryo 實例的 ClassResolver。 如果 CompressedSizes class 在注冊表中不存在,我們將所有 id x >= 13 的類注冊為 x + 1。這確實很難看,但作為本地修復,它可以工作。 顯然,它也可能會因 EMR/Kryo/Spark 的新版本而中斷,所以要格外小心(我們只在本地使用它進行調試,這仍然很多)。
代碼:以前,我們會像這樣創建 Kryo 實例:
val kryoSerializer = new KryoSerializer(sc.getConf)
val kryo = kryoSerializer.newKryo()
現在,我們使用這個:
val kryo = adjustRegistrationsForEmrSpark(kryoSerializer.newKryo())
在哪里
private def adjustRegistrationsForEmrSpark(kryo: Kryo): Kryo = {
val existingRegistrations = getRegistrations(kryo)
val emrSpecificClassExists = existingRegistrations.exists(_.getType.getName.contains("CompressedSizes"))
if (emrSpecificClassExists) {
println(s"detected emr-specific class when creating kryo, not making any adjustments")
kryo
} else {
println(s"emr-specific class missing from registrations, adjusting existing classes by an offset of 1 to compensate")
val classResolver = kryo.getClassResolver
existingRegistrations.filter(_.getId >= 13).foreach { registration =>
val toRegister = new Registration(registration.getType, registration.getSerializer, registration.getId + 1)
classResolver.register(toRegister)
}
kryo
}
}
private def getRegistrations(kryo: Kryo): List[Registration] = {
var classIndex = 0
var reg: Registration = null
var result: List[Registration] = List()
do {
reg = kryo.getClassResolver.getRegistration(classIndex)
if (reg != null) result ++= List(reg)
classIndex = classIndex + 1
} while (reg != null)
result
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.