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 <Keyspaces> i </Keyspaces> wstawiamy następujące linie:

<Keyspace Name="Geography">
</Keyspace>

Keyspace odpowiada schematowi w relacyjnej bazie danych. Dajemy mu nazwę Geography. Właśnie pomiędzy tymi znacznikami umieszczamy wszystkie pozostałe instrukcje.

<ColumnFamily CompareWith="UTF8Type" Name="Cities"/>

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.

<ReplicaPlacementStrategy>org.apache.cassandra.locator.RackUnawareStrategy</ReplicaPlacementStrategy>
<ReplicationFactor>1</ReplicationFactor>
<EndPointSnitch>org.apache.cassandra.locator.EndPointSnitch</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<String, List<ColumnOrSuperColumn>> job = new HashMap<String, List<ColumnOrSuperColumn>>();
 
            List<ColumnOrSuperColumn> columns = new ArrayList<ColumnOrSuperColumn>();
            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<KeySlice> 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:

<Keyspace Name="Geography">	 
    <ColumnFamily CompareWith="UTF8Type" Name="Cities"/>
    <ReplicaPlacementStrategy>org.apache.cassandra.locator.RackUnawareStrategy</ReplicaPlacementStrategy>
    <ReplicationFactor>1</ReplicationFactor>
    <EndPointSnitch>org.apache.cassandra.locator.EndPointSnitch</EndPointSnitch>
</Keyspace>

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<String, List<ColumnOrSuperColumn>> job = new HashMap<String, List<ColumnOrSuperColumn>>();
 
            List<ColumnOrSuperColumn> columns = new ArrayList<ColumnOrSuperColumn>();
            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<KeySlice> 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<ColumnOrSuperColumn> 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

    <Keyspace Name="Dictionary">
      <ColumnFamily Name="Words"
                    ColumnType="Super"
                    CompareWith="UTF8Type"
                    CompareSubcolumnsWith="UTF8Type"
                    RowsCached="10000"
                    KeysCached="50%"
                    Comment="A column family with supercolumns, whose column and subcolumn names are UTF8 strings"/>
      <ReplicaPlacementStrategy>org.apache.cassandra.locator.RackUnawareStrategy</ReplicaPlacementStrategy>
 
      <!-- Number of replicas of the data -->
      <ReplicationFactor>1</ReplicationFactor>
 
      <EndPointSnitch>org.apache.cassandra.locator.EndPointSnitch</EndPointSnitch>
    </Keyspace>
#!/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.
pl/dydaktyka/ztb/2010/projekty/nosql_casandra/start.txt · ostatnio zmienione: 2017/07/17 08:08 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0