Dieser Artikel wurde maschinell übersetzt.

Programmiererpraxis

Cassandra – die NoSQL-Datenbank, Teil 2: Programmierung

Ted Neward

 

Ted NewardIn meinem Artikel vom August 2012 "Cassandra NoSQL-Datenbank: Erste Schritte,"untersuchte ich Apache Cassandra. Es wird beschrieben, wie die "open-Source, verteilte, dezentrale, elastisch skalierbare, hochverfügbare, fehlertolerante, tuneably konsistent, spaltenorientiert Datenbank, die seine Verteilung-Gestaltung auf Amazon Dynamo und seine Datenmodell auf Google Bigtable gründet" in dem Buch, "Cassandra: The Definitive Guide"(O' Reilly Media, 2010). Um genauer zu sein präzise, ich schaute auf Cassandra zu installieren (die, weil es eine Java-basierte Datenbank ist auch erforderlich, eine Java Virtual Machine aufstehen und auf Ihrem Computer ausgeführt werden, wenn Sie nicht eine bereits), Herstellen von der Befehlszeile aus und was seine Datenmodell aussah. Das Datenmodell trägt wiederholen, weil es ganz deutlich in der Struktur der relationalen Datenbank unterscheidet die meisten Entwickler vertraut sind.

Wie letztes Mal erwähnt (msdn.microsoft.com/magazine/JJ553519), Cassandra ist ein "spaltenorientiert" Datenspeicher, was bedeutet, dass anstatt zu speichern identisch Tupel von Daten nach einer festen Struktur (das Schema der Tabelle) angeordnet strukturiert, Cassandra speichert "Spalte Familien" in "Keyspaces." In deskriptiver Hinsicht ordnet Cassandra einen Schlüsselwert einer variierenden Anzahl von Name/Wert-Paaren (Spalten), die von einer "Reihe" zu völlig unterschiedlich sein können.

Betrachten Sie beispielsweise die erledigten "Erde", ich letztes Mal habe, mit einer Spalte Familie mit dem Namen "People", in dem ich schreibe Zeilen, die (vielleicht oder auch nicht) wie folgt aussehen:

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"

Wie Sie sehen können, jede "Zeile" enthält Daten, die im Prinzip ähnliche, aber nicht alle "Zeilen" haben die gleichen Daten, je nachdem, was der Entwickler bzw. das Unternehmen musste für eine bestimmte Zeile-Taste speichern. Ricks Alter, weiß ich nicht, speichere ich sie konnte nicht. In einer relationalen Datenbank Wenn das Schema beauftragt, dass Alter eine NULL-Spalte war, konnte nicht ich Rick überhaupt gespeichert haben. Cassandra sagt: "Warum nicht?"

Meinem vorigen Artikel gezeigt einfügen und Entfernen von Daten von der Befehlszeile aus, aber das ist nicht besonders hilfreich, wenn das Ziel ist es, Anwendungen zu schreiben, die auf und Speichern von Daten. Also, ohne weiteren Hintergrund, lassen Sie uns Eintauchen in das Zeug zum Schreiben von Anwendungen, die lesen und speichern in Cassandra.

Cassandra, O Cassandra, darum bist du Cassandra?

Um zu starten, muss ich zum Herstellen einer Verbindung mit Cassandra aus dem Microsoft .NET Framework. Dabei umfasst eine der zwei Techniken: Ich kann die native Apache Thrift-API verwenden, oder ich kann einen Dritter-Wrapper über der nativen Thrift-API verwenden. Sparsamkeit ist ein binärer Remoteprozedur-Aufruf-Toolkit, und in vielen Hinsichten ähnlich zu DCOM (Wette, Sie, die in ein paar Jahren gedacht habe nicht) oder CORBA oder .NET Remoting. Es ist ein besonders niedriger Ansatz zur Kommunikation mit Cassandra und während Thrift c# unterstützen, es ist nicht trivial, aufstehen und laufen. Alternativen zur Sparsamkeit gehören FluentCassandra, Cassandra-Sharp, Cassandraemon und Aquiles (die spanische Übersetzung des Achilles, die antike griechische Thema gesund und munter hält). Alle diese sind open Source und bieten einige schöner Abstraktionen auf der Cassandra-API. Für diese Spalte werde ich FluentCassandra verwenden, aber Sie scheinen das ungerade Internet Flamme War trotz ziemlich gut zu funktionieren.

FluentCassandra ist als ein Paket NuGet verfügbar, so ist der einfachste Weg um loszulegen Feuern Sie die NuGet-Paket-Manager in ein Visual Studio-Test-Projekt (also ich Exploration Tests schreiben können) und kann ein "Paket installieren FluentCassandra." (Die aktuellste Version zum Zeitpunkt des Schreibens ist 1.1.0.) Sobald das geschieht, und ich nachgesehen habe, dass Cassandra-Server noch läuft, nachdem ich mit ihm für die August-Spalte spielte, kann ich den ersten Exploration-Test schreiben: Verbinden mit dem Server.

FluentCassandra sind im Namespace "FluentCassandra" und zwei verschachtelte Namespaces ("Verbindungen" und "Typen"), weshalb ich in zu bringen, und schreiben Sie dann einen Test über die Verbindung mit der Datenbank zu sehen:

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);
  }
}

Beachten Sie, dass durch die Zeit, wenn Sie dies lesen, ist es möglich, dass die Versionsnummer anders ist, als ich schrieb, also wenn diese zweite Behauptung, überprüfen Sie das Ausgabefenster zu sehen, die zurückgegebene Zeichenfolge. (Beachten Sie, dass Exploration-Tests sind über Ihr Verständnis von der API zu testen, so schreiben Ausgabe nicht so viel schlechte Idee, wie es in einer automatisierte Komponententests.)

Die CassandraContext-Klasse verfügt über fünf unterschiedliche Überladungen für den Anschluss an einen laufenden Cassandra-Server, alle von ihnen ziemlich leicht herleiten — sie alle beschäftigen sich mit Verbindungsinformationen des einen oder anderen Form. In diesem speziellen Fall weil ich Schlüsselraums nicht erstellt haben, in dem ich zu speichern (und später lesen) die Daten sollen, bin ich Verbindung mit Schlüsselraums "System", das von Cassandra verwendet wird, um verschiedene systemische Details viel die gleiche Weise speichern, dass die meisten relationale Datenbanken eine Instanz reserviert für Datenbank-Metadaten und Sicherheit haben und solche. Aber das bedeutet, dass ich nicht möchte, dass in diesem System erledigten schreiben; Ich möchte meine eigene, erstellen bildet den nächsten Exploration-Test, wie in Abbildung 1.

Abbildung 1 Erstellen einer System-erledigten

[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"));
  }
}

Zugegebenermaßen ist die Schleife durch alle Keyspaces in der Datenbank unnötig — ich es hier tun, um nachzuweisen, dass es Orte in der FluentCassandra-API gibt, wo geben Sie die zugrunde liegenden Thrift-basierte API späht durch, und die "Apache.Cassandra.KsDef" ist einer von denen.

Jetzt, dass ich einen erledigten, benötige ich mindestens eine Spalte Familie innerhalb dieser erledigten. Der einfachste Weg zum Erstellen dieser verwendet Cassandra Query Language (CQL), eine vage SQL-ähnliche Sprache, wie in dargestellt Abbildung 2.

Abbildung 2 Erstellen einer Spalte-Familie mit Cassandra Query Language

[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);
  }
}

Die Gefahr von CQL ist, dass seine absichtlich SQL-ähnliche Grammatik mit leicht Fehleinschätzung kombiniert, dass "Cassandra hat Spalten, daher muss es Tabellen wie einer relationalen Datenbank" Trick der unachtsame Entwickler in das Denken in relationalen begriffen. Dies führt zu konzeptionellen Annahmen, die Wild falsch sind. Betrachten Sie z. B. die Spalten im Abbildung 2. In einer relationalen Datenbank wäre nur die fünf Spalten in dieser Spalte-Familie erlaubt. In Kassandra, die sind nur "Richtlinien" (in ein altväterliches "Fluch der Karibik" Art und Weise). Aber die Alternative, (nicht CQL überhaupt zu benutzen) ist bei weitem weniger attraktiv: Cassandra bietet die API-TryCreateColumnFamily (nicht abgebildet), aber egal wie oft ich versuche, meinen Kopf um ihn herum wickeln, dieses Gefühl noch mehr klobig und verwirrend als des CQL-Ansatzes.

'Daten, Daten, Daten! Ohne Ton kann ich keine Ziegel machen!'

Sobald die Familie Spalte vorhanden ist, die wirkliche Macht der FluentCassandra-API entsteht wie ich einige Objekte in der Datenbank zu speichern wie im Abbildung 3.

Abbildung 3 Objekte in der Datenbank gespeichert

[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);
  }
}

Beachten Sie die Verwendung der "dynamischen" Einrichtungen von c# 4.0 die Idee verstärken, dass die Spalte Familie keine streng typisierte Auflistung von Name-Wert-Paaren. Dies ermöglicht den c#-Code auf die Art des Datenspeichers spaltenorientiert wiedergeben. Ich kann das sehen, wenn ich ein paar mehr Menschen in die erledigten speichern, wie in Abbildung 4.

Abbildung 4 Speichern von mehr Menschen in die erledigten

 

[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);
  }
}

Nur um den Punkt nach Hause fahren, beachten Sie wieder, wie Rick eine Heimatstadt-Spalte hat, die in der früheren Beschreibung dieser Spalte-Familie angegeben war nicht. Dies ist völlig in Ordnung und durchaus üblich.

Beachten Sie auch, dass die FluentCassandra-API die Eigenschaft "LastError" bietet, die einen Verweis auf die letzte Ausnahme, die ausgelöst wird, aus der Datenbank enthält. Dies kann sinnvoll sein zu prüfen, wann der Status der Datenbank nicht bereits bekannt ist (wie bei der Rückkehr aus einer Reihe von anrufen, die die ausgelöste gegessen haben könnte, oder wenn die Datenbank keine Ausnahmen auslösen konfiguriert ist).

Noch einmal mit Gefühl

Verbindung zur Datenbank, erstellen Schlüsselraums (und später ablegen), definieren die Spalte-Familien und setzen in einigen Ausgangswerte — ich bin wahrscheinlich zu wollen diese Dinge viel innerhalb dieser Tests. Diese Sequenz des Codes ist ein großer Kandidat in Vortest einrichten und Nachtests abrüsten-Methoden. Durch Löschen der erledigten nach und vor jeder Prüfung neu zu erstellen, ich halte die Datenbank unberührten und in einem bekannten Zustand jedes Mal, wenn ich einen Test ausführen, wie im Abbildung 5. Reizend.

Abbildung 5 Ausführen eines Tests

[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");
}

"Blick auf meine arbeiten, All ihr mächtigen, und Verzweiflung!"

Lesen von Daten aus Cassandra, dauert ein paar Formen. Die erste ist zum Abrufen der Daten aus der Spalte-Familie mit die Get-Methode für das CassandraColumnFamily-Objekt, gezeigt Abbildung 6.

Abbildung 6 Abrufen von Daten mit der Get-Methode

[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);
  }
}

Das ist großartig, wenn ich den Schlüssel vor der Zeit, aber die meiste Zeit kennen, das nicht der Fall ist. In der Tat wird nicht ist es fraglich, dass die meisten der Zeit, die genaue Datensatz oder Datensätze bekannt sein. So ist ein anderer Ansatz (nicht dargestellt), die FluentCassandra LINQ-Integration verwenden, um eine Abfrage LINQ-Stil zu schreiben. Dies ist aber nicht ganz so flexibel wie traditionelle LINQ. Da die Spaltennamen sind nicht vor der Zeit bekannt ist, ist es viel schwieriger, z. B. Schreiben von LINQ-Abfragen, um alle Newards (Blick auf die LastName-Name-Wert-Paar aus der Familie der Spalte) finden in der Datenbank.

Glücklicherweise CQL rettet die, wie in Abbildung 7.

Abbildung 7 mit Cassandra LINQ Integration eine Abfrage LINQ-Stil zu schreiben

[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");
    }
  }
}

Beachten Sie jedoch, wenn ich diesen Code wie laufen, es fehl — Cassandra lässt mich kein Name/Wert-Paar innerhalb einer Spalte als ein Filterkriterium verwenden, wenn ein Index auf es explizit definiert ist. Dies erfordert eine andere CQL-Anweisung:

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

Ich möchte in der Regel festgelegt, dass bis zum Zeitpunkt die Familie Spalte erstellt wird. Beachten Sie auch, dass da Cassandra Schema-weniger, ist die "wählen Sie *" Teil dieser Abfrage ist ein wenig irreführend — die Name/Wert-Paare in der Spalte-Familie zurück, aber das bedeutet nicht, dass jeder Datensatz jeder Spalte haben wird. Dies bedeutet also, dass eine Abfrage mit "WHERE Geschlecht = 'W'" wird nie in Betracht, die Datensätze, die eine Spalte "Geschlecht" in ihnen nicht die Rick, Ted und "Der Künstler früher bekannt als Prinz" außer Betracht lässt. Dies unterscheidet sich völlig von einem relationalen Datenbank-Management-System, wo jede Zeile in einer Tabelle Werte für jeden einzelnen der Spalten müssen (obwohl ich oft diese Verantwortung Ente durch Speichern von "NULL" in diesen Spalten, die von einigen als ein Kardinalfehler gilt).

Die vollständige CQL-Sprache ist zu viel, um hier zu beschreiben, aber eine vollständige Referenz ist verfügbar auf der Cassandra-Website unter bit.ly/MHcWr6.

Nachbereitung, für jetzt

Ich bin nicht ganz fertig mit der verfluchten Prophetin nur noch — während ein-und immer Daten von Cassandra ist das interessanteste Teil an einen Entwickler (wie das ist, was sie den ganzen Tag tun), Multi-Node-Konfiguration ist auch ein ziemlich großer Teil der Cassandra-Geschichte. Tun, dass auf einem einzigen Windows (für Entwicklungszwecke; Sie werden sehen, wie wäre es einfacher, auf mehreren Servern zu tun) ist nicht gerade trivial, weshalb ich werde die Diskussion über Cassandra einpacken, indem Sie das nächste Mal tun.

Im Moment glücklich Codierung!

Ted Neward ist Berater für Softwarearchitektur bei Neudesic LLC. Er hat mehr als 100 Artikel geschrieben und hat mehrere Bücher allein und in Zusammenarbeit mit anderen geschrieben, darunter „Professional F# 2.0“ (Wrox 2010). Er ist bekannter Java-Experte und F#-MVP und spricht auf Java und .NET Konferenzen auf der ganzen Welt. Er berät und Mentoren regelmäßig – Sie erreichen ihn unter ted@tedneward.com oder Ted.Neward@neudesic.com Wenn Sie mit ihm kommen mit Ihrem Team Arbeiten interessiert sind. Er Blogs auf blogs.tedneward.com und kann auf Twitter bei Twitter.com/tedneward.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Kelly Sommers