【问题标题】:Run php script as daemon process将 php 脚本作为守护进程运行
【发布时间】:2011-01-03 10:42:23
【问题描述】:

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

【问题讨论】:

标签: php linux unix daemon


【解决方案1】:

您可以使用

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

nohup php myscript.php &

& 将您的进程置于后台。

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

【讨论】:

  • 如果终端存在,进程将不会退出。这就是“nohup”命令存在的原因。多年来,我一直在像这样的所有服务器上使用 PHP 脚本作为守护进程。可能有更好的解决方案,但这是最快的解决方案。
  • 这不会重启守护进程,如果它失败了,根本没有简单的方法来管理守护进程。
  • 我同意这里所说的——这是一个糟糕的解决方案。出于以下几个原因,您应该创建一个 init 脚本:1) Init 脚本在启动时自动启动 2) 您可以使用 start/stop/restart 命令管理守护进程。这是一个来自servefault的例子:serverfault.com/questions/229759/…
  • 嘿伙计们...在我看来nohup& 做了同样的事情:将启动的进程与shell 的当前实例分离。为什么我两个都需要?我不能只做php myscript.php &nohup myscript.php 吗?谢谢
  • 如果脚本写入标准输出(通过 echo 或 var_dump),那么您可以使用这样的日志文件捕获此信息:nohup php myscript.php > myscript.log &
【解决方案2】:

如果可以,请获取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. 再次fork() 并让父级退出。
  6. 使用chdir() 更改当前工作目录。
  7. fclose() stdin, stdoutstderr 不要给他们写信。正确的方法是将它们重定向到/dev/null 或文件,但我找不到在 PHP 中执行此操作的方法。当您启动守护程序以使用 shell 重定向它们时是可能的(您必须自己弄清楚如何做到这一点,我不知道 :)。
  8. 做好你的工作!

此外,由于您使用的是 PHP,因此请注意循环引用,因为 PHP 5.3 之前的 PHP 垃圾收集器无法收集这些引用,并且进程会发生内存泄漏,直到最终崩溃。

【讨论】:

  • 感谢您的信息。看起来 libslack 的守护程序几乎完成了您提到的所有准备工作。我想现在我会坚持下去,直到找到其他好的选择。
  • 发现这篇文章,期望代码复制并粘贴到一个无法关闭标准输入等的糟糕的旧应用程序中,很失望。 :p
  • 为什么又是(5)fork()?
  • 为我工作 - 干得好!
  • 对于未来读者询问为什么要分叉两次:stackoverflow.com/questions/881388/… -- TL;DR:防止僵尸。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

    我不知道具体情况,但也许还有另一种触发 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 &
    

    但是如果脚本在工作中怎么办?那么 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 脚本副本正在运行。

    祝你好运!

    【讨论】:

      【解决方案5】:

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

      我同意你的观点,PHP 不是最好的(甚至不是很好的)语言,但守护程序与面向 Web 的组件共享代码,因此总体而言,它对我们来说是一个很好的解决方案。

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

      您可以通过http://cr.yp.to/daemontools.html查看此内容。

      编辑:功能的快速列表。

      • 重启时自动启动守护进程
      • 失败时自动重启守护进程
      • 为您处理日志记录,包括翻转和修剪
      • 管理界面:“svc”和“svstat”
      • UNIX 友好(也许不是每个人的优点)

      【讨论】:

      • 也可以从存储库安装,例如很合适!
      【解决方案6】:

      你可以

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

      我会推荐最简单的方法(我认为是屏幕),然后如果您想要更多特性或功能,请转向更复杂的方法。

      【讨论】:

      • 你能提供一个类似的supervisord配置吗?
      【解决方案7】:

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

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

      【讨论】:

        【解决方案8】:

        另一种选择是使用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,我从那里学到了这项技术。

        【讨论】:

        • 喜欢这个。只是想知道,是否可以有多个并发工作人员?我只是有一个工人不够用的问题。
        • 这会在系统启动时自动运行吗?
        • Sudo "service myphpworker start" 对我不起作用。我用了“sudo start myphpworker”,效果很好
        • @Pradeepta 那是因为帖子中有一个错误——我不确定是什么(也没有测试过),但我认为sudo service myphpworker start/stop/status 仅适用于/etc/init.d 不是新贵服务。 @matt-sich 似乎发现了正确的语法。另一种选择是使用 Gearman 或 Resque,它允许后台处理和去恶魔化。
        • Ubuntu 本身正在转向使用 systemd 而不是新贵:zdnet.com/article/after-linux-civil-war-ubuntu-to-adopt-systemd
        【解决方案9】:

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

        我已经上传到github了,欢迎使用:https://github.com/sinasalek/EasyDeamonizer

        EasyDeamonizer

        只需监视您的应用程序(启动、重新启动、记录、监控等)。一个通用脚本,以确保您的应用程序保持正常运行。它故意使用 pid/lock 文件的进程名称插入来防止其所有副作用并保持脚本尽可能简单和前向,因此即使重新启动 EasyDaemonizer 本身也始终有效。 特点

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

        【讨论】:

          【解决方案10】:

          我最近需要一个跨平台解决方案(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 使用一些相当令人不快的方法(特别是“滴答声”)处理信号,这些方法会为进程很少看到的东西(即信号)消耗大量周期。 PHP 中的信号处理也仅在 POSIX 平台上几乎不可用,并且支持因 PHP 版本而异。它最初听起来像是一个不错的解决方案,但它还没有真正有用。

          随着时间的推移,PHP 在内存泄漏问题上的表现也越来越好。您仍然需要小心(DOM XML 解析器仍然容易泄漏),但这些天我很少看到失控的进程,与过去相比,PHP 错误跟踪器相当安静。

          【讨论】:

            【解决方案11】:

            使用新的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 脚本应该有一种“循环”来继续运行。

            <?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(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',但如果你不这样做,请担心,它会 需要时发出警报。

            【讨论】:

            • 被低估的答案。你有我的 +1。
            • 太棒了。希望我们也能得到答案,因为这不应该被埋在这个页面上。
            • 你应该检查systemctl status &lt;your_service_name&gt; -l的输出,它会给你一个线索。
            • @LeandroTupone MySQL 和 Memcached 演示了如何使用不必要的服务依赖项。
            • 这应该真正取代接受的答案,因为现在是 2019 年。
            【解决方案12】:

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

            https://github.com/jmullee/PhpUnixDaemon

            特性:特权删除、信号处理、日志记录

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

            【讨论】:

              【解决方案13】:

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

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

              worker.sh

              php /path/myscript.php
              

              守护进程启动

              pm2 start worker.sh
              

              干杯,就是这样。

              【讨论】:

                【解决方案14】:

                扩展 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 Beyond the web

                的书中读到了这篇文章

                【讨论】:

                  【解决方案15】:

                  我一直在寻找一种简单的解决方案,无需安装额外的东西,并且与允许 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 作业中,以检查“聊天服务器是否正在运行”,如果没有,则启动它:

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

                  【讨论】:

                    猜你喜欢
                    • 2015-03-21
                    • 2013-10-14
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2023-03-02
                    • 2015-11-07
                    相关资源
                    最近更新 更多