Глава 11 / 51
Том 2 · Конфигурация и сборка

Мультисервисность и топология: один процесс — целая карта сервисов

В services[] не обязан быть один элемент. Реальные развёртывания часто смешивают «ферму HTTP-сайтов + WebSocket-push + TCP-шлюз + UDP-сердцебиение» — всё на одном движке, одном наборе скриптовых возможностей и одной корректной остановке.

Обзор

Позиционирование: от «настроить один сервис» к «понимать всю топологию». Глава также вводит API запросов топологии — сырьё для плагина плоскости управления из главы 40.

11.1 Введение

Наивный путь мультиервисного развёртывания — «по процессу на тип» — оборачивается расползанием процессов, хаосом планирования портов и разбросанными логами. Топологическая модель XServer — много server в одном процессе: один xs.json описывает все эндпоинты, один процесс несёт все сервисы — с общим пулом потоков движка, общими скриптовыми возможностями и единой корректной остановкой. Продакшн-репозиторий home работает именно так: один процесс XServer, 14 vhost, смешаны http и статические сайты.

11.2 Один процесс, много протоколов

Пример смешанной топологии
{
  "engine": { "workers": 8 },
  "services": [
    { "class": "http", "name": "web",  "port": 80,  "tls": true, "port_tls": 443,
      "host_default": { "name": "app", "path": "wwwroot", "devlang": "c", "devfile": "main.c" } },
    { "class": "ws",   "name": "push", "port": 8081,
      "host_default": { "name": "app", "devlang": "c", "devfile": "push_main.c" } },
    { "class": "tcp",  "name": "gw",   "port": 9000, "tls": true, "port_tls": 9443,
      "devlang": "c", "devfile": "gw_main.c" },
    { "class": "udp",  "name": "beat", "port": 9010,
      "devlang": "c", "devfile": "beat_main.c" }
  ]
}

Что только что произошло: четыре сервиса, четыре скрипта; при сборке все кандидаты обязаны скомпилироваться до первого bind — любая неудача компиляции отвергает весь старт (та же дисциплина, что «сначала компиляция, потом замена» у горячей перезагрузки). Каждый сервис меняет поколения независимо (reload публикуется на уровне server), не мешая остальным.

11.3 engine.workers

  • Единственная ручка уровня процесса: 0–1024, 0 = умолчание xrt (на этой машине в демо — 12)
  • Колбэки всех server делят один пул — HTTP-запросы, WS-сообщения, TCP-данные и UDP-дейтаграммы исполняются на workers
  • Неизменяемо при горячей перезагрузке: намерение reload, меняющее workers, отклоняется (число потоков — факт уровня процесса)
  • Оценка ёмкости: колбэки — короткая событийная обработка; разумный старт — workers ≈ числу ядер CPU; долгие блокирующие задачи уходят в отдельный поток (глава 36), а не в наращивание workers

11.4 Запросы топологии в рантайме

Межсервисное взаимодействие: поиск http-сервиса из ws-сервиса
/* перечисляем все сервисы из скрипта сервиса push */
static bool PrintTopo(XS_ServerInfo* pServer, void* pUserData)
{
    printf("- %s (%s) :%u%s\n", pServer->Name, pServer->Class,
        pServer->Port, pServer->TLS ? " +tls" : "");
    return true;
}
xsEnumServers(PrintTopo, NULL);

/* берём retained lease по имени (обязателен Release) */
XS_ServerInfo* pWeb = xsServerFind("web");
if ( pWeb != NULL ) {
    XS_HostInfo* pHost = xsHostFind(pWeb, "app");
    /* ... читаем топологическую информацию между сервисами ... */
    xsServerRelease(pWeb);
}

Сторона чтения топологии защищена RWLock (много чтений, мало записей), путь Accept каждый раз входит в читающую сторону — запросы сами по себе дёшевы. Аргументы перебора заимствуются внутри колбэка; дисциплина lease — в справочнике.

11.5 Контракт

  • Сборка: при мультисервисности все кандидаты компилируются до bind; любая неудача отвергает весь старт.
  • Смена поколений: reload публикуется на уровне отдельного server; xsReloadAllSubmit — пакетная транзакция (всё или откат всего).
  • Запросы: xsServerFind возвращает retained lease; аргументы перебора заимствованы на время колбэка — перед сохранением Retain.

Частые ошибки

Попытка горячей правки workers
Симптом
Намерение reload FAILED; Message сообщает, что workers неизменяем.
Причина
Пул потоков — ресурс уровня процесса; пересоздание в рантайме изменило бы конкурентную семантику всех колбэков — явно отклоняется, а не рискует.
Голый указатель между сервисами через поколения
Симптом
После перезагрузки сервиса web закэшированный в push XS_ServerInfo* становится дикой ссылкой.
Причина
Указатели из колбэков перебора заимствуются на один раунд; сохранение между поколениями/сервисами требует Retain в lease.

Упражнения

БазовыйСмешать четыре протокола

Запустите топологию 11.2 (скрипты могут ничего не экспортировать) и составьте таблицу топологии по логу старта.

Приёмка: в логе строки server/bound всех четырёх сервисов, порты совпадают с конфигурацией.
ПродвинутыйМаршрут экспорта топологии

Добавьте в web-сервис маршрут /topo и выводите JSON-топологию через xsEnumServers.

Приёмка: в выводе у каждого сервиса поля name/class/port/tls.
ВызовЭксперимент с отказом одного сервиса

Намеренно сломайте компиляцию скрипта gw и запустите; затем почините gw и сломайте скрипт web.

Приёмка: вы можете описать вид лога старта при «отказе любого кандидата» и объяснить, почему «трём хорошим дать поработать» нельзя.

Итоги

ТезисВывод
ТопологияМного server в процессе, пять протоколов вперемешку, общий движок и остановка
СборкаВсе кандидаты компилируются до bind; любая неудача отвергает старт целиком
workersУровень процесса, общий для колбэков, неизменяем при reload; старт ≈ ядрам CPU
ЗапросыxsServerFind (lease) / xsEnumServers (заимствование)
Что дальше по xrt