Il presente articolo è stato tradotto automaticamente.

Il programmatore al lavoro

Database NoSQL Cassandra, parte 2: programmazione

Ted Neward

 

Ted NewardNel mio articolo di agosto 2012, "NoSQL Database Cassandra: Per iniziare,"esaminato Apache Cassandra. È descritto come il "open source, distribuito, decentrato, elasticamente scalabili, altamente disponibile, tolleranza, tuneably coerente, database column-oriented che basa la sua progettazione distribuzione su Amazon Dinamo e il modello di dati su Google Bigtable" nel libro "Cassandra: La guida definitiva"(o ' Reilly Media, 2010). Per essere più precisi, ho guardato come installare Cassandra (che, perché un database basato su Java, è anche necessario alzarsi una Java Virtual Machine e in esecuzione sulla vostra macchina se non avete già uno), come connettersi ad esso dalla riga di comando e che il modello di dati sembrava. Il modello di dati la pena ripeterlo perché è abbastanza notevolmente diverso nella struttura del database relazionale con cui la maggior parte degli sviluppatori sono familiare.

Come ho discusso l'ultima volta (msdn.microsoft.com/magazine/JJ553519), Cassandra è un archivio di dati "column-oriented", che significa che invece di memorizzare in modo identico strutturato tuple di dati organizzati secondo una struttura fissa (lo schema della tabella), Cassandra memorizza "famiglie colonna" in "keyspaces." In termini più descrittivi, Cassandra associa un valore di chiave con un numero variabile di coppie nome/valore (colonne) che potrebbe essere completamente diversa da una "riga" a altro.

Si consideri ad esempio il keyspace "Terra", ho creato l'ultima volta, con una colonna famiglia denominato "People", in cui scrivere le righe che (può o non può) simile a questa:

 

RowKey: tedneward
  ColumnName:"FirstName", ColumnValue:"Ted"
  ColumnName:"LastName", ColumnValue:"Neward"
  ColumnName:"Age", ColumnValue:41
  ColumnName:"Title", ColumnValue:"Architect"
RowKey: rickgaribay
  ColumnName:"FirstName", ColumnValue:"Rick"
  ColumnName:"LastName", ColumnValue:"Garibay"
RowKey: theartistformerlyknownasprince
  ColumnName:"Identifier", ColumnValue: <image>
  ColumnName:"Title", ColumnValue:"Rock Star"

Come potete vedere, ogni "riga" contiene dati concettualmente simili, ma non tutte le "righe" avrà gli stessi dati, a seconda di quello che lo sviluppatore o business necessari per archiviare qualsiasi chiave particolare riga. Non so che età di Rick, quindi io non riuscivo a memorizzare. In un database relazionale, se lo schema mandato era una colonna non NULLABLE, io non potevo sono archiviati Rick a tutti. Cassandra dice: "Perché no?"

Mio articolo precedente dimostrazione inserimento e rimozione di dati dalla riga di comando, ma questo non è particolarmente utile se l'obiettivo è di scrivere applicazioni che accedere e memorizzare i dati. Così, senza ulteriori sfondo, Tuffiamoci in quello che serve per scrivere applicazioni che leggere e memorizzano a Cassandra.

Cassandra, O Cassandra, perché sei tu Cassandra?

Per iniziare, è necessario connettersi a Cassandra da Microsoft .NET Framework. Così facendo coinvolge una delle due tecniche: Posso utilizzare l'API nativa di parsimonia Apache o posso usare un wrapper di terze parti in cima l'API nativa di parsimonia. Parsimonia è un binario remote procedure call toolkit, simili in molti modi per DCOM (scommetto che non avete pensato che in pochi anni) o CORBA o .NET Remoting. È un approccio particolarmente a basso livello di comunicazione con Cassandra, e mentre parsimonia ha c# supporta, non è banale per tutti che alzarsi e funzionante. Alternative a risparmio includono FluentCassandra, cassandra-sharp, Cassandraemon e Aquiles (la traduzione in spagnolo di Achille, che mantiene il tema di greco antico, vivo e vegeto). Tutti questi sono open source e offrono alcune astrazioni più belli l'API di Cassandra. Per questo articolo, ho intenzione di utilizzare FluentCassandra, ma nessuno di loro sembra funzionare abbastanza bene, la strana guerra di fiamma Internet nonostante.

FluentCassandra è disponibile come pacchetto NuGet, quindi il modo più semplice per iniziare è al fuoco il Manager Package NuGet in un progetto di Test Visual Studio (così posso scrivere test di esplorazione) e fare un "pacchetto di installazione FluentCassandra." (La versione più recente a partire da questa scrittura è 1.1.0). Una volta fatto questo, e ho ricontrollato di che il server di Cassandra è ancora in esecuzione dopo che ho giocato con esso per la colonna di agosto, posso scrivere il primo test di esplorazione: connessione al server.

FluentCassandra vive in spazio dei nomi "FluentCassandra" e due spazi di nomi nidificati ("Connessioni" e "Tipi"), così potrai portare quelle e poi scrivere un test per vedere sulla connessione al database:

private static readonly Server Server = 
  new Server("localhost");       
TestMethod]
public void CanIConnectToCassandra()
{
  using (var db = new CassandraContext(keyspace: "system", 
    server:Server))
  {
    var version = db.DescribeVersion();
    Assert.IsNotNull(version);
    testContextInstance.WriteLine("Version = {0}", version);
    Assert.AreEqual("19.30.0", version);
  }
}

Nota che con il tempo di leggere questo, è possibile che il numero di versione sarà diverso da quando ho scritto, così se questa seconda affermazione non riesce, controllare la finestra di output per visualizzare la stringa restituita. (Ricordate, test di esplorazione sono circa test la comprensione dell'API, in modo di scrittura dell'output non è tanto una cattiva idea come in un test automatico unità).

La classe CassandraContext ha cinque diversi overload per la connessione a un server in esecuzione di Cassandra, tutti abbastanza facile dedurre, trattano tutte le informazioni di connessione di una forma o in altra. In questo caso particolare, perché non ho creato il keyspace in cui voglio negozio (e più tardi leggere) i dati sono connessione al keyspace "sistema", che viene utilizzato da Cassandra per memorizzare i vari dettagli sistemiche in gran parte allo stesso modo che più database relazionali hanno un'istanza riservata per la sicurezza e metadati del database e tali. Ma questo significa che non voglio scrivere di quel sistema keyspace; Voglio creare il mio, che forma il prossimo test di esplorazione, come mostrato Figura 1.

Figura 1 creazione di un sistema Keyspace

[TestMethod]
public void DoesMyKeyspaceExistAndCreateItIfItDoesnt()
{
  using (var db = new CassandraContext(keyspace: "system", 
    server:Server))
  {
    bool foundEarth = false;
    foreach (CassandraKeyspace keyspace in db.DescribeKeyspaces())
    {
      Apache.Cassandra.KsDef def = keyspace.GetDescription();
      if (def.Name == "Earth")
        foundEarth = true;
    }
    if (!foundEarth)
    {
      var keyspace = new CassandraKeyspace(new 
      CassandraKeyspaceSchema
      {
        Name = "Earth"
      }, db);
      keyspace.TryCreateSelf();
    }
    Assert.IsTrue(db.KeyspaceExists("Earth"));
  }
}

Certo, il loop attraverso tutte le keyspaces nel database è inutile — fare qui per dimostrare che ci sono luoghi nell'API FluentCassandra dove i peeks API basata su risparmio sottostanti attraverso e "Apache.Cassandra.KsDef" tipo è uno di quelli.

Ora che ho un keyspace, ho bisogno di famiglia almeno una colonna all'interno di quel keyspace. Il modo più semplice per creare questo usa Cassandra Query Language (CQL), una lingua vagamente simile a SQL, come mostrato nella Figura 2.

Figura 2 creazione di una famiglia di colonna utilizzando il linguaggio di Query di Cassandra

[TestMethod]
public void CreateAColumnFamily()
{
  using (var db = new CassandraContext(keyspace: "Earth", 
    server: Server))
  {
    CassandraColumnFamily cf = db.GetColumnFamily("People");
    if (cf == null)
    {
      db.ExecuteNonQuery(@"CREATE COLUMNFAMILY People (
        KEY ascii PRIMARY KEY,
        FirstName text,
        LastName text,
        Age int,
        Title text);");
    }
    cf = db.GetColumnFamily("People");
    Assert.IsNotNull(cf);
  }
}

Il pericolo di CQL è che sua grammatica deliberatamente SQL-come si combina con il preconcetto facile che "Cassandra ha colonne, quindi deve disporre di tabelle di un database relazionale" trucco sviluppatore sprovveduto a pensare in termini relazionali. Questo porta a presupposti concettuali che sbagliano selvaggiamente. Si consideri, ad esempio, le colonne in Figura 2. In un database relazionale, sarebbero permesso solo le cinque colonne in questa famiglia colonna. Cassandra, quelli sono solo "linee guida" (in un curiosamente "Pirati dei Caraibi" sorta di modo). Ma l'alternativa (per non usare affatto CQL) è di gran lunga meno attraente: Cassandra offre l'API TryCreateColumnFamily (non illustrato), ma non importa quante volte cerco di avvolgere la testa intorno ad esso, si sente ancora più goffo e l'approccio CQL di confusione.

' Dati, dati, dati! Non posso fare mattoni senza l'argilla!'

Una volta che la famiglia colonna, il vero potere dell'API FluentCassandra emerge come memorizzare alcuni oggetti nel database, come mostrato Figura 3.

Figura 3 archiviare oggetti nel Database

[TestMethod]
public void StoreSomeData()
{
  using (var db = new CassandraContext(keyspace: "Earth", 
    server: Server))
  {
    var peopleCF = db.GetColumnFamily("People");
    Assert.IsNotNull(peopleCF);
    Assert.IsNull(db.LastError);
    dynamic tedneward = peopleCF.CreateRecord("TedNeward");
    tedneward.FirstName = "Ted";
    tedneward.LastName = "Neward";
    tedneward.Age = 41;
    tedneward.Title = "Architect";
    db.Attach(tedneward);
    db.SaveChanges();
    Assert.IsNull(db.LastError);
  }
}

Si noti l'utilizzo delle strutture "dinamiche" di c# 4.0 per rafforzare l'idea che la famiglia colonna non è un insieme di coppie nome/valore strettamente tipizzati. In questo modo il codice c# a riflettere la natura dell'archivio dati orientato alla colonna. Posso vedere questo, quando memorizzare alcune persone più nel keyspace, come mostrato Figura 4.

Figura 4 l'archiviazione di più persone nel Keyspace

 

[TestMethod]
public void StoreSomeData()
{
  using (var db = new CassandraContext(keyspace: "Earth", 
    server: Server))
  {
    var peopleCF = db.GetColumnFamily("People");
    Assert.IsNotNull(peopleCF);
    Assert.IsNull(db.LastError);
    dynamic tedneward = peopleCF.CreateRecord("TedNeward");
    tedneward.FirstName = "Ted";
    tedneward.LastName = "Neward";
    tedneward.Age = 41;
    tedneward.Title = "Architect";
    dynamic rickgaribay = peopleCF.CreateRecord("RickGaribay");
    rickgaribay.FirstName = "Rick";
    rickgaribay.LastName = "Garibay";
    rickgaribay.HomeTown = "Phoenix";
    dynamic theArtistFormerlyKnownAsPrince =
      peopleCF.CreateRecord("TAFKAP");
    theArtistFormerlyKnownAsPrince.Title = "Rock Star";
    db.Attach(tedneward);
    db.Attach(rickgaribay);
    db.Attach(theArtistFormerlyKnownAsPrince);
    db.SaveChanges();
    Assert.IsNull(db.LastError);
  }
}

Ancora una volta, solo per il punto a casa, notare come Rick ha una colonna della città natale, che non era specificata nella descrizione precedente della famiglia colonna. Questo è completamente accettabile e abbastanza comune.

Si noti inoltre che l'API di FluentCassandra offre la proprietà "LastError", che contiene un riferimento all'ultima eccezione generata dal database. Questo può essere utile per controllare quando lo stato del database non è noto già (come quando si ritorna da una serie di chiamate che potrebbe avere mangiato l'eccezione generata, o se il database è configurato per non generare eccezioni).

Ancora una volta con sentimento

Connessione al database, creando il keyspace (e poi cadere), definendo le famiglie colonna e mettere in alcuni dati di seme — sono probabilmente andando a voler fare queste cose molto all'interno di questi test. Tale sequenza di codice è un grande candidato per mettere in configurazione pre- e post-test di metodi di eliminazione. Eliminando il keyspace dopo e ricrearla prima di ogni prova, mantenere il database incontaminate e in uno stato noto ogni volta esegue un test, come mostrato Figura 5. in modo estremamente semplice.

Figura 5 in esecuzione di un Test

[TestInitialize]
public void Setup()
{
  using (var db = new CassandraContext(keyspace: "Earth", 
    server: Server))
  {
    var keyspace = new CassandraKeyspace(new CassandraKeyspaceSchema {
      Name = "Earth",
      }, db);
    keyspace.TryCreateSelf();
    db.ExecuteNonQuery(@"CREATE COLUMNFAMILY People (
      KEY ascii PRIMARY KEY,
      FirstName text,
      LastName text,
      Age int,
      Title text);");
    var peopleCF = db.GetColumnFamily("People");
    dynamic tedneward = peopleCF.CreateRecord("TedNeward");
    tedneward.FirstName = "Ted";
    tedneward.LastName = "Neward";
    tedneward.Age = 41;
    tedneward.Title = "Architect";
    dynamic rickgaribay = peopleCF.CreateRecord("RickGaribay");
    rickgaribay.FirstName = "Rick";
    rickgaribay.LastName = "Garibay";
    rickgaribay.HomeTown = "Phoenix";
    dynamic theArtistFormerlyKnownAsPrince =
      peopleCF.CreateRecord("TAFKAP");
    theArtistFormerlyKnownAsPrince.Title = "Rock Star";
    db.Attach(tedneward);
    db.Attach(rickgaribay);
    db.Attach(theArtistFormerlyKnownAsPrince);
    db.SaveChanges();
  }
}
[TestCleanup]
public void TearDown()
{
  var db = new CassandraContext(keyspace: "Earth", server: Server);
  if (db.KeyspaceExists("Earth"))
    db.DropKeyspace("Earth");
}

'Guarda i miei lavori, tutti voi possente e disperazione!'

Lettura di dati da Cassandra prende un paio di forme. Il primo è quello di recuperare i dati dalla famiglia colonna utilizzando il metodo Get dell'oggetto CassandraColumnFamily, mostrato in Figura 6.

Figura 6 il recupero dei dati con il metodo Get

[TestMethod]
public void StoreAndFetchSomeData()
{
  using (var db = new CassandraContext(keyspace: "Earth", 
    server: Server))
  {
    var peopleCF = db.GetColumnFamily("People");
    Assert.IsNotNull(peopleCF);
    Assert.IsNull(db.LastError);
    dynamic jessicakerr = peopleCF.CreateRecord("JessicaKerr");
    jessicakerr.FirstName = "Jessica";
    jessicakerr.LastName = "Kerr";
    jessicakerr.Gender = "F";
    db.Attach(jessicakerr);
    db.SaveChanges();
    Assert.IsNull(db.LastError);
    dynamic result = peopleCF.Get("JessicaKerr").FirstOrDefault();
    Assert.AreEqual(jessicakerr.FirstName, result.FirstName);
    Assert.AreEqual(jessicakerr.LastName, result.LastName);
    Assert.AreEqual(jessicakerr.Gender, result.Gender);
  }
}

Questo è grande se so la chiave davanti a tempo, ma gran parte del tempo, che non è il caso. In realtà, è discutibile che la maggior parte del tempo, il record esatto o il record non essere conosciuto. Così, un altro approccio (non mostrato) è utilizzare l'integrazione FluentCassandra LINQ per scrivere una query in stile LINQ. Questo non è abbastanza flessibile come LINQ tradizionale, tuttavia. Perché i nomi di colonna non sono noti in anticipo, è molto più difficile scrivere le query LINQ per trovare tutti i Newards (guardando la coppia nome/valore di cognome della famiglia colonna) nel database, ad esempio.

Fortunatamente, CQL cavalca alla riscossa, come mostrato Figura 7.

Figura 7 con Cassandra LINQ integrazione per scrivere una Query in stile LINQ

[TestMethod]
public void StoreAndFetchSomeDataADifferentWay()
{
  using (var db = new CassandraContext(keyspace: "Earth", 
    server: Server))
  {
    var peopleCF = db.GetColumnFamily("People");
    Assert.IsNotNull(peopleCF);
    Assert.IsNull(db.LastError);
    dynamic charlotte = peopleCF.CreateRecord("CharlotteNeward");
    charlotte.FirstName = "Charlotte";
    charlotte.LastName = "Neward";
    charlotte.Gender = "F";
    charlotte.Title = "Domestic Engineer";
    charlotte.RealTitle = "Superwife";
    db.Attach(charlotte);
    db.SaveChanges();
    Assert.IsNull(db.LastError);
    var newards =
      db.ExecuteQuery("SELECT * FROM People WHERE LastName='Neward'");
    Assert.IsTrue(newards.Count() > 0);
    foreach (dynamic neward in newards)
    {
      Assert.AreEqual(neward.LastName, "Neward");
    }
  }
}

Si noti che se si esegue questo codice così com'è, riuscirà, Cassandra non mi permette di utilizzare una coppia nome/valore all'interno di una famiglia colonna come criteri di filtro, a meno che un indice è definito in modo esplicito su di esso. In questo modo richiede un'altra istruzione CQL:

db.ExecuteNonQuery(@"CREATE INDEX ON People (LastName)");

Di solito, voglio impostare che fino al momento della famiglia colonna viene creata. Nota anche che poiché Cassandra è meno dello schema, il "selezionare *" parte della query è un po' ingannevole — restituirà tutte le coppie nome/valore della famiglia colonna, ma questo non significa che ogni record avrà ogni colonna. Ciò significa, quindi, che una query con "dove genere = 'F'" non prenderà in considerazione i record che non dispongono di una colonna di "Genere", che lascia Rick, Ted e "L'artista precedentemente noto come Prince" in considerazione. Questo è completamente diverso da un sistema di gestione di database relazionali, dove ogni riga in una tabella deve avere valori per ognuna delle colonne (anche se spesso anatra che la responsabilità memorizzando "NULL" in quelle colonne, che è considerato da alcuni come un peccato cardinale).

La lingua CQL completa è troppo per descrivere qui, ma un riferimento completo è disponibile sul sito Web di Cassandra a bit.ly/MHcWr6.

Avvolgendo, per ora

Non sono fatto con la profetessa maledetta ancora — mentre ottenere i dati in entrata e in uscita di Cassandra è la parte più interessante di uno sviluppatore (come quello è che cosa fanno tutto il giorno), configurazione nodale è anche una parte abbastanza grande della storia di Cassandra. Farlo su una singola casella di Windows (per scopi di sviluppo; vedrai come sarebbe più facile da fare su più server) non è esattamente banale, motivo per cui potrai concludere la discussione su Cassandra facendo che la prossima volta.

Per ora, codifica felice!

Ted Neward è un consulente architettonico con Neudesic LLC. Ha scritto oltre 100 articoli e autore o coautore di una dozzina di libri, tra cui "Professional F # 2.0" (Wrox, 2010). Egli è un F # MVP e noto esperto di Java e a conferenze sia Java e .NET tutto il mondo. Egli consulta e mentors regolarmente — contattarlo al ted@tedneward.com o Ted.Neward@neudesic.com se siete interessati ad avere lui venire a lavorare con il vostro team. Ha blog a blogs.tedneward.com e possono essere seguiti su Twitter a Twitter.com/tedneward.

Grazie all'esperto tecnica seguente per la revisione di questo articolo: Kelly Sommers