将PHP脚本作为守护进程运行


154

我需要将php脚本作为守护进程运行(等待说明并执行操作)。cron工作不会为我做这件事,因为指令一到达就需要立即采取措施。我知道由于内存管理问题,PHP并不是守护进程的最佳选择,但是由于种种原因,在这种情况下我必须使用PHP。我遇到了libslack的一个名为Daemon的工具(http://libslack.org/daemon),它似乎可以帮助我管理守护程序进程,但是最近5年没有任何更新,所以我想知道您是否知道一些其他适合我情况的选择。任何信息将不胜感激。


2
请检查我的答案。感谢
Henrik P. Hessel


1
我碰到了这篇文章gonzalo123.com/2010/05/23/…我相信它既放松又稳定。
Teson

Answers:


167

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

nohup php myscript.php &

&把你的程序在后台运行。

编辑:
是的,有一些缺点,但无法控制吗?那是错误的。
一个简单的kill processid将停止它。而且它仍然是最好和最简单的解决方案。


如果终端存在,则该过程不会退出。这就是为什么存在“ nohup”命令的原因。多年来,我一直在所有这样的服务器上使用PHP脚本作为守护程序。可能有更好的解决方案,但这是最快的解决方案。
CDR 2010年

27
如果守护程序失败,它将不会重新启动,并且根本没有简单的方法来管理该守护程序。
Phil Wallach 2010年

6
我同意这里所说的话-这是一个糟糕的解决方案。您应该出于以下两个原因创建一个初始化脚本:1)初始化脚本在启动时自动启动2)您可以使用启动/停止/重启命令来管理守护程序。这是servefault的示例:serverfault.com/questions/229759/…–
Simian

1
嗨,伙计们……在我看来,它nohup&做同样的事情:将启动的进程与shell的当前实例分离。为什么我都需要它们?我可以不只是做php myscript.php &还是nohup myscript.php?? 谢谢
诺丁2014年

1
如果脚本写入标准输出(通过echo或var_dump),则可以使用如下日志文件捕获此信息:nohup php myscript.php > myscript.log &
Mischa

167

另一种选择是使用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
喜欢这个。只是想知道,是否可能有多个并发工人?我只是有一个工人不够的问题。
曼努埃尔

1
它会在系统启动时自动运行吗?
slier 2014年

2
Sudo“ service myphpworker start”对我不起作用。我使用了“ sudo start myphpworker”,它的运行非常完美
Matt Sich

3
@Pradeepta那是因为帖子中有一个错误-我不确定是什么(并且还没有测试过),但是我认为这sudo service myphpworker start/stop/status仅适用于/etc/init.d非新贵服务中的服务。@ matt-sich似乎发现了正确的语法。另一种选择是使用Gearman或Resque,它可以进行后台处理和除氨。
ckm

3
Ubuntu本身正在使用systemd代替新贵:zdnet.com/article/after-linux-civil-war-ubuntu-to-adopt-systemd
Kzqai

72

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

你必须创建一个文件或符号连接/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
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脚本而不是直接调用PHP来将其调用到systemd服务文件中,例如:

#!/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

如果你选择了这些选项,你应该改变KillModemixed以流程时,bash(主)和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.

注意:每次更改“ myphpdaemon.service”时,都必须运行“ systemctl daemon-reload”,但是请放心,如果不这样做,将在需要时向您发出警报。


7
被低估的答案。你有我的+1。
Gergely Lukacsy

2
太棒了 希望我们也能得到解答,因为这不应该埋在本页上。
贾斯汀

1
您应该检查systemctl status <your_service_name> -l输出,它将为您提供线索。
LeonanCarvalho

1
@LeandroTupone MySQL和Memcached演示了如何使用服务依赖关系,这是不必要的。
LeonanCarvalho

3
这应该真的可以代替现在已经接受的答案,因为现在是2019
。–香料

47

如果可以,请在UNIX环境中获取Advanced Programming的副本。整个第13章致力于守护程序编程。示例在C中,但是您需要的所有功能在PHP中都有包装器(基本上是pcntlposix扩展名)。

简而言之-编写守护程序(仅在基于* nix的OS-es上才可能使用-Windows使用服务)是这样的:

  1. 致电umask(0)以防止权限问题。
  2. fork() 并有父母出口。
  3. 致电setsid()
  4. 的设置信号处理SIGHUP(通常会被忽略或用于向守护程序发送信号以重新加载其配置)和SIGTERM(以告知进程正常退出)。
  5. fork() 再次并有父母出口。
  6. 使用更改当前工作目录chdir()
  7. fclose() stdinstdout并且stderr不给他们写信。正确的方法是将那些重定向到/dev/null文件或文件,但是我找不到在PHP中执行此操作的方法。当您启动守护程序以使用shell重定向它们时,这是可能的(您必须了解如何做到这一点,我不知道:)。
  8. 做你的工作!

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


1
谢谢(你的)信息。看起来libslack的守护程序几乎完成了您所提到的所有准备工作。我想现在我会坚持下去,直到找到其他好的选择。
贝尔

1
找到这篇文章后,希望将代码复制并粘贴到无法关闭stdin等的cr脚的旧应用程序中的做法令人失望。:p
ThiefMaster

1
为什么又要(5)fork()?
TheFox

为我工作-干得好!
Gautam Sharma 2015年

对于将来的读者,为什么要两次分叉:stackoverflow.com/questions/881388/…-TL ; DR:防止僵尸。
Ghedipunk 2015年

24

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

我同意您的看法,PHP并不是实现此目的的最佳(甚至不是一种好的)语言,但是守护程序与面向Web的组件共享代码,因此总体而言,这对我们来说是一个很好的解决方案。

我们为此使用daemontools。它是智能,清洁和可靠的。实际上,我们使用它来运行所有守护程序。

您可以在http://cr.yp.to/daemontools.html上进行检查。

编辑:功能的快速列表。

  • 重新启动时自动启动守护程序
  • 失败时自动重启dameon
  • 为您处理日志,包括过渡和修剪
  • 管理界面:“ svc”和“ svstat”
  • UNIX友好(也许不是每个人都喜欢)

也可以从存储库中安装,例如在apt中!
卡扎伊2017年

14

您可以

  1. nohup按照Henrik的建议使用。
  2. 在其中使用screen和运行PHP程序作为常规过程。这给您比使用更多的控制权nohup
  3. 使用诸如http://supervisord.org/的守护程序(它是用Python编写的,但是可以守护任何命令行程序并为您提供远程控件来对其进行管理)。
  4. 像Emil所建议的那样编写自己的守护程序包装程序,但这太过分了。

我建议最简单的方法(我认为是屏幕),然后,如果您想要更多的功能,请转到更复杂的方法。


您能提供类似的监督配置吗?
Alix Axel 2014年

11

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

我不知道具体细节,但是也许还有另一种触发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脚本的一个副本。

祝好运!




3

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

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

全面支持Linux(通过sysvinit),以及Windows NT服务和Mac OSX。

如果您只需要Linux,那么这里介绍的其他两个解决方案也可以很好地工作,并且取决于风格。这些天还出现了Upstart和systemd,它们回退到sysvinit脚本。但是使用PHP的一半是,它本质上是跨平台的,因此用该语言编写的代码有很大的机会按原样工作。当某些外部本机OS方面输入图片(例如系统服务)时,就会开始出现缺陷,但是大多数脚本语言都会遇到这个问题。

试图按照PHP用户国的建议在这里捕获信号不是一个好主意。pcntl_signal()仔细阅读文档,您将很快了解到PHP使用一些相当不愉快的方法(特别是“滴答声”)来处理信号,这些方法会消耗大量周期来处理一些进程(例如信号)很少见的事情。PHP中的信号处理也仅在POSIX平台上才可用,并且支持因PHP版本而异。最初听起来像是一个不错的解决方案,但还远远不够真正有用。

随着时间的流逝,PHP在内存泄漏问题上也变得越来越好。您仍然必须小心(DOM XML解析器仍会泄漏),但是这些天我很少看到失控的过程,与过去相比,PHP Bug跟踪器非常安静。


1

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

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

EasyDeamonizer

只需监视您的应用程序(启动,重新启动,日志,监视等)。一个通用脚本,以确保您的应用程序正常运行。故意使用pid / lock文件的进程名instread来防止其所有副作用,并使脚本尽可能简单和合理,因此,即使重新启动EasyDaemonizer本身,它也始终有效。特征

  • 启动应用程序,并且可以选择每次启动的自定义延迟
  • 确保只有一个实例正在运行
  • 监控CPU使用率,并在达到定义的阈值时自动重新启动应用程序
  • 将EasyDeamonizer设置为通过cron运行,以在由于某种原因而停止运行时再次运行它
  • 记录其活动

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的书中阅读了此书-Web之外的PHP



0

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

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

工人

php /path/myscript.php

守护程序启动

pm2 start worker.sh

干杯,就是这样。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.