2579 字
13 分钟
如何优化Lambda冷启动问题
2025-04-12

Lambda便宜,高效,和AWS的生态集成得非常好。但是由于没有一个持久运行的服务,所以当第一次请求来到的时候,需要启动服务,这一部分的耗时就是冷启动问题。本文主要探究如何优化Lambda冷启动的问题

Lambda启动步骤#

lambda runtime lifecycle

Lambda的生命周期主要分为Init,Invoke…Invoke, Shutdown。冷启动主要指的就是Init部分。Init包括lambda准备运行时环境,启动代码直到Handler的入口方法。

减少冷启动时间有两个着重点 一个是减少Lambda准备运行时的时间,例如减少code size,减少image的大小(使用Image时),将固定不变的依赖放在function layer中。 另外一个是减少启动代码到Handler入口方法的时间,例如

  1. 不要加载不必要的的依赖
  2. 使用aot编译

Lambda对Init方法有一个额外限制,当Lambda被trigger启动,但是未能在10秒中Init完成时,会重试Init,这一次的重试的timeout时间是该Lambda的timeout。这意味着,如果你单次Init需要12秒完成,实际上你的Init会耗费22秒。

[!Reference] The Init phase ends when the runtime and all extensions signal that they are ready by sending a Next API request. The Init phase is limited to 10 seconds. If all three tasks do not complete within 10 seconds, Lambda retries the Init phase at the time of the first function invocation with the configured function timeout.
The 10-second timeout doesn’t apply to functions that are using provisioned concurrency or SnapStart. For provisioned concurrency and SnapStart functions, your initialization code can run for up to 15 minutes. The time limit is 130 seconds or the configured function timeout (maximum 900 seconds), whichever is higher.

冷启动剖析及优化#

Lambda每新建一个运行时的时候,都会执行Init,所以都会有冷启动。所以即使Function一直在处理请求,当请求并发数量上升时,Lambda会创建一个新的运行时实例,这个创建过程也会触发冷启动,会导致请求的延迟增加。所以如果对于延迟时间比较敏感的,需要尽可能地减少冷启动的时间,在上线前也要做一定的测试观察延迟是否满足需求。

这里有一个网站展示Lambda冷启动耗时的benchmark Lambda Perf。但是这个benchmark是有一定局限性的,首先这个benchmark的代码非常简单,就是直接返回一个response body。但是现实中的项目不会如此简单。

对于脚本语言,如果启动的过程中需要加载太多的库,那么会大大减慢启动的速度。 个人经验,1900MB的python运行时,import numpy大概耗时需要数百毫秒。 所以要尽可能的减少启动时加载的代码量。

对于Java来说, 一个可以使用GraalVM AOT编译成native的执行文件。但是对于很多项目来说,可能没有办法编译成native的执行文件。 那么同样地要尽可能地减少启动时加载各种类库,各种资源的耗时。对于Java生态中最常见的Spring来说,要尽可能地减少加载Bean的数量,不要加载用不到的Bean。或者某些Bean只有一些比较少调用的方法会用到,可以在使用时延迟加载。

像c,c++,rust,golang这些可以编译成native执行文件,其冷启动的时间主要就是启动时加载各种资源的时间,例如建立数据库连接的时间等等。同样地,不要加载不需要的资源。

上述的方法能够起一定的作用,但是有时候也未必有良好的效果。比如代码依赖的第三方库比较大,导致加载比较慢。Java无法编译成GraalVM,JVM启动耗时就比较长。代码较为复杂,无法做太多剥离等等。对于这些情况呢还是有一些优化方法发的

SnapStart#

AWS对于Java,Python3,以及.NET8这3种运行时,支持开启SnapStart

[!Ref] SnapStart is available for the following Lambda managed runtimes: 1 Java 11 and later 2 Python 3.12 and later 3 .NET 8 and later.
If you’re using the Lambda Annotations framework for .NET, upgrade to Amazon.Lambda.Annotations version 1.6.0 or later to ensure compatibility with SnapStart. Other managed runtimes (such as nodejs22.x and ruby3.4), OS-only runtimes, and container images are not supported.
SnapStart does not support provisioned concurrencyAmazon Elastic File System (Amazon EFS), or ephemeral storage greater than 512 MB.

开启Snapstart之后,Lambda会将一个初始好的运行时环境的内存和硬盘持久化下来,这样子下次创建新的的运行时会比较快速。根据个人经验,开启SnapStart之后,Java程序的冷启动时间大致在500毫秒左右。

但是使用SnapStart也会带来一些问题。

  1. 不是所有地区都支持SnapStart,使用前去官网查看一下您所使用的AWS region是否支持SnapStart
  2. 如果在初始化的时候生成了一些唯一性的内容,例如一个UUID,那么如果该UUID会被持久化下来,就意味着其它运行时环境也会具有相同的唯一ID。所以需要使得程序不要依赖在初始化阶段生成的唯一ID。如果需要唯一ID,可以在函数处理方法中创建。
  3. Lambda从快照中恢复时,无法保证在初始化阶段建立的连接状态不变。需要在使用时验证网络连接状态,必要时重新建立连接。
  4. 如果在初始化阶段下载了一些临时数据,如果这些数据是有有效期的,需要在函数处理程序中做对应的刷新处理。
  5. SnapStart不支持Container Image。这就意味着你部署到Lambda上的Function的code size在压缩前不能超过250MB。

如何开启SnapStart#

SnapStart是作用在published version上的,所以只是在Function的配置中开启SnapStart,是不能起到作用的。

第1步,开启Function的SnapStart

第2步,发布一个新的version

虽然lambda支持让某个version直接监听trigger,但是这样子会导致后续更新比较复杂。所以推荐创建一个Alias,并将该Alias指向这个version,并使用这个Alias去监听trigger。

activating and managing Lambda Snapstart

Provisioned Concurrency 预置并发#

预置并发相对好理解,就是AWS Lambda一直保活指定数量的机器。 由于AWS Lamba长期保活,所以就大大减少了冷启动的影响。 预置并发地适用范围要比SnapStart广很多,支持所有的运行时,效果也比SnapStart好很多,比如对于Java应用来说,开启SnapStart之后第一次请求仍需要500毫秒左右才会真正开始处理,而对于预置并发,当收到了第一次请求,立刻就会开始处理流程。

当然,预置并发也不是银弹,使用预置并发也存在下列的问题

  1. 费用。由于AWS Lambda需要一直保活对应的机器,在保活的时间范围内会一直收费
  2. 不能完全杜绝冷启动。如果并发数量较大,需要新启动机器来处理请求,仍会面临冷启动的问题。

开启 Provisioned Concurrency#

与SnapStart类似,预置并发只能作用在Version和Alias上。在Alias或者Version的配置中的Provisioned concurrency可以开启预置并发,并配置预置并发的数量。

外置预热#

lambda启动之后,处理完成请求之后并不会立刻关闭实例,该实例仍会存活一段时间(通常是),所以不断有请求进入之后,就可以保活一定数量的机器。

所以可以不间断地去触发Lambda来启动实例,如果需要有一定的并发度,可以同时发出多个请求,来触发多个lambda实例的启动。

这种方式相当于使用外部触发的方式一定程度上达成了预置并发的效果。和预置并发相比有以下优缺点

优点:

  1. 费用较低,由于lambda只对启动以及处理请求部分耗费的cpu/内存收费,处理请求之后实例存活期间并不收费。所以相比预置并发,会减少相当多的费用。
  2. 能同时当作健康检查

缺点

  1. 不能确定正在存活的实例数量,预置并发可以控制保活的实例数量,稳定性不如预置并发
  2. 需要一个第三方的定时任务,可能需要一个服务器来部署该定时任务,这一部分需要额外费用。如果需要预热的Lambda数量有限,费用优势不明显。但是如果需要预热的Lambda数量很多,费用优势较为明显。

其它#

Lambda的大小限制#

各个layer以及function code的存储总和是250MB(解压前)。

对于使用Container Image的Lambda,Image的大小上限是10GB。

Lambda 内存和CPU的关系#

AWS Lambda只能配置内存大小,内存大小范围是128MB到10GB。AWS官方没有公布过内存和CPU的关系,根据Stackoverflow的一个回答AWS Lambda Memory Vs CPU configuration,大概是每1800MB左右增加CPU。

连接关系型数据库#

当面临大量的请求时,由于AWS Lambda会启动大量的实例,每个实例都持有数据库连接,可能会耗尽数据的可用连接。解决这个问题有以下几种方式。

由于Lambda请求是一个接一个处理,很多时候一个Lambda实例只需要持有一个数据库连接即可。如果使用连接池的话,连接的数量也可以设置为1。在使用数据库连接的时候,也要验证当前连接是否有效。不过大部分连接池都会有自动恢复的功能。

另外,可以设置Lambda的并发上限,防止AWS 启动太多的Lambda实例耗尽数据库资源。但是这个可能会导致并发处理能力的不足。

如果是使用AWS RDS,那么可以在Lambda配置RDS Database。这样子AWS会在Lambda外部维护一个连接池,Lambda实例从这个连接池去取连接。具体的步骤可以参看一下官方文档。

将 AWS Lambda 与 Amazon RDS 结合使用

自动连接 Lambda 函数和数据库实例

如何优化Lambda冷启动问题
https://blog.ivyxjc.com/posts/lambda-cold-start/
作者
Clever Castle
发布于
2025-04-12
许可协议
CC BY-NC-SA 4.0