RabbitMQ надежная доставка сообщений

Представьте maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

Добавить конфигурацию:

spring:
  rabbitmq:
    host: 192.168.56.10
    port: 5672
    #虚拟主机
    virtual-host: /
    #开启发送端发送数据到broker确认接收到数据
    publisher-confirms: true
    #开启发送端确认,确认数据已经抵达队列
    publisher-returns: true
    template:
      #抵达队列,以异步模式优先回调组合ReturnCallback
      mandatory: true
    listener:
      simple:
        #手动ack消息 手动确认收货 手动确认模式 防止消息丢失
        acknowledge-mode: manual

Добавить класс конфигурации

/**
 * 运行之前,一定要小心,否则要删除队列/交换机重新运行 麻烦!
 *
 * 解决消息丢失(最怕)
 *  1 做好消息确认机制(publisher,consumer【手动ack】)
 *  2 每一个发送的消息都在数据库做好记录。定期将失败的消息再次发送一次
 * 解决消息重复
 *  1 幂等性
 *  2 防重表
 *  3 RabbitMQ自带redelivered (做法过于暴力)
 * 解决消息积压
 *  1 增加更多的消费者
 *  2 上线专门的队列消费服务,取出来,记录到数据库,离线慢慢处理
 */
//开启RabbitMQ消息队列
@EnableRabbit
@Configuration
public class MyRabbitMQConfig {
    
    

    @Autowired
    RabbitTemplate rabbitTemplate;

    @RabbitListener(queues = "order.release.order.queue")
    public void listening(OrderEntity entity, Channel channel, Message message) throws IOException {
    
    
        System.out.println("收到过期的订单,准备关闭订单。order:"+entity.getOrderSn());
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    //容器中的组建Queue Exchange Binding 都会自动创建(前提是RabbitMQ没有)
    @Bean
    public Queue orderDelayQueue() {
    
    

        // String name, boolean durable, boolean exclusive, boolean autoDelete,
        //			@Nullable Map<String, Object> arguments
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange", "order-event-exchange");//死信交换机
        arguments.put("x-dead-letter-routing-key", "order.release.order");//死信路由键
        arguments.put("x-message-ttl", 60000);//消息过期时间 ms 1分钟
        return new Queue("order.delay.queue", true, false, false, arguments);
    }

    @Bean
    public Queue orderReleaseOrderQueue() {
    
    

        //普通队列
        return new Queue("order.release.order.queue", true, false, false);
    }

    @Bean
    public Exchange orderEventExchange() {
    
    

        // String name, boolean durable, boolean autoDelete, Map<String, Object> arguments
        //普通交换机
        return new TopicExchange("order-event-exchange", true, false);
    }

    @Bean
    public Binding orderCreateOrderBinding() {
    
    

        //和延时队列绑定
        return new Binding("order.delay.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.create.order",
                null);
    }

    @Bean
    public Binding orderReleaseOrderBinding() {
    
    

        //和普通队列绑定
        return new Binding("order.release.order.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.release.order",
                null);
    }

    @Bean
    public Binding orderReleaseOtherBinding() {
    
    

        //订单释放直接和库存释放进行绑定
        return new Binding("stock.release.stock.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.release.other.#",
                null);
    }
//
    @Bean
    public Queue orderSeckillOrderQueue() {
    
    
        return new Queue("order.seckill.order.queue", true, false, false);
    }

    @Bean
    public Binding orderSeckillOrderQueueBinding() {
    
    
        return new Binding("order.seckill.order.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.seckill.order",
                new HashMap<>());
    }
//
//    /**
//     * 下面全都是基础配置
//     */
//
    @Bean
    public MessageConverter messageConverter() {
    
    

        return new Jackson2JsonMessageConverter();
    }
//
//    /**
//     * 定制rabbitTemplate
//     * 1.publisher-confirms: true
//     * 3.消费端确认 (保证每个消息被正确消费 此时才可以braker删除这个消息)
//     * 1.默认是自动确认的 只要消息接收到  客户端自动确认服务端就要移除这个消息
//     * 问题 :
//     * 收到很多消息 自动回复给服务器ack 只有一个消息处理成功 宕机了 发现消息丢失
//     * 手动确认模式: 只要我们没有确认高随MQ 货物被签收 没有ack
//     * 消息就一直是unacked状态 即使Consumer宕机 消息不会丢失 会重新变成ready
//     * 2.如果签收
//     */
    @PostConstruct  //MyRabbitConfig对象创建完成以后执行这个方法
    public void initRabbitTemplate() {
    
    

        //设置确认回调 消息到了队列
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
    
    

            /**
             * 1、消息抵达服务器 ack=true
             * @param correlationData 当前消息唯一关联的数据 这个是消息唯一id
             * @param ack 消息是否成功收到
             * @param cause 失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
    
    
                //服务器收到了
                System.out.println("消息抵达服务器confirm....correlationData[" + correlationData + "]==>ack[" + ack + "]cause>>>" + cause);
            }
        });

        //设置消息队列的确认回调 发送了,但是队列没有收到
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
    
    

            /**
             * 只要消息没有投递给指定的队列 就触发这个失败回调
             * @param message  投递失败的消息详细信息
             * @param replyCode 回复的状态码
             * @param replyText 回复的文本内容
             * @param exchange 当时这个消息发给那个交换机
             * @param routingKey 当时这个消息用那个路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
    
    
                //报错误 未收到消息
                System.out.println("Fail!! Message[" + message + "]==>[" + exchange + "]==>routingKey[" + routingKey + "]");
            }
        });
    }
}

Механизм подтверждающих сообщений RabbitMQ, надежное сообщение

Отправитель: чтобы
гарантировать, что сообщение не потеряно и надежно доставлено, можно использовать сообщения транзакции, а производительность снижена в 250 раз.
По этой причине вводится механизм подтверждения
publisher confirmCallback для подтверждения того, что сообщение было доставлено брокеру.
издатель returnCallback. Если сообщение не доставлено в очередь,
срабатывают confirmCallback и returnCallback . Уже в классе конфигурации Добавлен внутри.

Надежный механизм подтверждения сообщения о прибытии

Конец приема:

  • Потребитель получает сообщение и успешно обрабатывает его и может ответить брокеру
    basic.ack для подтверждения; брокер удалит сообщение.
    basic.nack используется для отрицательного подтверждения, вы можете указать, отклоняет ли брокер это сообщение, вы можете использовать пакетную операцию
    basic.reject для отрицательного подтверждения, как и выше, но не пакетную операцию
  • По умолчанию, когда сообщение получено потребителем, потребитель будет удален из очереди брокера.
    Сообщение будет автоматически подтверждено по умолчанию, но если невозможно определить, было ли сообщение обработано или успешно обработано, мы можем включить ручной режим подтверждения.
    Обработка сообщения выполнена успешно, ack (), получите следующее сообщение, этот брокер сообщений удалит его.
    Ошибка обработки сообщения, nack () / reject (), повторно отправить другим для обработки или подтвердить после отказоустойчивой обработки.
    В сообщении не был вызван метод ack / nack (). Брокер считает, что сообщение обрабатывается и не будет доставлено другим. В это время клиент отключен, и сообщение не будет удалено брокером, но будет доставлено другим.
    Код получателя:
//这个类能接受hello-java-queue消息
@RabbitListener(queues = {
    
    "hello-java-queue"})
@Service("orderItemService")
public class OrderItemServiceImpl extends ServiceImpl<OrderItemDao, OrderItemEntity> implements OrderItemService {
    
    

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
    
    
        IPage<OrderItemEntity> page = this.page(
                new Query<OrderItemEntity>().getPage(params),
                new QueryWrapper<OrderItemEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 监听消息
     * queues 声明需要监听的所有队列
     * org.springframework.amqp.core.Message
     * <p>
     * 参数可以写一下类型
     * 1、Message message: 原生消息详细信息。头+体
     * 2、发送的消息的类型: OrderReturnReasonEntity content;
     * 3、Channel channel:当前传输数据的通道
     * <p>
     * Queue:可以很多人都来监听,只要收到消息,队列删除消息,而且只能有一个收到此消息
     * 1)、订单服务启动多个:同一个消息,只能有一个客户端收到
     * 2)、只有一个消息完全处理完,方法运行结束,我们就可以接收到下一个消息
     */
//    @RabbitListener(queues = {"hello-java-queue"})
//    这个类的这个方法才能接受hello-java-queue消息
    @RabbitHandler
    public void receiveMessage(Message message, OrderReturnReasonEntity content, Channel channel) {
    
    

        //拿到消息体
//        byte[] body = message.getBody();
        //拿到消息头
//        MessageProperties properties = message.getMessageProperties();

        System.out.println("接收到消息:" + content);

        //消息处理完 手动确认  deliveryTag在Channel内按顺序自增
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("deliveryTag->" + deliveryTag);

        try {
    
    
            if (deliveryTag % 2 == 0) {
    
    
                //确认签收 队列删除该消息 false非批量模式
                channel.basicAck(deliveryTag, false);
                 System.out.println("签收了货物");
            } else {
    
    
                //拒收退货 第三个参数 -> true:重新入队 false:丢弃
                channel.basicNack(deliveryTag, false, true);
                 System.out.println("没有签收货物");
            }
        } catch (IOException e) {
    
    
            //网络中断
        }
    }

Как обеспечить надежность сообщения - потеря сообщения, дублирование сообщения
1, потеря сообщения:

  • После отправки сообщение не дошло до сервера из-за проблем с сетью.
  • Хороший отказоустойчивый метод (try-catch) может вызвать сбои сети при отправке сообщений.После сбоя должен существовать механизм повтора, который можно записать в базу данных. Используйте обычное сканирование и повторную отправку
  • Сделайте хорошую запись в журнале, следует ли записывать статус каждого сообщения, полученного сервером.
  • Регулярно выполняйте повторную передачу.Если сообщение не было успешно отправлено, периодически принимайте неудачное сообщение сканирования базы данных для повторной передачи.
    Когда сообщение приходит на Broke, Broke должен записать сообщение на диск (постоянство), чтобы считаться успешным. В это время Broke не завершил сохранение, и машина отключена.
  • Публикация должна также добавить механизм подтверждения для подтверждения успешных сообщений и изменения статуса сообщений базы данных.
  • В автоматическом состоянии ACK потребитель получает сообщение, но сообщение не выполняется вовремя.
    Необходимо включить ручной ACK, и потребление будет снято только после того, как потребление будет успешным, а noAck будет обработано, если он не удастся или нет времени для обработки и повторного присоединения к команде.

2. Повторное употребление

  • Если потребление прошло успешно, то транзакционная девушка была отправлена. При подтверждении машина не работает, а подтверждение не выполняется. Сообщение брокера снова меняется с unack на готово и отправляется другим потребителям.
  • Если потребление не удается, сообщение автоматически отправляется снова из-за механизма повтора.
  • После успешного использования cak вылетает, сообщение меняется с unack на готово, и Broke повторно отправляет.
    Например, интерфейс потребительского бизнес-потребления должен быть разработан таким образом, чтобы он был идемпотентным.

рекомендация

отblog.csdn.net/u014496893/article/details/114104540