我需要将一个php脚本作为守护进程运行(等待指令并执行操作)。cron作业无法满足我的需求,因为需要在指令到达时立即执行操作。我知道由于内存管理问题,PHP不是实现守护进程的最佳选择,但由于某些原因,在这种情况下我必须使用PHP。我发现了一个名为Daemon的libslack工具(http://libslack.org/daemon),它似乎可以帮助我管理守护进程,但是在过去的5年中没有任何更新,所以我想知道您是否知道其他适合我情况的替代品。非常感谢您提供的任何信息。
我需要将一个php脚本作为守护进程运行(等待指令并执行操作)。cron作业无法满足我的需求,因为需要在指令到达时立即执行操作。我知道由于内存管理问题,PHP不是实现守护进程的最佳选择,但由于某些原因,在这种情况下我必须使用PHP。我发现了一个名为Daemon的libslack工具(http://libslack.org/daemon),它似乎可以帮助我管理守护进程,但是在过去的5年中没有任何更新,所以我想知道您是否知道其他适合我情况的替代品。非常感谢您提供的任何信息。
nohup php myscript.php &
,其中&
将进程放入后台。kill processid
将停止它。而且这仍然是最好和最简单的解决方案。nohup
和&
似乎做的是同样的事情:将启动的进程与当前shell实例分离。我为什么需要它们两个?我不能只用php myscript.php &
或者nohup myscript.php
吗?谢谢。 - nourdinenohup php myscript.php > myscript.log &
。 - Mischa另一种选择是使用Upstart。它最初是为Ubuntu开发的(并默认随其打包),但旨在适用于所有Linux发行版。
这种方法类似于Supervisord和daemontools,因为它会在系统启动时自动启动守护进程,并在脚本完成后重新启动。
在/etc/init/
下创建一个新的脚本文件myphpworker.conf
。以下是一个示例:
# Info
description "My PHP Worker"
author "Jonathan"
# Events
start on startup
stop on shutdown
# Automatically respawn
respawn
respawn limit 20 5
# Run the script!
# Note, in this example, if your PHP script returns
# the string "ERROR", the daemon will stop itself.
script
[ $(exec /usr/bin/php -f /path/to/your/script.php) = 'ERROR' ] && ( stop; exit 1; )
end script
sudo service myphpworker start
sudo service myphpworker stop
sudo service myphpworker status
非常感谢Kevin van Zonneveld,从他那里学到了这个技巧。
sudo service myphpworker start/stop/status
只适用于在/etc/init.d
目录下的服务,而不适用于upstart服务。@matt-sich似乎已经找到了正确的语法。另一个选项是使用Gearman或Resque,它允许后台处理和守护进程化。 - ckm/etc/systemd/system/
中创建一个文件或符号链接,例如myphpdaemon.service,并放置像这样的内容,myphpdaemon将是服务的名称:[Unit]
Description=My PHP Daemon Service
#May your script needs MySQL or other services to run, eg. MySQL Memcached
Requires=mysqld.service memcached.service
After=mysqld.service memcached.service
[Service]
User=root
Type=simple
TimeoutSec=0
PIDFile=/var/run/myphpdaemon.pid
ExecStart=/usr/bin/php -f /srv/www/myphpdaemon.php arg1 arg2> /dev/null 2>/dev/null
#ExecStop=/bin/kill -HUP $MAINPID #It's the default you can change whats happens on stop command
#ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s
StandardOutput=null #If you don't want to make toms of logs you can set it null if you sent a file or some other options it will send all PHP output to this one.
StandardError=/var/log/myphpdaemon.log
[Install]
WantedBy=default.target
systemctl <start|status|restart|stop|enable> myphpdaemon
您也可以使用PHP本地服务器,命令为:php -S 127.0.0.1:<port>
,或者将其作为脚本运行。如果使用PHP脚本,您需要编写一个“无限循环”以保持服务持续运行。<?php
gc_enable();//
while (!connection_aborted() || PHP_SAPI == "cli") {
//Code Logic
//sleep and usleep could be useful
if (PHP_SAPI == "cli") {
if (rand(5, 100) % 5 == 0) {
gc_collect_cycles(); //Forces collection of any existing garbage cycles
}
}
}
工作示例:
[Unit]
Description=PHP APP Sync Service
Requires=mysqld.service memcached.service
After=mysqld.service memcached.service
[Service]
User=root
Type=simple
TimeoutSec=0
PIDFile=/var/run/php_app_sync.pid
ExecStart=/bin/sh -c '/usr/bin/php -f /var/www/app/private/server/cron/app_sync.php 2>&1 > /var/log/app_sync.log'
KillMode=mixed
Restart=on-failure
RestartSec=42s
[Install]
WantedBy=default.target
如果您的PHP例程应该在循环中执行一次(例如挖掘),则可以使用shell或bash脚本来代替直接调用PHP,例如:
#!/usr/bin/env bash
script_path="/app/services/"
while [ : ]
do
# clear
php -f "$script_path"${1}".php" fixedparameter ${2} > /dev/null 2>/dev/null
sleep 1
done
mixed
,以便进程、bash(main)和PHP(child)被杀死。ExecStart=/app/phpservice/runner.sh phpfile parameter > /dev/null 2>/dev/null
KillMode=process
如果你遇到了内存泄漏问题,这种方法也很有效。
注意:每次更改“myphpdaemon.service”时,必须运行`systemctl daemon-reload’,但是如果您不这样做,当需要时会发出警报。
systemctl status <your_service_name> -l
的输出,它会给你一个线索,告诉你正在发生什么。 - LeonanCarvalhoumask(0)
避免权限问题。fork()
并让父进程退出。setsid()
。SIGHUP
信号处理(通常忽略或用于向守护进程发送重新加载配置的信号)和 SIGTERM
信号处理(告诉进程优雅地退出)。fork()
再次并让父进程退出。chdir()
更改当前工作目录。fclose()
stdin
、stdout
和 stderr
并不写入它们。正确的方法是将它们重定向到 /dev/null
或文件,但我找不到在 PHP 中如何实现。当启动守护进程时,可以使用 shell 重定向它们(你需要自己找出如何做到这一点,我不知道 :)另外,由于您正在使用PHP,请注意循环引用,因为在PHP 5.3之前的版本中,PHP垃圾回收器无法收集这些引用,进程将内存泄漏,直到最终崩溃。
init
的子进程,并将在 init
的定期 wait
系统调用执行时得到清理。 - tonix我运行了大量的PHP守护进程。
我同意你的观点,即PHP不是用于此的最佳(甚至是好的)语言,但是这些守护进程与面向Web的组件共享代码,因此总体而言,这对我们来说是一个不错的解决方案。
我们使用daemontools来实现这一点。它很聪明,干净且可靠。事实上,我们使用它来运行所有的守护进程。
您可以在http://cr.yp.to/daemontools.html上查看。
编辑:功能列表如下。
nohup
。screen
并将你的 PHP 程序作为常规进程在其中运行。这比使用 nohup
更加灵活。解决这个问题的方法不止一种。
我不知道具体情况,但也许有另一种触发PHP进程的方式。例如,如果您需要根据SQL数据库中的事件运行代码,则可以设置触发器来执行脚本。这在PostgreSQL下非常容易实现:http://www.postgresql.org/docs/current/static/external-pl.html。
老实说,我认为最好的办法是使用nohup创建守护进程。 nohup允许命令在用户注销后继续执行:
nohup php myscript.php &
然而有一个非常严重的问题。正如你所说,PHP的内存管理器非常糟糕,它是建立在假设脚本只执行几秒钟然后退出的基础上的。你的PHP脚本在仅仅几天之后就会开始使用千兆字节的内存。你还必须创建一个定时脚本,每12或24个小时运行一次,像这样终止和重新启动你的PHP脚本:
killall -3 php
nohup php myscript.php &
但是,如果脚本正在执行任务呢?kill -3 是中断命令,就像在 CLI 上使用 ctrl+c 一样。您的 PHP 脚本可以使用 PHP pcntl 库捕获此中断并优雅地退出:http://php.oregonstate.edu/manual/en/function.pcntl-signal.php
以下是示例:
function clean_up() {
GLOBAL $lock;
mysql_close();
fclose($lock)
exit();
}
pcntl_signal(SIGINT, 'clean_up');
$lock的背后思想是PHP脚本可以使用fopen("file","w");打开文件。同一时间只有一个进程能够在文件上获取写锁,因此使用$lock可以确保只运行一个副本的PHP脚本。
祝好运!
Kevin van Zonneveld在他的一篇非常详细的文章中讲解了如何创建PHP守护进程,在他的示例中,他使用了System_Daemon
PEAR包(最后发布日期为2009-09-02)。
请查看https://github.com/shaneharter/PHP-Daemon
这是一个面向对象的守护进程库。它内置了日志记录和错误恢复等功能,并支持创建后台工作程序。
我最近需要解决在多个平台(Windows、Mac和Linux)上运行PHP脚本作为守护进程的问题。我通过编写自己的基于C++的解决方案并制作二进制文件来解决这个问题:
https://github.com/cubiclesoft/service-manager/
完全支持Linux(通过sysvinit),同时也支持Windows NT服务和Mac OSX launchd。
如果您只需要Linux,则此处介绍的其他解决方案中的一些都足够好,具体取决于版本。现在还有Upstart和systemd,它们具有回退到sysvinit脚本的功能。但是使用PHP的好处之一是它天生就是跨平台的语言,因此使用该语言编写的代码很有可能在任何地方原样工作。当涉及到某些外部本机操作系统级别的因素(如系统服务)时,缺陷开始显现,但大多数脚本语言都会遇到这个问题。
像某些人在这里建议的那样,在PHP用户空间中捕获信号并不是一个好主意。仔细阅读pcntl_signal()
的文档,您将很快了解到PHP使用一些相当不愉快的方法(具体来说是“ticks”)处理信号,这会消耗大量的周期,用于进程很少看到的东西(即信号)。PHP中的信号处理在POSIX平台上也仅仅是基本可用的,并且支持因PHP版本而异。这听起来最初像个不错的解决方案,但它远远不能真正有用。
随着时间的推移,PHP在内存泄漏问题方面也变得越来越好。您仍然需要小心(DOM XML解析器仍然容易泄漏),但我现在很少看到失控的进程,相比以前,PHP错误跟踪器也比较安静。