在咱们使用mq的时候,咱们发现了他的好处以及便捷性,可是mq中常常也会出现一些问题,好比mq的消息丢失、消息堆积、消息重复消费之类的问题,好在这些问题都有解决方法,而咱们本节主要说的就是mq的消息丢失问题
首先咱们要知道何时会丢失消息,在mq中总共有三个环节会丢失消息
1、生产者弄丢了数据
若是咱们的生产者在中途传输过程出现问题致使了消息的丢失。这个时候咱们应该怎么作,mq中提供了两种方式来防止生产者在传输的过程数据的丢失
一、事务
mq提供事务来保证消息的传输正确性,可是由于事务模式的方式性能会下降250%,所以在咱们生产环境中基本上不会使用它。
二、confirm模式
confirm模式,在生产者那里设置开启confirm模式以后,你每次写的消息都会分配一个惟一的id,而后若是写入了rabbitmq中,rabbitmq会给你回传一个ack消息,告诉你说这个消息ok了。若是rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,你能够重试。并且你能够结合这个机制本身在内存里维护每一个消息id的状态,若是超过必定时间还没接收到这个消息的回调,那么你能够重发。
如何开启呢,咱们这边主要使用的是spring boot进行开发的,因此咱们只须要在配置文件中进行以下的配置java
# 开启发送确认
spring.rabbitmq.publisher-confirms=true
# 开启发送失败退回
spring.rabbitmq.publisher-returns=true
同时咱们还须要本身写代码去实现callback以及return接口,以下所示web
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) {
}
其实实现这两个接口的做用就是当咱们生产者发送失败的时候能够咱们本身进行相应的业务处理。总结这个类的做用就是spring
若是消息没有到exchange,则confirm回调,ack=false 若是消息到达exchange,则confirm回调,ack=true
exchange到queue成功,则不回调return change到queue失败,则回调return(需设置mandatory=true,不然不回回调,消息就丢了)
作了这一步咱们就能确保生产者消息的不丢失json
2、RabbitMQ弄丢了数据
可能有人会问mq怎么可能好端端的丢失数据啊,你想想,若是你的服务器断电重启了,那么你发送在mq队列上的数据是否是丢失了呢。其实在好多异常状况下致使咱们mq重启或者其余状况下会使数据丢失,因此为了应对这个方式,咱们须要作的就是对mq里面的东西进行持久化。
首先能想到的就是队列,其实还有交换器以及消息,接下来我会说如何进行这些的持久化
一、首先队列持久化
咱们知道咱们要声明队列的时候咱们一般都是
return new Queue(“队列名称”);
其实咱们这个已经就是持久化了,由于咱们spring boot中的mq默认这些都是持久化,若是你有兴趣,能够点进去看,发现这queue里面会进行this(name,true)的调用,固然咱们本身也能够经过return new Queue(“队列名称”,true);来进行持久化得设置。false就是不持久化
二、交换器的持久化
其实也是同样的道理,默认都是持久化的,以下所示服务器
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange(EXCHANGE_NAME,false,false);
}
这个就是交换器的参数,durable就是是否持久化
三、消息的持久化
可能你们以为,消息为何要持久化呢?其实很好理解,队列只是载体,载体的东西也须要持久化,否则也不会有数据的呢。可是咱们日常发送的时候都是直接ide
//这个怎么设置消息的持久化呢,好像无法设置吧
amqpt.convertAndSend(EXCHANGE_NAME,"fanout", "haha"+i);
其实咱们发现这个方法的最后一个参数是message,可是咱们能够传递这些,若是咱们须要改的话就须要这样svg
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丢失的解决方法也说完了。
3、消费者消息丢失
主要是由于你消费的时候,刚消费到,还没处理,结果进程挂了,或者你的服务重启了,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会一直觉得咱们没有消费消息,而后进行重发呢,因此咱们须要在消费端进行以下所示的代码ui
@RabbitListener(queues="hello")
@Service
public class ReciveMessage {
@RabbitHandler
public void receive(Channel channel, String json, Message message,@Headers Map 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);
到这里咱们的三块的消息丢失都进行了相应的处理。this