【发布时间】:2023-01-04 19:07:19
【问题描述】:
如问题所述,我的群发邮件服务器出现问题。 因此,对于一些背景知识,我们的这个电子邮件应用程序在 CRON 作业上运行,每小时发送一次邮件。 每 12 小时,我们发送最大的邮件,大约有 8 个不同的组,每个组可以有 2000 到 4000 封邮件需要发送。
这个应用程序是用 Nodejs 构建的,并使用 Nodemailer 来处理邮件的发送。
我一段时间以来注意到的是,服务器每隔一段时间就会基本上“冻结”,并且应用程序的内存使用量会缓慢攀升。
Windows 事件日志在本应运行最后一批电子邮件后显示此错误,“由于所有此类端口都在使用中,从全局 TCP 端口空间分配临时端口号的请求失败。”
我浏览了 Nodemailer 网站上的 documentation 并应用了以下更改
- 使用池化 SMTP - 这目前仍在应用程序中
- 将 maxMessages 设置为无穷大 - 我已经删除了它,因为它似乎没有帮助
- 将 maxConnections 设置为 20 - 也被删除,因为它没有任何区别
- 使用专用队列管理器——这是我最后的尝试,我使用了 RabbitMQ 并应用了他们的发布/订阅模型。
RabbitMQ 已经提高了很多性能,但它仍然没有解决问题。
发布功能
const PublishMails = (mailObj) => { return new Promise((resolve, reject) => { var publishMailResult = {}; if (mailObj.length > 0) { var connection = global.RabbitMQConnection; connection.createChannel((err, channel) => { if (err) { publishMailResult.Result = false; publishMailResult.Message = err.stack; resolve(publishMailResult); //return process.exit(1); } channel.assertQueue(config.RabbitMQ.Queue_EmailQueue, { durable: true }, err => { if (err) { publishMailResult.Result = false; publishMailResult.Message = err.stack; resolve(publishMailResult); //return process.exit(1); } var mailData = {} for (var x = 0; x < mailObj.length; x++) { mailData.from = 'XXX@XXX.com'; mailData.to = mailObj[x].Email; mailData.firstName = mailObj[x].FirstName; mailData.login = mailObj[x].Login; mailData.email = mailObj[x].Email; mailData.mailID = mailObj[x].MailID; mailData.sendID = mailObj[x].SendID; mailData.subject = "Email Message"; mailData.template = 'EmailTempLate'; channel.sendToQueue(config.RabbitMQ.Queue_EmailQueue, Buffer.from(JSON.stringify(mailData)), { persistent: true, contentType: 'application/json' }); if (x === mailObj.length - 1) { channel.close(); publishMailResult.Result = true; publishMailResult.Message = "All mails successfully published."; resolve(publishMailResult); } } }); }) } else { publishMailResult.Result = false; publishMailResult.Message = "No mails were received - Mails Not Published."; resolve(publishMailResult); } }); }订阅功能
const SubscribeMails = (mailObj) => { return new Promise((resolve, reject) => { if (mailObj.PublishMailResult.Result == true) { var options = { viewEngine: { extname: '.html', layoutsDir: 'views/email/', defaultLayout: 'Email_Template' }, viewPath: 'views/email', extName: '.html' }; var transporter = nodemailer.createTransport(smtpTransport({ host: 'XXX.XXX.XXX.XX', port: 25, pool: true })); transporter.use('stream', require('nodemailer-dkim').signer({ domainName: 'XXX.com', keySelector: 'main', privateKey: 'XXXX' })); transporter.use('compile', hbs(options)); var connection = global.RabbitMQConnection; connection.createChannel((err, channel) => { if (err) { console.error(err.stack); return process.exit(1); } channel.assertQueue(config.RabbitMQ.Queue_EmailQueue, { durable: true }, err => { if (err) { console.error(err.stack); return process.exit(1); } channel.prefetch(1); channel.consume(config.RabbitMQ.Queue_EmailQueue, data => { if (data === null) { return; } let mail = JSON.parse(data.content.toString()); transporter.sendMail({ from: mail.from, to: mail.to, subject: mail.subject, template: mail.template, context: { FirstName: mail.firstName, Email: mail.email, MailID: mail.mailID, SendID: mail.sendID, } }, (err, info) => { if (err) { console.error(err.stack); return channel.nack(data); } channel.ack(data); channel.checkQueue(config.RabbitMQ.Queue_EmailQueue, function (checkErr, queueData) { if (queueData != null) { if (queueData.messageCount == 0) { channel.close(); transporter.close(); // Added in to test if this free's up TCP ports - Didn't help } } }); }); }); resolve(true); }); }); } }); }真的感觉我应该以某种方式手动关闭这些 TCP 连接,但我没有在文档中看到任何关于此的内容,也没有在我看到的任何示例代码中看到它。
我正在添加启动此过程的 Cron 作业,以帮助调试此问题。
var cronPCSec = '0'; var cronPCMin = '58'; var cronPCHour = '*'; var cronPCDay = '*'; var cronPCMonth = '*'; var cronPCDayOfWeek = '0-6'; var cronPCfulltimes = "" + cronPCSec + " " + cronPCMin + " " + cronPCHour + " " + cronPCDay + " " + cronPCMonth + " " + cronPCDayOfWeek + ""; var MailerCronJob = new CronJob({ cronTime: cronPCfulltimes, onTick: function () { let objCronJob = {}; modelPC.GetMails().then(function (mail) { return mail; }).then(PublishMails).then(function (PublishResult) { objCronJob.PublishMailResult = PublishResult; return objCronJob; }).then(SubscribeMails).then(function (result) { console.log("Completed Successfully"); }).catch(err => { console.log("Failed"); console.log(err) }); }, start: false }); MailerCronJob.start();谢谢
【问题讨论】:
-
您确定是来自邮件程序的 tcp 连接保持打开状态吗?根据 NodeMailer 文档,除非您覆盖这些数字,否则每个池最多打开 5 个连接,而且您似乎没有这样做。
-
嗨@Evert,感谢您的回复。是的,我确定它来自 Mailer。当我登录到我们的邮件服务器时,只有在我重新启动 nodemailer 服务器后,我才能看到一切都“冻结”了,系统资源被释放,一切都开始正常运行。
标签: javascript node.js cron rabbitmq nodemailer