作为守护进程运行 php 脚本

我需要运行一个 php 脚本作为守护进程(等待指示和做事情)。Cron job 不会为我这样做,因为行动需要尽快采取的指示到达。我知道由于内存管理问题,PHP 并不是守护进程的最佳选择,但是由于各种原因,在这种情况下我必须使用 PHP。我偶然发现一个名为 Daemon (http://libslack.org/daemon)的工具,它似乎可以帮助我管理守护进程,但是在过去的5年里没有任何更新,所以我想知道你是否知道一些其他的替代方案适合我的情况。任何信息我们都会很感激的。

165949 次浏览

您可以使用以下命令从命令行(即 bash)启动 php 脚本

nohup php myscript.php &

&把你的过程放在后台。

编辑:
是的,有一些缺点,但不可能控制? 这只是错误的。
一个简单的 kill processid就可以阻止它,而且它仍然是最好和最简单的解决方案。

如果可以的话,拿一份 UNIX环境高级编程。整个第13章都是关于守护进程编程的。示例使用 C 语言,但所有需要的函数都使用 PHP 包装器(基本上是 PcntlPosix扩展)。

简而言之——编写守护进程(只有在基于 * nix 的 OS-es-Windows 使用服务的情况下才可能实现)是这样的:

  1. 调用 umask(0)以防止权限问题。
  2. fork() 并具有父出口。
  3. 呼叫 setsid()
  4. 设置 SIGHUPSIGTERM的信号处理(通常忽略或用于向守护进程发出重新加载其配置的信号)和 SIGTERM(告诉进程优雅地退出)。
  5. 再次 fork(),并有父退出。
  6. chdir()更改当前工作目录。
  7. fclose() stdinstdoutstderr,不要给他们写信。正确的方法是将它们重定向到 /dev/null或者一个文件,但是我找不到在 PHP 中实现这一点的方法。当您启动守护进程使用 shell 重定向它们时,这是可能的(您必须自己找到如何做到这一点,我不知道:)。
  8. 做你的工作!

另外,由于您正在使用 PHP,因此要小心循环引用,因为 PHP 5.3之前的 PHP 垃圾收集器无法收集这些引用,并且进程将发生内存泄漏,直到最终崩溃。

Kevin van Zonneveld 写了一篇非常详细的文章,在他的例子中他使用了 System_Daemon梨包(上次发布日期是2009-09-02)。

解决这个问题的方法不止一种。

我不知道具体细节,但也许有另一种方法来触发 PHP 过程。例如,如果需要基于 SQL 数据库中的事件运行代码,可以设置一个触发器来执行脚本。这在 PostgreSQL: http://www.postgresql.org/docs/current/static/external-pl.html下很容易做到。

老实说,我认为你最好的办法是用 nohup 创建一个 Damon 过程。Nohup 允许即使在用户已经注销之后,命令仍然继续执行:

nohup php myscript.php &

然而,有一个非常严重的问题。正如您所说,PHP 的内存管理器完全是垃圾,构建它的假设是,脚本只执行几秒钟,然后就存在了。PHP 脚本将在几天后开始使用 GIGABYTES 内存。您还必须创建一个 cron 脚本,该脚本每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”)打开一个文件; 。只有一个进程可以对一个文件有写锁,因此使用这个锁可以确保只有一个 PHP 脚本副本正在运行。

祝你好运!

我运行了大量 PHP 守护进程。

我同意你的看法,PHP 不是最好的(甚至不是一个好的)语言,但守护进程与面向 Web 的组件共享代码,所以总的来说,它是一个很好的解决方案。

我们使用 daemontools。它聪明、干净、可靠。事实上,我们使用它来运行所有的守护进程。

你可以去 http://cr.yp.to/daemontools.html看看。

编辑: 一个特性的快速列表。

  • 在重新启动时自动启动守护进程
  • 故障时自动重启 Dameon
  • 日志记录将为您处理,包括滚动和修剪
  • 管理界面: ‘ svc’和‘ svstat’
  • 对 UNIX 友好(也许对每个人来说都不是好事)

你可以的

  1. 按照亨里克的建议使用 nohup
  2. 使用 screen并将 PHP 程序作为其中的常规进程运行。这比使用 nohup提供了更多的控制。
  3. 使用像 http://supervisord.org/这样的守护程序(它是用 Python 编写的,但可以守护任何命令行程序,并提供远程控制来管理它)。
  4. 像埃米尔建议的那样编写自己的守护神包装,但这有点过了。

我建议使用最简单的方法(我认为是屏幕) ,然后如果你想要更多的特性或功能,就转向更复杂的方法。

看看 https://github.com/shaneharter/PHP-Daemon

这是一个面向对象的守护进程库。它内置了诸如日志记录和错误恢复之类的支持,并且它还支持创建后台工作者。

另一种选择是使用 暴发户。它最初是为 Ubuntu 开发的(默认情况下也是打包的) ,但是打算适用于所有 Linux 发行版。

这种方法类似于 主管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,我在那里学到了这项技术。

正如其他人已经提到的,作为守护进程运行 PHP 非常容易,并且可以使用单行命令完成。但实际问题是如何保持它的运行和管理。很久以前我也遇到过同样的问题,尽管已经有很多解决方案,但大多数都有很多依赖项,或者很难使用,不适合基本用法。我编写了一个 shell 脚本,它可以管理包括 PHP cli 脚本在内的任何进程/应用程序。可以将其设置为 cronjob 以启动应用程序,并将其包含在应用程序中并对其进行管理。如果它再次执行,例如通过相同的 cronjob,它将检查应用程序是否正在运行,如果正在运行,则只需退出,并让其前一个实例继续管理应用程序。

我把它上传到了 github,你可以随意使用: https://github.com/sinasalek/EasyDeamonizer

轻松杀手

只需监视您的应用程序(启动、重新启动、记录、监视等)。一个通用脚本,以确保应用程序保持正常运行。它有意使用进程名替代 pid/lock 文件,以防止所有副作用,并使脚本尽可能简单和直观,因此即使 EasyDaemonizer 本身重新启动,它也始终能够工作。 特征

  • 启动应用程序,并可选地为每次启动定制延迟
  • 确保只有一个实例正在运行
  • 监视 CPU 使用情况,并在应用程序达到所定义的阈值时自动重新启动
  • 将 EasyDeamonizer 设置为通过 cron 运行,以便在出于任何原因停止时再次运行
  • 记录它的活动

我最近需要一个跨平台的解决方案(Windows、 Mac 和 Linux)来解决将 PHP 脚本作为守护进程运行的问题。我通过编写自己的基于 C + + 的解决方案和制作二进制文件来解决这个问题:

Https://github.com/cubiclesoft/service-manager/

完全支持 Linux (通过 sysvinit) ,也支持 WindowsNT 服务和 MacOSX 启动。

如果您只是需要 Linux,那么这里提供的其他几个解决方案工作得很好,并且取决于它们的风格。现在还有 Upstart 和 systemd,它们可以回退到 sysvinit 脚本。但是使用 PHP 的一半意义在于它本质上是跨平台的,所以用这种语言编写的代码很有可能在任何地方都能正常工作。当某些外部本机操作系统级别的方面(如系统服务)出现时,就会出现缺陷,但是在大多数脚本语言中都会遇到这个问题。

试图像某人在 PHP 用户界面中建议的那样捕捉信号并不是一个好主意。仔细阅读关于 pcntl_signal()的文档,您将很快了解到 PHP 使用一些相当不愉快的方法(特别是‘ ticks’)来处理信号,这些方法会为进程很少看到的东西(即信号)消耗大量周期。PHP 中的信号处理也仅仅在 POSIX 平台上可用,并且基于 PHP 版本的不同支持也有所不同。它最初听起来像是一个不错的解决方案,但它并不是真正有用的。

随着时间的推移,PHP 对内存泄漏问题的处理也越来越好。您仍然需要小心(DOM XML 解析器仍然倾向于泄漏) ,但是我现在很少看到失控的进程,而且与过去相比,PHP bug 跟踪器非常安静。

使用新的 系统,您可以创建一个服务。

您必须在 /etc/systemd/system/中创建一个文件或 Symlink,例如 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 -S 127.0.0.1:<port>的 PHP 本机服务器,或者将其作为脚本运行。使用 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 脚本来调用 Systemd 服务文件,而不是直接使用 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为 process,那么 bash (main)和 PHP (child)将被终止。

ExecStart=/app/phpservice/runner.sh phpfile parameter  > /dev/null 2>/dev/null
KillMode=process

This method also is effective if you're facing a memory leak.

注意: 每次更改“ myphpdaemon.service”时,都必须这样做 运行‘ systemctl daemon-reload’,但是如果您不这样做,那么它将会 需要时通知。

我编写并部署了一个简单的 php 守护进程,代码在线

Https://github.com/jmullee/phpunixdaemon

特性: 特权丢失,信号处理,日志记录

我在一个队列处理程序中使用了它(用例: 从一个网页触发一个冗长的操作,而不需要让生成页面的 php 等待,即启动一个异步操作) Https://github.com/jmullee/phpipcmessagequeue

你可以检查 pm2这里是 http://pm2.keymetrics.io/

创建一个 ssh 文件,比如将 worker.sh 放入您要处理的 php 脚本中。

工人,嘘

php /path/myscript.php

灵兽启动

pm2 start worker.sh

干杯,就这样。

扩展 埃米尔 · 伊万诺夫应答,您可以执行以下操作来在 php 中关闭 STDIN、 STDOUT 和 STDERROR

if (!fclose(STDIN)) {
exit("Could not close STDIN");
}


if (!fclose(STDOUT)) {
exit("Could not close STDOUT");
}


if (!fclose(STDERR)) {
exit("Could not close STDERR");
}


$STDIN = fopen('/dev/null', 'r');
$STDOUT = fopen('/dev/null', 'w');
$STDERR = fopen('/var/log/our_error.log', 'wb');

基本上就是关闭文本数据流,这样 PHP 就没有地方可以写了。以下 fopen调用将标准 IO 设置为 /dev/null

这是我从 Rob Aley-超越网络的 PHP的书上读到的

我一直在寻找一个简单的解决方案,不需要安装额外的东西,也不需要与允许 SSH 访问的通用主机兼容。

我最终为我的聊天服务器安装了以下设置:

-rwxr-xr-x  1 crazypoems psacln   309 ene 30 14:01 checkChatServerRunning.sh
-rw-r--r--  1 crazypoems psacln  3018 ene 30 13:12 class.chathandler.php
-rw-r--r--  1 crazypoems psacln    29 ene 30 14:05 cron.log
-rw-r--r--  1 crazypoems psacln  2560 ene 29 08:04 index.php
-rw-r--r--  1 crazypoems psacln  2672 ene 30 13:29 php-socket.php
-rwxr-xr-x  1 crazypoems psacln   310 ene 30 14:04 restartChatServer.sh
-rwxr-xr-x  1 crazypoems psacln   122 ene 30 13:28 startChatServer.sh
-rwxr-xr-x  1 crazypoems psacln   224 ene 30 13:56 stopChatServer.sh

还有剧本:

Startchatserver.sh

#!/bin/bash
nohup /opt/plesk/php/5.6/bin/php -q /var/www/vhosts/crazypoems.org/httpdocs/chat/php-socket.php > /dev/null &

Stopchatserver.sh

#!/bin/bash
PID=`ps -eaf | grep '/var/www/vhosts/crazypoems.org/httpdocs/chat/php-socket.php' | grep -v grep | awk '{print $2}'`
if [[ "" !=  "$PID" ]]; then
echo "killing $PID"
kill -9 $PID
else
echo "not running"
fi

Restartchatserver.sh

#!/bin/bash
PID=`ps -eaf | grep '/var/www/vhosts/crazypoems.org/httpdocs/chat/php-socket.php' | grep -v grep | awk '{print $2}'`
if [[ "" !=  "$PID" ]]; then
echo "killing $PID"
kill -9 $PID
else
echo "not running"
fi
echo "Starting again"
/var/www/vhosts/crazypoems.org/httpdocs/chat/startChatServer.sh

最后但并非最不重要的是,我把最后一个脚本放在 cron 作业上,检查“聊天服务器是否正在运行”,如果没有,启动它:

每时每刻都在做杂活

-bash-4.1$ crontab -l
*       *       *       *       * /var/www/vhosts/crazypoems.org/httpdocs/chat/checkChatServerRunning.sh > /var/www/vhosts/crazypoems.org/httpdocs/chat/cron.log

Checkchatserverrunning.sh

#!/bin/bash
PID=`ps -eaf | grep '/var/www/vhosts/crazypoems.org/httpdocs/chat/php-socket.php' | grep -v grep | awk '{print $2}'`
if [[ "" !=  "$PID" ]]; then
echo "Chat server running on $PID"
else
echo "Not running, going to start it"
/var/www/vhosts/crazypoems.org/httpdocs/chat/startChatServer.sh
fi

在这个设置中:

  • 我可以在需要的时候用脚本手动控制服务(例如: 维护)
  • Cron 作业将在重启或崩溃时启动服务器