Остановка рабочих-механиков

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

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

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

12 ответов

Решение

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

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

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

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

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

Решение 1


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

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

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

Решение 2


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

function AutoRestart() {
   static $startTime = time();

   if (filemtime(__FILE__) > $startTime) {
      exit();
   }
}

AutoRestart();  

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

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

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

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

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

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

Я столкнулся с этой же проблемой и нашел решение для 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

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

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

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

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

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

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

Я использую следующий код, который поддерживает оба 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();

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

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

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

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

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

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

Другие вопросы по тегам