RabbitMQ消息丢失的场景以及解决办法

RabbitMQ消息丢失的场景以及解决办法

1. 生产者生产信息发送到RabbitMQ Server消息丢失场景

  1. 外界原因:网络问题等原因RabbitMQ Server端收不到消息

  2. 代码层面,配置层面,考虑不全导致消息丢失

  3. 解决方案:就是AMQP协议提供的事务机制:
    RabbitMQ客户端中Channel接口提供了几个事务相关的方法:
    channel.txSelect
    channel.txcommit
    channel.txRollback
    在生产者发送数据到RabbitMQ Server之前开启RabbitMQ事务channel.txSelect,然后发送消息;如果消息没有成功发送到RabbitMQ中,返回给生产者会收到异常报错,此时就可以回滚事务channel.txRollback,然后重新发送消息;如果发送消息成功,提交事务channel.txcommit

    //开启事务
    channel.txSelecttry{
    //这里发送消息
    }catch(Exception e){
    channel.txRollback
    //重新发送消息
    }
    //提交事务
    channel.txCommit
    

    问题来了,RabbitMQ事务机制(同步)一搞,一条消息发送之后会使发送端阻塞,等待RabbitMQ Server的响应,之后才能发送下一条消息,生产者生产消息的吞吐量和性能都会大大降低。
    幸运的是RabbitMQ提供了一个改进方案,发送方确认机制(publisher confirm):
    首先生产者通过调用channel.confirmSelect将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道的消息都会被指派一个唯一的id(从1开始),如果消息写入到RabbitMQ中,RabbitMQ会发送一个确认(Basic.Ack)给生产者(包含信息的唯一deliveryTag和multiple参数),说明消息给到了RabbitMQ。如果RabbitMQ没能处理这个消息,会回调你的nack接口,告诉你消息接收失败,你可以重试。而且可以结合这个机制,自己在内存里维护每个消息id的状态,如果超过一定时间还没有收到消息的回调,那么可以重发。每次发送完消息把消息先存起来,以便于RabbitMQ接受失败可以重新发送消息,建议采用KV存储,KV存储承载高并发能力高,性能好,但是要保证KV的高可用,单个有个缺点就是引入第三方中间件,复杂度升高。
    Confirm模式的三种实现:

    1. 串行Confirm模式:生产者每发送一条消息后,调用waitForConfirms()方法,等待broker端confirm,如果服务器端返回false或者超过时间内未返回,客户端进行消息重传
    2. 批量Confirm模式:生产者每发送一批消息后,调用waitForConfirms()方法,等待broker端confirm
    3. 异步Confirm模式:提供一个回调方法,broker confirm了一条或者多条消息后,生产者会回调这个方法

    事务机制和confirm机制最大的区别在于事务机制是同步的,你提交一个事务会阻塞在那里,但是confirm机制是异步的,发送这个消息可以继续发送下一个消息,然后RabbitMQ接收到消息之后会异·步回调你的一个接口通知你消息收到了。

2.RabbitMQ弄丢了数据
开启RabbitMQ的持久化,就是消息写入RabbitMQ之后就持久化到磁盘,哪怕是RabbitMQ自己挂了,恢复之后自动读取存储的数据即可,一般数据是不会丢失的,还有一种可能就是在持久化之前就丢失了,这样的话可以和生产者的confirm机制配合起来,只有消息被持久化到磁盘后,回传一个ack消息, 这样哪怕在RabbitMQ持久化之前消息丢失,生产者收不到ack,会重新发送消息。
持久化的两个步骤

  1. 创建queue的时候将其设置为持久化,这样可以保证RabbitMQ持久化queue的元数据,而不持久化queue里的数据。
  2. 发送消息的时候将消息的deliveryMode设置为2,就是将消息设置为持久化的,此时RabbitMQ就会将消息持久化到磁盘上
  3. 必须要同时设置这两个持久化才行,哪怕queue挂了,再次重启,也会从磁盘上重启恢复queue,恢复queue里的数据。

3.消息端丢失数据
刚消费到,还没有处理,结果进程就挂了,比如重启,RabbitMQ认为你都消费了,这数据就丢了
这个时候就得用到RabbitMQ提供的ack机制了,简单的来说,就是你必须关闭RabbitMQ的自动ack,设置成手动ack( channel.basicConsume 方法中第二个参数为boolean 类型,意思是消息的ack 需要自动true,还是手动false),保证正确消费后给回馈,说明正常消费了这时候队列就可以把这条消息删除了。如果没有返回ack应答,那么这条信息就会继续存在unacked状态下,占据队列的空间,等空间满了,就出现消息不能被消费,如果消费过程中异常了,需要channel.basicNack方法,有两个种处理方法,第一,这条消息重新放回队列重新消费。第二,抛弃此消息。根据捕捉异常类型,在做处理。

你可能感兴趣的:(Java小白的成长之路)