天天看點

使用 initializer 接口解決函數計算上傳代碼包大小受限問題

背景

未引入初始化函數功能前如何解決函數計算對上傳代碼包大小的限制問題?

阿裡雲

函數計算

為了保證函數計算系統的可用性更高,對使用者上傳的代碼包做了

大小限制

,要求原始代碼包大小不超過 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 如下:

使用 initializer 接口解決函數計算上傳代碼包大小受限問題

你可以通過

SDK

API fun 控制台

等多種方式進行部署,這裡直接通過控制台上傳代碼包。

  • 首先選擇一個 region 并建立一個函數。
  • 在建立函數的代碼配置中選擇檔案夾上傳,點選你的項目目錄全部上傳即可。
    使用 initializer 接口解決函數計算上傳代碼包大小受限問題
  • 在環境配置中配置初始化函數,将函數入口和 initializer 入口分别設定為

    loader.handler

    loader.initializer

    ,并配置合理的處理函數逾時時間和初始化逾時時間,配置結束後點選下一步即可。
    使用 initializer 接口解決函數計算上傳代碼包大小受限問題

功能測試

  • 在函數計算控制台執行函數,發現當引入 initializer 功能後,首次執行時間為 3205ms:
    使用 initializer 接口解決函數計算上傳代碼包大小受限問題
  • 為驗證函數是否執行成功,我們到 OSS 控制台檢視指定 bucket 下面是否存在名為 picture 的 object,并驗證是否旋轉成功,圖檔如下,由此可見函數執行成功。
    使用 initializer 接口解決函數計算上傳代碼包大小受限問題

性能測試

為驗證 initializer 函數不僅可以解決上傳代碼包大小受限的問題,且可以規避加載代碼包的冷啟動時間,下面将對函數進行改造,關閉初始化功能。

  • 在函數概覽頁面中點選修改函數,并關閉

    開啟初始化功能

    按鈕:
    使用 initializer 接口解決函數計算上傳代碼包大小受限問題
  • 将 initializer 函數中的邏輯放到到處理函數中,并删除原有初始化函數,點選執行檢視運作結果:
    使用 initializer 接口解決函數計算上傳代碼包大小受限問題
  • 函數産生日志如下:
    使用 initializer 接口解決函數計算上傳代碼包大小受限問題

從上圖中可以發現,在未引入 initializer 函數之前,首次函數函數執行時間約為 14159ms,從 OSS 加載過大的代碼包會占用大量的冷啟動時間,并且影響函數的性能。

總結

從測試的結果可以發現,initializer 接口的引入解決了文章開篇提到的問題。即解決函數計算對上傳代碼包的限制問題,又規避了加載代碼包的冷啟動時間,極大的提升函數性能。

最後歡迎大家通過掃碼加入我們使用者群中,使用過程中有問題或者有其他問題可以在群裡提出來。函數計算官網客戶群(11721331)。

使用 initializer 接口解決函數計算上傳代碼包大小受限問題

繼續閱讀