Изоляция каналов

Изоляция каналов в STOMP.js применяется для разделения потоков сообщений между различными группами клиентов, сервисов и типов данных. Такой подход предотвращает пересечение логики обработки, снижает вероятность ошибок маршрутизации и повышает безопасность системы обмена сообщениями.

В системах реального времени один брокер сообщений может одновременно обслуживать:

  • чат-приложения;
  • уведомления;
  • финансовые транзакции;
  • телеметрию;
  • административные события;
  • системные логи;
  • очереди фоновых задач.

Без изоляции сообщения разных подсистем начинают смешиваться, что приводит к:

  • лишнему сетевому трафику;
  • утечкам данных;
  • росту нагрузки на клиентов;
  • ошибкам подписки;
  • усложнению масштабирования.

STOMP.js предоставляет механизмы, позволяющие строить полностью разделённые каналы обмена сообщениями.


Базовая модель каналов в STOMP

В STOMP основными сущностями являются:

  • destination;
  • topic;
  • queue;
  • subscription.

Примеры каналов:

/topic/chat
/topic/notifications
/queue/orders
/queue/logs

Каждый destination фактически становится изолированным каналом передачи данных.

Пример подписки:

client.subscribe('/topic/chat', message => {
    console.log(message.body);
});

Пример отправки:

client.publish({
    destination: '/topic/chat',
    body: JSON.stringify({
        text: 'Hello'
    })
});

Логическая изоляция каналов

Наиболее распространённый способ разделения — использование различных пространств имён.

Пример структуры

/topic/public/*
/topic/private/*
/topic/system/*
/topic/admin/*

Пример:

client.subscribe('/topic/public/news', handler);
client.subscribe('/topic/private/messages', handler);

Преимущества:

  • предсказуемая маршрутизация;
  • удобная фильтрация;
  • упрощение ACL;
  • независимое масштабирование.

Изоляция по ролям пользователей

Одним из важнейших механизмов является разделение доступа по ролям.

Административный канал

client.subscribe('/topic/admin/events', eventHandler);

Пользовательский канал

client.subscribe('/topic/user/notifications', notificationHandler);

На стороне брокера доступ ограничивается ACL-политиками.

Например:

ROLE_ADMIN -> /topic/admin/**
ROLE_USER -> /topic/user/**

Это предотвращает:

  • чтение чужих сообщений;
  • публикацию в системные каналы;
  • перехват внутренних событий.

Персональные пользовательские каналы

Для полной изоляции пользователей применяются индивидуальные destination.

Пример

/user/125/messages
/user/890/messages

Подписка:

client.subscribe('/user/125/messages', message => {
    console.log(message.body);
});

Отправка:

client.publish({
    destination: '/user/125/messages',
    body: 'Private message'
});

Динамическая генерация каналов

В больших системах каналы часто создаются динамически.

Каналы комнат чата

const roomId = 15;

client.subscribe(`/topic/rooms/${roomId}`, onMessage);

Каналы игровых сессий

client.subscribe(`/topic/game/${sessionId}`, onGameEvent);

Каналы IoT-устройств

client.subscribe(`/topic/device/${deviceId}`, onTelemetry);

Такой подход обеспечивает естественную изоляцию данных.


Изоляция через отдельные брокеры

Крупные системы иногда разделяют каналы не только логически, но и физически.

Пример архитектуры

Брокер Назначение
Broker A чат
Broker B аналитика
Broker C системные события
Broker D телеметрия

Подключение к отдельному брокеру:

const client = new Client({
    brokerURL: 'wss://chat.example.com/ws'
});

Другой канал:

const metricsClient = new Client({
    brokerURL: 'wss://metrics.example.com/ws'
});

Преимущества:

  • независимое масштабирование;
  • отказоустойчивость;
  • снижение взаимного влияния подсистем;
  • отдельные политики безопасности.

Изоляция через виртуальные хосты

Многие брокеры STOMP поддерживают virtual hosts.

Пример:

const client = new Client({
    brokerURL: 'wss://broker.example.com/ws',
    connectHeaders: {
        host: 'finance-vhost'
    }
});

Другой виртуальный хост:

connectHeaders: {
    host: 'chat-vhost'
}

Виртуальные хосты позволяют:

  • разделять tenants;
  • изолировать клиентов;
  • разделять namespace;
  • использовать независимые очереди.

Tenant-изоляция

Многопользовательские SaaS-системы часто используют tenant-based routing.

Пример

/topic/tenant/100/orders
/topic/tenant/200/orders

Формирование destination:

const tenantId = 100;

client.subscribe(
    `/topic/tenant/${tenantId}/orders`,
    processOrders
);

Преимущества:

  • полная сегментация клиентов;
  • независимые ACL;
  • удобный аудит;
  • безопасная маршрутизация.

Изоляция publish и subscribe

Иногда требуется разделить каналы отправки и чтения.

Пример

/topic/inbound/orders
/topic/outbound/orders

Публикация:

client.publish({
    destination: '/topic/inbound/orders',
    body: orderData
});

Подписка:

client.subscribe(
    '/topic/outbound/orders',
    handleOrderResult
);

Подобная архитектура уменьшает риск циклических сообщений.


Изоляция системных событий

Системные события не должны смешиваться с пользовательскими данными.

Плохая архитектура

/topic/messages

Внутри:

{
  "type": "chat"
}

и

{
  "type": "system"
}

Правильная архитектура

/topic/chat/messages
/topic/system/events
/topic/system/errors

Это:

  • упрощает фильтрацию;
  • снижает нагрузку;
  • уменьшает вероятность ошибок.

Изоляция по типам нагрузки

Каналы могут разделяться по характеру трафика.

Высокочастотные данные

/topic/telemetry

Редкие уведомления

/topic/notifications

Критические события

/topic/critical

Такой подход помогает:

  • управлять QoS;
  • балансировать нагрузку;
  • ограничивать burst traffic.

Изоляция через префиксы

Распространённая практика — использование стандартизированных префиксов.

Пример

/ws/chat/*
/ws/system/*
/ws/private/*
/ws/admin/*

Или:

/topic/*
/queue/*
/exchange/*

Преимущества:

  • единый стандарт;
  • простая навигация;
  • удобный мониторинг.

Изоляция ACK-потоков

При ручном подтверждении сообщений важно избегать смешивания различных потоков ACK.

Пример

client.subscribe('/queue/orders', message => {

    processOrder(message);

    message.ack();

}, {
    ack: 'client'
});

Для критичных данных используют отдельные очереди:

/queue/payments
/queue/security

Это уменьшает вероятность неправильного подтверждения сообщений.


Изоляция транзакций

STOMP поддерживает транзакции.

Пример:

const tx = client.begin();

client.publish({
    destination: '/queue/orders',
    body: orderData,
    transaction: tx.id
});

tx.commit();

Изоляция транзакционных каналов позволяет:

  • отделять критичные операции;
  • уменьшать конкуренцию;
  • повышать предсказуемость обработки.

Изоляция heartbeat-трафика

Heartbeat-пакеты не должны перегружать основные каналы.

Настройка heartbeat:

const client = new Client({
    brokerURL: 'wss://broker.example.com/ws',
    heartbeatIncoming: 10000,
    heartbeatOutgoing: 10000
});

В высоконагруженных системах heartbeat может обслуживаться отдельным балансировщиком или кластером.


Изоляция WebSocket endpoint

Даже WebSocket endpoint может быть разделён.

Пример

/ws/chat
/ws/admin
/ws/metrics
/ws/telemetry

Подключение:

const chatClient = new Client({
    brokerURL: 'wss://example.com/ws/chat'
});

Административный канал:

const adminClient = new Client({
    brokerURL: 'wss://example.com/ws/admin'
});

Изоляция через разные STOMP-клиенты

Иногда выгоднее использовать несколько экземпляров Client.

Пример

const notificationClient = new Client({
    brokerURL: 'wss://broker/ws'
});

const analyticsClient = new Client({
    brokerURL: 'wss://analytics/ws'
});

Преимущества:

  • независимое reconnect-поведение;
  • отдельные heartbeat;
  • независимые очереди подписок;
  • разграничение логики.

Изоляция reconnect-механизмов

При сбое одного канала не должна останавливаться вся система.

Пример

const metricsClient = new Client({
    reconnectDelay: 10000
});

Для критичных каналов:

const criticalClient = new Client({
    reconnectDelay: 1000
});

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


Изоляция через message selectors

Некоторые брокеры поддерживают selectors.

Пример

client.subscribe('/topic/events', handler, {
    selector: "type = 'system'"
});

Другой подписчик:

client.subscribe('/topic/events', handler, {
    selector: "type = 'user'"
});

Хотя сообщения находятся в одном topic, логическая обработка остаётся изолированной.


Изоляция через correlation-id

При RPC-взаимодействии применяются correlation-id.

Пример отправки

client.publish({
    destination: '/queue/rpc',
    headers: {
        'correlation-id': 'req-100'
    },
    body: requestData
});

Обработка ответа:

client.subscribe('/queue/rpc-replies', message => {

    const correlationId =
        message.headers['correlation-id'];

    if (correlationId === 'req-100') {
        processResponse(message);
    }

});

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


Изоляция бинарного и текстового трафика

Для производительности иногда разделяют:

  • JSON;
  • бинарные данные;
  • protobuf;
  • telemetry payload.

Пример

/topic/json/events
/topic/binary/telemetry

Такой подход уменьшает overhead сериализации.


Изоляция потоков логирования

Логи не должны попадать в пользовательские каналы.

Пример

/topic/logs/errors
/topic/logs/debug
/topic/logs/security

Разделение логов позволяет:

  • ограничивать доступ;
  • управлять retention;
  • снижать нагрузку на клиентов.

Безопасность изоляции каналов

Изоляция напрямую связана с безопасностью.

Основные угрозы

Угроза Последствие
подписка на чужой topic утечка данных
публикация в системный канал нарушение логики
wildcard-подписки массовый перехват
отсутствие ACL полный компромисс

Защита каналов

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

ALLOW user -> /topic/user/**
DENY user -> /topic/admin/**

Проверка JWT

connectHeaders: {
    Authorization: 'Bearer token'
}

Валидация destination

На сервере необходимо проверять:

  • допустимые префиксы;
  • tenant-id;
  • user-id;
  • права публикации.

Масштабирование изолированных каналов

Изолированные каналы проще масштабируются.

Пример

Канал Узел
chat node-1
telemetry node-2
analytics node-3

Это снижает:

  • конкуренцию за CPU;
  • блокировки;
  • задержки доставки.

Типичные ошибки проектирования

Смешивание всех сообщений

/topic/all

Недостатки:

  • сложная фильтрация;
  • перегрузка клиента;
  • высокий риск ошибок.

Отсутствие namespace

Плохой пример:

/orders
/messages
/events

Хороший пример:

/topic/orders
/topic/chat/messages
/topic/system/events

Использование wildcard без ограничений

Опасный пример:

/topic/**

Это может привести к:

  • утечке данных;
  • чрезмерной нагрузке;
  • проблемам безопасности.

Архитектура полностью изолированной системы

Пример структуры:

/ws/chat
/ws/admin
/ws/telemetry

/topic/chat/*
/topic/system/*
/topic/admin/*
/topic/tenant/*

/queue/payments
/queue/security
/queue/rpc

Дополнительно:

  • ACL;
  • JWT;
  • virtual hosts;
  • разные брокеры;
  • разные reconnect-политики;
  • отдельные heartbeat-настройки.

Такая архитектура обеспечивает:

  • высокую отказоустойчивость;
  • предсказуемую маршрутизацию;
  • безопасность;
  • удобное масштабирование;
  • независимость подсистем.