web-dev-qa-db-fra.com

Comment Hadoop traite-t-il les enregistrements répartis à travers les limites de blocs?

Selon le Hadoop - The Definitive Guide

Les enregistrements logiques définis par FileInputFormats ne s’intègrent généralement pas parfaitement dans les blocs HDFS. Par exemple, les enregistrements logiques d’un TextInputFormat sont des lignes qui traversent le plus souvent les limites HDFS. Cela n’a aucune incidence sur le fonctionnement de votre programme (les lignes ne sont pas omises ou brisées, par exemple), mais cela vaut la peine de le savoir, car cela signifie que les cartes locales aux données (c’est-à-dire des cartes qui s'exécutent sur le même hôte que leur données d'entrée) effectuera des lectures à distance. La légère surcharge que cela cause n'est normalement pas significative.

Supposons qu'une ligne d'enregistrement soit divisée en deux blocs (b1 et b2). Le mappeur qui traite le premier bloc (b1) remarquera que la dernière ligne n'a pas de séparateur EOL et extrait le reste de la ligne du bloc de données suivant (b2).

Comment le mappeur qui traite le deuxième bloc (b2) détermine-t-il que le premier enregistrement est incomplet et doit-il traiter à partir du deuxième enregistrement du bloc (b2)?

116
Praveen Sripati

Question intéressante, j'ai passé quelque temps à regarder le code pour les détails et voici ce que je pense. Les fractionnements sont gérés par le client par InputFormat.getSplits. Un examen de FileInputFormat donne donc les informations suivantes:

  • Pour chaque fichier d'entrée, obtenez la longueur du fichier, la taille du bloc et calculez la taille du partage sous la forme max(minSize, min(maxSize, blockSize))maxSize correspond à mapred.max.split.size Et minSize est mapred.min.split.size.
  • Divisez le fichier en différents FileSplits en fonction de la taille de division calculée ci-dessus. Ce qui est important ici est que chaque FileSplit est initialisé avec un paramètre start correspondant au décalage dans le fichier d’entrée. Il n'y a toujours pas de traitement des lignes à ce stade. La partie pertinente du code ressemble à ceci:

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

Après cela, si vous regardez le LineRecordReader qui est défini par le TextInputFormat, c'est là que les lignes sont gérées:

  • Lorsque vous initialisez votre LineRecordReader, il tente d’instancier un LineReader qui est une abstraction pour pouvoir lire des lignes sur FSDataInputStream. Il y a 2 cas:
  • Si un CompressionCodec est défini, ce codec est responsable de la gestion des limites. Probablement pas pertinent pour votre question.
  • S'il n'y a pas de codec cependant, c'est là que les choses sont intéressantes: si le start de votre InputSplit est différent de 0, alors vous retournez un caractère puis sautez la première ligne rencontre identifiée par\n ou\r\n (Windows)! Le retour en arrière est important car si les limites de votre ligne sont identiques à celles des divisions, vous évitez de sauter la ligne valide. Voici le code pertinent:

    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;
    

Ainsi, puisque les divisions sont calculées dans le client, les mappeurs n'ont pas besoin de s'exécuter en séquence, chaque mappeur sait déjà s'il doit ou non supprimer la première ligne.

Donc, fondamentalement, si vous avez 2 lignes de chaque 100 Mo dans le même fichier, et pour simplifier, supposons que la taille de la scission soit de 64 Mo. Ensuite, lorsque les fractionnements en entrée sont calculés, nous aurons le scénario suivant:

  • Fractionner 1 contenant le chemin et les hôtes de ce bloc. Initialisé au début 200-200 = 0 Mo, longueur 64 Mo.
  • Fractionné 2 initialisé au début 200-200 + 64 = 64 Mo, longueur 64 Mo.
  • Fractionné 3 initialisé au début 200-200 + 128 = 128 Mo, longueur 64 Mo.
  • Split 4 initialisé au début 200-200 + 192 = 192Mb, longueur 8Mb.
  • Le mappeur A traitera le groupe 1, le début vaut 0, donc ne sautez pas la première ligne et lira une ligne complète qui dépasse la limite de 64 Mo, nécessite une lecture à distance.
  • Le mappeur B traitera le groupe 2, le début est! = 0, sautez donc la première ligne après 64 Mo-1 octet, ce qui correspond à la fin de la ligne 1 à 100 Mo qui est toujours dans le groupe 2, nous avons 28 Mo de la ligne dans le groupe 2, donc à distance lire les 72 Mo restants.
  • Le mappeur C traitera le groupe 3, le début est! = 0, donc sautez la première ligne après 128 Mo/1 octet, ce qui correspond à la fin de la ligne 2 à 200 Mo, ce qui correspond à la fin du fichier. Ne faites rien.
  • Le mappeur D est identique au mappeur C, sauf qu'il recherche une nouvelle ligne après 192 Mo-1 octet.
155
Charles Menguy

L'algorithme Map Reduce ne fonctionne pas sur les blocs physiques du fichier. Cela fonctionne sur les fractionnements d'entrées logiques. Le fractionnement en entrée dépend de l'endroit où l'enregistrement a été écrit. Un enregistrement peut s'étendre sur deux mappeurs.

La manière [~ # ~] hdfs [~ # ~] a été configurée, elle décompose les très gros fichiers en gros blocs (par exemple, la mesure 128 Mo) et stocke trois copies de ces blocs sur différents nœuds du cluster.

HDFS n'a aucune connaissance du contenu de ces fichiers. Un enregistrement peut avoir été démarré dans Block-a mais une fin de cet enregistrement peut être présente dans Block-b.

Pour résoudre ce problème, Hadoop utilise une représentation logique des données stockées dans des blocs de fichiers, appelée fractionnements en entrée. Lorsqu'un client du travail MapReduce calcule les fractionnements d'entrée , il détermine où se trouve le premier enregistrement complet un bloc commence et où se termine le dernier enregistrement du bloc.

Le point clé:

Dans les cas où le dernier enregistrement d'un bloc est incomplet, le fractionnement en entrée inclut les informations d'emplacement pour le bloc suivant et le décalage d'octet des données nécessaires pour terminer l'enregistrement.

Regardez le diagramme ci-dessous.

enter image description here

Jetez un coup d'œil à ceci article et à la question SE correspondante: À propos du fractionnement de fichiers Hadoop/HDFS

Plus de détails peuvent être lus de documentation

La structure Map-Reduce repose sur le format d’entrée du travail pour:

  1. Validez la spécification d'entrée du travail.
  2. Divisez le ou les fichiers d'entrée en InputSplits logiques, qui sont ensuite affectés à un mappeur individuel.
  3. Chaque InputSplit est ensuite assigné à un mappeur individuel pour traitement. Split pourrait être Tuple. InputSplit[] getSplits(JobConf job,int numSplits) est l’API pour s’occuper de ces choses.

FileInputFormat , qui étend la méthode InputFormat implémentée getSplits (). Jetez un coup d'oeil aux éléments internes de cette méthode à grepcode

16
Ravindra babu

Je le vois comme suit: InputFormat est responsable de la division des données en divisions logiques en tenant compte de la nature des données.
Rien ne l’empêche de le faire, bien que cela puisse ajouter une latence importante au travail - toute la logique et la lecture autour des limites de taille de division souhaitées se produiront dans le gestionnaire de tâches.
Le format d'entrée le plus simple compatible avec l'enregistrement est TextInputFormat. Cela fonctionne comme suit (d'après ce que j'ai compris du code) - le format d'entrée crée des fractionnements par taille, quelles que soient les lignes, mais LineRecordReader toujours:
a) Omettre la première ligne de la division (ou une partie de celle-ci), si ce n'est pas la première division
b) Lire une ligne après la limite de la division à la fin (si les données sont disponibles, il ne s’agit donc pas de la dernière division).

7
David Gruzman

D'après ce que j'ai compris, lorsque le FileSplit est initialisé pour le premier bloc, le constructeur par défaut est appelé. Par conséquent, les valeurs de début et de longueur sont zéro initialement. À la fin du traitement du premier bloc, si la dernière ligne est incomplète, la valeur de la longueur sera supérieure à la longueur de la division et la première ligne du bloc suivant sera également lue. Pour cette raison, la valeur de départ pour le premier bloc sera supérieure à zéro et, dans cette condition, le LineRecordReader ignorera la première ligne du deuxième bloc. (Voir source )

Si la dernière ligne du premier bloc est terminée, la valeur de la longueur sera égale à la longueur du premier bloc et la valeur du début du deuxième bloc sera égale à zéro. Dans ce cas, le LineRecordReader ne sautera pas la première ligne et lira le deuxième bloc au début.

Logique?

3
aa8y

De hadoop code source de LineRecordReader.Java le constructeur: je trouve quelques commentaires:

// 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;

je pense que hadoop lira une ligne supplémentaire pour chaque division (à la fin de la division actuelle, lira la ligne suivante dans la division suivante), et si ce n’est pas la première division, la première ligne sera jetée. de sorte qu'aucun enregistrement de ligne ne sera perdu et incomplet

1
Shenghai.Geng

Les mappeurs ne doivent pas communiquer. Les blocs de fichiers sont en HDFS et le mappeur actuel (RecordReader) peut lire le bloc contenant la partie restante de la ligne. Cela se passe dans les coulisses.

0
user3507308