Обновить

Комментарии 28

Кейсы с блокировкой требуются в крон-задачах и почти никогда при обычных запусках. Если учитывать это — думаю лучшим вариантом было бы просто написать простенький адаптер под уже существующее несколько лет решение, которое использует симфонийский консольный компонент: https://github.com/illuminate/console


Оно уже покрывает все проблемы, озвученные в статье.


$sheduler->command('some')->everyFiveMinute()->withoutOverlap();

М?

Безусловно, но это решение значительно больше, чем 2 коротких файла.
Да и в моем случае это не крон.

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


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


Короче, да, согласен, надо смотреть. В качестве быстрого решения на коленке — ваш вариант оправдан более чем.+

    public function lock($name)
    {
        $file = $this->getFilePath($name);
        if ($this->fileSystem->exists($file)) {
            return false;
        }
        $this->fileSystem->touch($file);
        return true;
    }


Если команда сломалась и ваш лок-файл остался, то команда больше никогда не будет вызвана.
Например, если процесс будет убит из консоли. Или выключится электричество. Или произойдет какой-то сбой на сервере.
Да, в этом случае команда не запустится. Это стоит учитывать.
В моем случае есть сборщик мусора, который проверяет «залежавшиеся» лок-файлы и сообщает о том, что это подозрительно. Как ни крути — при солидном кол-ве серверов такое бывает частенько, чем-то жертвовать приходится.
Подумаю над Вашим замечанием, может быть придет идея, спасибо.
НЛО прилетело и опубликовало эту надпись здесь
Я давненько не встречал таких девайсов, которым нужно так много времени между проверкой и созданием, чтобы другой «вклинился».
Но, в принципе, это можно поправить.
Скорость проверки значения не имеет. Race condition все равно есть. По ссылке пример вообще в памяти, которая куда быстрее «таких девайсов»
Поправил на чуть более удачный вариант.

А зачем в абстрактном классе


    /** @var string */
    protected $name = null;

?
Можно же использовать \Symfony\Component\Console\Command\Command::getName

Действительно, как-то я не заметил :) Осталось после «причесывания». Поправлю, спасибо.

Я бы еще конструктор сделал идентичный базовому:


    /**
     * SingletonCommand constructor.
     * @param LockService $lockService
     * @throws \Exception
     */
    public function __construct(LockService $lockService, $name = null)
    {
        $this->lockService = $lockService;
        parent::__construct($name);
    }
Поправил, спасибо

Я стесняюсь спросить, а что это за юс-кейсы такие странные? Один процесс на N серверов?


Что это таким путём надо делать? Не проще ли это порешать очередью, где можно сколько угодно консумеров запускать, но отрабатывать они могут по одному друг за другом?

Завидую Вашему опыту :) Видимо, Вы еще никогда не слышали отказов типа: «заказчик пока не видит смысла уходить с php 5.3», «мы пока не можем поставить gearman, еtс», «этот модуль поставить нельзя, у нас один php-билд для всех проектов», «да, эта штука мертва уж 5 лет, но у нас есть приоритетнее задачи» и тому подобного.
Безусловно, в стартап-ах и молодых проектах есть возможность не задумываться о таком, но мои статьи в основном связаны с проектами-тинейджерами, где все не так просто.

@jced и всё-таки что за юс-кейсы то такие?

Невозможность установки серверов очередей. Архитектура такова, что есть сервера с шаренной директорией, туда «сбрасываются» на лету сгенерированные скрипты, которые нужно выполнять. На каждом сервере — по демону, которые «рахватывают» эти скрипты-джобы и выполняют. Как-то так, в общих чертах.
Невозможность установки серверов очередей.

Наше вам сочувствие.


Я писал микросервис для лицо-распознавания на питонах и вместо скучного REST'а сделал AMQP-консумера. Это оказалось просто и эффективно. Особенно помогло в горизонтальном масштабировании — можно было запустить кучу консумеров где угодно и задачи отрабатывались быстрее.

Да, в моем случае отличных готовых решений хоть отбавляй, если бы была возможность, но чтобы не городить что-то еще запутаннее чем есть в текущих условиях — решил найти самое короткое решение.
Как мне показалось — один файл (без учета интерфейса), довольно «изящно», решил, может есть такие же как я, застрявшие в 20 веке и им тоже пригодится :)
Лично я сейчас на проекте использую возможности MySQL для блокировок конкурентных запусков команд, а именно функции GET_LOCK(), IS_FREE_LOCK(), IS_USED_LOCK() и RELEASE_LOCK() которые били доданы где то в MySQL 4.1.

Плюсы: Простота, не нужно подчищать lock файлы, так как в случае падения/завершения команды блокировка автоматически удалится. Также есть возможность ожидания освобождение блокировки.
Минусы: не дружит с репликацией.
И второй минус — нужен MySQL :)
НЛО прилетело и опубликовало эту надпись здесь
на фри хостингах нету проблемы создания блокирующихся тасков. А там где и когда эта проблема может возникнуть явно уже не фри хостинг.
НЛО прилетело и опубликовало эту надпись здесь
Можно было бы сделать console command listener, завязавшись на console.command, тогда бы не пришлось переписывать текущие команды

Это типа на нескольких серверах, а не на одном.

Это для разных серверов и, как я написал, я пытался уйти от реализации блокировки там, где вызывается команда.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации