簡體   English   中英

EMR 生成的文件的 Spark Kryo 反序列化在本地失敗

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM