RabbitMQ reliable message delivery

Introduce maven

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

Add configuration:

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

Add configuration class

/**
 * 运行之前,一定要小心,否则要删除队列/交换机重新运行 麻烦!
 *
 * 解决消息丢失(最怕)
 *  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 confirmation message mechanism, reliable message

Sender: To
ensure that the message is not lost and arrive reliably, transaction messages can be used, and the performance is reduced by 250 times.
For this reason, the confirmation mechanism
publisher confirmCallback is introduced to confirm whether the message has arrived at the broker
publisher returnCallback. If the message is not delivered to the queue, the
confirmCallback and returnCallback are triggered. Already in the configuration class Added inside.

Reliable arrival-Ack message confirmation mechanism

Receiving end:

  • The consumer obtains the message and processes it successfully, and can reply ack to broker
    basic.ack for affirmative confirmation; the broker will remove the message.
    basic.nack is used for negative confirmation, you can specify whether the broker discards this message, you can batch operation
    basic.reject for negative confirmation, the same as above, but not batch operation
  • By default, when the message is received by the consumer, the consumer will be removed from the queue of the broker. The
    message will be automatically acked by default, but if it is impossible to determine whether the message has been processed or successfully processed, we can turn on the manual ack mode .
    Message processing is successful, ack(), receive the next message, this message broker will remove it.
    Message processing fails, nack()/reject(), resend it to others for processing, or ack after fault-tolerant processing.
    The message has not called the ack/nack() method. The broker believes that the message is being processed and will not be delivered to others. At this time, the client is disconnected and the message will not be removed by the broker, but will be delivered to others.
    Receiver code:
//这个类能接受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) {
    
    
            //网络中断
        }
    }

How to ensure message reliability-message loss, message duplication
1, message loss:

  • After the message was sent, it did not reach the server due to network problems.
  • A good fault-tolerant method (try-catch) may cause network failures when sending messages. After the failure, there must be a retry mechanism that can be recorded in the database. Use regular scanning and resending
  • Make a good log record, and record whether the status of each message has been received by the server.
  • Do a good job of retransmitting regularly. If the message is not sent successfully, periodically take the unsuccessful message of the database scan and retransmit it.
    When the message arrives at Broke, Broke has to write the message to disk (persistence) to be considered successful. At this time, Broke has not completed the persistence, and the machine is down.
  • Publish must also add a confirmation mechanism to confirm successful messages and modify the status of database messages.
  • In the automatic ACK state, the consumer receives the message, but the message fails in time.
    Manual ACK must be turned on, and the consumption will be removed only after the consumption is successful, and noAck will be processed if it fails or there is no time to process and re-join the team.

2. Repeated consumption

  • If the consumption is successful, the transaction girl has been submitted. When ack, the machine is down, and the ack is not successful. Broker's message is changed from unack to ready again, and sent to other consumers
  • If the consumption fails, the message is automatically sent out again due to the retry mechanism.
  • After successful consumption, the cak crashes, the message changes from unack to ready, and Broke resends.
    The consumer business consumption interface should be designed to be idempotent, for example.

Guess you like

Origin blog.csdn.net/u014496893/article/details/114104540