优化网络

Cloud Functions 简单易用,让您可以快速开发代码并在无服务器环境中运行。在规模适中的情况下,运行函数的成本很低,因此优化代码似乎不是一项高优先级的工作。但是,随着部署规模的扩大,代码优化会变得越来越重要。

本文档介绍如何优化函数的网络连接方式。优化网络连接方式可以获得如下好处:

  • 减少在每次函数调用时建立新连接所需的 CPU 时间。
  • 降低用尽连接配额或 DNS 配额的可能性。

维持持久性连接

本部分通过若干示例,说明如何在函数中维持持久性连接。如果不在函数中维持持久性连接,可能会迅速耗尽连接配额。

本部分包含下列使用场景:

  • HTTP/S
  • Google API

HTTP/S 请求

下面的优化代码段显示了如何维持持久性连接,而不是在每次函数调用时创建新连接:

Node.js

const http = require('http');
const functions = require('firebase-functions');

// Setting the `keepAlive` option to `true` keeps
// connections open between function invocations
const agent = new http.Agent({keepAlive: true});

exports.function = functions.https.onRequest((request, response) => {
    req = http.request({
        host: '',
        port: 80,
        path: '',
        method: 'GET',
        agent: agent, // Holds the connection open after the first invocation
    }, res => {
        let rawData = '';
        res.setEncoding('utf8');
        res.on('data', chunk => { rawData += chunk; });
        res.on('end', () => {
            response.status(200).send(`Data: ${rawData}`);
        });
    });
    req.on('error', e => {
        response.status(500).send(`Error: ${e.message}`);
    });
    req.end();
});

Python

from firebase_functions import https_fn
import requests

# Create a global HTTP session (which provides connection pooling)
session = requests.Session()

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

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

    # Process the request
    response = session.get(url)
    response.raise_for_status()
    return https_fn.Response("Success!")
    

此 HTTP 函数使用连接池来发出 HTTP 请求。它接受请求对象 (flask.Request),并返回响应文本,或是可使用 make_response 转换为 Response 对象的任何一组值。

访问 Google API

以下示例使用的是 Cloud Pub/Sub,但此方法也适用于其他客户端库,例如,Cloud Natural LanguageCloud Spanner。请注意,性能改进可能取决于特定客户端库的当前实现。

如果创建 Pub/Sub 客户端对象,则每次调用会执行一次连接和两次 DNS 查询。为了避免不必要的连接和 DNS 查询,请按照以下示例所示,在全局范围内创建 Pub/Sub 客户端对象:

node.js

const PubSub = require('@google-cloud/pubsub');
const functions = require('firebase-functions');
const pubsub = PubSub();

exports.function = functions.https.onRequest((req, res) => {
    const topic = pubsub.topic('');

    topic.publish('Test message', err => {
        if (err) {
            res.status(500).send(`Error publishing the message: ${err}`);
        } else {
            res.status(200).send('1 message published');
        }
    });
});

Python

import os

from firebase_functions import https_fn
from google.cloud import pubsub_v1

# from firebase_functions import https_fn
# Create a global Pub/Sub client to avoid unneeded network activity
pubsub = pubsub_v1.PublisherClient()

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

    project = os.getenv("GCP_PROJECT")
    request_json = request.get_json()

    topic_name = request_json["topic"]
    topic_path = pubsub.topic_path(project, topic_name)

    # Process the request
    data = b"Test message"
    pubsub.publish(topic_path, data=data)

    return https_fn.Response("1 message published")
    

此 HTTP 函数使用缓存的客户端库实例来减少每次调用函数所需的连接数。它接受请求对象 (flask.Request),并返回响应文本,或是可使用 make_response 转换为 Response 对象的任何一组值。

GCP_PROJECT 环境变量在 Python 3.7 运行时中会自动设置。在较新的运行时中,请务必在函数部署时指定它。请参阅配置环境变量

出站连接

出站请求超时

对于从函数到 VPC 网络的请求,空闲时间达到 10 分钟后超时。对于从函数到互联网的请求,空闲时间达到 20 分钟后超时。

出站连接重置

在底层基础架构重启或更新时,从函数到 VPC 网络和互联网的连接流可能会偶尔被终止和替换。如果您的应用重复使用长期有效的连接,我们建议您将应用配置为重新建立连接,以避免重用无效连接。

函数负载测试

要测量您的函数平均执行多少次连接,只需将其部署为 HTTP 函数,然后使用性能测试框架以特定的 QPS 对其进行调用。您可选择使用 Artillery - 只需一行代码就能调用:

$ artillery quick -d 300 -r 30 URL

此命令会以 30 QPS 的速度提取指定的网址,持续时间为 300 秒。

执行测试后,您可以在 Cloud 控制台中前往 Cloud Functions API 配额页面检查连接配额的使用情况。如果使用量一直在 30(或其倍数)左右,说明您在每次调用中都会建立一个(或多个)连接。优化代码后,您应该只会在测试开始时看到一些连接(10-30 个)。

您还可以在同一页面上的 CPU 配额图中比较优化前后的 CPU 费用。