PHP-很好地停止齿轮工
我有许多Gearman工作程序一直在运行,保存诸如用户页面浏览量记录之类的内容。有时,我将更新Gearman工作程序使用的PHP代码。 为了使工作人员切换到新代码,我杀死并为工作人员重新启动PHP进程。
有什么更好的方法可以做到这一点? 据推测,当我杀死其中一个工作进程时,我有时会丢失数据(尽管不是很重要的数据)。
编辑:我找到了一个适合我的答案,并将其发布在下面。
Karptonite asked 2020-08-01T14:09:58Z
12个解决方案
12 votes
解决方案1
通常,我使用带有-r标志的unix守护程序实用程序来运行我的工作程序,并让他们在一项工作后过期。 您的脚本将在每次迭代后正常结束,并且守护程序将自动重新启动。
您的工作人员对一份工作而言会过时,但对您而言,损失可能不如丢失数据那么大
该解决方案还具有释放内存的优势。 如果您要执行大量工作,则可能会遇到内存问题,因为PHP 5.3之前版本的GC非常糟糕。
解决方案2
您还可以向所有退出脚本的工作人员添加退出功能。 当您想重启时,只需给齿轮手打电话以高优先级退出即可。
bnmrrs answered 2020-08-01T14:10:33Z
8 votes
function AutoRestart() {
static $startTime = time();
if (filemtime(__FILE__) > $startTime) {
exit();
}
}
AutoRestart();
Orwellophile answered 2020-08-01T14:10:48Z
7 votes
好吧,我发布了这个问题,现在我想我已经找到了一个很好的答案。
如果在Net_Gearman_Worker的代码中查找,则会发现在工作循环中,函数stopWork被监视,如果返回true,则退出该函数。
我做了以下事情:
使用内存缓存,我创建了一个缓存值gearman_restarttime,并且每当我更新站点时,我都使用单独的脚本将其设置为当前时间戳。 (我使用了Memcache,但是它可以存储在任何地方-数据库,文件或任何东西)。
实际上,我将Worker类扩展为Net_Gearman_Worker_Foo,并让我的所有Worker实例化了该类。 在Foo类中,我覆盖了stopWork函数以执行以下操作:首先,它检查gearman_restarttime; 第一次,它将值保存在全局变量中。 从那时起,每次将缓存的值与全局值进行比较。 如果已更改,则stopWork返回true,然后工作程序退出。 Cron会每分钟检查一次,以查看每个工作进程是否仍在运行,然后重新启动所有退出的工作进程。
也可以将计时器设置为stopWork,并且每x分钟仅检查一次缓存。 在我们的例子中,Memcache足够快,以至于每次检查该值似乎都不成问题,但是如果您使用其他系统存储当前时间戳,则减少检查频率会更好。
Karptonite answered 2020-08-01T14:11:33Z
1 votes
嗯,您可以在辅助程序中实现一个代码,以偶尔检查源代码是否被修改,如果是,则在认为合适时杀死自己。 也就是说,检查它们在作业中间的时间,以及作业是否很大。
另一种方法是实现某种中断,也许是通过网络说出只要有机会就停止并重新启动。
最后一个解决方案是帮助修改Gearman的源以包括此功能。
Yarek T answered 2020-08-01T14:12:03Z
1 votes
我最近也一直在研究这个问题(尽管在Gearl :: XS的perl中)。 我的用例与您的用例相同-允许一名长期运行的齿轮工定期检查其新版本并重新加载。
我的第一次尝试只是让工作人员跟踪自上次检查工作程序脚本版本以来的时间(md5sum也可以工作)。 然后,在作业之间经过N秒后,它将检查自身是否有新版本,并重新启动自身(fork()/ exec())。 这样做确实可以,但是注册为稀有工作的工作人员可能需要等待数小时才能返回work(),从而检查当前时间。
因此,我现在在等待带有work()的作业时将超时设置为较短的时间,以便可以更定期地检查时间。 PHP接口建议您在注册作业时可以设置此超时值。 我正在使用SIGALRM触发新版本检查。 Perl接口在work()上阻塞,因此最初并未触发警报。 将超时设置为60秒即可使SIGALRM工作。
d5ve answered 2020-08-01T14:12:33Z
1 votes
如果有人在寻找运行Perl的工人的答案,那就是GearmanX :: Starter库的一部分。 您可以通过两种不同的方式在完成当前任务之后停止工作程序:在外部通过向工作程序进程发送SIGTERM来停止工作,或者通过编程方式设置全局变量来停止工作程序。
runrig answered 2020-08-01T14:12:53Z
1 votes
鉴于这些工作程序是用PHP编写的,因此最好在已知的时间表上回收它们。 自启动以来这可以是静态时间,也可以在尝试一定数量的作业后完成。
这实际上用一块石头杀死了(没有双关语)两只鸟。 您正在减轻内存泄漏的可能性,并且可以采用一致的方式来确定何时您的工作人员将使用任何潜在的新代码。
我通常编写工作程序,以便他们向stdout和/或日志记录工具报告其时间间隔,因此很容易检查工作程序中的位置。
Wil Moore III answered 2020-08-01T14:13:22Z
1 votes
我遇到了同样的问题,并提出了python 2.7的解决方案。
我正在编写一个Python脚本,该脚本使用gearman与系统上的其他组件进行通信。 该脚本将有多个工作程序,每个工作程序都在单独的线程中运行。 所有工作人员都接收齿轮员数据,他们处理该数据并将其存储在消息队列中,并且主线程可以根据需要将数据从队列中拉出。
我彻底关闭每个工人的解决方案是子类stopwork(),并重写poll_timeout函数:
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命令。 当工作人员收到此消息时,便知道要关闭自己。
希望这可以帮助!
RobotNerd answered 2020-08-01T14:14:05Z
1 votes
[HTTP://PHP scaling.com/2009/06/23/doing-他和-work-elsewhere-sidebar-running-他和-worker/]
就像上面的文章所演示的那样,我已经在BASH shell脚本中运行了一个worker,偶尔在两次作业之间退出以进行清理(或重新加载worker-script)-或者如果给定了给定的任务,则可以使用特定的退出 退出代码并关闭。
Alister Bulman answered 2020-08-01T14:14:30Z
0 votes
这非常适合您的持续集成系统。 我希望您拥有它,或者您应该尽快拥有它:-)
签入新代码时,它会自动生成并部署到服务器上。 作为构建脚本的一部分,您将杀死所有工作进程并启动新的工作进程。
Alex Weinstein answered 2020-08-01T14:14:54Z
0 votes
我使用下面的代码同时支持Ctrl-C和kill -TERM。如果未修改signal=设置,则默认情况下supervisor发送TERM信号。 在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();
happy_marmoset answered 2020-08-01T14:15:15Z
0 votes
我要做的是使用gearmadmin检查是否正在运行任何作业。 我使用admin API为此创建了一个UI。 当工作闲置时,杀死他们没有任何伤害。
kagronick answered 2020-08-01T14:15:35Z