MapReduce-példaprogramok
Egyszerű MapReduce-példaként írhatunk olyan feladatot/programot, amely megszámolja, hogy az egyes szavak hányszor fordulnak elő egy fájlban vagy fájlkészletben. Ha például feltételezzük, hogy két fájl (A és B) egyaránt tartalmazza a „This is a cloud computing course” és a „This is learning path 4 in the cloud computing course” kifejezéseket, akkor az alábbi táblázatban látható kimenetet várhatjuk. Az ilyen programokat általában WordCountnak nevezzük. A WordCount-program a Hadoop-terjesztés része, továbbá futtatáshoz és teszteléshez is elérhető. A szövegfeldolgozási algoritmusok széles spektrumának felel meg a big data típusú adatokat használó alkalmazásokban. Éppen ezért sokan a Hadoop hatékonyságának értékelésére használható, korszerű teljesítménytesztnek tartják. Az eredeti MapReduce-cikk1 a WordCountot használta teljesítménytesztként.
Az alábbi táblázatban két, a „This is a cloud computing course” és a „This is learning path 4 in the cloud computing course” kifejezéseket egyaránt tartalmazó fájlban az egyes szavak előfordulási gyakorisága látható, ahogy azt a WordCount teljesítményteszt-program feltételezhetően előállította:
Word | Gróf | Word | Gróf | Word | Gróf | ||
---|---|---|---|---|---|---|---|
This | 2 | computing | 2 | 4 | 1 | ||
is | 2 | course | 2 | in | 1 | ||
a | 1 | tanulás | 1 | The | 1 | ||
cloud | 2 | ösvény | 1 |
A MapReduce-programokat Java programozási nyelvben lehet megírni. A WordCount-programok (illetve általában MapReduce-programok) írását általában a leképezési és a csökkentési függvények bemeneti és kimeneti formátumainak definiálásával kezdjük. Mivel kulcs-érték párokkal foglalkozunk, csak azokat a tényleges kulcsokat és értékeket kell megadnunk, amelyeket a bemeneti és kimeneti fájlok tartalmazni fognak, valamint ezek típusait (például sztring vagy numerikus). A feladat bemenetének (a bemeneti adathalmaznak) egy vagy egymillió fájl esetében is mindig azonos formátumúnak kell lennie. Hasonlóképpen, a feladat kimeneti fájljainak is mindig azonos formátumúnak kell lenniük, amely eltérhet a bemeneti formátumtól.
A bemeneti és kimeneti formátumok tetszőleges soralapú naplók, képek, videók, többsoros rekordok vagy egészen mások is lehetnek. A Hadoop bármilyen fájlformátumot fel tud dolgozni, a sima szöveges formátumtól kezdve, a binárison keresztül, a strukturált adatbázisokig. Ennek rendezéséhez a felhasználók felülbírálhatják a Hadoop InputFormat
(alapértelmezés szerint TextInputFormat
) és OutputFormat
(alapértelmezés szerint TextOutputFormat
) osztályait. Az előbbi azt határozza meg, hogy a bemeneti fájlok hogyan lesznek beolvasva kulcs-érték párokként, és létrehozza a leképezési feladatoknak megfelelő bemeneti felosztási egységeket. A Hadoop-motor az így létrehozott bemeneti felosztási egységenként egy-egy leképezési feladatot állít elő. Az OutputFormat
osztály hasonlóképpen azt határozza meg, hogyan írják a csökkentési feladatok a kulcs-érték párokat kimeneti fájlokként a HDFS-re. A következő videó részletesen bemutatja, hogy a Hadoop MapReduce hogyan használja az InputFormat
és az OutputFormat
osztályokat.
Az alapértelmezett I/O-alosztályok alkalmasak a szövegfeldolgozásra. A TextInputFormat
lehetővé teszi a szövegfájlok olvasását, ahol a sorok bájtban megadott eltolása a kulcs, a sor tényleges tartalma pedig az érték. A TextOutputFormat
lehetővé teszi a fájlok kulcs-érték3 szövegpárokként való írását. A Hadoop tartalmaz további formázási osztályokat (például SequenceFileInputFormat
és SequenceFileOutputFormat
) is, amelyek lehetővé teszik a bináris fájlok olvasását és írását. Emellett a Hadoop-felhasználók implementálhatnak a bemeneti adathalmazaikra szabott egyéni bemeneti és kimeneti formázási osztályokat is. A teendőkről további információt White "Hadoop: The Definitive Guide" (Hadoop: The Definitive Guide) című témakörében talál.3
Az itt látható WordCount-példa azt feltételezi, hogy a bemenetek szöveges fájlok. Így közvetlenül használhatjuk a TextInputFormat
osztályt, ahol a kulcs a sorok bájtban megadott eltolása, az érték pedig maga a sor tartalma. Továbbá közvetlenül használhatjuk a TextOutputFormat
osztályt is, ahol a kulcs a bemeneti adathalmazban előforduló szó, az érték pedig a szó gyakorisága. A kulcs típusa beállítható Java Long
formátumra (LongWritable
a Hadoopban), az érték típusa szintén beállítható Java String
formátumra (Text
a Hadoopban). A csökkentési függvénynek a leképezési feladatokból származó szavakat kell megkapnia kulcsként, illetve szavanként az 1 számot értékekként,4 így a kulcs típusa a szavak típusa (Text
), az érték típusa pedig az egységet jelölő szám típusa (Java Integer
, IntWritable
a Hadoopban) lesz. Ezek után már csak a leképezési és a csökkentési függvények logikáját kell megérteni. A leképezési függvény esetében a bemeneti felosztási egységeket elemezni kell, amelynek kimenetében minden szóhoz 1-es darabszám tartozik. A csökkentési függvény esetében minden egyes fogadott szó egyszerűen, változatlan formában jeleníthető meg a kimenetben, az adott szám esetében kapott 1-esek összegzésével számított gyakorisággal együtt.5
Az alábbi kódrészlet bemutatja a Hadoop 0.20.0 új Java MapReduce API-jának teljes WordCount-példakódját.
import java.io.IOException;
import java.util.*;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.conf.*;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapreduce.*;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
public class WordCount {
public static class WCMap extends Mapper<LongWritable, Text, Text,
IntWritable> {
private final static IntWritable one = new
IntWritable(1);
private Text word = new Text();
public void map(LongWritable key, Text value, Context context) throws
IOException, InterruptedException {
String line = value.toString();
StringTokenizer tokenizer = new StringTokenizer(line);
while (tokenizer.hasMoreTokens()) {
word.set(tokenizer.nextToken());
context.write(word, one);
}
}
}
public static class WCReduce extends Reducer<Text, IntWritable, Text,
IntWritable> {
public void reduce(Text key, Iterable<IntWritable> values, Context
context)
throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
context.write(key, new IntWritable(sum));
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = new Job(conf, "wordcount");
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
job.setMapperClass(WCMap.class);
job.setReducerClass(WCReduce.class);
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
FileInputFormat.addInputPath(job, new Path(args[1]));
FileOutputFormat.setOutputPath(job, new Path(args[2]));
job.waitForCompletion(true);
}
}
A kódrészlet alapján látható, hogy a programozóknak csupán két szekvenciális függvényt kell létrehozniuk (a leképezési és a csökkentési függvényt), amelyek két (belső) osztályban találhatók: ez esetben a WCMap
és a WCReduce
belső osztályokban. A WCMap
belső osztály a Mapper
osztály kiterjesztése, és felülbírálja annak map()
függvényét. A Mapper
osztály adott bemeneti kulcs-érték pártípusokat (LongWritable
és Text
) képez le a kimeneti kulcs-érték pártípusokra (Text
és IntWritable
). A Mapper
osztályban meghatározott kimeneti kulcs-érték pártípusoknak mindig meg kell egyezniük a Reducer
osztály bemeneti kulcs-érték pártípusaival. A WCReduce
belső osztály a Reducer
osztály kiterjesztése, és felülbírálja annak reduce()
függvényét. A bemeneti kulcs-érték pártípusok meghatározásán kívül a Reducer
osztály meghatározza a kimeneti kulcs-érték pártípusokat (Text
és IntWritable
) is, amelyet a csökkentési feladatok a végeredmény előállításához használnak.
A WCMap
és a WCReduce
belső osztályok map()
és reduce()
függvényei tartalmazzák a WordCount-program tényleges logikáját. A Context
paraméter mindkét függvényben I/O-írásokat végez a helyi lemezekre és a HDFS-re. A main()
függvény egy olyan feladatot állít be, amely végrehajtja a WordCount-programot egy bemeneti fájlkészleten az addInputPath()
függvény használatával. Azt is megadja, hogy hová kerüljenek a kimeneti fájlok a HDFS-en a setOutputPath()
függvény használatával. A main()
függvényben setOutputKeyClass()
és setOutputValueClass()
adja meg azokat a kulcs-érték pártípusokat, amelyeket a csökkentési feladatok kimenetében megtalálhatók, és alapértelmezés szerint feltételezi, hogy ezek a típusok megegyeznek a leképezési feladat kimeneti kulcs-érték típusaival. Ellenkező esetben a main()
függvénynek a setMapOutputKeyClass()
és a setMapOutputValueClass()
függvényeket is meg kell hívnia a leképezési feladat kimeneti kulcs-érték típusainak megadásához. A bemeneti és kimeneti formátumok beállításához a rendszer a setInputFormatClass()
és a setOutputFormatClass()
függvényeket hívja meg. Végül a setMapperClass()
és setReducerClass()
függvények használatával állíthatja be a feladat belső leképezési és csökkentési osztályait (WCMap
és WCReduce
). Az alábbi videó a Sortot ismerteti, amely egy másik klasszikus példa a MapReduce-ra.
A következő videó a Sobelt mutatja be, amely a képfeldolgozásra és az élészlelésre szolgál példaként.
3 Vegye figyelembe, hogy a kulcs és az érték között tabulátorhely (nem egyszerű szóköz) van.
4 A W szóhoz tartozó 1-es szám, mint kimenet azt jelzi, hogy W megjelenik egy bemeneti felosztási egységben. Ez lehetővé teszi a W szót fogadó csökkentési feladat számára, hogy egyszerűen növelje a W szóhoz tartozó számláló értékét.
5 Minden egyes csökkentési feladat több szó fogadására képes több leképezési feladatból, de az egyes szavak csak egy-egy csökkentési feladatnál jelennek meg.
Hivatkozások
- J. Dean and S. Ghemawat (Dec. 2004). MapReduce: Egyszerűsített adatfeldolgozás nagy fürtökön OSDI
- M. Zaharia, A. Konwinski, A. Joseph, R. Katz, and I. Stoica (2008). A MapReduce teljesítményének javítása heterogén környezetekben OSDI
- T. White (2011). Hadoop: The Definitive Guide 2nd Edition O'Reilly