亚马逊AWS官方博客

降低AWS Lambda 冷启动时间的4种方案

背景

自从 AWS 推出无服务架构 AWS Lambda 以来,成千上万的用户都受益于此,您无需预置或管理任何服务器,就可以快速部署和运行代码。但是在无服务器架构带来极大便利的同时,我们需要承认,如果您使用无服务器架构模型,在实时性要求较高的应用场景下,Lambda 冷启动将会是您的应用程序需要面临的一个切实的挑战。

Lambda 生命周期

我们先来看一下 Lambda 执行环境的生命周期,Lambda 生命周期可以分为三个阶段:

  • Init:在此阶段,Lambda 会尝试解冻之前的执行环境,若没有可解冻的环境,Lambda 会进行资源创建,下载函数代码,初始化扩展和 Runtime,然后开始运行初始化代码(主程序外的代码)。
  • Invoke:在此阶段,Lambda 接收事件后开始执行函数。函数运行到完成后,Lambda 会等待下个事件的调用。
  • Shutdown:如果 Lambda 函数在一段时间内没有接收任何调用,则会触发此阶段。在Shutdown 阶段,Runtime 关闭,然后向每个扩展发送一个 Shutdown 事件,最后删除环境。


当您在触发 Lambda 时,若当前没有处于激活阶段的 Lambda 可供调用,则 Lambda 会下载函数的代码并创建一个 Lambda 的执行环境。从事件触发到新的 Lambda 环境创建完成这个周期通常称为 “冷启动时间”。

冷启动的影响

由于 Lambda 特性,冷启动问题是无法避免的。

假设我们采用 Lambda 来构建 Web 服务,冷启动和 Web 服务初始化时间一共超过了5秒钟,那么无疑将会使您网站的用户体验大打折扣,因此设法减少冷启动时间,提高终端用户的使用体验,是您在构建无服务器架构时亟待解决的问题。

减少 Lambda 冷启动影响的方案

本文中,对目前业界的一些解决冷启动的方案进行梳理和对比,可以供您参考和选择。

1 选择合适的编程语言

目前 Lambda 支持的编程语言(Runtime)有 .NET Core、Go、Java、Node.js、Python、Ruby。在默认 512MB 内存的情况下,我们创建不同语言的lambda函数,做一个冷启动时间横向的测试和比较。各个语言的冷启动时间如下表所示。

从表中可以看出,选择不同的语言,带来的冷启动时间也各不相同。如果业务和技术架构允许的前提下,我们可以尽量选择如python这样的编程语言,从根本上降低冷启动带来的影响。

2 减小应用程序大小

由于 Lambda 在冷启动的时候会下载函数代码,下载代码这个过程也影响着启动时间,若是代码包太大,则下载时间将会变长,直接增加 Lambda 的启动时间。

所以,想要降低冷启动时间,可以对应用程序进行瘦身,比如在程序中移除不必要的代码、减少不必要的第三方库依赖等。

3 预热

在事件触发 Lambda 函数时,若此时有处于激活状态的 Lambda 可被调用,那么就可以避免冷启动,降低响应时间。在 AWS 环境中,有多种方式可以预热 Lambda,这里我们向大家介绍使用Amazon EventBridge 和预制并发两种方式,供大家参考。

3.1 Amazon EventBridge

Amazon EventBridge 是AWS 的一个 Serverless 服务,它可以创建事件去驱动其它程序或服务。EventBridge 可以配置时间计划,按照时间间隔去生成事件。
因此,想要让 Lambda 达到一个预热的效果,我们可以通过EventBridge按照一定的时间间隔去对lambda进行调用。比如通过创建一个每五分钟(时间间隔根据应用场景决定)触发一次的 EventBridge 来执行调用 Lambda。这样在真正想要处理的事件抵达之前,就会有已经被预热 Lambda 保持激活状态以等待响应。以上方式在有些文章中也被称为“温程序”。


不过这种方式比较适用于事件较少且同一时间段事件请求不会太多的应用场景,当同一时间段调用 Lambda 的事件过多,预热的函数不足以响应时,AWS 就会增加新的 Lambda实例,同样造成冷启动,增加响应时间。对于这种场景,我们可以通过配置预置并发的方式同时预热更多 Lambda 实例来进行响应。

3.2 预置并发

预置并发是 AWS Lambda 在 2019 年推出的一个功能,该功能能够指定处于激活状态的 Lambda 实例数量,使函数保持初始化状态,在两位数毫秒的超短时间内做出响应。这是实现交互式服务(例如 Web 和移动后端、对延迟敏感的微服务或同步 API)的理想选择。
我们可在 Lambda 控制台中进行配置预置并发。配置过程如下图所示。

4 JVM 分层编译

如果您的 Lambda 采用的是 Java 语言的话,那么还可以采用分层编译的方式来减少冷启动时间。

4.1 原理

从 Java 开发工具包 (JDK) 的第 8 版开始,两个即时编译器 C1 和 C2 已结合使用。 C1 设计用于客户端并为开发人员启用短反馈循环。 C2 设计用于服务器端并在分析后实现更高的性能。分层用于确定使用哪个编译器来实现更好的性能。这些表示为五个级别:

在应用程序启动时,JVM 最初会解释所有字节码并收集有关它的分析信息。然后,JIT 编译器使用收集到的分析信息来查找热点。首先,JIT 编译器使用 C1 编译频繁执行的代码段,以快速达到本机代码性能。稍后,当有更多分析信息可用时,C2 就会启动。C2 使用更激进和更耗时的优化重新编译代码以提高性能:

C2 编译器通常需要更多时间和更多内存来编译相同的方法。但是,它生成的本机代码比 C1 生成的代码优化得更好。综上所述,如果您希望实现更快启动时间的 Lambda 客户可以使用c1编译进行配置,而降低启动时间,但是带来的风险是运行效率有所降低。

4.2 禁用分层编译

我们可以通过设置 –XX:-TieredCompilation 标志来禁用分层编译。当我们设置这个标志时,JVM 不会在编译级别之间转换。因此,我们需要选择要使用的 JIT 编译器:C1 或 C2。除非明确指定,否则 JVM 会根据我们的 CPU 决定使用哪个 JIT 编译器。

为了禁用 C2 并仅使用 C1 而没有分析开销,我们可以应用 -XX:TieredStopAtLevel=1 参数。要完全禁用两个 JIT 编译器并使用解释器运行所有内容,我们可以应用 -Xint 标志。但是,我们应该注意禁用 JIT 编译器会对性能产生负面影响。

4.3 操作步骤

4.3.1 创建 Layer
  1. 新建文件 java-exec-wrapper,内容为
#!/bin/sh
shift
export _JAVA_OPTIONS="-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
java "$@"
  1.  修改文件权限并压缩
 	chmod 755 java-exec-wrapper && zip layer.zip java-exec-wrapper
  1. 登录到 AWS Lambda Console
  2. 进入 Layers 创建新的 Layer

5. 将刚刚压缩后的文件上传,创建新的 Layer

4.3.2 对 Lambda 添加分层编译
  • 进入 Lambda Functions,进入优化目标的 Java Lambda 函数

  • 默认在 Code 标签页下,将页面下滑至 Layers 面板
  • 点击 Add a layer 对 Lambda 进行添加 Layer

  • 选择 Custom Layer,并使用刚刚创建的 Layer,选择 Version 后添加

  • 给 Lambda 添加环境变量

  • 环境变量 Key 为 AWS_LAMBDA_EXEC_WRAPPER,Value 为 /opt/java-exec-wrapper,点击保存

按照如上步骤操作后,我们可以通过 CloudWatch 来查看是否生效,我们调用 Lambda,若在 CloudWatch 的 Log 中存在 Picked up _JAVA_OPTIONS: -XX:+TieredCompilation -XX:TieredStopAtLevel=1 日志,则 Java Lambda 分层编译生效。

总结

本文总结了四种有关 Lambda 冷启动优化的解决方案。编程语言选择, 应用程序瘦身,代码预热(温程序)和jvm优化。每个解决方案,都有自己的优势和局限。比如编程语言选择,这个和具体的业务场景和技术方案相关,需要架构师和开发人员有的放矢,具体的问题进行具体分析。再比如,预热对于可预期的流量效果显著,但是在处理有明显流量尖峰或不可预期流量时,也会同样面临冷启动的影响。

希望本文可以帮助您在了解和掌握解决冷启动方式和方法的同时,结合您的业务和使用场景,选择不同的应对方案,有效的提升和解决无服务器架构中的性能问题和隐患。

参考文献

https://aws.amazon.com/cn/blogs/china/introduction-to-lambda-pre-configured-concurrency/
https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1/
https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-2/
https://aws.amazon.com/cn/blogs/china/new-provisioned-concurrency-for-lambda-functions/
https://aws.amazon.com/cn/blogs/compute/increasing-performance-of-java-aws-lambda-functions-using-tiered-compilation/

本篇作者

张晓功

AWS云原生应用架构师,负责基于AWS的云计算方案架构的咨询和设计,同时致力于AWS云服务在国内的应用和推广。精通微服务架构设计、治理、容器编排、监控熔断等性能和可靠性等具体功能落地。具有丰富的互联网产品开发、大规模并行计算、性能优化等经验。

许和风

AWS 云原生应用工程师,负责基于 AWS 的云计算方案架构的设计和实施。对公有云、DevOps、微服务、容器化、Serverless、全栈开发等有深入的研究,同时致力于推广云原生应用,帮助客户利用云原生来实现业务需求。