【问题标题】:Provisioned concurrency not resolving cold start预置并发无法解决冷启动
【发布时间】:2022-02-19 04:08:48
【问题描述】:

我有一个 lambda 函数,它在生产环境中很少被调用,但它是面向公众的,所以我想避免冷启动。所以我想我可以使用预置并发来避免这个问题。我的 Cloudformation 模板如下所示:

QuoteLinkServiceFunction:
    Type: AWS::Serverless::Function
    Properties:
      # other lambda properties...
      ProvisionedConcurrencyConfig:
        ProvisionedConcurrentExecutions: 1

但当我在我的测试环境中创建此堆栈时(我是唯一的用户,因此没有同时发生其他调用),几个小时后返回使用此功能时,我仍然会遇到冷启动。随着 lambda 的预热,第一次调用后的后续调用运行得更快。

lambda 控制台显示此函数的别名实际上已设置为预配置并发 1,并且我已验证 ALB 目标组指向该别名。那为什么我还是开始冷?

【问题讨论】:

  • 你确定这些真的是冷星延迟,而不是实例的数据库连接问题吗?您是否考虑过使用 X 射线进行追踪?
  • @BAD_SEED 我还没有设置 X 射线,但你可能是对的。为了与docs.aws.amazon.com/lambda/latest/dg/best-practices.html 保持一致,我在处理程序构造函数(它是一个 .NET Core lambda)中的函数处理程序之外进行了一些初始繁重的工作,因此函数处理程序本身对于每次调用都非常轻量级。我现在意识到,我只是假设当 lambda 预热时会调用处理程序构造函数,但我想它不会那样做 - lambda 可能只是保持容器准备就绪,并延迟调用构造函数和处理程序直到第一次调用.
  • @BAD_SEED 如果您想重新发布您的评论作为答案,我很乐意接受。
  • 我也看到了同样的问题,它显然与这里的答案无关,因为在 cloudwatch 中,它有一段时间没有呼叫后的初始呼叫:REPORT RequestId: 3c0d1c43-4dc3-4059-9e51-203ed4387756 Duration: 492.12 ms Billed Duration: 493 ms Memory Size: 256 MB Max Memory Used: 101 MB 初始化持续时间:4831.59 毫秒。其他调用没有初始化持续时间。由于预配置的并发性,我永远不会在 cloudwatch 中看到 Init Duration。有任何想法吗?您在 cloudwatch 中有 Init Duration 吗?
  • 其实我假设 Init Duration 包括构造函数的时间,但仍然 provisionedConcurrency 应该阻止它被召回?

标签: aws-lambda


【解决方案1】:

tl;博士:


我们遇到了同样的问题,但没有找到任何解决方法。最后,Lambda 实例是瞬态的,因此无法保证连续正常运行时间(即使是预配置的并发)。

不过,预配置的并发性确实为您提供了许多正在运行的实例的保证——尽管这些实例可以在任何时间点与其他实例交换(并且在发生这种情况时会导致冷启动)。交换的频率似乎相当随意,我认为完全取决于 AWS。

编辑:我们最终意识到这根本不是问题!它只是与预置并发功能的性质有关:

  • 使用预置并发,初始化/冷启动仍然发生,但它们发生在 Lambda 可供调用之前。

    • 您可以在将预置并发设置为 1 并查看日志时发现这种情况——新实例将在前一个实例仍在被调用时被初始化,然后新实例将在前一个实例被调用时开始被调用实例被丢弃。
    • 这意味着实际上,客户不会遇到这些冷启动!它只是在后台发生。 ?
  • 但是,如果 lambda 函数不能很好地使用 static initialization,客户端仍然可能会遇到另一种形式的冷启动——这通常比 Lambda 初始化本身要慢:

在我们对跨生产调用的 Lambda 性能的分析中,数据显示,函数执行前延迟的最大贡献者来自 INIT 代码。

下面的图片很好地总结了所有这些(来自Lambda Performance Optimization Guide)。

  • 前两个没有预配置并发:

  • 另外两个已启用预配置并发:


判断 Lambda 是否确实在冷启动的一个好方法是查看 CloudWatch 中的日志。每个请求都应该有一个REPORT 日志,如下所示:

REPORT RequestId: f840a316-cf35-42ec-8f4d-c03a6cde9192  Duration: 368.80 ms Billed Duration: 369 ms Memory Size: 128 MB Max Memory Used: 93 MB  Init Duration: 3569.10 ms

如果您在日志末尾看到Init Duration,那么这确实是一个冷启动。但是,对于预配置的并发,这个初始化持续时间发生在之前 Lambda 被调用。

此外,每次 AWS 启动一个新的 Lambda“实例”时,似乎都会创建一个新的 CloudWatch 日志流 - 这会导致冷启动,这可以通过每个日志流的第一个请求具有 Init Duration 的事实来证实。 因此,只需查看“第一次活动时间”列即可显示您所有的冷启动(该列可以通过首选项/齿轮图标添加)。

Lambda Performance Optimization Guide 进一步证实了这一点:

初始化代码的运行频率高于调用总数。由于 Lambda 具有高可用性,因此对于每一个预置并发单元,至少在不同的可用区中准备了两个执行环境。这是为了确保您的代码在服务中断时可用。随着环境的收获和负载平衡的发生,Lambda 会过度配置环境以确保可用性。您无需为此活动付费。 如果您的代码初始化程序实现了日志记录,则无论何时运行此代码,您都会看到额外的日志文件,即使没有调用主处理程序。


查看START 日志也是一个好主意,以确保正在调用预期的版本(配置了预配置并发的版本):

START RequestId: f840a316-cf35-42ec-8f4d-c03a6cde9192   Version: 15

确保版本不是$LATESTwhich cannot benefit from provisioned concurrency)尤为重要:

函数的每个版本只能有一个预置并发配置。这可以直接在版本本身上,也可以在指向该版本的别名上。两个别名不能为同一个版本分配预置并发。 此外,您不能在指向未发布版本 ($LATEST) 的别名上分配预配置并发。

【讨论】:

    【解决方案2】:

    您确定这些真的是冷启动延迟而不是数据库连接问题吗?您是否考虑过使用 X 射线进行追踪?您可以将要测量的指令包装在一个段内。

    Here 一个示例应用程序。

    【讨论】:

    • 判断 Lambda 是否确实在冷启动的一个好方法是查看 CloudWatch 中的日志。 See my answer below.
    【解决方案3】:

    我的一位同事进行了一项测试,以弄清楚这里发生了什么,而 cloudwatch 日志具有误导性。 当您拥有provisionedConcurrency 并看到Init Duration 时,这并不意味着它实际上花费了这么多额外的时间,而是如果没有provisionedConcurrency 会怎样。我知道这有悖常理,但这就是测试显示的结果。

    测试设置

    1. Lambda #1 - 有 ProvisionConcurency = 1
    2. Lambda #2 - 执行 Lambda #1 并在此 lambda 中记录执行 lambda #1 所用的时间。
    3. 当 lambda #1 和 lambda #2 都空闲很长时间时执行 Lambda #2,确保它会触发冷启动。

    测试结果

    Lambda #1 云观察日志:

    2021-06-11T12:09:22.427+03:00   START RequestId: 8f90de41-3c2b-4baf-b843-99173d5862ba Version: 7
    2021-06-11T12:09:22.600+03:00   Lambda #1 request: {"Key1":null,"Key2":null,"Key3":null}
    2021-06-11T12:09:22.617+03:00   END RequestId: 8f90de41-3c2b-4baf-b843-99173d5862ba
    2021-06-11T12:09:22.618+03:00   REPORT RequestId: 8f90de41-3c2b-4baf-b843-99173d5862ba Duration: 189.24 ms Billed Duration: 190 ms Memory Size: 256 MB Max Memory Used: 110 MB Init Duration: 5079.01 ms
    

    Lambda #2 云观察日志:

    2021-06-11T12:09:21.861+03:00   START RequestId: 3cf51d5a-816b-4319-8db9-c9fee88e3e09 Version: $LATEST
    2021-06-11T12:09:22.177+03:00   Lambda#2 request: {"Key1":"value1","Key2":"value2","Key3":"value3"}
    2021-06-11T12:09:22.624+03:00   Lambda#1 time taken: 00:00:00.4294049
    2021-06-11T12:09:22.635+03:00   END RequestId: 3cf51d5a-816b-4319-8db9-c9fee88e3e09
    2021-06-11T12:09:22.635+03:00   REPORT RequestId: 3cf51d5a-816b-4319-8db9-c9fee88e3e09 Duration: 772.90 ms Billed Duration: 773 ms Memory Size: 256 MB Max Memory Used: 109 MB Init Duration: 837.07 ms
    

    注意:Lambda#1 所用时间:00:00:00.4294049lambda #1 的初始化持续时间为 5079.01 毫秒。因此,如果只是 cloudwatch 日志具有欺骗性,则 provisionedConcurrency 的工作方式就是这样。

    【讨论】:

    • 这个答案具有误导性。确实存在具有预置并发的Init Duration,除非它发生在 Lambda 实例准备好/允许调用之前。这确保了对 Lambda 的请求永远不会导致冷启动/初始化持续时间。 CloudWatch 日志没有欺骗性,它们只是显示初始化阶段的持续时间。不同之处在于,如果没有预置并发,Init Duration 将是 Lambda 执行时间的一部分。
    • @MarcoRoy 这正是我写的,这就是它具有误导性的原因。如果你想编辑我的答案,那就去吧,但你在 cloudwatch 中有两个完全相同的语句,但如果 lambda 是否启用了 provisionedConcurrency 则意味着完全不同的东西是否具有误导性。假设 provisionedConcurrency 设置为 1 和 2 个冷启动 lambdas 一起运行,您不知道 Init Duration 时间是否实际发生在当时或之前。
    • 啊,我明白你的意思了!是的,从这个意义上说,它肯定会产生误导!最初阅读您的答案时,我不明白这一点。 ?‍♂️
    猜你喜欢
    • 2020-10-24
    • 1970-01-01
    • 2023-01-24
    • 1970-01-01
    • 2017-04-04
    • 2020-12-23
    • 2019-11-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多