Индивидуальное квитирование в контексте STOMP.js представляет собой режим подтверждения доставки, при котором каждое сообщение подтверждается отдельно, независимо от других сообщений в очереди или подписке. Такой подход используется в сценариях, где требуется строгий контроль обработки каждого события, высокая надёжность и возможность точечного повторного получения сообщений при сбоях.
В отличие от автоматического режима, где брокер считает сообщение обработанным сразу после отправки клиенту, индивидуальное квитирование переносит ответственность подтверждения на клиентскую сторону и позволяет явно управлять жизненным циклом каждого сообщения.
Протокол STOMP 1.2 и его реализация в STOMP.js поддерживают несколько режимов подтверждения:
Сообщение считается успешно обработанным сразу после доставки клиенту.
Классический ручной режим подтверждения.
ackИндивидуальное подтверждение каждого сообщения.
Именно режим client-individual является основой
индивидуального квитирования.
При использовании client-individual брокер назначает
каждому сообщению уникальный идентификатор доставки. Клиент обязан
подтвердить обработку каждого сообщения отдельно.
Процесс выглядит следующим образом:
ack: 'client-individual'ack() для
конкретного сообщенияЕсли клиент не подтвердил сообщение:
В STOMP.js режим задаётся при подписке:
client.subscribe('/queue/orders', (message) => {
const payload = JSON.parse(message.body);
// обработка
processOrder(payload);
// подтверждение
message.ack();
}, {
ack: 'client-individual'
});
Ключевой момент заключается в том, что режим фиксируется на уровне подписки. После этого каждое сообщение требует явного подтверждения.
Каждое доставленное сообщение содержит метаданные, необходимые для подтверждения:
message-id — идентификатор сообщенияsubscription — идентификатор подпискиack — токен подтверждения (используется
библиотекой)В STOMP.js объект сообщения инкапсулирует эти данные, поэтому разработчик обычно не работает с ними напрямую.
client.subscribe('/queue/tasks', (message) => {
try {
const task = JSON.parse(message.body);
handleTask(task);
message.ack();
} catch (e) {
message.nack();
}
}, { ack: 'client-individual' });
nack сообщает брокеру, что сообщение не обработано и
может быть отправлено повторно или перенаправлено в DLQ (dead letter
queue), в зависимости от конфигурации брокера.
message.nack();
Разница между режимами часто становится критической при высокой нагрузке.
Индивидуальное квитирование напрямую влияет на повторную доставку сообщений.
Такой механизм обеспечивает at-least-once delivery.
При индивидуальном квитировании обработка сообщений должна быть идемпотентной, так как возможны повторные доставки.
Типичные подходы:
message-idЧасто подтверждение откладывается до завершения асинхронных операций:
client.subscribe('/queue/images', async (message) => {
const data = JSON.parse(message.body);
try {
await resizeImage(data.url);
await saveResult(data.id);
message.ack();
} catch (err) {
message.nack();
}
}, { ack: 'client-individual' });
Важно учитывать, что пока ack не вызван, сообщение остаётся “в обработке” с точки зрения брокера.
Индивидуальное квитирование часто используется в воркерных системах.
Особенности:
При высокой параллельности важно контролировать:
Если сообщение не было подтверждено:
Это особенно характерно для брокеров типа RabbitMQ или ActiveMQ, которые строго учитывают ack-состояние.
Некоторые реализации STOMP позволяют запросить подтверждение самого факта ack:
message.ack({ receipt: 'ack-123' });
В ответ брокер отправляет frame RECEIPT, подтверждающий
получение ack-команды. Это используется в критичных системах, где важно
гарантировать не только обработку, но и фиксацию подтверждения на
сервере.
Индивидуальное квитирование применяется в системах:
В этих сценариях потеря или двойная обработка сообщений недопустимы, поэтому требуется строгий контроль ack на уровне каждого сообщения.
Сообщения накапливаются в состоянии unacknowledged и повторно доставляются.
Подтверждение до завершения обработки приводит к потере данных при сбое.
Ошибочные сообщения не возвращаются в очередь и теряются или блокируют поток обработки.
Слишком большое количество одновременно неподтверждённых сообщений может перегрузить брокер.
В STOMP.js каждый объект message инкапсулирует контекст
доставки, включая идентификаторы, необходимые для ack/nack операций.
При вызове:
message.ack();
библиотека формирует STOMP frame:
ACK
id: <ack-id>
subscription: <subscription-id>
и отправляет его через WebSocket соединение.
Индивидуальное квитирование занимает позицию режима, ориентированного на стабильность и предсказуемость поведения системы при сбоях и высокой нагрузке.