我们知道在启动一个Java项目的时候普遍都比较慢,这是因为一个典型的java项目的启动会涉及到JVM启动、类路径组件扫描、类加载、对象创建和初始化、依赖注入、class字节码JIT编译等耗时环节,直到全部的工作就绪后才能对外服务。以一个SpringBoot项目为例,一般典型的启动时间在几十秒到数分钟不等。如果Java Spring项目在普通的Docker环境中运行,程序是长时间运行的,冷启动频次较低还可以接受;如果Spring项目在AWS Lambda中运行,程序可能会跟随请求压力动态不断的创建和销毁,冷启动的影响就比较大。
每个环境的生命周期包括三个主要阶段:Init
、Invoke
和 Shutdown
。其中,Init
阶段引导函数的运行时,并运行函数的静态代码。在许多情况下,这些操作是在几毫秒内完成的,不会以任何明显的方式延长阶段。在其余情况下,出于多种原因,它们可能需要相当长的时间。首先,初始化某些语言的运行时可能很昂贵。例如,结合使用 Java 运行时和框架(如 Spring Boot、Quarkus 和 Micronaut)的 Lambda 函数的 Init
阶段有时会长达 10 秒钟(这包括依赖注入、函数代码编译和类路径组件扫描)。其次,静态代码可能会下载一些机器学习模型,预先计算一些参考数据,或者与其他 AWS 服务建立网络连接。在为特定 Lambda 函数启用 Lambda SnapStart 后,发布该函数的新版本将触发优化过程。该过程会启动您的函数并运行整个 Init
阶段。然后,它获取内存和磁盘状态的不可变的加密快照,并缓存以供重用。随后调用该函数时,将根据需要以区块形式从缓存中检索状态,并用于填充执行环境。这种优化使调用时间更快且更具可预测性,因为创建全新的执行环境不再需要专用的 Init
阶段。
snapstart开启后,对Java程序的优化如下图:
- 预先进行程序的启动,完成初始化后立即创建一个snap保存下来供后续使用,这个过程相当于预热了程序的启动
- 如果有event触发lambda的执行,lambda会直接使用snap来恢复出现一个lambda instance,由于snap完成了最耗时的环节,恢复所需的时间相当于在程序预热的基础上快速的恢复运行。冷启动时间因此减少了。
关于SnapStart的更多细节可以看:Improving startup performance with Lambda SnapStart
在AWS SAM中完成Java SnapStart
使用AWS SAM为java程序开启SnapStart非常简单,只需要在template.yaml
中完成如下的设置即可 。
java程序部署到lambda后,会马上被启动并创建snap快照,这个快照是被后续lambda instance冷启动使用的,snap快照也会被周期性的自动重新创建。
后续如果有事件触发lambda的执行,那么lambda insatance 会读取snap快照,立即恢复出就绪环境。
SnapStart和Graval的加速原理的区别
首先GraalVM最大的特点使用AOT(ahead-of-time)思想,可以把传统的java项目直接编译为GraalVM Native Image程序。通过将Java和基于Java字节码的应用编译为原生二进制可执行文件,这样用java和C/C++等编译型语言一样直接在OS上运行起来,无需JVM即可直接运行。Native Image二进制可执行文件支持近乎瞬时的启动,内存占用低,可零预热地达到峰值性能。GraalVM支持所有主要微服务框架,包括 Helidon、Micronaut、Quarkus 和 Spring Boot。
AWS Lambda SnapStart目前只能用在AWS Lambda无服务器函数计算平台,并且并不是AOT思想,而生快照思想。在提前完成类加载,对象创建和初始化等操作后,把当前的就绪状态打一个快照,实现就绪状态的冻结;后续需要按需启动时,直接用这个快照快速的还原出就绪状态,规避了启动过程中最耗时的缓解。恢复后程序还是在标准的JVM中,SnapStart技术的优点是兼容性最大程度的被保证,运行特性和我们平常的使用特性别无二致。