Прекрасно прекратить работу

У меня есть несколько рабочих Gearman, работающих постоянно, сохраняя такие вещи, как записи просмотров пользовательских страниц и т. Д. Иногда я обновляю PHP-код, который используется рабочими Gearman. Чтобы заставить рабочих перейти на новый код, я убиваю и перезапускаю процессы PHP для рабочих.

Что это лучший способ сделать это? Предположительно, я иногда теряю данные (хотя и не очень важные данные), когда я убью один из этих рабочих процессов.

Изменить: я нашел ответ, который работает для меня, и разместил его ниже.

Решение 1


Обычно я запускаю своих рабочих с помощью утилиты daemon unix с флагом -r и позволяю им истекать после одного задания. После каждой итерации ваш скрипт закончится грациозно, и демон перезапустится автоматически.

Ваши работники будут устаревшими для одной работы, но это может быть не так сложно для вас, как потеря данных

Это решение также имеет преимущество освобождения памяти. У вас могут возникнуть проблемы с памятью, если вы выполняете большие задания, поскольку PHP pre 5.3 имеет ужасный GC.

Решение 2


Вы также можете добавить функцию выхода для всех ваших работников, которая выходит из сценария. Когда вы хотите перезапустить, вы просто даете призывному персоналу выйти с высоким приоритетом.

function AutoRestart() { static $startTime = time(); if (filemtime(__FILE__) > $startTime) { exit(); } } AutoRestart(); 

Ну, я задал этот вопрос, теперь я думаю, что нашел для него хороший ответ.

Если вы посмотрите в коде для Net_Gearman_Worker, вы обнаружите, что в рабочем цикле отслеживается функция stopWork, и если он возвращает true, он выходит из функции.

Я сделал следующее:
Используя memcache, я создал кешированное значение, gearman_restarttime, и я использую отдельный скрипт, чтобы установить его на текущую временную метку, когда я обновляю сайт. (Я использовал Memcache, но это можно было хранить где угодно – базу данных, файл или что-то еще).

Я расширил класс Worker, по сути, Net_Gearman_Worker_Foo, и все мои сотрудники инстанцировали это. В классе Foo я перевернул функцию stopWork, чтобы сделать следующее: во-первых, он проверяет gearman_restarttime; в первый раз, он сохраняет значение в глобальной переменной. С этого момента каждый раз, сравнивая кешированное значение с глобальным. Если он изменился, stopWork возвращает true, и рабочий завершает работу. Cron проверяет каждую минуту, чтобы увидеть, работает ли каждый рабочий, и перезапускает любого работника, который ушел.

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

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

Другим способом было бы реализовать какое-то прерывание, возможно, через сеть, чтобы сказать «стоп» всякий раз, когда у вас есть шанс и перезапуск.

Последнее решение помогает модифицировать источник Gearman для включения этой функции.

Я тоже смотрел на это недавно (хотя в Perl с Gearman :: XS). Моя учетная запись была такой же, как ваша. Позвольте долгому сотруднику-реселлеру периодически проверять новую версию и перезагружать.

Моя первая попытка заключалась в том, что рабочий всегда отслеживал, как долго он проверял версию рабочего сценария (также будет работать md5sum). Затем, как только N секунд истекло, между заданиями, он будет проверять, доступна ли новая версия, и перезагрузиться (fork () / exec ()). Это работало нормально, но работники, зарегистрированные для редких заданий, потенциально могли ожидать часы ожидания работы () для возврата и, таким образом, для проверки текущего времени.

Поэтому я сейчас устанавливаю довольно короткий тайм-аут, ожидая работы с work (), поэтому я могу проверять время более регулярно. Интерфейс PHP предполагает, что вы можете установить это значение таймаута при регистрации для задания. Я использую SIGALRM для запуска проверки новой версии. Интерфейс perl блокирует работу (), поэтому сигнал тревоги не запускался изначально. Установка таймаута на 60 секунд обеспечила работу SIGALRM.

Если кто-то искал ответ для рабочего, выполняющего perl, это часть того, для чего предназначена библиотека GearmanX :: Starter . Вы можете остановить работников после завершения текущего задания двумя разными способами: извне, отправив рабочий процесс SIGTERM или программно, установив глобальную переменную.

Учитывая тот факт, что рабочие написаны на PHP, было бы неплохо переработать их по известному графику. Это может быть статическое количество времени с момента запуска или может быть выполнено после того, как было предпринято определенное количество заданий.

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

Обычно я пишу работникам, чтобы они сообщали о своем переходе в stdout и / или в средство ведения журнала, поэтому просто проверить, где находится рабочий.

Я столкнулся с этой проблемой и придумал решение для python 2.7.

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

Мое решение для чистого закрытия каждого работника состояло в подклассе gearman.GearmanWorker и переопределении функции work() :

 from gearman import GearmanWorker POLL_TIMEOUT_IN_SECONDS = 60.0 class StoppableWorker(GearmanWorker): def __init__(self, host_list=None): super(StoppableWorker,self).__init__(host_list=host_list) self._exit_runloop = False # OVERRIDDEN def work(self, poll_timeout=POLL_TIMEOUT_IN_SECONDS): worker_connections = [] continue_working = True def continue_while_connections_alive(any_activity): return self.after_poll(any_activity) while continue_working and not self._exit_runloop: worker_connections = self.establish_worker_connections() continue_working = self.poll_connections_until_stopped( worker_connections, continue_while_connections_alive, timeout=poll_timeout) for current_connection in worker_connections: current_connection.close() self.shutdown() def stopwork(self): self._exit_runloop = True - from gearman import GearmanWorker POLL_TIMEOUT_IN_SECONDS = 60.0 class StoppableWorker(GearmanWorker): def __init__(self, host_list=None): super(StoppableWorker,self).__init__(host_list=host_list) self._exit_runloop = False # OVERRIDDEN def work(self, poll_timeout=POLL_TIMEOUT_IN_SECONDS): worker_connections = [] continue_working = True def continue_while_connections_alive(any_activity): return self.after_poll(any_activity) while continue_working and not self._exit_runloop: worker_connections = self.establish_worker_connections() continue_working = self.poll_connections_until_stopped( worker_connections, continue_while_connections_alive, timeout=poll_timeout) for current_connection in worker_connections: current_connection.close() self.shutdown() def stopwork(self): self._exit_runloop = True 

Используйте его так же, как GearmanWorker. Когда пришло время выйти из сценария, вызовите stopwork() . Он не остановится сразу – он может занять до poll_timeout секунд, прежде чем он выйдет из цикла запуска.

Может быть несколько умных способов вызвать stopwork() . В моем случае я создаю временного клиента-ретранслятора в основном потоке. Для рабочего, которого я пытаюсь отключить, я посылаю специальную команду STOP через сервер ретранслятора. Когда рабочий получает это сообщение, он знает, что он закрыт.

Надеюсь это поможет!

http://phpscaling.com/2009/06/23/doing-the-work-elsewhere-sidebar-running-the-worker/

Как показано в приведенной выше статье, я запускаю рабочего внутри сценария оболочки BASH, периодически удаляя промежутки между заданиями для очистки (или перезагружая рабочий сценарий), или если ему задана заданная задача, он может выйти с определенным выйти и закрыть.

Это прекрасно впишется в вашу систему непрерывной интеграции. Я надеюсь, что у вас есть это или вы должны скоро это получить 🙂

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

Я использую следующий код, который поддерживает как Ctrl-C и kill -TERM . По умолчанию supervisor отправляет сигнал TERM если не изменил настройку signal= . В PHP 5.3+ declare(ticks = 1) устарела, вместо этого используйте pcntl_signal_dispatch() .

 $terminate = false; pcntl_signal(SIGINT, function() use (&$terminate) { $terminate = true; }); pcntl_signal(SIGTERM, function() use (&$terminate) { $terminate = true; }); $worker = new GearmanWorker(); $worker->addOptions(GEARMAN_WORKER_NON_BLOCKING); $worker->setTimeout(1000); $worker->addServer('127.0.0.1', 4730); $worker->addFunction('reverse', function(GearmanJob $job) { return strrev($job->workload()); }); $count = 500 + rand(0, 100); // rand to prevent multple workers restart at same time for($i = 0; $i < $count; $i++) { if ( $terminate ) { break; } else { pcntl_signal_dispatch(); } $worker->work(); if ( $terminate ) { break; } else { pcntl_signal_dispatch(); } if ( GEARMAN_SUCCESS == $worker->returnCode() ) { continue; } if ( GEARMAN_IO_WAIT != $worker->returnCode() && GEARMAN_NO_JOBS != $worker->returnCode() ) { $e = new ErrorException($worker->error(), $worker->returnCode()); // log exception break; } $worker->wait(); } $worker->unregisterAll(); 

Я использую gearmadmin чтобы проверить, есть ли какие-либо задания. Я использовал API-интерфейс администратора для создания пользовательского интерфейса. Когда рабочие места сидят без дела, нет никакого вреда в их убийстве.