Блокировка дубликатов Symfony Сommand

от автора

image

Сегодня хочу предложить вашему вниманию частный случай для решения «неудобств», связанных с периодичным запуском процессов в том случае, если предыдущий еще не завершился. Иначе говоря — блокировка запущенных процессов в symfony/console. Но все было бы слишком банально, если бы не необходимость блокировки среди группы серверов, а не на отдельно взятом.

Дано: Один и тот же процесс, который запускается на N серверов.
Задача: Сделать так, чтобы в единицу времени был запущен только один.

Наиболее популярные решения, которые можно встретить на «просторах»:

  1. блокировка через базу данных;
  2. сторонние приложения;
  3. нативное использование lock-файла

Основные минусы каждого из них:

База данных

  • требует подключение к базе в каждом запускаемом скрипте;
  • нужна таблица;
  • нужен код, обслуживающий запись/удаление;
  • сложности при «падении» скрипта с тем, как снять lock, нужен watchDog;
  • сложности при «падении» самой базы

Сторонние приложения (к примеру, run-one для Ubuntu)

  • не для всех платформ есть одинаковые приложения с одинаково предсказуемым поведением;
  • не всегда есть возможность установить что-то дополнительное;
  • не все умеют блокировать «в сети»

Нативные lock-файлы

  • каждая команда должна сопровождаться созданием файла;
  • сколько команд — столько строк с путем и именем lock-файла

Наиболее распространенный, конечно же, — 3й вариант, но он создает очень много неудобств при наличии большого кол-ва серверов и процессов.
Поэтому я решил поделиться идеей написания singleton-команды на базе symfony/console. Но идею можно использовать и в любом другом фреймворке.

Итак, первое же, от чего пришлось отказаться — flock, который используется, к примеру, в LockHandler от symfony. Он не дает возможность блокировки среди нескольких серверов.
Вместо этого будем создавать lock-файл в расшаренной между серверами директории, с помощью маленького сервиса, это практически аналог LockHandler, но с «выпиленным» flock.

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

Для этого предлагаю применить нечто, похожее на Mediator — реализовать и финализировать стандартный метод execute(), который будет запущен при старте команды и навязать использование нового метода lockExecute().
Для чего это нужно:

  • весь код команды будет содержаться в методе lockExecute();
  • вызываемый при запуске метод execute() будет создавать блокировку, регистрировать снятие блокировки при падении/завершении скрипта и только потом — выполнять lockExecute()

В итоге, стандартная команда symfony:

class CreateUserCommand extends Command {     protected function configure()     {         // ...     }      protected function execute(InputInterface $input, OutputInterface $output)     {         // ...     } }

будет выглядеть так:

class CreateUserCommand extends SingletonCommand implements SingletonCommandInterface {     protected $name = 'user:command';      protected function configure()     {         // ...         parent::configure();     }      public function lockExecute(InputInterface $input, OutputInterface $output)     {         // ...     } }

Писать значительно больше кода не придется и при этом она будет гарантированно запущена только 1 раз, сколько бы серверов не попытались это сделать.
Единственное условие — общая директория для lock-файлов.

Уже готовое решение и больше деталей можно посмотреть на гитхаб: singleton-command

Спасибо за внимание!
ссылка на оригинал статьи https://habrahabr.ru/post/317258/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *