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

Sending emails by SES as fast as possible without exceeding the rate limit

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

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

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

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

以下方法保证您将有 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
}

解决方案 1

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

解决方案 2

但是我看到你把这个 running without stopping time.

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

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

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

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

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

因为你正在使用节点所以你可以看看这个速率限制器库https://www.npmjs.com/package/ratelimiter

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

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

我的结论

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

我考虑了以下几点。

错误处理

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

可扩展性

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

电子邮件优先级

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

AWS 成本

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

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

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

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

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

但是随后出现了一个新问题 - 如果您的函数在 <1 秒内执行得太快,您最终将超过速率限制。您现在有 2 个选择:

  • 重试请求直到成功
  • 按比例减少reservedConcurrency

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

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

附近重试代码