
背景 未引入初始化函数功能前如何解决函数计算对上传代码包大小的限制问题? 阿里云 函数计算 为了保证函数计算系统的可用性更高,对用户上传的代码包做了 大小限制,要求原始代码包大小不超过 250MB,压缩后的代码包大小不超过 50MB。由于用户的函数逻辑可能需要大量的依赖库,所以代码包很容易达到函数计算设定的阈值。在未引入 initializer 接口之前,为解决这个问题,需要对代码包进行分类,除项目代码和少量依赖库可以在创建函数时上传,其他依赖库都需要预先上传到 OSS 中,当函数被触发时,再从 OSS 上下载并存放到磁盘指定目录。 解决了函数计算对代码包大小的限制后对用户有什么影响? 用户将部分依赖库预先上传到 OSS,并在函数被触发执行时开始从 OSS 上加载依赖, 这类依赖的加载操作均可定义应用层冷启动,当加载依赖结束后,应用层冷启动才结束,函数的处理逻辑才开始执行,应用层冷启动的开销往往会导致毛刺的产生,影响函数的性能。 如何做到既可以解决函数计算对上传代码包的限制问题,又不影响函数的性能呢? 为同时解决上述功能和性能问题,函数计算推出 initializer 功能。您可以将从 OSS 加载代码包的函数逻辑放在 initializer 函数中。函数计算保证在处理函数执行之前,initializer 函数成功执行过一次并且只有一次,从而可以保证在处理请求到达之前代码已全部加载完成,并且不会对处理函数逻辑的功能和性能产生影响。 案例实践 大部分场景中,用户代码包过大都是由于所依赖的库过大导致的,下文通过 python2.7 演示一个对图片旋转的案例。 图片旋转需要用到 tensorflow 和 opencv 库,两个库的大小还不及函数计算设置上传代码包大小的阈值,这里我们可假设已经超过了代码包大小的限制。 上传依赖库 编写函数 部署 测试 上传依赖库 本案例需要安装的依赖库有 tensorflow 和 opencv-python,需要提前在本地下载并打包上传到 OSS。推荐使用 fcli 工具的 sbox 命令,下面以 runtime 为 python2.7 进行操作: cd <'此项目的根目录中'> mkdir applib // 创建存储所有应用依赖的目录 fcli shell // fcli version >= 0.25 sbox -d applib -t python2.7 pip install -t $(pwd) tensorflow==1.8.0 pip install -t $(pwd) opencv-python 完成之后 exit 退出沙盒环境,并执行 exit 退出fcli。 编写函数 编写函数需要注意以下几点: 定义 initializer 入口并实现其接口,将从 OSS 加载代码包的逻辑放入初始化函数中。 由于对图片操作所用到的两个库需要在初始化函数中临时加载,所以可以把对函数处理的操作单独放到一个文件中,在处理函数中调用即可。综合上面两点考虑,我们的代码结构如下: project root └─ code ├─ loader.py # 处理函数逻辑和 initializer 逻辑 └─ index.py # 图片旋转逻辑 └─ pic └─ e2.jpg # 被操作图片 loader.py 代码如下: # -*- coding:utf-8 -*- import sys import zipfile import os import oss2 import imp import time app_lib_object = os.environ['AppLibObject'] app_lib_dir = os.environ['AppLibDir'] model_object = os.environ['ModelObject'] model_dir = os.environ['ModelDir'] local = bool(os.getenv('local', "")) print 'local running: ' + str(local) def download_and_unzip(objectKey, path, context): creds = context.credentials if (local): print 'thank you for running function in local!!!!!' auth = oss2.Auth(creds.access_key_id, creds.access_key_secret) else: auth = oss2.StsAuth(creds.access_key_id, creds.access_key_secret, creds.security_token) endpoint = os.environ['Endpoint'] bucket = os.environ['Bucket'] print 'objectKey: ' + objectKey print 'path: ' + path print 'endpoint: ' + endpoint print 'bucket: ' + bucket bucket = oss2.Bucket(auth, endpoint, bucket) zipName = '/tmp/tmp.zip' print 'before downloading ' + objectKey + ' ...' start_download_time = time.time() bucket.get_object_to_file(objectKey, zipName) print 'after downloading, used %s seconds...' % (time.time() - start_download_time) if not os.path.exists(path): os.mkdir(path) print 'before unzipping ' + objectKey + ' ...' start_unzip_time = time.time() with zipfile.ZipFile(zipName, "r") as z: z.extractall(path) print 'unzipping done, used %s seconds...' % (time.time() - start_unzip_time) def initializer(context): if not local: download_and_unzip(app_lib_object, app_lib_dir, context) download_and_unzip(model_object, model_dir, context) sys.path.insert(1, app_lib_dir) def handler(event, context): desc = None fn, modulePath, desc = imp.find_module('index') mod = imp.load_module('index', fn, modulePath, desc) request_handler = getattr(mod, 'handler') return request_handler(event, context) index.py 代码如下: # -*- coding:utf-8 -*- import cv2 import oss2 import tensorflow as tf def handler(event, context): filename="pic/e2.jpg" image = cv2.imread(filename, 1) x = tf.Variable(image, name='x') model = tf.initialize_all_variables() with tf.Session() as session: x = tf.transpose(x, perm=[1, 0, 2]) session.run(model) result = session.run(x) cv2.imwrite("/tmp/pic.jpg", result) cv2.waitKey (0) auth = oss2.Auth(<'Your access_key_id'>, <'Your access_key_secret'>) bucket = oss2.Bucket(auth, <'Your endpoint'>, <'Your bucket'>) bucket.put_object_from_file('picture', '/tmp/pic.jpg') return 'success' pic/e2.jpg 如下: 部署 你可以通过 SDK、API、fun、控制台 等多种方式进行部署,这里直接通过控制台上传代码包。 首先选择一个 region 并创建一个函数。 在新建函数的代码配置中选择文件夹上传,点击你的项目目录全部上传即可。 在环境配置中配置初始化函数,将函数入口和 initializer 入口分别设置为 loader.handler 和 loader.initializer,并配置合理的处理函数超时时间和初始化超时时间,配置结束后点击下一步即可。 测试 功能测试 在函数计算控制台执行函数,发现当引入 initializer 功能后,首次执行时间为 3205ms: 为验证函数是否执行成功,我们到 OSS 控制台查看指定 bucket 下面是否存在名为 picture 的 object,并验证是否旋转成功,图片如下,由此可见函数执行成功。 性能测试 为验证 initializer 函数不仅可以解决上传代码包大小受限的问题,且可以规避加载代码包的冷启动时间,下面将对函数进行改造,关闭初始化功能。 在函数概览页面中点击修改函数,并关闭 开启初始化功能 按钮: 将 initializer 函数中的逻辑放到到处理函数中,并删除原有初始化函数,点击执行查看运行结果: 函数产生日志如下: 从上图中可以发现,在未引入 initializer 函数之前,首次函数函数执行时间约为 14159ms,从 OSS 加载过大的代码包会占用大量的冷启动时间,并且影响函数的性能。 总结 从测试的结果可以发现,initializer 接口的引入解决了文章开篇提到的问题。即解决函数计算对上传代码包的限制问题,又规避了加载代码包的冷启动时间,极大的提升函数性能。 最后欢迎大家通过扫码加入我们用户群中,使用过程中有问题或者有其他问题可以在群里提出来。函数计算官网客户群(11721331)。
背景 深度学习场景使用函数计算典型案例 阿里云 函数计算 客户 码隆科技 是一家专注于深度学习与计算机视觉技术创新的公司。当码隆的客户上传大量图像数据后,需要尽快把图像按照客户指定的方式处理,包括商品识别,纺织面料等柔性材质识别分析,内容审查,以图搜图等等。图像处理基于码隆预先训练好的深度学习模型,要求在短时间内准备大量的计算资源进行大规模并行处理。客户将深度学习推理逻辑实现为函数,在函数中加载模型后对图像数据进行处理。通过函数计算提供的大规模计算能力,客户能够短时间处理大量图像,平稳应对峰值压力。更多详细案例请见 函数计算客户案例。 深度学习场景的客户在使用函数计算服务中更希望平台做哪些改进? 深度学习场景下加载模型是主要的应用层冷启动开销,模型的规格多为 500MB+,应用层冷启动开销往往会导致毛刺的产生,为归避这类问题,函数计算引入了 initializer 接口来解决应用层冷启动开销带来的毛刺问题。 功能简介 Initializer 编程模式为用户提供了 initializer 入口定义,便于用户将业务逻辑分为initializer函数和请求处理函数两部分。函数计算使用容器执行用户函数代码,这样的执行环境我们称之为函数实例。函数实例会在启动的时候能够自动执行 initializer 函数,进行业务层冷启动,成功之后,该实例收到用户的 Invoke 请求,就能够执行用户的请求处理函数了。 引入 initializer 接口的优势: 分离初始化逻辑和请求处理逻辑,程序逻辑更清晰,让用户更易写出结构良好,性能更优的代码; 用户函数代码更新时,系统能够保证用户函数的平滑升级,规避应用层初始化冷启动带来的性能损耗。新的函数实例启动后能够自动执行用户的初始化逻辑,在初始化完成后再处理请求; 在应用负载上升,需要增加更多函数实例时,系统能够识别函数应用层初始化的开销,更精准的计算资源伸缩的时机和所需的资源量,让请求延时更加平稳; 即使在用户有持续的请求且不更新函数的情况下,FC系统仍然有可能将已有容器回收或更新,这时没有平台方(FC)的冷启动,但是会有业务方冷启动,Initializer 的引入可以最大限度减少这种情况; 案例实践 本实践以 函数计算部署机器学习遇到的问题和解法 这篇文章为基础,做了进一步改造和优化。下文将按照以下几个步骤讲解如何利用函数计算以高性能、低延时玩转深度学习场景下的识别手写数字案例: 安装依赖 训练模型 应用依赖的安装 上传依赖 将机器学习应用迁移至函数计算 引入 initializer 接口 部署 测试 功能测试 性能测试 安装依赖 训练模型 首先需要训练预期的模型,模型的训练可参考 这篇文章。按照文章中的步骤下载 MINIST 数据库和相关代码并开始训练模型,训练时长持续半小时左右,训练成功后的结构目录如下,其中 model_data 目录下的文件便是通过训练得到的模型。 project root ├── main.py ├── grf.pb └── model_data ├── checkpoint ├── model.data-00000-of-00001 ├── model.index └── model.meta 应用依赖的安装 本案例需要安装的应用依赖有 tensorflow 和 opencv-python,模型的训练和函数的处理逻辑都强依赖这两个库,训练模型可在本地直接操作,通过 pip 在本地安装两个依赖库即可,版本不限。由于函数运行在函数计算(FC)系统同样依赖这两个库,需要提前下载好依赖并打包上传到 OSS。推荐使用 fcli 工具的 sbox 命令,下面以 runtime 为 python2.7 进行操作: 目前 Pypi 上 tensorflow 最新版本为 1.11.0,为避免因版本问题影响您的实践,建议安装 1.8.0 版本。 cd <'此项目的根目录中'> mkdir applib // 创建存储所有应用依赖的目录 fcli shell // fcli version >= 0.24 sbox -d applib -t python2.7 pip install -t $(pwd) tensorflow==1.8.0 pip install -t $(pwd) opencv-python 完成之后 exit 退出沙盒环境,并执行 exit 退出fcli。 上传依赖 依赖和模型下载成功后需要进行压缩并上传到 OSS 中,以便后面函数可以直接从 OSS 下载即可,在项目的根目录执行下面两条命令可以得到 applib.zip 和 model_data.zip 两个 zip 压缩包。 cd applib && zip -r applib.zip * && mv applib.zip ../ ; cd .. cd model_data && zip -r model_data.zip * && mv model_data.zip ../ ; cd .. 下面提供了一个简单的上传 zip 包到 OSS 的模版,上传成功后删除本地的依赖和模型目录即可。 # -*- coding: utf-8 -*- import oss2 auth = oss2.Auth(<'Your access_key_id'>, <'Your access_key_secret'>) bucket = oss2.Bucket(auth, <'Your endpoint'>, <'Your bucket'>) bucket.put_object_from_file('applib.zip', <'Your applib.zip path'>) bucket.put_object_from_file('model_data.zip', <'Your model_data.zip path'>) 将机器学习应用迁移至函数计算 如何将本地机器学习应用进行改造并迁移到函数计算的流程在 将机器学习应用迁移至函数计算 中有详细的步骤,这篇文章中的改造并没有 initializer 的概念,您只需要关注 index.py 和 loader.py 是如何产生的,详细代码链接,改造后的目录结构如下,其中 index.py 存放了机器学习相关逻辑的代码,loader.py 存放了函数入口和加载依赖逻辑的代码。 project root └─── code ├── loader.py └── index.py └── pic └── e2.jpg e2.jpg 只是文章中提供的一个简单的数字 2 图片,如做验证性测试需要更多的素材可以通过 keras.js 平台手动绘制生成。 引入 initializer 接口 经过应用迁移处理后得到了一个可以运行在函数计算服务上的函数,很明显可以看到函数入口 loader.handler 中首先需要从 OSS 加载应用依赖(tensorflow、opencv)和资源依赖(模型),加载的过程都属于应用层冷启动,冷启动所耗费的时间在一定程度上和所需依赖的大小规格成正比。为避免后续处理逻辑受到应用层冷启动延时的影响,这里将加载依赖逻辑放入 initializer 函数中。 其中 index.py 文件保持不变,loader.py 文件需要进行如下改造: 添加 initializer 函数,initializer 入口便为 loader.initializer。 将对 download_and_unzip_if_not_exist 的调用从 handler 中更换到 initializer 函数中。 loader.py 经过改造后的代码如下: # -*- coding:utf-8 -*- import sys import zipfile import os import oss2 import imp import time app_lib_object = os.environ['AppLibObject'] app_lib_dir = os.environ['AppLibDir'] model_object = os.environ['ModelObject'] model_dir = os.environ['ModelDir'] local = bool(os.getenv('local', "")) print 'local running: ' + str(local) def download_and_unzip_if_not_exist(objectKey, path, context): creds = context.credentials if (local): print 'thank you for running function in local!!!!!' auth = oss2.Auth(creds.access_key_id, creds.access_key_secret) else: auth = oss2.StsAuth(creds.access_key_id, creds.access_key_secret, creds.security_token) endpoint = os.environ['Endpoint'] bucket = os.environ['Bucket'] print 'objectKey: ' + objectKey print 'path: ' + path print 'endpoint: ' + endpoint print 'bucket: ' + bucket bucket = oss2.Bucket(auth, endpoint, bucket) zipName = '/tmp/tmp.zip' print 'before downloading ' + objectKey + ' ...' start_download_time = time.time() bucket.get_object_to_file(objectKey, zipName) print 'after downloading, used %s seconds...' % (time.time() - start_download_time) if not os.path.exists(path): os.mkdir(path) print 'before unzipping ' + objectKey + ' ...' start_unzip_time = time.time() with zipfile.ZipFile(zipName, "r") as z: z.extractall(path) print 'unzipping done, used %s seconds...' % (time.time() - start_unzip_time) def initializer(context): if not local: download_and_unzip_if_not_exist(app_lib_object, app_lib_dir, context) download_and_unzip_if_not_exist(model_object, model_dir, context) sys.path.insert(1, app_lib_dir) def handler(event, context): desc = None fn, modulePath, desc = imp.find_module('index') mod = imp.load_module('index', fn, modulePath, desc) request_handler = getattr(mod, 'handler') return request_handler(event, context) 部署 本地开发已经完成,下面借助阿里云函数计算的工具 fun 可以进行一键部署,Fun 是一个用于支持 Serverless 应用部署的工具,它通过一个资源配置文件(template.yml),协助您进行开发、构建、部署操作,步骤如下: 去 release 页面对应平台的 binary 版本,解压就可以使用。或者使用 npm install @alicloud/fun * -g 也可以直接使用。 使用 fun config 配置 ak、region 等信息。 编写 template.yml fun deploy 部署 template.yml 文件如下: ROSTemplateFormatVersion: '2015-09-01' Transform: 'Aliyun::Serverless-2018-04-03' Resources: tensorflow: # 服务名 Type: 'Aliyun::Serverless::Service' Properties: Description: 'tensorflow demo' Policies: - AliyunOSSReadOnlyAccess initializer: # 函数名 Type: 'Aliyun::Serverless::Function' Properties: Handler: loader.handler # 处理函数入口 Initializer: loader.initializer # initializer 入口 CodeUri: ./code/ Description: 'tensorflow application!' Runtime: python2.7 MemorySize: 1024 Timeout: 300 InitializationTimeout: 60 EnvironmentVariables: Bucket: test-bucket # 替换为自己的 oss bucket Endpoint: 'https://oss-cn-hangzhou.aliyuncs.com' # 替换掉 OSS Endpoint AppLibObject: applib.zip AppLibDir: /tmp/applib ModelObject: model_data.zip ModelDir: /tmp/model 执行 fun deploy 会显示如下信息,部署成功后可到对应 region 下查看部署是否生效。 测试 功能测试 登陆函数计算 控制台 对应 region 下找到所创建的函数,连续执行两次查看执行结果如下。 首次执行 第二次执行 从以上图片可以看到首次函数执行时间为 3793ms,第二次函数执行时间为 810ms,执行结果都为 the predict is 2 ,从执行结果可以确认函数执行正确,但性能真的提高了吗?下面会有简单的性能测试做对比。 性能测试 这里对改造前的函数做同样的测试: 首次执行 首次执行日志 第二次执行 从以上图片可以看到首次函数执行时间为 17506ms,第二次函数执行时间为 815ms,通过日志可以发现首次触发函数执行大约 13s 花费在加载模型和依赖库上,函数的执行时间会随着模型和依赖库规格的增大而增大。由此可见,initializer 函数的引入会使得函数实例在首次启动时规避冷启动开销,降低函数执行时间,提高函数性能,并且不会对后续的请求产生任何影响。 总结 通过将深度学习场景下规格较大的模型、依赖库的加载等初始化逻辑进行提取放到 initializer 函数中可以极大的提升函数性能,规避用户系统/函数升级带来的冷启动开销,帮助用户实现业务系统的热升级。 最后欢迎大家通过扫码加入我们用户群中,使用过程中有问题或者有其他问题可以在群里提出来。函数计算官网客户群(11721331)。 参考文章: 1 :Tensorflow MINIST数据模型的训练,保存,恢复和手写字体识别2:函数计算部署机器学习遇到的问题和解法
背景 用户函数调用链路包括以下几个阶段:1)系统为函数分配计算资源;2)下载代码;3)启动容器并加载函数代码;4)用户函数内部进行初始化逻辑;5)函数处理请求并将结果返回。其中1,2,3步是系统层面的冷启动开销,通过对调度以及各个环节的优化,函数计算(FC)能做到负载快速增长时稳定的延时,细节详见 函数计算系统冷启动优化。第4步是函数内部初始化逻辑,属于应用层面的冷启动开销,例如深度学习场景下加载规格较大的模型、数据库场景下连接池构建、函数依赖库加载等等。为了减小应用层冷启动对延时的影响,函数计算推出了 initializer 接口,系统能识别用户函数的初始化逻辑,从而在调度上做相应的优化。 功能简介 现在用户能为函数设置 initializer 和 handler 两个入口,分别对应初始化逻辑和请求处理逻辑。系统首先调用 initializer 完成函数的初始化,成功后再调用 handler 处理请求。Initializer 是可选的,用户也可不实现,此时系统将跳过 initializer,直接调用 handler 处理请求。 引入 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 的执行时间也会被计量,用户需要为此付费; 支持 runtime 下文将对各个 runtime 的 initializer 编写规则进行介绍: Python Nodejs Php Java python 当使用 Python 进行编程时,可参考下面示例: # file :main.py # initializer: main.my_initializer def my_initializer(context): print("hello world!") 更多细节请参考 python 函数入口。 nodejs 当使用 Nodejs 进行编程时,可参考下面示例: // file :main.php // initializer : main.my_initializer exports.my_initializer = function(context, callback) { console.log('hello world!'); callback(null, ''); }; 更多细节请参考 nodejs 函数入口。 php 当使用 Php 进行编程时,可参考下面示例: // file: main.js // initializer: main.my_initializer <?php function my_initializer($context) { echo 'hello world!' . PHP_EOL; } ?> 更多细节请参考 php 函数入口。 java java 针对流式输入和通过泛型的方式自定义输入和输出的两种函数形式都支持 initializer,当需要在 JAVA runtime 中添加 initializer 功能时,需在原有的函数结构基础上额外实现 initializer 预定义的接口。 一个简单的流式输入和 initializer 结合的示例如下: package example; import com.aliyun.fc.runtime.Context; import com.aliyun.fc.runtime.FunctionComputeLogger; import com.aliyun.fc.runtime.StreamRequestHandler; import com.aliyun.fc.runtime.FunctionInitializer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class HelloFC implements StreamRequestHandler, FunctionInitializer { @Override public void initialize(Context context) { FunctionComputeLogger logger = context.getLogger(); logger.debug(String.format("RequestID is %s %n", context.getRequestId())); } @Override public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { FunctionComputeLogger logger = context.getLogger(); logger.debug(String.format("RequestID is %s %n", context.getRequestId())); output.write(new String("hello world!").getBytes()); output.flush(); } } 从上面的示例可以看到,在 Java runtime 中,用户的函数如果需要添加 initializer 功能,需在实现原有的 StreamRequestHandler 接口基础上,额外实现 initializer 的 FunctionInitializer 接口。 initializer 所属包名和类名可以自定义。"initializer" 的格式同为 {package}.{class}::{method},与 处理函数不同的是处理函数中的 method 为 handleRequest,initializer 的 method 为 initialize。根据定义可知此示例的 initializer 为 example.HelloFC::initialize 在通过泛型方式自定义输入输出的函数中,添加 initializer 和上面的步骤一致,更多细节可参考函数计算官方文档 java 函数入口。 开发指南 当使用 initializer 接口时,可以通过 API、SDK、控制台、函数计算提供的丰富工具链(fcli、fun等工具)进行创建和更新。 SDK 开发 支持 initializer 的 SDK 有 Nodejs、Python、Php、Java 。以 Nodejs runtime 和 Php SDK 结合为例,创建 initializer 并进行更新操作。 对 initializer 的更新可以从有更新到无,即关闭 initializer 功能。也可以从无更新到有,即开启 initializer 功能。 通过 Nodejs 编写两个简单的函数如下: // file: main.js exports.initializer = function(context, callback) { console.log('hello initializer!'); callback(null, ""); }; exports.handler = function(event, context, callback) { callback(null, string("initializer")); }; // file: new_main.js exports.newInitializer = function(context, callback) { console.log('hello new initializer!'); callback(null, ""); }; exports.handler = function(event, context, callback) { callback(null, string("new initializer")); }; 对文件 main.js 压缩为 main.zip,文件 new_main.js 压缩为 new_main.zip 通过 Php SDK 创建函数并进行更新,示例如下: <?php require_once __DIR__ . '/vendor/autoload.php'; use AliyunFC\Client; $fcClient = new Client([ "endpoint" => '<Your Endpoint>', "accessKeyID" =>'<Your AccessKeyID>', "accessKeySecret" =>'<Your AccessKeySecret>' ]); $serviceName = 'test_service'; $functionName = 'test_function'; // Create service. $fcClient->createService($serviceName); // Create function. $fcClient->createFunction( $serviceName, array( 'functionName' => $functionName, 'handler' => 'main.handler', 'initializer' => 'main.initializer', 'runtime' => 'php7.2', 'memorySize' => 128, 'description' => "test initializer", 'code' => array( 'zipFile' => base64_encode(file_get_contents(<Your main.zip>)), ), ) ); $invkRes = $fcClient->invokeFunction($serviceName, $functionName); // The value of $invkRes['data'] is 'initializer' $fcClient->updateFunction( $serviceName, $functionName, array( 'functionName' => $functionName, 'handler' => 'main.handler', 'initializer' => 'main.newInitializer', 'runtime' => 'php7.2', 'memorySize' => 128, 'description' => "test update initializer", 'code' => array( 'zipFile' => base64_encode(file_get_contents(<Your new_main.zip>)), ), ) ); $invkRes = $fcClient->invokeFunction($serviceName, $functionName); // The value of $invkRes['data'] is 'new initializer' // Delete function $fcClient->deleteFunction($serviceName, $functionName); // Delete service. $fcClient->deleteService($serviceName); fcli 开发 为支持创建、更新 initializer ,fcli 为引入了两个新的参数: -i —initializer string 设置 initializer,initializer 格式为 “文件名. initializer 名”。例如 hello_world.initializer 指定了 initializer 的调用入口为 hello_world.js 文件中的 initializer 函数。 -e —initializationTimeout int32 initializer 最大运行时间,单位为秒。 更多参数请参考 fcli开发文档 创建 initializer 操作如下: // 在相应service目录下 // 代码存储在oss上且包含 initializer,-t 指定runtime,-h 指定函数入口,-i 指定 initializer 入口,-e 指定 initializer 超时时间,-b 指定代码所在的oss bucket,-o 指定了代码在bucket中的object key >>> mkf myFunction -t nodejs6 -h myFunction.handler -i myFunction.initializer -e 60 -b ossBucketName -o objectKey 更新 initializer 操作如下: // 在相应service目录下 // 代码存储在 OSS 上且包含 initializer,-t 指定 runtime,-i 指定 initializer 入口,-e 指定 initializer 超时时间,-b 指定代码所在的 oss bucket,-o 指定了代码在 bucket 中的 object key // 将initializer 从 myFunction.initializer 更新至 myFunction.newInitializer >>> upf myFunction -t nodejs6 -i myFunction.newInitializer -e 30 -b ossBucketName -o objectKey // 将initializer 从 myFunction.newInitializer 更新至空,关闭 initializer 功能。 >>> upf myFunction -t nodejs6 -i "" -b ossBucketName -o objectKey // 将initializer 从空更新至 myFunction.newInitializer,开启 initializer 功能。 >>> upf myFunction -t nodejs6 -i myFunction.newInitializer -e 30 -b ossBucketName -o objectKey fun 部署 Fun 是一个用于支持 Serverless 应用部署的工具,能帮助您便捷地管理函数计算、API 网关、日志服务等资源。它通过一个资源配置文件(template.yml),协助您进行开发、构建、部署操作,详细介绍请参考 fun开发文档。 Fun 为 initializer 的新增两个 Properties,可以支持在部署原有处理函数的基础上,对新增的 initializer 同样做到一起部署。新增的 Properties 如下: Initializer:initializer 入口 InitializationTimeout:initializer 最大运行时间,单位为秒。 下面我们用一个简单的示例演示如何使用 fun 进行部署,并在下文控制台开发中展示该函数的运行结果: # -*- coding: utf-8 -*- import logging init_flag = False init_count = 0 def my_initializer(context): global init_flag global init_count init_flag = True init_count += 1 def my_handler(event, context): global init_flag global init_count logger = logging.getLogger() if not init_flag: logger.info('no initializer has executed, init_count: %d', init_count) return init_count logger.info('initializer has executed, init_count: %d', init_count) return init_count 针对 template.yml 文件,在原有 Properties 的基础上增加 Initializer 和 InitializationTimeout 两个新的 Properties。 ROSTemplateFormatVersion: '2015-09-01' Transform: 'Aliyun::Serverless-2018-04-03' Resources: initializerDemo: # service name Type: 'Aliyun::Serverless::Service' Properties: Description: 'initializer demo' initializer: # function name Type: 'Aliyun::Serverless::Function' Properties: Handler: main.my_handler Initializer: main.my_initializer CodeUri: './' Description: 'Hello world with initializer!' Runtime: python2.7 MemorySize: 1024 Timeout: 300 InitializationTimeout: 60 编写完后直接执行 fun deploy 进行一键部署即可生效,部署成功后可以到 函数计算控制台 查看 initializerdemo 服务中 initializer 函数是否已经创建成功,如果已经成功,点击执行,查看函数执行结果。 函数执行结果恒为 1,且不会走到 if not init_flag: 逻辑中。 控制台 在函数计算控制台新建函数页面的环境配置中,可根据用户的需求来决定是否开启初始化功能 (初始化开关默认关闭)。如果需要,请点击开启初始化功能 开关,并配置 initializer 入口 和 initializer 超时时间 ; 如不需此功能,请确认没有打开 开启初始化功能 开关。更多关于函数计算控制台的操作请参考 控制台文档。 下面以上文 fun 开发中使用的示例进行演示: 开启初始化功能并进行配置 运行函数 在函数计算控制台进行更新函数操作时,如果需要更新 initializer 入口 和 initializer 超时时间 ,可以在控制台函数概览中对指定函数进行修改,如果将 initializer 入口 修改为空,即可关闭初始化功能。 更新 initializer 运行函数 总结 阿里云函数计算的 initializer 功能,可以有效的解决业务冷启动开销较大带来的痛点。同时借助函数计算丰富的工具链和多语言 SDK 的支持,可以帮助您快速上手,提高开发效率。 最后欢迎大家通过扫码加入我们用户群中,使用过程中有问题或者有其他问题可以在群里提出来。函数计算官网客户群(11721331)。