Serverless 工程實(shí)踐|Serverless 應(yīng)用優(yōu)化與調(diào)試秘訣
Serverless 應(yīng)用調(diào)試秘訣
在應(yīng)用開發(fā)過程中,或者應(yīng)用開發(fā)完成,所執(zhí)行結(jié)果不符合預(yù)期時,我們要進(jìn)行一定的調(diào)試工作。但是在 Serverless 架構(gòu)下,調(diào)試往往會受到極大的環(huán)境限制,出現(xiàn)所開發(fā)的應(yīng)用在本地可以健康、符合預(yù)期的運(yùn)行,但是在 FaaS 平臺上發(fā)生一些不可預(yù)測的問題的情況。而且在一些特殊環(huán)境下,本地沒有辦法模擬線上環(huán)境,難以進(jìn)行項(xiàng)目的開發(fā)和調(diào)試。
Serverless 應(yīng)用的調(diào)試一直都是備受詬病的,但是各個云廠商并沒有因此放棄在調(diào)試方向的深入探索。以阿里云函數(shù)計(jì)算為例,其提供了在線調(diào)試、本地調(diào)試等多種調(diào)試方案。
在線調(diào)試
1.簡單調(diào)試
所謂的簡單調(diào)試,就是在控制臺進(jìn)行調(diào)試。以阿里云函數(shù)計(jì)算為例,其可以在控制臺通過“執(zhí)行”按鈕,進(jìn)行基本的調(diào)試,如圖所示。
函數(shù)在線簡單調(diào)試頁面
必要的時候,我們也可以通過設(shè)置 Event 來模擬一些事件,如圖所示。
通過設(shè)置 Event 模擬事件
在線調(diào)試的好處是,可以使用線上的一些環(huán)境進(jìn)行代碼的測試。當(dāng)線上環(huán)境擁有 VPC 等資源時,在本地環(huán)境是很難進(jìn)行調(diào)試的,例如數(shù)據(jù)庫需要通過 VPC 訪問,或者有對象存儲觸發(fā)器的業(yè)務(wù)邏輯等。
2.斷點(diǎn)調(diào)試
除了簡單的調(diào)試之外,部分云廠商也支持?jǐn)帱c(diǎn)調(diào)試,例如阿里云函數(shù)計(jì)算的遠(yuǎn)程調(diào)試、騰訊云云函數(shù)的遠(yuǎn)程調(diào)試等。以阿里云函數(shù)計(jì)算遠(yuǎn)程調(diào)試為例,其可以通過控制臺進(jìn)行函數(shù)的在線調(diào)試。當(dāng)創(chuàng)建好函數(shù)之后,用戶可以選擇遠(yuǎn)程調(diào)試,并點(diǎn)擊“開啟調(diào)試”按鈕,如圖所示。
函數(shù)在線斷點(diǎn)調(diào)試頁面(一)
開啟調(diào)試之后,稍等片刻,系統(tǒng)將會進(jìn)入遠(yuǎn)程調(diào)試界面,如圖所示。
函數(shù)在線斷點(diǎn)調(diào)試頁面(二)
此時可以進(jìn)行一些斷點(diǎn)調(diào)試,如圖所示。
函數(shù)在線斷點(diǎn)調(diào)試頁面(三)
本地調(diào)試
1.命令行工具
就目前來看,大部分 FaaS 平臺都會為用戶提供相對完備的命令行工具,包括 AWS 的SAM CLI、阿里云的 Funcraft,同時也有一些開源項(xiàng)目例如 Serverless Framework、Serverless Devs 等對多云廠商的支持。通過命令行工具進(jìn)行代碼調(diào)試的方法很簡單。以 Serverless Devs 為例,本地調(diào)試阿里云函數(shù)計(jì)算。
首先確保本地?fù)碛幸粋€函數(shù)計(jì)算的項(xiàng)目,如圖所示。
本地函數(shù)計(jì)算項(xiàng)目
然后在項(xiàng)目下執(zhí)行調(diào)試指令,例如在 Docker 中進(jìn)行調(diào)試,如圖所示。
命令行工具調(diào)試函數(shù)計(jì)算
2.編輯器插件
以 VScode 插件為例,當(dāng)下載好阿里云函數(shù)計(jì)算的 VSCode 插件,并且配置好賬號信息之后,可以在本地新建函數(shù),并且在打點(diǎn)之后可以進(jìn)行斷點(diǎn)調(diào)試,如圖所示。
VSCode 插件調(diào)試函數(shù)計(jì)算
當(dāng)函數(shù)調(diào)試完成之后,執(zhí)行部署等操作。
其他調(diào)試方案
1.Web 框架的本地調(diào)試
在阿里云 FaaS 平臺開發(fā)傳統(tǒng) Web 框架,以 Python 語言編寫的 Bottle 框架為例,可以增加以下代碼:
- app = bottle.default_app()并且對run方法進(jìn)行條件限制 (if __name__ == '__main__'):if __name__ == '__main__': bottle.run(host='localhost', port=8080, debug=True)例如:# index.pyimport 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)
當(dāng)部署應(yīng)用到線上時,只需要在入口方法處填寫 ndex.app,即可實(shí)現(xiàn)平滑部署。
2.本地模擬事件調(diào)試
針對非 Web 框架,我們可以在本地構(gòu)建一個方法,例如要調(diào)試對象存儲觸發(fā)器:
- import jsondef 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())
這樣,通過構(gòu)造一個 event 對象,即可實(shí)現(xiàn)模擬事件觸發(fā)。
Serverless 應(yīng)用優(yōu)化
資源評估依舊重要
Serverless 架構(gòu)雖然是按量付費(fèi)的,但是并不代表它就一定比傳統(tǒng)的服務(wù)器租用費(fèi)用低。如果對自己的項(xiàng)目評估不準(zhǔn)確,對一些指標(biāo)設(shè)置不合理,Serverless 架構(gòu)所產(chǎn)生的費(fèi)用可能是巨大的。
一般情況下,F(xiàn)aaS 平臺的收費(fèi)和三個指標(biāo)有直接關(guān)系,即所配置的函數(shù)規(guī)格(例如內(nèi)存規(guī)格等)、程序所消耗的時間以及產(chǎn)生的流量費(fèi)用。通常情況下,程序所消耗的時間可能與內(nèi)存規(guī)格、程序本身所處理的業(yè)務(wù)邏輯有關(guān)。流量費(fèi)用與程序本身和客戶端交互的數(shù)據(jù)包大小有關(guān)。所以在這三個常見的指標(biāo)中,可能因?yàn)榕渲貌灰?guī)范導(dǎo)致計(jì)費(fèi)出現(xiàn)比較大偏差的就是內(nèi)存規(guī)格。以阿里云函數(shù)計(jì)算為例,假設(shè)有一個 Hello World 程序,每天都會被執(zhí)行 10000 次,不同規(guī)格的內(nèi)存所產(chǎn)生的費(fèi)用(不包括網(wǎng)絡(luò)費(fèi)用)如表所示。
通過表中可以看到,當(dāng)程序在 128MB 規(guī)格的內(nèi)存中可以正常執(zhí)行,如果錯誤地將內(nèi)存規(guī)格設(shè)置成 3072MB,可能每月產(chǎn)生的費(fèi)用將會暴漲 25 倍!所以在上線 Serverless 應(yīng)用之前,要對資源進(jìn)行評估,以便以更合理的配置來進(jìn)一步降低成本。
合理的代碼包規(guī)格
各個云廠商的 FaaS 平臺中都對代碼包大小有著限制。拋掉云廠商對代碼包的限制,單純地說代碼包的規(guī)格可能會產(chǎn)生的影響,通過函數(shù)的冷啟動流程可以看到,如圖所示。
函數(shù)冷啟動流程簡圖
在函數(shù)冷啟動過程中,當(dāng)所上傳的代碼包過大,或者文件過多導(dǎo)致解壓速度過慢,就會使加載代碼過程變長,進(jìn)一步導(dǎo)致冷啟動時間變久。
設(shè)想一下,當(dāng)有兩個壓縮包,一個是只有 100KB 的代碼壓縮包,另一個是 200MB 的代碼壓縮包,兩者同時在千兆的內(nèi)網(wǎng)帶寬下理想化(即不考慮磁盤的存儲速度等)下載,即使最大速度可以達(dá)到 125MB/s,那么前者的下載時間只有不到 0.01 秒,后者需要 1.6 秒。除了下載時間之外,加上文件的解壓時間,那么兩者的冷啟動時間可能就相差 2 秒。一般情況下,對于傳統(tǒng)的 Web 接口,如果要 2 秒以上的響應(yīng)時間,實(shí)際上對很多業(yè)務(wù)來說是不能接受的,所以在打包代碼時就要盡可能地降低壓縮包大小。以 Node.js 項(xiàng)目為例,打包代碼包時,我們可以采用 Webpack 等方法來壓縮依賴包大小,進(jìn)一步降低整體代碼包的規(guī)格,提升函數(shù)的冷啟動效率。
合理復(fù)用實(shí)例
為了更好地解決冷啟動的問題、更合理地利用資源,各個云廠商的 FaaS 平臺中是存在實(shí)例復(fù)用情況的。所謂的實(shí)例復(fù)用,就是當(dāng)一個實(shí)例完成一個請求后并不會釋放,而是進(jìn)入靜默的狀態(tài)。在一定時間范圍內(nèi),如果有新的請求被分配過來,則會直接調(diào)用對應(yīng)的方法,而不需要再初始化各類資源等,這在很大程度上減少了函數(shù)冷啟動的情況出現(xiàn)。為了驗(yàn)證,我們可以創(chuàng)建兩個函數(shù):
- 函數(shù)1:# -*- coding: utf-8 -*-def handler(event, context): print("Test") return 'hello world'函數(shù)2:# -*- coding: utf-8 -*-print("Test")def handler(event, context): return 'hello world'
在控制臺點(diǎn)擊“測試”按鈕,對上述兩個函數(shù)進(jìn)行測試,判斷其是否在日志中輸出了 “Test”,統(tǒng)計(jì)結(jié)果如表所示。
函數(shù)復(fù)用記錄
可以看到,其實(shí)實(shí)例復(fù)用的情況是存在的。進(jìn)一步思考,如果 print("Test") 語句是一個初始化數(shù)據(jù)庫連接,或者是函數(shù) 1 和函數(shù) 2 加載了一個深度學(xué)習(xí)模型,是不是函數(shù) 1 就是每次請求都會執(zhí)行,而函數(shù) 2 可以復(fù)用已有對象?
所以在實(shí)際的項(xiàng)目中,有一些初始化操作是可以按照函數(shù) 2 實(shí)現(xiàn)的,例如:
在機(jī)器學(xué)習(xí)場景下,在初始化的時候加載模型,避免每次函數(shù)被觸發(fā)都會加載模型。
在初始化的時候建立鏈接對象,避免每次請求都創(chuàng)建鏈接對象。
其他一些需要首次加載時下載、加載的文件在初始化時實(shí)現(xiàn),提高實(shí)例復(fù)用效率。
善于利用函數(shù)特性
各個云廠商的 FaaS 平臺都有一些特性。所謂的平臺特性,是指這些功能可能并不是 CNCF WG-Serverless Whitepaper v1.0 中規(guī)定的能力或者描述的能力,僅僅是作為云平臺根據(jù)自身業(yè)務(wù)發(fā)展和訴求從用戶角度出發(fā)挖掘出來并且實(shí)現(xiàn)的功能,可能只是某個云平臺或者某幾個云平臺所擁有的功能。這類功能一般情況下如果利用得當(dāng)會讓業(yè)務(wù)性能有質(zhì)的提升。
1.Pre-freeze & Pre-stop
以阿里云函數(shù)計(jì)算為例,在平臺發(fā)展過程中,用戶痛點(diǎn)(尤其是阻礙傳統(tǒng)應(yīng)用平滑遷移至 Serverless 架構(gòu))如下。
異步背景指標(biāo)數(shù)據(jù)延遲或丟失:如果在請求期間沒有發(fā)送成功,則可能被延遲至下一次請求,或者數(shù)據(jù)點(diǎn)被丟棄。
同步發(fā)送指標(biāo)增加延時:如果在每個請求結(jié)束后都調(diào)用類似 Flush 接口,不僅增加了每個請求的延時,對于后端服務(wù)也產(chǎn)生了不必要的壓力。
函數(shù)優(yōu)雅下線:實(shí)例關(guān)閉時應(yīng)用有清理連接、關(guān)閉進(jìn)程、上報(bào)狀態(tài)等需求。在函數(shù)計(jì)算中實(shí)例下線時,開發(fā)者無法掌握,也缺少 Webhook 通知函數(shù)實(shí)例下線事件。
根據(jù)這些痛點(diǎn),阿里云發(fā)布了運(yùn)行時擴(kuò)展 (Runtime Extensions) 功能。該功能在現(xiàn)有的 HTTP 服務(wù)編程模型上擴(kuò)展,在已有的 HTTP 服務(wù)器模型中增加了 PreFreeze 和 PreStop Webhook。擴(kuò)展開發(fā)者負(fù)責(zé)實(shí)現(xiàn) HTTP handler,監(jiān)聽函數(shù)實(shí)例生命周期事件,如圖所示。
擴(kuò)展編程模型與現(xiàn)有編程模型處理的工作內(nèi)容簡圖
PreFreeze:在每次函數(shù)計(jì)算服務(wù)決定冷凍當(dāng)前函數(shù)實(shí)例前,函數(shù)計(jì)算服務(wù)會調(diào)用 HTTP GET/prefreeze 路徑,擴(kuò)展開發(fā)者負(fù)責(zé)實(shí)現(xiàn)相應(yīng)邏輯以確保完成實(shí)例冷凍前的必要操作,例如等待指標(biāo)發(fā)送成功等,如圖所示。函數(shù)調(diào)用 InvokeFunction 的時間不包含 PreFreeze Hook 的執(zhí)行時間。
PreFreeze時序圖
PreStop:在每次函數(shù)計(jì)算決定停止當(dāng)前函數(shù)實(shí)例前,函數(shù)計(jì)算服務(wù)會調(diào)用 HTTP GET/prestop 路徑,擴(kuò)展開發(fā)者負(fù)責(zé)實(shí)現(xiàn)相應(yīng)邏輯以確保完成實(shí)例釋放前的必要操作,如等待數(shù)據(jù)庫鏈接關(guān)閉,以及上報(bào)、更新狀態(tài)等,如圖所示。
PreStope 時序圖
2.單實(shí)例多并發(fā)
眾所周知,各云廠商的函數(shù)計(jì)算通常是請求級別的隔離,即當(dāng)客戶端同時發(fā)起 3 個請求到函數(shù)計(jì)算,理論上會產(chǎn)生 3 個實(shí)例進(jìn)行應(yīng)對,這個時候可能會涉及冷啟動以及請求之間狀態(tài)關(guān)聯(lián)等問題。因此,部分云廠商提供了單實(shí)例多并發(fā)的能力(例如阿里云函數(shù)計(jì)算)。該能力允許用戶為函數(shù)設(shè)置一個實(shí)例并發(fā)度 (InstanceConcurrency) ,即單個函數(shù)實(shí)例可以同時處理多個請求,如圖所示。
單實(shí)例多并發(fā)效果簡圖
如上圖所示,假設(shè)同時有 3 個請求需要處理,當(dāng)實(shí)例并發(fā)度設(shè)置為 1 時,函數(shù)計(jì)算需要創(chuàng)建 3 個實(shí)例來處理這 3 個請求,每個實(shí)例分別處理 1 個請求;當(dāng)實(shí)例并發(fā)度設(shè)置為 10 時(即1個實(shí)例可以同時處理 10 個請求),函數(shù)計(jì)算只需要創(chuàng)建 1 個實(shí)例就能處理這 3 個請求。
單實(shí)例多并發(fā)的優(yōu)勢如下。
減少執(zhí)行時長,節(jié)省費(fèi)用。例如,偏 I/O 函數(shù)可以在一個實(shí)例內(nèi)并發(fā)處理請求,減少了實(shí)例數(shù),從而減少總的執(zhí)行時長。
請求之間可以共享狀態(tài)。多個請求可以在一個實(shí)例內(nèi)共用數(shù)據(jù)庫連接池,從而減少和數(shù)據(jù)庫之間的連接數(shù)。
降低冷啟動概率。由于多個請求可以在一個實(shí)例內(nèi)處理,創(chuàng)建新實(shí)例的次數(shù)會減少,冷啟動概率降低。
減少占用 VPC IP。在相同負(fù)載下,單實(shí)例多并發(fā)可以降低總的實(shí)例數(shù),從而減少 VPC IP 的占用。
單實(shí)例多并發(fā)的應(yīng)用場景比較廣泛,例如函數(shù)中有較多時間在等待下游服務(wù)響應(yīng)的場景就比較適合使用該功能。單實(shí)例多并發(fā)也有不適合應(yīng)用的場景,例如函數(shù)中有共享狀態(tài)且不能并發(fā)訪問時,單個請求的執(zhí)行要消耗大量 CPU 及內(nèi)存資源,這時就不適合使用單實(shí)例多并發(fā)功能。