ado.net tworzenie elastycznych aplikacji cz. 1

Wiadomo powszechnie że programista jest stworzeniem leniwym. Visual studio udostępnia mnóstwo różnych kreatorów które pozwalają na wyklikanie wielu różnych rzeczy. Niestety minusem tego podejścia jest kompletna niewiedza na temat tego jak pewne rzeczy działają. Dodatkowo często natrafimy na różne ograniczenia, które w pewnym momencie sprawią że albo będzie trzeba mocno się nakombinować, albo wręcz trzeba będzie napisać własne rozwiązanie.

Przykład który prezentuje tym razem jest raczej banalny i jest przeznaczony dla początkujących programistów. Podobnie jak udostępniona klasa jodb, jest mocno spolszczony i dodatkowo jest opisana praktycznie każda linia kodu.

Wyobraźmy sobie sytuację że mamy napisać aplikację do wprowadzania danych (na początku załóżmy że te dane nie muszą być walidowane w jakiś sensowny sposób). By sprawę maksymalnie uprościć załóżmy że będziemy dodawać klientów do bazy danych. Ale jak wiadomo wymagania mogą z czasem się zmienić i chcielibyśmy by nasza aplikacja była dość elastyczna.

Stwórzmy na początek bazę danych i tabelę gdzie będziemy trzymać nasze dane:

create database elasticApp;
use elasticApp;
go

create table klienci (
kli_id int not null primary key identity(1,1),
kli_imie varchar(255),
kli_nazwisko varchar(255),
kli_mail varchar(255),
kli_dataDodania datetime default getdate(),
kli_aktywny int default 1
)

Jak widać mamy prostą tabelę. Możemy napisać teraz prostą aplikację która będzie korzystała z jodb,
napiszemy sql z parametrami i można by powiedzieć że jest ok. Jednak jeśli ktoś chciałby byśmy zbierali jeszcze jedno pole np miejsce urodzenia, to musimy dokonać zmiany w bazie danych oraz zmiany w aplikacji (zmiana sql, dodanie kolejnych kontrolek itd).

Możemy też napisać aplikację, która sama zareaguje na zmiany w bazie i nie będziemy musieli nic zmieniać.

Nasza aplikacja może pobierać definicje tabeli z bazy i na tej podstawie generować pola tekstowe i później dynamicznie tworzyć zapytanie SQL by te dane zapisać.


string q = "SELECT top 1 column_name,data_type, character_maximum_length FROM"
+ " information_schema.COLUMNS WHERE table_name= @nazwaTabeli"
+ " ORDER BY ordinal_position";

powyższe zapytanie wklejone w C# i wykonane pozwoli nam na pobranie listy kolumn.

Później na podstawie listy kolumn generujemy textboxy:


//iterowanie po wszystich wierszach w obiektu datatable
// kazdy wiersz odpowiadna jednej kolumnie
//nazwa kolumny jest w column_name
foreach (DataRow dr in opisTabel.AsEnumerable())
{
if (!dr["column_name"].ToString().ToLower().Contains("id") && !dr["column_name"].ToString().ToLower().Contains("datadodania"))
{
Label l = new Label(); // stworzenie nowej labelki (opis tekstowy);
TextBox t = new TextBox(); // stworzenie nowego textboxa (pole tekstowe).
l.Name = "label_" + dr["column_name"].ToString(); //nadanie nazwy labelki
l.Text = dr["column_name"].ToString(); // ustawienie wyswietlanego tekstu
l.Top = 10 + a; // polozenie od gory
l.Left = 10; // polezenie od lewego brzegu formy
l.Show(); // pokazanie labelki*

t.Name = dr["column_name"].ToString(); //nadanie nazwy dla textboxa
t.Top = 10 + a; // polozenie od gory
t.Left = 160; // polezenie od lewego brzegu formy
t.Show();// pokazanie textboxa*

this.Controls.Add(t); // dodanie textboxa do listy kontrolek na formie
this.Controls.Add(l); // dodanie labelki do kontrolki na formie

a += 25; //dodanie 12 do ostatniej wartosci a
}
}
// po petli stworzymy guzik ktory bedzie zapisywał dane do bazy
Button b = new Button(); // stworzenie obiektu guzika
b.Left = 160; // polozenie od lewej
b.Top = 10 + 25 + a; //polozenie od gory
b.Text = "zapisz"; // nadanie etykiety guzika
b.Click += b_Click; //przypisanie eventu klikniecia do metody (ponizej)
b.Show(); //pokazanie guzika*
this.Controls.Add(b); // dodanie go do formy

//* obiekt zostanie pokazany na formie dopiero po tym jak zostanie dodany do kontrolek
// jesli zostala uzyta metoda show, a obiekt nie znajduje sie w kolekcji, nic sie nie pojawi.
}

Mając przygotowaną formatkę musimy jeszcze przygotować event który obsłuży nam zapis do bazy:


//metoda do obslugi zapisu danych
void b_Click(object sender, EventArgs e)
{
//slownik w ktorym bedziemy zbierac dane do zapisania
Dictionary slownik_danych = new Dictionary();
// przeiterowanie sie po textboxach w celu stworzenia slownika z danymi
foreach (Control c in this.Controls)
{
//prawie 'refleksja' sprawdzenie typow do rzutowania
if (c.GetType() == typeof(TextBox))
{
//rzutowanie kontrolki c na textbox
TextBox t = (TextBox)c;

//stworzenie danych w slowniku nazwa kontrolki (nazwa kolumny) oraz wpisana wartosc
slownik_danych.Add(t.Name, t.Text);
}
}

//zapisanie wprowadzonych danych do bazy
int i = jdb.zapisz_dane(slownik_danych, "klienci");
// sorawdzenie czy się udało zapisać dane:
if (i != 1)
{
// jesli nie to pokazujemy ładny komunikat.
MessageBox.Show("Nie udało się zapisać danych, popraw je", "błąd", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}

Oczywiście do tej pory w jdb nie mieliśmy metody która by potrafiła wygenerować odpowiedni SQL do zapisu danych, poniżej metody które pozwolą to osiągnąć:


///

/// wykonuje sql command z zapytaniami insert/update
///

/// sql command /// ilość wierszy
public int zapisz_dane(SqlCommand sqlc)
{
sqlc.Connection = this.polaczenie; // wskazanie polaczenia do bazy danych
return sqlc.ExecuteNonQuery();
}

///

/// pobiera opis wybranej tabeli
///

/// nazwa tabeli /// informacje o kolumnach w kolejnosci w jakiej są wyświetlane w sql server
public DataTable pobierz_opis_tabeli(string nazwaTabeli)
{
//zapytanie do information_schema pobierajace opis tabeli
string q = "SELECT column_name,data_type, character_maximum_length FROM"
+ " information_schema.COLUMNS WHERE table_name=@nazwaTabeli"
+ " ORDER BY ordinal_position";

//zwraca obiekt zwrocony przez metode pobierz dane z parametrem
return this.pobierz_dane(q, "@nazwaTabeli", nazwaTabeli);
}

///

/// metoda do wyznaczenia prefixu w nazwach kolumn
///

/// nazwa tabeli /// pefix uzywany w kolumnach
public string getColPrefix(string nazwaTabeli)
{
//probuje wyznaczyc prefix
try
{
// zapytanie ktore zwroci 1 wiersz z info o kolumnach w tabeli
string q = "SELECT top 1 column_name,data_type, character_maximum_length FROM"
+ " information_schema.COLUMNS WHERE table_name= @nazwaTabeli"
+ " ORDER BY ordinal_position";
//pobieranie danych do datatable
DataTable dt = this.pobierz_dane(q, "@nazwaTabeli", nazwaTabeli);

// stworzenie zmiennej prefix ktora zawiera cala nazwe kolumny z 1 wiersza
string prefix = dt.Rows[0]["column_name"].ToString();
int position = prefix.IndexOf('_'); // wyznaczenie pozycji znaku _ ktory oddziela prefix
prefix = prefix.Substring(0, position); // wyznaczenie napisu przed znakiem _

return prefix; // zwrocenie prefixu
}
// jesli sie nie udalo to zwroci null,
// mozna ustawic breakpoint na e.toString by sprawdzic jaki dokladnie blad wystapil
catch (Exception e)
{
e.ToString();
return null;
}
}

///

/// metoda ktora zwraca liste kolumn w formacie listy stringow
///

/// data table z wynikiem zapytania o opis tabeli /// liste stringow z nazwami kolumn
public List gen_lista_kolumn(DataTable dt)
{
//stworzenie nowej listy
List lista = new List();
//iterowanie po liscie kolumn dostarczonej jako parametr
foreach (DataRow dr in dt.AsEnumerable() ){
//dodaj do listy kolumne column_name z wiersza
lista.Add(dr["column_name"].ToString());
}
//zwroc liste stringow
return lista;
}

///

/// metoda do budowania zapytan typu insert
///

/// slownik z danymi do zapisania w bazie /// nazwa tabeli gdzie dane maja byc zapisane /// obiekt sql command do zapisania
public SqlCommand zbuduj_zapytanie_zapisujace(Dictionary daneWej, string nazwaTabeli)
{
//stworzenie obiektu ktory zostanie zwrocony
SqlCommand komenda_sql = new SqlCommand();
//zmienna kontrolna (wykorzystana pozniej)
int inu = 0;
//wygenerowanie listy kolumn w tabeli
List lista_kolumn = this.gen_lista_kolumn(this.pobierz_opis_tabeli(nazwaTabeli));
//zapytanie sql
string sql = " INSERT INTO " + nazwaTabeli + " (";

//iterowanie po kazdym wystapieniu ze slownika w celu zbudowania listy kolumn
foreach (var c in daneWej)
{
// linq (tutaj troche zbedne poniewaz metoda zostala uproszczona)
var kolatr = from p in lista_kolumn
where p.ToLower() == c.Key.ToLower()
select p;
// powyzsze linq sprawdza czy kolumna (c.key) znajduje sie na liscie kolumn w tabeli
kolatr.ToArray().Length.ToString();

//sprawdzenie czy linq zwrocilo jeden element (po drodze cast na tablice).
if (kolatr.ToArray().Length ==1)
{
//sprawdzenie czy dane do zapisania sa 'sensowne'
if (c.Value != null && c.Value.ToString() != "")
{
//doklejenie odpowiedniego kawalka sql
sql += c.Key + ", ";
//inkrementacja zmiennej kontrolnej
inu++;
}
}
}
// po petli usuwam zbedny przecinek
sql = sql.Substring(0, sql.Length - 2);

//dodaje kolejny kawalek kodu sql
sql += ") VALUES (";

// kolejna iteracja po danych wejsciowych teraz by dodac wartosci do zapytania
foreach (var c in daneWej)
{
// to samo linq co wyzej
var kolatr = from p in lista_kolumn
where p.ToLower() == c.Key.ToLower()
select p;
//sprawdzenie czy linq jest ok | tak jak wyzej
if (kolatr.ToArray().Length == 1)
{
//proba stworzenia odpowiedniego parametru i dodanie go sqlcommand
try
{
//sprawdzenie czy dane do zapisu maja 'sens' (jak wyzej)
if (c.Value != null && c.Value.ToString() != "")
{
//stworzenie nazwy parametru
string paramName = "@" + c.Key;
//dodanie parametru do sql
sql += paramName + ", ";
//stworzenie obiektu parametru
SqlParameter param = new SqlParameter (paramName, c.Value);
//dodanie parametru do sql command
komenda_sql.Parameters.Add(param);
}
}
//jesli sie cos nie powiodlo to mozna ustawic sobie breakpoint by zbadac dlaczego nie dziala.
catch (Exception e)
{
e.ToString();
}
}
}

//usuwam zbedne przecinki
sql = sql.Substring(0, sql.Length - 2);
// zakonczenie zapytania
sql += ")";

//sprawdzenie czy udalo sie dodac chociaz jedna kolumne
//zmienna kontrolna :)
if (inu > 0)
{
// przypisanie sklejonego stringa do komendy
komenda_sql.CommandText = sql;
//przypisanie polaczenia do komendy (teraz komenda jest gotowa do wykonania)
komenda_sql.Connection = this.polaczenie;
return komenda_sql;
}
//jesli nie udalo sie dodac zadnej kolumny to zwracamy null
return null;
}

///

/// zapisuje w bazie korzystajac z dynamicznego tworzenia zapytania
///

/// dane do zapisania /// nazwa tabeli gdzie dane maja byc zapisane /// 1 w przypadku sukcesu, -1 gdy porażka
public int zapisz_dane(Dictionary daneWej, string nazwaTabeli)
{
//proba zapisu
try
{
return this.zapisz_dane(this.zbuduj_zapytanie_zapisujace(daneWej, nazwaTabeli));
}
// jesli sie nie udalo, mozna ustawic break point by zobaczyc co poszlo nie tak
catch (Exception e)
{
e.ToString();
return -1;
}
}

kompletny projekt można ściągnąć tu: ElasticApp1

md5 i t-sql

Aby wyliczyć hash md5 w sql server korzystając z t-sql, powinniśmy zdefiniować prostą funkcję która nam to ułatwi. Jako że w większości przypadków posługujemy się hashem zapisanym jako 32 znaki w zapisie 16stkowym.

create FUNCTION genmd5
(
@inputstring varchar(max)
)
RETURNS varchar(32)
AS
BEGIN

return LOWER(CONVERT(VARCHAR(32), HashBytes('MD5', CONVERT(varchar, @inputstring )), 2))

END
GO

To co jest istotne w tej funkcji to linia:
return LOWER(CONVERT(VARCHAR(32), HashBytes('MD5', CONVERT(varchar, @inputstring )), 2))

wygląda ona dość skomplikowanie ze względu na konwersje. Należy się upewnić że ciąg wejściowy to varchar inaczej funkcja HashBytes może zwrócić inne wyniki (hashowanie odbywa się na poziomie bajtów a różne typy danych mogą mieć różne zapisy).
Jako że podobnie jak w .NET wynik ejst zwracany jako zapis bajtów należy go również zamienić na napis w odpowiedni sposób. Przyjęło się że hashe są przekazywane małymi literami.

md5 i .net (C#)

Czasem zachodzi potrzeba wygenerowania hasha MD5, aby tego dokonać w środowisku .NET potrzebujemy napisać prostą metodę:


public static string genMD5(string input)
{
System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();
byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
byte[] hashBytes = md5.ComputeHash(inputBytes);

StringBuilder sb = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++) { sb.Append(hashBytes[i].ToString("X2")); } return sb.ToString().ToLower(); }

tłumacząc kod od góry jest to publiczna metoda statyczna (można jej użyć bez instancjonowania klasy). Jako argument przyjmuje ona string który chcemy zakodować.

System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();
Tworzy nam obiekt md5, który odpowiada za generowanie hashy (skrótów).

System.Security.Cryptography
jest namespacem, którego użyłem by nie było problemu z usingami, można też to dodać na samej górze i wtedy stworzenie obiektu będzie wyglądało tak:

MD5 md5 = MD5.Create();

byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
Zamienia nasz napis na tablicę bajtów, które zostaną użyte to skalkulowania hasha.

byte[] hashBytes = md5.ComputeHash(inputBytes);
Powyższa linia tworzy tablicę bajtów w której znajduje się 16 elementów (Md5 zwraca hashe o długości 128bitów).


StringBuilder sb = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++) { sb.Append(hashBytes[i].ToString("X2")); }

Na koniec możemy zamienić nasz hash na napis zawierający 32znaki (liczba w zapisie 16stkowym).

Big Data, Hadoop & Tworzenie Tabeli w Hive

W poprzednim poście umieściłem info jak załadować dane do hdfs, Teraz by je przeanalizować za pomocą SQL potrzebujemy stworzyć odpowiednią Tabelę w HIVE.

By nie było zbyt pięknie najpierw musimy pobrać Jar’a który pozwoli nam obsłużyć format danych w naszych plikach( z danymi z Twittera).

http://files.cloudera.com/samples/hive-serdes-1.0-SNAPSHOT.jar

 

Jeśli mamy już Jara w jakimś przystępnym miejscu to możemy zalogować się do konsoli Hive. W sandboxie do którego się logowaliśmy przez http, mamy też GUI do obsługi hive, jednakże zalecam skorzystanie z  shella ze względu na lepszą czytelność komunikatów błędów

[root@sandbox conf]# hive

Powinno się pokazać coś takiego:

Logging initialized using configuration in jar:file:/usr/lib/hive/lib/hive-common-0.12.0.2.0.6.0-76.jar!/hive-log4j.properties

SLF4J: Class path contains multiple SLF4J bindings.

SLF4J: Found binding in [jar:file:/usr/lib/hadoop/lib/slf4j-log4j12-1.7.5.jar!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: Found binding in [jar:file:/usr/lib/hive/lib/slf4j-log4j12-1.7.5.jar!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.

SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]

hive>

najpierw dodajemy Jara do Hive:

hive> ADD JAR /usr/local/jakubjars/hive-serdes-1.0-SNAPSHOT.jar;

Wydaje się że trzeba go dodawać po każdym logowaniu co jest upierdliwe, ale jeszcze nie znalazłem miejsca gdzie można by zapisać to na trwałe.

Teraz tworzymy tabele:

CREATE EXTERNAL TABLE tweets (
id BIGINT,
created_at STRING,
source STRING,
favorited BOOLEAN,
retweet_count INT,
retweeted_status STRUCT<
text:STRING,
user:STRUCT<screen_name:STRING,name:STRING>>,
entities STRUCT<
urls:ARRAY<STRUCT<expanded_url:STRING>>,
user_mentions:ARRAY<STRUCT<screen_name:STRING,name:STRING>>,
hashtags:ARRAY<STRUCT<text:STRING>>>,
text STRING,
user STRUCT<
screen_name:STRING,
name:STRING,
friends_count:INT,
followers_count:INT,
statuses_count:INT,
verified:BOOLEAN,
utc_offset:INT,
time_zone:STRING>,
in_reply_to_screen_name STRING)
ROW FORMAT SERDE 'com.cloudera.hive.serde.JSONSerDe'
LOCATION '/dzejkop/flume/tweets';

 

 

To na co warto zwrócić uwagę to fakt iż zagnieżdżone dane zostały zapisane w postaci struktur. Daje to ciekawe możliwości. Według dokumentacji tabela może zawierać dodatkowe nieuwzględnione tutaj pola pod warunkiem że będą one miały nazwę i typ zgodny z danymi JSON od Twittera.

Teraz możemy wykonać przykładowe zapytanie SQL w Hive:

hive> select user.screen_name, user.followers_count c from tweets order by c desc;

powinno nam pokazać nazwy użytkowników I informacje o tym ile posiadają followersów. Zapytanei to nie posiada grupowania więc nazwy userów powinny wystąpić tyle razy ile Tweetów wpadło w nasze sidła. Po wykonaniu zapytania pojawią się różne napisy, zostanie stworzony odpowiedni job, który obrobi dane. Finałow finałów powinniśmy zobaczyć wyniki:

sabou35876529   139
sabou35876529   139
sabou35876529   139
sabou35876529   139
sabou35876529   139
sabou35876529   139
sabou35876529   139
Mister_Ka       137
Mister_Ka       137
Mister_Ka       137
Mister_Ka       137

Korzystając z sandboxa, możemy też wykonywać zapytania, jednakże każdorazowo do każdego zapytania musimy wskazać Jara.

Konfiguracja Flume

Teraz musimy sprawdzić czy mamy odpowiednie katalogi i pliki. De facto nie ma większego znaczenia gdzie będziemy przechowywać pliki konfiguracje dla Flume, jednak do tego został stworzony katalog /etc/ dlatego też sugeruję by zrobić to właśnie tam. Sprawdzmy czy mamy katalog /etc/flume/conf

Jeśli nie to należy go utworzyć:

[root@sandbox ~]# mkdir /etc/flume

[root@sandbox ~]# mkdir /etc/flume/conf

Następnie musimy utworzyć plik konfiguracyjny. W katalogu z binarką możemy odnaleźć przykładowe konfigi, lub też może się zdarzyć że instalator nam je tam już utworzył, jednak nie są one nam do niczego potrzebne. Dlatego też z czystym sumieniem możemy utworzyć swój własny plik:

[root@sandbox ~]# cd /etc/flume/conf

[root@sandbox ~]# touch flume.conf

Teraz nadchodzi ten trudny moment, gdzie trzeba zmierzyć się Vimem ;). Jeśli nie potrafisz korzystać z tego edytora przygotowałem miłą niespodziankę J.  Możesz odpalić mc (midnight commander) i znaleźć nasz utworzony plik i wcisnąć F4.

Jako że mamy do wyboru wiele sourców i sinków, zacznę od prostego przykładu: Flume hello world J. Skonfigurujemy source http i zapis danych do zwykłego pliku. Gdy zrozumiemy zasadę działania pokaże kilka innych konfiguracji.  Po zrozumieniu jak to działa dokumentacja od Flume będzie już wystarczająco zrozumiała by sobie samemu poradzić.

agent1.sources = source1
agent1.sinks = local-file-sink
agent1.channels = channel1

 

agent1.sources.source1.type = org.apache.flume.source.http.HTTPSource
agent1.sources.source1.bind = 192.168.56.101
agent1.sources.source1.port = 5140
agent1.sources.source1.handler.nickname = json handler
agent1.sources.source1.channel = channel1

agent1.sinks.local-file-sink.type = file_roll
agent1.sinks.local-file-sink.channel = channel1
agent1.sinks.local-file-sink.sink.directory = /root/http-test
agent1.sinks.local-file-sink.rollInterval = 5

agent1.channels.channel1.type = memory
agent1.channels.channel1.capacity = 1000
agent1.channels.channel1.transactionCapactiy = 100

agent1 to nazwa agenta identyfikuje ona konkretny przepływ danych. Nazwa jest wymyślana przez nas. Dlatego też szukając po necie przykładów można natrafić na przeróżne konfiguracje i w pierwszym momencie można odnieść wrażenie że się one bardzo różnią.  Każdy agent musi mieć przypisane źródło (lub źródła), kanał(kanały), oraz wyjście (wyjścia). Zaczniemy od prostych przykładów że wszystkiego będzie po jednym.

agent1.sources = source1

właściwość sources jest obligo, source1 to znów przez nas wymyślona nazwa która będzie nam identyfikować dane źródło.

agent1.sinks = local-file-sink

Analogicznie do źródła local-file-sink to nasza nazwa wyjścia. W tym wypadku będą to zapisywane pliki na lokalnym systemie plików.

 

agent1.channels = channel1

Tak samo jak powyżej, nasz kanał będzie buforem w pamięci komputera, znaczy się że będzie wydajny, ale w przypadku awarii agenta część informacji może być stracona.

 

agent1.sources.source1.type = org.apache.flume.source.http.HTTPSource
agent1.sources.source1.bind = 192.168.56.101
agent1.sources.source1.port = 5140
agent1.sources.source1.handler.nickname = json handler
agent1.sources.source1.channel = channel1

zapis konfiguracji przypomina programowanie obiektowe, agent posiada właściwość sources, do której dopisujemy tworzone przez nas źródło i jego właściwości. Zaczynamy od type, tutaj podajemy nazwę klasy która obsługuje dane wejście, bind oznacza adres IP na którym nasłuchujemy, cała reszta wydaje się zrozumiała, należy zwrócić uwagę na przypisanie kanału gdzie source ma przekazać swoje dane. Szczerze mówiąc nie wiem po co jest tam nickname J.

agent1.sinks.local-file-sink.type = file_roll
agent1.sinks.local-file-sink.channel = channel1
agent1.sinks.local-file-sink.sink.directory = /root/http-test
agent1.sinks.local-file-sink.rollInterval = 5

Należy zwrócić uwagę by konfigi były spójne, czyli by nazwy które tworzymy się wzajemnie do siebie odwoływały. Gdy zrobimy literówkę, albo skleimy dwa różne konfigi ze sobą, Flume się potrafi uruchomić poprawnie i nie rzucić nam żadnego błędu. A my stracimy trochę czasu zanim to zauważymyJ. Trzeba też utworzyć katalog w którym mają być zapisywane pliki (/root/http-test). Sinks są stosunkowo dobrze opisane w dokumentacji więc nie powinno być z nimi problemu.  Powyższy konfig będzie powodował powstawanie plików do których będą trafiać dane w formacie JSON wysyłanych po http na wskazany port.

[root@sandbox ~]# mkdir /root/http-test

Aby to przetestować, możemy skorzystać z narzędzia napisanego w javie z naszego komputera hosta (restclient-ui-3.2.2-jar-with-dependencies.jar) jest to prosta aplikacja do pobrania za darmo z sieci. Pozwala ona testować zapytania http. Przyszedł czas na to by odpalić Flume.

[root@sandbox ~]# flume-ng agent -c /etc/flume/conf/ -f /etc/flume/conf/flume-http.conf -n agent1

Należy wskazać ścieżkę do katalogu z plikami konfiguracyjnymi, zalecam podać całą ściężkę. Dodatkowo należy wskazać plik konfiguracyjny oraz nazwę agenta. Proszę zwrócić uwagę na to by nazwą agenta była nazwa z pliku konfiguracyjnego.

Po odpaleniu tej komendy powinno pokazać się wiele różnych rzeczy na konsoli a na koniec plus minus coś takiego:

14/03/18 04:10:18 INFO instrumentation.MonitoredCounterGroup: Monitoried counter group for type: SOURCE, name: source1, registered successfully.

14/03/18 04:10:18 INFO instrumentation.MonitoredCounterGroup: Component type: SOURCE, name: source1 started

Generalnie aplikacja jak działa blokuje nam konsolę. Ale to w niczym nie przeszkadza. Możemy odpalić kolejną sesję putty bądź też skorzystać z konsoli virtualboxa. Po chwili powinny zacząć powstawać pliki we wskazanym katalogu:

[root@sandbox ~]# cd /root/http-test/

[root@sandbox http-test]# ls

1395141017684-1  1395141017684-2  1395141017684-3  1395141017684-4

[root@sandbox http-test]# ls -l

total 0

-rw-r–r– 1 root root 0 Mar 18 04:10 1395141017684-1

-rw-r–r– 1 root root 0 Mar 18 04:10 1395141017684-2

-rw-r–r– 1 root root 0 Mar 18 04:11 1395141017684-3

-rw-r–r– 1 root root 0 Mar 18 04:11 1395141017684-4

-rw-r–r– 1 root root 0 Mar 18 04:12 1395141017684-5

 

Jeśli nie wykonamy żadnych akcji, pliki będą puste. Po odpaleniu rest clienta, możemy wysłać jakiś json. Jako url podajemy wcześniej używany ip i port wskazany w konfiguracji: http://192.168.56.101:5140/. Method Zaznaczamy POST, wchodzimy w zakładkę body i możemy wkleić jakiegoś JSON’a. Np. ten z dokumentacji:

[{

“headers” : {

“timestamp” : “434324343”,

“host” : “random_host.example.com”

},

“body” : “random_body”

},

{

“headers” : {

“namenode” : “namenode.example.com”,

“datanode” : “random_datanode.example.com”

},

“body” : “really_random_body”

}]

 

To o czym w dokumentacji nie napisali to fakt iż do pliku trafiają dane ze zmiennej body.  Najprostszy działający JSON:

[{“body” : “test”}]

W pliku znajdziemy słowo test.

[root@sandbox http-test]# ls –l

-rw-r–r– 1 root root  0 Mar 18 04:10 1395141017684-1

-rw-r–r– 1 root root  0 Mar 18 04:14 1395141017684-10

-rw-r–r– 1 root root 31 Mar 18 04:15 1395141017684-11

-rw-r–r– 1 root root  0 Mar 18 04:15 1395141017684-12

-rw-r–r– 1 root root  0 Mar 18 04:16 1395141017684-13

-rw-r–r– 1 root root  5 Mar 18 04:17 1395141017684-14

-rw-r–r– 1 root root  0 Mar 18 04:10 1395141017684-2

-rw-r–r– 1 root root  0 Mar 18 04:11 1395141017684-3

-rw-r–r– 1 root root  0 Mar 18 04:11 1395141017684-4

-rw-r–r– 1 root root  0 Mar 18 04:12 1395141017684-5

-rw-r–r– 1 root root  0 Mar 18 04:12 1395141017684-6

-rw-r–r– 1 root root  0 Mar 18 04:13 1395141017684-7

-rw-r–r– 1 root root  0 Mar 18 04:13 1395141017684-8

-rw-r–r– 1 root root  0 Mar 18 04:14 1395141017684-9

[root@sandbox http-test]# cat 1395141017684-11

random_body

really_random_body

[root@sandbox http-test]# cat 1395141017684-14

test

 

Jak widać działa.  Teraz możemy zająć się konfiguracją samego Twittera jako źródła danych.

Konfiguracja Twittera jako source

Aby wszystko działało będziemy potrzebować dodatkowy kompoment. Jest to plik jar który będzie obsługiwał komunikację z Twitterem. Dodatkowo będziemy potrzebować dane autoryzacyjne do API Twittera. Zacznijmy jednak od brakującego Jara. Możemy znów pobrać źródła lub gotowy Jar. Na początek polecam skorzystać z gotowca.

http://files.cloudera.com/samples/flume-sources-1.0-SNAPSHOT.jar

Umieszczamy ten plik gdzieś na naszej maszynie wirtualnej.

W katalogu z plikami konfiguracyjnymi (/etc/flume/conf) powinniśmy utworzyć pliko nazwie
flume-env.sh

[root@sandbox conf]# touch flume-env.sh

W pliku tym należy umieścić jedną linię, która będzie wskazywać nam pełną ścieżkę do jara pobranego w poprzednim kroku:

FLUME_CLASSPATH=”/usr/local/jakubjars/flume-sources-1.0-SNAPSHOT.jar

Plik ten będzie wczytywany przy starcie agenta, jeśli ścieżka będzie błędna powinniśmy dostać błąd.

Na chwilę obecną jako że najbardziej interesuje nas pobranie danych z Twittera, wykorzystamy konfig, który stworzyliśmy do pobierania danych z http. Możemy go skopiować:

[root@sandbox conf]# cp flume-http.conf flume-twitter.conf

Następnie edytujemy plik flume-twitter.conf

agent1.sources = Twitter
agent1.channels = channel1
agent1.sinks = local-file-sink

agent1.sources.Twitter.type = com.cloudera.flume.source.TwitterSource
agent1.sources.Twitter.channels = channel1
agent1.sources.Twitter.consumerKey =  wklej kod z twittera
agent1.sources.Twitter.consumerSecret =   wklej kod z twittera
agent1.sources.Twitter.accessToken =   wklej kod z twittera
agent1.sources.Twitter.accessTokenSecret =   wklej kod z twittera
agent1.sources.Twitter.keywords = Poznan, Polska, Ukraina, Ukraine

agent1.sinks.local-file-sink.type = file_roll
agent1.sinks.local-file-sink.channel = channel1
agent1.sinks.local-file-sink.sink.directory = /root/twitter-test
agent1.sinks.local-file-sink.rollInterval = 5

 

agent1.channels.channel1.type = memory
agent1.channels.channel1.capacity = 10000
agent1.channels.channel1.transactionCapacity = 100

 

Dodajemy nowe źródło o nazwie Twitter I usuwamy źródło od http. Należy też podać odpowiednią klasę do obsługi źródła. W Sink zmieniłem katalog w którym będą zapisywane dane. Należy go utworzyć. Teraz należy odpalić Flume i sprawdzić efekty:

[root@sandbox conf]#  flume-ng agent –conf /etc/flume/conf/ -f /etc/flume/conf/flume-twitter.conf   -n agent1

Flume powinien pokazać coś takiego:

14/03/18 05:01:49 INFO twitter4j.TwitterStreamImpl: Establishing connection.

14/03/18 05:01:54 INFO twitter4j.TwitterStreamImpl: Connection established.

14/03/18 05:01:54 INFO twitter4j.TwitterStreamImpl: Receiving status stream.

Teraz należy zobaczyć czy powstały pliki z oczekiwanymi przez nas danymi:

-rw-r–r– 1 root root 379716 Mar 18 05:02 1395144109296-2

-rw-r–r– 1 root root 382754 Mar 18 05:03 1395144109296-3

-rw-r–r– 1 root root 716820 Mar 18 05:04 1395144109296-4

-rw-r–r– 1 root root 309168 Mar 18 05:04 1395144109296-5

O to przykładowy Tweet:

{“filter_level”:”medium”,”retweeted_status”:{“contributors”:null,”text”:”Smiles,                                                              applause and Russian national anthem after signing ceremony to make Crimea part                                                              of Russia. #Ukraine”,”geo”:null,”retweeted”:false,”in_reply_to_screen_name”:nul                                                             l,”truncated”:false,”lang”:”en”,”entities”:{“symbols”:[],”urls”:[],”hashtags”:[{                                                             “text”:”Ukraine”,”indices”:[99,107]}],”user_mentions”:[]},”in_reply_to_status_id                                                             _str”:null,”id”:445892541163257856,”source”:”<a href=\”http://www.hootsuite.com\                                                             ” rel=\”nofollow\”>HootSuite<\/a>”,”in_reply_to_user_id_str”:null,”favorited”:fa                                                             lse,”in_reply_to_status_id”:null,”retweet_count”:5,”created_at”:”Tue Mar 18 12:0                                                             0:40 +0000 2014″,”in_reply_to_user_id”:null,”favorite_count”:0,”id_str”:”4458925                                                             41163257856″,”place”:null,”user”:{“location”:”UK”,”default_profile”:true,”profil                                                             e_background_tile”:false,”statuses_count”:8900,”lang”:”en”,”profile_link_color”:                                                             “0084B4″,”id”:119132506,”following”:null,”favourites_count”:22,”protected”:false                                                             ,”profile_text_color”:”333333″,”description”:”European Bureau Chief, Globe and M                                                             ail. All from a little village in Norfolk, UK.”,”verified”:true,”contributors_en                                                             abled”:false,”profile_sidebar_border_color”:”C0DEED”,”name”:”Paul Waldie  “,”pro                                                             file_background_color”:”C0DEED”,”created_at”:”Tue Mar 02 19:27:17 +0000 2010″,”i                                                             s_translation_enabled”:false,”default_profile_image”:false,”followers_count”:494                                                             4,”profile_image_url_https”:”https://pbs.twimg.com/profile_images/3019033972/d77                                                             1409e131ece4091f2af1ef3586223_normal.jpeg”,”geo_enabled”:true,”profile_backgroun                                                             d_image_url”:”http://abs.twimg.com/images/themes/theme1/bg.png”,”profile_backgro                                                             und_image_url_https”:”https://abs.twimg.com/images/themes/theme1/bg.png”,”follow                                                             _request_sent”:null,”url”:”http://www.theglobeandmail.com/authors/paul-waldie”,”                                                             utc_offset”:-18000,”time_zone”:”Central Time (US & Canada)”,”notifications”:null                                                             ,”profile_use_background_image”:true,”friends_count”:731,”profile_sidebar_fill_c                                                             olor”:”DDEEF6″,”screen_name”:”pwaldieGLOBE”,”id_str”:”119132506″,”profile_image_                                                             url”:”http://pbs.twimg.com/profile_images/3019033972/d771409e131ece4091f2af1ef35                                                             86223_normal.jpeg”,”listed_count”:237,”is_translator”:false},”coordinates”:null}                                                             ,”contributors”:null,”text”:”RT @pwaldieGLOBE: Smiles, applause and Russian nati                                                             onal anthem after signing ceremony to make Crimea part of Russia. #Ukraine”,”geo                                                             “:null,”retweeted”:false,”in_reply_to_screen_name”:null,”truncated”:false,”lang”                                                             :”en”,”entities”:{“symbols”:[],”urls”:[],”hashtags”:[{“text”:”Ukraine”,”indices”                                                             :[117,125]}],”user_mentions”:[{“id”:119132506,”name”:”Paul Waldie  “,”indices”:[                                                             3,16],”screen_name”:”pwaldieGLOBE”,”id_str”:”119132506″}]},”in_reply_to_status_i                                                             d_str”:null,”id”:445893466166689792,”source”:”<a href=\”http://twitter.com/downl                                                             oad/android\” rel=\”nofollow\”>Twitter for Android<\/a>”,”in_reply_to_user_id_st                                                             r”:null,”favorited”:false,”in_reply_to_status_id”:null,”retweet_count”:0,”create                                                             d_at”:”Tue Mar 18 12:04:21 +0000 2014″,”in_reply_to_user_id”:null,”favorite_coun                                                             t”:0,”id_str”:”445893466166689792″,”place”:null,”user”:{“location”:””,”default_p                                                             rofile”:true,”profile_background_tile”:false,”statuses_count”:71883,”lang”:”en”,                                                             “profile_link_color”:”0084B4″,”profile_banner_url”:”https://pbs.twimg.com/profil                                                             e_banners/288755234/1355087024″,”id”:288755234,”following”:null,”favourites_coun                                                             t”:2642,”protected”:false,”profile_text_color”:”333333″,”description”:”Figure wi                                                             th Tweet.\r\n\r\nFocusing on Syria, the wider Arab Spring, and the UK phone hack                                                             ing scandal.  http://brown-moses.blogspot.co.uk”,”verified”:false,”contributors_                                                             enabled”:false,”profile_sidebar_border_color”:”C0DEED”,”name”:”Brown Moses”,”pro                                                             file_background_color”:”C0DEED”,”created_at”:”Wed Apr 27 12:18:25 +0000 2011″,”i                                                             s_translation_enabled”:false,”default_profile_image”:false,”followers_count”:156                                                             98,”profile_image_url_https”:”https://pbs.twimg.com/profile_images/1503800758/FW                                                             M_normal.jpg”,”geo_enabled”:true,”profile_background_image_url”:”http://abs.twim                                                             g.com/images/themes/theme1/bg.png”,”profile_background_image_url_https”:”https:/                                                             /abs.twimg.com/images/themes/theme1/bg.png”,”follow_request_sent”:null,”url”:”ht                                                             tp://brown-moses.blogspot.co.uk”,”utc_offset”:0,”time_zone”:”Casablanca”,”notifi                                                             cations”:null,”profile_use_background_image”:true,”friends_count”:1096,”profile_                                                             sidebar_fill_color”:”DDEEF6″,”screen_name”:”Brown_Moses”,”id_str”:”288755234″,”p                                                             rofile_image_url”:”http://pbs.twimg.com/profile_images/1503800758/FWM_normal.jpg                                                             “,”listed_count”:908,”is_translator”:false},”coordinates”:null}

 

Jak widać dane są mało czytelne, dlatego też w kolejnym kroku postaramy się zapisać dane w Hdfs (hadoop) I później skorzystamy z Hive Aby odpytywać te dane za pomocą SQL co już będzie stosunkowo proste.

 

Konfiguracja Sink z HDFS.

Abyśmy mogli skorzystać z dobrodziejst Hive (odpytywanie via sql) musimy zapisać dane z Twittera w HDFS. Flume ma już przygotowanego odpowiedniego Sinka, jedyne co musimy zrobić to stworzyć odpowiedni katalog w hdfs.

[root@sandbox conf]#  hadoop fs -mkdir /dzejkop

[root@sandbox conf]#  hadoop fs -mkdir /dzejkop/flume

[root@sandbox conf]#  hadoop fs -mkdir /dzejkop/flume/tweets

Zamiast dzejkop oczywiście możemy sobie wstawić dowolną inną nazwę.

 

Aby wszystko zadziałało powinniśmy też dla katalogu w którym będziemy przechowywać tweety nadać odpowiednie uprawnienia:

[root@sandbox conf]#  hadoop fs -chmod -R 777 /dzejkop

 

Edytujemy nasz plik konfiguracyjny flume (możemy sobie utworzyć nową jego wersję).

Usuwamy local-file-sink i wstawiamy HDFS

agent1.sinks = HDFS

agent1.sinks.HDFS.channel = channel1
agent1.sinks.HDFS.type = hdfs
agent1.sinks.HDFS.hdfs.path = hdfs://sandbox:8020/dzejkop/flume/tweets
agent1.sinks.HDFS.hdfs.fileType = DataStream
agent1.sinks.HDFS.hdfs.writeFormat = Text
agent1.sinks.HDFS.hdfs.batchSize = 1000
agent1.sinks.HDFS.hdfs.rollSize = 1024
agent1.sinks.HDFS.hdfs.rollInterval= 60
agent1.sinks.HDFS.hdfs.filePrefix = tweety
agent1.sinks.HDFS.hdfs.fileSuffix = .log
agent1.sinks.HDFS.hdfs.rollCount = 0

 

Proszę pamiętać o zmianie w sinks dla agenta oraz ustawieniu odpowiedniego kanału z którego mają być pobierane dane. Problemem jest trochę ścieżka (Path), teoretycznie powinna być tam nazwa noda hadoop, jednakże przeglądając konfigi nie znalazłem zdefiniowanej nazwy, więc finalnie skorzystałem z nazwy hosta wirtualki. Port jest standardowy dla hdfs:// a ścieżka po porcie to nasz katalog który utworzyliśmy na hdfs. Reszta Parametrów to drobiazgi opisane w dokumentacji Flume.

 

Odpalamy Flume:

[root@sandbox conf]#  flume-ng agent –conf /etc/flume/conf/ -f /etc/flume/conf/flume-twitter.conf   -n agent1

I po chwili powinno zacząć się pojawiać coś takiego:

lume/tweets/tweety.1395147049566.log.tmp

14/03/18 05:51:09 INFO hdfs.BucketWriter: Renaming hdfs://sandbox:8020/dzejkop/flume/tweets/tweety.1395147049566.log.tmp to hdfs://sandbox:8020/dzejkop/flume/tweets/tweety.1395147049566.log

14/03/18 05:51:09 INFO hdfs.BucketWriter: Creating hdfs://sandbox:8020/dzejkop/flume/tweets/tweety.1395147049567.log.tmp

Teraz Tweety są zbierane do Hadoopa. Pozostaje nam utworzyć odpowiednią tabelę w Hive i zacząć się bawić SQL :).

Instalacja Flume.

Flume napisany jest w Javie, możemy pobrać kod źródłowy z Githuba, albo zainstalować gotowy pakiet przez yum, lub ściągnąć binarkę. Generalnie nie ma większej różnicy. Nie ważne który wariant wybierzemy będziemy musieli się upewnić czy istnieją pewne pliki. Najprościej jest skorzystać z yum:

[root@sandbox ~]# yum install -y flume

Później:

[root@sandbox ~]# yum install -y flume-agent

Po wykonaniu jednej i drugiej komendy na końcu efekt powinien być podobny do tego:

Installed:

flume-agent.noarch 0:1.4.0.2.0.6.0-76.el6

Complete!

Weryfikacja instalacji, jest prosta:

[root@sandbox ~]# flume-ng

Error: Unknown or unspecified command ”

 

Usage: /usr/lib/flume/bin/flume-ng <command> [options]…

 

 …

  –help,-h              display help text

 

  Either –rpcProps or both –host and –port must be specified.

 

Note that if <conf> directory is specified, then it is always included first

in the classpath.

 

Teraz wiemy że wszystko działa. Jeśli ściągnęliśmy binarkę, bądź kompilowaliśmy ze źródeł, należy przejść do katalogu w którym znajduje się plik flume-ng (flume/bin) u mnie to wyglądało tak:

/root/flume/apache-flume-1.4.0-bin/bin

Pobrałem binarkę do katalogu /root/flume, tam ją rozpakowałem i powstał katalog apache-flume-1.4.0-bin.

 

Jeśli bawiliśmy się w instalację ze źródeł, która jest dość upierdliwa:

/root/download/apache-flume-1.4.0-src/flume-ng-dist/target/apache-flume-1.4.0-bin/apache-flume-1.4.0-bin/bin

To ścieżka może wyglądać plus minus tak.

Teraz czas na Konfiguracja Flume

Konfiguracja Wirtualki – HortonWorks Sandbox

Krok pierwszy to uruchomienie wirtualki z Hortonworks. Generalnie wystarczy pobrany obraz zaimportować do virtualbox. Jednakże by wszystko działało zgodnie z naszymi oczekiwaniami powinniśmy zmienić ustawienia sieci. Przed uruchomieniem maszyny wirtualnej w ustawieniach (devices/network) należy Adapter 1 ustawić jako host only(attached to). Należy także kliknąć zakładkę z adapter 2 i tam wybrać opcję NAT (attached to). Zgodnie z obrazkami poniżej:

Wybierz odpowiednią wirtualkę
wybranie odpowiedniej wirtualki
ustawienia wirtualki
Wybierz ustawienia z menu w celu edycji ustawień sieciowych
adapter 1
ustawienia karty sieciowej nr 1.
adapter 2
ustawienia karty sieciowej nr 2.

W ten sposób maszyna wirtualna może korzystać z sieci Internet oraz swobodnie komunikować się w obie strony z maszyną hostem. Można się także zastanowić nad przydzieleniem większej ilości pamięci Ram. Ja przypisałem swojej wirtualce 4GB ram (komputer ma 8GB pamięci). W ustawieniach w zakładce system należy przesunąć suwak:

zmiana pamięci RAM
przesuń suwak na odpowiednią wartość

 

Jeśli korzystamy z systemu Windows, przydadzą nam się jeszcze 2 freeware’’owe narzędzia: putty oraz winscp. Pierwsze to aplikacja do podłączenia się do linii komend naszej maszyny wirtualnej. Druga aplikacja pozwala na kopiowanie i przenoszenie plików pomiędzy maszynami.

Gdy mamy już komplet zabawek, odpalamy maszynę wirtualną.  Po jej uruchomieniu w oknie virtualbox pokaże się komunikat z adresem http.

ekran powitalny hortonworks
Ekran powitalny hortonworks

Należy w tym momencie wcisnąć alt + f5 (zgodnie z informacjami na dole ekranu). Pojawi się okno z logowaniem (oraz z loginem i hasłem 😉 ).

logowanie do hortonworks
logowanie do hortonworks

Nasza maszyna jest dość uboga w narzędzia dodatkowe, możemy poruszać się za pomocą linii komend co dla pewnych osób może być dość upierdliwe. Najpierw sprawdźmy czy wszystko działa.

Weryfikacja adresów IP

Wywołajmy komendę ifconfig eth0, powinna ona zwrócić coś takiego:

eth0      Link encap:Ethernet  HWaddr 08:00:27:B1:D7:08
inet addr:192.168.56.101  Bcast:192.168.56.255  Mask:255.255.255.0
inet6 addr: fe80::a00:27ff:feb1:d708/64 Scope:Link
UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
RX packets:26301 errors:0 dropped:0 overruns:0 frame:0
TX packets:28872 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:4005290 (3.8 MiB)  TX bytes:26068182 (24.8 MiB)
Interrupt:19 Base address:0xd020

Wklejenie adresu IP do naszej przeglądarki powinno nam wyświetlić okno powitalne ‘sandboxa’.

Okno powitalne Sandbox
Okno powitalne Sandbox, Interfejs HUE

Po kliknięciu go to sandbox, powinno nam się pojawić okno w którym możemy pracować z różnymi narzędziami zbudowanymi wokół Hadoop.

Korzystając z tego samego adresu IP, powinniśmy się podłączyć przez putty do tej samej linii komend. Po podłączeniu się sprawdźmy czy nasza maszyna wirtualna posiada dostęp do Internetu, możemy wykonać komendę ping (komendy są wyboldowane, można je skopiować, wkleja się do putty po przez kliknięcie prawym przyciskiem myszy):

Weryfikacja działania sieci

[root@sandbox ~]# ping onet.pl

PING onet.pl (213.180.141.140) 56(84) bytes of data.

64 bytes from sg1.any.onet.pl (213.180.141.140): icmp_seq=2 ttl=58 time=24.2 ms

64 bytes from sg1.any.onet.pl (213.180.141.140): icmp_seq=3 ttl=58 time=17.2 ms

 

Jeśli ukaże się coś podobnego znaczy się że jest ok. Aby przerwać ‘pingowanie’ należy wcisnąć ctrl + c.

Wtedy ukaże się coś takiego:

— onet.pl ping statistics —

3 packets transmitted, 2 received, 33% packet loss, time 2445ms

rtt min/avg/max/mdev = 17.220/20.733/24.246/3.513 ms

 

Instalacja midnight commander’a

Teraz możemy zainstalować midnight commander’a (odpowiednik Norton commandera/ lub Total Commandera). Aby to uczynić należy wklepać:

[root@sandbox ~]# yum install -y mc

Pokażą się różne komunikaty, które po chwili powinny zakończyć się plus minus tak:

Installed:

mc.x86_64 1:4.7.0.2-3.el6

Complete!

Aktualizacja JDK javy

Aby wszystko działało w pełni sprawnie byłem zmuszony do przeprowadzenia aktualizacji JDK Javy. Wirtualka HortonWorks została dostarczona z wersją 1.6 a Flume(lub któryś z jego dodatków) potrzebował wersji 1.7. Z strony Oracle pobrałem odpowiednie jdk z postaci RPM dla architektury 64bit (System operacyjny to CentOS). Jako że ze strony Oracle Wget nie chciał pobrać tego rpm’a pobrałem go z poziomu hosta (mojego Windowsa). Na maszynę wirtualną skopiowałem plik przy pomocy winscp (podajemy adres IP wirtualnej maszyny i login i hasło do konsoli). W katalogu w którym się znajduje plik odpalamy komendę:

[root@sandbox ~]# rpm -i jdk-7u51-linux-x64.rpm

Później trzeba jeszcze zaktualizować zmienną środowiskową wskazującą na katalog w którym java jest zainstalowana:

[root@sandbox ~]# export JAVA_HOME=’/usr/java/jdk1.7.0_51/’

 

Proszę zwrócić uwagę iż z czasem nr wersji mogą się zmienić I skopiowanie komend może nie przynieść oczekiwanych rezultatów.,

Kolejny krok to: Instalacja Flume.

Cloudera i HortonWorks

Mając już mgliste pojęcie o tym czym jest Flume można by się zastanowić do czego go użyć. Dość powszechnym przykładem w sieci jest wykorzystanie API strumieniowego Twittera do zapisu logów w HDFS wraz z późniejszym wykorzystaniem HIVE by odpytywać zebrane informacje za pomocą SQL (Teraz wiadomo dlaczego się tym zająłem;) ). Nie mniej by zebrać to wszystko do przysłowiowej kupy i uruchomić trzeba się trochę napocić. Najwięcej opisów/tutoriali pochodzi ze stron Cloudera, jest to firma/organizacja która rozwija rozwiązania oparte o Hadoop’a i żyje z wdrożeń i szkoleń (dodatkowo to oni napisali końcówkę do obsługi Twittera dla Flume). Jej bezpośrednim konkurentem jest Hortonworks, który w mojej ocenie posiada bardziej otwartą politykę (więcej materiałów jest dostępnych za darmo). Obydwie organizacje oferują gotowe obrazy do virtualbox z w pełni skonfigurowanymi środowiskami (Hadoop, Hive itp.). Obydwa środowiska oczywiście nie zawierają Flume ;). Ja osobiście bardziej przekonałem się do środowiska dostarczanego przez Hortonworks i oto środowisko opieram poniższą instrukcję/tutorial.

 

Zanim zaczniesz chciałbym uprzedzić, iż będzie potrzebna znajomość środowiska linux.

Jeśli jest gotowy czytaj dalej: Konfiguracja Wirtualki – HortonWorks Sandbox

Big Data & Flume – zbieranie danych

Ostatnimi czasy modne stało się hasło Big Data. Powstało wiele narzędzi i rozwiązań wspierających analizy danych w sposób który do tej pory był praktycznie niedostępny dla większości organizacji. Wraz ze wspomnianymi narzędziami pojawiły się możliwości analizy online (w momencie gdy dane się pojawią). Dodatkowo w dość łatwy sposób można wykorzystać źródła danych których struktura znacząco odbiega od tak zwanych płaskich tabel.

Dość popularnym frameworkiem (kombajnem) do obsługi Big Data stał się Hadoop tworzony przez Apache foundation. Nie jest to typowa baza danych a raczej zestaw narzędzi napisany w javie, którego celem jest zarządzanie danymi znajdującymi się na wielu różnych maszynach(tworzenie farm) dzięki czemu jesteśmy wstanie obrabiać naprawdę duże zbiory danych.

Jedną z aplikacji która współpracuje z Hadoop’em  jest Flume. Generalnie można powiedzieć że to aplikacja stworzona do przekazywania różnych zdarzeń. Cała koncepcja programu skupia się na 3 głównych bytach:

Sources – Są to źródła, końcówki, które zbierają/nasłuchują na zdarzenia. Np. powstanie wpisu w logu. Już dziś w standardowym pakiecie FLume mamy do dyspozycji kilka zaimplementowanych źródeł. Jeśli nie spełniają one naszych oczekiwań, przy znajomości języka Java możemy tworzyć w dość przystępny sposób obsługę innych źródeł. W sieci pojawiają się także projekty w których powstają biblioteki do obsługi różnych źródeł (np. Twitter).

Channels – Kanały, pozwalają zarządzać transferem informacji do źródeł di sinków (destynacji). Źródła dodają informacje do kanału a ‘sinks’ je z niego usuwają. Tak więc kanał staje się buforem na dane. Mamy do wyboru kilka różnych kanałów oraz wiele opcji konfiguracyjnych.

Sinks – Destynacje, gdy zbierzemy już dane ze źródła (z kanału) należy je gdzieś zapisać. Podobnie jak w przypadku źródeł, mamy do dyspozycji wiele gotowych implementacji. Pierwotnym ‘Sinkiem’ był hdfs, czyli system plików używanych przez Hadoop.

Postaram się tutaj przedstawić podstawy konfiguracji Flume, oraz możliwości analizy danych zbieranych przez to narzędzie.

Czytaj dalej  Cloudera i HortonWorks , czyli kolejna część tego kursu.