Статьи

Краткое введение в Riak, от кластеров до ссылок на MapReduce

Во время недавних исследований баз данных NoSQL я наткнулся на Риака, обнаружил, что заинтригован, и решил немного углубиться. Быстро и грязно: Riak — это распределенная база данных с ключом-значением, состоящая из нескольких независимых узлов, которые можно объединить в кластеры Riak. Как правило, когда данные записываются в кластер Riak, они записываются на несколько узлов, так что даже если один узел выйдет из строя, данные все равно будут доступны. Одним из товарных знаков Riak является ориентация на высокую доступность.

Будучи базой данных NoSQL, Riak делает все относительно просто. Он не увязает в сложных, жестких схемах или сложных отношениях между частями данных, которые проявляются как внешние ключи, ограничения и тому подобное в типичной СУБД. Скорее, данные хранятся в серии помеченных «сегментов» и классифицируются по типу MIME. После того как вы настроили кластер Riak и привязали его к порту, вы можете использовать его RESTful API для взаимодействия с ним. Отправьте PUT для создания или обновления записей, DELETE для их удаления и GET для чтения их значений. Для большинства основных операций URL будет иметь вид:

HTTP: // SERVER: PORT / Riak / Ковш / KEY

Настройка Riak Cluster

Самый простой способ настроить кластер Riak — это скомпилировать из исходного кода. Я бы рекомендовал следовать инструкциям на вики-странице Basho, чтобы сделать это. Эти три или четыре узла разработки дают вам более чем достаточно, чтобы покопаться и начать экспериментировать с Riak.

Несколько примеров …

Почему бы не начать с Привет, мир? Используйте curl в командной строке для создания HTTP-запроса:

curl -X PUT http://localhost:8091/riak/pages/hello.html -H "Content-type: text/html" -d "<html><head><title>Greeting</title></head><body>Hello from Riak!</body></html>"

Достаточно просто. Это хранит полную HTML-страницу в вашем «страницах» и соответствующим образом классифицирует ее, чтобы Riak знал, как обрабатывать ее при доступе к ней. Теперь, если я открою свой браузер и зайду на http: // localhost: 8091 / riak / pages / hello.html, моя страница hello world возвращается. Удивительно! Ну, не особенно, но это начало.

Далее, почему бы нам не сохранить некоторые более значимые данные, такие как объект в кодировке JSON, представляющий некоторые реальные данные? Кодирование объектов в виде JSON удобно при использовании Riak с помощью кода, потому что когда вы извлекаете значение объекта, оно, как правило, сохраняется в поиске значения ключа (например, хэша Ruby), что должно сделать инициализацию объектов в вашем коде легкой и безболезненной. Это также упрощает передачу данных на пользовательскую карту и упрощает функции, что мы увидим позже.

Нет ничего плохого в использовании curl, но он становится немного утомительным для всего, кроме самых простых операций. Давайте переключимся на Ruby, используя гем Riak (запустите «gem install riak», чтобы получить его), и сохраним объект, представляющий человека. мы поместим это в ведро людей.

  require "riak"
  
  client = Riak::Client.new(:http_port => 8091)
  bucket = client.bucket("people")
  
  bruce = Riak::RObject.new(bucket, "Bruce")
  bruce.content_type = "application/json"
  bruce.data = { "name" => "Bruce Wayne", "quote" => "I'm Batman." }
  
 bruce.store

После инициализации клиентского объекта Riak все, что вам нужно сделать, это получить ссылку на корзину (она будет создана, если вы впервые обращаетесь к ней), затем я могу создать новый объект Riak в этой корзине, установить его тип содержимого и данные, и хранить их. Просто!

Теперь у меня есть свой собственный супергерой JSON Alter Ego.

Добавление ссылок

Riak поддерживает отношения между записями, используя простые записи ссылок. Ссылка связана с одной записью и имеет одну запись назначения, на которую она ссылается. Каждая ссылка имеет пользовательский тег. При обходе ссылок этот тег дополнительно указывается в качестве параметра поиска, который сужает ссылки, по которым Riak будет пытаться следовать. Например, если бы мы использовали Riak в сценарии социальных сетей, у нас могли бы быть записи для каждого отдельного человека со ссылками, помеченными как «друг», ведущими к каждому из их отдельных друзей. Или каждая запись может содержать ссылку с тегом «профиль», которая ведет к записи для изображения, которое они используют в качестве изображения профиля. Возможности безграничны.

Давайте дадим Брюсу некоторую компанию. Предполагая, что мы находимся в том же сеансе irb, что и последний фрагмент кода, и клиент уже инициализирован, мы можем сделать следующее:

 alfred = Riak::RObject.new("people", "Alfred")
 alfred.content_type = "application.json"
 alfred.data = { "name" => "Alfred Pennyworth", "quote" => "Good evening, sir." }
 
 link = Riak::Link.new("people", "Bruce", "works_for")
 alfred.links.add(link)
 
 alfred.store

 

Мы создаем новый объект Riak в корзине людей, как и раньше, затем создаем ссылку и добавляем ее в новую запись. Конструктор ссылок принимает параметры для корзины, к которой он привязан, ключ, под которым хранится запись назначения, и тег, который будет присвоен ссылке. Таким образом, у Альфреда есть ссылка с тегом «works_for», которая ведет к записи «Bruce» в корзине «people». Опять же, довольно очевидно.

Также обратите внимание, что запись, на которую ведет ссылка, не должна существовать при создании ссылки. Совершенно законно создать ссылку на запись, которая еще не содержит данных, но может быть заполнена в будущем.

Что мы делаем, когда хотим получить запись, к которой ведет ссылка? Опять же в том же сеансе:

 links = alfred.links.select { |x| x.tag == "works_for" }
 
 links.each do |x|
 	obj = client.bucket(link.bucket).get(link.key).data
 	puts obj["name"]	# "Bruce Wayne"
 	puts obj["quote"]	# "I'm Batman"
 end

Ссылочный объект Riak — это на самом деле просто тег и пункт назначения, поэтому после того, как вы получили ссылки с интересующими вас тегами, все, что осталось, — это посмотреть свойства корзины и ключа в ссылке (оба хранятся в виде простые строки) и иди захватить пункт назначения.

Конечно, это очень простой пример, но в дикой природе функциональность связывания Riak может использоваться для создания и обхода и очень больших и сложных наборов данных так же легко, как если бы создавался и проходил график.

Карта уменьшить в Риаке

В качестве заключительного трюка я продемонстрирую возможности сокращения карт Риака. Уменьшение карты — довольно сложная тема, но если вы никогда о ней не слышали, это широко используемая методика извлечения данных из больших и распределенных наборов данных с помощью двухэтапного процесса. Во-первых, функция «карта» применяется ко всем элементам входного набора. Эта функция возвращает некоторые данные, описывающие отдельную запись. Все данные, возвращаемые каждой отдельной функцией карты, отправляются в функцию «уменьшить», которая объединяет данные и использует их для ответа на какой-то более крупный вопрос.

Давайте использовать Riak, чтобы создать и сохранить набор из 100 результатов тестов, а затем использовать карту Riak Reduce, чтобы уменьшить количество раз, когда встречается каждая буквенная оценка.

Шаг 1: Создайте результаты тестов

  require 'riak'
  
  client = Riak::Client.new(:http_port => 8091)
  bucket = client.bucket("test_scores")
  grades = ['A', 'B', 'C', 'D', 'F']
  
  1.upto(100) do |num|
      score = Riak::RObject.new(bucket, num)
      score.content_type = "application/json"
     score.data = { "student_id" => num.to_s, "grade" => grades[rand(5)] }
     score.store
 end

Шаг 2: Используйте mapreduce Riak, чтобы подсчитать число каждой буквенной оценки

 require 'riak'
 
 client = Riak::Client.new(:http_port => 8091)
 mapred = Riak::MapReduce.new(client)
 
 # Here we're telling the mapreduce object to take the entire test_scores bucket as its input
 # In production, you'd probably want to use Riak's key filters to choose specific elements in a bucket
 # But since we know we only have 100 test scores, this is fine for us
 mapred.add("test_scores")
 
 # Map and reduce functions can be expressed in Erlang or JavaScript - we'll use JS here
 # From each test score, return just its grade and 1, so the reduce function can count all the results
 mapFunction = <<-EOF
 function (v) {
     var parsed_data = JSON.parse(v.values[0].data);
     var data = {};
     data[parsed_data.grade] = 1;
     return [data];
     }
 EOF
 
 # Collect all the results, iterate over them, and find the sum for each key
 reduceFunction = <<-EOF
 function(v) {
     var totals = {};
     for (var i in v) {
         for (var grade in v[i]) {
             if (totals[grade]) {
                 totals[grade] += v[i][grade];
             }
             else {
                 totals[grade] = v[i][grade];
             }
         }
     }
     return [totals];
 }
 EOF
 
 results = mapred.map(mapFunction).reduce(reduceFunction, :keep => true).run
 
 puts results[0].sort # => {"A"=>25, "B"=>24, "C"=>15, "D"=>20, "F"=>16}

Это было довольно круто.

Чтобы быстро запустить и запустить кластер Riak, не требуется много усилий, и, поскольку это действительно просто хранилище ключей-значений, при первом запуске не так много нужно изучить. Это может быть полезно для людей, которым не нужны сложности и издержки традиционной СУБД или которым не нужны сложные SQL-подобные запросы к своим данным, триггерам, ограничениям и тому подобное.

Тем не менее, Riak идет гораздо глубже, если вы хотите копать — благодаря поддержке поиска и преобразования карт, а также возможности осуществлять очень детальный контроль над чтением и записью, Riak должен быть особенно интригующим для всех, кто интересуется распределенными базами данных. Если это звучит так, как вы, но, возможно, у вас не хватает десятков гигабайт данных, что потребует более мощного движка, такого как HBase, или если вы просто заинтересованы в изучении новых движков баз данных NoSQL, возможно, Riak хорошо подходит для вашего следующего проекта.