Apresentando o Riak, Parte 1: A API HTTP independente da linguagem

Armazene e recupere dados usando a interface HTTP do Riak

Esta é a Parte 1 de uma série com duas partes sobre Riak, um armazenamento de dados altamente escalável e distribuído escrito em Erlang e baseado no Dynamo, o armazenamento de valor-chave de alta disponibilidade da Amazon. Aprenda sobre as noções básicas do Riak e como armazenar e recuperar itens usando sua API HTTP. Explore como usar sua estrutura Map/Reduce para realização de consultas distribuídas, como os links permitem a definição de relacionamentos entre objetos e como consultar esses relacionamentos usando link walking.

Simon Buckle, Independent Consultant, Freelance

Photograph of Simon BuckleSimon Buckle é consultor independente. Seus interesses incluem sistemas distribuídos, algoritmos e simultaneidade. Ele possui um mestrado em computação pela Imperial College, Londres. Confira seu website em simonbuckle.com.



30/Mar/2012

Introdução

Os bancos de dados relacionais típicos modernos apresentam um desempenho ruim em determinados tipos de aplicativos e têm dificuldades para lidar com as demandas de desempenho e escalabilidade dos aplicativos atuais da Internet. Uma abordagem diferente é necessária. Nos últimos anos, um novo tipo de armazenamento de dados, chamado normalmente de NoSQL, tornou-se popular, pois resolve diretamente algumas das deficiências dos bancos de dados relacionais. Riak é um exemplo desse tipo de armazenamento de dados.

Riak não é o único armazenamento de dados NoSQL que existe. Outros dois armazenamentos de dados populares são MongoDB e Cassandra. Apesar de serem parecidas de muitas maneiras, há também algumas diferenças significativas. Por exemplo, o Riak é um sistema distribuído enquanto o MongoDB é um banco de dados de sistema único— Riak não tem um conceito de nó mestre, tornando-o mais resiliente a falhas. Embora também tenha base na descrição da Amazon sobre o Dynamo, Cassandra omite recursos como relógios vetoriais e hashing consistente para a organização de seus dados. O modelo de dados do Riak é mais flexível. No Riak, os buckets são criados durante o primeiro acesso; o modelo de dados do Cassandra é definido em um arquivo XML, de modo que sua alteração exige a reinicialização de todo o cluster.

Outro ponto forte do Riak é ser escrito em Erlang. O MongoDB e o Cassandra são escritos com o que pode ser chamado de linguagens de uso geral (C++ e Java, respectivamente). Por outro lado, a Erlang foi projetada desde o início para suportar aplicativos distribuídos e tolerante a falhas e, dessa forma, é mais adequada para o desenvolvimento de aplicativos como armazenamento de dados NoSQL que compartilham algumas características com os aplicativos para os quais a Erlang foi originalmente criada.

As tarefas de Map/Reduce podem ser escritas somente em Erlang ou JavaScript. Para este artigo, escolhemos escrever as funções map e reduce em JavaScript, mas também é possível escrevê-las em Erlang. Embora o código Erlang seja um pouco mais rápido de executar, escolhemos o código JavaScript devido à sua acessibilidade a um público maior. Consulte Recursos e encontre links para aprender mais sobre o Erlang.


Introdução

Se você quiser experimentar alguns exemplos deste artigo, será necessário instalar o Riak (consulte Recursos) e Erlang em seu sistema.

Também é necessário desenvolver um cluster contendo três nós em execução em sua máquina local. Todos os dados armazenados no Riak são replicados para uma quantidade de nós no cluster. Uma propriedade (n_val) no bucket onde os dados estão armazenados determina o número de nós a serem replicados. O valor padrão desta propriedade é três, portanto, precisamos criar um cluster com pelo menos três nós (depois disso você pode criar quantos quiser) para que ele seja efetivo.

Depois de fazer o download do código de origem, é necessário desenvolvê-lo. As etapas básicas são as seguintes:

  1. Desempacote a origem: $ tar xzvf riak-1.0.1.tar.gz
  2. Altere o diretório: $ cd riak-1.0.1
  3. Desenvolva: $ make all rel

Isso desenvolverá o Riak (./rel/riak). Para executar diversos nós localmente, é necessário criar cópias de ./rel/riak — uma cópia para cada nó adicional. Copie ./rel/riak para ./rel/riak2, ./rel/riak3 e assim por diante e, em seguida, faça as seguintes alterações em cada cópia:

  • No riakN/etc/app.config mude os seguintes valores: a porta especificada na seção http{}, handoff_port e pb_port, para algo exclusivo
  • Abra riakN/etc/vm.args e mude o nome, também para algo exclusivo, por exemplo, -name riak2@127.0.0.1

Agora inicie cada nó por vez, como mostra a Listagem 1.

Lista 1. Listagem 1. Iniciando cada nó
$ cd rel
$ ./riak/bin/riak start
$ ./riak2/bin/riak start
$ ./riak3/bin/riak start

Finalmente, una os nós para criar um cluster, como mostra a Listagem 2.

Lista 2. Listagem 2. Criando um cluster
$ ./riak2/bin/riak-admin join riak@127.0.0.1
$ ./riak3/bin/riak-admin join riak@127.0.0.1

Agora você deve ter um cluster com três nós em execução localmente. Para testá-lo, execute o seguinte comando: $ ./riak/bin/riak-admin status | grep ring_members.

Você deverá ver cada nó do cluster recém-criado, por exemplo, ring_members : ['riak2@127.0.0.1','riak3@127.0.0.1','riak@127.0.0.1'].


A API do Riak

Atualmente, há somente três formas de acessar o Riak: uma API HTTP (interface RESTful), Buffers de protocolo e uma interface nativa de Erlang. Ter mais de uma interface proporciona o benefício de ser capaz de escolher como integrar seu aplicativo. Se você tiver um aplicativo escrito em Erlang, fará sentido usar a interface nativa de Erlang para conseguir uma integração perfeita entre os dois. Também há outros fatores, como desempenho, que podem desempenhar um papel na decisão sobre qual interface usar. Por exemplo, um cliente que usa a interface Buffers de protocolo terá um desempenho melhor do que outro que interage com a API HTTP; menos dados são comunicados e a análise de todos esses cabeçalhos HTTP por ter um preço (relativamente) alto em termos de desempenho. No entanto, os benefícios de ter uma API HTTP se devem ao fato de que a maioria dos desenvolvedores atuais — particularmente desenvolvedores da web — está familiarizada com interfaces RESTful e a maioria das linguagens de programação têm primitivos integrados para solicitação de recursos sobre HTTP, por exemplo, abrir uma URL, portanto, nenhum software adicional é necessário. Neste artigo, nos concentraremos na API HTTP.

Todos os exemplos usarão curl para interagir com o Riak por meio de sua interface HTTP. Isso serve apenas para entender um pouco mais sobre a API subjacente. Há diversas bibliotecas de cliente disponíveis em várias linguagens diferentes e você deverá considerar o uso de uma delas ao desenvolver um aplicativo que usa o Riak como o armazenamento de dados. As bibliotecas cliente fornecem uma API para o Riak que facilita a integração em seu aplicativo. Não será necessário escrever um código por conta própria para lidar com os tipos de respostas que você verá ao usar curl.

A API suporta os métodos HTTP normais: GET, PUT, POST, DELETE, que serão usados para recuperação, atualização, criação e exclusão de objetos respectivamente. Cada um deles será coberto.

Armazenando objetos

Pense em Riak como a implementação de um mapa distribuído de chaves (cadeias de caractere) para valores (objetos). O Riak armazena os valores em buckets. Não há a necessidades de criar explicitamente um bucket antes de armazenar um objeto em uma deles; se um objeto estiver armazenado em um bucket que não existe, ele será criado automaticamente para nós.

Os buckets são um conceito virtual no Riak e existem principalmente como uma forma de agrupar objetos relacionados. Os buckets também têm propriedades e o valor dessas propriedades define o que o Riak faz com os objetos armazenados nele. Veja alguns exemplos das propriedades do bucket:

  • n_val— O número de vezes que um objeto deve ser replicado no cluster
  • allow_mult— Se as atualizações simultâneas devem ser permitidas

Você pode ver as propriedades de um bucket (e seus valores atuais) fazendo uma solicitação GET no próprio bucket.

Para armazenar um objeto, fazemos um HTTP POST para uma das URLs exibidas na Listagem 3.

Lista 3. Listagem 3. Armazenando um objeto
POST -> /riak/<bucket> (1)
POST -> /riak/<bucket>/<key> (2)

As chaves podem ser alocadas automaticamente pelo Riak (1) ou definidas pelo usuário (2).

Ao armazenar um objeto com uma chave definida pelo usuário, também é possível fazer um HTTP PUT para (2) a fim de criar o objeto.

A versão mais recente do Riak também suporta o seguinte formato de URL: /buckets/<bucket>/keys/<key>, mas usaremos o formato antigo neste artigo para manter a compatibilidade reversa com versões anteriores do Riak.

Se nenhuma chave for especificada, o Riak alocará automaticamente uma chave para o objeto. Por exemplo, vamos armazenar um objeto de texto simples no bucket "foo" sem especificar explicitamente uma chave (consulte a listagem 4).

Lista 4. Listagem 4. Armazenando um objeto de texto simples sem especificar uma chave
$ curl -i -H "Content-Type: plain/text" -d "Some text" \
http://localhost:8098/riak/foo/

HTTP/1.1 201 Created
Vary: Accept-Encoding
Location: /riak/foo/3vbskqUuCdtLZjX5hx2JHKD2FTK
Content-Type: plain/text
Content-Length: ...

Ao examinar o cabeçalho Location, você pode ver a chave alocada pelo Riak para o objeto. Isso não é muito fácil de lembrar, portanto, a alternativa é fazer o usuário fornecer uma chave. Vamos criar um bucket de artistas e adicionar um artista que tem o nome de Bruce (consulte a Listagem 5).

Lista 5. Listagem 5. Criando um bucket de artistas e adicionando um artista
$ curl -i -d '{"name":"Bruce"}' -H "Content-Type: application/json" \
http://localhost:8098/riak/artists/Bruce

HTTP/1.1 204 No Content
Vary: Accept-Encoding
Content-Type: application/json
Content-Length: ...

Se o objeto tiver sido armazenado corretamente usando a chave que especificamos, receberemos uma resposta 204 No Content do servidor.

Neste exemplo, estamos armazenando o valor do objeto como JSON, mas poderia ser facilmente um texto simples ou algum outro formato. É importante observar que ao armazenar um objeto o cabeçalho Content-Type precisa estar definido corretamente. Por exemplo, se você quiser armazenar uma imagem JPEG, defina o tipo de conteúdo como imagem/jpeg.

Recuperando um objeto

Para recuperar um objeto armazenado, faça um GET no usando a chave do objeto que você deseja recuperar. Se o objeto existir, ele será retornado no corpo da resposta, caso contrário uma resposta 404 Object Not Found será retornada pelo servidor (consulte a Listagem 6).

Lista 6. Listagem 6. Executando um GET no bucket
$ curl http://localhost:8098/riak/artists/Bruce

HTTP/1.1 200 OK
...
{ "name" : "Bruce" }

Atualizando um objeto

Ao atualizar um objeto, assim como ao armazenar um objeto, o cabeçalho Content-Type é exigido. Por exemplo, vamos adicionar o apelido do Bruce, como mostra a Listagem 7.

Lista 7. Listagem 7. Adicionando o apelido do Bruce
$ curl -i -X PUT -d '{"name":"Bruce", "nickname":"The Boss"}' \
-H "Content-Type: application/json" http://localhost:8098/riak/artists/Bruce

Conforme mencionado anteriormente, o Riak cria buckets automaticamente. Os buckets têm propriedades. Uma dessas propriedades, allow_mult, determina se as gravações simultâneas são permitidas. Por padrão, ela está definida como falso; no entanto, se as atualizações simultâneas forem permitidas, o cabeçalho X-Riak-Vclock também deverá ser enviado para cada atualização. O valor desse cabeçalho deve ser definido como o valor que foi visto quando o objeto foi lido pela última vez pelo cliente.

O Riak usa relógios vetoriais para determinar a causa das modificações nos objetos. O modo como os relógios vetoriais funcionam está além do escopo deste artigo, mas basta dizer que quando as gravações simultâneas são permitidas, há a possibilidade da ocorrência de conflitos, e ficará por conta do aplicativo resolver esses conflitos (consulte Recursos).

Removendo um objeto

A remoção de um objeto segue um padrão parecido com os comandos anterior: nós simplesmente executamos um HTTP DELETE na URL que corresponde ao objeto que desejamos excluir: $ curl -i -X DELETE http://localhost:8098/riak/artists/Bruce.

Se o objeto tiver sido removido com êxito, receberemos uma resposta 204 No Content do servidor; se o objeto que estamos tentando excluir não existir, o servidor responderá com um 404 Object Not Found.


Até o momento, vimos como armazenar objetos associando um objeto a uma chave específica de modo que ele possa ser recuperado posteriormente. Seria útil se pudéssemos estender esse modelo simples para conseguir expressar como (e se) os objetos têm relação uns com os outros. Bom, nós podemos fazer isso, e o Riak consegue isso por meio de links.

Então, o que são links? Os links permitem que o usuário crie relacionamentos entre os objetos. Se você estiver familiarizado com os diagramas de classe UML, poderá pensar em um link como uma associação entre objetos com um rótulo descrevendo o relacionamento; em um banco de dados relacional, o relacionamento seria expresso usando uma chave estrangeira.

Os links são "anexados" aos objetos por meio do cabeçalho "Link". Veja abaixo um exemplo da aparência de um cabeçalho de link. O destino do relacionamento, por exemplo, o objeto com o qual estamos criando um vínculo, é o que está entre o sinal de maior e menor. O tipo de relacionamento — nesse caso "performer" — é expresso pela propriedade riaktag: Link: </riak/artists/Bruce>; riaktag="performer".

Vamos adicionar alguns álbuns e associá-los ao artista Bruce que tocou nos álbuns (consulte a Listagem 8).

Lista 8. Listagem 8. Adicionando alguns álbuns
$ curl -H "Content-Type: text/plain" \
-H 'Link: </riak/artists/Bruce> riaktag="performer"' \
-d "The River" http://localhost:8098/riak/albums/TheRiver

$ curl -H "Content-Type: text/plain" \
-H 'Link: </riak/artists/Bruce> riaktag="performer"' \
-d "Born To Run" http://localhost:8098/riak/albums/BornToRun

Agora que definimos alguns relacionamentos, é hora de consultá-los por meio do link walking — link walking é o nome dado ao processo de consulta dos relacionamentos entre os objetos. Por exemplo, para encontrar o artista que tocou no álbum The River, faça o seguinte: $ curl -i http://localhost:8098/riak/albums/TheRiver/artists,performer,1.

O pedaço no final é a especificação do link. Essa é a aparência de uma consulta de link. A primeira parte (artists) especifica o bucket ao qual devemos restringir a consulta. A segunda parte (performer) especifica a tag que desejamos usar para limitar os resultados e, finalmente, o 1 indica que desejamos incluir os resultados dessa fase específica da consulta.

Também é possível emitir consultas transitivas. Vamos assumir que definimos os relacionamentos entre os álbuns e artistas conforme a Figura 1.

Figura 1. Figura 1. Exemplo de relacionamento entre os álbuns e artistas

Agora é possível emitir consultas como "Quais artistas colaboraram com o artista que tocou em The River", executando o seguinte: $ curl -i http://localhost:8098/riak/albums/TheRiver/artists,_,0/artists,collaborator,1. O sublinhado na especificação do link age como um caractere curinga e indica que não nos preocupamos em qual é o relacionamento.


Executando consultas Map/Reduce

Map/Reduce é uma estrutura popularizada pelo Google para a execução de computações distribuídas em paralelo sobre enormes conjuntos de dados. O Riak também suporta Map/Reduce permitindo que as consultas mais eficientes sejam executadas nos dados armazenados no cluster.

Uma função Map/Reduce é composta por uma fase map e uma fase reduce. A fase map é aplicada a alguns dados e produz zero ou mais resultados; isso é equivalente em termos de programação funcional ao mapeamento de uma função sobre cada item em uma lista. As fases map ocorrem em paralelo. Em seguida, a fase reduce pega todos os resultados das fases map e os combina.

Por exemplo, considere contar o número de cada instância de uma palavra em um grande conjunto de documentos. Cada fase map calcularia o número de vezes que cada palavra aparece em um documento específico. Esses totais intermediários, depois de calculados, seriam enviados à função reduce que somaria os totais e emitiria o resultado para todo o conjunto de documentos. Consulte Recursos para obter um link ao documento sobre Map/Reduce do Google.


Exemplo: grep distribuído

Para este artigo, vamos desenvolver uma função Map/Reduce que fará um grep distribuído em um conjunto de documentos armazenados no Riak. Assim como o grep, a saída final será um conjunto de linhas que correspondem ao padrão fornecido. Além disso, cada resultado também indicará o número da linha no documento onde a correspondência ocorreu.

Para executar uma consulta Map/Reduce, realizamos um POST para o recurso /mapred. O corpo da solicitação é uma representação JSON da consulta; assim como nos casos anteriores, o cabeçalho Content-Type precisa estar presente e sempre definido como application/json. A Listagem 9 mostra a consulta que executaremos para realizar o grep distribuído. Cada parte da consulta será discutida.

Lista 9. Listagem 9. Exemplo de consulta Map/Reduce
{
  "inputs": [["documents","s1"],["documents","s2"]],
  "query": [
    { "map": {
        "language": "javascript",
        "name": "GrepUtils.map",
        "keep": true,
        "arg": "[s|S]herlock" }
    },
    { "reduce": { "language": "javascript", "name": "GrepUtils.reduce" } }
  ]
}

Cada consulta é composta por um número de entradas, por exemplo, o conjunto de documentos sobre os quais desejamos realizar uma computação, e o nome de uma função para executar durante as fases map e reduce. Também é possível incluir a origem das funções map e reduce diretamente na consulta usando a propriedade de origem em vez do nome, mas eu não fiz isso aqui; no entanto, para usar as funções nomeadas será necessário fazer algumas alterações nas configurações padrão do Riak. Salve o código da Listagem 9 em um diretório em algum lugar. Para cada nó no cluster, localize o arquivo etc/app.config, abra-o e defina a propriedade js_source_dir para o diretório onde você salvou o código. Será necessário reiniciar todos os nós no cluster para que as alterações tenham efeito.

O código na Listagem 10 contém as funções que serão executadas durante as fases map e reduce. A função map analisa cada linha do documento e verifica se ela corresponde ao padrão fornecido (o parâmetro arg ). A função reduce nesse exemplo específico não faz muito coisa; ela se comporta como uma função de identidade e retorna apenas sua entrada.

Lista 10. Listagem 10. GrepUtils.js
var GrepUtils = {
    map: function (v, k, arg) {
        var i, len, lines, r = [], re = new RegExp(arg);
        lines = v.values[0].data.split(/\r?\n/);
        for (i = 0, len = lines.length; i < len; i += 1) {
            var match = re.exec(lines[i]);
            if (match) {
                r.push((i+1) + “. “ + lines[i]);
            }
        }
        return r;
    },
    reduce: function (v) {
        return [v];
    }
};

Antes de podermos executar a consulta, precisamos de alguns dados. Eu fiz o download de alguns e-books do Sherlock Holmes do website Project Gutenberg (consulte Recursos). O primeiro texto está armazenado no bucket "documents" sob a chave "s1"; o segundo texto está no mesmo bucket com a chave "s2".

A Listagem 11 é um exemplo de como você pode carregar um documento como esse no Riak.

Lista 11. Listagem 11. Carregando um documento no Riak
$ curl -i -X POST http://localhost:8098/riak/documents/s1 \
-H “Content-Type: text/plain” --data-binary @s1.txt

Após o carregamento dos documentos, podemos pesquisá-los. Nesse caso, queremos quaisquer linhas que correspondam à expressão regular "[s|S]herlock" (consulte a Listagem 12).

Lista 12. Listagem 12. Pesquisando nos documentos
$ curl -X POST -H "Content-Type: application/json" \
http://localhost:8098/mapred --data @-<<\EOF
{
  "inputs": [["documents","s1"],["documents","s2"]],
  "query": [
    { "map": {
        "language":"javascript",
        "name":"GrepUtils.map",
        "keep":true,
        "arg": "[s|S]herlock" }
    },
    { "reduce": { "language": "javascript", "name": "GrepUtils.reduce" } }
  ]
}
EOF

A propriedade arg na consulta contém o padrão no qual desejamos realizar o grep para os documentos; esse valor é passado para a função map como o parâmetro arg .

A saída da execução da tarefa Map/Reduce nos dados de amostra está na Listagem 13.

Lista 13. Listagem 13. Amostra de saída da execução da tarefa Map/Reduce
[["1. Project Gutenberg's The Adventures of Sherlock Holmes, by Arthur Conan
Doyle","9. Title: The Adventures of Sherlock Holmes","62. To Sherlock Holmes
she is always THE woman. I have seldom heard","819. as I had pictured it from
Sherlock Holmes' succinct description,","1017. \"Good-night, Mister Sherlock
Holmes.\"","1034. \"You have really got it!\" he cried, grasping Sherlock
Holmes by" …]]

Otimizando Map/Reduce

Para concluir esta seção sobre Map/Reduce, vamos observar brevemente o recurso de otimização de Map/Reduce do Riak. É útil para tarefas com fases map que demoram um pouco para serem concluídas, uma vez que a otimização dos resultados permite acessar os resultados de cada fase map logo que eles fiquem disponíveis e antes da execução da fase reduce.

Podemos aplicar isso à consulta grep distribuída para conseguir esse resultado. A etapa reduce no exemplo não faz muita coisa. Na verdade, podemos nos livrar da fase reduce e emitir apenas os resultados de cada fase map diretamente para o cliente. Para conseguir isso, precisamos modificar a consulta removendo a etapa reduce e adicionando ?chunked=true ao final da URL para indicar que desejamos otimizar os resultados (consulte a listagem 14).

Lista 14. Listagem 14. Modificando a consulta para otimizar os resultados
$ curl -X POST -H "Content-Type: application/json" \
http://localhost:8098/mapred?chunked=true --data @-<<\EOF
{
  "inputs": [["documents","s1"],["documents","s2"]],
  "query": [
        { "map": {
            "language": "javascript",
            "name": "GrepUtils.map",
            "keep": true, "arg": "[s|S]herlock" } }
  ]
}
EOF

Os resultados de cada fase map — neste exemplo, linhas que correspondem à cadeia de caracteres de consultas — serão retornados ao cliente à medida que cada fase map é concluída. Essa abordagem seria útil para aplicativos que precisam processar os resultados intermediários de uma consulta assim que eles são disponibilizados.


Conclusão

Riak é um armazenamento de software livre e altamente escalável de valor da chave baseado em princípios do documento Dynamo da Amazon. Ele é fácil de implementar e escalar. É possível adicionar outros nós ao cluster. Recursos como link walking e suporte para Map/Reduce permitem consultas mais complexas. Além da API HTTP, também há uma API Erlang nativa e suporte para Buffers de protocolo. Na Parte 2 desta série, vamos explorar diversas bibliotecas cliente disponíveis em diferentes linguagens e mostrar como o Riak pode ser usado como um cache altamente escalável.

Recursos

Aprender

Obter produtos e tecnologias

  • Faça o download do Riak em basho.com.
  • Faça o download da linguagem de programação Erlang .
  • Inove seu próximo projeto de desenvolvimento de software livre usando um software especialmente para desenvolvedores; acesse versão de teste do software IBM, disponível para download ou em DVD.

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre
ArticleID=807305
ArticleTitle=Apresentando o Riak, Parte 1: A API HTTP independente da linguagem
publish-date=03302012