Apache Hadoop

hadoop

Konečně jsem se dostal k paralelizaci! Posledních několik dnů pracuji s Apache Hadoop. Musím říct, že mě to baví mnohem víc než nějaké řešení bugů apod. A konečně se začínám přibližovat k cíli…

Instalace virtuálního Apache Hadoop clusteru

Tento týden jsem se zabýval převážně instalací virtuálního Apache Hadoop clusteru. Jako virtualizační platformu jsem použil VirtualBox a Debian Linux. Zprovoznění clustru není zas tak složité, ale narazil jsem na jeden velice nepříjemný bug. Týká se verze XML parseru Xerces. Hadoop vyžaduje k běhu určitou verzi této knihovny, ale v JRE se nachází jiná verze. A Hadoop si není schopen říci o tu správnou ani v případě, že je přiložena do .jar souboru. Nepodařilo se mi najít nějaké „správné“ řešení, a tak jsem to vyřešil workaroundem – na začátku main metody volám

System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
 "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");

.

Map/Reduce

Apache Hadoop je dělaný pro řešení problémů pomocí algoritmu Map/Reduce. Nejprve se přivede vstup do Mapperu, který jako vstup očekává kolekci hodnot key-value. V tomto Mapperu se provedou nějaké operace (například transformace, agregace hodnot apod.) a výstupem je opět kolekce key-value. Dále se provede seskupení do kolekce key-(value-list) a ta se přivede na vstup Reduceru. Reducer jí následně zpracuje a výstupem je hodnota kolekce hodnot value. Moc pěkně zpracovaný příklad je na Slideshare. Velice dobře zpracovaný je i tutorial na Yahoo, který se zabývá i pokročilejšími věcmi v Hadoopu.

Když používáte Hadoop na něco, na co není určený

Bohužel problém, který řeším, se pro Hadoop vůbec nehodí. Já potřebuji v ideálním případě nějakou sdílenou paměť a v ní frontu úloh, do které budou jednotlivé nody přistupovat. To je ovšem zcela proti logice Hadoopu – ten naopak nepředpokládá žádnou sdílenou paměť. Očekává, že nody budou pracovat se soubory, které se nacházejí na distribuovaném systému souborů HDFS. Hadoop taky předpokládá, že jednotlivé úlohy budou náročné, ale nejen časově, ale také množstvím zpracovávaných dat. Hadoop je vhodný, pokud potřebujete zpracovávat velké množství dat, řádově desítky terabytů a více. Naopak se naprosto nehodí na úlohy, které potřebujete zpracovat rychle. Pokud vám tedy jde o množství zpracovaných dat, je Hadoop správnou volbou. Pokud potřebujete co nejrychleji výsledek, měli byste se poohlédnout po nějakém jiném nástroji.

Map/Reduce očekává vstup a to v podobě souborů. Já vlastně žádný vstup nemám, mám pouze parametr, se kterým potřebuji úlohu spustit. Ano, onen parametr by se dal nazvat vstupem, ale jak ho Mapperu předat? Navíc nepotřebuji žádný Reducer, stačí mi pouze Mapper.

Pokud chcete na Hadoopu spouštět úlohy typu Map/Reduce, naleznete příkladů a tutoriálů plný internet. Pokud chcete něco netypického, nenaleznete bohužel nic 🙁 Vůbec nic… Všechno co jsem o Hadoopu našel se týkalo Map/Reduce a nikdo nikde nikdy neřešil úlohu, která by tomuto paradigmatu neodpovídala. Bohužel jediná dokumentace, kterou Hadoop nabízí, je JavaDoc + jednoduchý tutoriál, jak vyřešit typický problém, kde si vystačíte na 90% s nabízenými výchozími třídami. Abych tedy vyřešil svůj problém, s úlohou, která vůbec neodpovídá paradigmatu Map/Reduce, musel jsem jí přiohnout, aby tak alespoň vzdáleně vypadala. Musel jsem dělat různé psí kusy, aby se mi to povedlo. Připadal jsem si jako krotitel lvů nutící slona skákat skrz obruče.

Krkolomné – ale funkční řešení

Ale nakonec se mi to podařilo! Po několikadenním úsilí mi konečně na Hadoopu běží i mé atypické úlohy! Abych spustil jednu úlohu, s jedním jednoduchý parametrem, musel jsem napsat 4 třídy. InputSplit, InputFormat a RecordReader a samozřejmě samotný Mapper. Většina těch tříd, kromě mapperu, nemá žádnou užitkovou hodnotu, jedná se pouze o balast nutný pro donucení Hadoopu spustit onu úlohu, která neodpovídá paradigmatu Map/Reduce. Je to smutné, ale je to tak 🙁

Mé řešení funguje vpodstatě tak, že vygeneruje parametry pro všechny úlohy, vytvoří pro ně instanci třídy InputSplit a následně všechny instance vrátí jako kolekci. Toto dělá třída InputFormat. Následně se nějakým způsobem něco s těmito instancemi InputSplit udělá, a končí to tím, že se na ně zavolají metody write a read – přesně v tomto pořadí. Tyto metody asi zapíší data do HDFS nebo kdoví co, ale něco na ten způsob to bude. Následně si třída RecordReader převezme instanci třídy InputSplit a vytvoří z ní kolekci key-value párů. Jelikož potřebuji pouze 1 parametr, používám pouze hodnoty key. Následně si Hadoop sám volá čtení z RecordReaderu, a předává hodnoty Key do Mapperu, který je dále zpracovává. Byla to fuška, spoustu debugování, psaní kódu stylem pokus-omyl, ale funguje to!

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.