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

Простота 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 в глобальной области видимости, как показано в примере ниже:

узел.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 и используйте фреймворк тестирования производительности, чтобы вызывать ее при определенных QPS. Одним из возможных вариантов является Artillery , который можно вызвать одной строкой:

$ artillery quick -d 300 -r 30 URL

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

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

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