阿里云 函数计算 为了保证函数计算系统的可用性更高,对用户上传的代码包做了 大小限制,要求原始代码包大小不超过 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。
编写函数需要注意以下几点:
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'
你可以通过 SDK、API、fun、控制台 等多种方式进行部署,这里直接通过控制台上传代码包。
loader.handler
和 loader.initializer
,并配置合理的处理函数超时时间和初始化超时时间,配置结束后点击下一步即可。为验证 initializer 函数不仅可以解决上传代码包大小受限的问题,且可以规避加载代码包的冷启动时间,下面将对函数进行改造,关闭初始化功能。
从上图中可以发现,在未引入 initializer 函数之前,首次函数函数执行时间约为 14159ms,从 OSS 加载过大的代码包会占用大量的冷启动时间,并且影响函数的性能。
从测试的结果可以发现,initializer 接口的引入解决了文章开篇提到的问题。即解决函数计算对上传代码包的限制问题,又规避了加载代码包的冷启动时间,极大的提升函数性能。
最后欢迎大家通过扫码加入我们用户群中,使用过程中有问题或者有其他问题可以在群里提出来。函数计算官网客户群(11721331)。