AWS Lambda 优雅停机的最佳实践

本文主要介绍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

参考

相关推荐
XINGTECODE17 分钟前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
程序猿进阶23 分钟前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺28 分钟前
Spring Boot框架Starter组件整理
java·spring boot·后端
凡人的AI工具箱1 小时前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
先天牛马圣体1 小时前
如何提升大型AI模型的智能水平
后端
java亮小白19971 小时前
Spring循环依赖如何解决的?
java·后端·spring
2301_811274311 小时前
大数据基于Spring Boot的化妆品推荐系统的设计与实现
大数据·spring boot·后端
草莓base2 小时前
【手写一个spring】spring源码的简单实现--容器启动
java·后端·spring
Ljw...2 小时前
表的增删改查(MySQL)
数据库·后端·mysql·表的增删查改
编程重生之路2 小时前
Springboot启动异常 错误: 找不到或无法加载主类 xxx.Application异常
java·spring boot·后端