====== Wstęp ====== Cassandra jest magazynem klucz-wartość. Łączy ona rozproszone technologie Dynamo oraz model danych z Google BigTable. Podobnie ja Dynamo zakłada, iż po odpowiednio długim czasie bez nowych zmian wszystkie repliki bedą identyczne, natomiast tak jak BigTable dostarcza modele przypominające tabele. Cassandra została otwarta przez Facebooka w 2008 roku. Aktualnie rozwijana jest przez Apache oraz współpracowników z różnych firm. ====== Instalacja i Konfiguracja ====== Poniższy opis dotyczy instalacji i konfiguracji na systemie linuksowym a konkretniej na Debianie. W innych systemach operacyjnych kroki te przebiegają analogicznie. Debian został wybrany ze względu na częste wykorzystywanie go jako OS dla serwera baz danych. ===== Apache Thrift ===== Thrift wykorzystywany jest do tworzenie skalowalnych, multiplatformowych usług webowych. Generuje klienty RPC w dowolnym języku (z pośród C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk, OCaml) które mogą komunikować się z tym samym serwerem RPC. Dzięki takiemu podejściu Cassandra może być dostępna w łatwy i szybki sposób z pośród wielu języków programowania. instalacja dodatkowych pakietów na debianie * libboost-all-dev * bison * flex konfiguracja (bez wsparcia dla ruby): ./configure --disable-gen-rb --without-ruby make make install do poprawnego działania z pythonem należy jeszcze ręcznie zainstalować biblioteki: cd lib/py python setup.py install linki:\\ tutorial do thrifta [[http://wiki.apache.org/thrift/Tutorial]]\\ ===== Cassandra ===== najprostsza instalacja polega na rozpakowaniu tarballa z plikami cassandry i skonfigurowaniu ścieżek dostępu do logów i do danych w pliku conf/storage-conf.xml Jeżeli nie chcemy zmieniać defaultowych ścieżek wystarczy że stworzymy poniższe katalogi tak aby odpowiadały tym zdefiniowanym: sudo mkdir -p /var/log/cassandra sudo chown -R `whoami` /var/log/cassandra sudo mkdir -p /var/lib/cassandra sudo chown -R `whoami` /var/lib/cassandra Uruchmienie pojedynczego węzła: bin/cassandra -f # -f loguje na stdout testowanie węzła poprzez konsole cli: bin/cassandra-cli --host localhost --port 9160 ====== Model ====== Cassandra posiada model danych, który najlepiej wyobrazić sobie jako cztero lub pięcio wymiarowy hash. Podstawowe pojęcia to: * "Cluster" - maszyny/węzły w logicznej instancji Cassandry. * "Keyspace" - przestrzeń nazw ColumnFamilies, zazwyczaj jedna na wniosek. * "ColumnFamilies" - zawierają określoną ilość column, z których każda posiada nazwę, wartość, timestamp i odwołuje się do nich poprzez row keys. * "SuperColumns" - mogą być rozumiane jako kolumny, które same są podkolumnami. ===== Opis kolejnych struktur danych Cassandry ===== ==== Kolumny (Columns) ==== Jest to najmniejszy/niższy przyrost danych. Określane są jako trójki zawierające nazwę, wartość oraz czas. Definicja: struct Column { 1: binary name, 2: binary value, 3: i64 timestamp, } Wystkie wartości wypełnia klient, dlatego ważna jest synchronizacja na komputerach klienckich. Wartość timestamp może być dowolna ale konwenją jest czas w milisekundach od 1970 roku. Używa się go do rozwiązywania konfliktów. W wielu przypadkach pomija się to pole i myśli o kolumnie jako o parze nazwa/wartość. ==== Rodziny kolumn (Column Families) ==== Jest to pojemnik na kolumny (analogiczny do tabeli w relacyjnym systemie). Posiadają upożadkowaną listę kolumn, do których można odwaołać się poprzez nazwę. Definiuje się je w pliku storage-conf.xml, kórego nie można modyfikować bez restartowania Casandry. ==== Wiersze (Rows) ==== W Casasndrze, każda ColumnFamily jest przechowywana w oddzielnym pliku, który z kolei jest posortowany w wierszu (Row) okreslonym pożądku np. po kluczu. Powiązane kolumny, do których jest wspólny dostęp powinny być w jedej rodzinie kolumn. Klucz wiersza (row key) określa jakie dane są przechowywane na maszynie. Tak więc każdy klucz może być powiązany z danymi z różnych rodzin kolumn. Jest to jednak odrębne logicznie, dlatego interfejs Thrift jest zorientowany tak aby możliwy był dostęp do jedenego ColumnFamily poprze klucz w danej chwili. Przykład: { "klucz":{ "Uzytkownicy":{ "adresEmail":{"name":"adresEmail", "value":"user.email@email.com"}, "stronaWWW":{"name":"stronaWWW", "value":"http://userstrona.com"} }, "Statystyki":{ "wejscia":{"name":"wejscia", "value":"1000"} } }, "user2":{ "Uzytkownicy":{ "adresEmail":{"name":"adresEmail", "value":"user2@email.com"}, "twitter":{"name":"twitter", "value":"user2"} } } } Klucz "klucz" identyfikuje dane w dwóch rożnych rodzinach kolumn ("Uzytkownicy" i "Statystyki"), co wcale nie oznacza, że dane z tych kolumn są powiązane. Podsiadanie danych z tym samym kluczem dla róznych rodzin kolumn zalezy wyłacznie od aplikacji. Co więcej, rodzina kolumn "Uzytkownicy" posiada różne nazwy kolumn dla "klucz" oraz "user2" co jest dozwolone w Cassandrze. ==== Keyspaces ==== Jest to pierwszy wymiar cassandrowego hasha. Są kontenerem dla column families. Są mnie j więcej tej ziarnistości co schemat lub baza w relaycjnym systemie. Stanowią punkt konfiguracyjny i zarządzający dla column families. Są rownież strukturami na których wykonuje się "batch insert". ==== Super kolumny (Super Columns) ==== Są to kolumny, których wartościami są super kolumny. Oznacza to ze super kolumna jest posortowana tablicą asocjacyjną kolumn. O kolumnach i super kolumnach można myśleć jako o mapach: * wiersz w rodzinie kolumn jest posortowaną mapą nazw kolumn do wartości. * super rodzina kolumn to posortowana mapa nazw super kolumn do wartości super kolumn. { "mccv": { "Tags": { "cassandra": { "incubator": {"incubator": "http://incubator.apache.org/cassandra/"}, "jira": {"jira": "http://issues.apache.org/jira/browse/CASSANDRA"} }, "thrift": { "jira": {"jira": "http://issues.apache.org/jira/browse/THRIFT"} } } } } "Tags" - rodzina kolumn, "cassandra", "thrift" - super kolumny. ==== Range queries ==== Cassandra wspiera schematy partycjionowania o stosunkowo niewielkiej ilości kodu. Dostarcza ona dwóch narzędzi: * RandomPartitioner - dostarcza równoważenie obciążenia bez konieczności dalszej pracy. * OrderPreservingPartitioner - umożliwia wykonanie zapytań na przechowywanych kluczach, ale wymaga uważnego wyboru tokenów węzłów oraz oktywnego równoważenia obciążenia. System, który wspiera wyłącznie partycjonowanie oparte na has'ach, nie może efektywnie wywoływać dużych zapytań. ====== Hello World w Javie ====== ===== Wstępna konfiguracja ===== Do korzystania z Cassandry w Javie, będziemy używać klienta **Thrift**. Thrift pozwala oprócz Javy na pracę z wieloma innymi językami np. C++, PHP, Python. Aby móc zacząć pracę musimy najpierw skonfigurować bazę Cassandry poprzez modyfikacje pliku konfiguracyjnego **storage-conf.xml** znajdującego się w katalogu **CASSANDRA_HOME/conf**. Pomiędzy elementy **** i **** wstawiamy następujące linie: Keyspace odpowiada schematowi w relacyjnej bazie danych. Dajemy mu nazwę Geography. Właśnie pomiędzy tymi znacznikami umieszczamy wszystkie pozostałe instrukcje. ColumnFamily jest strukturą w której możemy przechowywać "wiersze", możemy utożsamiać ją z tabelą w RDBMS. Naszą "tabelę" nazywamy Cities. Atrybut CompareWith służy do sortowania danych. W Cassandrze nie mamy możliwości tak jak w relacyjnej bazie wykonywania zapytań i otrzymywania posortowanych wyników. Ustawienie atrybutu powoduje że dane zostają posortowane w momencie wkładania ich do bazy i takie w niej już pozostają. W naszym przypadku wartość **"UTF8Type"** powoduje że klucze kolumn będą traktowane jako stringi UTF8. org.apache.cassandra.locator.RackUnawareStrategy 1 org.apache.cassandra.locator.EndPointSnitch Powyższe linie są niezbędne aby móc operować i pracować z bazą Cassandry. Zostawiamy tutaj domyślne wartości. ==== Środowisko ==== Przykładowy program obrazujący pracę z Cassandrą zostanie wykonany w środowisku **Netbeans**. Aby móc pracować bez przeszkód i aby w ogóle mieć możliwość zaczęcia pracy z Cassandrą należy po stworzeniu nowego projektu dodać odpowiednie biblioteki do niego. Dokonujemy tego poprzez kliknięcie prawym przyciskiem na **Libraries** w drzewku z projektem. Wybieramy **Add JAR/Folder** i wskazujemy pliki znajdujące się w folderze **CASSANDRA_HOME/lib**. ===== Opis programu ===== Po użytych nazwach widzimy że nasz przykładowy program będzie przechowywał informacje o miastach. Tabela „Cities” zawiera odwzorowanie klucz-wartość. Kluczami będą nazwy miast np. „Kraków”, „Warszawa”, natomiast wartości będą „kolumnami”. Mówiąc kolumna mamy tutaj jednak na myśli strukturę „Column” z Cassandry która to zawiera trzy pola nazwę, wartość oraz czas. Kolumnami w naszej bazie będą „country” - kraj w którym leży dane miasto, „population” - jego ludność, „area” - jego powierzchnia. ===== Implementacja w Cassandrze ===== Ok, możemy teraz już zacząć pracę z kodem. ==== Nawiązanie połączenia ==== Pierwsze co musimy to połączyć się z bazą Cassandry – łączymy się na porcie 9160 domyślnym porcie na którym nasłuchuje Cassandra, po czym przekazujemy połączenie klientowi który zajmuje się utrzymaniem komunikacji z serwerem. Poniższa funkcja wykonuje to zadanie. private static Cassandra.Client setupConnection() throws TTransportException { try { tr = new TSocket("localhost", 9160); TProtocol proto = new TBinaryProtocol(tr); Cassandra.Client client = new Cassandra.Client(proto); tr.open(); return client; } catch (TTransportException exception) { exception.printStackTrace(); } return null; } Zamknięcie połączenia polega na wymuszeniu wykonania wszystkich zadań które mogą znajdować się w buforze i zamknięciu Socketa: private static void closeConnection() { try { tr.flush(); tr.close(); } catch (TTransportException exception) { exception.printStackTrace(); } } ==== Zapis do bazy ==== Przyjrzymy się teraz jak będziemy dodawać i przechowywać nasze miasta w bazie. private static void createCities(Cassandra.Client client, String key, String country, String population, String area) { try { long timestamp = System.currentTimeMillis(); Map> job = new HashMap>(); List columns = new ArrayList(); Column column = new Column("country".getBytes(KODOWANIE), country.getBytes(KODOWANIE), timestamp); ColumnOrSuperColumn columnOrSuperColumn = new ColumnOrSuperColumn(); columnOrSuperColumn.setColumn(column); columns.add(columnOrSuperColumn); column = new Column("population".getBytes(KODOWANIE), population.getBytes(KODOWANIE), timestamp); columnOrSuperColumn = new ColumnOrSuperColumn(); columnOrSuperColumn.setColumn(column); columns.add(columnOrSuperColumn); column = new Column("area".getBytes(KODOWANIE), area.getBytes(KODOWANIE), timestamp); columnOrSuperColumn = new ColumnOrSuperColumn(); columnOrSuperColumn.setColumn(column); columns.add(columnOrSuperColumn); job.put(TABELA, columns); client.batch_insert(SCHEMA, key, job, ConsistencyLevel.ALL); } catch (Exception exception) { exception.printStackTrace(); } } Jak już wspomnieliśmy wyżej każda kolumna musi zawierać pole **timestamp**. Pierwszą rzeczą którą robimy jest stworzenie zmiennej przechowującej czas utworzenia. Jest ona używana do rozwiązywania konfliktów gdy mamy kilka podobnych kluczy. Dlatego nie należy nigdy ustawiać takich samych wartości tego pola; najczęściej stosuje się liczbę milisekund które upłynęły od roku 1970. W następnej linii tworzymy mapę w której będziemy umieszczać wiersze. Kluczem w naszej mapie będzie String który jest nazwą ColumnFamily czyli naszą „tabelą” zdefiniowaną wcześniej w pliku konfiguracyjnym. Wartością mapy będzie lista specjalnej struktury mogącej przechowywać Columnę lub SuperColumnę, u nas to będą jak już wcześniej wspomnieliśmy Columny czyli konstrukcja przechowująca trzy pola. W kolejnych liniach tworzymy instacje naszych Column: country, population i area oraz wstawiamy do nich odpowiednie wartości przesłane jako argumenty funkcji, a następnie każdą Columne dodajemy do listy. Na końcu używamy metody **batch_insert** do zapisania wszystkich danych jednorazowo w bazie. W przypadku gdybyśmy chcieli zapisać tylko pojedynczą Columne należy użyć metody **insert**. Jako parametry dla metody batch_insert wykorzystujemy klucz naszej tabeli czyli miasto do którego odnoszą się wszystkie dane zapisane w strukturze Column, nazwę schematu oraz mapę o której mówiliśmy wcześniej. Ostatnim parametrem jest tzw. **ConsistencyLevel** który jest wykorzystywany przy wszystkich operacjach zapisu oraz czytania z bazy i wskazuje kiedy żądanie klienta zostaje wykonane. Typ **ALL** mówi, że zapis powinien się odbyć przed udzieleniem odpowiedzi klientowi. Dokładne znaczenie każdego parametru jest wyjaśnione na stronie wiki Cassandry [[http://wiki.apache.org/cassandra/API#ConsistencyLevel]]. ==== Odczyt z bazy ==== Jeśli zapisaliśmy dane do bazy, pewnie chcielibyśmy je z niej odczytać. Poniżej znajduje się funkcja realizująca odczytanie wszystkich naszych kolumn z tabeli: private static void selectAllCities(Cassandra.Client client) { try { KeyRange keyRange = new KeyRange(3); keyRange.setStart_key(""); keyRange.setEnd_key(""); SliceRange sliceRange = new SliceRange(); sliceRange.setStart(new byte[] {}); sliceRange.setFinish(new byte[] {}); SlicePredicate slicePredicate = new SlicePredicate(); slicePredicate.setSlice_range(sliceRange); ColumnParent columnParent = new ColumnParent(TABELA); List keySlices = client.get_range_slices(SCHEMA, columnParent, slicePredicate, keyRange, ConsistencyLevel.ONE); for (KeySlice keySlice : keySlices) { printToConsole(keySlice.getKey(), keySlice.getColumns()); } } catch (Exception exception) { exception.printStackTrace(); } } **KeyRange** jest używane do wyspecyfikowania jaki zakres kluczy chcemy wyciągnąć z bazy. W naszym przypadku w rzeczywistości nie definiujemy zakresu a jedynie ilość wierszy które chcemy wczytać z bazy, dlatego w konstruktorze przekazujemy liczbę 3 co odpowiada trzem wierszom. **SliceRange** jest strukturą przechowującą informacje o zakresie, kolejności oraz ilości dla zapytania które zwraca wiele kolumn. Można ją utożsamiać z poleceniami LIMIT oraz ORDER BY z języka SQL. Metoda **setStart** ustawia kolumnę od której powinniśmy zacząć pobieranie danych. Pusta tablica byte oznacza rozpoczęcie od pierwszej kolumny. Metoda **setFinish** ustawia Columne na której powinniśmy skończyć pobieranie danych. Pusta tablica byte oznacza odzyskanie wszystkich Column dopóki nie uzyskamy wartości count. SliceRange posiada jeszcze jedną funkcje **setCount** której nie użyliśmy tutaj, a oznaczającą liczbę Column które chcemy zwrócić – coś jak LIMIT w SQL. Następnie dzięki **SlicePredicate** ustawiamy dane które chcemy odzyskać, u nas jest to zdefiniowany wcześniej przy użyciu SliceRange pewien zakres kolumn. W końcu dzięki metodzie **get_range_slices** wczytujemy dane i przypisujemy je do listy obiektów **KeySlice**. Jest to obiekt przechowujący odzyskane wiersze zawierający klucz i kolumny należące do danego wiersza. Instrukcje wykonywane w funkcji main to: Cassandra.Client client = setupConnection(); removeCities(client, "Mediolan"); removeCities(client, "Warszawa"); removeCities(client, "Kraków"); System.out.println("Dodaje miasta"); createCities(client, "Mediolan", "włochy", "1308735", "18377"); createCities(client, "Warszawa", "polska", "1714446", "51724"); createCities(client, "Kraków", "polska", "754854", "32700"); System.out.println("Wyciagam dane z bazy:"); selectAllCities(client); closeConnection(); Na początku ustanawiamy połączenie. Kolejne trzy instrukcje to usuniecie wierszy z bazy na wypadek gdyby już z danym kluczem znajdowały się w bazie. W kolejnych liniach dodajemy trzy miasta: „Mediolan”, „Warszawa” „Kraków”, a następnie wczytujemy i wypisujemy dopiero co włożone informacje na ekran. Otrzymany rezultat wygląda następująco: Dodaje miasta Wyciągam dane z bazy: Key: 'Warszawa' name: 'area', value: '51724', timestamp: 1276876711965 name: 'country', value: 'polska', timestamp: 1276876711965 name: 'population', value: '1714446', timestamp: 1276876711965 Key: 'Kraków' name: 'area', value: '32700', timestamp: 1276876711967 name: 'country', value: 'polska', timestamp: 1276876711967 name: 'population', value: '754854', timestamp: 1276876711967 Key: 'Mediolan' name: 'area', value: '18377', timestamp: 1276876711924 name: 'country', value: 'włochy', timestamp: 1276876711924 name: 'population', value: '1308735', timestamp: 1276876711924 Wpis z deskryptora: org.apache.cassandra.locator.RackUnawareStrategy 1 org.apache.cassandra.locator.EndPointSnitch ===== Cały plik z kodem do ściągnięcia ===== package cassandracities; /** * * @author johnny */ import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.cassandra.thrift.Cassandra; import org.apache.cassandra.thrift.Column; import org.apache.cassandra.thrift.ColumnOrSuperColumn; import org.apache.cassandra.thrift.ColumnParent; import org.apache.cassandra.thrift.ColumnPath; import org.apache.cassandra.thrift.ConsistencyLevel; import org.apache.cassandra.thrift.InvalidRequestException; import org.apache.cassandra.thrift.KeyRange; import org.apache.cassandra.thrift.KeySlice; import org.apache.cassandra.thrift.NotFoundException; import org.apache.cassandra.thrift.SlicePredicate; import org.apache.cassandra.thrift.SliceRange; import org.apache.cassandra.thrift.TimedOutException; import org.apache.cassandra.thrift.UnavailableException; import org.apache.thrift.TException; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import org.apache.thrift.transport.TTransportException; public class Main { private static final String SCHEMA = "Geography"; private static final String TABELA = "Cities"; public static final String KODOWANIE = "utf-8"; private static TTransport tr = null; public static void main(String[] args) throws TException, InvalidRequestException, UnavailableException, UnsupportedEncodingException, NotFoundException, TimedOutException { Cassandra.Client client = setupConnection(); //System.out.println("Usuwam miasta o danym kluczu, na wypadek gdyby już znajdowały się w bazie"); removeCities(client, "Mediolan"); removeCities(client, "Warszawa"); removeCities(client, "Kraków"); System.out.println("Dodaje miasta"); createCities(client, "Mediolan", "włochy", "1308735", "18377"); createCities(client, "Warszawa", "polska", "1714446", "51724"); createCities(client, "Kraków", "polska", "754854", "32700"); System.out.println("Wyciagam dane z bazy:"); selectAllCities(client); closeConnection(); } private static Cassandra.Client setupConnection() throws TTransportException { try { tr = new TSocket("localhost", 9160); TProtocol proto = new TBinaryProtocol(tr); Cassandra.Client client = new Cassandra.Client(proto); tr.open(); return client; } catch (TTransportException exception) { exception.printStackTrace(); } return null; } private static void closeConnection() { try { tr.flush(); tr.close(); } catch (TTransportException exception) { exception.printStackTrace(); } } private static void removeCities(Cassandra.Client client, String key) { try { ColumnPath columnPath = new ColumnPath(TABELA); client.remove(SCHEMA, key, columnPath, System.currentTimeMillis(), ConsistencyLevel.ALL); } catch (Exception exception) { exception.printStackTrace(); } } private static void createCities(Cassandra.Client client, String key, String country, String population, String area) { try { long timestamp = System.currentTimeMillis(); Map> job = new HashMap>(); List columns = new ArrayList(); Column column = new Column("country".getBytes(KODOWANIE), country.getBytes(KODOWANIE), timestamp); ColumnOrSuperColumn columnOrSuperColumn = new ColumnOrSuperColumn(); columnOrSuperColumn.setColumn(column); columns.add(columnOrSuperColumn); column = new Column("population".getBytes(KODOWANIE), population.getBytes(KODOWANIE), timestamp); columnOrSuperColumn = new ColumnOrSuperColumn(); columnOrSuperColumn.setColumn(column); columns.add(columnOrSuperColumn); column = new Column("area".getBytes(KODOWANIE), area.getBytes(KODOWANIE), timestamp); columnOrSuperColumn = new ColumnOrSuperColumn(); columnOrSuperColumn.setColumn(column); columns.add(columnOrSuperColumn); job.put(TABELA, columns); client.batch_insert(SCHEMA, key, job, ConsistencyLevel.ALL); } catch (Exception exception) { exception.printStackTrace(); } } private static void selectAllCities(Cassandra.Client client) { try { KeyRange keyRange = new KeyRange(3); keyRange.setStart_key(""); keyRange.setEnd_key(""); SliceRange sliceRange = new SliceRange(); sliceRange.setStart(new byte[] {}); sliceRange.setFinish(new byte[] {}); SlicePredicate slicePredicate = new SlicePredicate(); slicePredicate.setSlice_range(sliceRange); ColumnParent columnParent = new ColumnParent(TABELA); List keySlices = client.get_range_slices(SCHEMA, columnParent, slicePredicate, keyRange, ConsistencyLevel.ONE); for (KeySlice keySlice : keySlices) { printToConsole(keySlice.getKey(), keySlice.getColumns()); } } catch (Exception exception) { exception.printStackTrace(); } } private static void printToConsole(String key, List result) { try { System.out.println("Key: '" + key + "'"); for (ColumnOrSuperColumn c : result) { if (c.getColumn() != null) { String name = new String(c.getColumn().getName(), KODOWANIE); String value = new String(c.getColumn().getValue(), KODOWANIE); long timestamp = c.getColumn().getTimestamp(); System.out.println(" name: '" + name + "', value: '" + value + "', timestamp: " + timestamp); } else { } } } catch (UnsupportedEncodingException exception) { exception.printStackTrace(); } } } ====== Hello World w pythonie ====== ===== Testowanie cassandry w pythonie ===== * generujemy interfejs do cassandry thrift -v -gen py path_to_cassandra/interface/cassandra.thrift * wygenerowało nam pakiet znajdujacy się w folderze path_to_cassandra/interface/gen-py/cassandra * możemy teraz ten pakiet zaimportować w pythonie: import Cassandra * oprócz biblioteki wygenerowany został plik do testowania: path_to_cassandra/interface/gen-py/cassandra/Cassandra-remote Jest to program pythonowy wykorzystujący wygenerowaną bibliotekę z pliku Cassandra.py * Możemy przetestować jego działanie: ./Cassandra-remote -h localhost:9160 describe_version ./Cassandra-remote -h localhost:9160 describe_keyspace Keyspace1 ===== implementacja Hello World ===== Została wykonana na podstawie wygenerowanego pliku Cassandra-remote. Konfiguracja w pliku conf/storage-conf.xml org.apache.cassandra.locator.RackUnawareStrategy 1 org.apache.cassandra.locator.EndPointSnitch #!/usr/bin/env python import sys import time import pprint from urlparse import urlparse from thrift.transport import TTransport from thrift.transport import TSocket from thrift.transport import THttpClient from thrift.protocol import TBinaryProtocol import Cassandra from ttypes import * pp = pprint.PrettyPrinter(indent = 2) host = 'localhost' port = 9160 uri = '' framed = False http = False argi = 1 if len(sys.argv)>1 and sys.argv[argi] == '-h': parts = sys.argv[argi+1].split(':') host = parts[0] port = int(parts[1]) argi += 2 if http: transport = THttpClient.THttpClient(host, port, uri) else: socket = TSocket.TSocket(host, port) if framed: transport = TTransport.TFramedTransport(socket) else: transport = TTransport.TBufferedTransport(socket) protocol = TBinaryProtocol.TBinaryProtocol(transport) client = Cassandra.Client(protocol) transport.open() class Dict: def translate(self, lang_from, word_from, lang_to): colPath = ColumnPath("Words", lang_from, lang_to) try: ret = client.get('Dictionary', word_from, colPath, ConsistencyLevel.ONE,) if ret: print "translate " + word_from + " (" + lang_from + " - " +lang_to+"): " + ret.column.value except NotFoundException: print "there is no translate for " + word_from + " (" + lang_from + " - " +lang_to+")" def getAllTranslates(self, lang_from, word_from): colParent = ColumnParent("Words", lang_from) slPred = SlicePredicate(None, SliceRange('','')) ret = client.get_slice('Dictionary', word_from, colParent, slPred, ConsistencyLevel.ONE,) print "all transalte for word " + word_from + " (" + lang_from + "):" for item in ret: print " translate " + word_from + " (" + lang_from + " - " +item.column.name+"): " + item.column.value def addWord(self, lang_from, lang_to, word_from, word_to): colPath = ColumnPath("Words", lang_from, lang_to) timestamp = int(time.time()) ret = client.insert('Dictionary', word_from, colPath, word_to, timestamp, ConsistencyLevel.ONE,) if ret is None: print "inserted word "+word_from+" ("+lang_from+")" + " - " +word_to+" ("+lang_to+")" else: print ret def removeWord(self, lang_from, lang_to, word_from): colPath = ColumnPath("Words", lang_from, lang_to) timestamp = int(time.time()) ret = client.remove('Dictionary', word_from, colPath, timestamp, ConsistencyLevel.ONE,) if ret is None: print "removed word " + word_from + " ("+lang_from+" - "+lang_to+")" dic = Dict() dic.addWord("pl","en","dom","home") dic.addWord("pl","de","dom","house") dic.addWord("pl","cz","dom","holpiczka") print dic.removeWord("pl","de","dom") print dic.translate("pl", "dom", "en") dic.translate("pl", "dom", "cz") dic.translate("pl", "dom", "de") print dic.getAllTranslates("pl","dom") transport.close() ====== Wady oraz zalety ====== Jak każda technologia Cassandra posiada w sobie zarówno wady jak i zalety. Oto część z nich: ===== Wady ===== * jest to nowa, innowacyjna technologia dlatego nie cieszy sie dużym zaufaniem wśród korporacji/firm, * w niektórych dziedzinach zastosowanie jej jest niewystaczające np. systemy bankowe * wymaga zmiany "podejścia" programistów (z relacyjnego do magazynu). ===== Zalety ===== * Szybka i wydajna - zapis 50GB w 0.12 milisekundy (ponad 2500 razy szybciej od MySQL). * Zdecentralizowana – każdy węzeł w klastrze jest identyczny. Nie ma słabego punktu którego usterka spowodowałaby zatrzymanie pracy całej bazy. * Odporna na uszkodzenia – dane są automatycznie replikowane do wielu węzłów. Zepsute węzły mogą być zastąpione bez żadnego przestoju. * Elastyczna – wydajność zapisywania i odczytywania wzrasta liniowo wraz z dodawaniem nowych maszyn bez żadnego przestoju lub przerw w działaniu aplikacji. * Wytrzymała - Cassandra jest odpowiednia dla aplikacji, które nie mogą sobie pozwolić na utratę danych, nawet gdy całe centrum danych padnie * "Sprawdzona" - mimo wciąż niezbyt wielkiej popularności z Cassandry korzystają takie firmy jak Digg, Facebook, Twitter, Reddit, Rackspace, Cloudkick, Cisco, SimpleGeo, Ooyala, OpenX, i wiele innych posiadających duże zbiory danych. Największy klaster danych zawiera ponad 100TB danych na ponad 150 urządzeniach. ==== Pozostałe możliwości Cassandry ==== * Posiada bogaty model danych, który umożliwia bardziej efektywne użytkowanie w porównaniu do prostych magazynów klucz-wartość. * Posiada obsługę wersjonowania oraz rozwiązywania konfliktów (z wbudowaną polityką "ostatnia aktualizacja wygrywa"). * Dane są automatycznie replikowane pomiędzy wieloma węzłami (również replikacja pomiędzy datacenters). * Cassandra stosuje model "eventually consistent", lecz ma też zaawansowane rozwiązania zmniejszające okresy braku spójności. * Zapisy zawsze się udają, dwie ścieżki odczytu. * Zapisy i odczyty są atomowe dla pojedynczej ColumnFamily.