FaaS冷启动
冷启动是什么?
当请求被调度到函数实例上时,如果这个函数实例在上一次执行完代码后没有被回收,那么接收到请求后只需要复用这个实例进行代码的执行即可,这个过程被称为热启动过程。
如果服务是首次请求,或者是容器实例在服务请求后被回收了,就会触发冷启动。
第一步,容器创建。这一步通常会体现在扩缩容过程,当所有容器实例都在处理请求时,就需要向集群申请创建新的容器。
这里需要提一下,函数计算平台通常会支持多种语言的运行时,这些运行时一般来说会打包一个镜像,以 DeamonSet 的方式运行在 Kubernetes 集群中。在冷启动的时候,会根据不同的参数请求,动态挂载所需的运行时到对应的运行路径下。
第二步,代码、层依赖下载。这一步应该是整个冷启动耗时比较长的过程。函数计算本身并不具备持久化的能力,代码包和层依赖通常都是从其他存储服务端拉取。
因此,这一步的耗时会受到用户上传代码包大小、网速等因素的影响。当然,代码包通常是压缩包的形式,下载到本地后,还需要解压。
第三步,环境变量、参数文件准备。主流的函数计算平台往往提供了环境变量注入的能力,这个过程就发生在冷启动阶段。除此之外,运行时以及容器本身可能还需要准备一些参数配置文件等。
第四步,用户 VPC 以及相关资源准备。如果用户还为函数接入了私有网络,那么还需要为容器进行一些 VPC 网络打通的初始化工作。同时,如果用户使用了类似分布式文件系统这类功能,还需要进行挂载,也会产生一部分额外耗时。
第五步,运行时初始化。通常指的是云厂商标准的 Runtime 环境的启动过程。这一步受编程语言类型的影响比较大,比如 Java 类型的代码由于要启动 JVM,启动相对其他语言就比较慢。
第六步,用户代码初始化。这一步有的时候和运行时初始化容易混淆,为什么这么说呢?比如编译型的语言,打包后就是一个完整的可执行程序,在运行时启动的时候,就已经有一部分工作加载起来了。
那么,如果是解释型语言,就可以把它理解为用户代码包的加载过程。 针对这一步,可以理解为用户的自定义业务逻辑代码的加载,某些业务逻辑 init 的过程,比如写在 Handler 之外的数据库连接的创建、Handler 方法内的缓存初始化等。
影响冷启动时长的因素有哪些?
资源调度和容器创建过程: 一般在秒级别时间完成;
代码和依赖层的下载过程:取决于代码大小以及是否有加速,一般从毫秒到秒级别不等;
VPC 网络的打通过程:主要是弹性网卡和路由下发耗时,通常在秒级别;
运行时与用户代码初始化过程:和用户比较贴近,依据不同的语言,启动时间会有所影响,大概在毫秒到秒之间不等。
优化冷启动的耗时时长方法
平台侧:
1.镜像加速手段优化资源调度和容器创建:如果你使用的是云平台厂商的 Kubernetes 调度服务,还可以通过配置预加载脚本的方式来进一步并行处理你的容器镜像;
2.包的缓存、极致的压缩和内置化通用依赖:减少用户在运行时刻的依赖;
3.利用代理模式的方法进行 VPC网络加速:VPC 的网络加速通常只会在跨 VPC 的业务场景用到,通过 IP 隧道技术 + 代理模式的方法,让前几年一直比较诟病的网络延时问题降低到毫秒级别,这是 Serverless 函数计算领域的一个比较大的突破;
4.通过函数实例的预测算法,也能从一定程度上帮助用户解决冷启动问题。
开发侧:
1.合理控制代码包大小:这里需要我们尽量改掉原来在传统编码部分不好的习惯,尽可能精简代码;
2.选择性能较高的运行时:相信通过 Golang 和 Java 的对比,你也能直观地感受到语言选择带来的性能差异;
3.预留实例和定时预热:这两个方向,也是用户和平台相互协作的过程,更需要你灵活使用;
4.合理利用本地缓存:对于一些大的数据拉取场景,可以适当的使用缓存的方式,避免频繁拉取数据,造成的网络带宽和执行耗时,这不仅影响你的服务处理性能,还会额外增加费用成本。