В STOMP-протоколе подписка представляет собой постоянное логическое соединение между клиентом и конкретным destination на брокере сообщений. В JavaScript-библиотеке STOMP.js каждая подписка создаётся как объект, связанный с активным соединением, и управляется через клиентский экземпляр.
Ключевая особенность модели заключается в том, что подписка не является статической: она может быть разорвана, пересоздана, потеряна при реконнекте или временно деактивирована при сбое транспортного уровня (WebSocket). Поэтому мониторинг состояния подписок становится критически важным элементом устойчивой архитектуры realtime-приложений.
Подписка в STOMP.js проходит несколько стадий:
client.subscribe(destination, callback)Важно учитывать, что STOMP-протокол сам по себе не гарантирует восстановление подписок после переподключения. Это поведение полностью лежит на стороне клиента.
Стандартная подписка выглядит следующим образом:
const subscription = client.subscribe('/topic/orders', (message) => {
const payload = JSON.parse(message.body);
console.log(payload);
});
Объект subscription содержит метод:
unsubscribe() — отмена подпискиОднако он не содержит встроенных инструментов для мониторинга состояния соединения или подтверждения доставки сообщений. Это формирует необходимость внешнего слоя наблюдения.
При разрыве WebSocket соединения STOMP.js в большинстве конфигураций выполняет повторное подключение. Но:
Это приводит к ситуации, когда соединение активно, но сообщений не поступает, поскольку подписки отсутствуют.
Основная стратегия — централизованное хранение всех активных подписок:
const subscriptions = new Map();
function addSubscription(key, destination, handler) {
const sub = client.subscribe(destination, handler);
subscriptions.set(key, { destination, handler, sub });
}
function removeSubscription(key) {
const entry = subscriptions.get(key);
if (entry) {
entry.sub.unsubscribe();
subscriptions.delete(key);
}
}
Такой реестр позволяет:
После переподключения клиента необходимо пройтись по реестру и восстановить подписки:
function restoreSubscriptions() {
subscriptions.forEach((entry, key) => {
const sub = client.subscribe(entry.destination, entry.handler);
subscriptions.set(key, { ...entry, sub });
});
}
Обычно это привязывается к событию onConnect:
client.onConn ect = () => {
restoreSubscriptions();
};
Подписки невозможно корректно мониторить без контроля состояния самого WebSocket/STOMP клиента.
STOMP.js предоставляет callback-и:
onConnectonDisconnectonStompErroronWebSocketCloseПример централизованного наблюдения:
client.onConn ect = () => {
console.log('connected');
restoreSubscriptions();
};
client.onDisconn ect = () => {
console.log('disconnected');
};
client.onStompEr ror = (frame) => {
console.error('STOMP error', frame);
};
client.onWebSocketCl ose = () => {
console.warn('socket closed');
};
Heartbeat в STOMP используется для проверки живости соединения.
const client = new Client({
brokerURL: 'ws://localhost:8080/ws',
heartbeatIncoming: 10000,
heartbeatOutgoing: 10000
});
Отсутствие heartbeat-сообщений может указывать на:
Однако heartbeat не проверяет конкретные подписки, он лишь контролирует транспортный уровень.
Практический метод мониторинга — фиксация факта получения сообщений:
const lastMessageTime = new Map();
function subscribeWithMonitoring(key, destination) {
const sub = client.subscribe(destination, (msg) => {
lastMessageTime.set(key, Date.now());
handleMessage(msg);
});
subscriptions.set(key, sub);
}
Далее можно периодически проверять “устаревшие” подписки:
setInterval(() => {
const now = Date.now();
lastMessageTime.forEach((time, key) => {
if (now - time > 60000) {
console.warn(`No messages for subscription ${key}`);
}
});
}, 30000);
Это позволяет обнаруживать:
STOMP.js позволяет анализировать входящие и исходящие фреймы при включённом debug-режиме:
const client = new Client({
brokerURL: 'ws://localhost:8080/ws',
debug: (str) => {
console.log(str);
}
});
Мониторинг фреймов полезен для:
В крупных приложениях подписки часто привязаны к состоянию UI или бизнес-логике.
Типичные проблемы:
Решение — строгая идентификация подписок:
const subscriptionKeys = {
ORDERS: 'orders_subscription',
NOTIFICATIONS: 'notifications_subscription'
};
И централизованное управление через менеджер:
class SubscriptionManager {
constructor(client) {
this.client = client;
this.registry = new Map();
}
subscribe(key, destination, handler) {
if (this.registry.has(key)) {
this.registry.get(key).sub.unsubscribe();
}
const sub = this.client.subscribe(destination, handler);
this.registry.set(key, { destination, handler, sub });
}
restore() {
this.registry.forEach((entry, key) => {
const sub = this.client.subscribe(entry.destination, entry.handler);
this.registry.set(key, { ...entry, sub });
});
}
}
Рассинхронизация возникает, когда:
Для выявления применяются комбинированные методы:
При нестабильной сети частые reconnect могут приводить к лавине подписок, если логика восстановления реализована некорректно.
Правильный подход:
let reconnecting = false;
client.onDisconn ect = () => {
reconnecting = true;
};
client.onConn ect = () => {
if (reconnecting) {
restoreSubscriptions();
reconnecting = false;
}
};
Для production-уровня полезно собирать следующие показатели:
Эти метрики позволяют выявлять: