kfosoft/symfony-daemonizable-command

Symfony 的守护进程命令(无限运行)。

20.10 2020-10-06 13:21 UTC

This package is auto-updated.

Last update: 2024-09-06 22:49:57 UTC


README

一个用于使用 Symfony 创建无限运行命令的小型捆绑包。

这些无限运行命令可以用类似 Upstart 或 systemd 这样的工具轻松守护。

为什么需要这个?

因为你想创建长时间运行的 PHP/Symfony 进程!例如,发送带有大附件的邮件、处理(延迟)付款或生成大型 PDF 报告。它们查询数据库或从消息队列中读取并执行任务。此捆绑包使您能够轻松地将此类进程作为 Symfony 命令创建。

如何安装?

使用 composer 将其包含到您的 Symfony 项目中

composer require kfosoft/symfony-daemonizable-command

使用哪个版本?

Symfony 进行了一些破坏性更改,因此您应确保使用兼容的捆绑包版本

  • 版本 3.0.* 用于 Symfony 4 和 5 及更高版本
  • 版本 2.0.* 用于 Symfony 3
  • 版本 1.3.* 用于 Symfony 2.8+

如何使用?

只需创建一个扩展自 EndlessCommand 的 Symfony 命令即可。以下是一个最小示例

namespace Acme\DemoBundle\Command;

use Wrep\Daemonizable\Command\EndlessCommand;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputInterface;

class MinimalDemoCommand extends EndlessCommand
{
	// This is just a normal Command::configure() method
	protected function configure()
	{
		$this->setName('acme:minimaldemo')
		     ->setDescription('An EndlessCommand implementation example');
	}

	// Execute will be called in a endless loop
	protected function execute(InputInterface $input, OutputInterface $output)
	{
		// Tell the user what we're going to do.
		// This will be a NullOutput if the user doesn't want any output at all,
		//  so you don't have to do any checks, just always write to the output.
		$output->write('Updating timestamp... ');

		// Do some work
		file_put_contents( '/tmp/acme-timestamp.txt', time() );

		// Tell the user we're done
		$output->writeln('done');
	}
}

通过 php app/console acme:minimaldemo 运行。

还有一个完整示例,它提供了最佳实践和执行基本任务的良好概述。

如何守护?

好吧,现在我们有一个在 前台 无限运行的命令。这对于调试很有用,但在生产中毫无用处!那么我们如何使这个命令成为真正的守护进程呢?

您应使用 systemd 来守护该命令。它们提供了非常健壮的守护进程化,在重启时启动守护进程,并且还监视进程,以便在崩溃的情况下尝试重新启动它。

如果您无法使用 Upstart 或 systemd,则可以使用 .lock 文件与 LockHandler 以及 crontab,每天启动一次启动脚本。

有一个示例 Upstart 脚本,将您的脚本放置在 /etc/init/ 中,并使用 start example-daemon 启动守护进程。.conf 文件的名称将是守护进程的名称。尚无 systemd 示例,但这不应该很难理解

命令行选项

默认情况下有一些选项可供使用,以使生活更加简单

  • 使用 -q 来抑制所有输出
  • 使用 --run-once 来仅运行一次命令,这对于调试很有用
  • 使用 --detect-leaks 来在每次运行后打印内存使用报告,下一节将详细介绍

内存使用和泄漏

对于长时间运行的进程,内存使用非常重要。Symfony 不是最小的框架,如果您在执行方法中泄漏内存,您的守护进程将会崩溃!已对 EndlessCommand 类进行了内存泄漏检查,但您也应该检查自己的代码。

如何防止泄漏?

始终以 -e prod --no-debug 标志启动您的命令。这将禁用 Symfony 的所有调试功能,这些功能会消耗越来越多的内存。

确保在 execute 方法中进行清理,确保您不是每次迭代都向数组中追加数据,例如不要打开套接字或文件句柄。

如果您在使用Monolog的交叉手指处理器,这也将成为内存泄漏的来源。这个处理器的想法是保留所有低于阈值的日志条目在内存中,只有在出现高于阈值的条目时才刷新。只要您在execute方法结束时手动刷新它,您仍然可以使用交叉手指处理器。

foreach ($this->getContainer()->get('logger')->getHandlers() as $handler)
{
    if ($handler instanceof FingersCrossedHandler) {
        $handler->clear();
    }
}

检测内存泄漏

使用--detect-leaks标志运行您的命令。请记住,调试模式会消耗内存,因此您需要使用-e prod --no-debug --detect-leaks来获得准确的报告。

每次迭代后,您的控制台都会打印出这样的内存报告

== MEMORY USAGE ==
Peak: 30038.86 KByte stable (0.000 %)
Cur.: 29856.46 KByte stable (0.000 %)

前三次迭代可能在内存使用方面可能不稳定,但之后应该稳定。即使是内存使用的轻微增加,随着时间的推移也会导致您的守护进程崩溃!

如果您看到增加/稳定/减少循环,您可能很安全。可能是垃圾回收器没有清理,您可以通过使用unset来清理变量以自行清理内存来修复此问题。

打破一些神话

调用gc_collect_cycles()无法帮助解决泄漏。PHP会自行在适当的时间清理内存,调用此方法可能会减慢泄漏的内存,但不会解决问题。此外,它会使查找泄漏更加困难,所以请不要使用它。

如果您在非调试模式下运行Symfony,它将不会泄漏内存,您不需要禁用任何SQL记录器。我遇到的唯一泄漏是上面提到的MonologBundle中的泄漏。

与Doctrine一起工作

由于EndlessContainerAwareCommand在每个迭代后清除Doctrine的EntityManager,请注意这一点。您可以通过重写finishIteration()来避免这种行为,但那时您必须自己处理EM。