繼前一篇《函數(shù)計(jì)算性能福利篇——系統(tǒng)冷啟動(dòng)優(yōu)化》,我們?cè)賮?lái)看看近期函數(shù)計(jì)算推出的?Initializer 功能之后,帶來(lái)的一波高能性能優(yōu)化成果。
背景
函數(shù)計(jì)算是一個(gè)事件驅(qū)動(dòng)的全托管 serverless 計(jì)算服務(wù),用戶(hù)可以將業(yè)務(wù)實(shí)現(xiàn)成符合函數(shù)計(jì)算編程模型的函數(shù),交付給平臺(tái)快速實(shí)現(xiàn)彈性高可用的云原生應(yīng)用。
用戶(hù)函數(shù)調(diào)用鏈路包括以下幾個(gè)階段:
系統(tǒng)為函數(shù)分配計(jì)算資源;
下載代碼;
啟動(dòng)容器并加載函數(shù)代碼;
用戶(hù)函數(shù)內(nèi)部進(jìn)行初始化邏輯;
函數(shù)處理請(qǐng)求并將結(jié)果返回。
其中前三步是系統(tǒng)層面的冷啟動(dòng)開(kāi)銷(xiāo),通過(guò)對(duì)調(diào)度以及各個(gè)環(huán)節(jié)的優(yōu)化,函數(shù)計(jì)算能做到負(fù)載快速增長(zhǎng)時(shí)穩(wěn)定的延時(shí),細(xì)節(jié)詳見(jiàn)?函數(shù)計(jì)算系統(tǒng)冷啟動(dòng)優(yōu)化。
第4步是函數(shù)內(nèi)部初始化邏輯,屬于應(yīng)用業(yè)務(wù)層面的冷啟動(dòng)開(kāi)銷(xiāo),例如深度學(xué)習(xí)場(chǎng)景下加載規(guī)格較大的模型、數(shù)據(jù)庫(kù)場(chǎng)景下連接池構(gòu)建、函數(shù)依賴(lài)庫(kù)加載等等。為了減小應(yīng)用層冷啟動(dòng)對(duì)延時(shí)的影響,函數(shù)計(jì)算推出了 initializer 接口,便于用戶(hù)抽離業(yè)務(wù)初始化邏輯。這樣用戶(hù)就能將自身業(yè)務(wù)的初始化邏輯和請(qǐng)求處理邏輯分離,分別是實(shí)現(xiàn)在 initializer 接口和 handler 接口中,使得系統(tǒng)能識(shí)別用戶(hù)函數(shù)的初始化邏輯,從而在調(diào)度上做相應(yīng)的優(yōu)化。
Initializer 功能簡(jiǎn)介
引入 initializer 接口的價(jià)值主要體現(xiàn)在如下幾個(gè)方面:
分離初始化邏輯和請(qǐng)求處理邏輯,程序邏輯更清晰,讓用戶(hù)更易寫(xiě)出結(jié)構(gòu)良好,性能更優(yōu)的代碼;
用戶(hù)函數(shù)代碼更新時(shí),系統(tǒng)能夠保證用戶(hù)函數(shù)的平滑升級(jí),規(guī)避應(yīng)用層初始化冷啟動(dòng)帶來(lái)的性能損耗。新的函數(shù)實(shí)例啟動(dòng)后能夠自動(dòng)執(zhí)行用戶(hù)的初始化邏輯,在初始化完成后再處理請(qǐng)求;
在應(yīng)用負(fù)載上升,需要增加更多函數(shù)實(shí)例時(shí),系統(tǒng)能夠識(shí)別函數(shù)應(yīng)用層初始化的開(kāi)銷(xiāo),更精準(zhǔn)的計(jì)算資源伸縮的時(shí)機(jī)和所需的資源量,讓請(qǐng)求延時(shí)更加平穩(wěn);
即使在用戶(hù)有持續(xù)的請(qǐng)求且不更新函數(shù)的情況下,F(xiàn)C系統(tǒng)仍然有可能將已有容器回收或更新,這時(shí)沒(méi)有平臺(tái)方(FC)的冷啟動(dòng),但是會(huì)有業(yè)務(wù)方冷啟動(dòng),Initializer可以最大限度減少這種情況;
具體的 Initializer 功能設(shè)計(jì)和使用指南,請(qǐng)參考官方?Initiliazer 介紹?。
初始化場(chǎng)景性能對(duì)比
上一節(jié)已經(jīng)簡(jiǎn)單了概括了 Initializer 的功能,這里,我們具體展示一下初始化場(chǎng)景下 Initializer 帶來(lái)的巨大的性能提升效應(yīng)。
函數(shù)實(shí)現(xiàn)
初始化應(yīng)用場(chǎng)景,如果不使用 initializer,那么函數(shù)的主要實(shí)現(xiàn)方式應(yīng)該是 Global variable 方式,下面提供兩種實(shí)現(xiàn)方式的 demo ,僅供參考,下面的性能測(cè)試也是對(duì)比這兩種函數(shù)實(shí)現(xiàn)方式進(jìn)行了。
使用 global variables 實(shí)現(xiàn)業(yè)務(wù)層初始化邏輯:
#?-*-?coding:?utf-8?-*-import?timeimport?json isInit?=?Falsedef?init_handler(): ??time.sleep(30)??global?isInit ??isInit?=?Truedef?handler(event,?context): ??evt?=?json.loads(event) ??funcSleepTime?=?evt['funcSleepTime']??if?not?isInit: ???????init_handler() ??time.sleep(funcSleepTime)
使用 initializer 的編程模型實(shí)現(xiàn)業(yè)務(wù)層初始化邏輯:
#?-*-?coding:?utf-8?-*-import?timeimport?jsondef?init_handler(context): ??time.sleep(30)def?handler(event,?context): ??evt?=?json.loads(event) ??funcSleepTime?=?evt['funcSleepTime'] ??time.sleep(funcSleepTime)
兩個(gè) function 的邏輯相同:
函數(shù)實(shí)例運(yùn)行時(shí),先執(zhí)行 init_handler 邏輯,執(zhí)行時(shí)間 30s,進(jìn)行業(yè)務(wù)層初始化;
如果已經(jīng)初始化,那么就執(zhí)行 handler 邏輯,執(zhí)行時(shí)間 0.1s,進(jìn)行請(qǐng)求處理;如果沒(méi)有初始化,那么先進(jìn)行初始化邏輯,再執(zhí)行 handler 邏輯。
場(chǎng)景對(duì)比
這里根據(jù)生產(chǎn)用戶(hù)請(qǐng)求場(chǎng)景,我們選擇如下三種測(cè)試 case 來(lái)對(duì)比兩種初始化函數(shù)實(shí)現(xiàn)的性能。
負(fù)載持續(xù)增加模式
波峰 burst 模式
業(yè)務(wù)邏輯升級(jí)模式
測(cè)試函數(shù)的特性如下:
函數(shù) handler 邏輯運(yùn)行時(shí)間為 100ms;
函數(shù) 初始化 邏輯運(yùn)行時(shí)間為 30s;
函數(shù)代碼包大小為 50MB;
runtime 為 python2.7;
Memory 為 3GB 。
這樣的函數(shù),系統(tǒng)層冷啟動(dòng)時(shí)間大約在 1s 左右,業(yè)務(wù)層冷啟動(dòng)在 30s,而函數(shù)自身請(qǐng)求執(zhí)行時(shí)間為100-130ms。
負(fù)載持續(xù)增加模式
該模式下,用戶(hù)的請(qǐng)求在一段時(shí)間內(nèi)會(huì)持續(xù)增長(zhǎng)。設(shè)計(jì)請(qǐng)求行為如下:
每波請(qǐng)求并發(fā)數(shù)翻倍遞增: 1, 2, 4, 8, 16, 32;
每波請(qǐng)求的時(shí)間間隔為 35s。
TPS情況如下,增長(zhǎng)率為100%:
注意:忽略第一批請(qǐng)求的完全冷啟動(dòng)的延時(shí)影響。
?不使用 initializer 實(shí)現(xiàn)的運(yùn)行結(jié)果:
從每波請(qǐng)求的請(qǐng)求延時(shí)可以看出,雖然系統(tǒng)層的調(diào)度能夠?yàn)楹髞?lái)的驟增的請(qǐng)求分配更多的函數(shù)實(shí)例,但是因?yàn)楹瘮?shù)實(shí)例都沒(méi)有執(zhí)行過(guò)業(yè)務(wù)層的初始化邏輯,所以新的函數(shù)實(shí)例花費(fèi)了大量的執(zhí)行時(shí)間在初始化邏輯的執(zhí)行上,所以看到 99th latency 都大于 30s 。實(shí)際上,系統(tǒng)層的調(diào)度優(yōu)化在這樣長(zhǎng)時(shí)間的初始化場(chǎng)景中并起不了作用。
使用 initializer 實(shí)現(xiàn)的運(yùn)行結(jié)果,可以看到使用 initializer 功能之后,請(qǐng)求增長(zhǎng)率在 100% 的情況下不會(huì)再有函數(shù)實(shí)例執(zhí)行初始化邏輯,相對(duì)于優(yōu)化前,99th latency 下降了 30 倍以上。
波峰 burst 模式
波峰burst模式是指用戶(hù)請(qǐng)求比較平穩(wěn),但是會(huì)有突然的波峰流量場(chǎng)景。設(shè)計(jì)請(qǐng)求行為如下:
每波請(qǐng)求時(shí)間間隔 35s;
每波平穩(wěn)請(qǐng)求數(shù) 2;
burst 請(qǐng)求數(shù) 18;
TPS請(qǐng)求如下,burst 流量猛增 9 倍:
注意:忽略第一批請(qǐng)求的完全冷啟動(dòng)的延時(shí)影響。???
?不使用 initializer 實(shí)現(xiàn)的運(yùn)行結(jié)果:
?
使用 initializer 實(shí)現(xiàn)的運(yùn)行結(jié)果,對(duì)于 burst 的流量,基本能夠?qū)?latency 的增長(zhǎng)控制在 函數(shù)處理邏輯 6 倍以?xún)?nèi),99th 的 latency 被優(yōu)化到原來(lái)的 2.9% 。
? 業(yè)務(wù)邏輯升級(jí)模式
業(yè)務(wù)邏輯升級(jí)模式是指用戶(hù)請(qǐng)求比較平穩(wěn),但是用戶(hù)函數(shù)會(huì)持續(xù) UpdateFunction,變更業(yè)務(wù)邏輯,進(jìn)行用戶(hù)業(yè)務(wù)升級(jí)。設(shè)計(jì)請(qǐng)求行為如下:
每波請(qǐng)求時(shí)間間隔 35s;
每波平穩(wěn)請(qǐng)求數(shù) 2;
每 6 波請(qǐng)求進(jìn)行一次 UpdateFunction 操作;
TPS 如下:
?
注意:忽略第一批請(qǐng)求的完全冷啟動(dòng)的延時(shí)影響。
?不使用 initializer 實(shí)現(xiàn)的運(yùn)行結(jié)果,這個(gè)時(shí)候請(qǐng)求又會(huì)重新執(zhí)行一次初始化邏輯,導(dǎo)致毛刺出現(xiàn)。
使用 initializer 實(shí)現(xiàn)的運(yùn)行結(jié)果,基本看出,UpdateFunction 操作對(duì)請(qǐng)求已經(jīng)沒(méi)有影響,業(yè)務(wù)層無(wú)感知。
?
?
總結(jié)
綜上數(shù)據(jù)分析,函數(shù)計(jì)算的 Initializer 功能極大的優(yōu)化了業(yè)務(wù)層冷啟動(dòng)的毛刺影響:
在用戶(hù)請(qǐng)求存在明顯 burst 或者在以一定速率增長(zhǎng)的情況下,能夠極大的緩解性能影響,如上,在負(fù)載持續(xù)增加模式和波峰模式場(chǎng)景下,請(qǐng)求平均 latency 僅僅增加 3 倍,99th latency 只增加了 5 倍,99th latency 僅為優(yōu)化前的 2.9% ,整整下降了 33 倍之多。
在用戶(hù)有持續(xù)的請(qǐng)求且不更新函數(shù)的情況下,優(yōu)化之后更新函數(shù),業(yè)務(wù)層能夠做到無(wú)感知,平滑熱升級(jí)。
Initializer 功能對(duì)業(yè)務(wù)層冷啟動(dòng)的優(yōu)化,又一次大大改善了函數(shù)計(jì)算在延時(shí)敏感場(chǎng)景下的表現(xiàn)!
評(píng)論
查看更多