Serverless 工程實踐 | Serverless 應用優化與調試秘訣

前言:本文將以阿里雲函數計算為例,提供了在線調試、本地調試等多種應用優化與調試方案。

Serverless 應用調試秘訣

在應用開發過程中,或者應用開發完成,所執行結果不符合預期時,我們要進行一定的調試工作。但是在 Serverless 架構下,調試往往會受到極大的環境限制,出現所開發的應用在本地可以健康、符合預期的運行,但是在 FaaS 平台上發生一些不可預測的問題的情況。而且在一些特殊環境下,本地沒有辦法模擬線上環境,難以進行項目的開發和調試。

Serverless 應用的調試一直都是備受詬病的,但是各個雲廠商並沒有因此放棄在調試方向的深入探索。以阿里雲函數計算為例,其提供了在線調試、本地調試等多種調試方案。

在線調試

1.簡單調試

所謂的簡單調試,就是在控制台進行調試。以阿里雲函數計算為例,其可以在控制台通過「執行」按鈕,進行基本的調試,如圖所示。

函數在線簡單調試頁面

必要的時候,我們也可以通過設置 Event 來模擬一些事件,如圖所示。

通過設置 Event 模擬事件

在線調試的好處是,可以使用線上的一些環境進行代碼的測試。當線上環境擁有 VPC 等資源時,在本地環境是很難進行調試的,例如資料庫需要通過 VPC 訪問,或者有對象存儲觸發器的業務邏輯等。

2.斷點調試

除了簡單的調試之外,部分雲廠商也支持斷點調試,例如阿里雲函數計算的遠程調試、騰訊云云函數的遠程調試等。以阿里雲函數計算遠程調試為例,其可以通過控制台進行函數的在線調試。當創建好函數之後,用戶可以選擇遠程調試,並點擊「開啟調試」按鈕,如圖所示。

函數在線斷點調試頁面(一)

開啟調試之後,稍等片刻,系統將會進入遠程調試界面,如圖所示。

函數在線斷點調試頁面(二)

此時可以進行一些斷點調試,如圖所示。

函數在線斷點調試頁面(三)

本地調試

1.命令行工具

就目前來看,大部分 FaaS 平台都會為用戶提供相對完備的命令行工具,包括 AWS 的SAM CLI、阿里雲的 Funcraft,同時也有一些開源項目例如 Serverless Framework、Serverless Devs 等對多雲廠商的支持。通過命令行工具進行代碼調試的方法很簡單。以 Serverless Devs 為例,本地調試阿里雲函數計算。

首先確保本地擁有一個函數計算的項目,如圖所示。

本地函數計算項目

然後在項目下執行調試指令,例如在 Docker 中進行調試,如圖所示。

命令行工具調試函數計算

2.編輯器插件

VScode 插件為例,當下載好阿里雲函數計算的 VSCode 插件,並且配置好賬號信息之後,可以在本地新建函數,並且在打點之後可以進行斷點調試,如圖所示。

VSCode 插件調試函數計算

當函數調試完成之後,執行部署等操作。

其他調試方案

1.Web 框架的本地調試

在阿里雲 FaaS 平台開發傳統 Web 框架,以 Python 語言編寫的 Bottle 框架為例,可以增加以下代碼:

app = bottle.default_app()
並且對run方法進行條件限制 (if __name__ == '__main__'):
if __name__ == '__main__':
    bottle.run(host='localhost', port=8080, debug=True)
例如:
# index.py
import bottle

@bottle.route('/hello/<name>')
def index(name):
    return "Hello World"

app = bottle.default_app()

if __name__ == '__main__':
    bottle.run(host='localhost', port=8080, debug=True)

當部署應用到線上時,只需要在入口方法處填寫 ndex.app,即可實現平滑部署。

2.本地模擬事件調試

針對非 Web 框架,我們可以在本地構建一個方法,例如要調試對象存儲觸發器:

import json
def handler(event, context):
    print(event)
def test():
    event = {
        "events": [
            {
                "eventName": "ObjectCreated:PutObject",
                "eventSource": "acs:oss",
                "eventTime": "2017-04-21T12:46:37.000Z",
                "eventVersion": "1.0",
                "oss": {
                    "bucket": {
                        "arn": "acs:oss:cn-shanghai:123456789:bucketname",
                        "name": "testbucket",
                        "ownerIdentity": "123456789",
                        "virtualBucket": ""
                    },
                    "object": {
                        "deltaSize": 122539,
                        "eTag": "688A7BF4F233DC9C88A80BF985AB7329",
                        "key": "image/a.jpg",
                        "size": 122539
                    },
                    "ossSchemaVersion": "1.0",
                    "ruleId": "9adac8e253828f4f7c0466d941fa3db81161****"
                },
                "region": "cn-shanghai",
                "requestParameters": {
                    "sourceIPAddress": "140.205.***.***"
                },
                "responseElements": {
                    "requestId": "58F9FF2D3DF792092E12044C"
                },
                "userIdentity": {
                    "principalId": "123456789"
                }
            }
        ]
    }
    handler(json.dumps(event), None)
if __name__ == "__main__":
    print(test())

這樣,通過構造一個 event 對象,即可實現模擬事件觸發。

Serverless 應用優化

資源評估依舊重要

Serverless 架構雖然是按量付費的,但是並不代表它就一定比傳統的伺服器租用費用低。如果對自己的項目評估不準確,對一些指標設置不合理,Serverless 架構所產生的費用可能是巨大的。

一般情況下,FaaS 平台的收費和三個指標有直接關係,即所配置的函數規格(例如內存規格等)、程序所消耗的時間以及產生的流量費用。通常情況下,程序所消耗的時間可能與內存規格、程序本身所處理的業務邏輯有關。流量費用與程序本身和客戶端交互的數據包大小有關。所以在這三個常見的指標中,可能因為配置不規範導致計費出現比較大偏差的就是內存規格。以阿里雲函數計算為例,假設有一個 Hello World 程序,每天都會被執行 10000 次,不同規格的內存所產生的費用(不包括網路費用)如表所示。

通過表中可以看到,當程序在 128MB 規格的內存中可以正常執行,如果錯誤地將內存規格設置成 3072MB,可能每月產生的費用將會暴漲 25 倍!所以在上線 Serverless 應用之前,要對資源進行評估,以便以更合理的配置來進一步降低成本。

合理的代碼包規格

各個雲廠商的 FaaS 平台中都對代碼包大小有著限制。拋掉雲廠商對代碼包的限制,單純地說代碼包的規格可能會產生的影響,通過函數的冷啟動流程可以看到,如圖所示。

函數冷啟動流程簡圖

在函數冷啟動過程中,當所上傳的代碼包過大,或者文件過多導致解壓速度過慢,就會使載入代碼過程變長,進一步導致冷啟動時間變久。

設想一下,當有兩個壓縮包,一個是只有 100KB 的代碼壓縮包,另一個是 200MB 的代碼壓縮包,兩者同時在千兆的內網帶寬下理想化(即不考慮磁碟的存儲速度等)下載,即使最大速度可以達到 125MB/s,那麼前者的下載時間只有不到 0.01 秒,後者需要 1.6 秒。除了下載時間之外,加上文件的解壓時間,那麼兩者的冷啟動時間可能就相差 2 秒。一般情況下,對於傳統的 Web 介面,如果要 2 秒以上的響應時間,實際上對很多業務來說是不能接受的,所以在打包代碼時就要儘可能地降低壓縮包大小。以 Node.js 項目為例,打包代碼包時,我們可以採用 Webpack 等方法來壓縮依賴包大小,進一步降低整體代碼包的規格,提升函數的冷啟動效率。

合理復用實例

為了更好地解決冷啟動的問題、更合理地利用資源,各個雲廠商的 FaaS 平台中是存在實例復用情況的。所謂的實例復用,就是當一個實例完成一個請求後並不會釋放,而是進入靜默的狀態。在一定時間範圍內,如果有新的請求被分配過來,則會直接調用對應的方法,而不需要再初始化各類資源等,這在很大程度上減少了函數冷啟動的情況出現。為了驗證,我們可以創建兩個函數:

函數1:
# -*- coding: utf-8 -*-

def handler(event, context):
    print("Test")
    return 'hello world'
函數2:
# -*- coding: utf-8 -*-
print("Test")

def handler(event, context):
    return 'hello world'

在控制台點擊「測試」按鈕,對上述兩個函數進行測試,判斷其是否在日誌中輸出了 「Test」,統計結果如表所示。

函數復用記錄

可以看到,其實實例復用的情況是存在的。進一步思考,如果 print("Test") 語句是一個初始化資料庫連接,或者是函數 1 和函數 2 載入了一個深度學習模型,是不是函數 1 就是每次請求都會執行,而函數 2 可以復用已有對象?

所以在實際的項目中,有一些初始化操作是可以按照函數 2 實現的,例如:

  • 在機器學習場景下,在初始化的時候載入模型,避免每次函數被觸發都會載入模型。
  • 在初始化的時候建立鏈接對象,避免每次請求都創建鏈接對象。
  • 其他一些需要首次載入時下載、載入的文件在初始化時實現,提高實例復用效率。

善於利用函數特性

各個雲廠商的 FaaS 平台都有一些特性。所謂的平台特性,是指這些功能可能並不是 CNCF WG-Serverless Whitepaper v1.0 中規定的能力或者描述的能力,僅僅是作為雲平台根據自身業務發展和訴求從用戶角度出發挖掘出來並且實現的功能,可能只是某個雲平台或者某幾個雲平台所擁有的功能。這類功能一般情況下如果利用得當會讓業務性能有質的提升。

1.Pre-freeze & Pre-stop

以阿里雲函數計算為例,在平台發展過程中,用戶痛點(尤其是阻礙傳統應用平滑遷移至 Serverless 架構)如下。

  • 非同步背景指標數據延遲或丟失:如果在請求期間沒有發送成功,則可能被延遲至下一次請求,或者數據點被丟棄。
  • 同步發送指標增加延時:如果在每個請求結束後都調用類似 Flush 介面,不僅增加了每個請求的延時,對於後端服務也產生了不必要的壓力。
  • 函數優雅下線:實例關閉時應用有清理連接、關閉進程、上報狀態等需求。在函數計算中實例下線時,開發者無法掌握,也缺少 Webhook 通知函數實例下線事件。

根據這些痛點,阿里雲發布了運行時擴展 (Runtime Extensions) 功能。該功能在現有的 HTTP 服務編程模型上擴展,在已有的 HTTP 伺服器模型中增加了 PreFreeze 和 PreStop Webhook。擴展開發者負責實現 HTTP handler,監聽函數實例生命周期事件,如圖所示。

擴展編程模型與現有編程模型處理的工作內容簡圖

  • PreFreeze:在每次函數計算服務決定冷凍當前函數實例前,函數計算服務會調用 HTTP GET/prefreeze 路徑,擴展開發者負責實現相應邏輯以確保完成實例冷凍前的必要操作,例如等待指標發送成功等,如圖所示。函數調用 InvokeFunction 的時間不包含 PreFreeze Hook 的執行時間。

PreFreeze時序圖

  • PreStop:在每次函數計算決定停止當前函數實例前,函數計算服務會調用 HTTP GET/prestop 路徑,擴展開發者負責實現相應邏輯以確保完成實例釋放前的必要操作,如等待資料庫鏈接關閉,以及上報、更新狀態等,如圖所示。

PreStope 時序圖

2.單實例多並發

眾所周知,各雲廠商的函數計算通常是請求級別的隔離,即當客戶端同時發起 3 個請求到函數計算,理論上會產生 3 個實例進行應對,這個時候可能會涉及冷啟動以及請求之間狀態關聯等問題。因此,部分雲廠商提供了單實例多並發的能力(例如阿里雲函數計算)。該能力允許用戶為函數設置一個實例並發度 (InstanceConcurrency) ,即單個函數實例可以同時處理多個請求,如圖所示。

單實例多並發效果簡圖

如上圖所示,假設同時有 3 個請求需要處理,當實例並發度設置為 1 時,函數計算需要創建 3 個實例來處理這 3 個請求,每個實例分別處理 1 個請求;當實例並發度設置為 10 時(即1個實例可以同時處理 10 個請求),函數計算只需要創建 1 個實例就能處理這 3 個請求。

單實例多並發的優勢如下。

  • 減少執行時長,節省費用。例如,偏 I/O 函數可以在一個實例內並發處理請求,減少了實例數,從而減少總的執行時長。
  • 請求之間可以共享狀態。多個請求可以在一個實例內共用資料庫連接池,從而減少和資料庫之間的連接數。
  • 降低冷啟動概率。由於多個請求可以在一個實例內處理,創建新實例的次數會減少,冷啟動概率降低。
  • 減少佔用 VPC IP。在相同負載下,單實例多並發可以降低總的實例數,從而減少 VPC IP 的佔用。

單實例多並發的應用場景比較廣泛,例如函數中有較多時間在等待下游服務響應的場景就比較適合使用該功能。單實例多並發也有不適合應用的場景,例如函數中有共享狀態且不能並發訪問時,單個請求的執行要消耗大量 CPU 及內存資源,這時就不適合使用單實例多並發功能。

關於作者:劉宇(江昱)國防科技大學電子信息專業在讀博士,阿里雲 Serverless 產品經理,阿里雲 Serverless 雲佈道師,CIO 學院特聘講師。

原文鏈接:http://click.aliyun.com/m/1000299610/

本文為阿里雲原創內容,未經允許不得轉載。