Este artigo foi traduzido por máquina.

O programador

Banco de dados NoSQL Cassandra, Parte 2: Programação

Ted Neward

 

Ted NewardNa minha coluna de agosto de 2012, "Cassandra NoSQL banco de dados: Getting Started,"examinei Apache Cassandra. É descrito como o "código aberto, distribuído, descentralizado, elasticamente escalável, altamente disponível, tolerante, tuneably consistente, orientada para a coluna de banco de dados que baseia sua concepção de distribuição na Amazon Dynamo e o seu modelo de dados no Google Bigtable" no livro, "Cassandra: O guia definitivo"(o ' Reilly Media, 2010). Para ser mais preciso, eu olhei para como instalar Cassandra (que, porque é um banco de dados baseado em Java, também necessário levantar uma máquina Virtual Java e rodando na sua máquina, se você não tiver um), como se conectar a ele na linha de comando e que seu modelo de dados parecia. O modelo de dados a pena repetir porque é bastante perceptível diferente na estrutura do banco de dados relacional que a maioria dos desenvolvedores são familiares.

Como já discutido de última hora (msdn.microsoft.com/magazine/JJ553519), Cassandra é um armazenamento de dados "coluna-oriented", que significa que em vez de armazenar identicamente estruturadas tuplas de dados organizados segundo uma estrutura fixa (o esquema da tabela), Cassandra armazena "famílias de coluna" em "keyspaces". Em termos mais descritivos, Cassandra associa um valor de chave com um número variável de pares nome/valor (colunas) que pode ser totalmente diferente de uma "linha" para outro.

Por exemplo, considere o seguinte "Terra", criei a última vez, com uma coluna família chamado "Povo", em que eu vou escrever linhas que (pode ou não pode) parecido com este:

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 você pode ver, cada "linha" contém dados conceitualmente similares, mas não todas as "linhas" terão os mesmos dados, dependendo o que o desenvolvedor ou negócios necessários para armazenar qualquer chave de linha específica. Eu não sei a idade de Rick, então eu não poderia armazená-lo. Em um banco de dados relacional, se o esquema obrigatória que idade era uma coluna não nulo, eu não conseguia armazenados Rick em tudo. Cassandra diz, "Porque não?"

Minha coluna anterior demonstrou inserindo e removendo dados na linha de comando, mas isso não é particularmente útil se o objetivo é escrever aplicações que irão acessar e armazenar dados. Assim, sem mais fundo, vamos mergulhar em que é preciso para escrever aplicativos que ler e armazenam para Cassandra.

Cassandra, O Cassandra, portanto és tu Cassandra?

Para começar, eu preciso ligar para Cassandra do Microsoft .NET Framework. Isso envolve uma das duas técnicas: Pode usar o Apache Thrift API nativa, ou eu posso usar um wrapper de terceiros no topo da API nativa de Thrift. Thrift é um toolkit de chamada de procedimento remoto binário, similar em muitas maneiras de DCOM (aposto que você não pensou que em poucos anos) ou CORBA ou .NET Remoting. É uma abordagem especial de baixo nível para comunicar-se com Cassandra, e enquanto Thrift tem c# suporta, não é trivial para obter tudo o que e funcionando. Alternativas ao Thrift incluem FluentCassandra, cassandra-sharp, Cassandraemon e Aquiles (a tradução espanhola de Aquiles, que mantém o tema grego antigo vivo e bem). Todos estes são open source e oferecem algumas abstrações mais agradáveis sobre a API de Cassandra. Para esta coluna, eu vou usar FluentCassandra, mas nenhum deles parece funcionar muito bem, o estranho Internet flame war não obstante.

FluentCassandra está disponível como um pacote NuGet, portanto, a maneira mais fácil de começar é fogo até o Gerenciador de pacotes NuGet em um projeto Visual Studio Test (assim eu posso escrever testes de exploração) e fazer um "pacote de instalação FluentCassandra." (A versão mais recente de redação deste é 1.1.0). Uma vez que é feito, e eu tenho verific novamente que o servidor de Cassandra está funcionando ainda, depois que eu brincava com ele para a coluna de agosto, posso escrever o primeiro teste de exploração: Conectando ao servidor.

FluentCassandra vive o namespace "FluentCassandra" e dois namespaces aninhados ("Conexões" e "Tipos"), então eu vou trazer os e, em seguida, escrever um teste para ver sobre a conexão ao banco de dados:

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 que no momento em que você ler isso, é possível que o número da versão será diferente de quando eu escrevi, então se essa segunda afirmação falhar, verifique a janela de saída para ver a seqüência de caracteres retornada. (Lembre-se, os testes de exploração são sobre como testar sua compreensão do API, para que não escrever a saída é tanto uma má idéia como é um teste de unidade automatizado.)

A classe CassandraContext tem cinco diferentes sobrecargas para conectar a um servidor em execução Cassandra, todos eles muito fácil inferir — todos eles lidam com informações de conexão de uma forma ou outra. Neste caso particular, porque eu ainda não criei o keyspace que quero armazenar (e posterior leitura) dos dados, estou ligando para o keyspace "sistema", que é usado por Cassandra para armazenar vários detalhes sistêmicos em muito da mesma maneira que mais bancos de dados relacionais têm uma instância reservada para a segurança e os metadados do banco de dados e tal. Mas isso significa que eu não quero escrever para esse espaço de sistema; Eu quero criar o meu próprio, que constitui o próximo teste de exploração, como mostrado na Figura 1.

Figura 1 criação de um 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 que o loop através de todas as keyspaces no banco de dados é desnecessário — fazer aqui para demonstrar que existem lugares na API do FluentCassandra onde o covarde baseado em Thrift API subjacente através de e o "Apache.Cassandra.KsDef" tipo é um desses.

Agora que tenho um keyspace, preciso de família pelo menos uma coluna dentro desse espaço. A maneira mais fácil para criar este usa linguagem de consulta Cassandra (CQL), uma linguagem vagamente semelhante do SQL, conforme mostrado na Figura 2.

Figura 2 Criando uma família de coluna usando linguagem 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);
  }
}

O perigo de CQL é que sua gramática deliberadamente SQL como combina com o equívoco fácil que "Cassandra tem colunas, portanto ele deve ter tabelas de um banco de dados relacional" para enganar o incauto desenvolvedor a pensar em termos relacionais. Isto leva a pressupostos conceituais que enganam-se descontroladamente. Considere, por exemplo, as colunas no Figura 2. Em um banco de dados relacional, apenas as cinco colunas seriam permitidas nesta família de coluna. Em Cassandra, aqueles são apenas "diretrizes" (em um singularmente "Piratas do Caribe" espécie de caminho). Mas, a alternativa (para não usar CQL em todos) é de longe menos atraente: Cassandra oferece a API TryCreateColumnFamily (não mostrado), mas não importa quantas vezes eu tento quebrar minha cabeça em torno dela, isso ainda se sente mais desajeitado e confusa do que a abordagem CQL.

‘Dados, dados, dados! não posso fazer tijolos sem argila!’

Uma vez que a família de coluna está no lugar, o verdadeiro poder da API do FluentCassandra emerge como armazenar alguns objetos no banco de dados, como mostrado na Figura 3.

Figura 3 armazenar objetos no banco de dados

[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 o uso das instalações "dinâmicas" do c# 4.0 para reforçar a ideia de que a família de coluna não é uma coleção estritamente tipada de pares nome/valor. Isso permite que o código c# refletir a natureza do armazenamento de dados orientado por coluna. Eu posso ver isso quando armazenar mais algumas pessoas para o espaço, como mostrado na Figura 4.

Figura 4 o Keyspace armazenar mais pessoas

 

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

Novamente, apenas para conduzir o repouso do ponto, observe como Rick tem uma coluna de Natal, que não foi especificada na descrição anterior desta família de coluna. Isso é totalmente aceitável e bastante comum.

Observe também que a API de FluentCassandra oferece a propriedade "LastError", que contém uma referência para a última exceção acionada fora do banco de dados. Isso pode ser útil para verificar quando o estado do banco de dados não é conhecido já (tais como ao retornar de um conjunto de chamadas que podem ter comido a exceção Descartado, ou se o banco de dados está configurado para não lançar exceções).

Mais uma vez, com sentimento

Conectando ao banco de dados, criando o keyspace (e deixá-la cair mais tarde), definindo as famílias de coluna e colocar em alguns dados de sementes — eu provavelmente vou querer fazer essas coisas muito dentro destes testes. Essa seqüência de código é um grande candidato para colocar em instalação pré-teste e Post-Test métodos teardown. Descartando o keyspace depois e recriá-lo antes de cada ensaio, manter o banco de dados pura e em um estado conhecido cada vez que eu executar um teste, como mostrado na Figura 5. Lindo.

Figura 5 executando um teste

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

'Olhar para meus trabalhos, todos vós, poderoso e desespero!'

Leitura de dados de Cassandra leva um par de formulários. A primeira é buscar os dados fora da família de coluna usando o método Get no CassandraColumnFamily objeto, mostrado na Figura 6.

Figura 6 buscando dados com o 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);
  }
}

Isso é ótimo, se souber a chave à frente do tempo, mas muito do tempo, que não é o caso. Na verdade, é discutível que a maior parte do tempo, o registro exato ou registros de não ser conhecidos. Assim, outra abordagem (não mostrada) é usar a integração do LINQ FluentCassandra para escrever uma consulta LINQ-estilo. Isto não é completamente tão flexível quanto o LINQ tradicional, no entanto. Porque os nomes de coluna não são conhecidos antes do tempo, é muito mais difícil de escrever consultas LINQ para encontrar todos os Newards (olhando para o par de nome/valor do sobrenome da família coluna) no banco de dados, por exemplo.

Felizmente, CQL passeios para o resgate, como mostrado na Figura 7.

Figura 7 usando Cassandra LINQ integração para escrever uma consulta 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");
    }
  }
}

Observe, entretanto, que, se eu executar esse código, como é, ele falhará — Cassandra não vai me deixar usar um par nome/valor dentro de uma família de coluna como um critério de filtro, a menos que um índice é definido explicitamente. Isso requer uma outra declaração de CQL:

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

Geralmente, eu quero definir que até no momento a família de colunas é criada. Observe também que, porque Cassandra é esquema-menos, o "Selecione *" parte dessa consulta é um pouco enganosa — ele irá retornar todos os pares nome/valor da família de coluna, mas isso não significa que cada registro terá cada coluna. Isso significa, então, que uma consulta com "WHERE sexo = 'F'" nunca irá considerar os registros que não tem uma coluna "Sexo", que deixa Rick, Ted e "O artista anteriormente conhecido como Príncipe" fora de consideração. Isso é completamente diferente de um sistema de gerenciamento de banco de dados relacional, onde cada linha em uma tabela deve ter valores para cada uma das colunas (embora eu muitas vezes pato essa responsabilidade, armazenando "NULL" nessas colunas, que é considerado por alguns como um pecado cardinal).

A língua CQL completa é demais para descrever aqui, mas uma referência completa está disponível no site da Cassandra bit.ly/MHcWr6.

Resumindo, para agora

Sou não é bem feito com a profetisa maldita ainda — embora obtendo dados dentro e fora de Cassandra é a parte mais interessante para um desenvolvedor (como que é o que eles fazem todos os dias), configuração de vários nós também é uma parte muito grande da história de Cassandra. Fazer isso em uma única caixa de Windows (para fins de desenvolvimento; você verá como é mais fácil de fazer em vários servidores) é não exatamente trivial, por isso vou de encerrar a discussão sobre Cassandra fazendo da próxima vez.

Por agora, feliz codificação!

Ted Neward é um consultor de arquitetura na Neudesic LLC. Ele já escreveu mais de 100 artigos e é autor ou coautor de dezenas de livros, incluindo “Professional F# 2.0” (Wrox, 2010). Ele é um F # MVP e especialista Java e fala em conferências, Java e .NET em todo o mundo. Ele consulta e mentores regularmente — contatá-lo em ted@tedneward.com ou Ted.Neward@neudesic.com se você está interessado em ter-lhe vir trabalhar com sua equipe. Ele blogs em blogs.tedneward.com e pode ser seguido no Twitter em Twitter.com/tedneward.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Kelly Sommers