Cloud Functions 函数执行环境

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

运行时

Cloud Functions 函数支持多语言运行时。如果您要通过命令行Terraform 部署函数,则需要运行时 ID 值。

运行时 基础映像 运行时 ID
Node.js 16(推荐) Ubuntu 18.04 nodejs16
Node.js 14 Ubuntu 18.04 nodejs14
Node.js 12 Ubuntu 18.04 nodejs12
Node.js 10 Ubuntu 18.04 nodejs10
Node.js 8(已弃用) Ubuntu 18.04 nodejs8
Node.js 6(已停用) Debian 8 nodejs6
Python 3.9(推荐) Ubuntu 18.04 python39
Python 3.8 Ubuntu 18.04 python38
Python 3.7 Ubuntu 18.04 python37
Go 1.16(推荐) Ubuntu 18.04 go116
Go 1.13 Ubuntu 18.04 go113
Go 1.11 Ubuntu 18.04 go111
Java 11(推荐) Ubuntu 18.04 java11
.NET Core 3.1(推荐) Ubuntu 18.04 dotnet3
Ruby 2.7(推荐) Ubuntu 18.04 ruby27
Ruby 2.6 Ubuntu 18.04 ruby26
PHP 7.4(推荐) Ubuntu 18.04 php74

通常,系统会自动更新运行时(除非另有通知)。当语言社区提供可用的语言版本自动更新时,所有运行时都会收到。同样,Cloud Functions 可能会将更新应用于执行环境的其他方面,例如操作系统或包含的软件包。这些更新有助于确保函数的安全。

无状态函数

Cloud Functions 实现了无服务器范式,让您只需运行代码,而不必担心底层基础架构(如服务器或虚拟机)。为了让 Google 自动管理和扩缩函数,函数必须是无状态的,一次函数调用不应该依赖于先前调用设置的内存中状态。但是,您往往可以通过重复使用现有状态来实现性能优化;如需了解详情,请参阅提示和技巧中的建议。

例如,以下函数返回的计数器值与函数的总调用次数不一致,因为调用可能由不共享全局变量、内存、文件系统或其他状态的不同函数实例处理:

Node.js

// Global variable, but only shared within function instance.
let count = 0;

/**
 * HTTP Cloud Function that counts how many times
 * it is executed within a specific instance.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.executionCount = (req, res) => {
  count++;

  // Note: the total function invocation count across
  // all instances may not be equal to this value!
  res.send(`Instance execution count: ${count}`);
};

Python

# Global variable, modified within the function by using the global keyword.
count = 0

def statelessness(request):
    """
    HTTP Cloud Function that counts how many times it is executed
    within a specific instance.
    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 count
    count += 1

    # Note: the total function invocation count across
    # all instances may not be equal to this value!
    return 'Instance execution count: {}'.format(count)

Go


// Package http provides a set of HTTP Cloud Functions samples.
package http

import (
	"fmt"
	"net/http"
)

// count is a global variable, but only shared within a function instance.
var count = 0

// ExecutionCount is an HTTP Cloud Function that counts how many times it
// is executed within a specific instance.
func ExecutionCount(w http.ResponseWriter, r *http.Request) {
	count++

	// Note: the total function invocation count across
	// all instances may not be equal to this value!
	fmt.Fprintf(w, "Instance execution count: %d", count)
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;

public class ExecutionCount implements HttpFunction {

  private final AtomicInteger count = new AtomicInteger(0);

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    count.getAndIncrement();

    // Note: the total function invocation count across
    // all instances may not be equal to this value!
    BufferedWriter writer = response.getWriter();
    writer.write("Instance execution count: " + count);
  }
}

C#

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

namespace ExecutionCount
{
    public class Function : IHttpFunction
    {
        // Note that this variable must be static, because a new instance is
        // created for each request. An alternative approach would be to use
        // dependency injection with a singleton resource injected into the function
        // constructor.
        private static int _count;

        public async Task HandleAsync(HttpContext context)
        {
            // Note: the total function invocation count across
            // all servers may not be equal to this value!
            int currentCount = Interlocked.Increment(ref _count);
            await context.Response.WriteAsync($"Server execution count: {currentCount}");
        }
    }
}

Ruby

require "functions_framework"

# Global variable, but scoped only within one function instance.
$count = 0

FunctionsFramework.http "execution_count" do |_request|
  $count += 1

  # NOTE: the total function invocation count across all instances
  # may not be equal to this value!
  "Instance execution count: #{$count}"
end

如果您需要在多次函数调用之间共享状态,则您的函数应使用 DatastoreFirestoreCloud Storage 等服务来保留数据。如需了解所有可用的存储方案,请参阅选择存储方案

自动扩容和并发

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

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

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

由于并发请求由不同的函数实例处理,因此这些请求不会共享变量或本地内存。本文档稍后会对此进行详细介绍。

控制自动扩容行为

使用 Cloud Functions,您可以针对在任何给定时间都可以共存的函数实例的总数设定限制。在某些情况下,无限地扩容是不可取的。例如,您的函数可能依赖于某项资源(例如数据库),而该资源无法纵向扩容到与 Cloud Functions 函数相同的程度。请求数量急剧增加可能导致 Cloud Functions 函数创建的函数实例数量超出数据库的承受能力。

冷启动

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

  • 当您部署函数时。

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

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

如果您的函数对延迟时间较为敏感,请考虑设置实例数上限以避免冷启动。

函数实例有效期

除非您的函数崩溃或者系统因缺少持续流量而减少实例数量,否则运行函数实例的环境通常具有弹性,会在随后的函数调用中重复使用。也就是说,当一个函数执行结束时,另一次函数调用可由同一个函数实例处理。因此,建议尽可能在全局范围内缓存多次调用的状态。您的函数应仍然可以在没有此缓存的情况下正常运行,因为无法保证下一次调用将发往相同的函数实例(请参阅无状态函数)。

函数范围与全局范围

单次函数调用只会导致系统执行被声明为入口点的函数代码。函数文件中的全局范围(一般包含函数定义)将在每次冷启动时执行,但是如果实例已经初始化,相应全局范围将不会执行。

Node.js

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

/**
 * HTTP function that declares a variable.
 *
 * @param {Object} req request context.
 * @param {Object} res response context.
 */
exports.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

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

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 'Instance: {}; function: {}'.format(instance_var, 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()
}

// 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

您可以假设在新的函数实例中调用您的函数代码之前(以及随后每次创建新的函数实例时),全局范围都正好执行了一次。但是,您不应该依赖全局范围执行的总次数或时间,因为这些因素取决于由 Google 管理的自动调节机制。

函数执行时间轴

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

例如,发送 HTTP 响应后执行的代码可能随时被中断:

Node.js

/**
 * HTTP Cloud Function that may not completely
 * execute due to early HTTP response
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.afterResponse = (req, res) => {
  res.end();

  // This statement may not execute
  console.log('Function complete!');
};

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

执行保证

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

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

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

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

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

错误

对于如何让函数发出信号来指示出错,建议的方式根据函数类型而有所不同:

  • HTTP 函数应该返回可指明具体错误的适当 HTTP 状态代码。如需示例,请参阅 HTTP 函数

  • 事件驱动的函数应记录并返回错误消息。如需查看相关示例,请参阅后台函数CloudEvent 函数

如果按建议的方式返回错误,那么返回错误的函数实例会被标记为运行正常,并且会在需要时用于处理将来的请求。

如果您的代码或者您调用的其他任何代码抛出了未捕获到的异常或使当前进程崩溃,相应函数实例可能会在处理下一次调用之前重新启动。这可能会导致更多次冷启动,从而出现较长的延时,因此建议尽量避免这种情况。

有关如何报告 Cloud Functions 函数中的错误的更多讨论,请参阅报告错误

超时

函数执行时间受超时时长的限制,您可以在部署函数时指定该时长。默认情况下,函数的超时时长设置为 1 分钟,但您可以延长此时长(最多 9 分钟)。

当函数执行时间超出该值时,系统将立即向调用者返回错误状态。对于超时的函数实例,系统会限制其对 CPU 资源的使用,并且可能会立即暂停对请求的处理。被暂停的处理可能会也可能不会在后续请求中继续进行,因此可能导致意外的副作用。

在下面的代码段中,部分代码被安排在函数开始执行 2 分钟后再执行。如果超时值被设置为 1 分钟,那么这些代码就永远不会执行:

Node.js

/**
 * HTTP Cloud Function that may not completely
 * execute due to function execution timeout
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.afterTimeout = (req, res) => {
  setTimeout(() => {
    // May not execute if function's timeout is <2 minutes
    console.log('Function running...');
    res.end();
  }, 120000); // 2 minute delay
};

Python

def timeout(request):
    print('Function running...')
    time.sleep(120)

    # May not execute if function's timeout is <2 minutes
    print('Function completed!')
    return 'Function completed!'

Go


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

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

// Timeout sleeps for 2 minutes and may time out before finishing.
func Timeout(w http.ResponseWriter, r *http.Request) {
	log.Println("Function execution started...")
	time.Sleep(2 * time.Minute)
	log.Println("Function completed!")
	fmt.Fprintln(w, "Function completed!")
}

Java


package functions;

import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

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

  // Simple function to return "Hello World"
  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException, InterruptedException {
    logger.info("Function running...");
    TimeUnit.MINUTES.sleep(2);

    // May not execute if function's timeout is <2 minutes
    logger.info("Function completed!");
    BufferedWriter writer = response.getWriter();
    writer.write("Function completed!");
  }
}

在某些情况下,上述代码可能会以意外的方式成功执行。请考虑函数超时场景。正在处理请求的实例暂停(通过限制 CPU)。待处理工作暂停。如果某一后续请求被路由到同一实例,则系统会恢复待处理工作并向日志发送 Function running...

上述行为的常见表现是与一个请求相关的工作和日志“泄露”到某一后续请求。由于已暂停的工作不一定会得到恢复,因此您不应依赖上述行为。在这种情况下,您的函数应结合使用以下技术来避免发生超时情况:

  1. 将超时值设置为高于函数的预期执行时间。
  2. 跟踪剩余执行时间并提前执行清理/退出操作。

如需使用 gcloud 命令行工具设置函数的执行时间上限,请在部署时使用 --timeout 标志:

gcloud functions deploy FUNCTION_NAME --timeout=TIMEOUT FLAGS...

在上述命令中,FLAGS... 是指您在部署函数期间传递的其他选项。如需查看 deploy 命令的完整参考信息,请参阅 gcloud functions deploy

您还可以通过 Cloud Console 在创建函数期间设置超时值,如下所示:

  1. 在 Cloud Console 中转到 Cloud Functions 概览页面

  2. 点击创建函数

  3. 填写函数的必填字段。

  4. 点击更多以查看高级设置。

  5. 超时字段中输入一个值。

内存

如需使用 gcloud 命令行工具设置函数的内存,请将 --memory 标志与兆字节数搭配使用。例如:

gcloud functions deploy FUNCTION_NAME --memory=MEMORY

默认情况下,系统为每个函数分配的内存为 256MB。

文件系统

函数执行环境包含可运行的函数文件以及已部署的函数包中包含的文件和目录(例如本地依赖项)。这些文件位于一个只读目录中,该目录可根据函数文件位置确定。请注意,函数的目录可能与当前工作目录不同。

以下示例列出了位于函数目录中的文件:

Node.js

const fs = require('fs');

/**
 * HTTP Cloud Function that lists files in the function directory
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.listFiles = (req, res) => {
  fs.readdir(__dirname, (err, files) => {
    if (err) {
      console.error(err);
      res.sendStatus(500);
    } else {
      console.log('Files', files);
      res.sendStatus(200);
    }
  });
};

Python

def list_files(request):
    import os
    from os import path

    root = path.dirname(path.abspath(__file__))
    children = os.listdir(root)
    files = [c for c in children if path.isfile(path.join(root, c))]
    return 'Files: {}'.format(files)

Go


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

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

// ListFiles lists the files in the current directory.
// Uses directory "serverless_function_source_code" as defined in the Go
// Functions Framework Buildpack.
// See https://github.com/GoogleCloudPlatform/buildpacks/blob/56eaad4dfe6c7bd0ecc4a175de030d2cfab9ae1c/cmd/go/functions_framework/main.go#L38.
func ListFiles(w http.ResponseWriter, r *http.Request) {
	files, err := ioutil.ReadDir("./serverless_function_source_code")
	if err != nil {
		http.Error(w, "Unable to read files", http.StatusInternalServerError)
		log.Printf("ioutil.ListFiles: %v", err)
		return
	}
	fmt.Fprintln(w, "Files:")
	for _, f := range files {
		fmt.Fprintf(w, "\t%v\n", f.Name())
	}
}

Java


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

public class FileSystem implements HttpFunction {

  // Lists the files in the current directory.
  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    File currentDirectory = new File(".");
    File[] files = currentDirectory.listFiles();
    PrintWriter writer = new PrintWriter(response.getWriter());
    writer.println("Files:");
    for (File f : files) {
      writer.printf("\t%s%n", f.getName());
    }
  }
}

C#

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

namespace FileSystem
{
    public class Function : IHttpFunction
    {
        public async Task HandleAsync(HttpContext context)
        {
            string[] files = Directory.GetFiles(".");

            await context.Response.WriteAsync("Files:\n");
            foreach (string file in files)
            {
                await context.Response.WriteAsync($"\t{file}\n");
            }
        }
    }
}

Ruby

require "functions_framework"

FunctionsFramework.http "concepts_filesystem" do |_request|
  files = Dir.entries "."
  "Files: #{files.join "\n"}"
end

PHP


use Psr\Http\Message\ServerRequestInterface;

function listFiles(ServerRequestInterface $request): string
{
    $contents = scandir(__DIR__);

    $output = 'Files:' . PHP_EOL;

    foreach ($contents as $file) {
        $output .= "\t" . $file . PHP_EOL;
    }

    return $output;
}

您还可以从随该函数部署的其他文件中加载代码。

文件系统本身是完全可写的(底层操作系统使用的文件除外),并且存储在 Cloud Functions 实例的内存中。

网络

您的函数可以使用来自运行时或第三方提供方的标准库访问公共互联网。例如,您可以下面的方式调用 HTTP 端点:

Node.js

const fetch = require('node-fetch');

/**
 * HTTP Cloud Function that makes an HTTP request
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.makeRequest = async (req, res) => {
  const url = 'https://example.com'; // URL to send the request to
  const externalRes = await fetch(url);
  res.sendStatus(externalRes.ok ? 200 : 500);
};

Python

def make_request(request):
    """
    HTTP Cloud Function that makes another HTTP request.
    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>.
    """
    import requests

    # The URL to send the request to
    url = 'http://example.com'

    # Process the request
    response = requests.get(url)
    response.raise_for_status()
    return 'Success!'

Go


// Package http provides a set of HTTP Cloud Functions samples.
package http

import (
	"fmt"
	"net/http"
	"time"
)

var urlString = "https://example.com"

// client is used to make HTTP requests with a 10 second timeout.
// http.Clients should be reused instead of created as needed.
var client = &http.Client{
	Timeout: 10 * time.Second,
}

// MakeRequest is an example of making an HTTP request. MakeRequest uses a
// single http.Client for all requests to take advantage of connection
// pooling and caching. See https://godoc.org/net/http#Client.
func MakeRequest(w http.ResponseWriter, r *http.Request) {
	resp, err := client.Get(urlString)
	if err != nil {
		http.Error(w, "Error making request", http.StatusInternalServerError)
		return
	}
	if resp.StatusCode != http.StatusOK {
		msg := fmt.Sprintf("Bad StatusCode: %d", resp.StatusCode)
		http.Error(w, msg, http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "ok")
}

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.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpResponse.BodyHandlers;
import java.time.Duration;

public class SendHttpRequest implements HttpFunction {

  // Create a client with some reasonable defaults. This client can be reused for multiple requests.
  // (java.net.httpClient also pools connections automatically by default.)
  private static HttpClient client =
      HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException, InterruptedException {
    // Create a GET sendHttpRequest to "http://example.com"
    String url = "http://example.com";
    var getRequest = java.net.http.HttpRequest.newBuilder().uri(URI.create(url)).GET().build();

    // Send the sendHttpRequest using the client
    var getResponse = client.send(getRequest, BodyHandlers.ofString());

    // Write the results to the output:
    var writer = new PrintWriter(response.getWriter());
    writer.printf("Received code '%s' from url '%s'.", getResponse.statusCode(), url);
  }
}

C#

using Google.Cloud.Functions.Framework;
using Google.Cloud.Functions.Hosting;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Http;
using System.Threading.Tasks;

namespace SendHttpRequest
{
    // Dependency injection configuration, executed during server startup.
    public class Startup : FunctionsStartup
    {
        public override void ConfigureServices(WebHostBuilderContext context, IServiceCollection services)
        {
            // Make an HttpClient available to our function via dependency injection.
            // There are many options here; see
            // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests
            // for more details.
            services.AddHttpClient<IHttpFunction, Function>();
        }
    }

    // Function, decorated with the FunctionsStartup attribute to specify the startup class
    // for dependency injection.
    [FunctionsStartup(typeof(Startup))]
    public class Function : IHttpFunction
    {
        private readonly HttpClient _httpClient;

        public Function(HttpClient httpClient) =>
            _httpClient = httpClient;

        public async Task HandleAsync(HttpContext context)
        {
            string url = "http://example.com";
            using (HttpResponseMessage clientResponse = await _httpClient.GetAsync(url))
            {
                await context.Response.WriteAsync($"Received code '{(int) clientResponse.StatusCode}' from URL '{url}'.");
            }
        }
    }
}

Ruby

require "functions_framework"
require "net/http"

FunctionsFramework.http "concepts_requests" do |_request|
  url = "example.com"
  response = Net::HTTP.get_response url, "/"
  "Received code: #{response.code} from url: #{url}"
end

PHP


use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response;

function makeRequest(ServerRequestInterface $request): ResponseInterface
{
    // This sample uses the GuzzleHTTP client
    // See its documentation for usage information
    // https://docs.guzzlephp.org/en/stable/

    // Specify the URL to send requests to
    $client = new Client([
        'base_uri' => 'https://example.com',
    ]);

    // Send the request
    $url_response = $client->get('/');

    $function_response = new Response(
        $url_response->getStatusCode(),
        [], // headers
        ''  // body
    );

    return $function_response;
}

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

多个函数

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

如需在部署的函数之间共享数据,您可以使用 DatastoreFirestoreCloud Storage 等存储服务。或者,您也可以使用适当的触发器从一个函数调用另一个函数。例如,向 HTTP 函数的端点发出 HTTP 请求,或者向 Pub/Sub 主题发布消息以触发 Pub/Sub 函数。