У нас есть полный набор механизмов для управления сервисами, которыми мы пользуемся. Эти механизмы включают следующие функции:
Все эти функции зависят от нашей подсистемы upstream. Используя эту систему, мы можем легко реализовать более сложные сетевые функции.
Имя upstream аналогично доменному имени внутри программы, но имеет больше возможностей, таких как:
На практике, если нет необходимости обращаться к внешним сетям, upstream может полностью заменить доменные имена и DNS.
В файле 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
, и будет использована стандартная функция консистентного хеширования.
Мы хотим направлять 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", ¶ms);
params.weight = 1;
UpstreamManager::upstream_add_server("www.sogou.com", "127.0.0.1:8000", ¶ms);
params.weight = 4;
UpstreamManager::upstream_add_server("www.sogou.com", "127.0.0.1:8080", ¶ms);
WFHttpTask *task = WFTaskFactory::create_http_task("http://www.sogou.com/index.html", ...);
...
}
Обратите внимание, что все эти функции могут вызываться в любых контекстах, они полностью поточно-безопасны и действуют в реальном времени.
Кроме того, поскольку все наши протоколы, включая пользовательски-определённые протоколы, используют URL, функциональность upstream применима ко всем протоколам.
То же самое пример, где мы хотим направлять запросы с параметром "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, этот подход позволяет легко реализовать разделение чтения и записи баз данных (необходимо отметить, что это работает для операций вне транзакций).
В этом примере нам нужно случайным образом выбрать одну из десяти машин 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", ¶ms);
Параметр 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 )