Фильтрация сообщений на уровне брокера представляет собой механизм, при котором сервер сообщений самостоятельно определяет, какие клиенты должны получить конкретное сообщение. В контексте STOMP.js этот подход особенно важен при работе с высоконагруженными системами, большим количеством подписчиков и множеством параллельных каналов обмена данными.
Без серверной фильтрации брокер вынужден передавать все сообщения всем подписчикам канала, а фильтрация переносится на клиентскую сторону. Такой подход создаёт ряд проблем:
При фильтрации на уровне брокера сообщение получает только тот клиент, который соответствует заданным условиям.
Простейший вариант выглядит следующим образом:
client.subscribe('/topic/orders', (message) => {
const order = JSON.parse(message.body);
if (order.userId !== currentUserId) {
return;
}
renderOrder(order);
});
В этом примере:
При небольшом количестве пользователей это может быть допустимо, однако при тысячах подключений ситуация становится критической.
Особенно заметны проблемы в системах:
Наиболее распространённый способ фильтрации — разделение сообщений по destination.
Например:
/topic/orders/user/15
/topic/orders/user/28
/topic/orders/user/41
Подписка выполняется только на собственный канал:
client.subscribe('/topic/orders/user/15', (message) => {
const order = JSON.parse(message.body);
renderOrder(order);
});
Теперь брокер:
Отправка выполняется следующим образом:
client.publish({
destination: '/topic/orders/user/15',
body: JSON.stringify({
id: 1001,
status: 'created'
})
});
Многие брокеры поддерживают иерархическую структуру адресов.
Пример:
/topic/orders/europe/kz
/topic/orders/europe/de
/topic/orders/asia/jp
Подписчики получают только нужную географическую категорию:
client.subscribe('/topic/orders/europe/kz', callback);
Такой подход позволяет:
Некоторые брокеры поддерживают персональные очереди.
Пример для RabbitMQ:
/exchange/amq.direct/user.15
Подписка:
client.subscribe('/exchange/amq.direct/user.15', (message) => {
console.log(message.body);
});
Отправка:
client.publish({
destination: '/exchange/amq.direct/user.15',
body: 'Private message'
});
Такой механизм часто применяется:
Фильтрация может выполняться через структуру topic.
Например:
/topic/news/sport
/topic/news/politics
/topic/news/finance
Клиент подписывается только на нужную категорию:
client.subscribe('/topic/news/finance', (message) => {
console.log('Finance:', message.body);
});
Это позволяет:
Некоторые брокеры поддерживают wildcard-маршрутизацию.
Примеры:
/topic/orders/*
/topic/orders/**
Либо:
topic.orders.*
topic.orders.#
Конкретный синтаксис зависит от брокера:
Пример подписки:
client.subscribe('/topic/orders/*', (message) => {
console.log(message.body);
});
Wildcard-маршруты позволяют:
Некоторые брокеры поддерживают фильтрацию по заголовкам сообщений.
Сообщение:
client.publish({
destination: '/topic/orders',
headers: {
region: 'kz',
priority: 'high'
},
body: JSON.stringify({
id: 500
})
});
Подписчик может быть настроен брокером так, чтобы получать только сообщения:
region = kz
priority = high
Подобная фильтрация особенно популярна в:
В JMS-совместимых брокерах используются selectors.
Пример selector:
region = 'kz' AND priority = 'high'
STOMP-подписка:
client.subscribe(
'/topic/orders',
callback,
{
selector: "region = 'kz'"
}
);
Либо:
client.subscribe(
'/topic/orders',
callback,
{
selector: "priority = 'critical'"
}
);
Брокер анализирует заголовки сообщений и самостоятельно решает:
Сообщения:
client.publish({
destination: '/topic/events',
headers: {
type: 'payment'
},
body: JSON.stringify({
amount: 100
})
});
Подписка:
client.subscribe(
'/topic/events',
(message) => {
console.log(message.body);
},
{
selector: "type = 'payment'"
}
);
Сообщения другого типа не будут доставлены клиенту.
ActiveMQ поддерживает сложные селекторы.
Пример:
client.subscribe(
'/queue/tasks',
callback,
{
selector: "priority > 5 AND region = 'kz'"
}
);
Допустимы:
Пример:
{
selector: "department IN ('sales', 'support')"
}
Сообщение:
client.publish({
destination: '/topic/alerts',
headers: {
priority: 'critical'
},
body: 'Disk failure'
});
Подписка:
client.subscribe(
'/topic/alerts',
callback,
{
selector: "priority = 'critical'"
}
);
Это особенно полезно в:
В multi-tenant системах брокер часто фильтрует сообщения по идентификатору клиента.
Публикация:
client.publish({
destination: '/topic/data',
headers: {
tenant: 'companyA'
},
body: JSON.stringify({
report: 'monthly'
})
});
Подписка:
client.subscribe(
'/topic/data',
callback,
{
selector: "tenant = 'companyA'"
}
);
Это предотвращает:
Некоторые брокеры поддерживают content-based routing.
Маршрутизация строится на содержимом сообщения:
{
"country": "kz",
"priority": "high",
"amount": 5000
}
Брокер анализирует payload и направляет сообщение только нужным подписчикам.
В чистом STOMP подобный механизм встречается редко, однако часто реализуется:
При использовании Spring WebSocket Broker Relay STOMP.js взаимодействует не со встроенным брокером Spring, а с полноценным сервером сообщений:
В этом случае становятся доступны:
RabbitMQ использует routing key и exchange.
Пример:
exchange: orders
routing key: kz.high
Подписка:
client.subscribe('/exchange/orders/kz.high', callback);
Публикация:
client.publish({
destination: '/exchange/orders/kz.high',
body: JSON.stringify({
id: 10
})
});
Topic exchange поддерживает шаблоны.
Примеры routing key:
kz.payment
kz.alert
us.payment
Подписка:
client.subscribe('/exchange/orders/kz.*', callback);
Либо:
client.subscribe('/exchange/orders/#', callback);
Durable subscription сохраняет подписку даже после отключения клиента.
Пример:
client.subscribe(
'/topic/orders',
callback,
{
id: 'orders-subscription',
durable: 'true',
auto-delete: 'false'
}
);
Вместе с selectors это позволяет:
Некоторые брокеры разделяют трафик через virtual hosts.
Примеры:
/dev/orders
/test/orders
/prod/orders
Либо разные vhost в RabbitMQ:
vhost-dev
vhost-prod
Такое разделение помогает:
Фильтрация на уровне брокера значительно уменьшает:
Особенно это важно при:
Плохой пример:
/topic/all-events
Все клиенты получают:
Это быстро приводит к перегрузке.
Плохой подход:
if (message.userId !== currentUserId) {
return;
}
Проблемы:
Плохо:
/orders/europe/kz/almaty/shop/15/device/mobile
Избыточная детализация:
Фильтрация не должна заменять авторизацию.
Даже если используется отдельный topic:
/topic/private/user15
Брокер обязан проверять:
Иначе пользователь сможет подписаться на чужой канал.
Многие брокеры поддерживают ACL.
Пример концепции:
user15 -> subscribe -> /topic/private/user15
DENY -> /topic/private/*
Такой механизм критически важен для:
Грамотно спроектированная фильтрация позволяет:
В крупных системах фильтрация сообщений на уровне брокера является обязательным элементом архитектуры, а не дополнительной оптимизацией.