В Интернете, в том числе на Хабре, неоднократно поднималась тема обработки сигналов с помощью инструментов PHP, но большинство из них достаточно старые, содержат неактуальную информацию и не отвечают на часто задаваемый вопрос: «зачемЭ», вот тут и займемся.
начнем Начнем.
В каких случаях может понадобиться обработка сигналов в php?
- В любом загруженном проекте рано или поздно приходится сталкиваться с необходимостью распараллеливания процесса, и наиболее распространенным способом является использование сервера сообщений, такого как RabbitMq, Gearman, Kafka и других.
В этот момент возникает необходимость создания так называемого потребителя.
Он состоит из цикла проверки новых сообщений и их обработки.
Теперь реальная ситуация: мы обновили код и нам нужно перезапустить потребителей — если мы просто отправим сигнал SIGTERM, то будет вероятность получить несогласованные данные в базе данных или другие проблемы из-за поломки скрипта в середине.
обработки, и в этом случае нам поможет обработка сигналов.
- Также в эпоху контейнеризации хорошо иметь возможность корректно гасить контейнер с приложением, не теряя при этом обработанную информацию.
- Ну и третий вариант — это конкретные задачи, где нужен демон и он написан на PHP, здесь нам понадобится ряд сигналов для обновления конфигурации, выполнения скрипта и других действий.
Пример .
Арсенал, который предоставляет нам php для работы с сигналами:
- pcntl_signal() php > = 4.1.0 — функция регистрации обработчика сигнала.
- объявить (тикание = 1) php < 5.3 — указывает, сколько тиков интерпретатор будет проверять на наличие сигнала.
- pcntl_signal_dispatch() php > = 5.3.0 — запуск проверки наличия необработанного сигнала вручную, как более продуктивная альтернатива объявлению.
- pcntl_async_signals() php > = 7.1.0 — асинхронная вставка обработчика сигнала в стек вызовов.
- pcntl_signal_get_handler() php > = 7.1.0 — получение функции-обработчика сигнала.
- pcntl_alarm() php > = 4.3.0 — отправь себе СИГАЛАРМ.
- pcntl_sigprocmask() php > = 5.3.0 — вы можете заблокировать или разблокировать обработку указанных сигналов, а также удалить или заменить стек заблокированных сигналов.
Немного теории:
Для каждого сигнала, который мы хотим обработать, нам необходимо зарегистрировать функцию-обработчик с помощью pcntl_signal().Важное замечание от php.net pcntl_signal() — не собирает обработчики сигналов в стеке, а заменяет их, то есть если вы где-то в коде снова определите обработчик сигнала, он просто переопределит предыдущий, поэтому его не используют в сторонние библиотеки и старайтесь не использовать эти возможности.
Но в конце статьи будет небольшой бонус по этому поводу.
После выполнения функции-обработчика интерпретатор продолжает свою работу с точки прерывания.
Если, конечно, вы не вызвали die() в обработчике.
Из предыдущих версий PHP мы знаем о существовании директивы Declare(ticks = 1), которая сообщала нашему скрипту после выполнения каждой операции проверять, получили ли мы сигнал, соответственно это давало заметные накладные расходы при выполнении, особенно если есть много кода, он достаточно хорошо описан Здесь .
Но к счастью, на дворе 2018 год и разработчики языка добавили потрясающую вещь — pcntl_async_signals(), эта функция позволяет интерпретатору не отвлекаться на проверку сигнала; Фактически, он помещает наш обработчик сигнала следующим в стек вызовов после выполнения функции.
Синтетический тест производительности не выявил различий с pcntl_async_signals() и без него.
Теперь поговорим об ограничениях обработчика, сигнал будет обработан только после завершения текущей функции, если это вызов API или базы данных, то время до обработки сигнала может занять больше времени, это должно быть учитывается, например, если вы используете супервизор или докер, увеличьте таймаут перед отправкой SIGKILL на время вашего собственного длинного блокирующего вызова плюс резерв.
Еще хочу сказать, что во время тестирования я столкнулся с интересным поведением функции Sleep(), которое оказалось задокументированным, но я этого не ожидал — оно прерывается по сигналу и возвращает количество неспавшихся секунд, то есть если вдруг понадобится воспользоваться и быть уверенным в продолжительности сна, то это будет выглядеть так:
Еще хотелось бы акцентировать внимание на том, что примеры с обработкой SIGKILL вы найдете на многих ресурсах — но это не работает (работало на старых версиях Linux), но теперь этот сигнал убивает процесс из операционной системы, и повлиять на это невозможно.$sleep = 1000; while ($sleep > 0) { $sleep = sleep($sleep); }
И немного кода
Как это будет выглядеть в случае с костюмером RabbitMq: Это наш упрощенный менеджер, задача которого подготовить потребителя и контролировать выполнение задач.
class QueueManager
{
private $stopConsume = false;
public function stopConsume()
{
$this->stopConsume = true;
}
public function consume($consumerAlias)
{
$consumer = $this->getConsumerBuilder()->create($consumerAlias);
$channel = $consumer->getChannel();
while (\count($channel->callbacks) && $this->stopConsume !== true) {
$channel->wait();
}
}
}
И это все, что вам нужно в контроллере:
class SomeController
{
private $queueManager;
public function __construct()
{
$this->queueManager = new QueueManager();
pcntl_signal(SIGTERM, [$this->queueManager, 'stopConsume']);
}
public function consumeSomeQueue()
{
$this->queueManager->consume('SomeConsumer');
}
}
При получении сигнала будет вызван метод stopConsume объекта очередиManager — он, в свою очередь, установит параметр stopConsume в значение true и останется только дойти до конца обработки текущего сообщения, после чего цикл завершится .
Целью статьи не является рассмотрение побочных вопросов демонизации процессов, таких как поддержание соединений, утечки памяти и т.п.
Надеюсь, эта информация была для вас полезной и интересной, Если у вас возникнут вопросы, буду рад ответить в комментариях или при необходимости дополнить статью.
Сам бонус Класс такого формата даст нам возможность поставить на сигнал несколько обработчиков, если спросить зачем — просто потому что мы можем.
class SigHandler
{
private $handlers = [];
public function handle($sigNumber)
{
if (!empty($this->handlers[$sigNumber])) {
foreach ($this->handlers[$sigNumber] as $signalHandler) {
$signalHandler($sigNumber);
}
}
}
public function subscribe($sigNumber, $handler)
{
$this->handlers[$sigNumber][$this->getFunctionHash($handler)] = $handler;
}
public function unsubscribe($sigNumber, $handler)
{
unset($this->handlers[$sigNumber][$this->getFunctionHash($handler)]);
}
private function getFunctionHash($callable)
{
return spl_object_hash($callable);
}
}
Попробуйте на работе:
pcntl_async_signals(true);
$sigHandler = new SigHandler();
pcntl_signal(SIGTERM, [$sigHandler, 'handle']);
$sigHandler->subscribe(SIGTERM, function () {
echo 'sigterm_1', PHP_EOL;
});
$sigHandler->subscribe(SIGTERM, function () {
echo 'sigterm_2', PHP_EOL;
});
while (true) {
}
Теги: #php #pcntl #Потребитель #сигналы #Ненормальное программирование #Разработка веб-сайтов #php #программирование
-
Что Такое Intel Centrino?
19 Oct, 24 -
Ховеры Бывают Разные
19 Oct, 24 -
Как Написать Хороший Sla
19 Oct, 24 -
Look-At — Менеджер Фокуса
19 Oct, 24 -
В Поисках Идеального Фреймворка Javascript
19 Oct, 24