Ruby ♥ ØMQ (Fuck You, Erlang!) eveel сб 04 июн 2011 02:43:00 №6151680466

Сложные системы состоят из множества компонент, между которыми возникает необходимость организовывать взаимодействие, часто сводящееся к передаче сообщений. Разные команды разработчиков решали эту задачу по-разному. Итак, как же в рамках экосистемы Ruby можно решить эту задачу?

ØMQ — очень эффективная и навороченная библиотека для организации обмена сообщениями, не привязанная к конкретному транспортному уровню. Внутри одного процесса можно устроить обмен сообщениями между потоками поверх UNIX-сокетов или внутрипроцессового IPC, а удалённые приложения могут «разговаривать» друг с другом поверх протоколов TCP или UDP. Более того, ØMQ ориентирована на верхний уровень модели OSI, поэтому даже сообщение в полмегабайта переданное из пункта А в пункт Б придёт в целости и сохранности.

Внутри ØMQ реализованы традиционные паттерны построения сетевых приложений: парадигма «запрос-ответ», «издатель-подписчик», механизмы маршрутизации сообщений, построения отказоустойчивых систем и многое другое, описанное в гайде.

Итак, главное для нас — сообщения. Сообщениями обмениваются изолированные приложения при помощи специальных сокетов в контексте ØMQ.

Рассмотрим более-менее прикладную задачу. Пусть имеется Web-приложение, при помощи которого пользователи обрабатывают что-то большое. Это самое «что-то большое» должно обрабатываться и отдаваться клиентам, не вынуждая систему отказывать им в обслуживании из-за высокой нагруженности. Логично разделить приложение на две части (как показано на рисунке):

Архитектура системы

Возьмём Sinatra и биндинги к ØMQ. Кстати, из-за особенностей реализации потоков в MRI, следует использовать версию Ruby 1.9 и неофициальный гем ffi-rzmq. Официальный гем zmq «подвешивает» интерпретатор при завершении работы.

В качестве нагрузки будем вычислять CRC32-хэш переданной строки и вызывать sleep() на несколько секунд внутри бэкэнда, чтобы дать видимость сложной вычислительной задачи. Обмениваться данными будем в формате JSON при помощи библиотеки Yajl.

Итак, код бэкэнда. Вообще, я старался комментировать код, хотя проблем при чтении быть не должно: всё очень просто.

loop do
  begin
    logger.info(recv = pull.recv_string)
    data = Yajl.load(recv).first

    # imagine that we're processing this piece of crap
    crc32 = Zlib.crc32(data)
    sleep 2

    rep = [ data, crc32 ].to_json
    pub.send_string rep

    logger.info(rep)
  rescue Interrupt
    break
  end
end

Теперь код фронтэнда. Это вполне себе обычное приложение на микрофреймворке Sinatra.

get '/' do
  # index template should be saved at views/index.erb
  erb :index
end

post '/' do
  data = params['data'] || ''
  if data.strip.empty?
    return redirect '/'
  end

  $queries[data] = nil

  # send query to backend
  push.send_string [data].to_json

  redirect '/'
end

Кстати, не забываем положить файл с ERb-шаблоном страницы по адресу views/index.erb относительно остальных файлов.

Итак, для начала запускаем backend.rb, который забивает на себя UNIX-сокеты и готовится обрабатывать данные. После этого запускаем frontend.rb и заходим браузером по адресу http://localhost:4567.

Как видно, если добавить в очередь несколько заявок подряд, то они выполнятся последовательно. Таким образом, всю работу по доставке данных от фронтэнда к бэкэнду и обратно выполнила ØMQ, за что ей большое спасибо. У меня это выглядит примерно вот так:

Ruby ♥ Sinatra + ØMQ

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

Приведённый в статье пример может показаться слишком простым. Это действительно так, и он не показывает даже верхушку айсберга возможностей ØMQ, но исключительно базовые вещи. В качестве материала для дальнейшего чтения могу категорически порекомендовать две шикарные статьи Ilya Grigorik [1,2].

blog comments powered by Disqus