amazon-web-services - 以较短的启动时间运行大型作业并针对突发流量进行自动缩放
问题描述
对于我在 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 条消息,但我们不知道是否要提供更多容器,因为我们不知道这些容器是否已经在处理消息,或者它们是否正在处理消息'正在等待)。或者,我们可以引入一些机制,或者是外部编排器,或者是容器内的一些逻辑和一些数据存储,来管理这些容器的状态。
从本质上讲,为了处理这些问题中的每一个,架构变得越来越复杂,实现起来会很困难并且容易出错。在我看来,这些解决方案似乎在重新发明轮子,我觉得肯定有一些服务已经解决了这个问题,但我似乎找不到。
我看到的处理这个问题的建议是:
- 也许 AWS 批处理更适合这个用例- 事实上,对于这样的工作负载,AWS 批处理可能是更推荐的方法,但是,我们不会通过切换来消除任何冷启动问题。AWS 批处理仍会为每个作业创建一个新容器。
- 在 EC2 而不是 Fargate 上运行 ECS 任务,然后在主机上缓存容器映像——这样,我们将管理我们自己的基础设施,理想情况下我们希望它是无服务器的。
- 对队列中的消息数量发出警报,并让此警报触发一个 lambda,然后启动更多容器- /AWS 日志组上的警报的最短时间为 1 分钟。这意味着直到我们收到的请求超出我们的预置容器可以处理的数量后一分钟才会触发警报。此外,我们必须设置许多警报以根据不同数量的消息进行扩展。
我想知道是否有人知道可能使这样做更可行的潜在服务/框架?或者如果有人对替代架构有建议?
解决方案
如果您不介意对突发的响应时间稍慢一点,您可以创建一个自动缩放组(我假设 ECS 有类似的东西)。该组可以由自定义指标管理,例如队列长度除以工作人员的数量。详细指南在这里:https ://docs.aws.amazon.com/autoscaling/ec2/userguide/as-using-sqs-queue.html
在任何情况下,我都会将扩展决策与工作人员代码分离,因为您需要同步的工作人员数量不同。有一个监督者来控制应该有多少工人要容易得多。因为监督者不在任务处理的关键路径上,所以您不需要太关心它的正常运行时间。如果它在故障后恢复之前需要几分钟就可以了 - 工作人员仍然在那里,至少以某种容量处理。
推荐阅读
- cocos2d-x - 如何更改 lambda 表达式中的字符串变量?
- android - 无法在 react-native 中安装应用程序
- r - 在不指定列名的情况下将字符串转换为 R 中的分类变量
- django - 如何在 django 通用视图中实现命中计数
- c - 解析数学公式
- c++ - 在 Visual Studio 调试器下,C++ 异常会减慢 3dsmax
- java - 使用 PDF/A 一致性时,OpenHtmlToPdf 会导致错误
- rules - 无法在 CLIPS 中实现 Bind 功能
- php - 如何将数组从 Laravel Blade 传递到 Laravel 6/Vue 2.6 中的 Vue 组件。*
- r - 预先使用 auto.arima 检测错误(xreg 等级不足/系统完全是奇异的)