秘訣與技巧

本文件說明設計、實作、測試及部署 Cloud Functions 的最佳做法。

正確性

本節說明設計和實作 Cloud Functions 的一般最佳做法。

編寫冪等函式

即使多次呼叫函式,這些函式也應該產生相同結果。這樣一來,如果之前叫用程式碼的作業中途失敗,您便可以重試叫用。詳情請參閱重試事件導向函式一文。

請勿啟動背景活動

背景活動是指函式終止後發生的任何活動。當函式傳回或表示完成時 (例如在 Node.js 事件導向函式中呼叫 callback 引數),函式叫用就會完成。在安全終止後執行的任何程式碼都無法存取 CPU,也不會執行任何進度。

此外,在相同環境中執行後續叫用時,背景活動會恢復,並幹擾新叫用。這可能會導致難以診斷的非預期行為和錯誤。在函式終止後存取網路,通常會導致連線重設 (ECONNRESET 錯誤代碼)。

通常可在個別叫用的記錄中偵測背景活動,只要尋找在一行表示叫用已完成之後記錄的任何項目即可。背景活動有時在程式碼中較深,尤其是在存在回呼或計時器等非同步作業時。請檢查程式碼,確認在您終止函式之前,所有非同步作業皆已完成。

一律刪除暫存檔案

暫存目錄中的本機磁碟儲存空間是一個記憶體內部檔案系統。您編寫的檔案會耗用用於函式的記憶體,而且有時會在叫用間持續存在。不明確刪除這些檔案最終可能會導致發生記憶體不足的錯誤,並造成後續冷啟動。

您可以在 GCP 控制台的函式清單中選取函式,並選擇「記憶體用量」圖,查看個別函式使用的記憶體。

請勿嘗試在暫存目錄外部編寫檔案,並務必使用獨立於平台/OS 的方法建構檔案路徑。

您可以使用管道來減少處理大型檔案時的記憶體需求。舉例來說,您可以建立讀取串流、透過串流型程序傳輸檔案,並將輸出串流直接寫入 Cloud Storage,藉此在 Cloud Storage 上處理檔案。

Functions Framework

部署函式時, Functions Framework 會使用當前版本自動新增為依附元件。如要確保在不同環境中以一致的方式安裝相同的依附元件,建議您將函式固定使用特定版本的 Functions Framework。

方法是在相關的鎖定檔案中加入偏好的版本 (例如,Node.js 適用的 package-lock.json,或是 Python 適用的 requirements.txt)。

工具

本節提供如何使用工具實作、測試 Cloud Functions,及與 Cloud Functions 互動的指南。

本機開發

函式部署需要花點時間,因此在本機測試函式的程式碼通常比較快。

Firebase 開發人員可以使用 Firebase CLI Cloud Functions 模擬器

使用 Sendgrid 傳送電子郵件

Cloud Functions 不允許在通訊埠 25 上進行傳出連線,因此您無法與 SMTP 伺服器之間建立不安全的連線。傳送電子郵件時,建議使用 SendGrid。您可以在 Google Compute Engine 的從執行個體傳送電子郵件教學課程中找到其他傳送電子郵件的選項。

效能

本節說明最佳化效能的最佳做法。

謹慎使用依附元件

由於函式是無狀態的,因此執行環境通常是從頭開始初始化 (這期間就是所謂的「冷啟動」)。發生冷啟動時,會評估函式的全域背景資訊。

如果函式匯入模組,在冷啟動期間,這些模組的載入時間會增加叫用的延遲時間。您可以正確載入依附元件,而不載入函式不使用的依附元件,來減少這一延遲時間以及部署函式需要的時間。

使用全域變數在未來叫用中重複使用物件

在未來叫用中,無法保證會保留 Cloud 函式的狀態。不過,Cloud Functions 通常會重複使用之前叫用的執行環境。如果您在全域範圍宣告變數,便可以在後續叫用中重複使用變數的值,而不必重新計算。

這樣一來,您便可以快取在每次叫用函式時重新建立起來費用可能比較高的的物件。將這類物件從函式主體移至全域範圍可能會使效能大幅提升。下列範例只會為每個函式執行個體建立一個重型物件,並在到達指定執行個體的所有函式叫用中共用這個物件:

Node.js

console.log('Global scope');
const perInstance = heavyComputation();
const functions = require('firebase-functions');

exports.function = functions.https.onRequest((req, res) => {
  console.log('Function invocation');
  const perFunction = lightweightComputation();

  res.send(`Per instance: ${perInstance}, per function: ${perFunction}`);
});

Python

import time

from firebase_functions import https_fn

# Placeholder
def heavy_computation():
  return time.time()

# Placeholder
def light_computation():
  return time.time()

# Global (instance-wide) scope
# This computation runs at instance cold-start
instance_var = heavy_computation()

@https_fn.on_request()
def scope_demo(request):

  # Per-function scope
  # This computation runs every time this function is called
  function_var = light_computation()
  return https_fn.Response(f"Instance: {instance_var}; function: {function_var}")
  

這個 HTTP 函式會採用要求物件 (flask.Request),並傳回回應文字,或可使用 make_response 轉換為 Response 物件的任何一組值。

在全域範圍內快取網路連線、程式庫參考資料和 API 用戶端物件特別重要。範例請參閱最佳化網路一文。

對全域變數執行延遲初始化

如果您在全域範圍內初始化變數,系統會一律透過冷啟動叫用執行初始化程式碼,因此會增加函式的延遲時間。在某些情況下,如果呼叫的服務未在 try/catch 區塊中正確處理,就會造成呼叫服務的間歇性逾時。如果某些物件並未在所有程式碼路徑中使用,請考慮根據需要延遲初始化這些物件:

Node.js

const functions = require('firebase-functions');
let myCostlyVariable;

exports.function = functions.https.onRequest((req, res) => {
  doUsualWork();
  if(unlikelyCondition()){
      myCostlyVariable = myCostlyVariable || buildCostlyVariable();
  }
  res.status(200).send('OK');
});

Python

from firebase_functions import https_fn

# Always initialized (at cold-start)
non_lazy_global = file_wide_computation()

# Declared at cold-start, but only initialized if/when the function executes
lazy_global = None

@https_fn.on_request()
def lazy_globals(request):

  global lazy_global, non_lazy_global

  # This value is initialized only if (and when) the function is called
  if not lazy_global:
      lazy_global = function_specific_computation()

  return https_fn.Response(f"Lazy: {lazy_global}, non-lazy: {non_lazy_global}.")
  

這個 HTTP 函式使用延遲初始化的全域變數。它會採用要求物件 (flask.Request),並傳回回應文字,或可使用 make_response 轉換為 Response 物件的任何一組值。

如果您在單一檔案中定義多個函式,且不同函式使用不同變數,這個方法尤為重要。除非您使用延遲初始化,否則會浪費已初始化但從未使用的變數資源。

設定執行個體數量下限,減少冷啟動次數

根據預設,Cloud Functions 會依據傳入要求數量調整執行個體數量。如要變更這項預設行為,請設定 Cloud Functions 必須隨時保持準備好處理要求的執行個體數量下限。設定執行個體數量下限會減少應用程式的冷啟動次數。如果您的應用程式易受延遲時間影響,建議您設定執行個體數量下限。

如要進一步瞭解這些執行階段選項,請參閱「控制資源調度行為」一節。

其他資源

如要進一步瞭解最佳化效能,請觀看「Google Cloud 效能集錦」影片:Cloud Functions 冷啟動時間