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 學院特聘講師。