Partager via


Cet article a fait l'objet d'une traduction automatique.

Le programmeur au travail

Base de données NoSQL Cassandra, 2ème partie : Programmation

Ted Neward

 

Ted NewardDans mon article d'août 2012, "Cassandra NoSQL Database : Pour commencer,"J'ai examiné Apache Cassandra. Il est décrit comme le « open source, distribuée, décentralisée, élastiquement évolutive, hautement disponible, fault-tolerant tuneably cohérente, base de données orientée colonne qui fonde sa conception de la distribution sur Amazon Dynamo et son modèle de données sur Google Bigtable "dans le livre," Cassandra : Le Guide définitif » (o ' Reilly Media, 2010). Pour être plus précis, j'ai regardé comment installer Cassandre (qui, parce que c'est une base de données Java, aussi nécessaire une Machine virtuelle Java de se lever et s'exécutant sur votre ordinateur si vous n'avez pas déjà), comment s'y connecter depuis la ligne de commande et ce que son modèle de données ressemblé. Le modèle de données porte répéter parce que c'est assez sensiblement différent dans la structure de la base de données relationnelle qui connaissent bien la plupart des développeurs.

Comme j'ai expliqué la dernière fois (msdn.microsoft.com/magazine/JJ553519), Cassandra est un magasin de données "orienté sur la colonne", ce qui signifie qu'au lieu de stocker de manière identique structuré des tuples de données organisées selon une structure fixe (le schéma de la table), Cassandra stocke des "familles de colonne" dans "keyspaces". En termes plus descriptives, Cassandra associe une valeur de clé avec un nombre variable de paires nom/valeur (colonnes) qui peut être totalement différente d'une « ligne » à l'autre.

Par exemple, considérez l'espace de clé « Terre », j'ai créé la dernière fois, avec une colonne famille nommée « People », dans lequel je vais écrire les lignes qui (peut ou non) ressembler à ceci :

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"

Comme vous pouvez le voir, chaque « ligne » contient des données similaires sur le plan conceptuel, mais pas tous les « lignes » ont les mêmes données, selon ce que le développeur ou l'entreprise devait stocker pour n'importe quelle touche de ligne particulière. Je ne sais pas l'âge de Rick, donc je ne pouvais pas stocker. Dans une base de données relationnelle, si le schéma obligatoire que l'âge était une colonne non NULLABLE, je ne pouvais pas avoir stocké Rick du tout. Cassandra dit: « Pourquoi pas? »

Mon article précédent démontre insertion et suppression de données de la ligne de commande, mais ce n'est pas particulièrement utile si le but est d'écrire des applications qui vont accéder et stocker des données. Donc, sans précisions, plongeons-nous dans ce qu'il faut pour écrire des applications qui lire et stockent à Cassandra.

Cassandra, O Cassandre, c'est pourquoi es-tu Cassandra ?

Pour commencer, j'ai besoin de vous connecter à Cassandra de Microsoft .NET Framework. Cela implique une des deux techniques : Je peux utiliser l'Apache Thrift API native, ou je peux utiliser un wrapper de tierce partie sur le dessus de l'API native d'aubaines. Aubaines est un binaire remote procedure call toolkit, semblable à bien des égards à DCOM (parie que vous n'avez pas pensé à ça dans quelques années) ou CORBA ou .NET Remoting. C'est une approche particulièrement bas niveau pour communiquer avec Cassandra, et tandis que l'épargne a c# prend en charge, il n'est pas trivial pour obtenir tout cela vers le haut et en cours d'exécution. Alternatives à Thrift incluent FluentCassandra, cassandra-sharp, Cassandraemon et Aquiles (la traduction en espagnol d'Achille, qui maintient le thème grec antique vivant et très bien). Toutes ces sont open source et offrent quelques abstractions plus agréable sur l'API de Cassandra. Pour cet article, je vais utiliser FluentCassandra, mais aucun d'entre eux semblent fonctionner assez bien, la guerre du feu étrange Internet nonobstant.

FluentCassandra est disponible comme paquet NuGet, donc la meilleure façon de commencer est de tirer vers le haut le gestionnaire de Package NuGet dans un projet de Test Visual Studio (donc je peux écrire des tests d'exploration) et faire un « Package d'installation FluentCassandra. » (La dernière version en date de cette écriture est 1.1.0.) Une fois cela fait, et j'ai vérifié que le serveur de Cassandra est toujours en cours après que j'ai joué avec elle pour la colonne d'août, je peux écrire le premier test d'exploration : connexion au serveur.

FluentCassandra vit dans l'espace de noms « FluentCassandra » et deux espaces de noms imbriqués (« Connexions » et « Tape »), donc je vais traduire les en et puis écrire un test pour voir sur la connexion à la base de données :

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

Note qu'au moment où vous lisez ces lignes, il est possible que le numéro de version sera différent de quand je l'ai écrit, donc si cette deuxième assertion échoue, vérifiez la fenêtre de sortie pour afficher la chaîne retournée. (N'oubliez pas, les tests d'exploration sont sur les tests de votre compréhension de l'API, sortie d'écriture aussi bien d'une mauvaise idée car c'est lors d'un test unitaire automatisé n'est donc.)

La classe CassandraContext a cinq différentes surcharges pour se connecter à un serveur exécutant Cassandra, chacun d'eux assez facile d'inférer — ils sont tous traitent les informations de connexion d'une forme ou une autre. Dans ce cas particulier, parce que je n'ai pas créé de l'espace dans lequel je veux store (et plus tard lire) des données, je suis de connexion à l'espace de clé « système », qui est utilisée pour stocker divers détails systémiques à peu près la même façon que les bases de données relationnelles les plus ont une instance réservée pour la sécurité et les métadonnées de la base de données par Cassandra et tel. Mais cela signifie que je ne veux pas écrire dans cet espace de clé système ; Je veux créer mon propre, qui constitue le prochain test d'exploration, comme illustré à la Figure 1.

Figure 1 création d'un espace de clé système

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

Certes, la boucle à travers tous les keyspaces dans la base de données n'est pas nécessaire, je le fais ici pour démontrer qu'il existe des endroits dans l'API de FluentCassandra où les pics API basée sur les aubaines sous-jacente à travers et le « Apache.Cassandra.KsDef » type est de ceux-là.

Maintenant que j'ai un espace de clé, j'ai besoin de famille au moins une colonne au sein de cet espace. La meilleure façon de créer cette utilise Cassandra Query Language (CQL), un langage ressemblant vaguement à SQL, comme illustré à la Figure 2.

Figure 2 création d'une famille de colonne à l'aide du langage de requête de 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);
  }
}

Le danger de CQL est que sa grammaire SQL comme délibérément combine avec la simple perception erronée que « Cassandra a colonnes, donc il doit avoir des tableaux comme une base de données relationnelle » de tromper le développeur imprudent en pensant en termes relationnels. Cela conduit à des hypothèses conceptuelles qui sont sauvagement mal. Prenons, par exemple, les colonnes de Figure 2. Dans une base de données relationnelle, seules les cinq colonnes seraient autorisées dans cette famille de colonne. Cassandra, ceux qui sont justes « lignes directrices » (dans un curieusement « Pirates des Caraïbes » sorte de passage). Cependant, l'alternative (à ne pas utiliser CQL du tout) est de loin moins attrayant : Cassandra offre l'API TryCreateColumnFamily (non illustré), mais peu importe combien de fois j'ai essayer d'envelopper la tête autour de lui, il se sent encore plus maladroit et déroutante que l'approche CQL.

« Données, données, données ! Je ne peux pas faire de briques sans argile ! »

Une fois la famille de la colonne en place, la véritable puissance de l'API FluentCassandra émerge comme je stocker des objets dans la base de données, comme indiqué dans Figure 3.

Figure 3 stocker des objets dans la base de données

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

Notez l'utilisation des installations de c# 4.0 « dynamiques » pour renforcer l'idée que la famille de colonne n'est pas une collection strictement typée de paires nom/valeur. Cela permet au code c# afin de refléter la nature de la Banque de données orientée colonne. Je peux voir ceci quand je stocker plus de quelques personnes en l'espace de clé, comme indiqué dans Figure 4.

Figure 4 stocker plus de personnes dans l'espace de clé

 

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

Encore une fois, juste pour enfoncer le clou, remarquez comment Rick a une colonne ville natale, qui n'a pas été spécifiée dans la description précédente de cette famille de colonne. C'est complètement acceptable et assez fréquent.

Notez également que l'API de FluentCassandra offre la propriété « LastError », qui contient une référence à la dernière exception levée hors de la base de données. Cela peut être utile de vérifier quand l'état de la base de données n'est pas déjà connu (tels que lors du retour d'un ensemble d'appels et qui croyait avoir mangé l'exception levée, ou si la base de données est configurée pour ne pas lever d'exceptions).

Une fois de plus, avec le sentiment

Connexion à la base de données, créer l'espace de clé (et plus tard en le déposant), définissant la famille de la colonne et mettre certaines données semences — je vais probablement vouloir faire ces choses beaucoup au sein de ces tests. Cette séquence de code est un candidat idéal pour mettre en configuration pré-test et post-test méthodes de démontage. En supprimant les clés après et recréer avant chaque essai, je garde la base de données vierge et dans un état connu chaque fois que je lance un test, comme indiqué dans Figure 5. Sweet.

Figure 5 exécution d'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");
}

« Regard sur mes oeuvres, vous tous puissant et le désespoir! »

Lecture des données de Cassandra prend deux formes. La première consiste à extraire les données hors de la famille de colonne à l'aide de la méthode Get sur l'objet de CassandraColumnFamily, dans Figure 6.

La figure 6, extraction de données avec la méthode 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);
  }
}

C'est super si je sais que la clé avance sur le temps, mais la plupart du temps, ce n'est pas le cas. En fait, on peut soutenir que la plupart du temps, l'ou les enregistrements exacts ne s'appeler. Alors, une autre approche (non illustrée) consiste à utiliser l'intégration FluentCassandra LINQ pour écrire une requête de style LINQ. Ce n'est pas tout à fait aussi souple que LINQ traditionnel, cependant. Parce que les noms de colonnes ne sont pas connus en avant du temps, il est beaucoup plus difficile d'écrire des requêtes LINQ pour rechercher toutes les Newards (regardant la paire nom/valeur LastName dans la famille colonne) dans la base de données, par exemple.

Heureusement, CQL rides à la rescousse, comme le montre Figure 7.

La figure 7 à l'aide de Cassandra intégration de LINQ pour écrire une requête LINQ-Style

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

Notez, cependant, que si je lance ce code tel quel, il échouera, Cassandra ne me laisse pas utiliser une paire nom/valeur au sein d'une famille de colonne comme critère de filtre, sauf si un index est défini explicitement à ce sujet. Cela nécessite une autre instruction CQL :

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

En général, je tiens à mettre que vers le haut au moment où la famille colonne est créée. Notez également que Cassandra étant sans schéma, le "sélectionnez *" partie de cette requête est un peu trompeur — elle renverra toutes les paires nom/valeur dans la famille de la colonne, mais cela ne signifie pas que chaque enregistrement possède toutes les colonnes. Cela signifie donc qu'une requête avec "où sexe = « F »" ne considère jamais les enregistrements qui n'ont pas une colonne « Genre » en eux, qui laisse Rick, Ted et « L'artiste anciennement connu comme Prince » d'une considération. C'est complètement différent d'un système de gestion de base de données relationnelle, où chaque ligne d'un tableau doit avoir des valeurs pour chacun des colonnes (même si j'ai souvent canard cette responsabilité en stockant la valeur « NULL » dans ces colonnes, qui est considéré par certains comme un péché capital).

La langue CQL complet est trop difficile à décrire ici, mais une référence complète peut être consultée sur le site Web de Cassandra à bit.ly/MHcWr6.

La conclusion, pour l'instant

Je n'ai pas tout à fait fini avec la prophétesse maudite tout de suite, tandis que l'extraction de données dans et hors de Cassandre est la partie la plus intéressante pour un développeur (car c'est ce qu'ils font toute la journée), une configuration à plusieurs nœuds est aussi une assez grande partie de l'histoire de Cassandra. Faire cela sur une seule boîte de Windows (à des fins de développement ; vous verrez comment il serait plus facile à faire sur plusieurs serveurs) n'est pas exactement trivial, c'est pourquoi je vais terminer la discussion sur Cassandra en faisant que la prochaine fois.

Pour l'instant, amusez-vous !

Ted Neward est consultant en architecture chez Neudesic LLC. Auteur de plus de 100 articles, il a rédigé ou corédigé plus d'une dizaine d'ouvrages, y compris « Professional F# 2.0 » (Wrox, 2010). Il est un éminent expert Java MVP F # et prononce des conférences fois Java et .NET dans le monde entier. Il consulte et mentors régulièrement — le joindre à ted@tedneward.com ou Ted.Neward@neudesic.com si vous êtes intéressé par lui avoir à venir travailler avec votre équipe. Il blogs à blogs.tedneward.com et peut être suivi sur Twitter à Twitter.com/tedneward.

Merci à l'expert technique suivant d'avoir relu cet article : Kelly Sommers