Ruby & PHP. Скрещиваем ужа с ежом с помощью Starling и Zend_Queue.

В этой статье я расскажу о животрепещущем для многих вопросе. Как соединить между собой приложения на разных языках. Например, Ruby и PHP. В Twitter проблему интеграции с очередью решили с помощью Starling. Вообще сейчас намечается тенденция, что для каждой задачи подбирают свой язык. Гомогенных систем становится всё меньше. В следствии этого возникает потребность в стандартах на интеграцию разношерстного ПО в единую систему.

 

Постановка задачи

Допустим у нас есть ситуация, при которой во время какого-либо HTTP запроса необходимо сделать длительную операцию. Например, при добавлении пользователем комментария к статье отправить email уведомление её автору. В классической схеме взаимодействия (рис 7-1) мы вынуждены ждать завершения этой операции, чтобы уведомить пользователя. Получается что эта операция блокирует выполнение веб-приложения, а значит происходит потеря производительности. Да и пользователю приходится ждать лишнее время. Когда количество пользователей невелико – это не проблема, но в Highload проектах этот вопрос приобретает большое значение. Мы определились с задачей – вынести все ресурсоемкие задачи в фоновые процессы. Рассмотрим случай с хостингом и добавлением домена в веб-панели. Задачей будет выполнение скрипта на Ruby, который будет производить манипуляции с DNS зонами и перезагружать DNS сервис.

Теперь определимся с схемой работы. Клиентом очереди будет ZendFramework приложение, которое будет добавлять задачи в очередь (отправлять сообщения в очередь). Сервером (воркером) будет Ruby, который будет принимать сообщения из очереди и обрабатывать их.

Почему выбрана именно такая комбинация? ZF для клиента исторически сложился, а вот сервер на Ruby выбран, т.к. очень не хочется давать PHP права root, думаю многие админы меня поймут. Выбор очереди был сделан в пользу Starling, т.к. она основана на Memcached и использует его протокол (ну почти 🙂 ) и реализация на руби предельно проста.

Что такое очереди сообщений

“А как вы организуете очередь?
– С помощью бабушек!”

с Хабрахабра

Тема очередей сообщений была навеяна докладом “Разделение труда: Организация многозадачной, распределенной системы в Zend Framework с помощью Job Queue”  Александра Готгельфа на последнем ZFCONF. Он работал с сервером очередей Gearman, и рассказал о его немногочисленных, но очень серьезных багах (таких, как проблема освобождения памяти). В связи с этим я начал смотреть в торону других и остановился на MemcacheQ и Starling, который очень советовали пользователи хабра. А т.к. имплементация на руби проще со вторым он и был выбран.

Что такое Starling?

Starling – это скворец, певчая птица семейства скворцовых, широко распространённая на значительной территории Евразии, а также успешно интродуцированная в Южную Африку, Северную Америку, Австралию и Новую Зеландию. На юге и западе Европы ведёт оседлый образ жизни, а в северной и восточной её части является перелётной, в зимние месяцы мигрируя на юг. Внешне (размерами, желтым клювом и темным оперением) слегка напоминает чёрных дроздов, но в отличие от них ходит по земле, а не прыгает. 🙂

А ещё так называется очередь сообщений, написанная на Ruby. Её используют в Twitter! Для установки сервера очередей у вас уже должен быть установлен Ruby. Сам же Starling ставится очень просто. Следующая строка выполняется только один раз!

Ставим гем (можно делать сколько угодно раз).

Если вы ставили руби для всех юзеров, то выполняем вместо sudo rvmsudo

Дальше надо запустить сервер и можно коннектить серверов и клиентов.

Клиентами будем называть тех, кто отправляет задания в очередь, а серверами – тех кто получает и обслуживает их (to serve). Теперь можно подключать клиентов на руби и php.

Общий формат данных. Ruby маршализация в PHP

Первое с чем я столкнулся это формат хранения сообщений в очереди. На выбор было несколько вариантов.

  • [б] Маршализация (marshalling) объектов.  Так делает руби и это дефолтовый способ работы Starling,
  • [т] Сериализация (serializing). Так хранит объекты Zend Framework, а именно Zend_Queue,
  • [т] JSON. Стандарт де-факто для взаимодействия в гетерогенных средах,
  • [т] XML. Стандарт де-юро, однако имеет больший расход памяти для хранения сообщений, т.к. имеет теговую природу,

где [т] – текстовый формат, [б] -бинарный формат.

Скажу так, в начале у меня был соблазн вообще не трогать Starling, а научиться в PHP работать с маршализованными руби объектами. Но ничего похожего я найти не смог. Использовать XML не хотелось, для работы с JSON надо было бы здорово переписывать сервер очередей. Вот тогда то мне на глаза и попался гем php_serialize.

Ruby + Starling

Делает он ровно то, что я от него и ожидал, а именно выполняет serialize() функцию над Ruby объектами и наоборот. Итак схема такова: php -> serialize -> starling -> unserialize -> ruby. Для этого правда всё равно пришлось коё-что переписать в Starling. Приведу здесь код простейшего воркера очереди.

Как видите, вместо Marshal.load из MemCache::get и использую PHP::unserialize и пропустив один уровень наследования (MemCache) для того, чтобы не поломать обычный MemCache клиент я внедрил его в класс Starling. Отлчино, теперь примемся за вторую часть нашей системы.

PHP + Starling (ZendExtra_Queue_Adapter_Starling)

В Zend Framework уже есть неплохой класс работы с очередями – Zend_Queue, но к сожалению он не поддерживает Starling. Для внедрения поддержки создадим наш адаптер, который отнаследуем от ближайшего родственника (Zend_Queue_Adapter_MemcacheQ).

В Зенд-Кью (а именно так читается Zend_Queue) очень хорошо реализована работа с адаптерами хранилищ. Каждый из них очень разный, и паттерн “Стратегия” тут бы не подошёл, однако авторы нашли выход и добавили функцию getCapabilities(), которая возвращает масси возможностей хранилища.

Для нашего хранилища есть возможность получить количество сообщений в очереди на обслуживание, но его надо реализовать. В написал/переписал/дописал часть функций адаптера в результате родился
ZendExtra_Queue_Adapter_Starling

А вот и пример инициализации класса и работы с ним:
StarlingZF.php

Или тоже самое но на уровне PHP:
StarlingPHP.php

Как видите всё не так сложно. Теперь можно даже написать утилиту мониторинга (если вам не подходят существующие), которая будет следить за очередью.

Преимущества и недостатки Starling

Давайте посмотрим, что у нас получилось, и что нам это даёт.

  • Скорость. Данные хранятся в Memcache, который славится своей производительностью.
  • Масштабируемость. Очередь может быть распределена на несколько серверов.
  • Стандартизированность и унификация. Используется стандартный Memcached-протокол.

Но есть и недостатки.

  • Надежность. Т.к. все данные хранятся в памяти, то при перезапуске сервера задачи – теряются.

Но в некоторых случаях это может быть не критично. Например в случае хостера отметка о завершении задачи ставится в конце неё, и при необходимости клиентская часть сможет повторить запрос. В случае отправки email -есть внешняя очередь Postfix, куда ставятся сообщения на отправку, а Starling является буферной очередью для почтового сервера. В случае ресайза картинок, задача на ресайз может быть поставлена при повторном обращении ккартинке по проверки условия выполнения задачи. Вот, что пишут про неё сами разработчики.

Система надёжна, быстра и использует стандартный протокол memcached. По заверениям разработчиков, это вообще самое стабильное звено твиттера. Когда другие элементы системы отключаются, Starling всегда продолжает работать.

Так что не всё так плохо, важно понимать, что идеальных решений не существует и любое из них компромисс в ту или иную сторону и определитсья с приоритетами. Если приоритет – Highload, то, думаю, сквоец – отлчиное решение!

Литература

http://www.aagh.net/projects/ruby-php-serialize
http://rubypond.com/blog/the-complete-guide-to-setting-up-starling
https://github.com/highgroove/scout-plugins/raw/master/starling_monitor/starling_monitor.rb
https://github.com/starling/starling
http://rubyjunction.us/ruby-asynchronous-messaging
http://habrahabr.ru/blogs/hi/44907/
http://habrahabr.ru/blogs/hi/45891/
ZF Proposal

9 Comments

  1. Автору спасобо, давно искал что то подобное но php + Ruby это лучший вариант.

  2. Мсье знает толк в извращениях))
    Пара вопросов:
    1) Зачем было строить Zoo? На клиенте рельсы, было бы проще
    2) Если так нужен был ZF почему бы не смотреть на другие решения, типа RabbitMQ – там есть хорошие реализации и примеры на разные языки.
    3) Для простых задач, не смотрели Delayed Job на ruby?

    1. Это был 2011, мы выживали, как могли) А если серьезно, то:
      1) Зоопарк уже был, был и демон на Ruby и приложение на ZF. Оставалось выбрать очередь.
      2) Эта показалась наиболее простой, да так в принципе и есть. А главное есть клиенты под все языки. По этому и выбрал. Из-за интерфейса.
      3) Смотрел, но клиент то у меня на ZF. Надо оттуда задачи создавать.
      Кстати сейчас реализую подобную задачу. Уже совершенно по другому. На основе аатомарных обновлений в MongoDB. Клиенты (Symfony2 приложение) и сервер (PHP libevent демон очереди) будут юзать монгу для её хранения.

      1. Ясно все с вами) Выживать это хорошо. Да и очереди не плохо наверное, хотя мне пока delayed’a за глаза. Но смотрю на rabbitmq.
        А вот про MongoDB можно подробнее? Вернее как это там выглядит (в документацию лезть не хочу пока). Я рельсовик, поэтому не знаю что такое libevent…могу предположить, что это что-то вроде нашего EventMachine ?

        1. Ага, насколько я знаю EventMachine это такая же обертка для системной Libev/libevent как и расширение php_libevent. Про монгу потом подробнее напишу отдельную статью. Основная идея в findAndModify см. http://www.slideshare.net/mongodb/mongodb-as-message-queue Там кстати по тестам такая штука оказалась быстрее, чем RabbitMQ 🙂

  3. Хм, а я решил познать python для работы с демонами и фоновыми процессами. Все-так если использовать сервисы сообщений, то можно извращаться над разными технологиями, а системные утилитки писать на python сподручнее будет (да простит меня любимый ruby)…А разве в php нету реализации Delaed Job ? А монго для меня пока закрытая книга, несмотря на то, что я ее сдуру подключил к одному проекту (решил поизвращаться) но выяснилось, что мне возможностей РСУБД хвататает. Наверное проблема в том, что я сразу залез в нее через ODM Mongoid (это реализация ActiveRecord под MongoDB) и там это все настолько похоже, что прям удобней не бывает…поэтому некоторые мощный возможности все-же скрыты за “магией”…

    1. Да, Сергей, Mongoid говорят весьма и весьма неплох. Насчёт магии ODM – это точно. Не зная, что там ниже лежит можно всякую фигню понаписать. Но нам хорошо, юзаем профайлер от Sf2 тулбара, он нам все-все-все запросы показывает и косяки выплывают, если есть. А по поводу языка для системных утилит, ну если потечет вдруг демон я его просто убиваю (по лимиту потребляемой памяти), а supervisord палит это дело и сразу перезапускает. Очередь – persistant, так что это ничего страшного. Что до Delaed Job – то как раз пишу сейчас подобную штуку.

      1. Я бы не сказал, что там можно фигню написать…в общем то, как раз, фигню написать и не получится, он там все по полкам раскидывает, как и AD. С другой стороны дальше “простых вещей” оттуда вряд ли можно добраться. Меня в Монго изначально подкупила их возможность на лету создавать атрибуты, что бы не париться с EAV моделью данных…

        1. Ну дык schemaless во всей красе. Правда больше обработки в приложении надо делать, но это ничего. Бекэндов можно много понаделать.

Leave a Comment