टास्क सीरीज़ेबल नहीं: java.io.NotSerializableException जब कॉलिंग आउट फ़ंक्शन केवल क्लासेस पर नहीं वस्तुओं पर


224

किसी कार्य को बंद करने के बाद अजीब व्यवहार करना:

  • जब फ़ंक्शन एक वस्तु में होता है तो सब कुछ काम कर रहा होता है
  • जब समारोह एक कक्षा में हो:

कार्य क्रमबद्ध नहीं: java.io.NotSerializableException: परीक्षण

समस्या यह है कि मुझे एक कक्षा में अपने कोड की आवश्यकता है न कि किसी वस्तु की। कुछ पता है कि ऐसा क्यों हो रहा है? क्या स्कैला वस्तु क्रमबद्ध है (डिफ़ॉल्ट?)

यह एक वर्किंग कोड उदाहरण है:

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(_))
}

यह गैर-कार्यशील उदाहरण है:

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 क्या है? विधि ctx AFAICT के साथ कोई स्पार्क वस्तु नहीं है
javadba

जवाबों:


334

RDDs सीरियल इंटरफ़ेस का विस्तार करते हैं , इसलिए यह ऐसा नहीं है जो आपके कार्य को विफल कर रहा है। अब इसका मतलब यह नहीं है कि आप RDDस्पार्क के साथ अनुक्रम कर सकते हैं और बचेंNotSerializableException

स्पार्क एक वितरित कंप्यूटिंग इंजन है और इसका मुख्य अमूर्त एक लचीला वितरित डेटासेट ( RDD ) है, जिसे एक वितरित संग्रह के रूप में देखा जा सकता है। मूल रूप से, RDD के तत्वों को क्लस्टर के नोड्स में विभाजित किया जाता है, लेकिन स्पार्क इसे उपयोगकर्ता से दूर करता है, जिससे उपयोगकर्ता RDD (संग्रह) के साथ बातचीत करता है जैसे कि यह एक स्थानीय हो।

नहीं भी कई विवरण में प्राप्त करने के लिए, लेकिन जब आप एक RDD (पर विभिन्न परिवर्तनों को चलाने map, flatMap, filterऔर अन्य), अपने परिवर्तन कोड (बंद) है:

  1. चालक नोड पर क्रमबद्ध,
  2. क्लस्टर में उपयुक्त नोड्स के लिए भेज दिया,
  3. deserialized,
  4. और अंत में नोड्स पर निष्पादित किया गया

आप निश्चित रूप से इसे स्थानीय रूप से (आपके उदाहरण के अनुसार) चला सकते हैं, लेकिन वे सभी चरण (नेटवर्क पर शिपिंग के अलावा) अभी भी होते हैं। [यह आपको उत्पादन पर तैनात होने से पहले ही किसी भी कीड़े को पकड़ने देता है]

आपके दूसरे मामले में क्या होता है कि आप एक विधि कह रहे हैं, testingजो मानचित्र फ़ंक्शन के अंदर से कक्षा में परिभाषित है । स्पार्क यह देखता है कि और चूंकि विधियां अपने दम पर क्रमबद्ध नहीं की जा सकती हैं, स्पार्क पूरी testing कक्षा को क्रमबद्ध करने की कोशिश करता है , ताकि कोड तब भी काम करेगा जब किसी अन्य जेवीएम में निष्पादित हो। आपके पास दो संभावनाएँ हैं:

या तो आप कक्षा परीक्षण को क्रमबद्ध बनाते हैं, इसलिए पूरे वर्ग को स्पार्क द्वारा क्रमबद्ध किया जा सकता है:

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
}

या आप someFuncएक विधि के बजाय फ़ंक्शन करते हैं (फ़ंक्शन स्काला में ऑब्जेक्ट हैं), ताकि स्पार्क इसे क्रमबद्ध करने में सक्षम हो जाएगा:

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
}

इसी तरह, लेकिन कक्षा क्रमांकन के साथ समान समस्या आपके लिए रुचि नहीं हो सकती है और आप इस स्पार्क शिखर सम्मेलन 2013 की प्रस्तुति में इस पर पढ़ सकते हैं ।

एक साइड नोट के रूप में, आप फिर rddList.map(someFunc(_))से लिख सकते rddList.map(someFunc)हैं, वे बिल्कुल समान हैं। आमतौर पर, दूसरा पसंद किया जाता है क्योंकि यह पढ़ने के लिए कम क्रिया और क्लीनर है।

EDIT (2015-03-15): SPARK-5307 ने SerializationDebugger पेश किया और स्पार्क 1.3.0 इसका उपयोग करने वाला पहला संस्करण है। यह क्रमांकन पथ को NotSerializableException में जोड़ता है । जब NotSerializableException का सामना होता है, तो डीबगर ऑब्जेक्ट के लिए पथ को खोजने के लिए ऑब्जेक्ट ग्राफ़ पर जाता है जिसे क्रमबद्ध नहीं किया जा सकता है, और ऑब्जेक्ट को खोजने के लिए उपयोगकर्ता की मदद करने के लिए जानकारी का निर्माण करता है।

ओपी के मामले में, यह वही है जो stdout को मुद्रित किया जाता है:

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
हम्म, जो आपने समझाया है वह निश्चित रूप से समझ में आता है, और बताता है कि क्यों पूरी कक्षा को क्रमबद्ध किया जाता है (कुछ मुझे पूरी तरह से समझ में नहीं आया)। फिर भी मैं अभी भी पकड़ सकता हूँ कि rdd क्रमिक नहीं हैं (अच्छी तरह से वे Serializable बढ़ाते हैं, लेकिन इसका मतलब यह नहीं है कि वे NotSerializableException का कारण नहीं है, यह कोशिश करें)। यही कारण है कि यदि आप उन्हें कक्षाओं के बाहर रखते हैं तो यह त्रुटि को ठीक करता है। मैं अपना उत्तर संपादित करने जा रहा हूं, जो मेरे कहने के अर्थ के बारे में अधिक सटीक है - अर्थात वे अपवाद का कारण हैं, न कि वे इंटरफ़ेस का विस्तार करते हैं।

35
यदि आप वर्ग पर नियंत्रण करने की आवश्यकता नहीं है, तो आपको अनुक्रमिक होने की आवश्यकता है ... यदि आप स्काला का उपयोग कर रहे हैं, तो आप इसे केवल सीरियल के साथ तत्काल कर सकते हैं:val test = new Test with Serializable
मार्क एस

4
"rddList.map (someFunc (_)) to rddList.map (someFunc), वे वास्तव में समान हैं" नहीं वे वास्तव में समान नहीं हैं, और वास्तव में उत्तरार्द्ध का उपयोग करने से क्रमांकन अपवाद हो सकते हैं जो पूर्व नहीं थे।
samthebest

1
@samthebest क्या आप बता सकते हैं कि मानचित्र (someFunc (_)) क्रमांकन अपवादों का कारण क्यों नहीं बनेगा जबकि मानचित्र (someFunc) होगा?
Alon

31

ग्रीगा का उत्तर यह समझाने में महान है कि मूल कोड क्यों काम नहीं करता है और समस्या को ठीक करने के दो तरीके हैं। हालांकि, यह समाधान बहुत लचीला नहीं है; उस मामले पर विचार करें जहां आपके बंद होने में गैर- Serializableवर्ग पर एक विधि कॉल शामिल है जिसका आपके पास कोई नियंत्रण नहीं है। आप न तो Serializableइस वर्ग में टैग जोड़ सकते हैं और न ही किसी फ़ंक्शन में विधि को बदलने के लिए अंतर्निहित कार्यान्वयन को बदल सकते हैं।

नीलेश इसके लिए एक महान समाधान प्रस्तुत करते हैं, लेकिन समाधान को अधिक संक्षिप्त और सामान्य दोनों बनाया जा सकता है:

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

इस फंक्शन-सीरियलाइज़र को क्लोजर और मेथड कॉल को स्वचालित रूप से लपेटने के लिए इस्तेमाल किया जा सकता है:

rdd map genMapper(someFunc)

इस तकनीक का उपयोग करने के लिए अतिरिक्त शार्क निर्भरता की आवश्यकता नहीं होने का भी लाभ है KryoSerializationWrapper, क्योंकि ट्विटर की चिल को पहले ही कोर स्पार्क द्वारा खींच लिया गया है


नमस्ते, मुझे आश्चर्य है कि अगर मुझे आपके कोड का उपयोग करने के लिए कुछ रजिस्टर करने की आवश्यकता है? मैंने कोशिश की और क्रायो से एक असमर्थ खोज वर्ग अपवाद प्राप्त किया। THX
G_cy

25

समस्या को पूरी तरह से समझाते हुए पूरी बात करें, जो इन क्रमिक समस्याओं से बचने के लिए एक महान प्रतिमान स्थानांतरण का प्रस्ताव करता है: https://github.com/samthebest/dump/blob/master/sams-scala-tutorial/serifications-exception-and-memory- leaks-no-ws.md

शीर्ष मतदान का जवाब मूल रूप से एक पूरी भाषा सुविधा को फेंकने का सुझाव दे रहा है - जो अब विधियों का उपयोग नहीं कर रहा है और केवल फ़ंक्शन का उपयोग कर रहा है। वास्तव में कक्षाओं में कार्यात्मक प्रोग्रामिंग विधियों से बचा जाना चाहिए, लेकिन उन्हें कार्यों में बदलना यहां डिजाइन मुद्दे को हल नहीं कर रहा है (ऊपर लिंक देखें)।

इस विशेष स्थिति में एक त्वरित सुधार के रूप में आप केवल @transientएनोटेशन का उपयोग कर सकते हैं यह बताने के लिए कि यह अपमानजनक मूल्य को क्रमबद्ध करने की कोशिश नहीं करेगा (यहाँ, Spark.ctxओपी के नामकरण के बाद स्पार्क की एक कस्टम क्लास नहीं है):

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

आप कोड का पुनर्गठन भी कर सकते हैं ताकि rddList कहीं और रहे, लेकिन यह भी बुरा है।

भविष्य संभवतः बीजाणु है

भविष्य में स्काला में "बीजाणु" नाम की ये चीजें शामिल होंगी, जो हमें ठीक अनाज नियंत्रण की अनुमति देती हैं, जो एक बंद करने से बिल्कुल नहीं मिलता है। इसके अलावा यह गैर-क्रमिक प्रकारों (या किसी भी अवांछित मूल्यों) में गलती से खींचने की सभी गलतियों को संकलित त्रुटियों के बजाय बदल देना चाहिए जो अब तक के भयानक अपवाह अपवाद / मेमोरी लीक हैं।

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

क्रियो क्रमांकन पर एक टिप

काइरो का उपयोग करते समय, इसे बनाएं ताकि पंजीकरण आवश्यक हो, इसका मतलब यह होगा कि आपको मेमोरी लीक के बजाय त्रुटियां मिलेंगी:

"अंत में, मुझे पता है कि kryo में kryo.setRegistrationOptional (सच) है, लेकिन मुझे यह पता लगाने में बहुत मुश्किल समय आ रहा है कि इसका उपयोग कैसे किया जाए। जब ​​यह विकल्प चालू होता है, तो kryo अभी भी अपवादों को फेंकने लगता है जो मैंने पंजीकृत नहीं किया है। कक्षाएं। "

क्रायो के साथ कक्षाएं पंजीकृत करने की रणनीति

बेशक यह केवल आपको टाइप-स्तर नियंत्रण देता है, मूल्य-स्तर नियंत्रण नहीं।

... और विचार आने चाहिए।


9

मैंने एक अलग दृष्टिकोण का उपयोग करके इस समस्या को हल किया। आपको बस बंद होने से गुजरने से पहले वस्तुओं को क्रमबद्ध करना होगा, और बाद में डी-सीरियल करना होगा। यह दृष्टिकोण बस काम करता है, भले ही आपकी कक्षाएं सीरियल योग्य न हों, क्योंकि यह पर्दे के पीछे क्रियो का उपयोग करता है। बस आपको कुछ करी चाहिए। ;)

यहाँ एक उदाहरण है कि मैंने यह कैसे किया:

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 }
}

ब्लाह को आप जितना चाहें उतना जटिल बनाने के लिए स्वतंत्र महसूस करें, क्लास, साथी ऑब्जेक्ट, नेस्टेड क्लास, कई 3 पार्टी लिब के संदर्भ।

KryoSerializationWrapper को संदर्भित करता है: https://github.com/amplab/shark/blob/master/src/main/scala/shark/execution/serialization/KryoSerializationWrapper.scala


क्या यह वास्तव में उदाहरण को क्रमबद्ध करता है या एक स्थिर उदाहरण बनाता है और एक संदर्भ को क्रमबद्ध करता है (मेरा उत्तर देखें)।
samthebest

2
@samthebest क्या आप विस्तृत कर सकते हैं? यदि आप जांच KryoSerializationWrapperकरते हैं कि आप पाएंगे कि यह स्पार्क को लगता है कि यह वास्तव में है java.io.Serializable- यह केवल Kryo का उपयोग करके आंतरिक रूप से वस्तु को क्रमबद्ध करता है - तेज, सरल। और मुझे नहीं लगता कि यह एक स्थिर उदाहरण से संबंधित है - यह केवल वैल्यू-सीरियल करता है जब वैल्यू.apply () कहा जाता है।
नीलेश

8

मुझे इसी तरह के मुद्दे का सामना करना पड़ा, और मुझे ग्रीगा के जवाब से जो समझ में आया वह है

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

}

आपका doIT मेथड कुछ Func (_) मेथड को serialize करने की कोशिश कर रहा है , लेकिन जैसा कि मेथड सीरियलाइज़्ड नहीं है, यह क्लास टेस्टिंग को सीरियलाइज़ करने की कोशिश करता है, जो फिर से सेरेक्टेबल नहीं है।

तो अपने कोड काम करते हैं, आप परिभाषित करना चाहिए someFunc अंदर छदाम विधि। उदाहरण के लिए:

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

और अगर कई कार्य चित्र में आ रहे हैं, तो उन सभी कार्यों को मूल संदर्भ के लिए उपलब्ध होना चाहिए।


7

मैं पूरी तरह से निश्चित नहीं हूं कि यह स्कैला पर लागू होता है लेकिन, जावा में, मैंने NotSerializableExceptionअपने कोड को रीक्रिएट करके हल किया, ताकि क्लोजर एक गैर-धारावाहिक finalक्षेत्र तक पहुंच न सके ।


मैं जावा में एक ही समस्या का सामना कर रहा हूं, मैं आरडीडी फ़ॉरच विधि के अंदर जावा आईओ पैकेज से फाइलवेटर वर्ग का उपयोग करने की कोशिश कर रहा हूं। क्या आप कृपया मुझे बता सकते हैं कि हम इसे कैसे हल कर सकते हैं।
शंकर

1
खैर @Shankar, अगर FileWriterएक है finalबाहरी वर्ग के क्षेत्र के लिए, आप यह नहीं कर सकते। लेकिन FileWriterएक Stringया Fileदोनों से निर्माण किया जा सकता है , जो दोनों हैं Serializable। तो FileWriterबाहरी कोड से फ़ाइल नाम के आधार पर स्थानीय निर्माण के लिए अपने कोड को रिफ्लेक्टर करें ।
ट्रेबोर रूड

0

स्पार्क 2.4 में बहुत से FYI करें, शायद आप इस मुद्दे का सामना करेंगे। Kryo क्रमांकन बेहतर हो गया है, लेकिन कई मामलों में आप spark.kryo.unsafe = true या naive kryo serializer का उपयोग नहीं कर सकते हैं।

त्वरित सुधार के लिए अपने स्पार्क कॉन्फ़िगरेशन में निम्नलिखित को बदलने का प्रयास करें

spark.kryo.unsafe="false"

या

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

मुझे लगता है कि मैं का सामना कस्टम RDD परिवर्तनों को संशोधित करने या व्यक्तिगत रूप से स्पष्ट प्रसारण वैरिएबल का उपयोग और नए इनबिल्ट चहचहाना-सर्द एपीआई का उपयोग, उन लोगों से परिवर्तित करके लिखने rdd.map(row =>के लिए rdd.mapPartitions(partition => {कार्य करता है।

उदाहरण

पुराना (महान नहीं) रास्ता

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

वैकल्पिक (बेहतर) रास्ता

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
})

यह नया तरीका केवल विभाजन के अनुसार प्रसारण चर को एक बार कॉल करेगा जो बेहतर है। यदि आप कक्षाएं पंजीकृत नहीं करते हैं, तो भी आपको जावा सीरियलाइज़ेशन का उपयोग करना होगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.