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

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

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

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();