作者 | 杨航
Lambda是AWS推出的基于Function-as-a-Service(FaaS)的Serverless服务。我结合项目使用体验,发现Lambda不适合或者说不能独立支撑以下场景:
- 用户期望稳定的低延迟
- 请求需要在多个函数间跳转
- 可预期的大量调用
与此同时,Lambda和其它AWS服务结合起来能为以下场景提供良好的解决方案:
- 作为监听器异步响应Webhook (API Gateway + SQS + Lambda)
- 处理需要延时执行或指定时间执行的任务 (Step Functions + SQS + Lambda)
Lambda仅支持单请求模式,可以考虑使用AWS的App Runner或者GCP的Cloud Run替代。
背景介绍
笔者参与的项目大量使用Lambda进行开发,Lambda所承担的角色包括:作为AppServer支撑前端功能、监听第三方系统的Webhook,作为后台程序执行批处理任务,等等。在使用过程中,笔者感觉Lambda并非万能良方,有其设计和功能上的限制,所以根据项目的使用情况和体验,梳理了Lambda适合和不适合的场景,分享给大家,供大家在技术选型时进行参考。
Lambda有什么限制
- 单请求模式:一个实例一次只能处理一个请求,如果在处理完成前又有新的请求需要处理,Lambda需要创建一个新的实例来处理。
- 体积:一个函数解压后体积不能超过250MB,硬性限制;在使用Lambda时务必注意控制依赖,避免无用的依赖增大体积,并将静态文件等从代码库中抽离。特别值得注意的是Lambda运行时自带了aws-sdk,除非需要指定SDK的版本,否则请勿将SDK打入部署包中。
- 并发数量:默认的一个帐户的区域并发限制是1000,也就是说可以同时处理1000个请求;可向AWS提出申请扩展到上万。如果到达上限,新的请求会被节流。在大型项目中不同模块请务必使用不同的帐号,以隔离对并发的需求,避免单模块workload的波动影响到整个系统的稳定性。可以通过Reserved Concurrency来限制单个函数并发数量,但同时会削减未设置Reserved Concurrency函数的并发上限。
- 超时时间:最大900秒的超时时间,不可更改;如果在Happy Path时也不能判断执行时间少于900秒,则需要拆分Lambda或者使用其它方案。
- 工具:Lambda有特定的部署方式,需要工具来支持,才能保证完整的开发流程;可使用的工具包括CDK、SAM、Serverless等。
Lambda的特点
生命周期
Lambda作为一种Serverless的计算服务,一个很重要的特点就是按需创建实例,即在请求到来时创建实例来处理(冷启动)。当实例处理完成请求后,会保留一段时间,可以响应后续请求(热启动)。如果实例空闲超过一段时间,就会被Lambda回收(AWS未明确提及回收的等待时间)。AWS官方没有给出状态的标准名称,我们这里用非标准的术语来描述生命周期,如下图:
同步 vs 异步
Lambda的函数有同步和异步两种执行模式。在同步模式下,当我们执行函数时,Lambda会创建/复用实例,并等待实例执行完成后再返回结果;在异步模式下,Lambda会将请求加入队列并立即返回,然后在后台创建/复用实例进行处理。使用异步模式时可以设置重试次数,并且如果重试后仍然不能成功,可以通过设置将失败的请求发送到另外的地方,比如SNS的Topic。
很多AWS服务都能与Lambda进行集成,需要查文档来明确调用Lambda的方式,比如API Gateway是以同步模式调用Lambda,CloudWatch Event是以异步模式调用Lambda。
Lambda不适合的场景
用户期望稳定的低延迟
基于Lambda的生命周期,当有请求需要处理时,如果此时无可用实例,Lambda会初始化一个新实例并使用,也就是冷启动。结合Lambda单请求模式的特点,意味着一定会出现相当数量的冷启动,请求的响应时间会掺杂着实例初始化时间,出现延迟的波动。以项目经验来看,一个不复杂的NodeJS实现的函数,启动时间大概在1-3秒区间内波动;这个区间数值来自于CloudWatch的日志输出,实际体感时间可能更长,这部分时间会直接暴露给调用方。所以当一个场景需要提供持续稳定的低延迟响应时,以同步方式调用Lambda并不合适。
顺带一提,实例的启动时间是很重要的,如有些传统Java应用启动就需要几分钟的,建议不要直接放上Lambda。
请求需要在多个实例间跳转
如果一个请求需要以同步的形式在多个实例中跳转,在最坏情况下,会成倍放大请求的延迟,并且成倍消耗并发数量。以项目经验为例,有一个API Gateway -> Function A -> Function B -> 第三方系统的访问链路,在测试环境(用的人少,流量波动大)中,从页面调用这个接口的时间基本上在8秒以上,有时会超过10秒,让客户怀疑系统的性能有问题。
以网状结构设计的微服务模式应用,服务之间需要频繁同步通信,放上Lambda需慎重。
可预期的大量调用
如果一个接口有大量的调用,那么基于Efficiency和Cost的考虑,Lambda未必是合适的选择。
从一般性原则来讲,如果一个接口存在大量调用,那么为每次调用分配一个独占的实例显然不是一种明智的选择,这样会显著放大单个实例的边际开销。这种情况下,增加单个实例同时能处理的调用数量,能够有效提高系统吞吐量,提升系统的整体效率。
从价格方面来考虑,Lambda使用的是基于调用次数计费的模型,当调用次数增长到一定的阈值以上,其成本有效性必定会低于基于使用资源时长计费的模型。让我们用一个虚拟的场景来对比Lambda和App Runner:假设有一个接口,每天有3个小时的繁忙时段处理600 RPS的调用,另有12个小时非繁忙时段处理60 RPS的调用,其余时间没有调用;每次调用持续时间500ms。两种服务的价格对比如下:
- Lambda: 基于128M内存的配置,每天有600*60*60*3 + 60*60*60*12 = 9072000次调用,那么每月费用为$335.76。感兴趣的读者可以使用AWS Pricing Calculator自行计算。
- App Runner: 基于1 vCPU和2G内存的配置,假设每个实例可以同时处理60个请求,当超出60个请求后会创建新实例来处理。那么每天繁忙时段的花费是 $2.30,非繁忙时段的花费是$0.77,没有调用时段的费用是$0.34,每月总费用是$102。对费用详情感兴趣的读者请移步App Runner Pricing页面的Example3: High volume production app。
Lambda适合的场景
作为监听器异步响应Webhook
很多第三方系统提供Webhook来进行通知,并且一般Webhook的设计都是异步模式。这种场景可通过API Gateway,SQS和Lambda提供解决方案。
让我们按照AWS的5 Pillars来分析为什么这是一个良好的解决方案:
- Reliability: API Gateway加上SQS能够保证足够的高可用性,并且提供稳定的低延迟,这对Webhook的监听器来说相当重要,在Webhook设计里,如果监听器不能在短时间内提供响应,可能会被认为是不健康的,导致对监听器进行限流或屏蔽。
- Performance Efficiency: 上述服务提供了足够的可扩展性,保证监听器能够应对较大流量的变化,一般情况下无需提前预测流量来准备基础设施。
- Cost Optimization: 上述服务都是Serverless的服务,能够做到按实际使用付费,而无需为基础设施付费。
- Security: API Gateway和SQS自动提供了HTTPS协议,保证数据传输安全;SQS和Lambda可通过IAM确保访问控制,API Gateway可通过Authorizer或API Key来进行访问控制。
- Operational Excellence: 上述设计可完全通过Infrastructure as Code进行部署,无需手动操作。
处理需要延时执行或指定时间执行的任务
有时候一个任务需要等待一段时间之后才执行,或者到了一个特定的时间才执行,相比用一个Long-run的服务去定时扫描处理,Step Functions、SQS加上Lambda提供了一种更高效的解决方案。
前述的优点不再重复提及,这里补充一些对Step Functions的说明。Step Functions是AWS提供的Serverless的状态机服务,其中包含了等待的状态,最长可等待1年的时间;AWS保证了等待的可靠性。Step Functions结合Lambda,可以针对单个任务去设置处理时间,不再需要批量扫描处理任务。Step Functions按照状态变化收费,等待时状态并没有发生变化,无需付费,可有效降低费用开销。
写在最后
Serverless的特性决定了实例无法避免冷启动。Lambda支持同步和异步两种调用模式,以项目经验来看,同步调用模式受冷启动影响更大,有时会通过SQS将调用封装成异步模式。在Serverless工具中甚至提供了Serverless WarmUp Plugin插件,通过定时调用避免冷启动。AWS也提供了Provisioned Concurrency特性来维持热实例,减少冷启动的次数。
Lambda的单请求模式是一个很大的限制,既限制了实例的性能(比如使用NIO),又导致实例需要更频繁初始化。如果能够改变单请求模式,让一个实例接受更多的请求,将会是一个很好的特性。
Lambda有一套独立的生态系统,对代码和部署都有特定的要求,降低了代码可移植性。
有没有更好的选择呢?笔者推荐读者参考下GCP的Cloud Run服务,提供了Container-as-a-Service(CaaS)解决方案,能够将镜像以Serverless形式部署上去,通过指定实例的请求并发度,能显著减少初始化新实例的次数。AWS也提供了类似的服务App Runner,不过目前只在美国、爱尔兰和日本区域提供。