← Назад к вопросам

Какие знаешь гарантии доставки у очереди, которая работает по 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

  1. Используйте At-Least-Once для важных данных — платежи, заказы, критичные операции
  2. Реализуйте идемпотентность потребителей — обрабатывайте дубликаты
  3. Используйте транзакции — для согласованности БД и очереди
  4. Настройте TTL (Time To Live) — удаляйте устаревшие сообщения
  5. Мониторьте Dead Letter Queue — обрабатывайте сообщения которые не удалось доставить
  6. Тестируйте отказы — симулируйте падение broker, потребителя
  7. Документируйте гарантии — явно указывайте для каждой очереди

Пример с 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 — для критичных финансовых операций
  • Идемпотентность потребителя — ключ к правильной обработке
  • Мониторинг и обработка ошибок — обязательны
Какие знаешь гарантии доставки у очереди, которая работает по JMS протоколу? | PrepBro