函數計算性能福利篇(二) —— 業務冷啓動優化

繼前一篇《函數計算性能福利篇——系統冷啓動優化》,咱們再來看看近期函數計算推出的 Initializer 功能以後,帶來的一波高能性能優化成果。html

背景

函數計算是一個事件驅動的全託管 serverless 計算服務,用戶能夠將業務實現成符合函數計算編程模型的函數,交付給平臺快速實現彈性高可用的雲原生應用。python

用戶函數調用鏈路包括如下幾個階段:數據庫

  • 系統爲函數分配計算資源;
  • 下載代碼;
  • 啓動容器並加載函數代碼;
  • 用戶函數內部進行初始化邏輯;
  • 函數處理請求並將結果返回。

其中前三步是系統層面的冷啓動開銷,經過對調度以及各個環節的優化,函數計算能作到負載快速增加時穩定的延時,細節詳見 函數計算系統冷啓動優化
第4步是函數內部初始化邏輯,屬於應用業務層面的冷啓動開銷,例如深度學習場景下加載規格較大的模型、數據庫場景下鏈接池構建、函數依賴庫加載等等。爲了減少應用層冷啓動對延時的影響,函數計算推出了 initializer 接口,便於用戶抽離業務初始化邏輯。這樣用戶就能將自身業務的初始化邏輯和請求處理邏輯分離,分別是實如今 initializer 接口和 handler 接口中,使得系統能識別用戶函數的初始化邏輯,從而在調度上作相應的優化。編程

Initializer 功能簡介

引入 initializer 接口的價值主要體如今以下幾個方面:json

  • 分離初始化邏輯和請求處理邏輯,程序邏輯更清晰,讓用戶更易寫出結構良好,性能更優的代碼;
  • 用戶函數代碼更新時,系統可以保證用戶函數的平滑升級,規避應用層初始化冷啓動帶來的性能損耗。新的函數實例啓動後可以自動執行用戶的初始化邏輯,在初始化完成後再處理請求;
  • 在應用負載上升,須要增長更多函數實例時,系統可以識別函數應用層初始化的開銷,更精準的計算資源伸縮的時機和所需的資源量,讓請求延時更加平穩;
  • 即便在用戶有持續的請求且不更新函數的狀況下,FC系統仍然有可能將已有容器回收或更新,這時沒有平臺方(FC)的冷啓動,可是會有業務方冷啓動,Initializer能夠最大限度減小這種狀況;

具體的 Initializer 功能設計和使用指南,請參考官方 Initiliazer 介紹 性能優化

初始化場景性能對比

上一節已經簡單了歸納了 Initializer 的功能,這裏,咱們具體展現一下初始化場景下 Initializer 帶來的巨大的性能提高效應。併發

函數實現

初始化應用場景,若是不使用 initializer,那麼函數的主要實現方式應該是 Global variable 方式,下面提供兩種實現方式的 demo ,僅供參考,下面的性能測試也是對比這兩種函數實現方式進行了。less

使用 global variables 實現業務層初始化邏輯:python2.7

# -*- coding: utf-8 -*-
import time
import json

isInit = False
def init_handler():
  time.sleep(30)
  global isInit
  isInit = True

def handler(event, context):
  evt = json.loads(event)
  funcSleepTime = evt['funcSleepTime']
  if not isInit:
       init_handler()
  time.sleep(funcSleepTime)

使用 initializer 的編程模型實現業務層初始化邏輯:函數

# -*- coding: utf-8 -*-
import time
import json

def init_handler(context):
  time.sleep(30)

def handler(event, context):
  evt = json.loads(event)
  funcSleepTime = evt['funcSleepTime']
  time.sleep(funcSleepTime)

兩個 function 的邏輯相同:

  • 函數實例運行時,先執行 init_handler 邏輯,執行時間 30s,進行業務層初始化;
  • 若是已經初始化,那麼就執行 handler 邏輯,執行時間 0.1s,進行請求處理;若是沒有初始化,那麼先進行初始化邏輯,再執行 handler 邏輯。

場景對比

這裏根據生產用戶請求場景,咱們選擇以下三種測試 case 來對比兩種初始化函數實現的性能。

  • 負載持續增長模式
  • 波峯 burst 模式
  • 業務邏輯升級模式

測試函數的特性以下:

  • 函數 handler 邏輯運行時間爲 100ms;
  • 函數 初始化 邏輯運行時間爲 30s;
  • 函數代碼包大小爲 50MB;
  • runtime 爲 python2.7;
  • Memory 爲 3GB 。

這樣的函數,系統層冷啓動時間大約在 1s 左右,業務層冷啓動在 30s,而函數自身請求執行時間爲100-130ms。

負載持續增長模式

該模式下,用戶的請求在一段時間內會持續增加。設計請求行爲以下:

  • 每波請求併發數翻倍遞增: 1, 2, 4, 8, 16, 32;
  • 每波請求的時間間隔爲 35s。

TPS狀況以下,增加率爲100%:

注意:忽略第一批請求的徹底冷啓動的延時影響。

不使用 initializer 實現的運行結果:

從每波請求的請求延時能夠看出,雖然系統層的調度可以爲後來的驟增的請求分配更多的函數實例,可是由於函數實例都沒有執行過業務層的初始化邏輯,因此新的函數實例花費了大量的執行時間在初始化邏輯的執行上,因此看到 99th latency 都大於 30s 。實際上,系統層的調度優化在這樣長時間的初始化場景中並起不了做用。

使用 initializer 實現的運行結果,能夠看到使用 initializer 功能以後,請求增加率在 100% 的狀況下不會再有函數實例執行初始化邏輯,相對於優化前,99th latency 降低了 30 倍以上。

波峯 burst 模式

波峯burst模式是指用戶請求比較平穩,可是會有忽然的波峯流量場景。設計請求行爲以下:

  • 每波請求時間間隔 35s;
  • 每波平穩請求數 2;
  • burst 請求數 18;
  • TPS請求以下,burst 流量猛增 9 倍:

注意:忽略第一批請求的徹底冷啓動的延時影響。

不使用 initializer 實現的運行結果:

使用 initializer 實現的運行結果,對於 burst 的流量,基本可以將 latency 的增加控制在 函數處理邏輯 6 倍之內,99th 的 latency 被優化到原來的 2.9% 。


業務邏輯升級模式

業務邏輯升級模式是指用戶請求比較平穩,可是用戶函數會持續 UpdateFunction,變動業務邏輯,進行用戶業務升級。設計請求行爲以下:

  • 每波請求時間間隔 35s;
  • 每波平穩請求數 2;
  • 每 6 波請求進行一次 UpdateFunction 操做;

TPS 以下:

注意:忽略第一批請求的徹底冷啓動的延時影響。

不使用 initializer 實現的運行結果,這個時候請求又會從新執行一次初始化邏輯,致使毛刺出現。


使用 initializer 實現的運行結果,基本看出,UpdateFunction 操做對請求已經沒有影響,業務層無感知。


總結

綜上數據分析,函數計算的 Initializer 功能極大的優化了業務層冷啓動的毛刺影響:

  • 在用戶請求存在明顯 burst 或者在以必定速率增加的狀況下,可以極大的緩解性能影響,如上,在負載持續增長模式和波峯模式場景下,請求平均 latency 僅僅增長 3 倍,99th latency 只增長了 5 倍,99th latency 僅爲優化前的 2.9% ,整整降低了 33 倍之多。
  • 在用戶有持續的請求且不更新函數的狀況下,優化以後更新函數,業務層可以作到無感知,平滑熱升級。

Initializer 功能對業務層冷啓動的優化,又一次大大改善了函數計算在延時敏感場景下的表現!

原文連接

相關文章
相關標籤/搜索