运行php脚本作为守护进程

时间:2010-01-10 10:06:50

标签: php linux unix daemon

我需要运行一个php脚本作为守护程序进程(等待指令并做一些事情)。 cron job不会为我做,因为一旦指令到达就需要采取行动。我知道由于内存管理问题,PHP实际上不是守护进程的最佳选择,但由于各种原因,我必须在这种情况下使用PHP。我遇到了一个名为守护进程(http://libslack.org/daemon)的libslack工具,它似乎可以帮助我管理守护程序进程,但是在过去的5年中没有任何更新,所以我想知道你是否知道其他一些适用于我的情况。任何信息都将非常感激。

15 个答案:

答案 0 :(得分:159)

您可以使用

从命令行(即bash)启动PHP脚本

nohup php myscript.php &

&将您的流程置于后台。

编辑:
是的,有一些缺点,但无法控制?那是错的。
一个简单的kill processid会阻止它。它仍然是最好,最简单的解决方案。

答案 1 :(得分:154)

另一种选择是使用Upstart。它最初是为Ubuntu开发的(默认情况下附带它),但它适用于所有Linux发行版。

此方法类似于Supervisorddaemontools,因为它会在系统启动时自动启动守护程序,并在脚本完成时重新生成。

如何设置:

/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,我从中学习了这项技术。

答案 2 :(得分:49)

使用新的systemd,您可以创建服务。

您必须在/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脚本应该有一种&#34;循环&#34;继续跑步。

<?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例程应该在一个循环中执行一次(比如diggest),你可以使用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

如果您选择了这些选项,则应将KillMode更改为mixed进程,bash(main)和PHP(子)被杀死。

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.

  

注意:每次更改&#34; myphpdaemon.service&#34;你必须   运行`systemctl daemon-reload&#39;,但是如果你不这样做就会担心,它会   在需要时发出警报。

答案 3 :(得分:47)

如果可以 - 抓取Advanced Programming in the UNIX Environment的副本。整个第13章专门用于守护程序编程。示例在C中,但您需要的所有函数都包含PHP中的包装器(基本上是pcntlposix扩展名。)

简而言之 - 编写一个守护进程(这只适用于基于* nix的OS-es - Windows使用服务)是这样的:

  1. 致电umask(0)以阻止权限问题。
  2. fork()并让父退出。
  3. 致电setsid()
  4. SIGHUP的设置信号处理(通常忽略或用于指示守护程序重新加载其配置)和SIGTERM(告诉进程正常退出)。
  5. 再次
  6. fork()并让父退出。
  7. 使用chdir()更改当前工作目录。
  8. fclose() stdinstdoutstderr并且不写信给他们。正确的方法是将它们重定向到/dev/null或文件,但我找不到在PHP中执行此操作的方法。当您启动守护程序以使用shell重定向它们时(您可能必须自己了解如何执行此操作,我不知道:)。
  9. 做你的工作!
  10. 此外,由于您使用的是PHP,请注意循环引用,因为在PHP 5.3之前的PHP垃圾收集器无法收集这些引用,并且该进程将内存泄漏,直到它最终崩溃。

答案 4 :(得分:24)

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

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

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

您可以在http://cr.yp.to/daemontools.html处查看。

编辑:快速的功能列表。

  • 重启时自动启动守护程序
  • 失败时自动重启dameon
  • 为您处理记录,包括翻转和修剪
  • 管理界面:'svc'和'svstat'
  • UNIX友好(对每个人来说都不是一个加分)

答案 5 :(得分:14)

你可以

  1. 使用nohup作为Henrik的建议。
  2. 使用screen并将PHP程序作为常规进程运行。与使用nohup相比,这可以提供更多控制。
  3. 使用像http://supervisord.org/这样的守护进程(它是用Python编写的,但可以对任何命令行程序进行守护,并为您提供远程控制来管理它)。
  4. 像Emil建议的那样编写你自己的守护程序封装器,但这对IMO来说太过分了。
  5. 我建议使用最简单的方法(我认为是屏幕)然后如果你想要更多的功能或功能,请转向更复杂的方法。

答案 6 :(得分:11)

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

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

老实说,我认为最好的办法是使用nohup创建一个Damon进程。 nohup允许命令在用户注销后继续执行:

nohup php myscript.php &
然而,有一个非常严重的问题。正如你所说,PHP的内存管理器是完全垃圾,它是在假设脚本只执行几秒然后存在的情况下构建的。您的PHP脚本将在几天后开始使用GIGABYTES内存。您还必须创建一个每12或24小时运行一次的cron脚本,这样可以杀死并重新生成您的php脚本,如下所示:

killall -3 php
nohup php myscript.php &

但是如果脚本正在工作中呢?杀-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脚本的一个副本。

祝你好运!

答案 7 :(得分:10)

Kevin van Zonneveld wrote a very nice detailed article on this,在他的例子中,他使用System_Daemon PEAR package(2009-09-02上次发布日期)。

答案 8 :(得分:6)

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

这是一个面向对象的守护程序库。它内置了对日志记录和错误恢复等内容的支持,并且支持创建后台工作程序。

答案 9 :(得分:3)

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

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

完全支持Linux(通过sysvinit),但也启动了Windows NT服务和Mac OSX。

如果您只需要Linux,那么此处介绍的其他几种解决方案效果不错,具体取决于风格。现在还有Upstart和systemd,它们都有回溯到sysvinit脚本。但使用PHP的一半原因是它本质上是跨平台的,因此用该语言编写的代码很有可能在任何地方工作。当某些外部本机操作系统级别的方面进入图片(例如系统服务)时会出现缺陷,但是您会遇到大多数脚本语言的问题。

尝试捕获信号,因为这里有人在PHP用户区建议不是一个好主意。仔细阅读pcntl_signal()上的文档,您将很快了解到PHP使用一些相当令人不愉快的方法处理信号(特别是,&#39; ticks&#39;),这些方法为进程中很少见到的东西咀嚼了一堆循环(即信号)。 PHP中的信号处理在POSIX平台上也几乎不可用,并且支持因PHP的版本而异。它最初听起来像是一个不错的解决方案,但它确实没有真正有用。

随着时间的推移,PHP在内存泄漏问题上的表现也越来越好。你仍然需要小心(DOM XML解析器往往会泄漏)但我现在很少看到失控的进程,而且与以前的日子相比,PHP bug跟踪器相当安静。

答案 10 :(得分:2)

我一直在寻找一种简单的解决方案,而无需安装额外的东西,并且与允许 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

重启ChatServer.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 作业将在重新启动或崩溃时启动服务器

答案 11 :(得分:1)

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

我将它上传到github,随意使用它:https://github.com/sinasalek/EasyDeamonizer

<强> EasyDeamonizer

只需查看您的应用程序(启动,重启,登录,监控等)。一个通用脚本,以确保您的应用程序保持正常运行。故意它使用pid / lock文件的进程名称instread来防止其所有副作用并使脚本保持尽可能简单和尽头,因此即使EasyDaemonizer本身重新启动它也总是有效。 特征

  • 启动应用程序并为每次启动启用自定义延迟
  • 确保只有一个实例正在运行
  • 监视CPU使用率并在达到定义的阈值时自动重新启动应用程序
  • 将EasyDeamonizer设置为通过cron运行,如果因任何原因暂停,则再次运行
  • 记录其活动

答案 12 :(得分:1)

扩展 Emil Ivaov 答案,您可以执行以下操作以关闭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超越网络

的书中阅读了此书

答案 13 :(得分:0)

我编写并部署了一个简单的php-daemon,代码在这里

https://github.com/jmullee/PhpUnixDaemon

功能:权限下降,信号处理,记录

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

答案 14 :(得分:0)

您可以在这里查看pm2,http://pm2.keymetrics.io/

在要处理的php脚本中创建一个ssh文件,例如worker.sh。

worker.sh

php /path/myscript.php

守护进程启动

pm2 start worker.sh

干杯,就是这样。