【问题标题】:Best practise to execute long running PHP scripts执行长时间运行的 PHP 脚本的最佳实践
【发布时间】:2018-04-10 01:06:14
【问题描述】:

我们需要分发应该包含一个 PHP 脚本的软件,该脚本将运行几分钟。因此,我正在寻找 2017 年的最佳实践方法。

  • 它必须由 HTTP 请求调用。应该没有 HTTP 请求等待几分钟,因此脚本必须在访问者收到他的 HTTP 响应后继续运行。
  • 它必须定期运行(每晚)。它还应该在默认情况下每晚运行一次(如 cron 作业)。注意:由于该软件将分发给客户端,因此我们无法手动添加 cronjob(我们无法访问我们的客户端服务器)。一切都应该在 PHP 代码中完成。

(请注意,我自己阅读了现有的博客文章和 Stackoverflow 问题,但找不到令人满意的答案)

也许有人知道像 Symfony 和 Laravel 这样的框架或像 Magento 这样的网上商店是如何完成这些任务的?我仍然想知道如何在不使用框架或库的情况下用纯 PHP 自己完成。

【问题讨论】:

  • 这有点偏离 SO 的主题,但您希望将其作为非阻塞 CLI 脚本运行。不确定这是否是最佳实践,但我有一个关于这件事的课程。 github.com/ArtisticPhoenix/MISC/blob/master/BgProcess.php 适用于 CentOS 和 Windows。
  • @ArtisticPhoenix 是否可以将其称为 exec() (php.net/manual/de/function.exec.php) 对于 UNIX 和 COM 对象 (php.net/manual/de/class.com.php) 对于 Windows 的(非常方便的)包装器?
  • 是的,windows 部分要让它非阻塞运行真的很痛苦。
  • 是的,这是合理的。我将它与 Code Igniter 一起使用,因此它可以在参数中使用 / 而不是 {spaces} 构造命令。这将用于路由到 CI 控制器。我不记得我是否从它的特定副本中清除了那些东西的所有依赖项。
  • 你可能会发现这个很有用,github.com/ArtisticPhoenix/MISC/blob/master/ProcLock.php 这是 PHP 的一种进程锁定机制。基本上,它将进程 ID pid 存储在一个文件中,并且一次只允许您运行一个进程。如果运行时间比 Cron 循环时间更长,这对于阻止 Cron 启动进程的多个副本很有用。您可以执行多个过程,只需以不同的方式命名锁定文件。

标签: php laravel symfony magento cron


【解决方案1】:

存在许多解决方案:

  • 使用 exec(相当不安全)触发后台作业(在 cmets 中推荐,我可能更喜欢 symfony 进程,但仍然不安全)。
  • 经常使用 cron 触发 symfony 进程,而不是通过 http,这样更安全。
  • 使用 php-fpm,您可以在不停止进程的情况下使用fastcgi_finish_request 发送响应
  • 使用队列系统(SQS、RabbitMQ、Kafka 等)。
  • 使用cron manager in PHP
  • 使用守护程序和类似 supervisord 的东西来确保它连续运行。

最好的解决方案肯定是队列和cron,然后是PHP-FPM,其余的都是垃圾。

如果不做一些在某些时候不起作用的事情,你绝对没有办法在某人的服务器上运行。

旁注:你说你不想图书馆是为了知道如何自己做,我添加了图书馆的链接,因为阅读它们可能会让你对技术有更深入的了解,这些图书馆的质量真的很高。

【讨论】:

  • 来自文档的快速注释:Symfony Process 如果在子进程有机会完成之前发送了响应,则服务器进程将被终止(取决于您的操作系统)。
  • 我相信这只有在你不使用 php-fpm 和 fastcgi_finish_request 的情况下才是正确的,因为这是由于系统在完成脚本时会杀死任何孩子的方式,而 fastcgi_finish_request 可以帮助我们发送响应后主脚本(php-fpm)将继续的方式。我在那里对你有意义吗?
  • 这很有道理,我没有深入了解正在发生的事情,但学习一些已知的东西总是很高兴。谢谢!有很多东西要学:)
  • exec () 本身不会触发后台作业,但它可以被认为是分裂头发。所以我的意思是exec 仍然处于阻塞状态,所以它会在运行时阻止当前脚本。我会将后台作业定义为与当前线程异步。您可以将> /dev/null & 添加到命令末尾(在 Linux 上)以将其与当前 shell 分离。
  • 使用 rabbit 并没有摆脱在命令行中运行进程的需要,因为你必须让工作人员来处理你的东西。并不是说这是一个坏主意。我在工作中使用 RabbitMq,效果很好。但主要的好处是它为您提供了发布者和消费者之间的分离层。
【解决方案2】:

如果你为 Magento 设置了一个常规的 cronjob,Magento 只会运行它 cronjobs。它有一个 cron.sh,每分钟运行一次,并在 Magento 的队列中执行作业。

任何通过 http 执行长时间运行任务的解决方案都涉及到 Web 服务器配置。

【讨论】:

  • 所以有人必须手动编辑 cronjob 文件,比如“crontab -e”。也许这不是一个好主意,但我认为您可以使用 PHP 添加一个 cronjob:variomedia.de/faq/Wie-richte-ich-einen-Cronjob-ein/article/86
  • 通常一个 php 进程将作为 webserver 用户 (www-data) 运行,并且没有运行 crontab 的权限。一些配置甚至完全阻止了 system()、exec() 等的使用(参见 safe_mode_exec_dir)。在任何情况下,服务器管理员都必须调整用户组或权限才能实现这一点。
【解决方案3】:

最后我认为有两种方法可以通过HTTP请求在PHP中启动一个长时间运行的进程(不让用户等待很长时间):

  • 使用 FPM 并使用 fastcgi_finish_request() 将响应发送给用户。发送响应后,您可以做任何您想做的事情,例如长时间运行的任务。 (这里不必启动新进程,继续 PHP 即可)。

    fastcgi_finish_request();
    longrunningtask();
    
  • 使用以下函数之一创建一个新进程。将 STDOUT 和 STDERR 重定向为 null 并将其置于后台(您必须同时执行这两项操作)。为了仍然获得新进程的输出,新进程可以写入某个日志文件。

    exec('php longrunningtask.php >/dev/null 2>/dev/null &');
    shell_exec('php longrunningtask.php >/dev/null 2>/dev/null &');
    system('php longrunningtask.php >/dev/null 2>/dev/null &');
    passthru('php longrunningtask.php >/dev/null 2>/dev/null &');
    

    或者像symfony/process那样使用proc_open()


注意事项:

  • symfony/process:在您可以阅读的文档中,您可以将 FPM 和 fastcgi_finish_request() 用于长时间运行的任务。
  • 安全性:我看不出任何内置的安全风险,您只需做正确的事,一切都会好起来的(您可以使用密码保护,验证可能的命令输入等)。
  • 对于问题的 cron-job-part,没有答案。我认为不可能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-05
    • 2017-12-21
    • 1970-01-01
    相关资源
    最近更新 更多