Hadoop प्रक्रिया रिकॉर्ड को ब्लॉक सीमाओं के बीच कैसे विभाजित करती है?


119

के अनुसार Hadoop - The Definitive Guide

FileInputFormats को परिभाषित करने वाले तार्किक रिकॉर्ड आमतौर पर HDFS ब्लॉकों में बड़े करीने से फिट नहीं होते हैं। उदाहरण के लिए, एक TextInputFormat के तार्किक रिकॉर्ड लाइनें हैं, जो एचडीएफएस सीमाओं को अधिक बार पार नहीं करेगा। उदाहरण के लिए, आपके प्रोग्राम की लाइनों पर कोई असर नहीं पड़ा है - लाइनें छूटी हुई या टूटी हुई नहीं हैं - लेकिन इसके बारे में जानने लायक है, क्योंकि इसका मतलब है कि डेटा-स्थानीय मैप्स (यानी वे नक्शे जो अपने मेजबान के समान ही चल रहे हैं इनपुट डेटा) कुछ रिमोट रीड्स करेगा। मामूली उपरि यह कारण सामान्य रूप से महत्वपूर्ण नहीं है।

मान लीजिए कि एक रिकॉर्ड लाइन दो ब्लॉकों (बी 1 और बी 2) में विभाजित है। पहले ब्लॉक (b1) को संसाधित करने वाला मैपर ध्यान देगा कि अंतिम पंक्ति में EOL विभाजक नहीं है और डेटा के अगले खंड (b2) से शेष रेखा को प्राप्त करता है।

दूसरे ब्लॉक (b2) को संसाधित करने वाला मैपर कैसे निर्धारित करता है कि पहला रिकॉर्ड अधूरा है और ब्लॉक (b2) में दूसरे रिकॉर्ड से शुरू होने वाली प्रक्रिया होनी चाहिए?

जवाबों:


160

दिलचस्प सवाल, मैंने विवरण के लिए कोड को देखने में कुछ समय बिताया और यहां मेरे विचार हैं। क्लाइंट द्वारा विभाजन को नियंत्रित किया जाता है InputFormat.getSplits, इसलिए FileInputFormat पर एक नज़र निम्नलिखित जानकारी देती है:

  • प्रत्येक इनपुट फ़ाइल के लिए, फ़ाइल की लंबाई, ब्लॉक आकार प्राप्त करें और विभाजित आकार की गणना करें max(minSize, min(maxSize, blockSize))जहां से maxSizeमेल खाती है mapred.max.split.sizeऔर minSizeहै mapred.min.split.size
  • FileSplitऊपर गणना की गई विभाजित आकार के आधार पर फ़ाइल को अलग-अलग एस में विभाजित करें। यहाँ क्या महत्वपूर्ण है कि प्रत्येक इनपुट फ़ाइल में ऑफसेट के अनुरूप FileSplitएक startपैरामीटर के साथ आरंभीकृत किया गया है । उस बिंदु पर अभी भी लाइनों का कोई संचालन नहीं है। कोड का प्रासंगिक हिस्सा इस तरह दिखता है:

    while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
      int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
      splits.add(new FileSplit(path, length-bytesRemaining, splitSize, 
                               blkLocations[blkIndex].getHosts()));
      bytesRemaining -= splitSize;
    }
    

उसके बाद, यदि आप देखते हैं LineRecordReaderकि किसके द्वारा परिभाषित किया गया है TextInputFormat, तो यह वह जगह है जहाँ लाइनें संभाला जाता है:

  • जब आप अपना इनिशियलाइज़ कर लेते हैं तो LineRecordReaderयह तुरंत ही LineReaderसमाप्त होने की कोशिश करता है जो कि लाइनों को पढ़ने में सक्षम होने के लिए एक अमूर्तता है FSDataInputStream। 2 मामले हैं:
  • यदि कोई CompressionCodecपरिभाषित है, तो यह कोडेक सीमाओं को संभालने के लिए जिम्मेदार है। संभवतः आपके प्रश्न के लिए प्रासंगिक नहीं है।
  • यदि कोई कोडेक नहीं है, तो यह वह जगह है जहाँ चीजें दिलचस्प होती हैं: यदि startआपकी InputSplitसंख्या 0 से भिन्न है, तो आप 1 वर्ण को पीछे ले जाते हैं और फिर पहली पंक्ति को छोड़ देते हैं जिसे आपने \ n या \ n \ n (विंडोज़) से पहचाना है ! बैकट्रैक महत्वपूर्ण है क्योंकि यदि आपकी लाइन की सीमाएं विभाजित सीमाओं के समान हैं, तो यह सुनिश्चित करता है कि आप मान्य लाइन को छोड़ें नहीं। यहाँ प्रासंगिक कोड है:

    if (codec != null) {
       in = new LineReader(codec.createInputStream(fileIn), job);
       end = Long.MAX_VALUE;
    } else {
       if (start != 0) {
         skipFirstLine = true;
         --start;
         fileIn.seek(start);
       }
       in = new LineReader(fileIn, job);
    }
    if (skipFirstLine) {  // skip first line and re-establish "start".
      start += in.readLine(new Text(), 0,
                        (int)Math.min((long)Integer.MAX_VALUE, end - start));
    }
    this.pos = start;
    

इसलिए चूंकि क्लाइंट में स्प्लिट्स की गणना की जाती है, मैपर्स को अनुक्रम में चलाने की आवश्यकता नहीं होती है, प्रत्येक मैपर को पहले से ही पता होता है कि यह पहली पंक्ति को त्यागने के लिए है या नहीं।

तो मूल रूप से यदि आपके पास एक ही फ़ाइल में प्रत्येक 100Mb की 2 लाइनें हैं, और सरल बनाने के लिए मान लें कि विभाजन का आकार 64Mb है। फिर जब इनपुट विभाजन की गणना की जाती है, तो हमारे पास निम्न परिदृश्य होगा:

  • इस ब्लॉक में पथ और मेजबानों वाले 1 को विभाजित करें। शुरुआत में 200-200 = 0Mb, लंबाई 64Mb।
  • स्प्लिट 2 शुरू में 200-200 + 64 = 64Mb, लंबाई 64Mb।
  • स्प्लिट 3 शुरू 200-200 + 128 = 128Mb, लंबाई 64Mb।
  • स्प्लिट 4 को 200-200 + 192 = 192Mb, लंबाई 8Mb पर शुरू किया गया।
  • मैपर ए 1 को विभाजित करने की प्रक्रिया करेगा, प्रारंभ 0 है इसलिए पहली पंक्ति को छोड़ें नहीं, और एक पूरी लाइन पढ़ें जो 64Mb सीमा से आगे जाती है ताकि दूरस्थ पढ़ने की आवश्यकता हो।
  • Mapper B प्रक्रिया 2 को विभाजित करेगा, प्रारंभ है! = 0 इसलिए 64Mb-1byte के बाद पहली पंक्ति को छोड़ें, जो पंक्ति 1 के अंत में 100Mb से मेल खाती है जो अभी भी 2 में विभाजित है, हमारे पास विभाजन 2 में 28Mb पंक्ति है, इसलिए दूरस्थ ने शेष 72Mb पढ़ा।
  • मैपर सी प्रक्रिया 3 को विभाजित करेगी, प्रारंभ है! = 0 इसलिए 128Mb-1byte के बाद पहली पंक्ति को छोड़ें, जो 200Mb पर पंक्ति 2 के अंत से मेल खाती है, जो फ़ाइल का अंत है इसलिए कुछ भी न करें।
  • Mapper D मैपर सी के समान है सिवाय इसके कि यह 192Mb-1byte के बाद एक नई रेखा की तलाश करता है।

इसके अलावा @PraveenSripati यह उल्लेख के लायक है कि किनारे के मामले जहां एक सीमा पर \ r \ n रिटर्न में आर होगा LineReader.readLine, मुझे लगता है कि यह आपके प्रश्न के लिए प्रासंगिक नहीं है, लेकिन यदि आवश्यक हो तो अधिक विवरण जोड़ सकते हैं।
चार्ल्स मेंगयू

मान लें कि इनपुट में सटीक 64MB के साथ दो लाइनें हैं और इसलिए InputSplits लाइन की सीमाओं पर बिल्कुल होती हैं। तो, क्या मैपर हमेशा दूसरे ब्लॉक में लाइन को नजरअंदाज करेगा क्योंकि स्टार्ट! = 0.
प्रवीण श्रीपति

6
@PraveenSripati उस स्थिति में, दूसरा मैपर स्टार्ट दिखाई देगा! = 0, इसलिए 1 वर्ण को पीछे ले जाता है, जो आपको पहली पंक्ति के \ n से पहले वापस लाता है और फिर निम्न \ n पर छोड़ देता है। तो यह पहली पंक्ति को छोड़ देगा लेकिन दूसरी पंक्ति को अपेक्षित रूप से संसाधित करेगा।
चार्ल्स मेंग्यु

@CharlesMenguy यह संभव है कि फ़ाइल की पहली पंक्ति किसी तरह से छूट जाए? इसके विपरीत, मेरे पास कुंजी = 1 के साथ पहली पंक्ति है, और मान a है, तो फ़ाइल में कहीं एक ही कुंजी के साथ दो और लाइनें हैं, कुंजी = 1, वैल = बी और कुंजी = 1, वैल = सी। बात यह है, मेरे reducer को {1, [a, b, c]} के बजाय {1, [b, c]} और {1, [a]} मिलता है। यदि मैं अपनी फ़ाइल की शुरुआत में नई पंक्ति जोड़ता हूं तो ऐसा नहीं होता है। क्या कारण हो सकता है, सर?
कोबे-वान केनोबी

@CharlesMenguy एचडीएफ पर फ़ाइल एक द्विआधारी फ़ाइल (पाठ फ़ाइल के विपरीत, जिसमें \r\n, \nरिकॉर्ड ट्रंकेशन का प्रतिनिधित्व करता है) क्या होगा?
CZZ

17

नक्शा कम करें एल्गोरिथ्म फ़ाइल के भौतिक ब्लॉकों पर काम नहीं करता है। यह तार्किक इनपुट विभाजन पर काम करता है। इनपुट विभाजन इस बात पर निर्भर करता है कि रिकॉर्ड कहां लिखा गया था। एक रिकॉर्ड में दो मैपर हो सकते हैं।

जिस तरह से एचडीएफएस की स्थापना की गई है, यह बहुत बड़ी फ़ाइलों को बड़े ब्लॉकों में तोड़ता है (उदाहरण के लिए, 128 एमबी को मापने), और क्लस्टर में विभिन्न नोड्स पर इन ब्लॉकों की तीन प्रतियां संग्रहीत करता है।

HDFS को इन फ़ाइलों की सामग्री के बारे में कोई जानकारी नहीं है। ब्लॉक- ए में रिकॉर्ड शुरू किया जा सकता है, लेकिन उस रिकॉर्ड का अंत ब्लॉक-बी में मौजूद हो सकता है ।

इस समस्या को हल करने के लिए, Hadoop फ़ाइल ब्लॉक में संग्रहीत डेटा के एक तार्किक प्रतिनिधित्व का उपयोग करता है, जिसे इनपुट विभाजन के रूप में जाना जाता है। जब MapReduce जॉब क्लाइंट इनपुट स्प्लिट्स की गणना करता है , तो यह पता लगाता है कि ब्लॉक में पहला पूरा रिकॉर्ड कहां से शुरू होता है और ब्लॉक में आखिरी रिकॉर्ड कहां समाप्त होता है

मुख्य बिंदु:

ऐसे मामलों में जहां एक ब्लॉक में अंतिम रिकॉर्ड अधूरा है, इनपुट विभाजन में अगले ब्लॉक के लिए स्थान की जानकारी और रिकॉर्ड को पूरा करने के लिए आवश्यक डेटा की बाइट ऑफसेट शामिल हैं।

नीचे आरेख पर एक नज़र है।

यहां छवि विवरण दर्ज करें

इस लेख और संबंधित एसई प्रश्न पर एक नज़र डालें : Hadoop / HDFS फ़ाइल विभाजन के बारे में

अधिक विवरण प्रलेखन से पढ़ा जा सकता है

मैप-रिड्यूस फ्रेमवर्क नौकरी के InputFormat पर निर्भर करता है:

  1. नौकरी के इनपुट-विनिर्देश को मान्य करें।
  2. तार्किक InputSplits में इनपुट फ़ाइल को विभाजित करें, जिनमें से प्रत्येक को फिर एक व्यक्तिगत मैपर को सौंपा गया है।
  3. प्रत्येक InputSplit को प्रसंस्करण के लिए एक व्यक्तिगत मैपर को सौंपा जाता है। स्प्लिट टपल हो सकता हैInputSplit[] getSplits(JobConf job,int numSplits) इन बातों का ध्यान रखने के लिए एपीआई है।

FileInputFormat , जो InputFormatलागू getSplits() विधि का विस्तार करता है । इस विधि के इंटर्ल्स पर एक नज़र डालें grepcode पर


7

मैं इसे निम्न रूप में देखता हूं: InputFormat डेटा की प्रकृति को ध्यान में रखते हुए डेटा को तार्किक विभाजन में विभाजित करने के लिए जिम्मेदार है।
ऐसा करने के लिए कुछ भी नहीं रोकता है, हालांकि यह नौकरी के लिए महत्वपूर्ण विलंबता जोड़ सकता है - वांछित विभाजन आकार सीमाओं के आसपास सभी तर्क और पढ़ना नौकरी ट्रैकर में होगा।
सरलतम रिकॉर्ड अवगत इनपुट प्रारूप TextInputFormat है। यह निम्नलिखित के रूप में काम कर रहा है (जहां तक ​​मुझे कोड से समझा गया है) - इनपुट प्रारूप रेखाओं की परवाह किए बिना आकार से विभाजन बनाते हैं, लेकिन लाइनरकार्डऑर्डर हमेशा:
ए) विभाजन में पहली पंक्ति छोड़ें (या इसका हिस्सा), अगर यह नहीं है पहला विभाजन
b) अंत में विभाजन की सीमा के बाद एक पंक्ति पढ़ें (यदि डेटा उपलब्ध है, तो यह अंतिम विभाजन नहीं है)।


Skip first line in the split (or part of it), if it is not the first split- यदि गैर-प्रथम ब्लॉक में पहला रिकॉर्ड पूरा हो गया है, तो यह सुनिश्चित नहीं करें कि यह तर्क कैसे काम करेगा।
प्रवीण श्रीपति

जहाँ तक मुझे कोड दिखाई देता है - प्रत्येक स्प्लिट में यह पढ़ा जाता है कि इसकी अगली पंक्ति क्या है। तो अगर लाइन ब्रेक ब्लॉक सीमा पर नहीं है - यह ठीक है। जब केस टूट जाता है तो ब्लॉक बाउंड पर वास्तव में कैसे संभाला जाता है - यह समझना होगा - मैं कोड को थोड़ा और
पढ़ूंगा

3

मैंने जो समझा है, जब FileSplitपहले ब्लॉक के लिए इनिशियलाइज़ किया जाता है, तो डिफॉल्ट कंस्ट्रक्टर को कहा जाता है। इसलिए शुरू और लंबाई के लिए मूल्य शुरू में शून्य हैं। मुट्ठी ब्लॉक के प्रसंस्करण के अंत तक, यदि अंतिम पंक्ति अधूरी है, तो लंबाई का मूल्य विभाजन की लंबाई से अधिक होगा और यह अगले ब्लॉक की पहली पंक्ति को भी पढ़ेगा। इसके कारण पहले ब्लॉक के लिए शुरुआत का मूल्य शून्य से अधिक होगा और इस शर्त के तहत, LineRecordReaderदूसरे ब्लॉक की मुट्ठी लाइन को छोड़ देगा। ( स्रोत देखें )

यदि पहले ब्लॉक की अंतिम पंक्ति पूरी हो जाती है, तो लंबाई का मूल्य पहले ब्लॉक की लंबाई के बराबर होगा और दूसरे ब्लॉक के लिए शुरुआत का मूल्य शून्य होगा। उस स्थिति में LineRecordReaderपहली पंक्ति को छोड़ना नहीं होगा और दूसरे ब्लॉक को शुरुआत में पढ़ना होगा।

समझ में आता है?


2
इस परिदृश्य में, मैपर्स को एक दूसरे के साथ संवाद करना होता है और किसी विशेष ब्लॉक में अंतिम पंक्ति पूरी नहीं होने पर ब्लॉक को क्रम में संसाधित करना होता है। यकीन नहीं होता अगर यह काम करता है।
प्रवीण श्रीपति

1

LineRecordReader.java के अनूप स्रोत कोड से: मुझे कुछ टिप्पणियां मिलीं:

// If this is not the first split, we always throw away first record
// because we always (except the last split) read one extra line in
// next() method.
if (start != 0) {
  start += in.readLine(new Text(), 0, maxBytesToConsume(start));
}
this.pos = start;

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


0

मैपर को संवाद करने की आवश्यकता नहीं है। फ़ाइल ब्लॉक एचडीएफएस में हैं और वर्तमान मैपर (रिकॉर्डराइडर) उस ब्लॉक को पढ़ सकते हैं जिसमें लाइन का शेष भाग है। यह पर्दे के पीछे होता है।

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