В протоколе STOMP подтверждение доставки сообщений (acknowledgement) определяет, когда и при каких условиях брокер считает сообщение успешно обработанным потребителем. Это критически важный механизм, влияющий на гарантии доставки, повторную отправку и устойчивость системы к сбоям.
В STOMP 1.2 введена расширенная модель подтверждений, которая управляется через заголовки подписки и методы подтверждения на стороне клиента. STOMP.js реализует этот функционал через объект подписки и объект входящего сообщения.
Каждое сообщение, полученное клиентом, может быть подтверждено или
отклонено. Поведение зависит от выбранного режима ack.
При подписке на очередь или топик клиент указывает режим подтверждения:
client.subscribe('/queue/orders', onMessage, {
ack: 'auto'
});
Брокер отправляет сообщения клиенту, а затем ожидает либо:
Каждое входящее сообщение содержит служебные поля:
messageIdsubscriptionack (если поддерживается брокером)В STOMP.js объект сообщения содержит методы:
message.ack()message.nack()Режим auto означает автоматическое подтверждение
доставки сразу после получения сообщения клиентом.
client.subscribe('/queue/orders', (message) => {
const body = JSON.parse(message.body);
processOrder(body);
}, {
ack: 'auto'
});
Если внутри обработчика возникает исключение:
client.subscribe('/queue/orders', (message) => {
throw new Error('fail');
}, {
ack: 'auto'
});
сообщение уже считается подтвержденным, и повторной доставки не будет.
Режим client переводит ответственность за подтверждение
сообщений на клиента. Брокер считает сообщение неподтвержденным, пока не
будет вызван ack().
client.subscribe('/queue/orders', (message) => {
try {
const order = JSON.parse(message.body);
processOrder(order);
message.ack();
} catch (e) {
message.nack();
}
}, {
ack: 'client'
});
message.ack();
Подтверждает успешную обработку сообщения.
message.nack();
Означает отказ от обработки. Брокер может:
Режим client-individual является более строгим вариантом
client. Он позволяет подтверждать каждое сообщение
независимо, даже если они приходят в рамках одной подписки.
client.subscribe('/queue/orders', (message) => {
if (isValid(message.body)) {
message.ack();
} else {
message.nack();
}
}, {
ack: 'client-individual'
});
Некоторые брокеры трактуют client и
client-individual одинаково, но спецификация STOMP 1.2
разделяет их поведение.
Режим подтверждения задается в момент подписки:
client.subscribe('/queue/tasks', handler, {
ack: 'client'
});
Допустимые значения:
autoclientclient-individualЕсли параметр не указан, большинство реализаций используют
auto по умолчанию.
Каждая подписка имеет идентификатор:
client.subscribe('/queue/tasks', handler, {
id: 'sub-001',
ack: 'client'
});
Этот id используется брокером для связывания сообщений с
конкретной подпиской. При вызове ack() брокер понимает, к
какой очереди и какой подписке относится подтверждение.
В STOMP.js объект сообщения содержит метаданные, необходимые для подтверждения:
{
command: 'MESSAGE',
headers: {
destination: '/queue/tasks',
message-id: '12345',
subscription: 'sub-001'
},
body: '{"task":"build"}'
}
Метод ack() использует эти заголовки:
message.ack();
Эквивалентно низкоуровневому STOMP-фрейму:
ACK
id:12345
STOMP позволяет отправлять отдельные команды:
ACK
id:message-123
subscription:sub-001
NACK
id:message-123
subscription:sub-001
STOMP.js абстрагирует эти детали, но логика остается той же.
При использовании client или
client-individual брокер может повторно доставить
сообщение, если:
Поведение зависит от брокера:
Если соединение обрывается до подтверждения:
MESSAGE -> клиент
(нет ACK)
disconnect
сообщение возвращается в очередь и может быть доставлено повторно.
Это важная гарантия режима client.
client.subscribe('/queue/jobs', (message) => {
process(message.body);
});
При ack: 'client' такое поведение приводит к зависанию
сообщений в unacked состоянии.
message.ack();
message.ack();
В большинстве брокеров это игнорируется или вызывает ошибку протокола.
client.subscribe('/queue/tasks', async (message) => {
await processAsync(message.body);
message.ack();
}, { ack: 'client' });
Если processAsync падает, ack не будет вызван, и
сообщение вернется в очередь.
ACK может быть объединен с транзакциями STOMP:
const tx = client.begin();
client.subscribe('/queue/orders', (message) => {
process(message.body);
message.ack({ transaction: tx.id });
tx.commit();
}, {
ack: 'client'
});
В этом случае подтверждение становится частью транзакции и фиксируется только при commit.
ACK режим напрямую влияет на backpressure:
auto увеличивает скорость обработки, но снижает
надежностьclient позволяет ограничивать поток необработанных
сообщенийБрокеры часто ограничивают количество unacked сообщений на подписку, что предотвращает перегрузку клиента.
Типичная стратегия:
client.subscribe('/queue/tasks', (message) => {
try {
const task = JSON.parse(message.body);
execute(task);
message.ack();
} catch (e) {
message.nack();
}
}, {
ack: 'client-individual'
});
Поведение:
STOMP.js реализует ack как метод объекта сообщения, который инкапсулирует:
Методы:
ack(headers?)nack(headers?)Дополнительные headers позволяют указывать:
message.ack({
id: message.headers['message-id']
});
Хотя STOMP задает единый интерфейс, реальное поведение зависит от реализации:
Различия проявляются в:
ACK режим формирует базовую модель надежности системы доставки:
При проектировании потоков сообщений выбор режима определяет баланс между: