Протокол STOMP поверх брокеров сообщений (RabbitMQ, ActiveMQ, Apollo и др.) реализует модель доставки, в которой клиент подписывается на очередь или топик и получает сообщения асинхронно. Важной частью этой модели является механизм подтверждения получения сообщения (acknowledgement), который определяет момент, когда брокер может считать сообщение успешно обработанным.
Подтверждение получения влияет на гарантию доставки:
В STOMP.js управление подтверждениями реализуется через заголовки подписки и методы объекта сообщения.
В режиме автоматического подтверждения сообщение считается успешно доставленным сразу после отправки клиенту. Брокер не ожидает явного подтверждения.
Характеристики:
Пример подписки:
client.subscribe('/queue/orders', (message) => {
const body = JSON.parse(message.body);
processOrder(body);
}, {
ack: 'auto'
});
Особенность режима заключается в том, что даже если обработка
processOrder завершится с ошибкой, брокер уже считает
сообщение доставленным.
Режим client предполагает, что клиент обязан явно
подтвердить сообщение. Брокер удерживает сообщение до получения ACK.
Основные свойства:
Пример:
client.subscribe('/queue/orders', (message) => {
try {
const data = JSON.parse(message.body);
processOrder(data);
message.ack();
} catch (err) {
message.nack();
}
}, {
ack: 'client'
});
Методы:
message.ack() — подтверждает успешную обработку;message.nack() — сообщает о неуспешной обработке.Режим client-individual расширяет модель
client, добавляя строгое индивидуальное подтверждение
каждого сообщения без группировки.
Свойства:
Пример:
client.subscribe('/queue/tasks', (message) => {
const task = JSON.parse(message.body);
try {
executeTask(task);
message.ack();
} catch (e) {
message.nack();
}
}, {
ack: 'client-individual'
});
В этом режиме невозможно подтвердить сразу несколько сообщений одним вызовом, что особенно важно при параллельной обработке.
Метод ack() сообщает брокеру, что сообщение успешно
обработано.
Внутренне он отправляет STOMP-команду:
ACK
id:message-id
Используется только при режимах client и
client-individual.
Пример поведения:
message.ack();
При вызове:
Метод nack() используется для отрицательного
подтверждения.
Он сообщает брокеру, что сообщение не может быть обработано и должно быть переотправлено или перенаправлено в DLQ (Dead Letter Queue), если она настроена.
Пример:
message.nack();
Поведение зависит от брокера:
Подтверждение происходит строго после завершения обработки:
client.subscribe('/queue/invoices', (message) => {
const invoice = JSON.parse(message.body);
generateInvoice(invoice);
message.ack();
}, {
ack: 'client'
});
Преимущество — высокая надежность.
Недостаток — снижение throughput при долгих операциях.
ACK отправляется после завершения асинхронной операции:
client.subscribe('/queue/files', async (message) => {
const file = JSON.parse(message.body);
try {
await uploadFile(file);
message.ack();
} catch (e) {
message.nack();
}
}, {
ack: 'client'
});
Особенность заключается в необходимости корректного управления промисами, иначе ACK может быть отправлен до завершения операции.
Антипаттерн:
client.subscribe('/queue/bad', (message) => {
message.ack();
processSomething(message.body);
}, {
ack: 'client'
});
Последствия:
При разрыве соединения:
prefetch и политики
брокера.Особенно важно в режимах client и
client-individual, где ACK обязателен.
Параметр prefetch определяет, сколько сообщений может
быть доставлено без подтверждения.
Пример:
client.subscribe('/queue/jobs', handler, {
ack: 'client',
'prefetch': 10
});
Поведение:
Неправильная настройка prefetch может привести к:
При использовании nack() или отсутствии
ack() в течение таймаута:
Это важно учитывать при разработке:
Так как повторная доставка является нормальным сценарием, обработка сообщений должна быть устойчивой к дублям.
Пример:
client.subscribe('/queue/payments', async (message) => {
const payment = JSON.parse(message.body);
const exists = await checkPayment(payment.id);
if (exists) {
message.ack();
return;
}
await savePayment(payment);
message.ack();
}, {
ack: 'client'
});
Такая логика предотвращает повторное применение одной и той же операции.
ACK может использоваться вместе с транзакционными отправками сообщений. В этом случае:
client.subscribe('/queue/test', (message) => {
process(message.body);
});
Результат:
message.ack();
message.ack();
Последствия:
client.subscribe('/queue/test', (message) => {
message.ack();
}, {
ack: 'auto'
});
Результат:
ack() игнорируется или вызывает ошибку;Механизм подтверждения в STOMP.js является ключевым элементом построения надежных систем обработки сообщений. Он определяет:
Выбор режима ACK напрямую влияет на архитектуру всей системы обработки сообщений.