1. 程式人生 > >函式計算效能福利篇(二) —— 業務冷啟動優化

函式計算效能福利篇(二) —— 業務冷啟動優化

繼前一篇《函式計算效能福利篇——系統冷啟動優化》,我們來看看近期函式計算推出的 Initializer 功能帶來的效能優化效果。

背景

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

使用者函式呼叫鏈路包括以下幾個階段:

  • 系統為函式分配計算資源;
  • 下載程式碼;
  • 啟動容器並載入函式代;
  • 使用者函式內部進行初始化邏輯;
  • 函式處理請求並將結果返回。

其中前三步是系統層面的冷啟動開銷,通過對排程以及各個環節的優化,函式計算能做到負載快速增長時穩定的延時,細節詳見 函式計算系統冷啟動優化


第4步是函式內部初始化邏輯,屬於應用業務層面的冷啟動開銷,例如深度學習場景下載入規格較大的模型、資料庫場景下連線池構建、函式依賴庫載入等等。為了減小應用層冷啟動對延時的影響,函式計算推出了 initializer 介面,便於使用者抽離業務初始化邏輯。這樣使用者就能將自身業務的初始化邏輯和請求處理邏輯分離,分別是現在 initializer 介面和 handler 介面中,使得系統能識別使用者函式的初始化邏輯,從而在排程上做相應的優化。

Initializer 功能簡介

引入 initializer 介面的價值主要體現在如下幾個方面:

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

具體的 Initializer 功能實現和使用指南,請參考官方 Initiliazer 介紹

初始化場景效能對比

上一節已經簡單了概括了 Initializer 的功能,這裡,我們具體展示一下初始化場景下 Initializer 帶來的巨大的效能提升效應。

函式實現

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

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

# -*- 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%:

image.png

注意:忽略第一批請求的完全冷啟動的延時影響。

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

image.png

image.png

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

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

image.png

image.png

波峰 burst 模式

波峰burst模式是指使用者請求比較平穩,但是會有突然的波峰流量場景。設計請求行為如下:

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

image.png

注意:忽略第一批請求的完全冷啟動的延時影響。

不使用 initializer 實現的執行結果,

image.png
image.png

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

image.png
image.png

 業務邏輯升級模式

業務邏輯升級模式是指使用者請求比較平穩,但是使用者函式會持續 UpdateFunction,變更業務邏輯,進行使用者業務升級。設計請求行為如下:

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

TPS 如下:

image.png

注意:忽略第一批請求的完全冷啟動的延時影響。

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

image.png
image.png

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

image.png
image.png

總結

綜上資料分析,函式計算的 Initializer 功能極大的優化了業務層冷啟動的毛刺影響:

  • 在使用者請求存在明顯 burst 或者在以一定速率增長的情況下,能夠極大的緩解效能影響,如上,在負載持續增加模式和,請求平均 latency 僅僅增加3倍,99th latency 只增加了5倍,99th latency僅為優化前的2.9%,整整下降了33倍之多。
  • 在使用者有持續的請求且不更新函式的情況下,優化之後更新函式業務層能夠做到無感知,平滑熱升級。

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