43

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

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

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

4

12 回答 12

12

解决方案 1


通常,我使用带有 -r 标志的 unix 守护程序实用程序运行我的工作人员,并让他们在一项工作后过期。您的脚本将在每次迭代后优雅地结束,并且守护程序将自动重新启动。

您的员工将无法胜任一份工作,但这对您来说可能没有丢失数据那么重要

此解决方案还具有释放内存的优点。如果您正在做大型工作,您可能会遇到内存问题,因为 PHP 5.3 之前的 GC 非常糟糕。

解决方案 2


您还可以为退出脚本的所有工作人员添加退出功能。当您想重新启动时,您只需给 gearman 调用以高优先级退出。

于 2010-03-23T14:36:37.940 回答
8
function AutoRestart() {
   static $startTime = time();

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

AutoRestart();  
于 2011-08-17T08:05:46.987 回答
7

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

如果查看 Net_Gearman_Worker 的代码,您会发现在工作循环中,函数 stopWork 被监控,如果返回 true,则退出该函数。

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

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

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

于 2010-06-09T19:33:22.813 回答
1

嗯,您可以在工作人员中实现一个代码,以偶尔检查源代码是否被修改,如果是,则在他们认为合适的时候自杀。也就是说,在他们处于工作中间时检查,以及工作是否非常大。

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

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

于 2010-02-16T20:36:27.800 回答
1

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

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

于 2010-03-03T13:18:45.020 回答
1

我最近也一直在研究这个(尽管在 perl 中使用 Gearman::XS)。我的用例与您的相同 - 允许长期运行的齿轮工工人定期检查自身的新版本并重新加载。

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

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

于 2010-06-03T10:34:01.773 回答
1

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

于 2010-08-12T18:40:54.130 回答
1

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

这基本上用一块石头杀死(没有双关语)两只鸟。您正在减轻内存泄漏的可能性,并且您有一种一致的方式来确定您的工作人员何时会接受任何潜在的新代码。

我通常编写工作人员,以便他们向标准输出和/或日志工具报告他们的时间间隔,以便轻松检查工作人员在进程中的位置。

于 2011-03-28T23:00:58.480 回答
1

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

我正在编写一个 python 脚本,它使用 gearman 与系统上的其他组件进行通信。该脚本将有多个工作人员,我让每个工作人员在单独的线程中运行。工作人员都接收到 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 客户端。对于我试图关闭的工作人员,我通过 gearman 服务器发送一个特殊的 STOP 命令。当工作人员收到此消息时,它知道要关闭自己。

希望这可以帮助!

于 2013-06-11T17:38:07.563 回答
1

我使用以下同时支持Ctrl-C和的代码kill -TERM。如果没有修改设置,默认supervisor发送信号。在 PHP 5.3+中已弃用,请改用。TERMsignal=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();
于 2015-08-28T10:33:05.873 回答
0

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

当您签入新代码时,它会自动构建并部署到服务器上。作为构建脚本的一部分,您会杀死所有工作人员并启动新工作人员。

于 2010-02-16T21:12:52.483 回答
0

我所做的是gearmadmin用来检查是否有任何作业正在运行。我使用管理 API 来为此制作 UI。当工作闲置时,杀死它们并没有什么坏处。

于 2016-06-10T21:15:22.280 回答