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

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

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

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

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

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

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

  • HTTP/С
  • 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. В более поздних средах выполнения обязательно укажите его при развертывании функции. См. раздел Настройка переменных среды .

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

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

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

Исходящее соединение сбрасывается

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

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

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

$ artillery quick -d 300 -r 30 URL

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

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

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