Görev serileştirilemez: java.io.NotSerializableException, işlevi yalnızca nesneler değil sınıflarda kapatma dışında çağırırken


224

Bir kapatma dışında işlevi çağırırken garip davranışlar alma:

  • işlev bir nesnede olduğunda her şey çalışır
  • işlev bir sınıftayken:

Görev serileştirilemez: java.io.NotSerializableException: test

Sorun benim kod bir sınıf değil, bir nesne gerekir. Bunun neden olduğu hakkında bir fikrin var mı? Scala nesnesi serileştirilmiş mi (varsayılan?)?

Bu bir çalışma kodu örneğidir:

object working extends App {
    val list = List(1,2,3)

    val rddList = Spark.ctx.parallelize(list)
    //calling function outside closure 
    val after = rddList.map(someFunc(_))

    def someFunc(a:Int)  = a+1

    after.collect().map(println(_))
}

Bu çalışmayan örnek:

object NOTworking extends App {
  new testing().doIT
}

//adding extends Serializable wont help
class testing {  
  val list = List(1,2,3)  
  val rddList = Spark.ctx.parallelize(list)

  def doIT =  {
    //again calling the fucntion someFunc 
    val after = rddList.map(someFunc(_))
    //this will crash (spark lazy)
    after.collect().map(println(_))
  }

  def someFunc(a:Int) = a+1
}

Spark.ctx dosyası nedir? Ctx AFAICT yöntemiyle Spark nesnesi yok
javadba

Yanıtlar:


334

RDD'ler Serialisable arabirimini genişletir , bu nedenle görevinizin başarısız olmasına neden olan şey bu değildir. Bu, RDDSpark ile serileştirebileceğiniz ve kaçınabileceğiniz anlamına gelmezNotSerializableException

Spark, dağıtılmış bir bilgi işlem motorudur ve ana soyutlaması, dağıtılmış bir koleksiyon olarak görüntülenebilen esnek bir dağıtılmış veri kümesidir ( RDD ). Temel olarak, RDD'nin öğeleri kümenin düğümleri arasında bölünür, ancak Spark bunu kullanıcıdan uzak tutar ve kullanıcının RDD (koleksiyon) ile yerel birmiş gibi etkileşim kurmasına izin verir.

Çok fazla detaya olsun, ancak bir RDD (farklı dönüşümleri yayınlandığında öyle değil map, flatMap, filterve diğerleri), sizin dönüşüm kodu (kapatma) 'dir:

  1. sürücü düğümünde serileştirilmiş,
  2. kümedeki uygun düğümlere gönderilir,
  3. serileştirilemezse,
  4. ve nihayet düğümlerde idam edildi

Elbette bunu yerel olarak çalıştırabilirsiniz (örneğin örnekte olduğu gibi), ancak tüm bu aşamalar (ağ üzerinden gönderim dışında) hala gerçekleşir. [Bu, üretime dağıtmadan önce bile hataları yakalamanızı sağlar]

İkinci durumunuzda olan şey testing, harita işlevinin içinden sınıfta tanımlanan bir yöntemi çağırmanızdır . Spark bunu görür ve yöntemler kendi başına serileştirilemediğinden, Spark tüm testing sınıfı serileştirmeye çalışır , böylece kod başka bir JVM'de yürütüldüğünde yine de çalışır. İki olasılığınız var:

Sınıf testini serileştirilebilir hale getirirsiniz, böylece tüm sınıf Spark tarafından serileştirilebilir:

import org.apache.spark.{SparkContext,SparkConf}

object Spark {
  val ctx = new SparkContext(new SparkConf().setAppName("test").setMaster("local[*]"))
}

object NOTworking extends App {
  new Test().doIT
}

class Test extends java.io.Serializable {
  val rddList = Spark.ctx.parallelize(List(1,2,3))

  def doIT() =  {
    val after = rddList.map(someFunc)
    after.collect().foreach(println)
  }

  def someFunc(a: Int) = a + 1
}

veya someFuncbir yöntem yerine işlev yaparsınız (işlevler Scala'daki nesnelerdir), böylece Spark bunu seri hale getirebilir:

import org.apache.spark.{SparkContext,SparkConf}

object Spark {
  val ctx = new SparkContext(new SparkConf().setAppName("test").setMaster("local[*]"))
}

object NOTworking extends App {
  new Test().doIT
}

class Test {
  val rddList = Spark.ctx.parallelize(List(1,2,3))

  def doIT() =  {
    val after = rddList.map(someFunc)
    after.collect().foreach(println)
  }

  val someFunc = (a: Int) => a + 1
}

Sınıf serileştirmesi ile benzer ancak aynı sorun sizi ilgilendirebilir ve bu Spark Summit 2013 sunumunda okuyabilirsiniz. .

Bir yan not olarak, yeniden yazabilirsiniz rddList.map(someFunc(_))için rddList.map(someFunc)onlar tam olarak aynıdır. Genellikle, daha az ayrıntılı ve okunması daha temiz olduğu için ikincisi tercih edilir.

DÜZENLEME (2015-03-15): SPARK-5307 , SerializationDebugger ve Spark 1.3.0'ı kullanan ilk sürümdür. NotSerializableException öğesine serileştirme yolu ekler . NotSerializableException ile karşılaşıldığında, hata ayıklayıcı, serileştirilemeyen nesneye giden yolu bulmak için nesne grafiğini ziyaret eder ve kullanıcının nesneyi bulmasına yardımcı olacak bilgileri oluşturur.

OP'nin durumunda, stdout'a yazdırılan şey budur:

Serialization stack:
    - object not serializable (class: testing, value: testing@2dfe2f00)
    - field (class: testing$$anonfun$1, name: $outer, type: class testing)
    - object (class testing$$anonfun$1, <function1>)

1
Hmm, açıkladığınız şey kesinlikle mantıklı ve tüm sınıfın neden serileştirildiğini (tam olarak anlamadığım bir şey) açıklıyor. Yine de hala RDD'lerin serileştirilebilir olmadığını (Serializable genişletmek, ancak bu NotSerializableException neden yok, denemek) anlamına gelmez. Bu yüzden onları sınıfların dışına koyarsanız hatayı düzeltir. Ne demek istediğim konusunda daha kesin olmak için cevabımı biraz düzenleyeceğim - yani, arayüzü genişlettiklerinden değil, istisnaya neden oluyorlar.
24the14'te samthebest

35
Sınıf üzerinde kontrolünüz yoksa, serileştirilebilir olmanız gerekir ... Scala kullanıyorsanız, Serializable ile hemen başlatabilirsiniz:val test = new Test with Serializable
Mark S

4
"rddList.map (someFunc (_)) 'dan rddList.map (someFunc)' e, tam olarak aynıdırlar" Hayır, tam olarak aynı değildirler ve aslında ikincisini kullanmak serileştirme istisnalarına neden olabilir.
samthebest

1
@ samthebest, haritanın (someFunc (_)) neden serileştirme istisnalarına neden olmadığını, harita (someFunc) neden olacağını açıklar mısınız?
Alon

31

Grega'nın cevabı , orijinal kodun neden çalışmadığını ve sorunu düzeltmenin iki yolunu açıklamakta harika. Ancak, bu çözüm çok esnek değildir; Kapanışınızın, Serializableüzerinde kontrol sahibi olmadığınız sınıf dışı bir yöntem çağrısı içerdiği durumu göz önünde bulundurun . SerializableYöntemi bir işleve dönüştürmek için etiketi bu sınıfa ekleyemez veya temel uygulamayı değiştiremezsiniz.

Nilesh bunun için harika bir çözüm sunuyor, ancak çözüm hem daha özlü hem de genel yapılabilir:

def genMapper[A, B](f: A => B): A => B = {
  val locker = com.twitter.chill.MeatLocker(f)
  x => locker.get.apply(x)
}

Bu işlev serileştiricisi daha sonra kapakları ve yöntem çağrılarını otomatik olarak sarmak için kullanılabilir:

rdd map genMapper(someFunc)

Bu tekniğin KryoSerializationWrapper, Twitter'ın Chill'i zaten çekirdek Spark tarafından çekildiğinden, erişmek için ek Köpekbalığı bağımlılıkları gerektirmeme avantajına da sahiptir.


Merhaba, kodunuzu kullanırsam bir şey kaydetmem gerekiyor mu acaba? Denedim ve kryo bir Unable bulmak sınıf istisnası olsun. THX
G_cy

25

Bu serileştirme sorunlarından kaçınmak için harika bir paradigma kaydırma yolu öneren sorunu tam olarak açıklayan tam konuşma: https://github.com/samthebest/dump/blob/master/sams-scala-tutorial/serialization-exceptions-and-memory- leaks-no-ws.md

En çok oylanan cevap temel olarak tüm dil özelliklerini atmayı öneriyor - bu artık yöntemleri kullanmıyor ve sadece işlevleri kullanıyor. Gerçekten de sınıflardaki fonksiyonel programlama yöntemlerinden kaçınılmalıdır, ancak bunları işlevlere dönüştürmek burada tasarım sorununu çözmez (yukarıdaki bağlantıya bakın).

Bu özel durumda hızlı bir düzeltme olarak, @transientek açıklamayı, rahatsız edici değeri serileştirmeye çalışmamasını söylemek için kullanabilirsiniz (burada, Spark.ctxSpark'ın OP'nin isimlendirmesini izleyen özel bir sınıf değildir):

@transient
val rddList = Spark.ctx.parallelize(list)

Ayrıca kodu rddList'in başka bir yerde yaşadığı şekilde yeniden yapılandırabilirsiniz, ancak bu da kötüdür.

Gelecek Muhtemelen Sporlar

Gelecekte Scala, "sporlar" adı verilen ve bir kapama ile tam olarak neyin çekilip neyin alınmadığını ince taneli kontrol etmemize izin verecek olan şeyleri içerecektir. Ayrıca bu, serileştirilemeyen türlerde (veya istenmeyen değerlerde) yanlışlıkla yapılan tüm hataların derleme hatalarına dönüştürülmelidir; bu, artık korkunç çalışma zamanı istisnaları / bellek sızıntılarıdır.

http://docs.scala-lang.org/sips/pending/spores.html

Kryo serileştirmesi hakkında bir ipucu

Kyro kullanırken, kayıt işleminin gerekli olmasını sağlayın, bu bellek sızıntıları yerine hatalar alacağınız anlamına gelir:

"Son olarak, kryo kryo.setRegistrationOptional (true) olduğunu biliyorum ama nasıl kullanılacağını anlamaya çalışırken çok zor zaman geçiriyorum. Bu seçenek açıldığında, kryo hala kayıtlı değilsem istisnalar atıyor gibi görünüyor sınıflar."

Kryo ile sınıfları kaydetme stratejisi

Tabii ki bu size değer seviyesi kontrolü değil, sadece tip seviyesi kontrolü sağlar.

... daha fazla fikir gelecek.


9

Bu sorunu farklı bir yaklaşım kullanarak çözdüm. Kapaktan geçmeden önce nesneleri serileştirmeniz ve daha sonra serileştirmeyi kaldırmanız yeterlidir. Bu yaklaşım, sınıflarınız Seri Hale Getirilemese bile işe yarar, çünkü sahne arkasında Kryo kullanır. Tek ihtiyacınız olan bir köri. ;)

İşte bunu nasıl yaptığımın bir örneği:

def genMapper(kryoWrapper: KryoSerializationWrapper[(Foo => Bar)])
               (foo: Foo) : Bar = {
    kryoWrapper.value.apply(foo)
}
val mapper = genMapper(KryoSerializationWrapper(new Blah(abc))) _
rdd.flatMap(mapper).collectAsMap()

object Blah(abc: ABC) extends (Foo => Bar) {
    def apply(foo: Foo) : Bar = { //This is the real function }
}

Blah'ı istediğiniz kadar karmaşık, sınıf, tamamlayıcı nesne, iç içe sınıflar, birden fazla 3. parti kütüphanesine referans yapmaktan çekinmeyin.

KryoSerializationWrapper şunu ifade eder: https://github.com/amplab/shark/blob/master/src/main/scala/shark/execution/serialization/KryoSerializationWrapper.scala


Bu gerçekten örneği serileştiriyor mu yoksa statik bir örnek mi oluşturuyor ve bir referansı serileştiriyor mu (cevabımı görün).
samthebest

2
@samthebest biraz ayrıntı verebilir misiniz? Araştırırsanız KryoSerializationWrapper, Spark'ın gerçekten olduğunu düşünmesini sağlar java.io.Serializable- nesneyi Kryo kullanarak dahili olarak serileştirir - daha hızlı, daha basit. Ve ben statik bir örnek ile ilgili olduğunu sanmıyorum - value.apply () çağrıldığında sadece serisinin serileştirmesi.
Nilesh

8

Benzer bir sorunla karşılaştım ve anladıklarım Grega'nın cevabından şey

object NOTworking extends App {
 new testing().doIT
}
//adding extends Serializable wont help
class testing {

val list = List(1,2,3)

val rddList = Spark.ctx.parallelize(list)

def doIT =  {
  //again calling the fucntion someFunc 
  val after = rddList.map(someFunc(_))
  //this will crash (spark lazy)
  after.collect().map(println(_))
}

def someFunc(a:Int) = a+1

}

senin doitin yöntemi seri hale getirmek çalışıyor someFunc (_) yöntemini ancak yöntem seri hale getirilebilir değil gibi, bu serialize sınıfı çalışır test tekrar seri hale getirilebilir değil.

Kodunuzun çalışmasını sağlayın, someFunc içeride Doit yöntemiyle. Örneğin:

def doIT =  {
 def someFunc(a:Int) = a+1
  //function definition
 }
 val after = rddList.map(someFunc(_))
 after.collect().map(println(_))
}

Resme gelen birden fazla işlev varsa, tüm bu işlevler ana içerik için kullanılabilir olmalıdır.


7

Ben tamamen bu Scala için geçerli olduğundan emin değilim, ancak Java, NotSerializableExceptionkodumu yeniden düzenleyerek kapatma kapatılamaz bir finalalana erişmedi böylece çözüldü .


Java aynı sorunla karşı karşıya, RDD foreach yöntemi içinde Java IO paketinden FileWriter sınıfını kullanmaya çalışıyorum. Lütfen bunu nasıl çözebileceğimizi bize bildirir misiniz?
Shankar

1
@ @ Khankar, eğer dış sınıfın FileWriterbir finalalanı ise, bunu yapamazsın. Ancak her ikisi de olan FileWritera Stringveya a'dan inşa edilebilir . Dış sınıftan dosya adına dayalı bir yerel oluşturmak için kodunuzu yeniden düzenleyin . FileSerializableFileWriter
Trebor Rude

0

Spark 2.4'teki FYI, muhtemelen bu sorunla karşılaşacaksınız. Kryo serileştirmesi daha iyi hale geldi, ancak çoğu durumda spark.kryo.unsafe = true veya naif kryo serileştiricisini kullanamazsınız.

Hızlı bir düzeltme için Spark yapılandırmanızda aşağıdakileri değiştirmeyi deneyin

spark.kryo.unsafe="false"

VEYA

spark.serializer="org.apache.spark.serializer.JavaSerializer"

Karşılaştığım veya kişisel olarak yazdığım özel RDD dönüşümlerini, açık yayın değişkenlerini kullanarak ve yeni yerleşik twitter-chill api'yi rdd.map(row =>kullanarak rdd.mapPartitions(partition => {işlevlerden işlevlere dönüştürerek değiştiriyorum .

Misal

Eski (büyük olmayan) Yol

val sampleMap = Map("index1" -> 1234, "index2" -> 2345)
val outputRDD = rdd.map(row => {
    val value = sampleMap.get(row._1)
    value
})

Alternatif (daha iyi) yol

import com.twitter.chill.MeatLocker
val sampleMap = Map("index1" -> 1234, "index2" -> 2345)
val brdSerSampleMap = spark.sparkContext.broadcast(MeatLocker(sampleMap))

rdd.mapPartitions(partition => {
    val deSerSampleMap = brdSerSampleMap.value.get
    partition.map(row => {
        val value = sampleMap.get(row._1)
        value
    }).toIterator
})

Bu yeni yol, yayın değişkenini bölüm başına yalnızca bir kez çağıracak ve bu daha iyi olacaktır. Sınıfları kaydetmezseniz yine de Java Serialization kullanmanız gerekecektir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.