【问题标题】:memory leak with symfony2 & swiftmailersymfony2 和 swiftmailer 的内存泄漏
【发布时间】:2014-07-27 18:19:27
【问题描述】:

我正在使用 symfony2 命令作为 cron 作业向站点成员发送批量电子邮件。

实际代码:

$output->writeln('Before: '.(memory_get_usage() / 1024 / 1024));

$mailer->send($m);

$output->writeln('After: '.(memory_get_usage() / 1024 / 1024));

我的结果:

Before: 182.38 MB
After: 183.73 MB

每次我发送电子邮件时,swiftmailer 都会额外消耗 1+MB 的内存。这似乎不正确,但每次发送新消息时内存都会增加。我在这里做错了吗?

【问题讨论】:

  • +1 把有趣的东西放在桌子上
  • 我们需要查看有关电子邮件的更多代码和详细信息。你在附加文件吗?您是否在电子邮件之间正确清除/重置内容?
  • 这似乎是一个众所周知的错误,没有众所周知的解决方案。见herehere
  • 没有附件,我在每次发送之间启动一个新的 Swift_Message 对象,因为电子邮件是为每个用户完全自定义的,但是在创建新消息对象时没有大量的内存丢失,它特别是使用 send() 函数的行为。我还禁用了节流器,甚至最后的 mail() 调用自己也没有成功。似乎它与未设置的对象有关,但仍在搜索代码
  • 我发现如果我在传输的 send() 函数中注释掉事件调度程序代码,内存泄漏就会消失。所以这绝对是事件的问题..可能只会创建我自己的节流方法

标签: php symfony swiftmailer


【解决方案1】:

SwiftMailer 内存假脱机系统

您指出的问题实际上更多的是解决方案而不是问题。你没有做错任何事,这是由于 SwiftMailer 的内部假脱机发送电子邮件的方式。

send() 方法上,SwiftMailer 实际上并没有发送任何东西,而是简单地将其放入他的线轴中。默认情况下,假脱机选项是memory,并且假脱机刷新发生在内核终止之前。所以memory_get_usage() 不可能告诉你内存已被释放(因为很明显,你的脚本仍在运行并且 SwiftMailer 还没有刷新它的 spool)。

来自 Symfony2 文档:

当您使用假脱机将电子邮件存储到内存时,它们会得到 在内核终止之前发送。这意味着只有电子邮件 如果整个请求在没有任何未处理的情况下执行,则发送 异常或任何错误。

使用文件假脱机系统

如果这个假脱机选项给您带来麻烦,您可以切换到File 假脱机选项。

可以通过改变这个来完成:

# app/config/config.yml
swiftmailer:
    # ...
    spool: { type: memory }

到这里:

# app/config/config.yml
swiftmailer:
    # ...
    spool:
        type: file
        path: /path/to/spool

然后,您可以配置 cron 作业以自动定期刷新 spool:

$ php app/console swiftmailer:spool:send --env=prod

不使用线轴

或者,您可以选择完全不使用假脱机系统,这意味着您的所有电子邮件都将在脚本执行期间发送。它可以让您摆脱内存问题,但可能会伤害您的用户,具体取决于您是否在他们请求页面时发送邮件。但是,当您通过 cron 工作执行此操作时,就您而言,这肯定不会成为问题。不过可能是一天。

【讨论】:

  • 如果我正在假脱机,情况就是这样,但我没有假脱机。消息会立即通过 mail() 命令发出,除非通过节流器插件进行节流。根据文档,节流器插件仅使用 sleep() 命令在消息之间暂停。我发现如果我在传输的 send() 函数中注释掉事件调度程序代码,内存泄漏就会消失。所以这绝对是事件的问题..可能只会创建我自己的节流方法
  • 好的,我以为你在假脱机,因为它是默认配置。
  • 无论如何感谢您的意见,我相信您的回答会帮助很多其他人解决这个问题。非常感谢
  • 感谢您的回答,我几天来一直在努力找出为什么我的 beanstalkd 工作守护程序无法发送电子邮件。现在这很有意义,我知道 Symfony 在 Symfony 内核终止之前实际上不会发送假脱机。因为我有一个 Symfony 命令作为守护进程运行,所以内核永远不会终止!
【解决方案2】:

我在使用 swiftmailer 发送电子邮件时遇到了类似的内存问题。插件似乎会造成这些内存泄漏,因此无需删除事件调度程序代码,只需禁用插件即可(对于您的情况,请使用您自己的解决方案进行修补)。

使用 simfony2,在配置中禁用日志记录很容易:

swiftmailer:
    logging: false

或使用多个邮件:

swiftmailer:
    mailers:
        my_mailer:
            logging: false

在我的情况下,发送约 1500 封电子邮件,内存增长到约 100MB(只是因为记录器),现在 >0.2MB。

我知道这有点晚了,但也许这对遇到同样问题的人有用。 :-)

【讨论】:

  • 啊,真的,如果早点提出这个解决方案,我会接受的。
【解决方案3】:

编辑:不确定用户为何投反对票。事实是 swiftmailer 本身存在与其事件/插件系统直接相关的内存泄漏。当钩子被执行时,它会吃掉永远不会被释放的内存。单独删除事件系统修复了内存泄漏,而不会破坏 swiftmailer 在实际发送电子邮件时的工作方式。

我解决了这个问题。我编辑了 MailTransport 类并删除了 swiftmailer 中的事件系统。我只允许构建和发送消息本身,没有插件前后的废话。

我从 throttlerplugin 中提取了部分,并由它自己的版本创建,它专门只会每分钟处理消息:

class EmailThrottler {

    private $startTime;
    private $messagesPerMinute;
    private $_messages;

    public function __construct($messagesPerMinute = 25)
    {
        $this->startTime = time();
        $this->messagesPerMinute = $messagesPerMinute;
        $this->_messages = 0;
    }

    public function run()
    {
        $this->_messages++;
        $duration = time() - $this->startTime;
        $sleep = $this->_throttleMessagesPerMinute($duration);

        if ($sleep > 0) {
            sleep($sleep);
        }
    }

    private function _throttleMessagesPerMinute($duration)
    {
        $expectedDuration = $this->_messages / ($this->messagesPerMinute / 60);
        return (int) ceil($expectedDuration - $duration);
    }

}

我在邮件循环之前初始化了我的自定义节流器类:

$throttler = new EmailThrottler($pendingCampaign->getRateLimit());

我在每发送一封电子邮件后运行它:

$mailer->send($m);
$throttler->run();

希望他们能找到事件系统的修复方法。无论如何,我将看看在 5.4 上的性能是否有任何不同,但对于那些使用 5.3 的人来说,我刚刚详细介绍了这个解决方案在 5.3 上为我工作

干杯:)

【讨论】:

  • 看来这个问题还是存在的。
猜你喜欢
  • 2012-03-30
  • 1970-01-01
  • 2010-12-02
  • 2013-11-08
  • 2016-05-03
  • 2011-11-25
  • 2010-11-19
  • 2014-09-27
  • 2013-07-31
相关资源
最近更新 更多