一,rabbitMQ消息的传递过程及可能发生丢失的地方
消息从生产者产生,然后发生给rabbitMQ服务器,再发送给对应的消费者。
在这个过程中,生产者发生消息的时候,可能会因为网络等问题,导致消息丢失了,也就是①的地方
也可能rabbitMQ服务器挂掉了,造成的消息丢失,也就是②的地方
也可能因为服务器发送消息给客户端,或者客户端出现错误(拿到消息后并没有正确完整地处理完对应的业务)而导致出错,也就是③
最后,③这个地方说一下,因为rabbitMQ是把消息发送给消费者之后,它就会删除掉对应的消息,所以消费者如果处理消息出问题了,消息是会丢失掉的。
二,对应的解决方案
①情况下的解决方法:
解决方法1:使用rabbitMQ自带的事务功能,当发生错误的时候回滚,然后根据错误来选择对应的操作。在生产者在发送数据之前开启事务,然后发送消息,如果消息没有成功被rabbitmq接收到,那么生产者会受到异常报错,这时就可以回滚事务,然后尝试重新发送;如果收到了消息,那么就可以提交事务。
@RestController
public class DirectSendMessageController {
@Autowired
RabbitTemplate rabbitTemplate; //使用RabbitTemplate,这提供了接收/发送等等方法
@GetMapping("/sendDirectMessage")
public String sendDirectMessage(Channel channel) throws Exception {
//方法按参数如下:
//exchange:交换机名称,也就是我们在配置时候,指定的交换机名称
//routingKey:路由键,指定这个消息的路由键
//object:具体的消息内容
//将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
//开启事务
channel.txSelect();
try{
rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", "hello,i an message");
//没有问题,则提交事务
channel.txCommit();
}catch (Exception e){
//如果发送消息的时候,出现异常或者错误
//事务回滚
channel.txRollback();
}
return "ok";
}
}
缺点:rabbitmq事物已开启,就会变为同步阻塞操作,生产者会阻塞等待是否发送成功,太耗性能会造成吞吐量的下降。
解决方法2:开启confirm模式
在生产者将信道设置成Confirm模式,一旦信道进入Confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(以confirm.select为基础从1开始计数),一旦消息被投递到所有匹配的队列之后,Broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了。Confirm模式最大的好处在于它是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条basic.nack来代替basic.ack的消息。
@RestController
public class DirectSendMessageController {
@Autowired
RabbitTemplate rabbitTemplate; //使用RabbitTemplate,这提供了接收/发送等等方法
@GetMapping("/sendDirectMessage")
public String sendDirectMessage(Channel channel) throws Exception {
//方法按参数如下:
//exchange:交换机名称,也就是我们在配置时候,指定的交换机名称
//routingKey:路由键,指定这个消息的路由键
//object:具体的消息内容
//将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
//开启confirm模式
channel.confirmSelect();
for(int i=0;i<5;i++){
//发送5次消息
rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", "hello,i an message");
}
//此方法可以得到rabbitMQ服务器返回的结果
//也可以调用waitForConfirmsOrDie这个方法,这个方法会等到最后一条信息发生完才返回结果,会造成阻塞
if(channel.waitForConfirms()){
//发送成功
}else{
//可以选择重新发生或者其他操作
}
channel.close();
return "ok";
}
}
注意,confirm模式和事务只能二选一,不能两个一起开启。
解决方法3, 回调函数
@Component
public class CallBackSender implements RabbitTemplate.ConfirmCallback {
@Autowired
private RabbitTemplate rabbitTemplatenew;
public void send() {
rabbitTemplatenew.setConfirmCallback(this);
String msg = "callbackSender : i am callback sender";
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
System.out.println("callbackSender UUID: " + correlationData.getId());
this.rabbitTemplatenew.convertAndSend("exchange", "topic.messages", msg, correlationData);
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
//可以在这个回调函数里,根据ack的值来判断消息是否发送成功
System.out.println("callbakck confirm: " + correlationData.getId() + " ACK : " + ack);
}
}
②情况下的解决方法:
如果是在rabbitMQ服务器下消息丢失了,可以做一个消息的持久化。注意,队列和交换机的持久化是消息持久化的前提,具体持久换配置是在队列,交换机和消息创建时候的参数设置的,具体可以到前边的文章里参考,这里不再贴代码了
③情况下的解决方法:
方法1,ack确认机制。
默认情况下,rabbitMQ默认是自动提交的,也就是说,rabbitMQ服务器把消息发送到客户端之后,rabbitMQ服务器就会删除这条消息,这样子,消费者若是出现错误异常情况,消息就丢失了。
我们只需要把自动提交改成手动提交即可。
第二个参数设为true为自动应答,false为手动ack
channel.basicConsume("队列名", false, new DefaultConsumer(channel)