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

Простота 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();
});

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

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

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

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

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

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

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

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

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

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

$ artillery quick -d 300 -r 30 URL

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

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

Также вы можете сравнить затраты ресурсов процессора до и после оптимизации на графике квоты использования процессора на той же странице.