← Назад к вопросам
Какие знаешь гарантии доставки у очереди, которая работает по JMS протоколу?
2.2 Middle🔥 141 комментариев
#Базы данных и SQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Гарантии доставки в JMS
Введение в JMS (Java Message Service)
JMS — это Java API для взаимодействия между приложениями через асинхронный обмен сообщениями. Гарантии доставки — критический аспект надежности систем на основе очередей.
1. At-Most-Once (Максимум один раз)
At-Most-Once — сообщение может быть потеряно, но не будет доставлено дважды:
import javax.jms.*;
@Configuration
@EnableJms
public class JmsProducerConfig {
@Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
// Настройка для At-Most-Once доставки
jmsTemplate.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
jmsTemplate.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
return jmsTemplate;
}
}
@Service
public class OrderProducer {
@Autowired
private JmsTemplate jmsTemplate;
public void sendOrder(Order order) {
// Сообщение может быть потеряно если broker упадет
jmsTemplate.convertAndSend("orders.queue", order);
}
}
// Недостатки:
// - Если broker упадет, сообщение теряется
// - Не подходит для критичных операций (платежи, заказы)
// Преимущества:
// - Максимальная производительность
// - Нет дублирования
2. At-Least-Once (Как минимум один раз)
At-Least-Once — гарантирует доставку, но может быть дублирование:
@Configuration
@EnableJms
public class JmsQueueConfig {
@Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
// Настройка для At-Least-Once доставки
jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
jmsTemplate.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
return jmsTemplate;
}
}
@Service
public class PaymentProducer {
@Autowired
private JmsTemplate jmsTemplate;
public void sendPayment(Payment payment) {
// Сообщение сохраняется на диск
// Гарантированная доставка даже если broker упадет
jmsTemplate.convertAndSend("payments.queue", payment);
}
}
// Consumer с обработкой дубликатов
@Component
public class PaymentConsumer {
@JmsListener(destination = "payments.queue")
public void processPayment(Payment payment, Session session) {
try {
// Обработка платежа
if (paymentService.isAlreadyProcessed(payment.getId())) {
// Идемпотентность - просто подтверждаем
session.acknowledge();
return;
}
paymentService.process(payment);
session.acknowledge(); // Подтверждаем получение
} catch (Exception e) {
// При ошибке сообщение будет переправлено
// throw e; // Requeue для повторной обработки
}
}
}
3. Exactly-Once (Ровно один раз)
Exactly-Once — идеальная гарантия, но сложно реализуется:
import org.springframework.transaction.annotation.Transactional;
import javax.transaction.Transactional;
@Configuration
public class ExactlyOnceConfig {
@Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
// Persistent delivery
jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
// Дождаться подтверждения от broker
jmsTemplate.setExplicitQosEnabled(true);
jmsTemplate.setTimeToLive(3600000); // 1 час
return jmsTemplate;
}
}
@Service
public class OrderService {
@Autowired
private JmsTemplate jmsTemplate;
@Autowired
private OrderRepository orderRepository;
@Transactional
public void createOrderWithNotification(Order order) {
// 1. Сохраняем заказ в БД в транзакции
Order savedOrder = orderRepository.save(order);
// 2. Отправляем сообщение в транзакции
// Если транзакция откатится, оба действия откатываются
jmsTemplate.convertAndSend("order.notifications",
new OrderCreatedEvent(savedOrder.getId()));
// Если broker упадет между сохранением и отправкой,
// сообщение будет пересчитано из базы
}
}
@Component
public class OrderEventListener {
@Autowired
private NotificationService notificationService;
@Autowired
private ProcessingRepository processingRepository;
@JmsListener(destination = "order.notifications")
@Transactional
public void onOrderCreated(OrderCreatedEvent event, Session session) {
try {
// Проверяем идемпотентность
if (processingRepository.exists(event.getOrderId())) {
session.acknowledge();
return;
}
// Обработка
notificationService.notifyOrderCreated(event.getOrderId());
// Отмечаем обработанным
processingRepository.mark(event.getOrderId());
session.acknowledge();
} catch (Exception e) {
// Сообщение вернется в очередь для повторной обработки
throw new RuntimeException("Processing failed", e);
}
}
}
4. Режимы Acknowledgment
Различные режимы подтверждения доставки:
@Configuration
public class AcknowledgmentModes {
// 1. AUTO_ACKNOWLEDGE - автоматическое подтверждение
@Bean
public JmsTemplate autoAckTemplate(ConnectionFactory connectionFactory) {
JmsTemplate template = new JmsTemplate(connectionFactory);
template.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
// Сообщение подтверждается сразу после получения
// Если потребитель упадет, сообщение теряется
return template;
}
// 2. CLIENT_ACKNOWLEDGE - ручное подтверждение
@Bean
public JmsTemplate clientAckTemplate(ConnectionFactory connectionFactory) {
JmsTemplate template = new JmsTemplate(connectionFactory);
template.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
// Потребитель должен явно вызвать acknowledge()
return template;
}
// 3. DUPS_OK_ACKNOWLEDGE - разрешено дублирование
@Bean
public JmsTemplate dupsOkTemplate(ConnectionFactory connectionFactory) {
JmsTemplate template = new JmsTemplate(connectionFactory);
template.setSessionAcknowledgeMode(Session.DUPS_OK_ACKNOWLEDGE);
// Производительность важнее дублирования
// Может быть задержка в подтверждении
return template;
}
// 4. SESSION_TRANSACTED - транзакционное подтверждение
@Bean
public JmsTemplate transactedTemplate(ConnectionFactory connectionFactory) {
JmsTemplate template = new JmsTemplate(connectionFactory);
template.setSessionTransacted(true);
// Требует явного коммита транзакции
return template;
}
}
@Component
public class AcknowledgmentExamples {
// CLIENT_ACKNOWLEDGE пример
@JmsListener(destination = "orders.queue")
public void processOrderWithClientAck(Order order, Message message, Session session) throws JMSException {
try {
System.out.println("Processing: " + order.getId());
// Обработка...
message.acknowledge(); // Явное подтверждение
} catch (Exception e) {
// Не подтверждаем - сообщение вернется в очередь
throw new RuntimeException(e);
}
}
// DUPS_OK_ACKNOWLEDGE пример
@JmsListener(destination = "high.volume.queue")
public void processHighVolume(String message) {
// Автоматическое подтверждение с возможностью дублирования
// Подходит для high-performance scenarios
System.out.println("Processing: " + message);
}
}
5. Persistence и Delivery Modes
@Configuration
public class PersistenceConfig {
@Bean
public JmsTemplate persistentTemplate(ConnectionFactory connectionFactory) {
JmsTemplate template = new JmsTemplate(connectionFactory);
// Persistent - сообщение сохраняется на диск
template.setDeliveryMode(DeliveryMode.PERSISTENT);
// Non-persistent - сообщение только в памяти (быстрее)
// template.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
return template;
}
}
@Service
public class MessageProducer {
@Autowired
private JmsTemplate jmsTemplate;
// Для важных сообщений - используем PERSISTENT
public void sendCriticalMessage(String message) {
jmsTemplate.convertAndSend("critical.queue", message, msg -> {
msg.setJMSDeliveryMode(DeliveryMode.PERSISTENT);
msg.setJMSPriority(Message.DEFAULT_PRIORITY);
msg.setJMSExpiration(60000); // TTL - 1 минута
return msg;
});
}
// Для некритичных - используем NON_PERSISTENT для скорости
public void sendAnalyticsEvent(String event) {
jmsTemplate.convertAndSend("analytics.queue", event, msg -> {
msg.setJMSDeliveryMode(DeliveryMode.NON_PERSISTENT);
return msg;
});
}
}
Сравнение гарантий доставки
| Гарантия | Потеря данных | Дублирование | Производительность | Сложность |
|---|---|---|---|---|
| At-Most-Once | Возможна | Нет | Очень высокая | Низкая |
| At-Least-Once | Нет | Возможно | Средняя | Средняя |
| Exactly-Once | Нет | Нет | Низкая | Высокая |
Best Practices
- Используйте At-Least-Once для важных данных — платежи, заказы, критичные операции
- Реализуйте идемпотентность потребителей — обрабатывайте дубликаты
- Используйте транзакции — для согласованности БД и очереди
- Настройте TTL (Time To Live) — удаляйте устаревшие сообщения
- Мониторьте Dead Letter Queue — обрабатывайте сообщения которые не удалось доставить
- Тестируйте отказы — симулируйте падение broker, потребителя
- Документируйте гарантии — явно указывайте для каждой очереди
Пример с Dead Letter Queue
@Component
public class MessageListener {
@JmsListener(destination = "orders.queue")
public void processOrder(Order order) {
try {
// Обработка
orderService.process(order);
} catch (Exception e) {
// После нескольких попыток сообщение отправляется в DLQ
throw new RuntimeException("Failed after retries", e);
}
}
@JmsListener(destination = "orders.queue.dlq") // Dead Letter Queue
public void handleFailedOrder(Order order) {
// Обработка неудачных сообщений
failureService.logFailure(order);
notificationService.alertAdmin("Order processing failed: " + order.getId());
}
}
Выводы
- At-Most-Once — для некритичных данных (аналитика, логи)
- At-Least-Once — для важных данных (заказы, платежи)
- Exactly-Once — для критичных финансовых операций
- Идемпотентность потребителя — ключ к правильной обработке
- Мониторинг и обработка ошибок — обязательны