简单来说,lambda就是aws提供的无服务器计算服务,你写好代码扔上去,它帮你运行,不用管服务器。api gateway则是一个托管的api服务,可以把http请求转发给lambda或其他服务。两者配合使用,就能快速搭建出一个完整的api后端。
这是一种非常轻量的单任务节点。我通常用它们来快速搭建一些概念上比较独立的任务api。
这个方案最大的优点就是方便:部署和更新都很简单,不用操心环境配置,不用租服务器,也不用专门做运维。在请求量不大、运行时间不长的情况下,成本也很低。
但它也有不少局限性。最明显的就是api gateway的超时限制——默认最多29秒,想延长需要找aws客服申请,而且延长超时时间还会降低并发上限。lambda本身的超时时间可以设置得更长(最长15分钟),所以单独跑长任务是没问题的。但如果lambda是被api gateway触发的,情况就比较复杂了。
下面用一个平均耗时3分钟的任务来举例,看看在不同场景下会发生什么:
1. 不使用api gateway的情况
假设我们用aws的定时器(eventbridge)来触发lambda,比如每分钟触发一次,lambda的超时时间设置为10分钟。
第1分钟,第一次定时任务触发了,lambda开启一个沙盒环境来跑这个任务。
第2分钟,第二次任务又来了。这时候第一个沙盒还在忙着,lambda就会再开一个新沙盒来处理。这里有个要注意的:通常一个aws账号所有区域共享的lambda线程上限是1000,只要你没有给这个lambda设置更低的并发限制,它就会一直开新线程。
第3分钟,同样的,第三个沙盒开启。
第4分钟,情况变得有意思了。假设第一个沙盒在2分50秒的时候已经跑完了,这个沙盒会怎样?重点来了——它不会马上被关闭,而是会继续待命几分钟。这时候第四个任务进来,lambda就会直接用这个空闲的沙盒,而不是再开一个新的。
冷启动 vs 热启动
这个特性解释了lambda的一个常见现象:为什么同样一个只需要0.5秒的任务,第一次调用要等5秒才看到结果,但10秒内再调用一次,1秒就返回了?
第一次调用(冷启动)- 总耗时 5秒:
请求 → 创建沙盒(1.5s) → 加载代码(1.5s) → 初始化(1.5s) → 执行(0.5s) → 结果
10秒内再次调用(热启动)- 总耗时 1秒:
请求 → 使用已有沙盒(0.5s) → 执行(0.5s) → 结果
答案就是第一次调用包含了沙盒环境的warm up时间。特别是python的lambda,这个启动时间会比较明显。如果你的服务需要更快的响应,可以利用刚才说的沙盒复用特性——设置一个定时器(比如每分钟一次)发送空白请求,把沙盒保持在热状态。
另外,当你的并发量比较大(比如经常有300个线程在跑)的时候,总有一些沙盒是热的,需要冷启动的概率就大大降低了。虽然冷启动的可能性永远存在,但发生的频率会低很多。
2. 使用api gateway的情况
如果换成用api gateway来触发lambda,处理同样这个3分钟的任务,就会遇到一个很棘手的问题:api gateway最多只能等29秒。
理论上说,根据aws官方文档,lambda不应该因为api gateway超时就被关掉,它应该继续跑完自己的任务。
实践发现
但实际情况往往不是这样。在我做的几个项目里,api gateway一超时,lambda就直接被终止了,cloudwatch里连执行日志都找不到。这可能跟具体的集成方式、调用类型、权限配置有关系,但核心问题是:我们不能指望lambda在api gateway超时后还会继续工作。
这带来一个实际的麻烦:用户等了29秒,收到的只是一个超时错误,任务结果根本拿不到。
异步处理方案
所以对于这种超过29秒的任务,得换个思路,用异步的方式来处理。下面是几种实战验证过的方案:
方案1:Lambda链式调用(最简单)
第一个lambda收到请求后,立马返回200和一个任务id,告诉客户端"任务已经开始了"。然后它异步调用第二个lambda去慢慢处理真正的工作。第二个lambda跑完后把结果存到数据库或s3。客户端可以拿着任务id定期来查询,或者我们在任务完成时主动推送通知。
Lambda A → 返回200 + 任务ID → 异步调用 Lambda B Lambda B → 处理任务 → 写入数据库 客户端 → 轮询查询结果
方案2:SQS队列(适合高并发)
第一个lambda把任务信息丢进sqs队列就返回了,第二个lambda由队列自动触发去处理。这样做的好处是天然有削峰填谷的效果,而且可以配置死信队列,失败的任务不会丢。
Lambda A → 入队 SQS → 返回200 SQS → 触发 Lambda B → 处理任务 优势:削峰填谷 + 失败重试
方案3:Step Functions(适合复杂流程)
lambda启动一个step functions状态机就返回,后面的复杂流程交给step functions去编排。这个方案的优势是有可视化界面,看得清楚流程走到哪一步了,错误处理也比较完善。
方案4:WebSocket API(适合实时反馈)
通过websocket建立一个持久连接,lambda处理任务的时候可以随时推送进度,比如"50%完成"、"正在处理第3步"之类的,完成后再推送最终结果。用户体验会好很多。
当然,也可以在ec2或ecs上搭个api服务来处理这些异步任务。不过既然都在用aws生态了,上面这些serverless方案其实更省心——不用管服务器,按用量付费,还能自动扩缩容。