Question intéressante, j'ai passé un peu de temps à regarder le code pour plus de détails et voici mes pensées. Les divisions sont gérés par le client, en InputFormat.getSplits
, donc un coup d'oeil à FileInputFormat donne les informations suivantes:
- Pour chaque fichier d'entrée, d'obtenir la longueur du fichier, la taille du bloc et de calculer la taille du segment en tant que
max(minSize, min(maxSize, blockSize))
où maxSize
correspond mapred.max.split.size
et minSize
est mapred.min.split.size
.
-
Diviser le fichier en différents FileSplit
s, basée sur la répartition de la taille calculée ci-dessus. Ce qui est important ici, c'est que chaque FileSplit
est initialisé avec un start
paramètre correspondant à l'offset dans le fichier d'entrée. Il n'y a pas encore de manutention des lignes à ce point. 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 l' LineRecordReader
qui est défini par l' TextInputFormat
, c'est l'endroit où les lignes sont traitées:
- Lorsque vous initialisez votre
LineRecordReader
il tente d'instancier un LineReader
qui est une abstraction pour être en mesure de lire les lignes de plus de FSDataInputStream
. Il y a 2 cas:
- Si il y a un
CompressionCodec
défini, ce codec est responsable de la gestion des frontières. Probablement pas pertinentes à votre question.
-
Si il n'y a pas de codec cependant, c'est là que les choses sont intéressantes: si l' start
de votre InputSplit
est différent de 0, alors vous backtrack 1 personnage et ensuite ignorer la première ligne que vous rencontrez identifiés par \n ou \r\n (Windows) ! Le backtrack est important parce que, dans le cas où votre ligne limites sont les mêmes que split limites, cela garantit que vous ne sautez pas de la validité de la ligne. Voici le code correspondant:
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;
Donc, depuis que les divisions sont calculés dans le client, les utilisateurs n'ont pas besoin de s'exécutent en séquence, chaque mappeur sait déjà si il dne de jeter la première ligne ou pas.
Donc, fondamentalement, si vous avez 2 lignes de chaque 100 mo dans le même fichier, et pour simplifier disons que la scission de la taille est de 64 mo. Ensuite, lorsque l'entrée splits sont calculés, nous aurons le scénario suivant:
- Split 1 contenant le chemin d'accès et les hôtes de ce bloc. Initialisé au début 200-200=0Mb, longueur de 64 mo.
- Split 2 initialisé au début 200-200+64=64 mo, longueur de 64 mo.
- Split 3 initialisé au début 200-200+128=128 mo, la longueur de 64 mo.
- Split 4 initialisé au début 200-200+192=avec 192mo, longueur de 8 mo.
- Mapper Une volonté processus de division 1, de départ est 0 afin de ne pas sauter de première ligne, et de lire une ligne complète qui va au-delà de la limite de 64 mo, donc des besoins de lecture à distance.
- Mappeur B processus de split 2, le début est != 0 donc ignorer la première ligne après 64 mo-1 octet, ce qui correspond à la fin de la ligne 1 à 100 mo, qui est toujours en split 2, nous avons 28Mb de la ligne à split 2, donc à distance de lire le reste 72Mb.
- Mappeur C processus de split 3, le début est != 0 donc ignorer la première ligne après 128 mo-1 octet, ce qui correspond à la fin de la ligne 2 à 200 mo, ce qui est en fin de fichier, de façon à ne pas faire n'importe quoi.
- Mappeur D est la même que mappeur C sauf qu'il regarde pour un retour à la ligne après avec 192mo-1byte.