本文主要介绍AWS Lambda优雅停机的具体实现原理,优雅停机功能赋予了每个AWS Lambda instance主动释放资源的能力。通过这个功能我们可以完成例如MySQL连接池中长链接的主动关闭、长事务(超过15min)超时前的主动撤销回滚等pre stop
类资源释放工作。
我们知道AWS Lambda是AWS推出的一种serverless计算平台,它支持多种主流的编程语言并为你管理好了底层服务器资源和软件运行环境,你只需要编写具体的业务逻辑代码即可完成事件驱动型微服务开发工作。当您部署好了自己的代码后,Lambda通过极致的弹性能力按照您的业务负载压力情况近乎实时(两位数毫秒)的响应并自动的进行计算资源的水平扩展和收缩,您只需按实际使用的计算时间付费,代码未运行时不产生费用。例如我们有一个一天内访问流量波动很大的HTTPS RESTFul API服务,通过把代码运行在AWS Lambda上,我们可以做到平常的个位数请求我们只需要1到个位数个instance进行低频并行处理;当请求数量在高峰时段突然上升时,Lambda可以在数秒内迅速扩展出数百数千个instance进行高并行处理来快速的处理大量请求;在后续的流量逐渐降低后它会及时的释放闲置的instance直到刚好满足业务需求;甚至在没有任何请求时instance的数量可以缩减为0。
在理解优雅停机具体实现前,我们需要了解单个lambda instance的生命周期。一个lambda instance在可以处理event前,会经历初始化(init
)、event调用(invoke
)、instance资源闲置一段时间后的关闭(shutdown
)共3个主要的阶段。
如下图:
常见的Linux上的普通服务进程或者Kubernetes容器服务的优雅停机钩子,一般是通过监听SIGTERM
信号来实现。在类UNIX系统中SIGTERM
信号用于终止程序,它是一种软性的终止信号,运行在类UNIX系统中的进程可以选择忽略它或者捕获它。aws lambda底层的OS是Amazon Linux,所以实现的思路类似。我们可以在init阶段提前注册好SIGTERM
信号处理钩子,当lambda instance进入shutdown阶段时这个钩子捕获SIGTERM
信号,执行资源释放等优雅停机相关的代码。
实现AWS Lambda优雅停机的具体原理
通过前面的介绍,我们可以知道实现这个功能的核心前提 是我们能够准确的在AWS Lambda instance即将被回收时,提前一小段时间捕获到具体的SIGTERM
信号。只有SIGTERM
被捕获后我们才能及时的完成后续的优雅停机操作。
为了捕获这个关键性的SIGTERM
信号,我们需要参考Lambda Extensions API:
从图中我们知道SIGTERM
信号实际上是instance处于shutdown阶段时,runtime shutdown行为产生时发出的(见下图中红色方框部分)。因此我们可以通过为lambda加上Lambda extensions来实现SIGTERM
信号的捕获。
Shutdown阶段持续时间限制
Shutdown阶段的最长持续时间取决于已注册扩展的配置:
- 0 毫秒 -- 无已注册扩展的函数
- 500 毫秒 -- 带有注册的内部扩展的函数
- 2000 毫秒 -- 具有一个或多个注册的外部扩展的函数
对于具有外部扩展的lambda函数,Lambda最多保留300毫秒(对于具有内部扩展的运行时为500毫秒),以便运行时进程执行正常关闭,所以优雅停机的代码逻辑必须在这段时间内完成 。Lambda 会将2000毫秒限制的剩余时间分配给要关闭的外部扩展,如果运行时或扩展未在限制范围内响应Shutdown事件,则 Lambda 将使用SIGKILL
信号强制结束进程。
怎么实现AWS Lambda优雅停机
不同编程语言都能进行SIGTERM
的捕获和处理,我们以一个AWS SAM编排的python3.12 demo为例进行详细的说明。其他的语言请看graceful-shutdown-with-aws-lambda。
注册Lambda extension
首先我们需要在SAM模板里面注册Lambda extension,推荐您推荐注册使用最新的extensions,相关的ARN可以在Lambda Insights extension中找到。
具体的AWS SAM 模版ymal样例为:
yaml
Layers:
# Add Lambda Insight Extension: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-extension-versions.html
- !Sub "arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension-Arm64:5"
Policies:
# Add IAM Permission for Lambda Insight Extension
- CloudWatchLambdaInsightsExecutionRolePolicy
如下图:
编写优雅停机处理逻辑
我们使用python3代码进行优雅停机时的逻辑处理。当instance init阶段冷启动后,instance被首次invoke调用时,下面的python 3.12代码被运行一次,这也是这部分代码仅有的唯一一次运行,后续的invoke只会运行handler里面的event处理逻辑。
优雅停机的处理代码样例为:
python3
def exit_gracefully(signum, frame):
r"""
SIGTERM Handler: https://docs.aws.amazon.com/lambda/latest/operatorguide/static-initialization.html
Listening for os signals that can be handled,reference: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html
Termination Signals: https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html
"""
print("[runtime] SIGTERM received")
print("[runtime] cleaning up")
# perform actual clean up work here.
time.sleep(0.2)
print("[runtime] exiting")
sys.exit(0)
signal.signal(signal.SIGTERM, exit_gracefully)
如下图:
注意: 任何语言都是类似的编程模型,优雅停机逻辑需要写在handler外面,作为initialization code运行。更细节的部分可以参考Lambda programming model
测试优雅停机
当我们调用多次lambda后停止调用一段时间,instance会被自动的回收,我们可以在cloudwatch观察到如下的日志。
具体的日志详情:
yaml
2023-12-15T14:48:34.955+08:00 INIT_START Runtime Version: python:3.12.v16 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:5eaca0ecada617668d4d59f66bf32f963e95d17ca326aad52b85465d04c429f5
2023-12-15T14:48:35.021+08:00 LOGS Name: cloudwatch_lambda_agent State: Subscribed Types: [Platform]
2023-12-15T14:48:35.130+08:00 EXTENSION Name: cloudwatch_lambda_agent State: Ready Events: [INVOKE, SHUTDOWN]
2023-12-15T14:48:35.131+08:00 START RequestId: 19234f5f-b2f8-4a9e-b7da-641ef9b4a181 Version: $LATEST
2023-12-15T14:48:35.171+08:00 END RequestId: 19234f5f-b2f8-4a9e-b7da-641ef9b4a181
2023-12-15T14:48:35.171+08:00 REPORT RequestId: 19234f5f-b2f8-4a9e-b7da-641ef9b4a181 Duration: 39.75 ms Billed Duration: 40 ms Memory Size: 128 MB Max Memory Used: 45 MB Init Duration: 175.18 ms
2023-12-15T14:48:37.515+08:00 START RequestId: a20821af-2040-4aa5-9908-09ec9aec0279 Version: $LATEST
2023-12-15T14:48:37.531+08:00 END RequestId: a20821af-2040-4aa5-9908-09ec9aec0279
2023-12-15T14:48:37.531+08:00 REPORT RequestId: a20821af-2040-4aa5-9908-09ec9aec0279 Duration: 16.06 ms Billed Duration: 17 ms Memory Size: 128 MB Max Memory Used: 45 MB
2023-12-15T14:54:28.850+08:00 [runtime] SIGTERM received
2023-12-15T14:54:28.850+08:00 [runtime] cleaning up
2023-12-15T14:54:29.051+08:00 [runtime] exiting
关于优雅停机的总结
对于Serverless函数式计算,AWS Lambda instance的创建和销毁是比较频繁的,这必然产生大量的冷启动和资源初始化动作,单个instance的创建时冷启动会消耗一定的时间,资源的初始化也会耗费一些时间并对其他资源产生影响(比如大量的MySQL连接的创建会影响数据库的性能)。为了更好的降低冷启动的影响、更合理的利用资源,aws lambda instance会被尽可能的高效的复用。instance复用就是当一个instance完成一个event请求后并不会立即释放,而是进入"等待"的状态。在一定时间范围内,如果有新的event请求被分配过来,则会直接调用对应的handler方法,而不需要再次初始化各类资源等,这在很大程度上减少了上述冷启动的影响,尽量利用复用功能是我们开发者优化lambda函数代码的关注重点。在整个复用的过程中,这些instance一直是活着的状态,它们维持这个对外界资源的持有状态,直到instance被销毁。
在一些典型的状态持有场景:
- 数据库等连接,我们推荐在初始化的时候进行数据库连接的建立,避免每次请求都创建连接,降低数据库频繁的连接创建和销毁压力。
- 长事务或者长耗时任务的处理,由于单次event事件的处理时长不能超过15分钟。这些耗时比较长但是不确定15分钟内一段可以处理完的情况下,我们需要在event超时前进行主动撤销
- 状态的同步或者通知,一些可中断可重新计算的分布式业务可能是多个lambda instance协调处理的,某个被即将中断的instance需要在死亡前主动告知其他instance自己的状态并进行断点状态的保存。
这些场景都需要pre stop
,也就是在每次AWS Lambda决定停止当前instance前调用某种善后逻辑,开发者负责实现相应逻辑以确保完成instance销毁前的必要操作,如关闭数据库链接,以及上报、更新状态等。
一般最佳实践是我们实现主动+被动的资源处理逻辑,主动处理逻辑就本文讲的优雅停机,做到资源的主动释放;被动处理逻辑一般是资源侧的超时处理,比如MySQL服务器主动在一段时间后释放无instance认领的连接,Redis对注册在内存里面的分布式锁状态进行超时释放等。
AWS Lambda上各种语言的有关优雅停机的支持列表
下面的表格是几种主流的语言在AWS Lambda runtime中以zip方式运行时有关于优雅停机的支持情况:
language version | Identifier | Operating system | Architectures | Support status |
---|---|---|---|---|
Python 3.12 | python3.12 | Amazon Linux 2023 | arm64 x86_64 | ✅ Support |
Python 3.11 or earlier | python3.11 python3.10 python3.9 | Amazon Linux 2 | arm64 x86_64 | ❌ NOT Support |
Node.js 20 | nodejs20.x | Amazon Linux 2023 | arm64 x86_64 | ✅Support |
Node.js 18 | nodejs18.x | Amazon Linux 2 | arm64 x86_64 | ✅Support |
go 1.x (1.19,1.20,1.21) | provided.al2023 | Amazon Linux 2023 | arm64 x86_64 | ✅Support |
go 1.x (1.19,1.20,1.21) | provided.al2 | Amazon Linux 2 | arm64 x86_64 | ✅Support |
rust | provided.al2023 | Amazon Linux 2023 | arm64 x86_64 | ✅Support |
Java 21 | java21 | Amazon Linux 2023 | arm64 x86_64 | ✅Support |
Java 17 | java17 | Amazon Linux 2 | arm64 x86_64 | ✅Support |
Java 11 | java11 | Amazon Linux 2 | arm64 x86_64 | ✅Support |
Java 8 | java8.al2 | Amazon Linux 2 | arm64 x86_64 | ✅Support |
.NET 6 | dotnet6 | Amazon Linux 2 | arm64 x86_64 | ✅Support |