首页 > 解决方案 > 以较短的启动时间运行大型作业并针对突发流量进行自动缩放

问题描述

对于我在 AWS 上开发的网站,用户可以提交大型作业(例如,选择大量项目并要求以某种方式全部更新)。我们不想限制这些用户提交的作业的大小,因此该作业理论上可以运行很长时间并且需要大量内存(这排除了 AWS Lambda 作为计算引擎选项) . 我们希望作业尽可能相互独立,因此我们选择在 Amazon ECS 中的自己的容器中运行每个作业。当用户提交作业请求时,我们目前所做的是向 SQS 队列发送带有作业 ID/引用的消息,让 AWS lambda 轮询该队列,并在收到消息后,lambda 启动 ECS 任务(SQS -> Lambda -> ECS)。这有一个问题是每个请求都会启动一个新的 ECS 任务,所以必须启动一个新容器,这可能需要几分钟。这种延迟对用户来说是直接可见的,如果用户的工作不是特别大,但他们仍然等待几分钟来启动容器,那么这种延迟是特别不可接受的。此外,持续运行或两个容器的成本不会太成问题。

我一直在玩弄一些更新此流程的想法。

尝试 1: 在这个更新的流程中,我们将创建一个如下所示的 ECS 任务:

message = null; 
while (message == null) { 
    message = pollForMessages(); 
} 
processMessage(message); 
// task finishes, and container can be brought down 

我们从流程中删除 lambda,只使用 SQS -> ECS 而不是 SQS -> Lambda -> ECS。在这种情况下,假设容器正在为消息旋转,就不会有冷启动。我们可以将要运行的最小任务数设置为大于 0 的数字,以确保在某个时刻处理所有消息。然而,这会遇到一个问题,即它不会随着队列中消息数量的增加而自动缩放。所以当流量增加时,一些东西需要产生更多的容器。

尝试 2: 在这个更新的流程中,我们将创建一个如下所示的 ECS 任务:

message = null; 
while (message == null) { 
    message = pollForMessages(); 
} 
If (number of running tasks < number of messages in queue) {
    spawnMoreContainers();
}
processMessage(message); 
// task finishes, and container can be brought down

这带来了一个问题,如果多个容器看到队列中的消息多于正在运行的任务,我们最终可能会过度配置容器。由于这些任务在处理消息之前一直运行,这可能会导致大量不必要的成本。它也可以提供容器 - 如果任务看到正在运行的任务数 >= 消息数,但这些正在运行的任务已经忙于处理消息,这些任务最终不会将这些消息之一从队列中取出,我们可能最终得到必须等待很长时间才能处理的消息。

尝试 3:

message = null; 
while (message == null) { 
    message = pollForMessages(); 
    If (# of containers > min provisioned && this particular container has been running longer than some timeout) {
        // finish this task so this container can be brought down
        return;
    }
} 
If (number of running tasks < number of messages in queue) {
    spawnMoreContainers();
}
processMessage(message); 
// task finishes, and container can be brought down

虽然与尝试 2 相比,这可能会为我们节省一些成本,因此过度配置不会成为太大问题,但我们仍然有可能无法配置容器,在这种情况下,某些作业请求可能需要等待很长时间处理前的时间。

尝试 4: 我们可以引入锁定(例如https://aws.amazon.com/blogs/database/building-distributed-locks-with-the-dynamodb-lock-client/) 来缓解一些竞争条件,但是我们总是会遇到一个问题,即正在运行的任务并不一定意味着可以接收消息的任务,并且 Fargate 无法让我们区分这些,这使得我们很难确定要提供多少容器(例如,我们看到有 5 个正在运行的容器和 5 条消息,但我们不知道是否要提供更多容器,因为我们不知道这些容器是否已经在处理消息,或者它们是否正在处理消息'正在等待)。或者,我们可以引入一些机制,或者是外部编排器,或者是容器内的一些逻辑和一些数据存储,来管理这些容器的状态。

从本质上讲,为了处理这些问题中的每一个,架构变得越来越复杂,实现起来会很困难并且容易出错。在我看来,这些解决方案似乎在重新发明轮子,我觉得肯定有一些服务已经解决了这个问题,但我似乎找不到。

我看到的处理这个问题的建议是:

我想知道是否有人知道可能使这样做更可行的潜在服务/框架?或者如果有人对替代架构有建议?

标签: amazon-web-servicescontainersamazon-ecsaws-fargateaws-batch

解决方案


如果您不介意对突发的响应时间稍慢一点,您可以创建一个自动缩放组(我假设 ECS 有类似的东西)。该组可以由自定义指标管理,例如队列长度除以工作人员的数量。详细指南在这里:https ://docs.aws.amazon.com/autoscaling/ec2/userguide/as-using-sqs-queue.html

在任何情况下,我都会将扩展决策与工作人员代码分离,因为您需要同步的工作人员数量不同。有一个监督者来控制应该有多少工人要容易得多。因为监督者不在任务处理的关键路径上,所以您不需要太关心它的正常运行时间。如果它在故障后恢复之前需要几分钟就可以了 - 工作人员仍然在那里,至少以某种容量处理。


推荐阅读