wrep/daemonizable-command

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

5.0.0 2024-06-21 14:21 UTC

This package is auto-updated.

Last update: 2024-09-21 14:53:57 UTC


README

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

这些无限运行的命令与 Upstart 或 systemd 等工具结合使用,非常容易实现守护进程化。

为什么需要这个?

因为你想要创建长时间运行的 PHP/Symfony 进程!例如,用于发送带有大附件的邮件、处理(延迟)付款或生成大型 PDF 报告。它们查询数据库或从消息队列中读取并执行任务。这个包使得将此类进程作为 Symfony 命令创建变得非常容易。

如何安装?

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

composer require wrep/daemonizable-command

使用哪个版本?

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

  • 版本 5.* 用于 Symfony 7 及以上版本
  • 版本 4.* 用于 Symfony 6
  • 版本 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。