在不超过速率限制的情况下尽快通过 SES 发送电子邮件

问题描述 投票:0回答:6

我目前正在使用 SendGrid 从许多无服务器 node.js Lambda 函数发送电子邮件,并计划从 SendGrid 转移到 SES(因为价格合理)。

SendGrid 的速率限制如此之高,以至于通过一些 lambda 函数同时使用 for 循环向用户发送电子邮件从来没有造成问题。但是 SES 有较低的速率限制(在我的例子中是每秒 50 封电子邮件),并且会发生超出最大发送速率限制的错误。

如何在不超过速率限制的情况下尽快发送电子邮件?

我目前的计划是通过许多 lambda 函数向 SQS 队列发送电子邮件请求,并通过 1 个运行的 Lambda 函数接收 SQS 消息以发送电子邮件而不停止时间。但是我不确定如何控制向SES发送请求的速度。

node.js email aws-sdk amazon-ses rate-limiting
6个回答
7
投票

我的结论

我决定创建两个 SQS 队列(A 和 B)。队列B用于触发邮件发送功能,队列A用于在等待队列中添加消息。所以我只需要控制将消息从队列 A 移动到 B 的速度,这让一切变得简单。

我考虑了以下几点。

错误处理

起初,我想使用 while 循环通过单个 Lambda 函数发送多封电子邮件,但这使得错误处理变得困难。当发生 rate limit exceeded 错误或其他一些意外异常时,需要等待一段时间再重试,但可能会超过 Lambda 函数的超时时间。

可扩展性

通过单个 Lambda 函数从 SQS 队列接收 50 条消息并在 1 秒内向 SES 发送 50 个请求是可能的,但如果速率限制上升到 300 或 500,则可能无法在一秒钟内处理那么多请求。将消息从一个 SQS 队列移动到另一个队列,以便为每封电子邮件调用 Lambda 函数,相对容易扩展。

邮件优先

带有验证码的注册确认电子邮件需要立即发送,但活动电子邮件可以延迟发送。有两个SQS队列,我可以控制优先级。

AWS 成本

为每封电子邮件调用 Lambda 函数比在单个 Lambda 函数中发送多封电子邮件需要更多时间,但 AWS 的成本并不是很昂贵。如果向 SES 发送请求需要 1 秒,则使用 128 MB 内存(弗吉尼亚北部地区)执行 Lambda 的成本为 0.000002083 美元。 1000 封电子邮件只需 0.02083 美元,可以忽略不计。


1
投票

您还可以使用 Lambda 预留并发功能 将并发 Lambda 执行的数量限制为 50。并将 SQS 的批处理大小设置为 1 条消息。

在无服务器框架中,这看起来像:

  sendNotificationsEmails:
    handler: lib/send-email/handler.handler
    reservedConcurrency: 50
    events:
      - sqs:
          arn: xxxxx
          batchSize: 1

这将确保您不会同时运行 >50 个 Lambda 实例。

但是随后出现了一个新问题——如果你的函数在 <1s, you'll end up exceeding the rate limit. You have 2 options now:

中执行得太快
  • 重试请求直到成功
  • 按比例减少
    reservedConcurrency

如果您决定减少

reservedConcurrency
,假设您的代码运行时间为 500 毫秒。这意味着,如果您有 50 个并发的 Lambda 实例,它们将在前 0.5 秒内发送 50 封电子邮件,然后在第二个 0.5 秒内尝试再发送 50 封电子邮件。

然后将

reservedConcurrency
设置为
25
。但我仍然建议在
aws-sd

附近重试代码

0
投票

以下方法保证您将有 x 个并发调用,而您的

arrayWithAllTheEmails
包含 x 或更多元素。

每次通话结束都会调用下一个。

let ongoing = 0

function sendXEmailsAtATime (arrayWithAllTheEmails, max = 50) {
  while (ongoing < max) {
    ongoing++
    const params = prepareParams(arrayWithAllTheEmails.shift())

    ses.sendEmail(params, (err, data) => {
      if (err) console.log(err, err.stack); // an error occurred
      else     console.log(data);           // successful response

      ongoing--
      if (arrayWithAllTheEmails.length > 0) {
        sendXEmailsAtATime(arrayWithAllTheEmails, max)
      }
    });
  }
}

function prepareParams (oneItemFromTheArrayWithAllEmails) {
  // ... here you prepare the params and return it
}


0
投票

您可以通过使用 sleep() 来实现。

 async function init() {
      console.log(1);
      await sleep(10000);
    }

0
投票

解决方案 1

我认为您使用 CloudWatch 创建了一个预定事件并每 1 秒或 2 秒触发一次事件,它会调用 lambda 函数。在 lambda 函数内部只从 SQS 检索进程 50 条消息并结束 lambda 函数。

解决方案 2

但是我看到你把这个

running without stopping time
.

如果你想这样做,那么你必须在从开始向 SES 发送电子邮件请求到发送 50 个请求的那一刻设置一个时间计数器。

如果时间差大于 1 秒,则处理下一组电子邮件。

如果时间差小于1秒则等待剩余时间再处理下一组邮件。

SES 文档在此链接上讨论了相同的问题,并建议在您的代码中引入速率限制逻辑。

https://aws.amazon.com/blogs/messaging-and-targeting/how-to-handle-a-throttling-maximum-sending-rate-exceeded-error/

当你使用节点时,你可以看看这个速率限制器库https://www.npmjs.com/package/ratelimiter


0
投票

2023年来到这里的每一个人:

Amazon 在使用 Amazon SQS 作为事件源功能时引入了 AWS Lambda 函数的最大并发性,这似乎是该任务的一个很好的用例。 来源

以下是我们如何实施节流: https://github.com/michaelhaar/Heimdall-Burstable-SES

© www.soinside.com 2019 - 2024. All rights reserved.