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

OSCHINA-MIRROR/sogou-workflow

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

О Upstream

В Nginx Upstream представляет конфигурацию балансировки нагрузки при обратном прокси. В данном контексте мы расширяем понятие Upstream следующими характеристиками:

  1. Каждый Upstream является независимым обратным прокси.
  2. Доступ к Upstream эквивалентен выбору одного из целей в группе сервисов с использованием подходящего алгоритма.
  3. Upstream обеспечивает балансировку нагрузки, обработку ошибок, мелtdown и другие возможности управления сервисами.
  4. Для повторных попыток одного запроса Upstream может игнорировать уже выбранные цели.
  5. Через Upstream можно настроить различные параметры соединения для разных целевых сервисов.
  6. Цели могут быть добавлены или удалены динамически, что позволяет легко интегрироваться с любыми системами открытия услуг.

Преимущества Upstream перед DNS-разрешением домена

Оба Upstream и DNS-разрешение позволяют связывать группы IP-адресов с одним хостом, но имеют различия:

  1. Разрешение доменного имени DNS не ориентировано на порты; одинаковые IP-адреса с разными портами не могут быть объединены в одном домене; но Upstream может это сделать.
  2. Адреса, соответствующие доменному имени DNS, всегда являются IP-адресами; адреса Upstream могут быть IP-адресами, доменным именем или Unix-доменной сокет.
  3. Обычно DNS-разрешение доменного имени кэшируется операционной системой или сетью DNS серверов, а время его обновления ограничивается TTL; Upstream может быть обновлено и применено немедленно.
  4. Парсинг и выборка доменного имени DNS требуют больше времени, чем Upstream.

Upstream в Workflow

Это локальный модуль обратного прокси, который применяется как для сервера, так и для клиента.

Поддерживает динамическую конфигурацию, которая может быть использована для системы открытия услуг. В настоящее время workflow-k8s может быть интегрирован с API сервером Kubernetes.

Имя Upstream не должно содержать номер порта, но запросы Upstream поддерживают указание портов (если используется протокол, отличный от встроенного, имя Upstream временно должно содержать номер порта).

Каждый Upstream имеет уникальное имя UpstreamName и добавляет набор адресов, которые могут быть:

  1. IPv4
  2. IPv6
  3. Доменное имя
  4. Unix-доменная сокет

Почему заменять Upstream Nginx?

Как работает Upstream Nginx

  1. Поддерживает только HTTP/HTTPS протоколы.
  2. Требуется запустить службу Nginx, что занимает ресурсы процессора и сокетов.
  3. Запросы направляются через Nginx, затем Nginx отправляет запросы к удалённому серверу, что увеличивает количество шагов общения.

Как работает локальный Upstream Workflow

  1. Протокол-независимость, вы можете использовать Upstream для доступа к MySQL, Redis, MongoDB и т.д.
  2. Нет необходимости запускать дополнительные процессы или порты, имитация обратного прокси происходит внутри процесса.
  3. Процесс выбора состоит из базовых вычислений и таблиц, нет дополнительных затрат на общение.

Использование Upstream

Часто используемые методы

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_create_vnswrr(const std::string& name);
    static int upstream_delete(const std::string& name);

public:
    static int upstream_add_server(const std::string& name,
                                   const std::string& address);
    static int upstream_add_server(const std::string& name,
                                   const std::string& address,
                                   const struct AddressParams *address_params);
    static int upstream_remove_server(const std::string& name,
                                      const std::string& address);
    ...
}

Пример 1: случайный выбор среди нескольких целей

Настройте локальный обратный прокси, чтобы все запросы к my_proxy.name были равномерно распределены между шестью целевыми серверами.

UpstreamManager::upstream_create_weighted_random(
    "my_proxy.name",
    true); // если встретили машину с мелtdown, продолжайте пытаться до тех пор пока не найдете рабочую или все машины будут в состоянии мелtdown

UpstreamManager::upstream_add_server("my_proxy.name", "192.168.2.100:8081");
UpstreamManager::upstream_add_server("my_proxy.name", "192.168.2.100:8082");
UpstreamManager::upstream_add_server("my_proxy.name", "192.168.10.10");
UpstreamManager::upstream_add_server("my_proxy.name", "test.sogou.com:8080");
UpstreamManager::upstream_add_server("my_proxy.name", "abc.sogou.com");
UpstreamManager::upstream_add_server("my_proxy.name", "abc.sogou.com");
UpstreamManager::upstream_add_server("my_proxy.name", "/dev/unix_domain_socket_sample");

auto *http_task = WFTaskFactory::create_http_task("http://my_proxy.name/somepath?a=10", 0, 0, nullptr);
http_task->start();

Основные принципы:

  1. Выбор случайного целевого сервера.
  2. Если try_another установлен в true, выбирается случайный живой сервер из всех живых.
  3. Выбор производится только для основного сервера, основной и резервный серверы считаются действительными.

Пример 2: случайный выбор среди нескольких целей с учетом веса

Настройте локальный обратный прокси, чтобы все запросы к weighted.random были распределены между тремя целевыми серверами с весами 5, 20 и 1 соответственно.

UpstreamManager::upstream_create_weighted_random(
    "weighted.random",
    false); // если встретили машину с мелtdown, прекращаем попытки, в этом случае запрос завершится неудачей

AddressParams address_params = ADDRESS_PARAMS_DEFAULT;
address_params.weight = 5; // вес равен 5
UpstreamManager::upstream_add_server("weighted.random", "192.168.2.100:8081", &address_params); // вес 5
address_params.weight = 20; // вес равен 20
UpstreamManager::upstream_add_server("weighted.random", "192.168.2.100:8082", &address_params); // вес 20
UpstreamManager::upstream_add_server("weighted.random", "abc.sogou.com"); // вес 1

auto *http_task = WFTaskFactory::create_http_task("http://weighted.random:9090", 0, 0, nullptr);
http_task->start();

Основные принципы:

  1. Распределение по весу, случайный выбор целевого сервера, где больший вес увеличивает вероятность выбора.
  2. Если try_another установлен в true, выбирается случайный живой сервер из всех живых по весу.
  3. Выбор производится только для основного сервера, основной и резервный серверы считаются действительными.### Пример 3: выбор согласно внутренней функции хэша
UpstreamManager::upstream_create_consistent_hash(
    "abc.local",
    nullptr); // nullptr указывает на использование внутренней функции хэша



UpstreamManager::upstream_add_server("abc.local", "192.168.2.100:8081");
UpstreamManager::upstream_add_server("abc.local", "192.168.2.100:8082");
UpstreamManager::upstream_add_server("abc.local", "192.168.10.10");
UpstreamManager::upstream_add_server("abc.local", "test.sogou.com:8080");
UpstreamManager::upstream_add_server("abc.local", "abc.sogou.com");

auto *http_task = WFTaskFactory::create_http_task("http://abc.local/service/method", 0, 0, nullptr);
http_task->start();

Основные принципы:

  1. Каждый основной сервер рассматривается как 16 виртуальных узлов.
  2. Фреймворк использует std::hash для вычисления значения узла на основе адреса узла, виртуального индекса и количества добавлений этого адреса к этому upstream.
  3. Фреймворк также использует std::hash для вычисления значения данных на основе пути, запроса и фрагмента.
  4. Выбирается ближайший живой узел.
  5. Для каждого основного сервера, если есть хотя бы один живой основной или резервный сервер в группе, он считается живым.
  6. Если при вызове upstream_add_server() указаны параметры AddressParams с весом, каждый основной сервер будет рассматриваться как 16 * weight виртуальных узлов, что полезно для использования взвешенного хэша или когда требуется меньшая стандартная дисперсия.

Пример 4: использование пользовательской функции хэша

UpstreamManager::upstream_create_consistent_hash(
    "abc.local",
    [](const char *path, const char *query, const char *fragment) -> unsigned int {
        unsigned int hash = 0;

        while (*path)
            hash = (hash * 131) + (*path++);
        
        while (*query)
            hash = (hash * 131) + (*query++);
        
        while (*fragment)
            hash = (hash * 131) + (*fragment++);
        
        return hash;
    });

UpstreamManager::upstream_add_server("abc.local", "192.168.2.100:8081");
UpstreamManager::upstream_add_server("abc.local", "192.168.2.100:8082");
UpstreamManager::upstream_add_server("abc.local", "192.168.10.10");
UpstreamManager::upstream_add_server("abc.local", "test.sogou.com:8080");
UpstreamManager::upstream_add_server("abc.local", "abc.sogou.com");

auto *http_task = WFTaskFactory::create_http_task("http://abc.local/sompath?a=1#flag100", 0, 0, nullptr);
http_task->start();

Основные принципы:

  1. Фреймворк использует пользовательскую функцию хэша для вычисления значения данных.
  2. Все остальные принципы аналогичны примеру выше.

Пример 5: использование пользовательского метода выбора

UpstreamManager::upstream_create_manual(
    "xyz.cdn",
    [](const char *path, const char *query, const char *fragment) -> unsigned int {
        return atoi(fragment);
    },
    true, // если выбранная цель находится в состоянии мелtdown, будет выполнена вторая попытка выбора
    nullptr); // nullptr указывает на использование внутренней функции хэша для второй попытки выбора

UpstreamManager::upstream_add_server("xyz.cdn", "192.168.2.100:8081");
UpstreamManager::upstream_add_server("xyz.cdn", "192.168.2.100:8082");
UpstreamManager::upstream_add_server("xyz.cdn", "192.168.10.10");
UpstreamManager::upstream_add_server("xyz.cdn", "test.sogou.com:8080");
UpstreamManager::upstream_add_server("xyz.cdn", "abc.sogou.com");

auto *http_task = WFTaskFactory::create_http_task("http://xyz.cdn/sompath?key=somename#3", 0, 0, nullptr);
http_task->start();

Основные принципы:

  1. Фреймворк сначала использует пользовательскую функцию выбора, чтобы выбрать основной сервер по модулю.
  2. Для каждого основного сервера, если есть хотя бы один живой основной или резервный сервер в группе, он считается живым.
  3. Если выбранная цель больше не жива и try_another установлена в true, будет выполнена вторая попытка выбора с помощью функции хэша.
  4. При выполнении второй попытки, функция хэша гарантирует выбор живого сервера, за исключением случая, когда все машины находятся в состоянии мелtdown.

Пример 6 простого режима master-slave

UpstreamManager::upstream_create_weighted_random(
    "simple.name",
    true); // Установка одного мастера и одного слейва не имеет значения

AddressParams address_params = ADDRESS_PARAMS_DEFAULT;
address_params.server_type = 0;
UpstreamManager::upstream_add_server("simple.name", "main01.test.ted.bj.sogou", &address_params); // мастер
address_params.server_type = 1;
UpstreamManager::upstream_add_server("simple.name", "backup01.test.ted.gd.sogou", &address_params); // слейв

auto *http_task = WFTaskFactory::create_http_task("http://simple.name/request", 0, 0, nullptr);
auto *redis_task = WFTaskFactory::create_redis_task("redis://simple.name/2", 0, nullptr);
redis_task->get_req()->set_query("MGET", {"key1", "key2", "key3", "key4"});
(*http_task * redis_task).start();

Основные принципы:

  1. Режим master-slave не конфликтует с другими режимами и может применяться одновременно.
  2. Количество мастеров и слейвов независимо друг от друга и ограничений нет. Мастеры равноправны между собой, слейвы тоже равноправны, но мастера и слейвы не равноправны.
  3. Как только один мастер доступен, запрос будет отправлен на него.
  4. Если все мастера выведены из строя, слейвы будут принимать запросы до восстановления хотя бы одного мастера.
  5. В каждом стратегическом контексте живые слейвы могут служить основанием для доступности мастера.

Пример 7 режима master-slave + алгоритма Consistent Hashing + группировки

UpstreamManager::upstream_create_consistent_hash(
    "abc.local",
    nullptr); // nullptr указывает на использование встроенной функции Consistent Hashing

AddressParams address_params = ADDRESS_PARAMS_DEFAULT;
address_params.server_type = 0;
address_params.group_id = 1001;
UpstreamManager::upstream_add_server("abc.local", "192.168.2.100:8081", &address_params); // мастер группы 1001
address_params.server_type = 1;
address_params.group_id = 1001;
UpstreamManager::upstream_add_server("abc.local", "192.168.2.100:8082", &address_params); // слейв группы 1001
address_params.server_type = 0;
address_params.group_id = 1002;
UpstreamManager::upstream_add_server("abc.local", "main01.test.ted.bj.sogou", &address_params); // мастер группы 1002
address_params.server_type = 1;
address_params.group_id = 1002;
UpstreamManager::upstream_add_server("abc.local", "backup01.test.ted.gd.sogou", &address_params); // слейв группы 1002
address_params.server_type = 1;
address_params.group_id = -1;
UpstreamManager::upstream_add_server("abc.local", "test.sogou.com:8080", &address_params); // слейв без группы
UpstreamManager::upstream_add_server("abc.local", "abc.sogou.com"); // мастер без группы

auto *http_task = WFTaskFactory::create_http_task("http://abc.local/service/method", 0, 0, nullptr);
http_task->start();

Основные принципы:

  1. Группа с номером -1 считается без группы, такая цель не относится ни к одной группе.
  2. Мастера без группы равноправны и могут рассматриваться как часть одной группы. Однако они изолированы от мастеров с группой.
  3. Слейвы без группы могут обслуживать любую цель любой группы.
  4. Номер группы позволяет различать, какие мастера и слейвы работают вместе.
  5. Слейвы разных групп изолированы друг от друга и обслуживают только свои группы.
  6. По умолчанию новая цель добавляется без группы и типа 0, что указывает на мастера.### Пример 8 NVSWRR стратегии выбора серверов с учетом веса
UpstreamManager::upstream_create_vnswrr("nvswrr.random");



AddressParams address_params = ADDRESS_PARAMS_DEFAULT;
address_params.weight = 3; // вес 3
UpstreamManager::upstream_add_server("nvswrr.random", "192.168.2.100:8081", &address_params); // вес 3
address_params.weight = 2; // вес 2
UpstreamManager::upstream_add_server("nvswrr.random", "192.168.2.100:8082", &address_params); // вес 2
UpstreamManager::upstream_add_server("nvswrr.random", "abc.sogou.com"); // вес 1

auto *http_task = WFTaskFactory::create_http_task("http://nvswrr.random:9090", 0, 0, nullptr);
http_task->start();

Основные принципы:

  1. Виртуальные узлы инициализируются согласно алгоритму SWRR.
  2. Виртуальные узлы инициализируются пакетами, чтобы избежать концентрации вычислений.
  3. Обеспечивает гладкое распределение нагрузки и O(1) временную сложность.
  4. Подробнее см. tengine.

Стратегии выбора upstream

При заполнении URIHost запроса значением UpstreamName, выбирается соответствующий upstream для выполнения запроса. Далее выбирается адрес из списка адресов upstream:

  1. Стратегия случайного выбора по весу.
  2. Стратегия выбора по алгоритму Consistent Hashing.
  3. Стратегия ручного выбора, используя функцию select пользователя.
  4. Стратегия master-slave.

round-robin/weighted-round-robin: эквивалентна стратегии 1, временно недоступна. Рекомендованная стратегия для обычных пользователей — 2, которая обеспечивает хорошую отказоустойчивость и масштабируемость. Для сложных требований рекомендуется использовать стратегию 3, позволяющую настраивать логику выбора.

Атрибуты адреса

struct EndpointParams
{
    size_t max_connections;
    int connect_timeout;
    int response_timeout;
    int ssl_connect_timeout;
    bool use_tls_sni;
};

static constexpr struct EndpointParams ENDPOINT_PARAMS_DEFAULT =
{
    .max_connections        = 200,
    .connect_timeout        = 10 * 1000,
    .response_timeout       = 10 * 1000,
    .ssl_connect_timeout    = 10 * 1000,
    .use_tls_sni            = false,
};

struct AddressParams
{
    struct EndpointParams endpoint_params;
    unsigned int dns_ttl_default;
    unsigned int dns_ttl_min;
    unsigned int max_fails;
    unsigned short weight;
    int server_type;
    int group_id;
};

static constexpr struct AddressParams ADDRESS_PARAMS_DEFAULT =
{
    .endpoint_params    =    ENDPOINT_PARAMS_DEFAULT,
    .dns_ttl_default    =    12 * 3600,
    .dns_ttl_min        =    180,
    .max_fails          =    200,
    .weight             =    1,    // только для мастера в UPSTREAM_WEIGHTED_RANDOM
    .server_type        =    0,
    .group_id           =    -1,
};

Каждый адрес можно настроить своими параметрами:

  • EndpointParams.max_connections, EndpointParams.connect_timeout, EndpointParams.response_timeout, EndpointParams.ssl_connect_timeout: параметры соединения
  • dns_ttl_default: значение TTL DNS-кэша по умолчанию, секунды, 12 часов по умолчанию, кэш DNS действителен только для текущего процесса
  • dns_ttl_min: минимальное время действия DNS, секунды, 3 минуты по умолчанию, используется при принятии решения о повторной попытке DNS после неудачной связи
  • max_fails: максимальное количество последовательных ошибок перед триггером фузинга
  • weight: вес, 1 по умолчанию, только для мастера, используется для стратегий случайного выбора и Consistent Hashing
  • server_type: тип сервера, мастер по умолчанию, мастера всегда имеют более высокий приоритет среди своих групп
  • group_id: группа, -1 по умолчанию, -1 означает отсутствие группы, слейвы без группы могут обслуживать любую группу, слейвы с группой имеют более высокий приоритет.

О фузинге

MTTR

Среднее время восстановления (Mean Time To Repair, MTTR) — это среднее время, необходимое для восстановления системы после выхода из строя.

Эффект цепного реагирования

Эффект цепного реагирования — это ситуация, когда одна система начинает зависеть от другой системы, и если первая система выходит из строя, то вторая также начинает зависеть от третьей системы и так далее. Это приводит к тому, что весь комплекс систем становится крайне уязвимым к отказам.

Механизм фузинга

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

Защита upstream от фузинга

MTTR = 30 секунд, временно не настраивается, но планируется предоставление возможности настройки пользователю. Если число последовательных ошибок достигнет максимума (по умолчанию 200), адрес будет заблокирован на 30 секунд. Адрес, находящийся в состоянии фузинга, будет удален из выборки, если он выбран стратегией.

Обратите внимание, что если выполняются условия 1-4, задача получит ошибку WFT_ERR_UPSTREAM_UNAVAILABLE = 1004:

  1. Стратегия случайного выбора по весу, все адреса находятся в состоянии фузинга.
  2. Стратегия выбора по алгоритму Consistent Hashing, все адреса находятся в состоянии фузинга.
  3. Стратегия ручного выбора && try_another == true, все адреса находятся в состоянии фузинга.
  4. Стратегия ручного выбора && try_another == false, и следующие условия выполняются:
    1. Выбранный мастер находится в состоянии фузинга, и все слейвы без группы также находятся в состоянии фузинга.
    2. Этот мастер является безгрупповым мастером, или все остальные адреса в этой группе находятся в состоянии фузинга.
    3. Все слейвы без группы находятся в состоянии фузинга.

Приоритет портов upstream

  1. Выберите явно указанное значение порта в адресе upstream.
  2. Если такого значения нет, выберите явно указанное значение порта в URL-запроса.
  3. Если таких значений нет, используйте стандартное значение порта протокола.```text Настройка UpstreamManager::upstream_add_server("my_proxy.name", "192.168.2.100:8081"); Запрос http://my_proxy.name:456/test.html => http://192.168.2.100:8081/test.html Запрос http://my_proxy.name/test.html => http://192.168.2.100:8081/test.html

```text
Настройка UpstreamManager::upstream_add_server("my_proxy.name", "192.168.10.10");
Запрос http://my_proxy.name:456/test.html => http://192.168.10.10:456/test.html
Запрос http://my_proxy.name/test.html => http://192.168.10.10:80/test.html

Опубликовать ( 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