Масштабирование путем распределения задач (PHP7 + Gearman + Supervisor)

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

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

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

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

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

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

Теперь об инструментах, которые помогут решить задачу:
  1. Операционная система Linux (дистрибутив Ubuntu)
  2. Веб сервер Apache
  3. PHP 7
  4. Приложение на PHP
  5. Сервер очередей Gearman
  6. Менеджер процессов Supervisor
Немного об инструментах:

Gearman - это открытая (OpenSource) система очередей. Позволяет масштабироваться на несколько серверов. При помощи Gearman можно выставлять приоритеты выполняемым задачам.

Дополнительные материалы:
  1. Официальный сайт
  2. Википедия
Supervisor - клиент / серверная система которая позволяет пользователям мониторить и управлять программами или скриптами. При помощи Supervisor можно запустить программу n количество раз параллельно, перезапустить ее при возникновении критической ошибки, отслеживать статус выполнения программы и управлять ее работой, фиксировать логи, назначать как готовые так и свои собственные обработчики на различные события программы и многое другое

Дополнительные материалы:
  1. Официальный сайт

Установка

Gearman

В стандартном репозитории Ubuntu 16.04 уже есть Gearman и все зависимые от него компоненты
sudo apt-get install gearman
  1. gearman-job-server - сервер
  2. gearman-tools - инструменты для управления и мониторинга Gearman сервером, например gearadmin
  3. libgearman7 - библиотека для работы с Gearman
  4. и т.д.
На момент написания статьи к сожалению Pecl репозиторий не поставляет библиотеку для работы с Gearman сервером через PHP 7, поэтому придется воспользоваться сторонним репозиторием. Перед тем как начать устанавливать PHP расширение для работы с Gearman по инструкции ниже, убедитесь, что библиотека на самом деле отсутствует в Pecl. Архив версий можно посмотреть по этой ссылке

Добавляем репозиторий, спасибо ondrej
sudo add-apt-repository -y ppa:ondrej/php
sudo apt-get update
sudo apt-get install php-gearman
Обновляем конфигурационные файлы сервера
sudo service apache2 reload

Supervisor

 sudo apt-get install supervisor

Приложение

В качестве примера будет написан "сервис" для генерации документов. Пользователь отправляет запрос на генерацию отчета, сервис генерирует документ и фиксирует статус в базе данных

Алгоритм

  1. Клиент (Client) отправляет запрос на генерацию документа
  2. Задача отправляется на сервер очередей и помечается как фоновая (background).
  3. Регистрируется функция и передаются аргументы
  4. На сервере очередей создается задача (task)
  5. Обработчик функции (Worker) увидев задачу в очереди начинает ее выполнение (если он свободен). Для обработчика, задача называется Job. Если обработчик функции отсутствует или занят, то задача ставится в очередь ожидая исполнения
Еще раз термины:
  1. Client - клиент, который отправляет запрос серверу очередей
  2. Background - фоновая задача, выполняемая асинхронно
  3. Task - задача, которая ожидает выполнения и стоит в очереди
  4. Worker - обработчик задачи
  5. Job - работа над задачей, которую выполняет обработчик
Еще один важный момент. Если задача будет отправлена в асинхронном режиме, то воркер не сможет ничего вернуть клиенту, как в данном примере. Все общение будет происходить через персистентное хранилище данных. Можно задачу пометить как синхронную, тогда воркер сможет ответить клиенту.

Создание клиента

<?php
// Name of function
$funcName = 'generateReport';
// Data for function
$data = [
    'report' => 'user_statistic',
];

$client = new GearmanClient();
$client->addServer('127.0.0.1', '4730');

$client->doBackground($funcName, json_encode($data));

Создание воркера

<?php
$worker = new GearmanWorker();
$worker->addServer();
$worker->addFunction('generateReport', 'generate');
$worker->work();

function generate($job)
{
    $workload = $job->workload();
    $data = json_decode($workload, true);
    
    // do generate...
    echo "generate... \n";
    sleep(3);
    //set status into db
    echo "set status into db... \n";
    sleep(2);
    echo "done! \n";
}

Тестируем

Запускаем обработчик
php Server.php
Запускаем клиент
php Client.php
Теперь можно увидеть, как обработчик вывел сообщение:
generate... 
set status into db... 
done! 
Готово! Обработчик выполнил свою работу и выключился. В этом можно убедиться воспользовавшись инструментом для мониторинга Gearman очередей gearadmin:
gearadmin --status
Как результат инструмент отобразит список функций и информацию о них:
generateReport    0    0    0
где первая колонка - наименование функции зарегистрированной на сервере; вторая колонка - количество задач в очереди; третья колонка - количество выполняемых задач в настоящий момент; четвертая колонка - количество обработчиков которые будут работать с задачами.

Если в очередной раз запустить клиент, то можно увидеть, что количество задач в очереди увеличится:
php Client.php
gearadmin --status
generateReport    1    0    0
Но задача не будет выполнена т.к. предыдущий обработчик завершил свою деятельность обработав предыдущий запрос. Запустим обработчик еще раз:
php Server.php
gearadmin --status
generateReport    1    1    1
gearadmin --status
generateReport    0    0    0
Обработчик еще раз выполнил работу и завершил свою деятельность, что и подтверждает утилита gearadmin.

Воркер получился каким-то однозадачным, выполнив работу единожды он сразу же отключился. Этот вариант мало кого может устроить. В идеале было бы замечательно, если бы обработчик после выполнения задачи снова бы запускался автоматически. Для удовлетворения такой потребности может помочь менеджер процессов Supervisor

Настройка Supervisor

Первое, что нужно сделать - это отконфигурировать Supervisor на запуск воркера. Чтобы не редактировать стоковый конфигурационный файл супервизора который находится в /etc/supervisor/supervisord.conf создадим свой собственный в /etc/supervisor/conf.d/

Супервизор автоматически получит созданный конфиг подключив его в основной. Также есть возможность указать свою директорию с конфигурационными файлами, который могут быть подключены в основной конфиг при помощи [include]. Подробнее читайте в официальной документации Supervisor.
sudo touch generate.conf
sudo vim generate.conf
Вставляем:
[program:generateDocument]
command=/usr/bin/php /a/path/Server.php    ; the program (relative uses PATH, can take args)
process_name=%(program_name)s ; process_name expr (default %(program_name)s)
numprocs=1                    ; number of processes copies to start (def 1)
directory=/tmp                ; directory to cwd to before exec (def no cwd)
umask=022                     ; umask for process (default None)
priority=999                  ; the relative start priority (default 999)
autostart=true                ; start at supervisord start (default: true)
autorestart=true              ; retstart at unexpected quit (default: true)
startsecs=10                  ; number of secs prog must stay running (def. 1)
startretries=3                ; max # of serial start failures (default 3)
exitcodes=0,2                 ; 'expected' exit codes for process (default 0,2)
stopsignal=QUIT               ; signal used to kill process (default TERM)
stopwaitsecs=10               ; max num secs to wait b4 SIGKILL (default 10)
user=username                 ; setuid to this UNIX account to run the program
redirect_stderr=true          ; redirect proc stderr to stdout (default false)
stdout_logfile=/a/path        ; stdout log path, NONE for none; default AUTO
stdout_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
stdout_logfile_backups=10     ; # of stdout logfile backups (default 10)
stdout_capture_maxbytes=1MB   ; number of bytes in 'capturemode' (default 0)
stdout_events_enabled=false   ; emit events on stdout writes (default false)
stderr_logfile=/a/path        ; stderr log path, NONE for none; default AUTO
stderr_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
stderr_logfile_backups=10     ; # of stderr logfile backups (default 10)
stderr_capture_maxbytes=1MB   ; number of bytes in 'capturemode' (default 0)
stderr_events_enabled=false   ; emit events on stderr writes (default false)
;environment=A=1,B=2          ; process environment additions (def no adds)
serverurl=AUTO                ; override serverurl computation (childutils)
Важно не забыть поменять имя пользователя напротив директивы user и указать свой путь к обработчику в command, а также не забыть поменять логи в директивах stdout_logfile и stderr_logfile

Теперь нужно запустить Supervisor
sudo supervisord
Далее запускаем утилиту для работы с supervisor
sudo supervisorctl
start generateDocument
Готово. Теперь воркер запущен и в этом можно убедиться воспользовавшись инструментом gearadmin
gearadmin --status
generateReport    0    0    1
php Client.php
gearadmin --status
generateReport    1    1    1
gearadmin --status
generateReport    0    0    1
Воркер успешно отработал и завершил свою деятельность, а supervisor вновь его запустил. Результат выполненной работы можно посмотреть в логах, путь до которых был указан в конфигурационном файле supervisor в директиве stdout_logfile
generate...
set status into db...
Как видим наша задача была полностью решена. Итоговая схема работы выглядит примерно следующим образом:



Пояснение схемы:
  1. Менеджер процессов supervisor запускает Worker
  2. Worker начинает мониторить наличие тасков на сервере очередей
  3. Client добавляет task в очередь
  4. Worker начинает работать с таском
  5. Worker фиксирует результат в базу данных
Тема масштабирования путем распределения задач довольно обширна и ей будет посвящены и другие конспекты из данного цикла. На этом все. Всего доброго!

Дополнительная информация

Версии ПО на которых был протестирован алгоритм:
  • Linux Ubuntu 16.04
  • PHP 7
  • Supervisor 3.2.0
  • Gearman 1.0.6

Информация

Автор конспекта


Дата создания: 01.01.2019
Категория: Веб-разработка