如何有效地使用 javax.mail API 发送批量邮件? & 我们可以使用重用认证会话来提高速度吗?

Posted

技术标签:

【中文标题】如何有效地使用 javax.mail API 发送批量邮件? & 我们可以使用重用认证会话来提高速度吗?【英文标题】:How to Send bulk mails using javax.mail API efficiently? & Can we use reuse authenticated sessions to improve speed? 【发布时间】:2012-10-28 13:25:34 【问题描述】:

我可以使用 javax.mail API 发送邮件。但这里的问题是平均每封邮件需要大约 4.3 秒才能发送到目的地。

如果我按顺序发送 20 封邮件,大约需要 86.599 秒。对于我的要求,这种方法不起作用。我正在寻找一种可以在更短的时间内发送大量邮件的方法。

当我查看调试日志时,API 正在尝试对其发送的每条消息向 SMTP 服务器进行身份验证。但我只创建一次会话,并对我发送的所有邮件使用相同的会话。现在我的问题是,每次向 smtp 服务器进行身份验证时,这不是一个开销过程吗?没有更好的方法吗?

以下是您可能会发现有用的日志跟踪。

250-AUTH LOGIN PLAIN XOAUTH XOAUTH2
250 ENHANCEDSTATUSCODES
DEBUG SMTP: Found extension "SIZE", arg "35882577"
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN XOAUTH XOAUTH2"
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Attempt to authenticate
DEBUG SMTP: check mechanisms: LOGIN PLAIN DIGEST-MD5 NTLM 
DEBUG SMTP: AUTH LOGIN command trace suppressed
DEBUG SMTP: AUTH LOGIN succeeded

请让我知道您对此的想法,非常感谢您提供任何帮助。

-纳伦德拉

【问题讨论】:

利用 sendmail 发送批量电子邮件。 对不起,我不能在这里分享我的代码,也不能使用 sendmail 。 【参考方案1】:

您如何发送消息? JavaMail FAQ 建议静态Transport.send 方法将为每条消息打开一个新连接,因为它是一种方便的方法,可以创建合适的Transport 实例,连接它,调用sendMessage,然后再次关闭连接。如果您从Session 获得自己的Transport 实例,您可以连接一次,然后反复调用sendMessage 以在一个连接上发送多条消息,最后close 它。类似于(未经测试)的东西:

Transport t = session.getTransport();
t.connect();
try 
  for(Message m : messages) 
    m.saveChanges();
    t.sendMessage(m, m.getAllRecipients());
  
 finally 
  t.close();

更新为使用资源块尝试:

try (Transport t = session.getTransport()) 
    t.connect();
    for(Message m : messages) 
        m.saveChanges();
        t.sendMessage(m, m.getAllRecipients());
    

【讨论】:

感谢您的回答。目前我正在使用 Transport.send()。我将使用 session.getTransport().sendMessage(message, addressarray); 测试我的代码; @Narendra 这并不那么简单,但我已经用一个更详细的例子编辑了我的答案。 感谢您的代码 sn-p。我用 sendMessage 方法尝试了我的代码,与使用 send() 相比,现在发送邮件的时间减少了一半。但是我有一个问题是:savechanges() 有什么用?调用 savechanges 很昂贵,对吧?在什么情况下我们应该称之为?请告诉我。 @Narendra 如果您想通过一个连接批量处理多条消息,我只是将其放入以显示静态 send 方法的最小变化。静态send 在发送消息之前调用saveChanges,如果您知道您的消息标头已经是最新的,您可以避免这种情况。 @Ian Roberts,我只是澄清一下,在您的回答中,这是否意味着多个电子邮件地址与多个 Message 对象相关联?在for循环内部,每个Message obj的收件人地址每次都是单独获取的?请建议【参考方案2】:

我在工作中也有同样的要求。我必须发送批量电子邮件和独立电子邮件。我没有找到简单而令人满意的答案:批量电子邮件可以使用单个连接发送,但独立电子邮件不能,直到我创建异步缓冲以批量发送电子邮件。

最后但同样重要的是,在短时间内使用大量Transport 连接会导致no more socket handles are available,因为所有端口都卡在TIME_WAIT 状态。

我最终得出结论,最好的将是 SMTP 连接池,因为不存在库(至少是免费的)I create mine 使用 Apache 公共池和 Java 邮件:

//Declare the factory and the connection pool, usually at the application startup
SmtpConnectionPool smtpConnectionPool = new SmtpConnectionPool(SmtpConnectionFactoryBuilder.newSmtpBuilder().build());

//borrow an object in a try-with-resource statement or call `close` by yourself
try (ClosableSmtpConnection transport = smtpConnectionPool.borrowObject()) 
    MimeMessage mimeMessage = new MimeMessage(session);
    MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false);
    mimeMessageHelper.addTo("to@example.com");
    mimeMessageHelper.setFrom("from@example.com");
    mimeMessageHelper.setSubject("Hi!");
    mimeMessageHelper.setText("Hello World!", false);
    transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());


//Close the pool, usually when the application shutdown
smtpConnectionPool.close();

【讨论】:

连接持续多长时间?假设我为不同的“发件人”组织地址分配了不同的会话(有些是 auth ,有些不是,有些是不同的主机)?假设所有都是 smtp 是否安全?密码呢,是否可以使用会话验证器而不是在构建阶段要求用户名和密码? 连接持续了you have configured。关于身份验证,SmtpConnectionFactoryBuilder#session 可以采用Authenticator。不同的主机将需要不同的连接池。当您编写不同的会话时,您的意思是传输? 我们有不同的 SMTP 主机,具体取决于电子邮件的发件人地址。根据使用的 msexchange 帐户,即使对于同一主机也不同的真实性。【参考方案3】:

上述答案要么不相关,要么太复杂而无法实施。所以在这里我发布了在 Spring Boot 中发送批量电子邮件的最简单的解决方案

随便用

mimeMessageHelper.setBcc(emailList);

在哪里,

mimeMessageHelperMimeMessageHelperemailListString[] emailList

注意:

确保你没有使用

mimeMessageHelper.setTo(emailList)

否则会在收件人收到的邮件中显示所有收件人的邮箱。

如需更多参考,This 和 This 可以帮助您学习如何在 Spring Boot 中发送电子邮件

【讨论】:

【参考方案4】:

不知道标准 Java 邮件 API 是否允许您尝试完成的任务(会话重用),但您可以考虑使用多线程:

我会使用 ThreadPool 并向其提交邮件发送作业。然后您在作业类代码中执行任何错误处理/重新发送,该代码由 ThreadPool 异步执行,您的主线程可以继续执行其他操作。提交作业只需几毫秒。自从我在 Java 中使用线程池实现某些东西已经有一段时间了,但我记得它相当简单明了。如果您在 Google 上搜索“Java ThreadPool”,您会发现很多资料。

【讨论】:

您好 FelixD,感谢您的回答。我主要是在寻找会话重用 bcz 每次身份验证都是一个开销过程。我的主要目标是将发送邮件的时间从 4 秒减少到 1 秒左右。您建议使用 theads,但它不会缩短从 src 系统发送邮件到目的地所需的时间。 嗨纳伦达,不用担心,感谢您的评论。我明白你的意思——不幸的是,我无法真正帮助更高级地使用 Java 邮件 API,我只用它完成了简单的发送。我希望我对多线程的建议可能对其他人有所帮助,因为在某些情况下这可以解决问题(例如,当邮件发送使您的主应用程序忙碌太久等时)。当然,通过我的方法,另一端邮件服务器的负载也不会减少...... 感谢 FelixD 的回复。正如 Ian Robert 在上面的回答中所建议的那样,使用 sendMessage 对我的场景很有用,并且它减少了邮件传递的时间,因为它重用了现有的会话对象。但正如 Ian Robert 所说,实施它并非易事。 你是对的。当我写最后一条评论时,我没有注意到 Ian Robert 的回复。他写的东西看起来很有趣,也许我也需要一些时间。 :)【参考方案5】:

您可以使用线程池,因为它提供了非常好的性能。我已经实现并分享了以下代码 sn-p。

try  
    ExecutorService executor = Executors.newFixedThreadPool("no. of threads"); // no. of threads is depend on your cpu/memory usage it's better to test with diff. no. of threads.
    Runnable worker = new MyRunnable(message); // message is the javax.mail.Message
    executor.execute(worker);
    executor.shutdown();
    executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);

【讨论】:

以上是关于如何有效地使用 javax.mail API 发送批量邮件? & 我们可以使用重用认证会话来提高速度吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用javax.mail.jar发送邮箱的验证

JAVA使用javax.mail发送邮件

如何使用 javax.mail 设置或更改 SMTP 消息 ID?

javax.mail.MessagingException:AppEngine 应用程序中的连接错误

使用javax.mail实现邮件的发送

使用javax.mail实现邮件的发送