Оптимизация сети

Простота Cloud Functions позволяет быстро разрабатывать код и запускать его в бессерверной среде. При умеренном масштабе стоимость запуска функций невелика, и оптимизация кода может показаться не такой уж важной. Однако по мере масштабирования развёртывания оптимизация кода становится всё более важной.

В этом документе описывается, как оптимизировать сетевое взаимодействие для ваших задач. Оптимизация сетевого взаимодействия имеет следующие преимущества:

  • Сократите время ЦП, затрачиваемое на установление новых исходящих соединений при каждом вызове функции.
  • Уменьшите вероятность исчерпания квот соединения или DNS.

Поддержание постоянных соединений

В этом разделе приведены примеры поддержания постоянных соединений в функции. Несоблюдение этого правила может привести к быстрому исчерпанию квот на соединения.

В этом разделе рассматриваются следующие сценарии:

  • HTTP/S
  • API Google

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();
});

Питон

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 ) и возвращает текст ответа или любой набор значений, который можно преобразовать в объект Response с помощью make_response .

Доступ к API Google

В примере ниже используется Cloud Pub/Sub , но этот подход также работает и с другими клиентскими библиотеками, например, Cloud Natural Language или Cloud 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');
        }
    });
});

Питон

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 ) и возвращает текст ответа или любой набор значений, который можно преобразовать в объект Response с помощью make_response .

Переменная окружения GCP_PROJECT автоматически задаётся в среде выполнения Python 3.7. В более поздних версиях среды выполнения обязательно укажите её при развёртывании функции. См. раздел Настройка переменных окружения .

Исходящие соединения

Тайм-ауты исходящих запросов

Для запросов из вашей функции к сети VPC действует тайм-аут после 10 минут простоя. Для запросов из вашей функции к интернету действует тайм-аут после 20 минут простоя.

Сброс исходящего соединения

Потоки подключений вашей функции к сети VPC и интернету могут периодически прерываться и заменяться при перезапуске или обновлении базовой инфраструктуры. Если ваше приложение повторно использует долгосрочные соединения, мы рекомендуем настроить его для повторного установления соединений, чтобы избежать повторного использования неработающего соединения.

Нагрузочное тестирование вашей функции

Чтобы измерить среднее количество соединений, которые ваша функция выполняет, разверните её как HTTP-функцию и используйте фреймворк для тестирования производительности, чтобы вызывать её с определённой частотой запросов в секунду. Один из возможных вариантов — Artillery , который можно вызвать одной строкой:

$ artillery quick -d 300 -r 30 URL

Эта команда извлекает указанный URL со скоростью 30 запросов в секунду в течение 300 секунд.

После выполнения теста проверьте использование квоты подключений на странице квот API Cloud Functions в Cloud Console. Если значение постоянно около 30 (или кратно этому значению), вы устанавливаете одно (или несколько) подключений при каждом вызове. После оптимизации кода вы должны увидеть несколько (10–30) подключений только в начале теста.

Вы также можете сравнить затраты ЦП до и после оптимизации на графике квоты ЦП на той же странице.