如何防止rabbitmq消息丢失

在我们使用mq的时候,我们发现了他的好处以及便捷性,但是mq中经常也会出现一些问题,比如mq的消息丢失、消息堆积、消息重复消费之类的问题,好在这些问题都有解决方法,而我们本节主要说的就是mq的消息丢失问题
首先我们要知道什么时候会丢失消息,在mq中总共有三个环节会丢失消息
一、生产者弄丢了数据
如果我们的生产者在中途传输过程出现问题导致了消息的丢失。这个时候我们应该怎么做,mq中提供了两种方式来防止生产者在传输的过程数据的丢失
1、事务
mq提供事务来保证消息的传输正确性,但是因为事务模式的方式性能会降低250%,因此在我们生产环境中基本上不会使用它。
2、confirm模式
confirm模式,在生产者那里设置开启confirm模式之后,你每次写的消息都会分配一个唯一的id,然后如果写入了rabbitmq中,rabbitmq会给你回传一个ack消息,告诉你说这个消息ok了。如果rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。
如何开启呢,我们这边主要使用的是spring boot进行开发的,所以我们只需要在配置文件中进行如下的配置

# 开启发送确认
spring.rabbitmq.publisher-confirms=true
# 开启发送失败退回
spring.rabbitmq.publisher-returns=true

同时我们还需要自己写代码去实现callback以及return接口,如下所示

public class TopicSend implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
//得初始化,不然无法回调
 @PostConstruct
	private void init() {
		// TODO Auto-generated method stub
		rabbitTemplate.setConfirmCallback(this);
		rabbitTemplate.setReturnCallback(this);
	}
 
 //这里是消息发送到exchange中,如果发送成功则是true,不然就是false
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (ack) {
            System.out.println("消息发送确认成功");
        } else {
            System.out.println("消息发送确认失败:" + cause);
        }
    }
 //这个是交换机发送到队列中的,如果发送失败就回调这个方法,如果成功就不回调
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
      
    }

其实实现这两个接口的作用就是当我们生产者发送失败的时候可以我们自己进行相应的业务处理。总结这个类的作用就是

  1. 如果消息没有到exchange,则confirm回调,ack=false 如果消息到达exchange,则confirm回调,ack=true
  2. exchange到queue成功,则不回调return change到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)

做了这一步我们就能确保生产者消息的不丢失

二、RabbitMQ弄丢了数据
可能有人会问mq怎么可能好端端的丢失数据啊,你想一想,如果你的服务器断电重启了,那么你发送在mq队列上的数据是不是丢失了呢。其实在好多异常情况下导致我们mq重启或者其他情况下会使数据丢失,所以为了应对这个方式,我们需要做的就是对mq里面的东西进行持久化。
首先能想到的就是队列,其实还有交换器以及消息,接下来我会说如何进行这些的持久化
1、首先队列持久化
我们知道我们要声明队列的时候我们通常都是
return new Queue(“队列名称”);
其实我们这个已经就是持久化了,因为我们spring boot中的mq默认这些都是持久化,如果你有兴趣,可以点进去看,发现这queue里面会进行this(name,true)的调用,当然我们自己也可以通过return new Queue(“队列名称”,true);来进行持久化得设置。false就是不持久化
2、交换器的持久化
其实也是一样的道理,默认都是持久化的,如下所示

 @Bean
  public FanoutExchange fanoutExchange(){
      return new FanoutExchange(EXCHANGE_NAME,false,false);   
  }

这个就是交换器的参数,durable就是是否持久化
在这里插入图片描述
3、消息的持久化
可能大家觉得,消息为什么要持久化呢?其实很好理解,队列只是载体,载体的东西也需要持久化,不然也不会有数据的呢。但是我们平常发送的时候都是直接

//这个怎么设置消息的持久化呢,好像没法设置吧
amqpt.convertAndSend(EXCHANGE_NAME,"fanout", "haha"+i);

其实我们发现这个方法的最后一个参数是message,但是我们可以传递这些,如果我们需要改的话就需要这样

Message message = MessageBuilder.withBody(("我们发送的消息内容存放在message的body里面"+i).getBytes()).build(); 
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT); 
amqpt.convertAndSend(EXCHANGE_NAME,"fanout", message);

这里我们发现setDeliveryMode方法,这个方法就是对消息进行是否持久化的设置,虽然默认是持久化的,因为那个setDeliveryMode参数为2,1为不是持久化
到这里为止我们的mq丢失的解决方法也说完了。
三、消费者消息丢失
主要是因为你消费的时候,刚消费到,还没处理,结果进程挂了,或者你的服务重启了,RabbitMQ认为你都消费了,这数据就丢了。或者消费者拿到数据之后挂了。但是mq任务你是消费了,但事实是没有消费的。
这个时候得用RabbitMQ提供的ack机制,也是一种处理完成发送回执确认的机制。如果MQ等待一段时间后你没有发送过来处理完成 那么RabbitMQ就认为你还没处理完,这个时候RabbitMQ会把这个消费分配给别的consumer去处理,消息是不会丢的。
要使用ack机制,我们首先需要在配置文件中讲默认自动确认改成手动确认,如下所示

# 开启ACK
spring.rabbitmq.listener.direct.acknowledge-mode=manual
spring.rabbitmq.listener.simple.acknowledge-mode=manual

当然我们还得在代码消费的过程自己动手进行反馈,不然我们的mq会一直以为我们没有消费消息,然后进行重发呢,所以我们需要在消费端进行如下所示的代码

@RabbitListener(queues="hello")
@Service
public class ReciveMessage {
	@RabbitHandler 
    public void receive(Channel channel, String json, Message message,@Headers Map<String,Object> map) throws Exception{
 try{
             //消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);//告诉mq我已经消费成功了
            System.out.println("receiver success");
        } catch (IOException e) {
            e.printStackTrace();
            //丢弃这条消息,如果最后一个参数为true表示返回进行重试,也就是说我们这条消息需要重新放到队列进行重试,false的话就是丢弃的意思
            //channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
            System.out.println("receiver fail");
        }
    }
}

//拒绝消息
//channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
//消息被丢失
//channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
//消息被重新发送
//channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
//多条消息被重新发送
//channel.basicNack(message.getMessageProperties().getDeliveryTag(), true, true);

到这里我们的三块的消息丢失都进行了相应的处理。

  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值