kfosoft / symfony-daemonizable-command
Symfony 的守护进程命令(无限运行)。
Requires
- php: ^7.1
- symfony/console: ^4.0 || ^5.0
- symfony/dependency-injection: ^4.0 || ^5.0
Requires (Dev)
- phpunit/phpunit: ^7.5.20 || ^8.0
Suggests
- symfony/filesystem: If you can't use Upstart or systemd
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。