很好地阻止齿轮工人

时间:2010-02-16 02:43:03

标签: php gearman

我有许多Gearman工作人员不断运行,保存用户页面视图记录等内容。偶尔,我会更新Gearman工作人员使用的PHP代码。为了让工作人员切换到新代码,我杀了并重新启动了工作者的PHP进程。

有什么更好的方法可以做到这一点?据推测,当我杀死其中一个工作进程时,我有时会丢失数据(尽管不是非常重要的数据)。

编辑:我找到了一个适合我的答案,并在下面发布。

12 个答案:

答案 0 :(得分:12)

解决方案1 ​​


通常我使用带-r标志的unix守护程序实用程序运行我的worker,让它们在一个作业后过期。每次迭代后,您的脚本将正常结束,守护程序将自动重启。

你的工人对于一份工作来说是陈旧的,但这可能不像丢失数据那么重要

此解决方案还具有释放内存的优势。如果你正在做大型工作,你可能会遇到内存问题,因为PHP前5.3有神奇的GC。

解决方案2


您还可以向退出脚本的所有工作人员添加退出功能。如果您想重新启动,只需给予齿轮手机呼叫以高优先级退出。

答案 1 :(得分:8)

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

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

AutoRestart();  

答案 2 :(得分:7)

好吧,我发布了这个问题,现在我觉得我找到了一个很好的答案。

如果你查看Net_Gearman_Worker的代码,你会发现在工作循环中,监视函数stopWork,如果它返回true,它将退出函数。

我做了以下事项:
使用memcache,我创建了一个缓存值gearman_restarttime,每当我更新站点时,我都会使用一个单独的脚本将其设置为当前时间戳。 (我使用了Memcache,但这可以存储在任何地方 - 数据库,文件或任何东西)。

我将Worker类扩展为Net_Gearman_Worker_Foo,并让我所有的工作者实例化。在Foo类中,我重写了stopWork函数以执行以下操作:首先,它检查gearman_restarttime;第一次通过时,它将值保存在全局变量中。从那时起,每次都将缓存值与全局值进行比较。如果已更改,则stopWork返回true,然后工作程序退出。 cron会每分钟检查一下每个工作程序是否仍在运行,并重新启动任何已退出的工作程序。

也可能值得在stopWork中放置一个计时器,并且每隔x分钟只检查一次缓存。在我们的例子中,Memcache足够快,每次检查值似乎都不是问题,但如果你使用其他系统来存储当前时间戳,那么检查频率会更好。

答案 3 :(得分:1)

嗯,您可以在工作程序中实现代码,以便在源代码被修改时偶尔检查,如果是,那么只要在他们认为合适的时候自行查看。也就是说,检查他们是否在工作中间,以及工作是否非常大。

其他方式是实现某种中断,也许是通过网络说停,只要你有机会并重新启动。

最后一个解决方案是帮助修改Gearman的源代码以包含此功能。

答案 4 :(得分:1)

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

就像上面的文章演示的那样,我在BASH shell脚本中运行一个worker,偶尔在作业之间退出以清理(或重新加载worker-script) - 或者如果给定给定任务它可以退出使用特定的退出代码并关闭。

答案 5 :(得分:1)

我最近也在考虑这个问题(尽管在使用Gearman :: XS的Perl中)。我的用例和你的一样 - 允许一个长期运行的齿轮工人定期检查自己的新版本并重新加载。

我的第一次尝试就是让工作人员跟踪上次检查工作人员脚本版本的时间(md5sum也可以)。然后,一旦N秒过去,在作业之间,它将检查是否有可用的新版本,并重新启动(fork()/ exec())。这确实工作正常,但是注册了罕见工作的工人可能最终等待工作()返回,从而检查当前时间。

所以我现在在等待work()工作时设置一个相当短的超时,所以我可以更频繁地检查时间。 PHP接口建议您在注册作业时设置此超时值。我正在使用SIGALRM来触发新版本检查。 perl接口阻塞work(),因此最初没有触发警报。将超时设置为60秒使SIGALRM正常工作。

答案 6 :(得分:1)

如果有人在寻找运行perl的工作人员的答案,那就是GearmanX::Starter库的用途。您可以通过两种不同的方式在完成当前作业后停止工作:外部通过发送工作进程SIGTERM,或通过设置全局变量以编程方式。

答案 7 :(得分:1)

鉴于工作人员是用PHP编写的,最好按照已知的时间表回收它们。这可以是自启动以来的静态时间量,也可以在尝试一定数量的作业后完成。

这基本上杀死了(没有双关语)两只一石二鸟。您正在减少内存泄漏的可能性,并且您可以通过一致的方式确定您的工作人员何时可以获取任何可能的新代码。

我通常写工人,他们会将他们的间隔报告给stdout和/或记录设施,这样就可以很容易地检查工人在这个过程中的位置。

答案 8 :(得分:1)

我遇到了同样的问题,并提出了python 2.7的解决方案。

我正在编写一个python脚本,它使用gearman与系统上的其他组件进行通信。该脚本将有多个worker,我让每个worker在不同的线程中运行。工作人员都接收到齿轮数据,他们处理并将数据存储在消息队列中,主线程可以根据需要从队列中提取数据。

我干净地关闭每个工作人员的解决方案是继承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命令。当工作人员收到此消息时,它知道要自行关闭。

希望这有帮助!

答案 9 :(得分:0)

这非常适合您的持续集成系统。我希望你能拥有它,或者你应该尽快拥有它: - )

当您签入新代码时,它会自动构建并部署到服务器上。作为构建脚本的一部分,您将终止所有工作者并启动新工作程序。

答案 10 :(得分:0)

我使用以下代码同时支持Ctrl-Ckill -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();

答案 11 :(得分:0)

我所做的是使用gearmadmin检查是否有任何作业正在运行。我使用管理API为此创建了一个UI。当工作闲置时,杀死他们是没有害处的。