使用 Lambda 的 AWS 多个计划任务

AWS multiple schedule tasks with Lambda

我有以下应用场景

我需要根据多个用户操作和规则在我的服务器中调用 API。在我的网络应用程序中,用户可能会单击一个按钮,我需要在 X 时间后安排一个事件,其中 X 值取决于用户配置。该事件必须触发对将处理一些数据的 API 的回调。

为了实现这一目标,我正在考虑使用 AWS 服务的多种方式。因此,一旦用户单击我的网络应用程序按钮,服务器将:

SQS 方法

  1. 创建一个 SQS 队列,然后每 2 分钟有一个 Lambda 函数 运行 并逐个队列检查元数据时间戳值是否准时执行 API打电话。 API 调用完成后,删除队列。

这里的问题是我可能有 10,000 个并发用户单击自己的按钮,这意味着 10,000 个队列,每个队列都有自己的元数据时间戳值,这是我在 Lambda 需要调用 API。从阅读中无法根据元数据值查询 SQS :例如。获取时间戳等于或小于实际时间的所有队列。

我也在研究队列延迟功能,只显示需要执行的队列,但缺点是延迟最大值为 15 分钟,我可以安排超过 6 小时的时间。

DynamoDB 方法

  1. 服务器将创建一条 DynamoDB 记录,而不是使用 SQS。然后 Lambda 每 2 分钟执行一次,将拉取所有 table 记录并循环验证时间戳成员是否等于或小于实际时间,如果是则调用 API 并删除数据库记录.

这个场景可能看起来更好,因为我猜想从 DynamoDB 获取记录的性能更快(是一个猜测)但是它又缺乏强大的查询,因为我只能查询主键。我正在阅读我可以进行扫描,但不知道是否可以根据日期和时间值进行扫描。

Cloudwatch 方法

  1. 不是使用 SQSDynamoDB,每次用户单击按钮时,服务器都必须创建一个 Cloudwatch 规则,该规则将在我需要 [=14] 时安排时间=] 函数被触发。一旦执行 Lambda 并调用 API,Lambda 将需要删除 Cloudwatch 规则,这样它就不会再次执行。

这种情况需要创建数千条 Cloudwatch 规则,不知道这是否可能,但我喜欢这种方法,因为我不必提取数据、循环每个项目、验证时间戳和触发器 Lambda 因为 Cloudwatch 自动执行。

Any advice or clue on which one is the correct approach or maybe I’m missing others. Thanks

发电机方法

我认为这是您最好的选择。你实际上可以拥有所谓的composite primary key:“这种类型的键由两个属性组成。第一个属性是分区键,第二个属性是排序键 ".

例如,您的排序键可以是执行作业的时间戳。通过这种方式,您可以同时查询主键(又名哈希属性)AND 排序键(又名分区键),以便仅检索将在某个时间执行的作业时间点,无需扫描。

OBS.: now() 将是 returns 当前时间戳的函数。

  1. 单击用户按钮后,生成请求应该运行 的时间戳(例如从现在起 5 小时 = now() + 60 * 60 * 5)并将此时间戳保存为 Dynamo 中的排序键。
  2. 在您的 Lambda 函数中(每 2 分钟自动触发一次),您将查询 Dynamo 以使用 sort_key < now() 检索请求,这将检索在该特定点执行的所有请求时间.
  3. 处理后,您将从 Dynamo 中删除请求或将其标记为已执行。

请注意,Dynamo 限制单个查询中要返回的项目数以及整个查询结果的大小(以 MB 为单位)。此外,Lambda 的执行时间限制为 5 分钟。根据您的处理时间以及在某个时候要处理的请求数量,您需要将其分成块,否则 Lambda 可能会超时,例如。

这里可以使用各种方法:

  • 相同的 Lambda 函数在每个作业结束时调用自身以继续处理待处理的请求(如果有的话)。这更容易实现,但缺点是当你有太多块时:后面的块会被延迟(他们将等待第一个块被执行)。延迟可能会有问题,因为您的用户希望更早地处理作业。
  • 您可以使用一个 Composer 函数从 Dynamo 中检索所有内容(可以 运行 多个查询,如果有太多待处理的作业)并并行多次触发另一个 Lambda 函数(在异步模式下)。这第二个 Lambda 将负责实际完成所有繁重的工作。这种方法的优点是几乎同时执行每个作业请求块,从而防止出现不必要的延迟。

Below is simple example of what a composer function would do in your case. I used Python syntax but should be simple for you to understand.

# In the Composer Lambda function:

# First, you'd get all scheduled tasks from DynamoDB
tasks = get_pending_tasks()

# Then you'd break it in multiple chunks before calling the Worker function
max_tasks_per_worker = 100
if len(tasks) <= max_tasks_per_worker:
    call_worker(tasks=tasks)

elif len(tasks) > max_tasks_per_worker:
    chunks = split_list(tasks, size=max_tasks_per_worker)
    for chunk in chunks:
        call_worker(tasks=chunk)

# split_list() just splits a list in chunks of n size
# Example: let's say you have a list of 240 items and want chunks of 100
# This function will return 3 lists with 100, 100, and 40 items each

# call_worker() just triggers another Lambda function that will actually
# execute the tasks that were scheduled

# You could use multiple threads to parallelize calls to the call_worker()

SQS 方法

正如您已经表达的那样,SQS 不是处理此类用例的工具。

Cloudwatch (CW) 方法

这里的问题是 CW 有一个限制 100 rules per region per account。你可以要求增加,但我不认为他们会允许你拥有多达数万或数十万条规则。它不适用于此类用例。

如果您的日程安排不需要细化,您仍然可以通过设置可由不同用户共享的标准规则来使用 CW。例如:

  1. 每小时设置 24 条规则 运行,这样您就可以覆盖一整天。您用一天中的小时来标识每个规则:"rule1:00AM"、"rule2:00AM" 等
  2. 假设现在是世界标准时间早上 7 点,用户想要安排 3 小时后的事情。您将使用 rule10:00AM-reqXYZ123.
  3. 等主键将此请求保存在 Dynamo 中
  4. 在 10:00 上午,适当的 CW 规则将触发 Lambda,它将从 Dynamo 检索所有主键以 "rule10:00AM" 开头的请求(参见 BEGIN_WITH Conditional Queries).然后就可以在Lambda上正常处理请求了。
  5. 处理后,您将从 Dynamo 中删除请求或将其标记为已执行。

还要遵守我上面提到的 Dynamo 和 Lambda 的相同限制。如果您需要更多粒度,您可以每 30 分钟有 48 个 CW 规则 运行ning,或者每 15 分钟有 96 个 CW 规则 运行ning。但无论如何,我更喜欢上面的 Dynamo 方法。这将花费您更多的时间来实施,但它更加灵活和可重用。

我不会使用您概述的任何方法。相反,我会开发一个利用 Amazon Step Functions 的解决方案。

当用户单击该按钮时,将实例化一个步骤函数,其中第一个步骤是参数化等待状态。这将为您提供用户配置的等待时间,并且可以根据需要长短。在等待状态之后,您可以执行工作流中的其余步骤。

与使用步进函数相比,您概述的所有方法都显得笨拙、脆弱且昂贵。有了无服务器解决方案,您就可以无缝扩展并高效运营。

https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-wait-state.html

虽然您已经通过使用 Step Function 找到了答案,但我仍然想分享我对此的看法,因为您的用例与我的非常相似,我最终使用了 DynamoDB。

但是,我的方法不是使用 Lambda 来查询和验证时间戳,而是使用 DynamoDB 的生存时间(时间戳列设置为 TTL),其中 table 中的记录到期后将被删除,删除的记录将出现在 DynamoDB 流中。一旦它出现在流中,就可以触发 Lambda 以进行进一步处理。您可以在此处找到有关 TTL and Stream 的文档。

所以一般来说,我的应用程序将通过在 DynamoDB 中添加一条记录来注册一个流程事件,该记录带有事件发生时的时间戳(时间戳是 TTL)。然后,一旦达到时间戳,DynamoDB 将删除记录并将其放入将触发 Lambda 以启动事件的流中。

决定使用这种方法是因为另一个用例,我的应用程序需要能够 view/edit/delete "scheduled event"。所以只要记录还在table,我还是可以操作的。