1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/sogou-workflow

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
about-service-governance.md 15 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 01.03.2025 10:20 552780a

О служебном управлении

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

  • Пользовательская система DNS.
  • Выбор адреса сервиса
    • Включает различные методы выбора, такие как случайный выбор с учётом веса, консистентное хеширование, пользовательское указание способа выбора и т.д.
  • Отключение и восстановление сервиса.
  • Балансировка нагрузки.
  • Независимая настройка параметров для каждого отдельного сервиса.
  • Основной и резервный сервисы.

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

Имя upstream

Имя upstream аналогично доменному имени внутри программы, но имеет больше возможностей, таких как:

  • Обычное доменное имя может указывать только на группу ip-адресов, а upstream может указывать на группу ip-адресов или доменов.
  • Целевой объект (домен или ip), на который указывает upstream, может содержать информацию о портах.
  • Управление и выбор целей upstream имеют мощные возможности, каждая цель может иметь множество свойств.
  • Обновление upstream происходит в реальном времени и полностью поточно-безопасно, тогда как информация DNS доменного имени не обновляется в реальном времени.

На практике, если нет необходимости обращаться к внешним сетям, upstream может полностью заменить доменные имена и DNS.

Создание и удаление upstream

В файле UpstreamManager.h представлены несколько интерфейсов для создания upstream:

using upstream_route_t = std::function<unsigned int (const char *, const char *, const char *)>;

class UpstreamManager
{
public:
    static int upstream_create_consistent_hash(const std::string& name,
                                                upstream_route_t consistent_hash);

    static int upstream_create_weighted_random(const std::string& name,
                                               bool try_another);

    static int upstream_create_manual(const std::string& name,
                                      upstream_route_t select,
                                      bool try_another,
                                      upstream_route_t consistent_hash);

    static int upstream_delete(const std::string& name);
    ...
};

Эти три функции создают три типа upstream: консистентное хеширование, случайный выбор с учётом веса и пользовательское назначение.
Параметр name представляет имя upstream, которое используется после его создания, аналогично доменному имени.
Параметры consistent_hash и select являются функциями типа upstream_route_t, используемыми для указания маршрутизации.
Параметр try_another указывает, следует ли продолжать попытки найти доступную цель при отказе выбранной цели. Этот параметр отсутствует в режиме консистентного хеширования.
Функция upstream_route_t принимает три параметра: путь, запрос и фрагмент URL. Например, для URL http://abc.com/home/index.html?a=1#bottom эти параметры будут равняться "/home/index.html", "a=1" и "bottom".
Обратите внимание, что параметр consistent_hash можно передать как nullptr, и будет использована стандартная функция консистентного хеширования.

Пример 1: Распределение веса

Мы хотим направлять 50% запросов к www.sogou.com на два адреса: 127.0.0.1:8000 и 127.0.0.1:8080, причём нагрузка должна распределяться в соотношении 1:4.
Мы не беспокоимся о количестве ip-адресов, связанных с доменным именем www.sogou.com. В любом случае, реальное доменное имя получит 50% запросов.

#include "workflow/UpstreamManager.h"
#include "workflow/WFTaskFactory.h"

int main()
{
    UpstreamManager::upstream_create_weighted_random("www.sogou.com", false);
    struct AddressParams params = ADDRESS_PARAMS_DEFAULT;

    params.weight = 5;
    UpstreamManager::upstream_add_server("www.sogou.com", "www.sogou.com", &params);
    params.weight = 1;
    UpstreamManager::upstream_add_server("www.sogou.com", "127.0.0.1:8000", &params);
    params.weight = 4;
    UpstreamManager::upstream_add_server("www.sogou.com", "127.0.0.1:8080", &params);

    WFHttpTask *task = WFTaskFactory::create_http_task("http://www.sogou.com/index.html", ...);
    ...
}

Обратите внимание, что все эти функции могут вызываться в любых контекстах, они полностью поточно-безопасны и действуют в реальном времени.
Кроме того, поскольку все наши протоколы, включая пользовательски-определённые протоколы, используют URL, функциональность upstream применима ко всем протоколам.

Пример 2: Ручной выбор

То же самое пример, где мы хотим направлять запросы с параметром "123" в URL на адрес 127.0.0.1:8000, а запросы с параметром "abc" — на адрес 127.0.0.1:8080, остальные запросы должны быть направлены на обычное доменное имя.

#include "workflow/UpstreamManager.h"
#include "workflow/WFTaskFactory.h"

int my_select(const char *path, const char *query, const char *fragment)
{
    if (strcmp(query, "123") == 0)
        return 1;
    else if (strcmp(query, "abc") == 0)
        return 2;
    else
        return 0;
}

int main()
{
    UpstreamManager::upstream_create_manual("www.sogou.com", my_select, false, nullptr);

    UpstreamManager::upstream_add_server("www.sogou.com", "www.sogou.com");
    UpstreamManager::upstream_add_server("www.sogou.com", "127.0.0.1:8000");
    UpstreamManager::upstream_add_server("www.sogou.com", "127.0.0.1:8080");

    /* This URL will route to 127.0.0.1:8080 */
    WFHttpTask *task = WFTaskFactory::create_http_task("http://www.sogou.com/index.html?abc", ...);
    ...
}

Поскольку мы предоставляем нативную поддержку для протоколов Redis и MySQL, этот подход позволяет легко реализовать разделение чтения и записи баз данных (необходимо отметить, что это работает для операций вне транзакций).

Пример 3: Консистентное хеширование

В этом примере нам нужно случайным образом выбрать одну из десяти машин Redis для связи, но гарантируем, что один и тот же URL всегда будет обращаться к одному и тому же серверу. Это просто:

int main()
{
    UpstreamManager::upstream_create_consistent_hash("redis.name", nullptr);

    UpstreamManager::upstream_add_server("redis.name", "10.135.35.53");
    UpstreamManager::upstream_add_server("redis.name", "10.135.35.54");
    UpstreamManager::upstream_add_server("redis.name", "10.135.35.55");
    ...
    UpstreamManager::upstream_add_server("redis.name", "10.135.35.62");

    auto *task = WFTaskFactory::create_redis_task("redis://:mypassword@redis.name/2?a=hello#111", ...);
    ...
}
~~~Наш Redis задачник не распознаёт часть запроса, поэтому её можно заполнить произвольно. Часть пути `/2` указывает на номер базы данных Redis.  
В этом случае функция `consistent_hash` получит три параметра: "/2", "a=hello" и "111". Поскольку мы передали `nullptr`, будет использована стандартная функция консистентного хеширования.  
Серверы внутри upstream не указывают порт, поэтому будет использован порт из URL. По умолчанию для Redis он равен 6379.  
Функция `consistent_hash` не имеет параметра `try_another`. При отказе выбранного сервера будет автоматически выбран другой. Для одного и того же URL будет выбрана одна и та же машина (дружественно к кэшу).

### Параметры сервера upstream

В первом примере мы установили параметр `weight` через параметр `params`. Однако параметры сервера не ограничиваются только весом. Эта структура определена следующим образом:
~~~cpp
// В файле EndpointParams.h
struct EndpointParams
{
    size_t max_connections;
    int connect_timeout;
    int response_timeout;
    int ssl_connect_timeout;
    bool use_tls_sni;
};

// В файле ServiceGovernance.h
struct AddressParams
{
    struct EndpointParams endpoint_params; ///< Настройки подключения
    unsigned int dns_ttl_default;          ///< В секундах, TTL DNS при успешном запросе сети
    unsigned int dns_ttl_min;             ///< В секундах, TTL DNS при неудачном запросе сети
/**
 * - Параметр max_fails устанавливает количество последовательных неудачных попыток связи с сервером.
 * - Через 30 секунд после отказа сервера, upstream проверяет сервер с помощью некоторых активных клиентских запросов.
 * - Если проверка проходит успешно, сервер помечается как работающий.
 * - Если max_fails установлено в 1, то сервер выйдет из выборки upstream за 30 секунд после единственной ошибки.
 */
    unsigned int max_fails;               ///< [1, INT32_MAX] max_fails = 0 означает max_fails = 1
    unsigned short weight;                ///< [1, 65535] weight = 0 означает weight = 1. Только для основного сервера
    int server_type;                      ///< 0 для основного и 1 для резервного
    int group_id;                         ///< -1 означает отсутствие группы. Резервный сервер без группы будет резервировать любой основной узел
};

Большинство параметров очевидны по своему значению. Параметры endpoint_params и dns могут переопределять глобальные настройки.
Например, если глобально максимальное число подключений для каждого ip-адреса равно 200, но мы хотим установить максимум в 1000 для 10.135.35.53, это можно сделать так:

    UpstreamManager::upstream_create_weighted_random("10.135.35.53", false);
    struct AddressParams params = ADDRESS_PARAMS_DEFAULT;
    params.endpoint_params.max_connections = 1000;
    UpstreamManager::upstream_add_server("10.135.35.53", "10.135.35.53", &params);

Параметр max_fails устанавливает максимальное количество последовательных ошибок. Если количество ошибок достигнет значения max_fails, сервер будет отключен. Если параметр try_another равен false, задача завершится ошибкой.
В коллбеке задачи get_state() вернёт значение WFT_STATE_TASK_ERROR, а get_error() — значение WFT_ERR_UPSTREAM_UNAVAILABLE.
Если try_another равен true, но все серверы отключены, будет возвращена та же ошибка. Время отключения составляет 30 секунд.
Параметры server_type и group_id используются для работы с основным и резервным серверами. Каждый upstream должен иметь хотя бы один сервер типа 0 (основной узел), иначе upstream становится недействительным.
Серверы типа 1 (резервные узлы) будут использоваться при отказе основного узла с таким же group_id.

Дополнительную информацию о функциях upstream можно найти здесь: about-upstream.md.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/sogou-workflow.git
git@api.gitlife.ru:oschina-mirror/sogou-workflow.git
oschina-mirror
sogou-workflow
sogou-workflow
master