AWS Lambda 节点函数静默失败
AWS Lambda Node Function Silently Fails
我有一个通过 AWS API 网关提供的 AWS Lambda 函数。在大多数情况下,一切似乎都运行良好。我们的 API 有一项功能无法正常工作。它是一个 webhook,用于接收传入的传真和电子邮件,让我们知道它已收到。
我们已经测试了所有内容,这在我们的本地机器上甚至在 linode 或 digital ocean 等提供商上都能完美运行。我们的问题仅在使用 AWS Lambda 部署时出现。
- 我已经尝试增加内存和执行限制,远远超出要求。
- 我尝试过多种电子邮件方法和库。
- 我尝试过各种函数格式。
lambda 的 cloudwatch 日志显示 console.log(req.body.MediaUrl);
但 stdout 或 stderr 上没有其他输出。即使我将明显的缺陷放入其中也不会有错误。几乎就好像这个函数在没有解释原因的情况下悄无声息地失败了。
如果我在大约 1-2 秒内发送 2-3 个 post 请求,我就能够让它工作。 1 可能会发送 2 封电子邮件,但绝不会全部发送。如果按正常使用方式发送单个 post 请求,您将永远不会收到电子邮件。
// Define a handler for when the fax is initially sent
const nodemailer = require('nodemailer');
const aws = require('aws-sdk');
// Start Email Logic
// AWS access keys are built into Lambda, modify permissions of the executor role
aws.config.update({region: 'us-west-2'});
let transporter = nodemailer.createTransport({
SES: new aws.SES({
apiVersion: '2010-12-01',
}),
});
exports.received = function(req, res) {
transporter.sendMail({
from: '"Fax" <fax@domain.com>',
to: process.env.FAX_ADDRESS,
subject: 'A new fax from ' + req.body.From,
text: 'You can view the fax at this url:\n\n' + req.body.MediaUrl,
}, (err, info) => {
if (err) {
console.log(err);
} else {
console.log(info.envelope);
console.log(info.messageId);
console.log('Email Sent');
}
});
// log the URL of the PDF received in the fax just in case email fails
console.log(req.body.MediaUrl);
res.status(200);
res.send();
};
顺便说一句,上面的代码有另一个文件,其中包含所有正确的快递项目,可以正确地提供这个服务。同样,它可以在本地、其他服务器上完美运行,甚至有时在泛洪 lambda 时也能正常运行;但它从来没有在 Lambda 上始终如一地工作。
我猜要么我是个需要睡觉的白痴,要么我在这里遗漏了一些关于 Lambda 的信息。任何人都可以阐明我所缺少的东西吗?
编辑:
多亏了 Michael 和 Hen,事情才得以落实。使用 Lambda 时,lambda 函数将在它通过主流程逻辑时关闭。它不会等待挂起的异步项目完成。以下更改解决了我在将 Express 与 Lambda 结合使用时遇到的问题。
请注意,我已将邮件函数移至响应的末尾,然后将响应状态和发送移至邮件函数内部。这是可以解决这个问题的众多方法之一,但对我们的需求来说是甜蜜而简单的。
exports.received = function(req, res) {
// log the URL of the PDF received in the fax just in case email fails
console.log(req.body.MediaUrl);
transporter.sendMail({
from: '"Fax" <fax@domain.com>',
to: process.env.FAX_ADDRESS,
subject: 'A new fax from ' + req.body.From,
text: 'You can view the fax at this url:\n\n' + req.body.MediaUrl,
}, (err, info) => {
if (err) {
console.log(err);
res.status(500);
res.send();
} else {
console.log(info.envelope);
console.log(info.messageId);
console.log('Email Sent');
res.status(200);
res.send();
}
});
};
本地代码 运行 与 lambda 中 运行 之间的区别在于,在结束 lambda 函数之前,您需要注意等待异步操作完成。
你所经历的是在 SendMail 结束之前 lambda 的终止,如果你一起发送几个请求让它发送一封电子邮件只是一个时间游戏。
我建议您使用 *await transporter.sendMail*
并且能够报告真正的成功/失败而不是总是返回 200。
它不是无声的失败,它只是进入了假死状态。短时间内再次尝试实际上是让之前的尝试有足够的运行时间来完成。
问题似乎出在这里:
res.status(200);
res.send();
这些应该在 sendMail()
的回调中。在成功之前你不应该 return 成功,但你在这之前 return 成功,因为 sendMail()
是异步的。
你不能指望 Lambda 在你告诉它你完成后继续为你做事。 Lambda 是 request/response.¹ 启动函数,执行操作,return 响应,停止。进程被冻结,运行时计费停止。如果事件循环中仍有东西,那么它们就会挂起,这取决于 context.callbackWaitsForEmptyEventLoop
的值,我怀疑 should/could 是否可以用 express 安全地更改。
如果下一个函数调用重用同一个容器(由基础架构做出的决定),那么当容器解冻时,您留下的东西 运行 仍然 运行,因此它们可能随后完成如果调用在时间上足够接近以至于套接字没有超时或任何其他潜在的与挂钟时间相关的故障没有发生。
任何时候在任何一个容器中只有一个调用运行,但这里您要留下一些东西 运行,因此前一个调用的工作实际上是在后一个调用中完成的。
如果您在回调中复制 console.log(req.body.MediaUrl);
行并将其保留在现在的位置,假设每个请求的值都不同,您实际上应该捕获一些证据来确认我的断言,这里。
¹Lambda 是 request/response。 Lambda 函数本身也可以作为 "event" 调用异步调用,因此对于外部调用者的 "response" 仅表示 Lambda 基础设施已同意为您执行该函数,如果您不这样做的话不想或需要等待实际响应,但这与此上下文无关。 Lambda 函数仍然是 request/response,即使在此替代模型下,函数的响应被丢弃,并且如果抛出异常,调用将重试两次。
我遇到了同样的问题,现在通过 await 语法解决了这个问题。对于那些需要更好解决方案的人(这就是我稍后要做的),创建特定的 lambda 函数将优化工作流程并为项目带来更好的方法。
我有一个通过 AWS API 网关提供的 AWS Lambda 函数。在大多数情况下,一切似乎都运行良好。我们的 API 有一项功能无法正常工作。它是一个 webhook,用于接收传入的传真和电子邮件,让我们知道它已收到。
我们已经测试了所有内容,这在我们的本地机器上甚至在 linode 或 digital ocean 等提供商上都能完美运行。我们的问题仅在使用 AWS Lambda 部署时出现。
- 我已经尝试增加内存和执行限制,远远超出要求。
- 我尝试过多种电子邮件方法和库。
- 我尝试过各种函数格式。
lambda 的 cloudwatch 日志显示 console.log(req.body.MediaUrl);
但 stdout 或 stderr 上没有其他输出。即使我将明显的缺陷放入其中也不会有错误。几乎就好像这个函数在没有解释原因的情况下悄无声息地失败了。
如果我在大约 1-2 秒内发送 2-3 个 post 请求,我就能够让它工作。 1 可能会发送 2 封电子邮件,但绝不会全部发送。如果按正常使用方式发送单个 post 请求,您将永远不会收到电子邮件。
// Define a handler for when the fax is initially sent
const nodemailer = require('nodemailer');
const aws = require('aws-sdk');
// Start Email Logic
// AWS access keys are built into Lambda, modify permissions of the executor role
aws.config.update({region: 'us-west-2'});
let transporter = nodemailer.createTransport({
SES: new aws.SES({
apiVersion: '2010-12-01',
}),
});
exports.received = function(req, res) {
transporter.sendMail({
from: '"Fax" <fax@domain.com>',
to: process.env.FAX_ADDRESS,
subject: 'A new fax from ' + req.body.From,
text: 'You can view the fax at this url:\n\n' + req.body.MediaUrl,
}, (err, info) => {
if (err) {
console.log(err);
} else {
console.log(info.envelope);
console.log(info.messageId);
console.log('Email Sent');
}
});
// log the URL of the PDF received in the fax just in case email fails
console.log(req.body.MediaUrl);
res.status(200);
res.send();
};
顺便说一句,上面的代码有另一个文件,其中包含所有正确的快递项目,可以正确地提供这个服务。同样,它可以在本地、其他服务器上完美运行,甚至有时在泛洪 lambda 时也能正常运行;但它从来没有在 Lambda 上始终如一地工作。
我猜要么我是个需要睡觉的白痴,要么我在这里遗漏了一些关于 Lambda 的信息。任何人都可以阐明我所缺少的东西吗?
编辑:
多亏了 Michael 和 Hen,事情才得以落实。使用 Lambda 时,lambda 函数将在它通过主流程逻辑时关闭。它不会等待挂起的异步项目完成。以下更改解决了我在将 Express 与 Lambda 结合使用时遇到的问题。
请注意,我已将邮件函数移至响应的末尾,然后将响应状态和发送移至邮件函数内部。这是可以解决这个问题的众多方法之一,但对我们的需求来说是甜蜜而简单的。
exports.received = function(req, res) {
// log the URL of the PDF received in the fax just in case email fails
console.log(req.body.MediaUrl);
transporter.sendMail({
from: '"Fax" <fax@domain.com>',
to: process.env.FAX_ADDRESS,
subject: 'A new fax from ' + req.body.From,
text: 'You can view the fax at this url:\n\n' + req.body.MediaUrl,
}, (err, info) => {
if (err) {
console.log(err);
res.status(500);
res.send();
} else {
console.log(info.envelope);
console.log(info.messageId);
console.log('Email Sent');
res.status(200);
res.send();
}
});
};
本地代码 运行 与 lambda 中 运行 之间的区别在于,在结束 lambda 函数之前,您需要注意等待异步操作完成。
你所经历的是在 SendMail 结束之前 lambda 的终止,如果你一起发送几个请求让它发送一封电子邮件只是一个时间游戏。
我建议您使用 *await transporter.sendMail*
并且能够报告真正的成功/失败而不是总是返回 200。
它不是无声的失败,它只是进入了假死状态。短时间内再次尝试实际上是让之前的尝试有足够的运行时间来完成。
问题似乎出在这里:
res.status(200);
res.send();
这些应该在 sendMail()
的回调中。在成功之前你不应该 return 成功,但你在这之前 return 成功,因为 sendMail()
是异步的。
你不能指望 Lambda 在你告诉它你完成后继续为你做事。 Lambda 是 request/response.¹ 启动函数,执行操作,return 响应,停止。进程被冻结,运行时计费停止。如果事件循环中仍有东西,那么它们就会挂起,这取决于 context.callbackWaitsForEmptyEventLoop
的值,我怀疑 should/could 是否可以用 express 安全地更改。
如果下一个函数调用重用同一个容器(由基础架构做出的决定),那么当容器解冻时,您留下的东西 运行 仍然 运行,因此它们可能随后完成如果调用在时间上足够接近以至于套接字没有超时或任何其他潜在的与挂钟时间相关的故障没有发生。
任何时候在任何一个容器中只有一个调用运行,但这里您要留下一些东西 运行,因此前一个调用的工作实际上是在后一个调用中完成的。
如果您在回调中复制 console.log(req.body.MediaUrl);
行并将其保留在现在的位置,假设每个请求的值都不同,您实际上应该捕获一些证据来确认我的断言,这里。
¹Lambda 是 request/response。 Lambda 函数本身也可以作为 "event" 调用异步调用,因此对于外部调用者的 "response" 仅表示 Lambda 基础设施已同意为您执行该函数,如果您不这样做的话不想或需要等待实际响应,但这与此上下文无关。 Lambda 函数仍然是 request/response,即使在此替代模型下,函数的响应被丢弃,并且如果抛出异常,调用将重试两次。
我遇到了同样的问题,现在通过 await 语法解决了这个问题。对于那些需要更好解决方案的人(这就是我稍后要做的),创建特定的 lambda 函数将优化工作流程并为项目带来更好的方法。