Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Обработка заданий на очередях

Обработка заданий на очередях

Avatar for Evgeny E. Neverov

Evgeny E. Neverov

June 13, 2023
Tweet

More Decks by Evgeny E. Neverov

Other Decks in Programming

Transcript

  1. Зачем нужны очереди? • Асинхронность взаимодействия: мы не ждём окончания

    обработки и ответа на запрос • Гарантия доставки сообщения • Псевдогарантия единственной обработки сообщений • История сообщений • Не думаем о получателях и обработчиках
  2. Зачем нужны? • Выполнение любых долгих вычислений • Расчёт зарплаты

    • Ресайз картинок • Выполнение задач, связанных с внешними системами (которые могут тормозить) • Отправка E-mail • Получение курсов валют • Расчёт пути по набору адресов через Яндекс.Карты для доставщика
  3. Как установить на сайт 1. Копируем наш php_interface в Битрикс:

    techdir.stuff/php_interface → local/ 2. Копируем наш класс Highloadblock в Битрикс: techdir.stuff/highloadblock → local/php_interface/ techdir/lib/ 3. Копируем всё, связанное с очередями в Битрикс: techdir.stuff/queues/queue → local/php_interface/ techdir/lib/
  4. Как установить на сайт 4. Создаём хайлоадблок из файла queue/highloadblock.xml:

    Админка → Контент → Highload - блоки → Импорт 5. Обязательно создаём индексы (queue/indexes.sql) : CREATE INDEX IX_QUEUE_SEARCH ON a_queue (UF_QUEUE(255), UF_COMPLETED_AT, UF_AVAILABLE_AT, UF_PROCESSING_STARTED_AT); CREATE INDEX IX_QUEUE_DUPLICATES ON a_queue (UF_QUEUE(32), UF_COMMAND(768), UF_OBJECT_ID(64), UF_COMPLETED_AT); CREATE INDEX IX_QUEUE_FIX ON a_queue (UF_PROCESSING_STARTED_AT, UF_COMPLETED_AT); CREATE INDEX IX_QUEUE_CLEANER ON a_queue (UF_COMPLETED_AT);
  5. Поля запланированной команды • Код очереди — любая строка, чтобы

    разные задачи обрабатывались разными очередями • Команда — выполняемый класс • Выполнение разрешено с — момент времени, после которого задача будет выполнена • Дата выполнения — когда задача фактически выполнена
  6. Коды очередей • Идея в том, чтобы разные задачи выполнялись

    «разными» обработчиками очередей • Нужно для того, чтобы более сложные/длительные задачи не тормозили выполнение более простых задач • Разные очереди обрабатываются разными процессорами очередей, число таких процессоров тоже может отличаться
  7. Переводим команду в другую очередь <?php namespace Techdir\PhpInterface\Queue\Command; class WelcomeEmailCommand

    extends AbstractCommand { public function _ _ construct() { $this - > onQueue('emails'); } public function handle() { . . . } }
  8. Переводим команду в другую очередь во время вызова if ($client

    - > isPaid()) { VideoProcessingCommand : : dispatch() - > onQueue('fast'); }
  9. Отложенные команды Нужны для того, чтобы команда выполнялась не сразу,

    а спустя некоторое время. Все команды выполняются по порядку с учётом времени, на которое они отложены.
  10. Отложенные команды / / Если параметр int — это «через

    10 секунд» TestCommand : : dispatch() - > delay(10); / / Строка — парсинг DateInterval : : createFromDateString TestCommand : : dispatch() - > delay('1 day + 6 hours'); TestCommand : : dispatch() - > delay('P1M4DT30M10S'); / / \DateTime — конкретный момент времени $date = new \DateTime('2024-01-01 00 : 00 : 00'); TestCommand : : dispatch() - > delay($date);
  11. NB : вызов параметров можно комбинировать в любом порядке TestCommand

    : : dispatch() - > delay('P1D') - > onQueue('fast'); TestCommand : : dispatch() - > onQueue('fast') - > delay('P1D');
  12. Процессор очередей 1. Заранее заготовленный PHP-скрипт 2. Образец лежит в

    techdir.stuff/queues 3. Копируем в /local/php_interface/ (либо исправляем путь до DOCUMENT_ROOT)
  13. Процессор очередей <?php use Techdir\PhpInterface\Queue\Processor; $_SERVER['DOCUMENT_ROOT'] = dirname( _ _

    FILE _ _ , 3); const NO_KEEP_STATISTIC = true; const NOT_CHECK_PERMISSIONS = true; require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/ prolog_before.php'; set_time_limit(0); (new Processor( _ _ DIR _ _ , 'default')) - > setWorkersLimit(3) - > run();
  14. Процессор очередей 1. С помощью блокировок убеждается, что запущено не

    более, чем workersLimit обработчиков очереди (иначе сразу выходит) 2. Запускает бесконечный цикл в котором: 1. Выбирает одну следующую задачу и блокирует её 2. Десериализует объект команды 3. Вызывает - > handle(); 4. После выполнения фиксирует в БД дату выполнения и результат 5. В случае ошибки фиксирует ошибку 3. Проверяет, что исходники команд не изменились, иначе умирает
  15. Параметры конструктора • Класс команды может содержать конструктор, который устанавливает

    внутренние параметры класса • Значения параметров передаются в : : dispatch( . . . ) • При выполнении параметры будут иметь заполненные значения
  16. Параметры конструктора • Класс команды может содержать конструктор, который устанавливает

    внутренние параметры класса • Значения параметров передаются в : : dispatch( . . . ) • При выполнении параметры будут иметь заполненные значения
  17. Параметры конструктора <?php namespace Techdir\PhpInterface\Queue\Command; class WelcomeMailCommand extends AbstractCommand {

    protected int $userId; public function _ _ construct(int $userId) { $this - > userId = $userId; } public function handle() { $user = \CUser : : GetByID($this - > userId) - > Fetch(); / / . . . } }
  18. Результаты выполнения • Функция handle() может вернуть любой результат, он

    будет сериализован и записан в таблицу очереди в поле UF_RESULT :
  19. Ошибки • В случае, если вызов приведёт к ошибке, она

    будет также зарегистрирована в БД :
  20. Ошибки • Можно разрешить команде перезапускаться в случае ошибки: <?php

    namespace Techdir\PhpInterface\Queue\Command; class WelcomeMailCommand extends AbstractCommand { public static int $maxRetries = 30; / / по - умолчанию = 1 public function handle() { . . . } }
  21. Контроль дубликатов • Иногда нельзя ставить задачу, если аналогичная уже

    в очереди на выполнение, но ещё не выполнена (расчёт ЗП) • А иногда нельзя ставить задачу, если аналогичная хоть когда - то уже выполнялась
  22. Контроль дубликатов / / Нельзя запускать расчёт зарплаты, / /

    если по данному сотруднику уже запланирован расчёт, / / но ещё не выполнен SalaryCalculatorCommand : : dispatch() - > preventDuplicates() - > withId('emp - ' . $user - > id);
  23. Контроль дубликатов / / Никогда нельзя повторно / / отправлять

    приветственное письмо e - mail WelcomeMailCommand : : dispatch($USER - > GetID()) - > withId('video - ' . $video - > id) - > connorMacLeodMode();
  24. Цепочка вызовов WelcomeMailCommand : : dispatch($USER - > GetID()) -

    > onQueue('emails') - > delay( (new \DateTime) - > add(new \DateInterval('P1D')) - > setTime(9, 0) ) - > withId('user - ' . $USER - > GetID()) - > connorMacLeodMode();
  25. Обработка несколькими процессорами • У процессора есть параметр setWorkersLimit(), который

    ограничивает число процессов, которые можно запустить. • Если работает несколько процессов, они последовательно берут возникающие задачи и обрабатывают их. • Процессы не трогают задачи друг друга за счёт блокировок.
  26. supervisor • Специальный демон, обеспечивающий непрерывную работу критичных сервисов: отслеживает,

    что процесс работает и перезапускает при необходимости. yum install supervisor
  27. /etc/supervisord.d/queue.ini [program:bx - queue] process_name=%(program_name)s_%(process_num)02d command=/usr/bin/php /home/bitrix/ w w w

    /local/ php_interface/queue - processor.php autostart=true autorestart=true user=bitrix numprocs=3 startsecs=0 stdout_logf i le=/home/bitrix/queue.log
  28. Отслеживание результатов • После выполнения каждой задачи, результат её работы

    и возникшие в процессе выполнения ошибки фиксируются в хайлоадблоке. • Было бы неплохо уметь с этим работать.
  29. Получаем результаты задания use Techdir\PhpInterface\Queue\JobRecord; / / Сохраняем идентификатор поставленной

    задачи $job = new JobRecord($id); / / Выполнилась ли задача? $job - > isCompleted(); / / Был ли результат $job - > hasResult(); / / Верни результат $job - > getResult();
  30. Получаем результаты задания use Techdir\PhpInterface\Queue\JobRecord; / / Сохраняем идентификатор поставленной

    задачи $job = new JobRecord($id); / / Верни число попыток $job - > getAttempts(); / / Была ли ошибка? $job - > hasError(); / / Верни объект ошибки $job - > getError();