全部产品
云市场

Initializer 入口定义

更新时间:2019-06-04 19:36:02

Initializer 是函数的初始化逻辑入口,不同于请求处理逻辑入口的 handler。在有函数初始化的需求场景中,设置了 Initializer 后,函数计算首先调用 initializer 完成函数的初始化,成功后再调用 handler 处理请求;没有函数初始化的需求则可以跳过 initializer,直接调用 handler 处理请求。

适用场景

用户函数调用链路包括以下几个阶段:1)系统为函数分配计算资源;2)下载代码;3)启动容器并加载函数代码;4)用户函数内部进行初始化逻辑;5)函数处理请求并将结果返回。其中1,2,3步是系统层面的冷启动开销,通过对调度以及各个环节的优化,函数计算(FC)能做到负载快速增长时稳定的延时。更多详情,请参阅 函数计算系统冷启动优化第4步是函数内部初始化逻辑,属于应用层面的冷启动开销,例如深度学习场景下加载规格较大的模型、数据库场景下连接池构建、函数依赖库加载等等。为了减小应用层冷启动对延时的影响,函数计算推出了 initializer 接口,系统能识别用户函数的初始化逻辑,从而在调度上做相应的优化。

功能价值

引入 initializer 接口的价值:

  • 分离初始化逻辑和请求处理逻辑,程序逻辑更清晰,让用户更易写出结构良好,性能更优的代码。

  • 用户函数代码更新时,系统能够保证用户函数的平滑升级,规避应用层初始化冷启动带来的性能损耗。新的函数实例启动后能够自动执行用户的初始化逻辑,在初始化完成后再处理请求。

  • 在应用负载上升,需要增加更多函数实例时,系统能够识别函数应用层初始化的开销,更精准的计算资源伸缩的时机和所需的资源量,让请求延时更加平稳。

  • 即使在用户有持续的请求且不更新函数的情况下,FC系统仍然有可能将已有容器回收或更新,这时没有平台方(FC)的冷启动,但是会有业务方冷启动,Initializer可以最大限度减少这种情况。

Initializer 接口规范

各个 runtime 的 initializer 接口有以下共性:

  • 无自定义参数

    Initializer 不支持用户自定义参数,只能获取函数计算提供的 context 参数中的变量进行相关逻辑处理,详细介绍请参考 context文档

  • 无返回值

    用户无法从 invoke 的响应中获取 initializer 预期的返回值。如果 initializer 执行失败,用户能够通过 response 中的 X-Fc-Error-Type 和 body 来确认 initializer 无法成功执行的出错类型,建议开启 Logging 功能,便于错误定位。

  • 超时时间

    用户可单独设置 initializer 的超时时间,与 handler 的超时相互独立,但最长不超过 300 秒。

  • 执行时机

    运行函数逻辑的进程称之为函数实例,运行在容器内。系统会根据用户负载伸缩函数实例。每当有新函数实例创建时,系统会首先调用 initializer。系统保证一定 initializer 执行成功后才会执行 handler 逻辑。

  • 最多成功执行一次

    系统保证每个函数实例启动后只会成功执行一次 initializer 。如果执行失败,那么该函数实例在收到 Invoke 请求之后都会先执行 initializer。一旦执行成功,那么该实例的生命周期内不会再执行 initializer ,收到 Invoke 请求之后只执行请求处理函数。

  • initializer 入口命名

    除 Java 外,其他 runtime 的 initializer 入口命名规范与原有的 处理函数入口命名 保持一致,格式为 [文件名].[ initializer 名],其中 initializer 名可自定义。Java 需要定义一个类并实现函数计算预定义的初始化接口。

  • 计量计费

    Initializer 的执行时间也会被计量,用户需要为此付费,initializer 只对执行时间和公网流量这两项进行计费,计量规则不变,详情参考 计费方式

Initializer 入口

下文将函数计算目前所支持语言中对 initializer 入口的定义以及参数意义进行介绍,目录如下:

Nodejs

函数计算目前支持以下 Nodejs 运行环境:

  • Nodejs 6.1 (runtime = Nodejs6 )
  • Nodejs 8.9 (runtime = Nodejs8 )

initializer 入口: index.initializer

Initializer 入口 格式为 [文件名].[initializer 名]。例如,实现 initializer 接口时指定的 Initializer 入口index.initializer,那么函数计算会去加载 index.js 中定义的 initializer 函数。

在函数计算服务中使用 Nodejs 编写 initializer 逻辑,需要定义一个 Nodejs 函数作为 initializer 入口,一个最简单的 initializer 示例如下:

  1. exports.initializer = function(context, callback) {
  2. callback(null, '');
  3. };

如果您需要在 HTTP 触发器中使用 initializer,HTTP 触发器和 initializer 的结构和参数都保持不变。详细信息请参考 Nodejs 函数接口

  • 函数名

    exports.initializer需要与实现 initializer 接口时的 Initializer 字段相对应:例如创建函数时指定的 Initializer 入口 为index.initializer,那么函数计算会去加载index.js中定义的initializer函数。

  • context 参数

    context 参数中包含一些函数的运行时信息(例如 request id / 临时 AK / function meta 等)。其类型是 object,具体结构和使用在下面的 Nodejs guide 介绍。

  • callback 参数

    callback 参数用于返回调用函数的结果,其签名是 function(err, data),与 Nodejs 中惯用的 callback 一样,它的第一个参数是 error,第二个参数 data。如果调用时 err 不为空,则函数将返回 HandledInitializationError,由于屏蔽了初始化函数的返回值,所以 data 中的数据是无效的,可以参考上文的示例设置为空。

Python

函数计算目前支持以下 Python 运行环境:

  • Python 2.7 (runtime = python2.7)
  • Python 3.6 (runtime = python3)

initializer 入口: main.my_initializer

Initializer 入口 格式为 “[文件名].[initializer 名]”。例如实现 initializer 接口时指定的 Initializer 入口main.my_initializer,那么函数计算会去加载 main.py 中定义的 my_initializer 函数。

在函数计算服务中使用 Python 编写 initializer,需要定义一个 Python 函数作为 initializer 入口,一个最简单的 initializer 示例如下:

  1. def my_initializer(context):
  2. print("hello world!")

如果您需要在 HTTP 触发器中使用 initializer 功能,HTTP 触发器和 initializer 的结构和参数都保持不变。详细信息请参考 Python 函数接口

  • 函数名

    my_initializer 需要与实现 initializer 接口时的 Initializer 字段相对应:实现 initializer 接口时指定的 Initializer 入口main.my_initializer ,那么函数计算会去加载 main.py 中定义的 my_initializer 函数。

  • context 参数

    context 参数中包含一些函数的运行时信息(例如 request id / 临时 AK / function meta 等)。其类型是 FCContext,具体结构和使用在下面的 Python guide 介绍。

Php

函数计算目前支持以下 Php 运行环境:

  • Php 7.2 (runtime = Php7.2)

Initializer 入口: main.my_initializer

Initializer 格式为 “[文件名].[initializer 名]”。例如创建函数时指定的 initializermain.my_initializer,那么函数计算会去加载 main.php 中定义的 my_initializer 函数。

在函数计算服务中使用 Php 实现 initializer 接口 ,需要定义一个 Php 函数作为 initializer 入口,一个最简单的 initializer 示例如下:

  1. <?php
  2. function my_initializer($context) {
  3. echo 'hello world' . PHP_EOL;
  4. }
  5. ?>

如果您需要在 HTTP 触发器中使用 initializer 功能,HTTP 触发器和 initializer 的结构和参数都保持不变。详细信息请参考 Python 函数接口

  • 函数名

    my_initializer 需要与实现 initializer 接口时的 initializer 字段相对应:例如实现 initializer 接口时指定的 Initializer 入口main.my_initializer ,那么函数计算会去加载 main.php 中定义的 my_initializer 函数。

  • context 参数

    context 参数中包含一些函数的运行时信息(例如 request id / 临时 AK / function meta 等)。其类型是 FCContext,具体结构和使用在下面的 Php guide 介绍。

Java

函数计算目前支持以下Java运行环境:

  • OpenJDK 1.8.0 (runtime = java8)

Initializer 入口: example.HelloFC::initialize

Initializer 的格式为 {package}.{class}::{method}。例如包名是 example,类名是 HelloFC,那么实现 initializer 接口时指定的 Initializer 入口为 example.HelloFC::initialize。

在函数计算服务使用 Java 编程,需要定义一个类并实现函数计算预定义的接口,一个最简单的 initializer 示例如下:

  1. package example;
  2. import com.aliyun.fc.runtime.Context;
  3. import com.aliyun.fc.runtime.FunctionComputeLogger;
  4. import com.aliyun.fc.runtime.FunctionInitializer;
  5. import java.io.IOException;
  6. public class HelloFC implements FunctionInitializer {
  7. @Override
  8. public void initialize(Context context) throws IOException {
  9. FunctionComputeLogger logger = context.getLogger();
  10. logger.debug(String.format("RequestID is %s %n", context.getRequestId()));
  11. }
  12. }
  • 包名/类名

    包名和类名可以是任意的,但是需要与创建函数时的 “initializer” 字段相对应:上面的例子包名是 “example”,类名是 “HelloFC”,那么创建函数时指定的 Initializer 为 example.HelloFC::initialize,”Handler” 的格式为 {package}.{class}::{method}

  • 实现的接口

    用户的代码中必须要实现函数计算预定义的接口。上面的例子中实现了 FunctionInitializer,函数参数没有 input 和 output。关于函数接口的更详细的介绍参考下面的 函数接口

  • context 参数

    context 参数中包含一些函数的运行时信息(例如 request id / 临时 AK / function meta 等)。其类型是 com.aliyun.fc.runtime.Context,具体结构和使用在下面的 Java guide介绍。

使用流程和处理函数的使用是一致的,具体可以参考 函数入口定义 的 Java runtime 相关介绍。

initializer 比 global variable 更适合初始化

首先看一个简单的 demo 了解两种编写方式,下面函数的功能为连接数据库(初始化逻辑)并查询数据(处理逻辑)。

initializer 编写方式:

  1. import pymysql.cursors
  2. connection = None
  3. # my_initializer: initializer function
  4. def my_initializer(context):
  5. global connection
  6. connection = pymysql.connect(host='localhost',
  7. user='user',
  8. password='passwd',
  9. db='db',
  10. charset='utf8mb4',
  11. cursorclass=pymysql.cursors.DictCursor)
  12. def my_handler(event, context):
  13. global connection
  14. with connection.cursor() as cursor:
  15. # Read a single record
  16. sql = "SELECT count(*) FROM `users`"
  17. cursor.execute(sql)
  18. result = cursor.fetchone()
  19. return result

global variable 编写方式:

  1. import pymysql.cursors
  2. initialized = False
  3. def my_handler(event, context):
  4. global initialized
  5. if not initialized:
  6. connection = pymysql.connect(host='localhost',
  7. user='user',
  8. password='passwd',
  9. db='db',
  10. charset='utf8mb4',
  11. cursorclass=pymysql.cursors.DictCursor)
  12. initialized = True
  13. with connection.cursor() as cursor:
  14. # Read a single record
  15. sql = "SELECT count(*) FROM `users`"
  16. cursor.execute(sql)
  17. result = cursor.fetchone()
  18. return result

多数场景下,用户可以将函数逻辑分为初始化逻辑和处理逻辑,其中初始化逻辑可以是深度学习场景下加载规格较大的模型、数据库场景下连接池构建、函数依赖库加载等等,初始化逻辑往往会产生应用层冷启动。使用 initializer 接口和 global variable 方式都可以保证函数的初始化逻辑仅执行一次,达到优化应用层冷启动的效果,但会在以下场景产生不同的效果:

  • Invoke 请求触发函数计算(FC)后台创建新的函数实例 / 负载上升创建更多实例:在使用 initializer 功能时,可以在 invoke 请求到来之前完成初始化逻辑,保证应用层冷启动不会对处理函数的功能和性能产生影响。global variable 方式的初始化逻辑和处理逻辑执行时机都是在 invoke 请求到来时连续执行的,初始化逻辑执行时间的长短往往会对处理逻辑产生影响。

  • 函数升级:使用 initializer 功能后系统能够保证用户函数的平滑升级,规避应用层初始化冷启动带来的性能损耗。新的函数实例启动后能够自动执行用户的初始化逻辑,在初始化完成后再处理请求。global variable 方式在新的函数实例启动后并不会规避应用层初始化冷启动。

  • FC系统周期性回收或更新已有容器:即使在用户有持续的请求且不更新函数的情况下,FC系统仍然有可能将已有容器回收或更新,这时没有平台方(FC)的冷启动,但是会有业务方冷启动,Initializer可以最大限度减少这种情况。global variable 方式并不会减少这类情况。