czwartek, 6 stycznia 2011

Mountable HDFS

Od jakiegoś czasu testuje dystrybucje Hadoopa dostarczaną przez firmę Cloudera. Posiada ona kilka fajnych udogodnień, takich jak możliwość instalacji za pomocą apt-get, łatwe zarządzanie konfiguracją, nowe bardziej estetyczne GUI wraz z obsługą Hive, oraz interfejs FUSE dla HDFS'a, który umożliwia zamontowanie go jako lokalny system plików. Dzięki temu możemy używać ulubionych narzędzi np. gnome-commandera, oraz pisać programy operujące na rozproszonym systemie plików w identyczny sposób jak na tradycyjnym systemie.
Jeśli do tej pory nie instalowaliśmy żadnych pakietów z dystrybucji Cloudery musimy dodać odpowiednie repozytoria. Tworzymy plik
/etc/apt/sources.list.d/cloudera.list
dodajemy:
deb http://archive.cloudera.com/debian lucid-cdh3 contrib
deb-src http://archive.cloudera.com/debian lucid-cdh3 contrib
 

Instalujemy Hadoop FUSE:
sudo apt-get install hadoop-0.20-fuse
Tworzymy katalog do zamontowania:
mkdir -p hdfs
Montujemy HDFS'a:
hadoop-fuse-dfs dfs://<name_node_hostname>:<namenode_port> hdfs -d
Jeśli wszystko działa jak należy, dodajemy wpisy w pliku /etc/fstab 
hadoop-fuse-dfs dfs://<name_node_hostname>:<namenode_port> hdfs fuse 
allow_other,usetrash,rw 2 0
 
Jeśli używamy Hadoopa od Cloudery nic więcej nie musimy konfigurować, w przeciwnym wypadku w pliku  
/usr/bin/hadoop-fuse-dfs
ustawiamy HADOOP_HOME.
 

wtorek, 28 września 2010

Hadoop instalacja, część II.

Do tej pory pracowaliśmy na Hadoopie w wersie Pseudo-Distributed, czyli nie rozproszonej. W tym poście postaram się opisać konfiguracje klastra złożonego z dwóch maszyn:
  1. Master (ip 192.xxx.xx.101) - będzie pełnił rolę NameNode, oraz JobTrackera. Jako że nasz klaster składa się zaledwie z dwóch maszyn, uruchomimy na nim DataNode oraz TaskTracker.
  2. Slave (ip 192.xxx.xx.102) - uruchomimy na nim DataNode oraz TaskNode.
Dla zapewnienia bezpieczeństwa oraz ułatwienia konfiguracji na obu maszynach, dodajemy grupę oraz użytkownika:
sudo addgroup hadoop
sudo adduser --ingroup hadoop hadoop
.

Logujemy się na nowo stworzonego użytkownika. Instalujemy oraz testujemy Hadoopa na obu maszynach, tak jak to pisałem w części pierwszej tego artykułu.

Możemy dla wygody ustawić na obydwóch maszynach:
etc/hosts
192.xxx.xx.101 master
192.xxx.xx.102 slave

Ponieważ Hadoop do komunikacji między Nodami używa ssh do poprawnego działania, użytkownik zalogowany na maszynę Master musi mieć możliwość zalogowania się przez ssh bez podawania hasła na maszyne Slave. W tym celu kopiujemy klucz z Mastera z katalogu <HOME>/.ssh/nazwa_klucza.pub do <HOME>/.ssh/authorized_keys na Slavie.

Przechodzimy do konfiguracji Hadoopa.
Najpierw konfigurujemy maszynę Master:
W pliku conf/masters dodajemy namiar na maszynę gdzie uruchomiony będzie NameNode oraz JobTracker:
master

Wpliku conf/slaves ustawiamy maszyny gdzie znajdują się DataNode oraz TaskNode:
master
slave

Na wszystkich maszynach modyfikujemy konfiguracje w następujący sposób:
W pliku conf/core-site.xml:
<property>
<name>fs.default.name</name>
<value>hdfs://master:9000</value>
</property>

W pliku conf/mapred-site.xml:
<property>
<name>mapred.job.tracker</name>
<value>master:9001</value>
</property> 
W pliku conf/hdfs-site.xml:
<property>
<name>dfs.replication</name>
<value>2</value>
</property>
Warto ustawić dodatkowe parametry, choć są one opcjonalne:
W pliku conf/mapred-site.xml:
mapred.local.dir - lista lokalnych katalogow dla tymczasowych danych - jeśli na maszynie mamy kilka dysków możemy je tutaj podać poprawi to czas operacji I/O.
mapred.map.tasks - maksymalna ilosc maperów - dokumentacja mówi, że najlepszym ustawieniem jest 10x ilość maszyn Slave.
mapred.reduce.tasks - maksymalna liczba reducerów - dokumentacja mówi, że najlepszym ustawieniem jest  2x ilość rdzeni na wszystkich maszynach Slave.

Uruchamiamy klaster:
Najpierw formatujemy NameNode:
bin/hadoop namenode -format
Uruchamiamy HDFS:
bin/start-dfs.sh

Uruchamiamy MapReduce:
bin/start-mapred.sh

Jeśli wszystko pójdzie po naszej myśli, na maszynie Master oraz Slave w katalogu <HADOOP_HOME>/logs pojawią się logi mówiące o poprawnym uruchomieniu poszczególnych elementów klastra.

środa, 1 września 2010

Pig, nie taki znowu leniwy zwierzak...


Tak wiem, tym razem powinienem opisać konfiguracje klastra Hadoop, ale nie mogłem się oprzeć pokusie zabawy dzieckiem korporacji Yahoo. Pig, bo o nim mowa, to platforma oraz język wysokiego poziomu do pisania Jobów MapReduce. Wyczytałem na jednej z marketingowych prezentacji, że używając Piga wystarczy 5% kodu, oraz 5% czasu, który musielibyśmy poświęcić, aby osiągnąć ten sam efekt w Javie. Brzmi nieźle.

Tradycyjnie zaczynamy od instalacji.

Pig pobieramy z tego adresu.
Rozpakowujemy paczkę. 
Sprawdzamy czy działa poleceniem:
bin/pig -help

Pig może działać w dwóch trybach:
1. Lokalnym - nie potrzebuje uruchomionego Hadoopa
bin/pig -x local
2. MapReduce - używa uruchomionego klastra:
W pliku <pig_home>/bin/pig.sh dodajemy zmienne środowiskowe:
PIG_PATH=<pig_home>
PIG_CLASSPATH=$PIG_PATH/pig-0.7.0-core.jar:<hadoop_home>/conf
PIG_HADOOP_VERSION=0.20.2
Uruchamiamy poleceniem:
bin/pig
lub
bin/pig -x mapreduce

To co właśnie widzimy, to konsola, grunt umożliwiająca pisanie skryptów. Kolejną możliwością jest uruchamianie skryptów z pliku:
bin/pig skrypt.pig
Możemy również wywołać skrypt z kodu Javowego.

Spróbujmy napisać skrypt, który zrobi dokładnie to samo, co testowy Job MapReduce, czyli zliczy wystąpienie poszczególnych słów w pliku.
Uruchamiamy konsole grunt:
bin/pig
Załadujemy dane poleceniem:
fs -copyFromLocal conf pig_test_data
Tak wygląda skrypt:
rawData = load 'pig_test_data';
words = foreach rawData generate flatten(TOKENIZE($0)) as word;
filteredWords = filter words by word matches '\\w+';
groupedWords = group filteredWords by word;
countedWords = foreach groupedWords generate COUNT(filteredWords), group;
orderedCountedWords = order countedWords by $0 desc;
store orderedCountedWords into 'pig_test_data_output/char_freq';

Wyniki sprawdzamy poleceniem:
fs -cat pig_test_data_output/char_freq/part-r-00000
Po szczegóły dotyczące składni języka Pig odysłam do naprawdę niezłej dokumentacji.

poniedziałek, 30 sierpnia 2010

Hadoop instalacja, część I.

Platformę Hadoop możemy instalować w trzech trybach:
1. Standalone - nie rozproszona, wszystko działa w jednym procesie Javowym, użyteczna podczas debugowanie Jobów. Brak HDSF-a.
2. Pseudo-Distributed - również nie rozproszona, ale każdy z elementów działa w osobnym wątku,  wszystkie elementy są uruchomione.
3. Fully-Distributed - czyli pełnoprawny klaster.


Dziś postaram się omówić dwa pierwsze tryby.


Aby zainstalować Hadoopa potrzebne nam będą:
1. System operacyjny z rodziny Linux - ja użyłem Ubuntu 9.10 (można uruchomić Hadoopa  pod systemem Windows ale tylko jako wersję deweloperską).
2. Zainstalowana Java 6.
3. Skonfigurowane ssh.
4. Hadoop - ja używam wersji 20.02


Ok, a więc zaczynamy!
Postów na temat jak instalować Ubuntu powstało już naprawdę dużo, dlatego przejdźmy od razu do instalowania Javy, aby to zrobić wykonujemy polecenie:
sudo apt-get install sun-java6-jre sun-java6-plugin sun-java6-fonts


Sprawdzamy czy mamy najnowsze pakiety dla ssh:
sudo apt-get install ssh
sudo apt-get install rsync


Uruchamiamy ssh:
ssh localhost


Jeśli musimy się uwierzytelniać podając hasło, dobrze jest dodać RSA dla naszego użytkownika:
Tworzymy klucz poleceniem:
ssh-keygen -t rsa -P ""
Nastepnie kopiujemy go do odpowiedniego katalogu używając polecenia:
cat /scieżka gdzie zapisalimy klucz/.ssh/nazwa_klucza.pub >> /katalog domowy/.ssh/authorized_keys
Potrzebny będzie restart!
Środowisko mamy gotowe, czas na Hadoopa.
Rozpakowujemy paczkę, w pliku conf/hadoop-env.sh ustawiamy JAVA_HOME. Hadoop w wersji Standalone powinień działać, aby się upewnić uruchamiamy przykład, który zliczy nam słowa z wszystkich plików znajdujących sie w katalogu input:
bin/hadoop jar hadoop-*-examples.jar grep input output '\w+'
Jeśli wszystko poszło dobrze w katalogu output znajdziemy plik z wynikami.


Czas na Pseudo-Distributed.
W pliku conf/core-site.xml podajemy adres węzła HDFS:
<configuration>
<property>
<name>fs.default.name</name>
<value>hdfs://localhost:9000</value>
</property>
</configuration>


Skoro mamy tylko jeden węzeł Hadoopa w pliku conf/hdfs-site.xml ilość replikacji ustawiamy na jeden:
<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
</configuration>


W pliku conf/mapred.site.xml wskazujmy gdzie będzie znajdował się węzeł, który będzie odpowiedzialny za uruchamianie jobów:
<configuration>
<property>
<name>mapred.job.tracker</name>
<value>localhost:9001</value>
</property>
</configuration>


Opcjonalnie warto jeszcze dodać w pliku conf/hdfs-site.xml ścieżki gdzie HDFS ma przetrzymywać swoje dane:
Katalog dla metadanych NameNode:
<property>
<name>dfs.name.dir</name>
<value>/home/user/hadoop/dfs/namenode</value>
</property>
Katalog dla bloków danych:
<property>
<name>dfs.data.dir</name>
<value>/home/user/hadoop/dfs/data</value>
</property>
Gdy wszystko mamy już skonfigurowane formatujemy HDFS-a:
bin/hadoop namenode -format

Uruchamiamy Hadoopa:
bin/start-all.sh
Jeśli wszystko jest w porządku będziemy mieli możliwość obejrzenia dwóch stron:
  • NameNode - http://localhost:50070/
  • JobTracker - http:localhost:50030/
W razie jakichkolwiek problemów bardzo pomocne są logi znajdujące się w katalogu hadoopa.


Uruchamiamy przykład:
Kopiujemy katalog input do HDFS-a:
bin/hadoop fs -put input input
sprawdzamy czy udało się skopiować:
bin/hadoop fs -ls
uruchamiamy ten sam przykład co poprzednio:
bin/hadoop jar hadoop-*-examples.jar grep input output '\w+'

sprawdzamy wyniki:
bin/hadoop fs -cat output/*
Aby zatrzymać Hadoopa wywołujemy:
bin/stop-all.sh

czwartek, 12 sierpnia 2010

Jak to robią wielcy...

Ostatnio przeglądając blogosferę natknąłem się na informacje na temat ilości przetwarzanych danych przez portale które można uznać za wielkie. Muszę przyznać, że zrobiło to na mnie spore wrażenie, mianowicie obecnie Facebook posiada 36 petabajtów danych, a do końca roku spodziewa się wzrostu do około 50 petabajtów, Google gromadzi każdego dnia 400 terabajtów logów (dane z 2007 roku). Zacząłem się zastanawiać, jak można sobie poradzić z taką ilością różnych danych, jak je przechowywał, filtrować, oraz nimi zarządzać. Okazało się, że inżynierowie Google wymyślili oraz opublikowali dokumentacje do dwóch technologii które potrafią sobie z tym poradzić, są to:

  • MapReduce
  • GFS (Google File System)
Na postawie tej dokumentacji Yahoo stworzyło, oraz udostępniło na licencji Apache platformę o nazwie Hadoop. Hadoop to zbiór 8 narzędzi, mnie jak na razie interesuje 5 z nich:

1. MapReduce - podstawa całej platformy. Wymyślona przez Google koncepcja jest genialna w swojej prostocie, w dwóch słowach chodzi tutaj o to, aby programista w najprostszej wersji dostarczył dwóch klas: mappera i reducera. Mapper na wejściu otrzymuje pewien obiekt z danymi wejściowymi (najczęściej linie z pliku), a na wyjściu produkuje pary klucz=wartość. Instancji mappera może być oczywiście wiele (Facebook ma klaster Hadoopa złożony z 2200 maszyn) gdy mappery skończą prace Hadoop scala klucze tak aby powstały pary klucz = {lista wartości z wszystkich mapperów}, tak z agregowane dane przekazywane są do reducera (także może ich być wiele), który wykonuję jakieś operacje biznesowe i przekazuje wyniki również w formie klucz=wartość.

2. HDSF (Hadoop Distributed File System) - czyli rozproszony system plików. HDSF ma zapewnić szybki odczyt, zapis, oraz replikacje danych. Jak on to robi? Wyobraźmy sobie klaster złożony z 10 komputerów, jeden pełni role NameNode Server, oznacza to, że on wie gdzie i jakie pliki są zapisane, pozostałe 9 tylko przechowują dane. Gdy wgrywamy plik do HDFS zostaje on podzielony na paczki (zazwyczaj 64, lub 128 mb), paczki są równolegle zapisywane na wszystkich maszynach oraz replikowane na określoną przez nas ilość maszyn. Zmniejsza to znacznie ograniczenie związane prędkością zapisu oraz odczytu dysku ponieważ jednocześnie piszemy na wiele dysków na wielu maszynach. Ma to jednak swoje słabe strony, gdy już raz zapiszemy plik w HDSF nie ma możliwości edytowania jego zawartości.

3. Hive - ludzi od Facebooka zaczął uwierać fakt, że do stworzenia joba, który będzie obrabiał dane używając MapReduce potrzebni są programiści, oraz sporo kodu który muszą napisać. Drugim problemem są analitycy danych, którzy chcą stworzyć sobie na bieżąco jakieś statystyki bez potrzeby zaprzęgania do roboty programistów. W odpowiedzi na te problemy powstał Hive, czyli technologia która potrafi opisać jakiś plik lub katalog HDSF-a schemą (stworzyć tabele podobną do tych znanych z baz danych opisującą jakie dane znajdują się w środku), gdy dane są opisane możemy uruchamiać na nich zapytania łudząco podobne do SQL-a, możliwe są nawet JOIN-y pomiędzy "tabelami", a wyniki możemy zapisać w kolejnej "tabeli" czyli tak naprawdę w pliku znajdującym się w HDSF. A i byłbym zapomniał - oczywiście nasze zapytania SQL są tak naprawdę konwertowane do jobów MapReduce.

4. Pig - Yahoo uznało, że Hive to nie do końca to co jest im potrzebne, dlatego powstał Pig. Jest to język, który umożliwia budowanie jobów MapReduce na znacznie wyższym poziomie abstrakcji niż możliwe jest to np. w Javie. Postawiono tutaj na duże możliwości związane nie tylko z przeszukiwaniem danych, ale również z ich przetwarzaniem, kolejnym bardzo ważnym elementem języka jest łatwość integracji z kodem napisanym w Javie, co pozwala na pisanie specyficznych elementów joba.

5. HBase - czyli implementacja BigTable, jest to nierelacyjna baza danych typu klucz=wartość, która wykorzystuje HDFS oraz łatwo integruje się z pozostałymi technologiami.

Ok, przydługi i trochę teoretyczny wstęp mamy za sobą. W kolejnych postach postaram się opisać w praktyczny sposób każdą z wymienionych technologi. Trzymajcie rękę na pulsie ponieważ w następnym wpisie uruchomimy nasz pierwszy klaster Hadoopa.

czwartek, 19 listopada 2009

Grails + Flex + App engine cz. 2

W poprzednim wpisie udało nam się stworzyć aplikacje w Grails i Flex oraz uruchomić ją w środowisku testowym App Engine. Teraz czas zdeployować ją w chmurze.
Jeśli nasza aplikacja nazywa się inaczej niż aplikacja utworzona w Google App Engine należy w pliku grails-app/conf/Config.groovy dodać następujący wpis:
google.appengine.application="nazwa aplikacji"

Google App Engine nie lubi wersji zawierających kropki dlatego poleceniem:
grails set-version 1
ustawiamy wersje aplikacji na jeden.

Kolejnym krokiem jest spakowanie aplikacji do pliku war:
grails app-engine package

Nie pozostaje nam nic innego jak tylko wysłać aplikacje na serwer Google:

grails app-engine deploy

Bywa że pierwsze logowanie do usługi zakończy się niepowodzeniem! Jest to spowodowane błędem w skrypcie Antowym. W takim wypadku należy użyć polecienia:

$APPENGINE_HOME/bin/appcfg.cmd update ./target/war

Aplikacja dostępna pod adresem:
http://todo-flex.appspot.com/main.swf

środa, 18 listopada 2009

Grails + Flex + App engine

Spróbujmy dziś stworzyć aplikacje w której back-end zostanie napisany w Groovy i Grails front-end zostanie stworzony w Flexie a całość będzie hostowana w Google App Engine.
Najpierw musimy stworzyć nową aplikacje w App Engine. Podajemy nazwę (w moim przypadku wolna okazała się dopiero nazwa 'todo-flex') oraz krótki opis.
Gdy w chmurze mamy już stworzoną aplikacje możemy przejść do tworzenia aplikacji w Grails.
Tworzymy aplikacje:


grails create-app todo-flex


Po przejściu do katalogu zaczynamy konfigurcje pluginów.
App Engine używa JDO lub JPA dlatego dla pewności usuwamy plugin Hibernate:

grails uninstall-plugin hibernate


Instalujemy plugin App Engine:

grails install-plugin app-engine

wybieramy JPA.

Instalujemy plugin umożliwiający korzystanie z GORM wraz z JPA:

grails install-plugin gorm-jpa


Ostatnim pluginem jest plugin dla Flexa:

grails install-plugin flex

Projekt mamy skonfigurowany czas na implementację.
Najpierw tworzymy klasę domenową:

grails create-domain-class com.mcz.ToDo


Kolejnym krokiem jest stworzenie serwisu:

grails create-service com.mcz.ToDo


W tym momencie możemy zaimportować projekt do Eclipse wybierając File/Import/General/Existing Projects into Workspace.

Wypełniamy klasę domenową kodem, w naszym przykładzie klasa com.mcz.ToDo bedzie miała nastepującą postać:

@Entity
class ToDo implements Serializable {

@Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 Long id

 @Basic
 String Title

 @Basic
 String text

}

Jak widać jest to zwykłe ziarno JPA.

Serwis ToDoService ma następującą postać:
class ToDoService {

 static expose = ['flex-remoting']

    boolean transactional = true

    def getToDosList() {
     ToDo.list(sort:"dateInserted", order:"desc")
    }

    def save(todo){
     todo.id = null
     todo.dateInserted = new Date()
     todo.save()
    }

   def deleteToDo(id){
     def todo = ToDo.get(id);
     todo.delete()
    }
}
Jest to standardowy serwis grailsowy, jedyne co jest w nim warte odnotowania, to statyczna lista  expose = ['flex-remoting'] mówiąca to tym, że ten serwis będzie dostępny poprzez Flex RemoteObject.

To tyle jeśli chodzi o stronę serwerową czas na widok.
Nadajemy projektowi Flex Project Nature, pozostawiamy wszystkie ustawienia na default.
Tworzymy w ActionScript klasę odpowiedzialną za mapowanie obiektów ToDo tak aby mogły być przesyłane z serwera do widoku.Kod klasy powinien wyglądać tak:
package com.mcz
{
 [Bindable]
    [RemoteClass(alias="com.mcz.ToDo")]
 public class ToDo
 {
  public function ToDo()
  {
  }
  public var id:Number;
  public var title:String;
  public var text:String;
  public var dateInserted:Date;

 }
}
W klasie nie ma nic specjalnego oprócz dwóch elementów:
1. [Bindable] - dzięki elementowi MetaData, jeśli wartość któregoś z pól zostanie zmieniona, nastąpi wygenerowanie zdarzenia.
2. [RemoteClass(alias="com.mcz.ToDo")] - wskazuje na który obiekt znajdujący się po stronie serwerowej dana klasa ma być mapowana.


W katalogu src znajduje się plik main.mxml to w nim tworzymy widok. Najciekawszym elementem jest konfiguracja RemoteObject pozwalająca wywoływać metody na serwisie ToDoService:
<mx:RemoteObject id="ro" destination="toDoService" fault="faultHandler(event)">
<mx:method name="getToDosList" result="getToDosListHandler(event)"/>
</mx:RemoteObject>
<mx:Script>
<![CDATA[
...

[Bindable]
private var todoList:ArrayCollection;

private function getToDosListHandler(event:ResultEvent):void{
    todoList = event.result as ArrayCollection;
}
...
]]>
</mx:Script>
Mamy już stworzoną stronę serwerową oraz widok potrzebne jest jeszcze tylko kilka zmian w konfiguracji:
1. W pliku grails-app/conf/Config.groovy dodajemy linie flex.webtier.compiler.enabled = false, wyłączamy w ten sposób kompilację w runtimie dla plików mxml. W przeciwnym wypadku podczas uruchamiania aplikacji dostaniemy informację o tym, że serwlet próbuje zapisu na dysku co w App Engine jest niedozwolone.
2. W pliku web-app/WEB-INF/flex/service-config.xml:
a)Dodajemy
<system>
 <manageable>false</manageable> </system>
b) Aplikacje w App Engine hostowane są bez kontekstu dlatego musimy usunąć {context.root} z channel-definition

Ostatnią rzeczą jeśli chodzi o ustawienia projektu jest wskazanie kompilatorowi Flexa zmodyfikowanego pliku service-config.xml. Aby to zrobić klikamy prawym przyciskiem na projekcie i wybieramy Properies/Flex Compiler/ Additional compiler arguments dodajemy -services [ścieżka względna dla pliku main.mxml]/services-config.xml u mnie ten wpis ma postać -services ../web-app/WEB-INF/flex/services-config.xml.

Kompilujemy projekt do kartoteki web-app za pomocą Export Release Build i gotowe!
Teraz wystarczy tylko przetestować naszą aplikacje.
W konsoli będąc w kartotece projektu wpisujemy
grails app-engine run

W kolejnym wpisie pokaże jak wersjonować oraz umieszczać aplikacje w App Engine.
Kod aplikacji dostępny pod adresem:
http://svn.xp-dev.com/svn/blog_svn/