Este artículo proviene de un motor de traducción automática.

El programador políglota

Base de datos NoSQL Cassandra, segunda parte: Programación

Ted Neward

 

Ted NewardEn mi columna de agosto de 2012, "base de datos NoSQL de Cassandra: "Introducción, examiné a Cassandra de Apache. Es descrito como el "código abierto, distribuido, descentralizado, elásticamente escalable, alta disponibilidad, tolerante, tuneably coherente, base de datos orientada a columna que basa su diseño de distribución en Amazon Dynamo y su modelo de datos en Google Bigtable" en el libro, "Cassandra: La guía definitiva"(o ' Reilly Media, 2010). Para ser más precisos, miré cómo instalar a Cassandra (que, porque es una base de Java, también requiere obtener una máquina Virtual Java y ejecutar en su equipo si no tiene uno ya), cómo conectarse a él desde la línea de comandos y que su modelo de datos que parecía. El modelo de datos la pena repetir porque es muy notablemente diferente en la estructura de la base de datos relacional con los que conocen la mayoría de los desarrolladores.

Como os comentamos la última vez (msdn.microsoft.com/magazine/JJ553519), Cassandra es un almacén de datos "orientado a la columna", lo que significa que en lugar de almacenar idénticamente estructurado tuplas de datos ordenados de acuerdo a una estructura fija (el esquema de la tabla), Cassandra almacena "familias de columna" en "keyspaces." En términos más descriptivos, Cassandra asocia un valor de clave con un número variable de pares nombre/valor (columnas) que puede ser totalmente diferente de "fila" de uno a otro.

Por ejemplo, considere la clave «Tierra» he creado la última vez, con una columna de familia llamado "Gente", en el que voy a escribir las filas que (puede o no) este aspecto:

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"

Como puede ver, cada "fila" contiene datos conceptualmente similares, pero no todas las "filas" tendrá los mismos datos, dependiendo de lo que el desarrollador o el negocio necesita para almacenar cualquier clave de fila determinada. No sé la edad de Rick, por lo que yo no podía almacenar. En una base de datos relacional, si el esquema el mandato era una columna que no acepta valores NULL, que no podía haber guardado Rick en todo. Cassandra dice, "por qué no?"

Mi columna anterior demostró la inserción y extracción de datos desde la línea de comandos, pero esto no es especialmente útil si el objetivo es escribir aplicaciones que tendrá acceso y almacenar datos. Así, sin más antecedentes, analizaremos en lo que se necesita para escribir aplicaciones que leen y almacenan a Cassandra.

¿Cassandra, O Cassandra, por tanto eres Cassandra?

Para empezar, necesito conectar a Cassandra de Microsoft .NET Framework. Al hacerlo implica uno de dos técnicas: ¿Puedo usar la API nativa de ahorro Apache, o puedo utilizar un contenedor de terceros sobre la API nativa de ahorro. Ahorro es un toolkit de llamada procedimiento remoto binario, similar en muchos aspectos a CORBA, DCOM (apuesta que no ha pensado que en unos años) o .NET Remoting. Es un enfoque particular de bajo nivel para comunicarse con Casandra, y mientras que ahorro tiene C# admite, no es trivial para obtener todo lo que y funcionando. Alternativas de ahorro incluyen FluentCassandra, cassandra-sharp, Cassandraemon y Aquiles (la traducción al español de Aquiles, que mantiene el tema griego antiguo vivito y coleando). Todas ellas son de código abierto y ofrecen algunas abstracciones más agradables sobre la API de Cassandra. Para esta columna, voy a usar FluentCassandra, pero ninguna de ellas parece funcionar bastante bien, la extraña guerra de llama de Internet a pesar de.

FluentCassandra está disponible como un paquete de NuGet, así que la forma más sencilla de empezar es disparar hasta el gestor de paquetes de NuGet en un proyecto de prueba de Visual Studio (por lo que puedo escribir pruebas de exploración) y no un "FluentCassandra de paquete de instalación". (La versión más reciente a partir de este escrito es 1.1.0.) Una vez hecho esto, y yo he comprobado que el servidor de Cassandra sigue funcionando después de se barajó para la columna de agosto, puedo escribir la primera prueba de la exploración: conectar con el servidor.

FluentCassandra vive en el espacio de nombres "FluentCassandra" y dos espacios de nombres anidados ("Conexiones" y "Tipos"), así que te los traen y luego escribir una prueba para saber acerca de la conexión a la base de datos:

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

Cuenta que cuando usted lea esto, es posible que el número de versión distinto de cuando lo escribí, así que si esa aseveración segunda falla, compruebe la ventana de salida para ver la cadena devuelta. (Recuerde, pruebas de exploración son acerca de cómo probar su comprensión de la API, así no escribir la salida es tan mala idea como en una prueba de unidad automatizada).

La clase CassandraContext tiene cinco diferentes sobrecargas para conectar a un servidor en ejecución Cassandra, todos ellos muy fácil inferir — tratan información de conexión de una forma u otra. En este caso particular, porque yo no he creado la clave en el que quiero tienda (y posterior lectura) los datos, soy conexión a la clave del "sistema", que es utilizado por Cassandra para almacenar diversos detalles sistémicas en mucho la misma manera que más bases de datos relacionales tienen reservada para la seguridad y metadatos de la base de datos de una instancia y tal. Pero esto significa que no quiero escribir a esa clave del sistema; Quiero crear mi propia, que forma la siguiente prueba de exploración, como se muestra en figura 1.

Figura 1 crear una clave del sistema

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

Es cierto que el lazo a través de todos los keyspaces en la base de datos es innecesario — lo hago aquí para demostrar que hay lugares en la API de FluentCassandra donde las subyacentes ojeadas API basada en el ahorro a través de y el "Apache.Cassandra.KsDef" tipo es uno de esos.

Ahora que tengo una clave, necesito la familia de al menos una columna dentro de esa clave. La forma más sencilla de crear este utiliza Cassandra Query Language (CQL), un lenguaje SQL como vagamente, como se muestra en figura 2.

Figura 2 crear una familia de columna utilizando el lenguaje de consulta 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);
  }
}

El peligro de CQL es que su gramática SQL como deliberadamente combina con la fácil percepción errónea "Cassandra tiene columnas, por lo tanto, debe tener las tablas como una base de datos relacional" para engañar incauto Desarrollador pensando en términos relacionales. Esto lleva a supuestos conceptuales que están tremendamente equivocados. Consideremos, por ejemplo, las columnas de figura 2. En una base de datos relacional, se permitiría únicamente las cinco columnas en esta familia de columna. En Casandra, son sólo "directrices" (en un sonreía "Piratas del Caribe" tipo de camino). Pero, la alternativa (para no usar CQL en todo) es menos atractiva por mucho: Cassandra ofrece la API TryCreateColumnFamily (no mostrado), pero no importa cuantas veces trate de envolver mi cabeza alrededor de él, se siente todavía más torpe y confuso que el enfoque CQL.

' Datos, datos, datos! No puedo hacer ladrillos sin arcilla!'

Una vez que la familia de la columna en su lugar, el poder real de la API de FluentCassandra emerge como guardar algunos objetos en la base de datos, como se muestra en figura 3.

Figura 3 almacenar objetos en la base de datos

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

Observe el uso de las instalaciones "dinámicos" de C# 4.0 para reforzar la idea de que la familia de la columna no es una colección estrictamente con tipo de pares nombre/valor. Esto permite que el código de C# reflejar la naturaleza de la tienda de datos orientados a la columna. Puedo ver esto cuando almacenar unas cuantas personas más en la clave, como se muestra en figura 4.

Figura 4 almacenar más gente en la clave

 

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

Una vez más, sólo para impulsar el punto de inicio, observe cómo Rick tiene una columna de natal, que no fue especificada en la descripción anterior de esta familia de columna. Esto es totalmente aceptable y bastante común.

También observe que la API de FluentCassandra ofrece la propiedad "LastError", que contiene una referencia a la última excepción producida fuera de la base de datos. Esto puede ser útil para comprobar cuando el estado de la base de datos no se sabe ya (como al volver de un conjunto de llamadas que podría haber comido la excepción, o si la base de datos está configurado para no producir excepciones).

Una vez más, con sentimiento

Conexión a la base de datos, crear la clave (y más tarde las caídas), definir las familias de la columna y poner algunos datos de semilla — probablemente voy a querer hacer estas cosas mucho dentro de estas pruebas. Esa secuencia de código es un gran candidato para poner en el programa de instalación previa y posterior métodos de desmontaje. Por la clave después de las caídas y recreando antes de cada prueba, mantener la base de datos prístino y en un estado conocido cada vez ejecuta una prueba, como se muestra en figura 5. Dulce.

Figura 5 ejecutando una prueba

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

'Mirar mis trabajos, todos vosotros, poderoso y la desesperación!'

Lectura de datos de Cassandra tarda un par de formas. La primera es para recuperar los datos de la familia de columna utilizando el método Get en el objeto de CassandraColumnFamily, se muestra en la figura 6.

Figura 6 datos con el método 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);
  }
}

Esto es genial si sé la clave antes de tiempo, pero gran parte del tiempo, que no es el caso. De hecho, es discutible que la mayor parte del tiempo, el registro exacto o registros no se conoce. Así, otro enfoque (no mostrado) es utilizar la integración de LINQ FluentCassandra para escribir una consulta de LINQ-estilo. Esto no es bastante tan flexible como LINQ tradicional, sin embargo. Porque no se conocen los nombres de columna antes de tiempo, es mucho más difícil de escribir consultas LINQ para encontrar todos los Newards (mirando el par nombre/valor de apellido de la familia de columna) en la base de datos, por ejemplo.

Afortunadamente, CQL paseos al rescate, como se muestra en figura 7.

Figura 7 mediante la integración de LINQ de Cassandra escribir una consulta de LINQ-estilo

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

Sin embargo, tenga en cuenta que Si ejecuto este código tal cual, se producirá: Cassandra Won't let me use un par de nombre y valor dentro de una familia de columna como un criterio de filtro, a menos que un índice se define explícitamente en él. Hacerlo requiere otra declaración de CQL:

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

Por lo general, quiero establecer que hasta el momento la familia de la columna se crea. Tenga en cuenta también que porque Cassandra es menos de esquema, el "seleccione *" parte de esa consulta es un poco engañoso — devolverá todos los pares de nombre y valor de la familia de la columna, pero eso no significa que cada registro de cada columna. Esto significa, entonces, que una consulta con "género donde = 'F'" nunca tendrá en cuenta los registros que no tienen una columna de "Género", que deja de Rick, Ted y "El artista anteriormente conocido como Prince" fuera de consideración. Esto es completamente diferente de un sistema de gestión de base de datos relacional, donde cada fila de una tabla debe tener valores para cada una de las columnas (aunque yo a menudo pato esa responsabilidad almacenando "NULL" en esas columnas, que es considerado por algunos como un pecado capital).

El lenguaje CQL completo es demasiado para describir aquí, pero una referencia completa está disponible en el sitio Web de Cassandra en bit.ly/MHcWr6.

Terminando, por ahora

No estoy hecho con la profetisa maldita todavía — mientras obteniendo datos de entrada y salida de Cassandra es la parte más interesante a un desarrollador (como que es lo que hacen todo el día), configuración de múltiples nodos es también una parte bastante grande de la historia de Cassandra. Hacer en una sola caja de Windows (para fines de desarrollo; verás cómo sería más fácil hacerlo en varios servidores) es no trivial, razón por la cual voy resumir la discusión sobre Cassandra haciendo esa próxima vez.

Por ahora, Feliz codificación!

Ted Neward es una consultora de arquitectura con Neudesic LLC. Ha escrito más de 100 artículos y autor o coautor de una docena de libros, incluyendo "Profesional F # 2.0" (Wrox, 2010). Es un F # MVP y destacado experto de Java y habla en Java y .NET conferencias alrededor del mundo. Consulta y mentores regularmente — llegar a él en ted@tedneward.com o Ted.Neward@neudesic.com si usted está interesado en tenerlo a venir a trabajar con su equipo. Blogs de él en blogs.tedneward.com y se puede seguir en Twitter en Twitter.com/tedneward.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Kelly Sommers