В протоколе STOMP подтверждение доставки сообщений (acknowledgement) является ключевым механизмом управления надежностью обработки. Брокер не освобождает сообщение из очереди до тех пор, пока не получит явное подтверждение от клиента, если используется режим клиентского квитирования. Это позволяет строить системы с гарантией обработки сообщений и управляемым повторным доставлением.
STOMP.js реализует три основных режима квитирования, задаваемых при подписке:
autoclientclient-individualКаждый режим определяет, кто и когда подтверждает получение сообщения.
auto:
автоматическое квитированиеВ режиме auto подтверждение отправляется библиотекой
автоматически сразу после доставки сообщения в клиент. Разработчик не
участвует в процессе подтверждения.
Подписка:
const subscription = client.subscribe('/queue/tasks', (message) => {
console.log(message.body);
}, { ack: 'auto' });
Поведение:
Особенности:
client: ручное подтверждение батча сообщенийРежим client переводит ответственность за подтверждение
на разработчика. Однако подтверждение относится к группе сообщений,
полученных в рамках подписки. Это означает, что ACK подтверждает все
сообщения до текущего момента.
Подписка:
const subscription = client.subscribe('/queue/tasks', (message) => {
console.log(message.body);
message.ack();
}, { ack: 'client' });
Механика работы:
message-idmessage.ack()message-idПроблема модели:
Этот режим используется реже из-за своей «грубости» и ограниченной управляемости.
client-individual: точечное квитирование сообщенийНаиболее гибкий режим — client-individual. Каждое
сообщение подтверждается отдельно, независимо от других.
Подписка:
const subscription = client.subscribe('/queue/tasks', (message) => {
try {
const data = JSON.parse(message.body);
processTask(data);
message.ack();
} catch (e) {
message.nack();
}
}, { ack: 'client-individual' });
Особенности поведения:
В STOMP.js сообщение предоставляет два ключевых метода управления состоянием:
ack() — подтверждение успешной обработкиnack() — отклонение сообщенияИспользуется для уведомления брокера об успешной обработке:
message.ack();
После вызова:
Используется для индикации ошибки обработки:
message.nack();
Поведение зависит от брокера:
STOMP позволяет связывать ACK с транзакциями. В этом случае подтверждение становится частью атомарной операции.
Пример:
const tx = client.begin();
const subscription = client.subscribe('/queue/tasks', (message) => {
try {
processTask(message.body);
message.ack({ transaction: tx.id });
tx.commit();
} catch (e) {
tx.abort();
}
});
Особенности:
Если соединение разрывается до отправки ACK:
client и client-individual
сообщения считаются необработаннымиЭто требует от клиента идемпотентной обработки:
| Режим | Управление ACK | Точность | Надежность | Сложность |
|---|---|---|---|---|
| auto | нет | низкая | низкая | минимальная |
| client | частичное | средняя | средняя | средняя |
| client-individual | полное | высокая | высокая | высокая |
Режим auto:
Режим client:
Режим client-individual:
Некорректное использование ACK приводит к серьезным сбоям:
ack() при client-individual →
блокировка очередиack() до завершения обработки → потеря
данныхnack() → зависание сообщений в состоянии
in-flightРазные брокеры STOMP по-разному интерпретируют квитирование:
client-individualЭто влияет на:
При использовании клиентского квитирования система должна быть устойчива к повторной доставке:
Типичный подход:
if (processedMessages.has(message.id)) {
message.ack();
return;
}
При асинхронных операциях ACK должен вызываться строго после завершения:
client.subscribe('/queue/tasks', async (message) => {
try {
await asyncProcess(message.body);
message.ack();
} catch (e) {
message.nack();
}
}, { ack: 'client-individual' });
Критично:
await