Functions 最佳实践

本文档介绍了设计、实现、测试和部署 Cloud Run functions 函数的最佳做法。

正确做法

本部分介绍 Cloud Run functions 函数设计和实现方面的常规最佳做法。

编写幂等函数

即使您的函数被多次调用,也应产生相同的结果。这样,如果前面的代码调用中途失败,您可以重新调用。如需了解详情,请参阅重试事件驱动型函数。

确保 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/escape"
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"] ||
         begin
           request.body.rewind
           JSON.parse(request.body.read)["name"]
         rescue StandardError
           nil
         end ||
         "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 控制台,在函数列表中选择相应的函数,然后选择“内存用量”图。

如果您需要访问长期存储空间,请考虑将 Cloud Run 卷装载与 Cloud Storage 或 NFS 卷搭配使用。

您可以在使用流水线处理大型文件时减少内存要求。例如,要在 Cloud Storage 上处理文件,您可以创建读取流,通过基于流的进程传递读取流,然后将输出流直接写入 Cloud Storage。

Cloud Functions 框架

为了确保在不同的环境中以一致的方式安装相同的依赖项,我们建议您在软件包管理系统中添加 Functions 框架库,并将依赖项固定到特定版本的 Functions 框架。

为此,请在相关锁定文件中添加您的首选版本(例如,Node.js 版为 package-lock.json,Python 版为 requirements.txt)。

如果 Functions 框架未明确列为依赖项,系统会在构建流程中使用最新可用版本自动添加该框架。

工具

本部分指导您如何使用工具来实现和测试 Cloud Run functions 函数并与之互动。

本地开发

函数部署需要一些时间,因此在本地测试函数的代码通常会更快。

错误报告

在使用异常处理的语言中,不要抛出未捕获的异常,因为这些异常会导致在未来调用函数时强制执行冷启动。

请勿手动退出

手动退出可能会导致出现意外行为。请改用以下特定语言的惯用语:

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 functions 函数不允许在端口 25 上建立出站连接,因此您无法建立到 SMTP 服务器的非安全连接。推荐使用第三方服务(例如 SendGrid)发送电子邮件。如需了解发送电子邮件的其他方式,请参阅适用于 Google Compute Engine 的从实例发送电子邮件教程。

性能

本部分介绍性能优化方面的最佳做法。

避免低并发

由于冷启动成本较高,因此能够在高峰期间重用最近启动的实例是处理负载的绝佳优化方式。限制并发数会限制可利用现有实例的方式,从而导致更多冷启动。

提高并发性有助于推迟每个实例的多个请求,从而更轻松地处理负载高峰。

谨慎使用依赖项

由于函数是无状态的,执行环境通常是从头开始初始化(称为“冷启动”)。当发生冷启动时,系统会对函数的全局环境进行评估。

如果您的函数导入了模块,那么在冷启动期间,这些模块的加载时间会造成调用延迟加重。正确加载依赖项而不加载函数不使用的依赖项,即可缩短此延迟时间以及函数部署时间。

使用全局变量,以便在日后的调用中重复使用对象

系统无法保证能保留 Cloud Run 函数的状态,以用于将来的调用。不过,Cloud Run functions 经常会回收利用先前调用的执行环境。如果您在全局范围内声明一个变量,就可以在后续的调用中再次使用该变量的值,而不必重新计算。

通过这种方式,您可以缓存在每次调用函数时重建的成本较高的对象。将此类对象从函数体移到全局范围可能会显著提升性能。以下示例会为每个函数实例创建一个重量级对象(每个实例仅限一次),并提供给连接指定实例的所有函数调用共用:

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 functions 函数会根据传入请求的数量扩缩实例数量。您可以更改这种默认行为,只需设置 Cloud Run functions 函数必须保持就绪状态以处理请求的实例数下限即可。设置实例数下限可以减少应用的冷启动次数。如果您的应用对延迟时间较为敏感,我们建议您设置实例数下限,并在加载时完成初始化。

如需了解如何设置实例数下限,请参阅使用最小实例数。

有关冷启动和初始化的注意事项

全局初始化在加载时进行。如果没有进行全局初始化,第一个请求需要完成初始化并加载模块,从而导致更长的延迟时间。

不过,全局初始化也会对冷启动产生影响。为了尽量减少这种影响,请仅初始化第一个请求所需的内容,以尽可能缩短第一个请求的延迟时间。

如果您如上文所述为对延迟时间较为敏感的函数配置了实例数下限,这一点就尤为重要。在这种情况下,在加载时完成初始化并缓存有用的数据可确保第一个请求无需执行这些操作,并能够以低延迟进行响应。

如果您在全局范围内初始化变量,那么根据所用语言的不同,较长的初始化时间可能会导致两种行为:- 对于某些语言和异步库的组合,函数框架可能异步运行并立即返回,导致代码在后台继续运行,这可能会导致无法访问 CPU 等问题。为避免这种情况,您应阻止模块初始化,具体如下文所述。这还可确保在初始化完成之前不会响应请求。- 另一方面,如果初始化是同步的,那么较长的初始化时间会导致更长的冷启动时间,这可能会成为一个问题,尤其是对于负载高峰期间的低并发函数。

异步 Node.js 库预热示例

使用 Firestore 的 Node.js 就是一个异步 Node.js 库示例。为了充分利用实例数下限,以下代码会在加载时完成加载和初始化,并阻止模块加载。

使用了 TLA,这意味着需要 ES6(为 Node.js 代码使用 .mjs 扩展程序或向 package.json 文件添加 type: module)。

{
  "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  # noqa: F824

    # 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 性能指南”视频 Cloud Run 函数冷启动时间。