Используем Gearman в работе

gearman logo - сервер задач/очередей, позволяющий выполнять множество полезных действий, таких как:

- балансировка нагрузки

- распараллеливание процессов

- взаимодействие модулей, написанных на разных языках программирования

- построение приложений с распределенной архитектурой

- вынесение медленных и тяжелых задач в фоновый режим для ускорения реакции приложения на действия пользователя

Изначально Gearman был разработан для LiveJournal.com человеком по имени Brad Fitzpatrick на Perl'е, но в дальнейшем он был переписан на Си.

Мы пройдемся по каждому из этих пунктов, и разберем их на примерах, но сначала нам необходимо разобраться в архитектуре Gearman'a и установить его.

Архитектура Gearman'a

Gearman состоит из трех основных частей:

Client - ставит задачи на выполнение, обращаясь к серверу задач

Worker - опрашивает сервер задач на наличие задач, которые он может обработать

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

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

Предлагаю перейти к установке сервера и расширения для PHP, а затем я покажу использование Gearman'a на примерах.

Установка Gearman сервера

В качестве примера я буду устанавливать Gearman на Fedora 19. В консоли достаточно выполнить

$ yum install gearmand -y

На этом установка сервера завершена. Давайте рассмотрим некоторые из параметров его запуска и ознакомимся как работать с ним через командную строку, прежде чем устанавливать PHP расширение.

-j [--job-retries] - количество попыток сервера выполнить работу, перед тем как он ее уберет из очереди. Бывает полезно выставить эту настройку, чтобы какая-нибудь кривая задача не заняла все воркеры.
-l [--log-file] - вывод логов
-L [--listen] - IP адрес для сервера. По умолчанию 127.0.0.1
-p [--port] - порт. По умолчанию 4730
-q [--queue-type] - указание постоянного хранилища невыполненных задач. Более подробно эта опция будет рассмотрена ближе к концу статьи.

Запускаем сервер

$ gearmand -d

Во всех следующих примерах использования Gearman'a подразумевается, что сервер у Вас уже запущен.

При установке сервера Вы также получаете Gearman Command Line Tool (gearman), которым мы и воспользуемся, чтобы запустить воркера и клиента.

Запускаем клиента, который передаст задание серверу

$ gearman -f TEST -- '4devs.io'

-f TEST - какой функцией должна обрабатываться задача. На нее откликнется любой из подписанных воркеров. Т.к. у нас их пока нет, то задача будет висеть в очереди.

Еще можно использовать опцию -b для выполнения поставленной клиентом задачи в фоне, когда нам не нужен результат обработки.

Проверить состояние сервера можно следующим образом:

1. Подключаемся к серверу

$ telnet 127.0.0.1 4730

2. С помощью команды status смотрим сколько и каких задач/воркеров сейчас находится в работе:

Формат вывода следующий:

1ый столбец - названия функций для которых есть либо задачи, либо есть воркеры

2ой - общее количество задач для функции

3ий - количество задач в обработке

4ый - количество запущенных воркеров для данной функции

Мы видим, что задача поставленная нашим клиентом попала в очередь, но т.к. нет воркеров для нее, то ее некому обработать. Давайте это исправим, запустив воркера:

$ gearman -w -f TEST -- Done

-w - указывает что мы запускаем именно воркера

-f TEST говорит что воркер подписывается на задачи для функции TEST и может их обрабатывать

-- Done - просто тестовый ответ воркера при выполнении задачи, которая в данном случае является пустой

После запуска воркера, в терминале с запущенным клиентом Вы должны увидеть слово Done и работа клиента на этом завершится. Воркер же продолжает ждать новых задач.

Установка PHP расширения для Gearman'а

Играться с консолью конечно весело, но толку мало, поэтому необходимо установить расширение для работы с Gearman'ом в наших PHP приложениях. Мы можем установить расширение с помощью PECL или собрать из исходников. Давайте рассмотрим оба варианта:

1. PECL

$ yum install php-devel
$ pecl install gearman

2. Собираем расширение из исходников

$ yum install gcc libgearman-devel -y
$ wget http://pecl.php.net/get/gearman-1.1.2.tgz
$ tar xf gearman-1.1.2.tgz 
$ cd gearman-1.1.2/
$ phpize
$ ./configure
$ make
$ make install

Далее осталось указать PHP на расширение Gearman'a

$ echo 'extension=gearman.so' >> /etc/php.ini 

phpinfo показывает нам что все у нас установилось успешно и включено:

На этом установку расширения можно считать завершенной

Область применения и примеры использования

Вынесение медленных задач в фоновый режим для ускорения реакции приложения на действия пользователя

Рассмотрим данный пункт на примере отправки инвайтов на почту. 

Пользователь в социальной сети вводит email'ы своих друзей, которых он хочет пригласить. Это может быть как 1 email, так и 10, 20, 30, кто знает сколько у него друзей, свободного времени и терпения на ввод этих данных. Далее  в цикле идет отправка email'ов, а наш пользователь в это время сидит и смотрит на то как дооолго грузится страница. Не годится. В данном случае пользователю не обязательно дожидаться факта отправки всех инвайтов, он должен лишь знать что мы их отправим и друзей у него станет больше. Это классическая задача, для которой отлично подходит Gearman. Вместо того, чтобы отправлять все письма сразу и заставлять пользователя ждать, лучше добавить их в очередь, тем самым полностью исключив для пользователя ожидание отправки писем. Ниже приведен небольшой пример, иллюстрирующий данную ситуацию.

Воркер, который и будет заниматься непосредственной обработкой очереди сообщений и отправкой писем:

<?php

// examples/mailQueue/mailQueueWorker.php

$worker = new GearmanWorker();
$worker->addServer();

// Регистрируем воркера на задачи mail_queue и функцию emailHandler в качестве обработчика
$worker->addFunction('mail_queue', 'emailHandler');

while ($worker->work());

function emailHandler(GearmanJob $job)
{
    $workload = unserialize($job->workload());

    if (is_array($workload)) {
        mail($workload['to'], $workload['subject'], $workload['message']);
    }
}

Запускаем воркер:

$ php examples/mailQueue/mailQueueWorker.php

Регистрируясь, воркер говорит серверу задач, что он способен обрабатывать задачи в очереди mail_queue. В функцию-обработчик передается GearmanJob объект. Нас больше всего интересует его метод workload, который позволяет получить сериализованные данные, переданные клиентом. В нашем случае это данные для отправки email'а. 

Экшн обработки приглашения пользователем друзей:

<?php

// examples/mailQueue/mailAction.php

// Наш произвольный экшн

$mailList = $_POST['mail_list'];

// Здесь к примеру делаем всю необходимую валидацию данных

$client = new GearmanClient();
$client->addServer(); // По умолчанию IP 127.0.0.1 и порт 4730

// Добавляем инвайты в очередь, тем самым не теряя время на их отправку
foreach ($mailList as $receiver) {
    $client->doBackground('mail_queue', serialize([
                'to' => $receiver,
                'subject' => 'Invite to 4devs.io',
                'message' => 'Hello my dear friend, please visit 4devs.io',
            ]));
}

// Теперь мы можем вернуть пользователю сообщение об отправке без задержек

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

Логирование данных / аналитика

Логи это наше всё. Без достаточно мощной аналитики Ваш проект движется наугад, т.е. в каком-то смысле сбор данных о пользователях, их перемещениях, активности является критическим для принятия решений по развитию проекта. В то же время, если делать выбор между работоспособностью проекта в целом и аналитикой, становится очевидным, что аналитика никак не должна влиять на доступность ресурса/сайта. Если вдруг Ваша система аналитики откажет, то ни в коем случае не должны страдать никакие другие части проекта. Сможете ли Вы прожить день без аналитики? Да, вполне. Сможете ли Вы прожить день без работающего проекта? Нет (по крайней мере меня это не устраивает). Решение в данном случае очень простое - Gearman. К примеру при построении системы логирования и аналитики, воспринимайте ее как вспомогательный, абсолютно несвязанный модуль для проекта, другими словами компонент. Вполне достаточно написать простую обертку для логирования, которая внутри будет использовать очереди Gearman'a. Пример расставит все на свои  места:

<?php

// examples/log/buyPremiumStatusAction.php
 
// Проверяем оплатил ли пользователь услугу
// Подключаем ему премиум статус

// Отмечаем данное событие в жизни пользователя
$client = new GearmanClient();
$client->addServer();
 
$data = [
    'table' => 'activity_log',
    'data' => [
      'user_id' => 3, // Просто для примера
      'action' => 'premium_status',
      'created_at' => date('Y-m-d H:i:s'),
    ],
];
 
$client->doBackground('log_queue', serialize($data));

// Возвращаем пользователю ответ об успешном получении премиум статуса
// без каких-либо задержек

Воркер в данном случае очень простой:

<?php

// examples/log/loggerWorker.php

$worker = new GearmanWorker();
$worker->addServer();

// Регистрируем воркер для обработки очереди
$worker->addFunction('log_queue', 'logHandler');

while ($worker->work());

function logHandler(GearmanJob $job)
{
    $workload = unserialize($job->workload());

    // Все что нам осталось сделать - извлечь данные и записать их в БД
}

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

Данные примеры специально по максимуму упрощены, чтобы легче было понять саму суть работы с Gearman'ом. Любые замечания и дополнения приветствуются.

Балансировка нагрузки и тяжелых задач, распараллеливание процессов

С помощью Gearman'a Вы можете балансировать нагрузку. Если Ваш сервер выполняет задачи медленно - запустите больше воркеров. Таким образом он будет обрабатывать задачи в несколько потоков. Если же сервер уже загружен по полной, то Вы легко можете дополнительно запустить воркеров на других серверах, что распределит задачи между серверами, обеспечив тем самым балансировку нагрузки.

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

Предположим что у Вас есть некий модуль/компонент в приложении, который работает недостаточно быстро. Один из вариантов для увеличения производительности - переписать данный компонент на языке более низкого уровня, таком как Си. Хорошо, переписали. Что теперь необходимо переделать в PHP коде, который работал с тем компонентом?

Если эти компоненты общались через Gearman - ничего, все как работало, так и будет работать дальше. Если же Gearman не использовался раньше, то добавить его не составит труда. Здесь обойдемся без примера, т.к. я не знаю Си (автору весьма стыдно), но хорошо, что в команде я работаю не один. Думаю и у Вас найдется кто-то способный помочь.

Также такой подход позволяет для различных компонентов системы выбирать язык наиболее близкий к специфике задач компонента, что тоже положительно отобразится на  производительности компонента.

Gearman и MySQL

Бывали случаи, когда Gearman сервер приходилось перезагружать, и терялись все данные из его очередей, т.к. по умолчанию он хранит их в памяти. Происходило это из-за неопытности, и порой из-за старых версий Gearman'a, установленных на серверах. Если у Вас есть данные, которые критичны для Вас, думаю Вам стоит обратить внимание на возможность Gearman'a использовать персистентные храналища для очередей, такие как MySQL, Postgres и т.д.. Со списком поддерживаемых хранилищ можно ознакомиться на официальном сайте. Ниже я приведу лишь небольшой простой пример использования MySQL в качестве такого хранилища.

gearmand -q mysql --mysql-host localhost --mysql-user root --mysql-db gearman -d

-q - тип постоянного хранилища

Давайте добавим немного задач, выполнив несколько раз

gearman -f test -b -- 1

Если сейчас посмотреть с помощью telnet состояние Gearman'a, то можно увидеть N задач в очереди test. Просто выполните:

telnet 127.0.0.1 4730
status

По умолчанию gearman создает служебную таблицу gearman_queue в указанной базе данных. Проверим сохранил ли он текущие невыполненные задачи:

select * from gearman_queue;

Все невыполненные задачи сохранились в MySQL. По мере выполнения, они будут оттуда стираться. В примере специально не запущен ни один воркер, чтобы можно было все неспеша просмотреть.

Давайте теперь сделаем как бы случайный креш Gearman сервера

ps ax | grep gearmand

kill 3491

Если бы у нас не было постоянного хранилища в виде MySQL, мы бы потеряли все наши данные. Но, если сейчас перезапустить Gearman сервер с теми же параметрами как и в первый раз, то он восстановит все задачи в памяти из MySQL таблицы, что определенно радует - дополнительная стабильность.

Чтобы посмотреть какие хранилища доступны для вашей системы, а также какие параметры для них необходимо задавать, выполните gearmand --help.

P.S.

Хотелось бы сказать еще пару слов о Gearman'e. На практике он показал себя как довольно мощный, удобный и что немаловажно - стабильный инструмент. Даже при больших нагрузках, когда воркеры не успевали разбирать задачи и на сервере присутствовали очереди по 4-5млн задач, Gearman чувствовал себя отлично, и постепенно их решал. Также хотелось бы обратить внимание, что если Вы используете какую-нибудь старую версию Gearman'a - задумайтесь об ее обновлении, порой это показывает существенный прирост в производительности. Надеюсь Вы не зря потратили время на чтение статьи и Gearman пригодится Вам в Ваших проектах, и сделает их немного лучше.

Примеры из статьи доступны здесь

Читайте также:

Docker установка и настройка

При разработке используется множество технологий. К примеру данный блог использует php(Symfony 2), mongodb, elastic, nginx это основные но также используется nodejs к примеру для минификации css, js. При разработке приходится настраивать все технологии как показано в статье. Но что делать если проектов несколько или они используют разные технологии, например другой проект использует MySQL, или еще могут использовать разные версии php или других библиотек. Для разработки и поддержки проектов на разных технологиях можно использовать Виртуальную машину и поставить на нее к примеру centos, но это не совсем удобно, надо все равно настраивать подобное окружение как на сервере и на других машинах разработчиков. Мы будет использовать контейнеры, такие как Docker. Настроем блог разработчиков чтобы использовать Docker для разработки.

Как использовать произвольное хранилище пользователей в FOSUserBundle

Практически все используют FOSUserBundle в своих Symfony проектах т.к. он ускоряет разработку и обладает хорошим набором функциональности для управления пользователями. Бандл предоставляет несколько готовых реализаций хранилищ данных: Propel и несколько для Doctrine (ORM и ODM). Это здорово, но иногда возникает необходимость работы с другими хранилищами данных. FOSUserBundle достаточно гибок и позволяет реализовать, и использовать произвольное хранилище. Для того, чтобы использовать все возможности FOSUserBundle Вам достаточно будет написать свой менеджер пользователей под конкретного провайдера.

Интеграция Paysera в Symfony

Сегодня сложно представить себе серьезный проект, где не понадобилась бы интеграция с платежными системами. Не смотря на то что существует множество популярных систем и аггрегаторов, таких как PayPal, RBKMoney, Paymentwall, Robokassa и т.д., я хочу рассказать о Paysera. Это еще одна, довольно новая платежная система. Они позиционируют себя как выгодных с точки зрения комиссий за их услуги. Paysera позволяет вашим пользователям расплачиваться карточками, SMS и т.д. Интеграция довольно простая, однако имеет некоторые неочевидные моменты, которые я и хочу осветить.

Создание сайта “Обратный отсчет” на Symfony2

Мы иногда видим сайты с обратным отсчетом, проект стартует через … Его сделать достаточно просто, и не займет много времени. Мы воспользуемся проектом fdevs/coming-soon, который основан на Symfony2. Также будем сохранять введеный пользователями адреса электронной почтой в базу данных MongoDB. У нас есть настроенное рабочее окружение Osx, о настройке можно прочитать в статье Yosemite настройка рабочего окружения. Но главное версия php не меньше 5.4. В проекте можно также использовать реляционную базу данных типа MySQL. По умолчанию в проекте вообще не используется база данных, а введеный email отправляется на почту.

Yosemite настройка рабочего окружения.

После обновления OSx Yosemite, необходимо настроить рабочую систему. Мы установим и настроим пакеты php-fpm, nginx, mongo, nodejs. Для начала установим Xcode из App Store обновим консольные команды $ xcode-select --install и уберем apache из автозагрузки $ sudo launchctl unload /System/Library/LaunchDaemons/org.apache.httpd.plist . Так как у Apple не появилось своего менеджера пакетов будем использовать проверенный brew.