Cloud Run functions 执行环境

Cloud Run functions 函数在全托管式无服务器环境中运行。在此环境中,Google 会处理基础设施、操作系统和运行时环境。每个函数都在其专有的孤立安全执行环境中运行,可自动调节,并且其生命周期不受其他函数的干扰。

运行时

Cloud Run functions 函数支持多语言运行时。每个语言的运行时都包含一组标准的系统软件包,以及该语言所需的工具和库。如果您要通过命令行Terraform 部署函数,则需要运行时 ID 值。

为所有 Cloud Run functions 函数和 Cloud Run functions 函数(第 1 代)执行环境提供安全和维护更新。这些更新会自动或手动应用,具体取决于环境及其配置方式。如需详细了解执行环境更新,请参阅保护 Cloud Run functions 函数

运行时映像托管在提供 Artifact Registry 的每个区域。您可以通过将 URI 的第一部分替换为您所选的区域来自定义运行时映像路径:

REGION-docker.pkg.dev/serverless-runtimes/STACK/runtimes/RUNTIME_ID

您需要进行如下替换:

  • REGION 替换为首选区域,例如 us-central1
  • STACK 替换为首选操作系统栈,例如 google-22-full
  • RUNTIME_ID 替换为函数使用的运行时 ID,例如 python310

例如,使用 google-22-full 栈且托管在 us-central1 中的最新 Node.js 20 基础映像将通过以下网址进行引用:us-central1-docker.pkg.dev/serverless-runtimes/google-22-full/runtimes/nodejs22

Node.js

运行时 生成 运行时 ID 堆栈 运行时基础映像
Node.js 22
(仅限预览版)
Run functions nodejs22 google-22-full google-22-full/nodejs22
Node.js 20 第 1 代、Run functions nodejs20 google-22-full google-22-full/nodejs20
Node.js 18 第 1 代、Run functions nodejs18 google-22-full google-22-full/nodejs18
Node.js 16 第 1 代、Run functions nodejs16 google-18-full google-18-full/nodjes16
Node.js 14 第 1 代、Run functions nodejs14 google-18-full google-18-full/nodjes14
Node.js 12 第 1 代、Run functions nodejs12 google-18-full google-18-full/nodjes14
Node.js 10 第 1 代、Run functions nodejs10 google-18-full google-18-full/nodjes10
Node.js 8 第 1 代、Run functions nodejs8 已停用 已停用
Node.js 6 第 1 代、Run functions nodejs6 已停用 已停用

Python

运行时 生成 运行时 ID 堆栈 运行时基础映像
Python 3.12 第 1 代、Run functions python312 google-22-full google-22-full/python312
Python 3.11 第 1 代、Run functions python311 google-22-full google-22-full/python311
Python 3.10 第 1 代、Run functions python310 google-22-full google-22-full/python310
Python 3.9 第 1 代、Run functions python39 google-18-full google-18-full/python39
Python 3.8 第 1 代、Run functions python38 google-18-full google-18-full/python38
Python 3.7 第 1 代、Run functions python37 google-18-full google-18-full/python37

Go

运行时 生成 运行时 ID 堆栈 运行时基础映像
Go 1.22 Run functions go122 google-22-full google-22-full/go122
Go 1.21 Run functions go121 google-22-full google-22-full/go121
Go 1.20 Run functions go120 google-22-full google-22-full/go120
Go 1.19 第 1 代、Run functions go119 google-22-full google-22-full/go119
Go 1.18 第 1 代、Run functions go118 google-22-full google-22-full/go120
Go 1.16 第 1 代、Run functions go116 google-18-full google-18-full/go116
Go 1.13 第 1 代、Run functions go113 google-18-full google-18-full/go113
Go 1.11 第 1 代、Run functions go111 已停用 已停用

Java

运行时 生成 运行时 ID 堆栈 运行时基础映像
Java 21 Run functions java21 google-22-full google-22-full/java21
Java 17 第 1 代、Run functions java17 google-22-full google-22-full/java17
Java 11 第 1 代、Run functions java11 google-18 google-18/java11

Ruby

运行时 生成 运行时 ID 堆栈 运行时基础映像
Ruby 3.3 第 1 代、Run functions ruby33 google-22-full google-22-full/ruby33
Ruby 3.2 第 1 代、Run functions ruby32 google-22-full google-22-full/ruby32
Ruby 3.0 第 1 代、Run functions ruby30 google-18-full google-18-full/ruby30
Ruby 2.7 第 1 代、Run functions ruby27 google-18-full google-18-full/ruby27
Ruby 2.6 第 1 代、Run functions ruby26 google-18-full google-18-full/ruby26

PHP

运行时 生成 运行时 ID 堆栈 运行时基础映像
PHP 8.3 Run functions php83 google-22-full google-22-full/php83
PHP 8.2 第 1 代、Run functions php82 google-22-full google-22-full/php82
PHP 8.1 第 1 代、Run functions php81 google-18-full google-18-full/php81
PHP 7.4 第 1 代、Run functions php74 google-18-full google-18-full/php74

.NET Core

运行时 生成 运行时 ID 堆栈 运行时基础映像
.NET Core 8 Run functions dotnet8 google-22-full google-22-full/dotnet8
.NET Core 6 第 1 代、Run functions dotnet6 google-22-full google-22-full/dotnet6
.NET Core 3 第 1 代、Run functions dotnet3 google-18-full google-18-full/dotnet3

自动扩缩行为

Cloud Run functions 实现了无服务器范式,让您只需运行代码,而不必担心底层基础架构(如服务器或虚拟机)。部署后,您的函数会自动进行管理和扩缩。

Cloud Run functions 通过将传入请求分配给函数实例来处理这些请求。Cloud Run functions 可以将请求分配给现有实例,也可以创建新的实例,具体取决于请求和现有函数实例的数量。

如果入站请求数量超过现有实例的数量,Cloud Run functions 可以启动多个新实例来处理请求。 通过这种自动扩容行为,Cloud Run functions 可以分别使用不同的函数实例并行处理多个请求。

在某些情况下,无限地扩容可能不可取。为了解决此问题,Cloud Run functions 允许您配置特定函数在任何给定时间可以共存的实例数上限

无状态

如需启用函数的自动管理和扩缩,函数必须是无状态的 - 一次函数调用不得依赖于先前调用设置的内存中状态。调用可能由不共享全局变量、内存、文件系统或其他状态的不同函数实例处理。

如果您需要在多次函数调用之间共享状态,则您的函数应使用 MemorystoreDatastoreFirestoreCloud Storage 等服务来保留数据。如需详细了解 Google Cloud 提供的数据库和存储选项,请参阅 Google Cloud 数据库Google Cloud 存储产品

并发

Cloud Run functions

Cloud Run functions 函数支持在单个函数实例上处理多个并发请求。这对于防止冷启动很有帮助,因为已预热的实例可以同时处理多个请求,从而缩短总体延迟时间。如需了解详情,请参阅并发

Cloud Run functions(第 1 代)

在 Cloud Run functions (第 1 代) 中,每个函数实例一次只处理一个并发请求。这意味着,当您的代码处理一个请求时,不可能有第二个请求被路由到同一个实例。因此,原始请求可充分使用您分配的资源(内存和 CPU)。

由于 Cloud Run functions (第 1 代) 中的并发请求由不同的函数实例处理,因此这些请求不会共享变量或本地内存。如需了解详情,请参阅无状态函数实例有效期

冷启动

在以下两种情况下,系统会启动新的函数实例:

  • 当您部署函数时。

  • 当系统自动创建新函数实例来扩展函数计算能力以处理相应负载时,或偶尔替换现有实例时。

启动新的函数实例涉及加载运行时和您的代码。如果请求会导致启动新的函数实例(称为冷启动),则其处理速度可能会慢于那些发往现有函数实例的请求。但是,如果您的函数收到的负载量较为稳定,那么冷启动的次数通常可以忽略不计,除非您的函数因崩溃而需要频繁重启函数环境。

如果您的函数代码抛出未捕获的异常或使当前进程崩溃,该函数实例可能会重启。这可能会导致多次冷启动,从而出现较长的延时,因此我们建议捕获异常并避免终止当前进程。请参阅报告错误,了解如何处理和报告 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();
  }
}

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

您可以使用全局变量作为性能优化方法,但不得依赖于先前函数调用在全局范围内设置的状态 - 如需了解详情,请参阅无状态

您可以假设在调用每个函数代码之前,每个函数实例都正好执行了一次全局范围。但是,您不能依赖于全局范围执行的总次数或时间,因为这些因素可能会因自动扩缩活动而异。

函数执行时间轴

函数只能在其执行期间使用所分配的资源(内存和 CPU)。如果在函数执行期之外运行代码,这些代码不一定会执行,并且可能随时会停止。因此,您应始终正确地发出信号来表明函数执行已结束,并避免在函数执行期之外运行任何代码。如需获取指导,请参阅 HTTP 函数后台函数CloudEvent 函数

函数执行还取决于函数的超时时长。如需了解详情,请参阅函数超时

在初始化应用时,请考虑执行时间轴。在初始化期间,不应在全局范围内创建后台任务,因为这些任务会在请求的持续时间以外执行。

执行保证

针对每个传入事件,您的函数通常会调用一次。但是,由于各种错误场景的差异,Cloud Run functions 函数并不保证在所有情况下都调用一次且只调用一次。

您的函数在单个事件中可被调用的次数上限或下限取决于函数的类型:

  • HTTP 函数最多会被调用一次。这是由于 HTTP 调用的同步性质所致,这意味着在函数调用期间发生的任何错误都将返回,而不会重试。HTTP 函数的调用者应负责处理错误,并在必要时重试。

  • 事件驱动函数至少被调用一次。这是由于事件的异步性质所致,在此情况下没有调用者等待响应。在极少数情况下,系统可能会多次调用事件驱动函数,以确保事件的传递。如果事件驱动函数调用因错误而失败,那么除非为该函数启用了失败时重试,否则该函数不会被再次调用。

为了确保函数在重新尝试执行时可正常运行,您应该使其具有幂等性,方法是恰当实现函数,让事件即使被传递多次,产生的结果(和副作用)也符合预期。对于 HTTP 函数,这还意味着即使调用者尝试重新调用 HTTP 函数端点,HTTP 函数也应该能返回所期望的值。如需详细了解如何使函数具有幂等性,请参阅重试事件驱动的函数

内存和文件系统

每个函数分配有一定的内存量供其使用。您可以在部署时配置内存量 - 如需了解详情,请参阅内存限制

函数执行环境包括内存文件系统,其中包含与您的函数一起部署的源代码文件和目录(请参阅构建源代码)。包含源文件的目录是只读的,但文件系统的其余部分是可写的(操作系统使用的文件除外)。使用文件系统会计入函数的内存用量。

您的函数可以使用每种编程语言的标准方法与文件系统进行交互。

网络

您的函数可以使用每种编程语言的标准方法访问公共互联网,无论是通过运行时提供的内置库,还是您作为依赖项添加的第三方库。

请尝试在多次函数调用中重复使用网络连接,如优化网络中所述。但是,请注意,如果某个连接未被使用的时间达到 10 分钟,则可能会被系统关闭。若继续尝试使用已关闭的连接,将导致“连接重置”错误。您的代码应该使用可以很好地处理已关闭连接的库,或者明确处理已关闭的连接(如果使用的是低层级网络结构)。

函数隔离

每个部署的函数都与其他所有函数(甚至是从同一个源文件部署的函数)隔离。具体来说,这些函数不会共享内存、全局变量、文件系统或其他状态。

如需在部署的函数之间共享数据,您可以使用诸如以下服务:MemorystoreDatastoreFirestoreCloud Storage。或者,您也可以从一个函数调用另一个函数(通过使用它们的相应触发器并传递必要的数据)。例如,向 HTTP 函数的端点发出 HTTP 请求,或者向 Pub/Sub 主题发布消息以触发 Pub/Sub 函数。