关于linux:运行php脚本作为守护进程

Run php script as daemon process

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


通过使用

nohup php myscript.php &

&将您的进程置于后台。

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


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

这种方法与supervisor和daemontools类似,因为它在系统启动时自动启动守护进程,并在脚本完成时重新显示。

如何设置:

/etc/init/myphpworker.conf上创建新的脚本文件。下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 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

启动和停止守护进程:

1
2
sudo service myphpworker start
sudo service myphpworker stop

检查您的守护进程是否正在运行:

1
sudo service myphpworker status

谢谢

非常感谢凯文·范佐尼菲尔德,在那里我学到了这项技术。


如果可以的话-获取一份Unix环境中高级编程的副本。整个第13章都是关于守护程序编程的。示例在C中,但是您需要的所有函数都有PHP中的包装器(基本上是PCNTL和POSIX扩展)。

用几句话来说——编写一个守护进程(这只在基于*nix的OS ES上可以设置——Windows使用服务)如下所示:

  • 打电话给umask(0)防止权限问题。
  • fork()和父母退出。
  • 打电话给setsid()
  • SIGHUPSIGTERM的设置信号处理(通常被忽略或用于向守护进程发送重新加载其配置的信号)和(告诉进程优雅地退出)。
  • fork()再次退出。
  • chdir()改变当前工作方向。
  • fclose()stdinstdoutstderr不给他们写信。正确的方法是将它们重定向到/dev/null或文件,但在PHP中找不到这样做的方法。当您使用shell启动守护进程来重定向它们时是有可能的(我不知道,您必须了解自己如何做)。
  • 做你的工作!
  • 此外,由于您使用的是PHP,因此请注意循环引用,因为在php 5.3之前的php垃圾收集器无法收集这些引用,并且进程将发生内存泄漏,直到最终崩溃。


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

    您必须在/etc/systemd/system/中创建一个文件或一个符号链接,例如myphpdemon.service,并放置这样的内容,myphpdemon将是服务的名称:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    [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 myphpdaemon

    PHP脚本应该有一种"循环"来继续运行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?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
            }
        }
    }

    工作示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    [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,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #!/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到processes,bash(main)和php(child)将被终止。

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

    如果遇到内存泄漏,此方法也有效。

    Note: Every time that you change your"myphpdaemon.service" you must
    run `systemctl daemon-reload', but do worry if you not do, it will be
    prompted when is needed.


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

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

    我们为此使用daemontools。它智能、干净、可靠。实际上,我们使用它来运行所有守护进程。

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

    编辑:功能的快速列表。

    • 重新启动时自动启动守护程序
    • 失败时自动重新启动Dameon
    • 日志记录是为您处理的,包括滚动和修剪
    • 管理接口:"svc"和"svstat"
    • Unix友好型(可能不是每个人的优势)


    你可以

  • 按照亨里克的建议,使用nohup
  • 使用screen并在其中作为常规进程运行PHP程序。这比使用nohup提供了更多的控制。
  • 使用类似http://monitordord.org/的守护进程(它是用python编写的,但是可以守护任何命令行程序,并提供远程控制来管理它)。
  • 写下你自己的守护进程包装,就像埃米尔建议的那样,但在我看来这太过分了。
  • 我建议使用最简单的方法(在我看来是屏幕),如果您想要更多的功能或功能,请转到更复杂的方法。


    有多种方法可以解决这个问题。

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

    老实说,我认为你最好的选择是用nohup创建一个damon进程。nohup允许命令继续执行,即使用户已注销:

    1
    nohup php myscript.php &

    但是有一个非常严重的问题。正如您所说,PHP的内存管理器是完全垃圾,它是在假设脚本只执行几秒钟然后就存在的情况下构建的。您的PHP脚本将在几天后开始使用千兆字节的内存。您还必须创建一个cron脚本,该脚本每隔12或24小时运行一次,以杀死并重新生成您的PHP脚本,如下所示:

    1
    2
    killall -3 php
    nohup php myscript.php &

    但如果剧本在工作中呢?well kill-3是一个中断,与在cli上执行ctrl+c相同。您的PHP脚本可以捕获这个中断并使用php pcntl库优雅地退出:http://php.oregonstate.edu/manual/en/function.pcntl-signal.php

    下面是一个例子:

    1
    2
    3
    4
    5
    6
    7
    function clean_up() {
      GLOBAL $lock;
      mysql_close();
      fclose($lock)
      exit();
    }
    pcntl_signal(SIGINT, 'clean_up');

    $lock背后的思想是PHP脚本可以用fopen("file","w")打开一个文件。一个文件只能有一个进程有一个写锁,因此使用它可以确保只有一个PHP脚本副本在运行。

    祝你好运!


    Kevin van Zonneveld写了一篇非常详细的文章,在他的例子中,他使用了System_DaemonPear包(上一次发布日期是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启动。

    如果您只需要Linux,那么这里提供的其他两个解决方案就可以很好地工作,这取决于其风格。现在也有一些新贵和SystemD,它们有对Sysvinit脚本的回退。但是使用PHP的一半意义在于它本质上是跨平台的,因此用该语言编写的代码有很好的机会在任何地方工作。当某些外部本机操作系统级别的方面(如系统服务)进入图片时,缺陷就会出现,但大多数脚本语言都会遇到这个问题。

    尝试像这里php userland中建议的那样捕捉信号不是一个好主意。仔细阅读pcntl_signal()上的文档,您将很快了解到PHP使用一些相当不愉快的方法(特别是"ticks")处理信号,这些方法会为进程很少看到的东西(即信号)消耗大量的周期。PHP中的信号处理在POSIX平台上也几乎不可用,并且根据PHP版本的不同,支持也有所不同。最初听起来是一个不错的解决方案,但却远远不能真正发挥作用。

    随着时间的推移,PHP在内存泄漏问题上的表现也越来越好。您仍然需要小心(domxml解析器仍然容易泄漏),但是最近我很少看到失控的进程,而且与以前相比,php bug跟踪器相当安静。


    扩展emil ivav答案,可以执行以下操作关闭php中的stdin、stdout和stderror

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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书中读到了这个。


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

    我把它上传到了Github,请随意使用:https://github.com/sinasalek/easydeamonizer

    易去污剂

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

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

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

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

    工匠

    1
    php /path/myscript.php

    守护进程启动

    1
    pm2 start worker.sh

    干杯,就这样。


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

    https://github.com/jmullee/phpunixdaemon

    特点:权限丢弃、信号处理、日志记录

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