RabbitMq(6) 如何保证消息不丢包

RabbitMQ一般情况很少丢失,但是不能排除意外,为了保证我们自己系统高可用,我们必须作出更好完善措施,保证系统的稳定性。

下面来介绍下,如何保证消息的绝对不丢失的问题,下面分享的绝对干货,都是在知名互联网产品的产线中使用。

1.消息持久化

2.ACK确认机制

3.设置集群镜像模式

4.消息补偿机制

一、消息持久化

RabbitMQ 的消息默认存放在内存上面,如果不特别声明设置,消息不会持久化保存到硬盘上面的,如果节点重启或者意外crash掉,消息就会丢失。

所以就要对消息进行持久化处理。如何持久化,下面具体说明下:

要想做到消息持久化,必须满足以下三个条件,缺一不可。

1)Exchange 设置持久化

2)Queue 设置持久化

3)Message持久化发送:发送消息设置发送模式deliveryMode=2,代表持久化消息

 Exchange 设置持久化

    @Bean
    TopicExchange exchange() {
        return new TopicExchange("topicExchange");
    }

默认的Topic交换机,持久化属性为true。

改为:

return new TopicExchange("topicExchange",false,false);  

 执行下:

报错:

inequivalent arg 'durable' for exchange 'topicExchange' in vhost '/': received 'false' but current is 'true'

看下当前topic交换机属性:

好像没找到线上切换交换机持久化属性的入口,有知道的大神可以告知下,我这里是删除掉原来的而交换机,再次执行:

此时重启RabbitMq服务,未持久化的交换机消失了。。。。

 

Queue 设置持久化

    @Bean
    public Queue queueArm() {
        return new Queue(TopicRabbitConfig.ARM_QUEUE);
    }

源码:

如果改为非持久化,也是先删除,再执行。非持久化后,重启服务,队列就没了。

 Message持久化发送

 RabbitTemplate.class

MessageProperties.class

默认是持久化。

二、ACK确认机制

 多个消费者同时收取消息,比如消息接收到一半的时候,一个消费者死掉了(逻辑复杂时间太长,超时了或者消费被停机或者网络断开链接),如何保证消息不丢?

这个使用就要使用Message acknowledgment 机制,就是消费端消费完成要通知服务端,服务端才把消息从内存删除。

这样就解决了,及时一个消费者出了问题,没有同步消息给服务端,还有其他的消费端去消费,保证了消息不丢的case。

demo:

package com.example.demo.rabbitMq.ack;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitTemplateConfig {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private AmqpTemplate rabbitTemplate;

    /**
     * 定制化amqp模板
     * ReturnCallback接口用于实现消息发送到RabbitMQ 交换器,但无相应队列与交换器绑定时的回调  即消息发送不到任何一个队列中  ack
     * ConfirmCallback接口用于实现消息发送到RabbitMQ交换器后接收ack回调   即消息发送到exchange  ack
     *
     * @return
     */
    @Bean("rabbitTemplateAck")
    public AmqpTemplate getabbitTemplate() {
        RabbitTemplate rabbitTemplateAck = (RabbitTemplate) this.rabbitTemplate;
        rabbitTemplateAck.setMandatory(true);

        //消息返回, 需要配置spring.rabbitmq.publisher-returns=true
        rabbitTemplateAck.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange,
                                        String routingKey) {
                String correlationId = message.getMessageProperties().getCorrelationId();
                logger.debug("消息:{} 发送失败, 应答码:{} 原因:{} 交换机: {}  路由键: {}", correlationId, replyCode, replyText,
                        exchange, routingKey);
            }
        });


        // 消息确认, 需要配置 spring.rabbitmq.publisher-confirms=true
        rabbitTemplateAck.setConfirmCallback((correlationData, ack, cause) -> {
            if (ack) {
                logger.debug("消息发送到exchange成功");
            } else {
                logger.debug("消息发送到exchange失败,原因: {}", cause);
            }
        });

        return rabbitTemplateAck;
    }
}
   @Test
    public void send4() throws Exception {
        topicSender.send4();
    }

测试:确认发送到交换机了:

 

修改发送路由,如下:

 rabbitTemplate.convertAndSend("topicExchange","1arm.gun",list);

再次执行,结果如下:

再看demo:

生产者新增交换机和队列

public static final String BYTE_QUEUE = "byte.queue";
    @Bean
    public Queue queueByte() {
        return new Queue(TopicRabbitConfig.BYTE_QUEUE);
    }
    @Bean
    Binding bindingExchangeByte(Queue queueByte, TopicExchange exchange) {
        return BindingBuilder.bind(queueByte).to(exchange).with("byte.#");
    }
TopicSender.java
public void send5() throws IOException {
        User user = new User();
        user.setUserName("Sender1.....");
        user.setMobile("555555555");
        byte[] body = Base64Utils.obj2byte(user);
        Message message = new Message(body,new MessageProperties());
        rabbitTemplate.convertAndSend("topicExchange","byte.message",message);
    }

消费者工程:

package com.example.demo.rabbitMq.ack;

import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitTemplateConfig {
    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        return factory;
    }
}
package com.example.demo.rabbitMq.exchange.topic;

import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class TopicReceiver5 {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

   @RabbitListener(queues = TopicRabbitConstant.BYTE_QUEUE, containerFactory="rabbitListenerContainerFactory")
    public void process(Message message, Channel channel) throws IOException {
        try{
            System.out.println("Receiver5  : " + message);
            //int i = 6/0;

        }catch (Exception e){
            if (!message.getMessageProperties().getRedelivered()) {
                System.out.println("消息即将再次返回队列处理...");
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            } else {
                //requeue为是否重新回到队列
                System.out.println("消息已重复处理失败,拒绝再次接收...");
                //BasicReject方法第一个参数是消息的DeliveryTag,对于每个Channel来说,每个消息都会有一个DeliveryTag
                //第二个参数是是否放回queue中,requeue,如果只有一个消费者的话,true将导致无限循坏
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            }
        }
    }
}

执行:

如果人造异常,将会走黄色部分,最后因为重复请求而拒绝再次放入队列。

三、设置集群镜像模式

参考:https://www.cnblogs.com/flyrock/p/8859203.html

四、消息补偿机制

参考:https://www.cnblogs.com/flyrock/p/8859203.html

https://www.cnblogs.com/milicool/p/9662447.html

猜你喜欢

转载自www.cnblogs.com/xiaozhuanfeng/p/10719874.html