Статьи

Использование Couchbase Ruby Gem с EventMachine

Примечание куратора: содержание этой статьи изначально было написано Сергеем Авсеевым в блоге Couchbase. 

Как вы, возможно, заметили, новый рубиновый камень couchbase был недавно выпущен. Релиз 1.2.2 в основном является техническим выпуском с несколькими исправлениями ошибок, но вы можете попробовать одну новую экспериментальную функцию: интеграцию с библиотекой EventMachine . В этом посте вы познакомитесь с тем, как начать использовать Couchbase Server с вашими приложениями на основе асинхронной модели EventMachine.

Интеграция с EventMachine доступна (в настоящее время) только в UNIX-подобных системах (таких как Linux, Solaris, BSD). Поскольку он использует волокна, он также требует MRI ruby ​​версии 1.9 или более поздней.

Настройте свою песочницу

Первым шагом является установка библиотеки libcouchbase, которая обрабатывает все подробности низкоуровневого протокола Couchbase. Вы можете следовать руководству по установке на официальной странице . Здесь я буду только повторять шаги, необходимые для типичного блока GNU / Linux (я использую нестабильную версию Debian):

Установите ключ хранилища PGP:

$ wget -O- http://packages.couchbase.com/ubuntu/couchbase.key | sudo apt-key add -

Настройка источника репозитория. Здесь я использую ссылку для Ubuntu 12.04, но в целом это не имеет значения, потому что мы собираемся использовать плагин EventMachine, который встроен в сам гем. Пакеты находятся в разных репозиториях пакетов, созданных с использованием одной и той же кодовой базы; единственное отличие — версия библиотек ввода-вывода ( libevent , libev ), включенная в версию дистрибутива.

$ sudo wget -O/etc/apt/sources.list.d/couchbase.list http://packages.couchbase.com/ubuntu/couchbase-ubuntu1204.list

Установите заголовки libcouchbase, библиотеку ядра и символы отладки. Опять же, вы можете захотеть установить инструменты командной строки или один из бэкэндов ввода-вывода, но это не требуется для выполняемой задачи.

$ sudo apt-get update
$ sudo sudo apt-get install libcouchbase-dev libcouchbase2-core libcouchbase-dbg

Вот и все. 

Теперь вам нужно установить Couchbase Server , следуя инструкциям с официального сайта. После установки вы получите консоль администратора, работающую по адресу http: // localhost: 8091, а также REST API, доступный по тому же порту. Пройдите начальные этапы настройки, и в конце концов вы назначите корзину с именем «default».

Наконец, вам нужно установить сам камень. Это так же просто, как набрать это в терминале:

$ gem install couchbase
Building native extensions.  This could take a while...
Successfully installed couchbase-1.2.2
1 gem installed
Installing ri documentation for couchbase-1.2.2...
Installing RDoc documentation for couchbase-1.2.2...

Сборка приложения

Чтобы продемонстрировать интеграцию, давайте создадим простое приложение чата с использованием EventMachine и добавим ведение журнала для всех событий в корзине Couchbase. Построить асинхронное приложение с помощью EventMachine чрезвычайно просто, и чтобы доказать это, я приведу полный исходный код в этом посте (он также находится в  каталоге examples / chat-em исходников gem).

class ChatServer < EM::Connection

  @@clients = []

  def post_init
    @username = nil
    send_data("*** What is your name?\n")
  end

  def receive_data(data)
    if @username
      broadcast(data.strip, @username)
    else
      name = data.gsub(/\s+|[\[\]]/, '').strip[0..20]
      if name.empty?
        send_data("*** What is your name?\n")
      else
        @username = name
        @@clients.push(self)
        broadcast("#{@username} has joined")
        send_data("*** Hi, #{@username}!\n")
      end
    end
  end

  def unbind
    @@clients.delete(self)
    broadcast("#{@username} has left") if @username
  end

  def broadcast(message, author = nil)
    prefix = author ? "<#{@username}>" : "***"
    @@clients.each do |client|
      unless client == self
        client.send_data("#{prefix} #{message}\n")
      end
    end
  end

end

EventMachine.run do
  # hit Control + C to stop
  Signal.trap("INT")  { EventMachine.stop }
  Signal.trap("TERM") { EventMachine.stop }

  EventMachine.start_server("0.0.0.0", 9999, ChatServer)
end

Это типичный сервер EventMachine на основе EM :: Connection. Для тех, кто не знает значения этих переопределенных методов, вот выдержка из официальной документации :

EventMachine :: Connection — это класс, который создается циклом обработки EventMachine при каждом создании нового соединения. (Новые подключения могут быть инициированы локально на удаленном сервере или приняты локально с удаленного клиента.) Когда создается экземпляр объекта Connection, он смешивается с функциональностью, содержащейся в пользовательском модуле, указанном в вызовах connect или start_server. Пользовательские модули-обработчики могут переопределять любой или все стандартные методы, определенные здесь, а также добавлять произвольный дополнительный код, который также будет добавлен.

EventMachine управляет одним объектом, унаследованным от EventMachine :: Connection (и содержащим смешанный код пользователя) для каждого сетевого подключения, которое активно в любой момент времени. Цикл событий будет автоматически вызывать методы для объектов EventMachine :: Connection всякий раз, когда в соответствующих соединениях происходят определенные события, как описано ниже.

Этот класс никогда не создается в пользовательском коде и не публикует метод инициализации. Методы экземпляра EventMachine :: Connection, которые могут вызываться циклом событий: #post_init, #connection_completed, #receive_data, #unbind, #ssl_verify_peer (если используется TLS), #ssl_handshake_completed

Все остальные методы экземпляра, определенные здесь, вызываются только кодом пользователя.

Протокол очень прост и ориентирован на линию. Для каждого соединения EventMachine создаст экземпляр ChatServer, который сначала запрашивает имя нового участника, а затем передает все его сообщения группе. Вы можете использовать ваш любимый инструмент, который позволяет вам общаться по произвольному текстовому протоколу, например, через telnet или nc. Вот пример сеанса между конечными точками.

~ $ telnet localhost 9999           │ ~ $ nc localhost 9999
Trying 127.0.0.1...                 │ *** What is your name?
Connected to localhost.             │ alice
Escape character is '^]'.           │ *** Hi, alice!
*** What is your name?              │ *** bob has joined
bob                                 │ <bob> hi everyone
*** Hi, bob!                        │ hello, bob! how are you?
hi everyone                         │ ^C
<alice> hello, bob! how are you?    │ ~ $
*** alice has left                  │
^]                                  │
telnet> Connection closed.          │
~ $                                 │

Теперь пришло время добавить немного Couchbase. Представьте, что я хотел бы хранить все сообщения в распределенной базе данных настолько эффективно, насколько смогу. Couchbase это ответ :). Для этого мне нужно:

Реализуйте метод log в классе ChatServer, который должен принимать сообщение и необязательного автора (для системных событий это будет ноль):

def log(message, author = nil)
  Couchbase.bucket.incr("log:key", :initial => 1) do |res|
    entry = {
      'time' => Time.now.utc,
      'author' => author || "[system]",
      'message' => message
    }
    Couchbase.bucket.set("log:#{res.value}", entry)
  end
end

Затем я добавляю вызов log (message, author) в методе широковещательной рассылки непосредственно перед итерацией всех подключенных клиентов. И оберните EventMachine.start_server обратным вызовом Couchbase :: Bucket # on_connect, чтобы запустить сервер сразу после подключения клиента. Результирующее выполнение цикла будет выглядеть так:

EventMachine.run do
  # hit Control + C to stop
  Signal.trap("INT")  { EventMachine.stop }
  Signal.trap("TERM") { EventMachine.stop }

  Couchbase.connection_options = {:async => true, :engine => :eventmachine}
  Couchbase.bucket.on_connect do |res|
    if res.success?
      EventMachine.start_server("0.0.0.0", 9999, ChatServer)
    else
      puts "Cannot connect to Couchbase Server: #{res.error}"
    end
  end
end

Это пока что! В будущем мы можем расширить этот пример, чтобы использовать более современные методы, такие как em-synchrony и, возможно, websockets. Следите за обновлениями в этом блоге.

Бонусные очки

Простое ведение журнала может быть не таким интересным, с Couchbase Server вы можете выполнять простую аналитику с помощью запросов View, используя возрастающую удивительность Couchbase Map-Reduce. Например, вот функция Map для получения всех записей в хронологическом порядке.

function (doc, meta) {
  if (doc.message) {
    if (doc.author == "[system]" && doc.time) {
      emit(new Date(doc.time), "*** " + doc.message);
    } else {
      emit(new Date(doc.time), "<" + doc.author + "> " + doc.message);
    }
  }
}

И вывод JSON.

{"total_rows":6,"rows":[
  {"id":"log:1","key":"2013-02-11T19:08:05.000Z","value":"*** alice has joined"},
  {"id":"log:2","key":"2013-02-11T19:08:18.000Z","value":"*** bob has joined"},
  {"id":"log:3","key":"2013-02-11T19:08:38.000Z","value":"<bob> hi everyone"},
  {"id":"log:4","key":"2013-02-11T19:08:48.000Z","value":"<alice> hello, bob! how are you?"},
  {"id":"log:5","key":"2013-02-11T19:08:58.000Z","value":"*** alice has left"},
  {"id":"log:6","key":"2013-02-11T19:09:01.000Z","value":"*** bob has left"}
]}

Ладно, пока это все. Наслаждайтесь этой экспериментальной новой функцией. Это будет полностью поддержано в будущем выпуске. Если у вас возникнут какие-либо проблемы, пожалуйста, зарегистрируйте проблему на трекере проблем проекта RCBC . Исправления и дополнения всегда приветствуются, и это с открытым исходным кодом под лицензией Apache 2.0. Вы найдете источники на github .