触发时机

类似于 LoadingCache

  1. 首次请求
  2. 容器实例在服务请求后被回收

启动过程

image-20240316112638848

容器创建

  1. 当所有容器实例都在处理请求时,需要向集群申请创建新的容器
  2. 函数计算平台会支持多种语言的运行时
    • 这些运行时一般来说会打包成一个镜像,然后以 DeamonSet 的方式运行在 Kubernetes 中
    • 在冷启动时,会根据不同的参数请求,动态挂载所需的运行时到对应的运行路径

代码包 / 层依赖

  1. 是整个冷启动耗时比较长的过程
  2. 函数计算本身不具备持久化的能力,代码包和层依赖通常都是从其它存储服务端拉取
  3. 代码包通常是压缩包的形式,下载到本地后,再解压

环境变量 / 参数文件

  1. 耗时相对较短
  2. 主流的函数计算平台往往提供了环境变量注入的能力,发生在冷启动阶段
  3. 运行时以及容器本身还需要准备一些参数配置文件

VPC 打通 / 资源准备

  1. 如果用户还为函数接入了私有网络,还需要为容器进行一些 VPC 网络打通的初始化工作
  2. 如果用户使用了类似分布式文件系统等功能,还需要进行挂载

运行时初始化

  1. 通常指的是云厂商标准的 Runtime 环境的启动过程
  2. 受编程语言类型的影响比较大(JVM 比较慢)

用户代码初始化

  1. 用户的自定义逻辑代码的加载,即某些业务逻辑 init 的过程
  2. 函数计费都是从程序执行开始计费的,即 JVM 的准备过程是不收费的

启动时长

  1. 资源调度容器创建,一般在秒级完成
  2. 代码包和依赖层的下载过程,取决于代码大小以及是否有加速
  3. VPC 网络的打通过程,主要是弹性网卡路由下发耗时,通常在秒级
  4. 运行时与用户代码初始化过程

启动优化

平台侧

资源调度 / 容器创建

  1. 容器启动时会先检查本地有无相关镜像,如没有,需要从远端仓库拉取
  2. 传统的容器运行时需要将全量的镜像数据下载后再解压,实际可能仅需要部分数据
    • 容器启动耗时比较长
    • 如果集群规模过大,下载解压环节会造成较大的网络、磁盘读写压力
  3. 镜像加速:主要解决传统容器需要提前下载镜像再启动而导致耗时较长的问题
    • 按需加载
      • 使用加速镜像版本,配置加速规则和标签
      • 通过按需加载的模式,免于全量下载镜像,避免带宽浪费,提高分发效率
    • P2P 加速
      • 利用计算节点内网带宽资源,在节点之间分发镜像,降低镜像仓库的压力
    • 多镜像仓库
      • 进一步缓解单仓库的存储和服务压力

代码包 / 层依赖

  1. 压缩包缓存
    • 通过实现本地缓存来提高代码包以及层依赖的获取速度
    • 在函数第一次冷启动处理某个请求时,会将代码包下载到 Node 上,并通过特殊编码进行标记
    • 同一个 Node 上有其它函数实例需要使用同一份代码时,可以根据标记码看本地是否有缓存
  2. 常用依赖内置化
    • 根据用户的使用场景,将一些常用的依赖提前内置化
    • 这些常用的依赖一般都会在构造容器基础镜像时一起构建
  3. 极致压缩
    • 对代码包进行二次压缩,让代码包变得更小

跨 VPC 加速

  1. 函数运行在一个专有的 VPC 集群内,如果函数需要访问用户 VPC 集群内的资源
  2. 传统方式:通过动态创建弹性网卡 ENI 来实现跨 VPC 的访问
    • 弹性网卡的创建耗时比较久,导致冷启动耗时急剧增加
    • 弹性网卡占用用户 VPC 的 IP 资源,如果没有空闲的资源,会导致创建失败
    • 造成弹性网卡较大的浪费

image-20240318150918076

在集群 VPC 内部创建代理

  1. 核心在于使用 IP 隧道技术
    • 平台可以在保持函数原始 IP 地址的情况下,将流量通过 Proxy 代理节点的方式触达用户端 VPC
  2. 函数平台会为关联了 VPC 的函数创建轻量的虚拟机
    • 弹性网卡绑定到代理虚拟机上,所有关联该 VPC 网络的流量最终都会通过 Proxy 进行发送
  3. 为了确保服务的稳定,Proxy 通常采用主备的方式进行
  4. 使用该方式,可以通过监听用户在 Console 关联 VPC 的操作时,提前进行创建
    • 在函数第一次处理请求时,只需要转发,可以带来一个数量级的性能提升
  5. 瑕疵:需要额外消耗 Proxy 的 Node 资源

image-20240318151534234

提前预加载

基于函数调用的场景进行提前预测

image-20240318170444913

基于函数版本进行预测

image-20240318170752864

提前加载镜像

  1. 对于一些大镜像(如 10G),提前分发,然后通过单机侧的 Agent 上报标记
  2. 当流量到来时,直接分发到预加载过的 Node 的容器上,以快速应对函数的执行处理

用户侧

  1. 合理控制代码包大小
    • 拉取代码包是整个冷启动过程中最耗时的部分
  2. 选择性能较高的运行时
    • Python/Node.js/Go > JVM
  3. 成本可控范围内合理使用预留实例
    • 可以为某个函数根据实际需要申请固定数量的预留实例
    • 每个实例都已经提前准备好函数代码,直到用户主动释放,都会始终处于常驻状态
      • 当函数收到请求后,会优先调度预留实例并且以热启动的方式进行处理
      • 直到该函数的所有预留实例都处于工作状态,才会将请求调度到一个非预留实例冷启动的方式执行
    • 使用预留实例来解决冷启动的问题,需要考虑闲置成本
  4. 定时激活延时敏感较高的函数实例
    • 通过一个定时触发器来提前预热服务,而函数平台通常会有一定的时间来保留热的容器
    • 如果函数涉及到业务处理,可以通过标志位,来区分预热非预热的请求
  5. 合理利用本地缓存
    • 适用于 AI 场景下的数据训练任务
    • 每次数据拉取前都提前从本地磁盘空间判断数据是否已经存在,可以避免每次数据拉取造成的启动延迟