Amazon Lambda 优雅停机的最佳实践

Python
MySQL
Amazon Lambda
0
0
本文主要介绍 Amazon Lambda 优雅停机的具体实现,优雅停机功能赋予了每个 Amazon Lambda instance 主动释放资源的能力。简单来讲通过这个功能我们可以完成 pre stop 类资源释放工作,例如 MySQL 连接池中长链接的主动关闭、未完成的长事务(超过 15min)超时前的主动回滚等。 ### Amazon Lambda Instance 的生命周期 在理解优雅停机具体实现前,我们需要了解单个 instance 的生命周期。一个 instance 会经历初始化(init)、多次 event 调用(invoke)、instance 资源闲置一段时间后的关闭(shutdown),共 3 个主要的阶段。 如下图: ![image.png](https://dev-media.amazoncloud.cn/5582bf9381d74412918fb335f02b0ae7_image.png "image.png") 常见的 Linux 上的普通服务进程或者 Kubernetes 容器服务的优雅停机钩子,一般是通过监听 SIGTERM 信号来实现。在类 UNIX 系统中 SIGTERM 信号用于终止程序,它是一种可捕获的软性的终止信号,运行在类 UNIX 系统中的进程可以选择忽略它或者捕获它,Amazon Lambda 底层的 OS 是 Amazon Linux,所以实现的思路类似。我们可以在 init 阶段提前注册好 SIGTERM 信号处理钩子,当 instance 进入 shutdown 阶段时这个钩子捕获 SIGTERM 信号并触发执行优雅停机相关的代码。 ### 实现 Amazon Lambda 优雅停机的具体原理 通过前面的介绍,我们可以知道实现这个功能的核心前提是我们能够准确地在 Amazon Lambda instance 即将被回收时,提前一小段时间捕获到具体的 SIGTERM 信号,只有 SIGTERM 被捕获后我们才能及时的完成后续的优雅停机操作。 为了捕获这个关键性的 SIGTERM 信号,我们需要参考 Lambda Extensions API: ![1712022671615.jpg](https://dev-media.amazoncloud.cn/69c0f29d44444a2fac1a17d258d83287_1712022671615.jpg "1712022671615.jpg") 从图中我们知道 SIGTERM 信号实际上是 instance 处于 shutdown 阶段时,runtime shutdown 行为产生时发出的(见下图中红色方框部分),我们可以通过为 Lambda 加上 Lambda extensions 来实现 SIGTERM 信号的捕获。 ### Shutdown 阶段持续时间限制 Shutdown 阶段的最长持续时间取决于已注册扩展的配置: - 0 毫秒 – 无已注册扩展的函数 - 500 毫秒 – 带有注册的内部扩展的函数 - 2000 毫秒 – 具有一个或多个注册的外部扩展的函数 对于具有外部扩展的 Lambda 函数,Lambda 最多保留 300 毫秒(对于具有内部扩展的运行时为 500 毫秒),以便运行时进程执行正常关闭,所以优雅停机的代码逻辑必须在这段时间内完成。Lambda 会将 2000 毫秒限制的剩余时间分配给要关闭的外部扩展,如果运行时或扩展未在限制范围内响应 Shutdown 事件,则 Lambda 将使用 SIGKILL 信号强制结束进程。 ### 怎么实现 Amazon Lambda 优雅停机 不同编程语言都能进行 SIGTERM 的捕获和处理,我们以一个 Amazon SAM 编排的 python3.12 demo 为例进行详细的说明,方便起见它采用 external extension 的方式来捕获 SIGTERM 信号。其他的语言请看 [graceful-shutdown-with-aws-lambda](https://github.com/aws-samples/graceful-shutdown-with-aws-lambda?trk=cndc-detail)。 #### 注册 Lambda extension 首先我们需要在 SAM 模板里面注册 Lambda extension,推荐您推荐注册使用最新版本的 extensions,相关的 ARN 可以在 Lambda Insights extension 中找到。 具体的 Amazon SAM 模版 yaml 样例为: ```js Layers: - !Sub "arn:aws:lambda:\${AWS::Region}:580247275435:layer:LambdaInsightsExtension-Arm64:5" Policies: # Add IAM Permission for Lambda Insight Extension - CloudWatchLambdaInsightsExecutionRolePolicy ``` 如下图: ![image.png](https://dev-media.amazoncloud.cn/1efe741ccb0f4e0bb7a476412607cf2f_image.png "image.png") #### 编写优雅停机处理逻辑 我们使用 python3 代码进行优雅停机时的逻辑处理。当 instance init 阶段冷启动后,instance 被首次 invoke 调用时下面的 python 3.12 代码被运行一次,这也是这部分代码仅有的一次运行;后续的 invoke 只会运行 handler 里面的 event 处理逻辑。 优雅停机的处理代码样例为: ```js 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) ``` 如下图: ![image.png](https://dev-media.amazoncloud.cn/d8e2580008294a3792fb2ef7d26c4770_image.png "image.png") 注意: 任何语言都是类似的编程模型,优雅停机逻辑需要写在 handler 外面,作为 initialization code 运行。更细节的部分可以参考 [Lambda programming model](https://docs.aws.amazon.com/lambda/latest/dg/foundation-progmodel.html?trk=cndc-detail)。 ### 测试优雅停机 当我们调用多次 Lambda 后停止调用一段时间,instance 会被自动回收,我们可以在 [Amazon CloudWatch](https://aws.amazon.com/cn/cloudwatch/?trk=cndc-detail) 观察到如下日志。 ![image.png](https://dev-media.amazoncloud.cn/afb85b772d1740798ad7c8c4860980e5_image.png "image.png") ### 关于优雅停机的总结 对于 Serverless 函数式计算,Amazon Lambda instance 的创建和销毁是比较频繁的,这必然产生大量的冷启动和资源初始化动作,单个 instance 的创建时冷启动会消耗一定的时间,资源的初始化也会耗费一些时间并对其他资源产生影响(比如大量的 MySQL 连接的创建会影响数据库的性能)。为了更好的降低冷启动的影响、更合理的利用资源,instance 会被尽可能的高效的复用。instance 复用就是当一个 instance 完成一个 event 请求后并不会立即释放,而是进入“等待”的状态。在一定时间范围内,如果有新的event请求被分配过来,则会直接调用对应的 handler 方法,而不需要再次初始化各类资源等,这在很大程度上减少了上述冷启动的影响,尽量利用复用功能是我们开发者优化 Lambda 函数代码的关注重点。在整个复用的过程中,这些 instance 一直是活着的状态,它们维持这个对外界资源的持有状态,直到 instance 被销毁。 我们来看一些典型的状态持有场景: - 数据库等连接,我们推荐在初始化的时候进行数据库连接的建立,后续尽量对连接进行复用,避免每次请求都创建连接,降低数据库频繁创建和销毁连接的压力。此外使用 [Amazon RDS](https://aws.amazon.com/cn/rds/?trk=cndc-detail) Proxy 也可以降低数据库频繁创建和销毁大量连接的压力。 - 长事务或者长耗时任务的处理,由于 Lambda 对单次 event 事件的处理时长不能超过 15 分钟。这些耗时比较长但是不确定15分钟内一定可以处理完的情况下,我们需要在 event 超时前进行主动撤销。 - 状态的同步或者通知,一些可中断可重新计算的分布式业务可能是由多个 Lambda instance 协调处理的,某个被即将中断的 instance 需要在死亡前主动告知其他 instance 自己的状态并保存断点状态。 这些场景都需要 pre stop,也就是在每次 Amazon Lambda 决定停止当前 instance 前调用某种善后逻辑,开发者负责实现相应逻辑以确保完成 instance 销毁前的必要操作,比如关闭数据库链接,回滚事务,上报、更新状态等。 对此我们的最佳实践是主动+被动结合的资源处理逻辑,主动处理逻辑就是本文介绍的优雅停机,主动释放资源;被动处理逻辑一般是资源侧管理的超时处理,比如 MySQL 服务器主动在一段时间后释放无 instance 认领的连接,Redis 对注册在内存里面的分布式锁状态进行超时释放等。 ### Amazon Lambda 上各种语言的有关优雅停机的支持列表 下面的表格是几种主流的语言在 Amazon Lambda runtime 中以 zip 方式运行时有关于优雅停机的支持情况: ![image.png](https://dev-media.amazoncloud.cn/074add8b128f46c586e7254c918a8eac_image.png "image.png") *注:截止 2023/12/22 日,已经 EOL 或者申明为即将 EOL 的编程语言版本没有包括在内。* ### 参考 - [Amazon Lambda 优雅停机](https://github.com/aws-samples/graceful-shutdown-with-aws-lambda?trk=cndc-detail) - [Amazon Lambda extensions](https://github.com/aws-samples/graceful-shutdown-with-aws-lambda?trk=cndc-detail) - [Understanding Amazon Lambda scaling and throughput](https://github.com/aws-samples/graceful-shutdown-with-aws-lambda?trk=cndc-detail) - [Understanding Amazon Lambda’s invoke throttling limits](https://github.com/aws-samples/graceful-shutdown-with-aws-lambda?trk=cndc-detail) ![开发者尾巴.gif](https://dev-media.amazoncloud.cn/64d5075cabba4239baa4de80101ab061_%E5%BC%80%E5%8F%91%E8%80%85%E5%B0%BE%E5%B7%B4.gif "开发者尾巴.gif")
0
目录
关闭