函式最佳做法

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

正確性

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

編寫冪等函式

即使多次呼叫函式,這些函式也應該產生相同結果。這樣一來,如果先前的叫用在程式碼中失敗,您就可以重試叫用。詳情請參閱「重試事件驅動函式」。

確保 HTTP 函式傳送 HTTP 回應

如果函式是 HTTP 觸發,請記得傳送 HTTP 回應,如下所示。否則,函式可能會一直執行,直到逾時為止。在這種情況下,您將為整個逾時時間付費。逾時也可能導致無法預測的行為,或在後續叫用時造成無法預測的行為或額外延遲。

Node.js

const functions = require('@google-cloud/functions-framework');
const escapeHtml = require('escape-html');

/**
 * Responds to an HTTP request using data from the request body parsed according
 * to the "content-type" header.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
functions.http('helloHttp', (req, res) => {
  res.send(`Hello ${escapeHtml(req.query.name || req.body.name || 'World')}!`);
});

Python


import functions_framework


from markupsafe import escape

@functions_framework.http
def hello_http(request):
    """HTTP Cloud Function.
    Args:
        request (flask.Request): The request object.
        <https://flask.palletsprojects.com/en/1.1.x/api/#incoming-request-data>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response>.
    """
    request_json = request.get_json(silent=True)
    request_args = request.args

    if request_json and "name" in request_json:
        name = request_json["name"]
    elif request_args and "name" in request_args:
        name = request_args["name"]
    else:
        name = "World"
    return f"Hello {escape(name)}!"

Go


// Package helloworld provides a set of Cloud Functions samples.
package helloworld

import (
	"encoding/json"
	"fmt"
	"html"
	"net/http"

	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
)

func init() {
	functions.HTTP("HelloHTTP", HelloHTTP)
}

// HelloHTTP is an HTTP Cloud Function with a request parameter.
func HelloHTTP(w http.ResponseWriter, r *http.Request) {
	var d struct {
		Name string `json:"name"`
	}
	if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
		fmt.Fprint(w, "Hello, World!")
		return
	}
	if d.Name == "" {
		fmt.Fprint(w, "Hello, World!")
		return
	}
	fmt.Fprintf(w, "Hello, %s!", html.EscapeString(d.Name))
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Logger;

public class HelloHttp implements HttpFunction {
  private static final Logger logger = Logger.getLogger(HelloHttp.class.getName());

  private static final Gson gson = new Gson();

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    // Check URL parameters for "name" field
    // "world" is the default value
    String name = request.getFirstQueryParameter("name").orElse("world");

    // Parse JSON request and check for "name" field
    try {
      JsonElement requestParsed = gson.fromJson(request.getReader(), JsonElement.class);
      JsonObject requestJson = null;

      if (requestParsed != null && requestParsed.isJsonObject()) {
        requestJson = requestParsed.getAsJsonObject();
      }

      if (requestJson != null && requestJson.has("name")) {
        name = requestJson.get("name").getAsString();
      }
    } catch (JsonParseException e) {
      logger.severe("Error parsing JSON: " + e.getMessage());
    }

    var writer = new PrintWriter(response.getWriter());
    writer.printf("Hello %s!", name);
  }
}

C#

using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;

namespace HelloHttp;

public class Function : IHttpFunction
{
    private readonly ILogger _logger;

    public Function(ILogger<Function> logger) =>
        _logger = logger;

    public async Task HandleAsync(HttpContext context)
    {
        HttpRequest request = context.Request;
        // Check URL parameters for "name" field
        // "world" is the default value
        string name = ((string) request.Query["name"]) ?? "world";

        // If there's a body, parse it as JSON and check for "name" field.
        using TextReader reader = new StreamReader(request.Body);
        string text = await reader.ReadToEndAsync();
        if (text.Length > 0)
        {
            try
            {
                JsonElement json = JsonSerializer.Deserialize<JsonElement>(text);
                if (json.TryGetProperty("name", out JsonElement nameElement) &&
                    nameElement.ValueKind == JsonValueKind.String)
                {
                    name = nameElement.GetString();
                }
            }
            catch (JsonException parseException)
            {
                _logger.LogError(parseException, "Error parsing JSON request");
            }
        }

        await context.Response.WriteAsync($"Hello {name}!", context.RequestAborted);
    }
}

Ruby

require "functions_framework"
require "cgi"
require "json"

FunctionsFramework.http "hello_http" do |request|
  # The request parameter is a Rack::Request object.
  # See https://www.rubydoc.info/gems/rack/Rack/Request
  name = request.params["name"] ||
         (request.body.rewind && JSON.parse(request.body.read)["name"] rescue nil) ||
         "World"
  # Return the response body as a string.
  # You can also return a Rack::Response object, a Rack response array, or
  # a hash which will be JSON-encoded into a response.
  "Hello #{CGI.escape_html name}!"
end

PHP

<?php

use Google\CloudFunctions\FunctionsFramework;
use Psr\Http\Message\ServerRequestInterface;

// Register the function with Functions Framework.
// This enables omitting the `FUNCTIONS_SIGNATURE_TYPE=http` environment
// variable when deploying. The `FUNCTION_TARGET` environment variable should
// match the first parameter.
FunctionsFramework::http('helloHttp', 'helloHttp');

function helloHttp(ServerRequestInterface $request): string
{
    $name = 'World';
    $body = $request->getBody()->getContents();
    if (!empty($body)) {
        $json = json_decode($body, true);
        if (json_last_error() != JSON_ERROR_NONE) {
            throw new RuntimeException(sprintf(
                'Could not parse body: %s',
                json_last_error_msg()
            ));
        }
        $name = $json['name'] ?? $name;
    }
    $queryString = $request->getQueryParams();
    $name = $queryString['name'] ?? $name;

    return sprintf('Hello, %s!', htmlspecialchars($name));
}

請勿啟動背景活動

背景活動是指函式終止後發生的任何活動。函式傳回或以其他方式表示完成後,函式叫用就會結束,例如在 Node.js 事件驅動函式中呼叫 callback 引數。在安全終止後執行的任何程式碼都無法存取 CPU,也不會取得任何進展。

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

您通常可以透過個別叫用作業的記錄,偵測背景活動,方法是尋找在表示叫用作業已完成的資料行後面記錄的任何內容。背景活動有時可能會埋藏在程式碼的較深層位置,尤其是在存在回呼或計時器等非同步作業時。請審查您的程式碼,確認在您終止函式之前,所有非同步作業皆已完成。

一律刪除暫存檔案

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

如要查看個別函式使用的記憶體,請在Google Cloud 主控台的函式清單中選取該函式,然後選擇「Memory usage」圖表。

如果您需要存取長期儲存空間,建議使用 Cloud Run 搭配 Cloud StorageNFS 磁碟區掛載的磁碟區。

使用管道處理較大的檔案時,可以減少記憶體需求。舉例來說,您可以建立讀取串流,將其傳遞至以串流為基礎的程序,然後直接將輸出串流寫入 Cloud Storage,藉此在 Cloud Storage 中處理檔案。

Functions Framework

為確保在各環境中安裝相同的依附元件,我們建議您在套件管理工具中加入 Functions Framework 程式庫,並將依附元件釘選至特定版本的 Functions Framework。

如要這麼做,請在相關的鎖定檔案中加入偏好的版本 (例如 Node.js 的 package-lock.json,或 Python 的 requirements.txt)。

如果未明確將 Functions Framework 列為依附元件,系統會在建構程序中自動使用最新可用的版本。

工具

本節提供如何使用工具實作、測試及與 Cloud Run 函式互動的規範。

本機開發

函式部署作業需要一些時間,因此通常在本機測試函式的程式碼會比較快。

錯誤報告

在使用例外狀況處理的語言中,請勿擲回未偵測到的例外狀況,因為這會在日後的叫用中強制執行冷啟動。

請勿手動退出

手動退出可能會導致非預期的行為。請改用下列語言專屬慣用語:

Node.js

請勿使用 process.exit()。HTTP 函式應透過 res.status(200).send(message) 傳送回應,而事件驅動函式會在傳回 (隱含或明確) 後結束。

Python

請勿使用 sys.exit()。HTTP 函式應明確地以字串形式傳回回應,而事件驅動函式會在傳回值 (明確或隱含) 後結束。

Go

請勿使用 os.Exit()。HTTP 函式應明確地以字串形式傳回回應,而事件驅動函式會在傳回值 (明確或隱含) 後結束。

Java

請勿使用 System.exit()。HTTP 函式應透過 response.getWriter().write(message) 傳送回應,而事件驅動函式會在傳回 (隱含或明確) 後結束。

C#

請勿使用 System.Environment.Exit()。HTTP 函式應透過 context.Response.WriteAsync(message) 傳送回應,而事件驅動函式會在傳回 (隱含或明確) 後結束。

Ruby

請勿使用 exit()abort()。HTTP 函式應明確地以字串形式傳回回應,而事件驅動函式會在傳回值 (明確或隱含) 後結束。

PHP

請勿使用 exit()die()。HTTP 函式應明確地以字串形式傳回回應,而事件驅動函式會在傳回值 (明確或隱含) 後結束。

使用 Sendgrid 傳送電子郵件

Cloud Run 函式不允許通訊埠 25 的傳出連線,因此您無法建立與 SMTP 伺服器的非安全連線。建議您使用 SendGrid 等第三方服務傳送電子郵件。如要瞭解其他傳送電子郵件的方法,請參閱 Google Compute Engine 的從執行個體傳送電子郵件教學課程。

成效

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

避免低並行性

由於冷啟動成本高昂,因此在尖峰期間重複使用最近啟動的執行個體,是處理負載的絕佳最佳化方式。限制並行作業會限制現有執行個體的使用方式,因此會導致更多冷啟動。

增加並行性有助於延遲每個執行個體的多個要求,讓您更輕鬆地處理負載尖峰。

謹慎使用依附元件

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

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

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

我們無法保證 Cloud Run 函式的狀態會保留供日後叫用。不過,Cloud Run 函式通常會回收先前呼叫的執行環境。如果您在全域範圍中宣告變數,其值可以在後續叫用中重複使用,而無需重新計算。

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

Node.js

const functions = require('@google-cloud/functions-framework');

// TODO(developer): Define your own computations
const {lightComputation, heavyComputation} = require('./computations');

// Global (instance-wide) scope
// This computation runs once (at instance cold-start)
const instanceVar = heavyComputation();

/**
 * HTTP function that declares a variable.
 *
 * @param {Object} req request context.
 * @param {Object} res response context.
 */
functions.http('scopeDemo', (req, res) => {
  // Per-function scope
  // This computation runs every time this function is called
  const functionVar = lightComputation();

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

Python

import time

import functions_framework


# 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()


@functions_framework.http
def scope_demo(request):
    """
    HTTP Cloud Function that declares a variable.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """

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

Go


// h is in the global (instance-wide) scope.
var h string

// init runs during package initialization. So, this will only run during an
// an instance's cold start.
func init() {
	h = heavyComputation()
	functions.HTTP("ScopeDemo", ScopeDemo)
}

// ScopeDemo is an example of using globally and locally
// scoped variables in a function.
func ScopeDemo(w http.ResponseWriter, r *http.Request) {
	l := lightComputation()
	fmt.Fprintf(w, "Global: %q, Local: %q", h, l)
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;

public class Scopes implements HttpFunction {
  // Global (instance-wide) scope
  // This computation runs at instance cold-start.
  // Warning: Class variables used in functions code must be thread-safe.
  private static final int INSTANCE_VAR = heavyComputation();

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    // Per-function scope
    // This computation runs every time this function is called
    int functionVar = lightComputation();

    var writer = new PrintWriter(response.getWriter());
    writer.printf("Instance: %s; function: %s", INSTANCE_VAR, functionVar);
  }

  private static int lightComputation() {
    int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    return Arrays.stream(numbers).sum();
  }

  private static int heavyComputation() {
    int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    return Arrays.stream(numbers).reduce((t, x) -> t * x).getAsInt();
  }
}

C#

using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using System.Linq;
using System.Threading.Tasks;

namespace Scopes;

public class Function : IHttpFunction
{
    // Global (server-wide) scope.
    // This computation runs at server cold-start.
    // Warning: Class variables used in functions code must be thread-safe.
    private static readonly int GlobalVariable = HeavyComputation();

    // Note that one instance of this class (Function) is created per invocation,
    // so calling HeavyComputation in the constructor would not have the same
    // benefit.

    public async Task HandleAsync(HttpContext context)
    {
        // Per-function-invocation scope.
        // This computation runs every time this function is called.
        int functionVariable = LightComputation();

        await context.Response.WriteAsync(
            $"Global: {GlobalVariable}; function: {functionVariable}",
            context.RequestAborted);
    }

    private static int LightComputation()
    {
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        return numbers.Sum();
    }

    private static int HeavyComputation()
    {
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        return numbers.Aggregate((current, next) => current * next);
    }
}

Ruby

# Global (instance-wide) scope.
# This block runs on cold start, before any function is invoked.
#
# Note: It is usually best to run global initialization in an on_startup block
# instead at the top level of the Ruby file. This is because top-level code
# could be executed to verify the function during deployment, whereas an
# on_startup block is run only when an actual function instance is starting up.
FunctionsFramework.on_startup do
  instance_data = perform_heavy_computation

  # To pass data into function invocations, the best practice is to set a
  # key-value pair using the Ruby Function Framework's built-in "set_global"
  # method. Functions can call the "global" method to retrieve the data by key.
  # (You can also use Ruby global variables or "toplevel" local variables, but
  # they can make it difficult to isolate global data for testing.)
  set_global :my_instance_data, instance_data
end

FunctionsFramework.http "tips_scopes" do |_request|
  # Per-function scope.
  # This method is called every time this function is called.
  invocation_data = perform_light_computation

  # Retrieve the data computed by the on_startup block.
  instance_data = global :my_instance_data

  "instance: #{instance_data}; function: #{invocation_data}"
end

PHP


use Psr\Http\Message\ServerRequestInterface;

function scopeDemo(ServerRequestInterface $request): string
{
    // Heavy computations should be cached between invocations.
    // The PHP runtime does NOT preserve variables between invocations, so we
    // must write their values to a file or otherwise cache them.
    // (All writable directories in Cloud Functions are in-memory, so
    // file-based caching operations are typically fast.)
    // You can also use PSR-6 caching libraries for this task:
    // https://packagist.org/providers/psr/cache-implementation
    $cachePath = sys_get_temp_dir() . '/cached_value.txt';

    $response = '';
    if (file_exists($cachePath)) {
        // Read cached value from file, using file locking to prevent race
        // conditions between function executions.
        $response .= 'Reading cached value.' . PHP_EOL;
        $fh = fopen($cachePath, 'r');
        flock($fh, LOCK_EX);
        $instanceVar = stream_get_contents($fh);
        flock($fh, LOCK_UN);
    } else {
        // Compute cached value + write to file, using file locking to prevent
        // race conditions between function executions.
        $response .= 'Cache empty, computing value.' . PHP_EOL;
        $instanceVar = _heavyComputation();
        file_put_contents($cachePath, $instanceVar, LOCK_EX);
    }

    // Lighter computations can re-run on each function invocation.
    $functionVar = _lightComputation();

    $response .= 'Per instance: ' . $instanceVar . PHP_EOL;
    $response .= 'Per function: ' . $functionVar . PHP_EOL;

    return $response;
}

在全域範圍內快取網路連線、程式庫參照和 API 用戶端物件特別重要。如需範例,請參閱「網路最佳做法」。

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

根據預設,Cloud Run 函式會依據傳入要求的數量調整執行個體數量。如要變更這項預設行為,請設定 Cloud Run 函式必須保持待命以便處理要求的最低執行個體數量。設定執行個體數量下限可減少應用程式的冷啟動情形。如果應用程式對延遲時間較為敏感,建議您設定最少的例項數量,並在載入期間完成初始化作業。

如要瞭解如何設定執行個體數量下限,請參閱「使用最小執行個體數」。

關於冷啟動和初始化的注意事項

全域初始化作業會在載入期間執行。否則,第一個要求就必須完成初始化和載入模組,因此會造成更高的延遲。

不過,全域初始化也會對冷啟動造成影響。為盡量減少這種影響,請只初始化第一個要求所需的內容,以便將第一個要求的延遲時間降到最低。

如果您已依照上述方式為延遲時間敏感的函式設定最低執行個體數,這一點就格外重要。在這種情況下,如果可以在載入時完成初始化作業,並快取實用的資料,就能確保第一個要求不必執行這項作業,且可以低延遲提供。

如果您在全域範圍內初始化變數,視語言而定,長時間的初始化作業可能會導致兩種行為:- 對於某些語言和非同步程式庫的組合,函式架構可以非同步執行並立即傳回,導致程式碼繼續在背景執行,這可能導致無法存取 CPU等問題。為避免這種情況,您應按照下文所述,在模組初始化時進行阻斷。這麼做也可以確保系統在初始化完成前,不會提供任何要求。- 另一方面,如果初始化是同步的,則長時間初始化會導致冷啟動時間更長,這可能會造成問題,尤其是在負載量激增期間,並且同時有低並行函式時。

非同步 Node.js 程式庫預熱示例

Node.js 搭配 Firestore 就是非同步 Node.js 程式庫的範例。為了充分利用 min_instances,下列程式碼會在載入時完成載入和初始化作業,並阻斷模組載入作業。

使用 TLA,也就是說必須使用 ES6,為 node.js 程式碼使用 .mjs 擴充功能,或將 type: module 新增至 package.json 檔案。

{
  "main": "main.js",
  "type": "module",
  "dependencies": {
    "@google-cloud/firestore": "^7.10.0",
    "@google-cloud/functions-framework": "^3.4.5"
  }
}

Node.js

import Firestore from '@google-cloud/firestore';
import * as functions from '@google-cloud/functions-framework';

const firestore = new Firestore({preferRest: true});

// Pre-warm firestore connection pool, and preload our global config
// document in cache. In order to ensure no other request comes in,
// block the module loading with a synchronous global request:
const config = await firestore.collection('collection').doc('config').get();

functions.http('fetch', (req, res) => {

// Do something with config and firestore client, which are now preloaded
// and will execute at lower latency.
});

全域初始化的範例

Node.js

const functions = require('@google-cloud/functions-framework');

// Always initialized (at cold-start)
const nonLazyGlobal = fileWideComputation();

// Declared at cold-start, but only initialized if/when the function executes
let lazyGlobal;

/**
 * HTTP function that uses lazy-initialized globals
 *
 * @param {Object} req request context.
 * @param {Object} res response context.
 */
functions.http('lazyGlobals', (req, res) => {
  // This value is initialized only if (and when) the function is called
  lazyGlobal = lazyGlobal || functionSpecificComputation();

  res.send(`Lazy global: ${lazyGlobal}, non-lazy global: ${nonLazyGlobal}`);
});

Python

import functions_framework

# 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


@functions_framework.http
def lazy_globals(request):
    """
    HTTP Cloud Function that uses lazily-initialized globals.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """
    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 f"Lazy: {lazy_global}, non-lazy: {non_lazy_global}."

Go


// Package tips contains tips for writing Cloud Functions in Go.
package tips

import (
	"context"
	"log"
	"net/http"
	"sync"

	"cloud.google.com/go/storage"
	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
)

// client is lazily initialized by LazyGlobal.
var client *storage.Client
var clientOnce sync.Once

func init() {
	functions.HTTP("LazyGlobal", LazyGlobal)
}

// LazyGlobal is an example of lazily initializing a Google Cloud Storage client.
func LazyGlobal(w http.ResponseWriter, r *http.Request) {
	// You may wish to add different checks to see if the client is needed for
	// this request.
	clientOnce.Do(func() {
		// Pre-declare an err variable to avoid shadowing client.
		var err error
		client, err = storage.NewClient(context.Background())
		if err != nil {
			http.Error(w, "Internal error", http.StatusInternalServerError)
			log.Printf("storage.NewClient: %v", err)
			return
		}
	})
	// Use client.
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;

public class LazyFields implements HttpFunction {
  // Always initialized (at cold-start)
  // Warning: Class variables used in Servlet classes must be thread-safe,
  // or else might introduce race conditions in your code.
  private static final int NON_LAZY_GLOBAL = fileWideComputation();

  // Declared at cold-start, but only initialized if/when the function executes
  // Uses the "initialization-on-demand holder" idiom
  // More information: https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
  private static class LazyGlobalHolder {
    // Making the default constructor private prohibits instantiation of this class
    private LazyGlobalHolder() {}

    // This value is initialized only if (and when) the getLazyGlobal() function below is called
    private static final Integer INSTANCE = functionSpecificComputation();

    private static Integer getInstance() {
      return LazyGlobalHolder.INSTANCE;
    }
  }

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    Integer lazyGlobal = LazyGlobalHolder.getInstance();

    var writer = new PrintWriter(response.getWriter());
    writer.printf("Lazy global: %s; non-lazy global: %s%n", lazyGlobal, NON_LAZY_GLOBAL);
  }

  private static int functionSpecificComputation() {
    int[] numbers = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
    return Arrays.stream(numbers).sum();
  }

  private static int fileWideComputation() {
    int[] numbers = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
    return Arrays.stream(numbers).reduce((t, x) -> t * x).getAsInt();
  }
}

C#

using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace LazyFields;

public class Function : IHttpFunction
{
    // This computation runs at server cold-start.
    // Warning: Class variables used in functions code must be thread-safe.
    private static readonly int NonLazyGlobal = FileWideComputation();

    // This variable is initialized at server cold-start, but the
    // computation is only performed when the function needs the result.
    private static readonly Lazy<int> LazyGlobal = new Lazy<int>(
        FunctionSpecificComputation,
        LazyThreadSafetyMode.ExecutionAndPublication);

    public async Task HandleAsync(HttpContext context)
    {
        // In a more complex function, there might be some paths that use LazyGlobal.Value,
        // and others that don't. The computation is only performed when necessary, and
        // only once per server.
        await context.Response.WriteAsync(
            $"Lazy global: {LazyGlobal.Value}; non-lazy global: {NonLazyGlobal}",
            context.RequestAborted);
    }

    private static int FunctionSpecificComputation()
    {
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        return numbers.Sum();
    }

    private static int FileWideComputation()
    {
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        return numbers.Aggregate((current, next) => current * next);
    }
}

Ruby

FunctionsFramework.on_startup do
  # This method is called when the function is initialized, not on each
  # invocation.

  # Declare and set non_lazy_global
  set_global :non_lazy_global, file_wide_computation

  # Declare, but do not set, lazy_global
  set_global :lazy_global do
    function_specific_computation
  end
end

FunctionsFramework.http "tips_lazy" do |_request|
  # This method is called every time this function is called.

  "Lazy: #{global :lazy_global}; non_lazy: #{global :non_lazy_global}"
end

PHP

PHP 函式無法在要求之間保留變數。上述範例中的範圍會使用延遲載入功能,在檔案中快取全域變數值。

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

其他資源

如要進一步瞭解如何提升效能,請觀看「Google Cloud Performance Atlas」影片中的「Cloud Run 函式的冷啟動時間」