- сервер задач/очередей, позволяющий выполнять множество полезных действий, таких как:
- балансировка нагрузки
- распараллеливание процессов
- взаимодействие модулей, написанных на разных языках программирования
- построение приложений с распределенной архитектурой
- вынесение медленных и тяжелых задач в фоновый режим для ускорения реакции приложения на действия пользователя
Изначально 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 пригодится Вам в Ваших проектах, и сделает их немного лучше.
Примеры из статьи доступны здесь