优化网络连接方式

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

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

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

维持持久性连接

本部分提供若干示例,分别说明在函数中维持持久性连接之前和之后的情况。如果不在函数中维持持久性连接,可能会导致您很快地耗尽连接配额。

本部分中涵盖的场景包括 HTTP/HTTPS 数据包、Axios 数据包和 Superagent 数据包以及访问 Google API。

带 HTTP/HTTPS 数据包的 HTTP 请求

本部分介绍对发出带有 HTTP/HTTPS 数据包的 HTTP 请求的函数进行优化前和优化后的示例。

优化前

下面的函数会在每次函数调用时执行新连接:

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

exports.function = functions.https.onRequest((request, response) => {
    req = http.request({
        host: '<HOST>',
        port: 80,
        path: '<PATH>',
        method: 'GET',
    }, (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();
});

优化后

下面是上述代码段的优化版本,通过搭配 keep-alive 选项使用自定义 HTTP 代理来维持持久性连接:

const http = require('http');
const functions = require('firebase-functions');
const agent = new http.Agent({keepAlive: true});

exports.function = functions.https.onRequest((request, response) => {
    req = http.request({
        host: '<HOST>',
        port: 80,
        path: '<PATH>',
        method: 'GET',
        agent: agent,
    }, (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();
});

同样的方法也适用于 HTTPS - 只是要使用 https.Agent 而不是 http.Agent

带 Axios 数据包的 HTTP 请求

本部分介绍对发出带有 Axios 数据包的 HTTP 请求的函数进行优化前和优化后的示例。

优化前

下面的函数要求每次调用时执行一次连接和两次 DNS 解析:

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

exports.function = functions.https.onRequest((request, response) => {
    axios({
        method: 'get',
        url: '<URL>',
        responseType: 'text'
    }).then(function(response) {
        res.status(200).send(`Data: ${response.data}`);
    });
});

Package.json

{
    "dependencies": {
        "axios": "^0.15.3"
    }
}

优化后

创建一个全局性 Axios 实例和一个维持持久性连接的自定义 HTTP 代理,即可减少连接次数。现在,在稳定的状态下不需要为每次函数调用执行连接或 DNS 查询:

const axios = require('axios');
const functions = require('firebase-functions');
const https = require('https');

const instance = axios.create({
    baseURL: '<URL>',
    timeout: 10000,
});

const requestConfig = {
  method: 'get',
  url: '',
  httpsAgent: new https.Agent({ keepAlive: true }),
};

exports.function = functions.https.onRequest((request, response) => {
    return instance.request(requestConfig).then((response) => {
        console.log(`Data: ${response.data}`);
        res.status(200).send(`Data: ${response.data}`);
    });
});

类似的方法也适用于 HTTP。

带 Superagent 数据包的 HTTP 请求

本部分介绍对发出带有 Superagent 数据包的 HTTP 请求的函数进行优化前和优化后的示例。

优化前

下面的函数每次调用时要进行一次连接和两次 DNS 解析:

const request = require('superagent');

exports.function = functions.https.onRequest((request, response) => {
    request
        .get('<URL>')
        .end((err, response) => {
            res.status(200).send(`Data: ${response.text}`);
    });
});

优化后

要保持持久性连接,只需为每个请求提供一个自定义的 HTTP 代理即可:

const request = require('superagent');
const functions = require('firebase-functions');

exports.function = functions.https.onRequest((request, response) => {
    request
        .get('<URL>')
        .end((err, response) => {
            res.status(200).send(`Data: ${response.text}`);
    });
});

访问 Google API

本部分介绍对一个访问 Google API 的函数进行优化前和优化后的示例。

优化前

本示例使用 Cloud Pub/Sub,但此方法也适用于其他客户端库(例如语言 API 客户端Cloud Spanner。但请注意,具体实现的节省可能取决于当前实现的特定客户端库。

下面的代码每次调用时执行一次连接和两次 DNS 查询:

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

exports.function = functions.https.onRequest((request, response) => {
    const pubsub = PubSub();
    const topic = pubsub.topic('<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');
        }
    });
});

优化后

要消除不必要的连接和 DNS 查询,只需在全局范围内创建 pubsub 对象即可:

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

exports.function = functions.https.onRequest((request, response) => {
    const topic = pubsub.topic('<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');
        }
    });
});

测试您的函数

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

$ artillery quick -d 300 -r 30 <URL>

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

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

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

发送以下问题的反馈:

此网页
需要帮助?请访问我们的支持页面