Глава 46 / 51
Том 5 · Расширения, эксплуатация и практика

Практика 2: TCP-шлюз XTP2 (сервис ключ-значение)

Говорим по XTP2 поверх TCP: KV-шлюз с сеансами, диспетчеризацией команд и серверным пушем — все концепции гл. 23 и гл. 33 сливаются здесь.

Обзор

Позиционирование: выпускной проект бинарного направления. Протокол берём XTP2, а не свой формат кадров, чтобы силы ушли на структуру сервиса — ровно то разделение труда, какое положено эпохе фреймворков.

46.1 Цель проекта

ВозможностьДизайн
Набор командGET/SET/DEL/PING (REQUEST → RESPONSE, связка по id)
Пушпри изменении key подписанным соединениям уходит EVENT (канал PUSH)
Сеансыреестр соединений: обработчик + буфер остатков + множество подписок
ПерсистентностьKV в памяти + опциональный сброс на диск (паттерн гл. 32)
Оборонаxtp2 limits + idle_timeout + лимит частоты команд
Развёртываниедва эндпоинта: открытый текст 9000 + TLS 9443

46.2 Сеансы и диспетчеризация команд

Ядро диспетчеризации (развитие сборки гл. 33)
static void DispatchMessage(XS_StreamConn* pConn, const xtp2_message* pMsg)
{
    switch ( pMsg->command ) {
    case CMD_GET: {
        xtp2_span tKey = { pMsg->body, pMsg->body_size };
        xbuffer* pVal = KV_Get(tKey);
        ReplyResponse(pConn, pMsg->id, pVal ? 0 : 1, pVal);  /* id возвращается */
        break; }
    case CMD_SET:
        KV_Set(SplitKeyVal(pMsg));               /* body = key\0val конвенция */
        NotifySubscribers(SplitKey(pMsg), pMsg); /* пуш EVENT */
        ReplyResponse(pConn, pMsg->id, 0, NULL);
        break;
    default:
        ReplyResponse(pConn, pMsg->id, 4096 + pMsg->command, NULL); /* неизвестная команда */
    }
}
/* ReplyResponse: measure→encode→Send (паттерн упаковки гл. 33) */

Что только что произошло: поле команды (u16) становится стабильной осью диспетчеризации — новая команда = новый case и строка документации, а поверхность протокола (формат кадра) не шевелится. Пространство кодов ошибок тоже размечено (0 успех / 1 нет ключа / 4096+ уровень протокола) — «соглашение об ошибках первым» из первой практики действует и в бинарном мире.

46.3 Ответы и пуш

  • Связка по id: RESPONSE дословно возвращает id REQUEST — клиент по нему сопоставляет асинхронные запросы (на одном соединении может висеть несколько в полёте)
  • Пуш EVENT: множества подписок (по набору префиксов key на сеанс) обходятся после фиксации SET — дисциплина вещания списана с гл. 22 (обход под блокировкой, копирующая отправка, терпимость к отказам)
  • Противодавление: оборона от шторма подписок — прикладное лимитирование (максимум EVENT на соединение в секунду) + деградация медленных — стратегическое противодавление гл. 22 перенесено на TCP

46.4 Оборона и два стека

  • limits ужаты: потолок value вашего KV — это max_body; ставьте по бизнесу (например 256 КБ), не оставляйте умолчание 1 МиБ
  • Битый кадр = разрыв + счёт: на SessionDrop наращивайте счётчик враждебных кадров в /stats (привычка наблюдаемости гл. 43)
  • Уборка простоев: idle_timeout выметает зомби-соединения; множества подписок чистятся в терминале Close
  • Два стека: открытый текст (отладка в интрасети) + TLS (наружу) на одном наборе колбэков (продукт упражнения 3 из гл. 23) — сертификаты SNI по гл. 12

46.5 Лист приёмки

ИзмерениеСтандарт
Функциичетыре команды + пуш по подпискам; связка id верна
Устойчивость протоколафрагментация/склейки/враждебные кадры — три класса тестов (упражнения гл. 33)
Конкурентность100 запросов в полёте на соединении под нагрузкой; вещание многим соединениям без перехлёстов
Жизненный циклпосле смены поколения подписки восстанавливаются сами (множество идёт в снимок Swap — ещё один образец «значения передаются»)
Развёртываниедва эндпоинта + учения по горячей замене сертификатов

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

Пуш без лимита частоты
Симптом
При высокочастотном SET горячего key отправочные буферы подписанных соединений распухают.
Причина
Отправка TCP — асинхронный буфер: без прикладного лимита EVENT и склейки (например, окно 50 мс сливает однотипные) медленные потребители утаскивают память. Копируйте стратегическое противодавление гл. 22.
Множество подписок не передаётся
Симптом
После горячей перезагрузки пуш умирает.
Причина
Подписки — «значения» (строки префиксов key): по критерию гл. 28 их надо передавать (снимок в Swap); реестр сеансов сам не передаётся (соединения принадлежат старому поколению), новые соединения подписываются заново.

Упражнения

БазовыйСобрать шлюз

По 46.2–46.5 соберите KV-шлюз (клиента можно написать вложенным TCC как тестовый конец).

Приёмка: все пять измерений зелёные.
ПродвинутыйКлиент протокола

Напишите минимальный клиентский модуль (connect → encode запроса → feed ответа → сопоставление id) для переиспользования другими скриптами.

Приёмка: 100 конкурентных запросов в полёте без перехлёстов.
ВызовHTTP-мост

Добавьте HTTP-сервис, переводящий REST-запросы в вызовы XTP2 (шлюз наоборот) — сотрудничество двух server (гл. 11).

Приёмка: семантика REST полностью ложится на командный протокол; мост не блокирует.

Итоги

ТезисВывод
Структурареестр сеансов + таблица диспетчеризации + связка id
Пушканал EVENT переиспользует дисциплину вещания WS (лимит/склейка/деградация)
Оборонаlimits по бизнесу + битый кадр = разрыв + уборка простоев
Поколенияподписки передаются (значения); таблица сеансов нет (соединения при поколении)
API, затронутые в главе
Что дальше по xrt
  • xrt · tcp — материал клиентских соединений (продвинутое упражнение)