今天的面试题;

如何保证RabbitMQ消息99%的发送成功;
看了就能懂!

  1. 生产者弄丢了数据

生产者将数据发送到MQ的过程中,数据被搞丢了,或许是网络啥的原因;

rabbitMQ提供的事务功能,生产者发送数据之间开启rabbitMQ事务,然后发送消息,如果消息没有成功被rabbitMQ接收到,生产者会收到异常报错,此时就可以回滚事务,然后重试发送消息;如果收到了消息,那就可以提交事务。但问题是,rabbitMQ事务机制一搞,基本上吞吐量会下来,因为太耗性能;

一般来说,如果你要确保说写rabbitMQ的消息别丢,可以开启confirm模式,在生产者那里设置开启confirm模式之后,每次写的消息都会分配一个唯一的ID,然后如果写入了rabbitMQ中,rabbitMQ会给你回传一个ack消息,告诉你说这个消息OK了,如果rabbitMQ没能处理这个消息,会回调你的一个nack接口,告诉你消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息ID的状态,如果超时一定时间还没有接收这个消息的回调,那么你可以重发;

事务机制和cnofirm机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是confirm机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息rabbitMQ接收了之后会异步回调你一个接口通知你这个消息接收到了。

所以一般在生产者这块避免丢失数据,都是用confirm机制的;

  1. MQ弄丢了数据

就是rabbitMQ自己弄丢了数据,这个你必须开启rabbitMQ的持久化,就是消息写入之后会持久化到磁盘,哪怕是rabbitMQ自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。除非极其罕见的是,rabbitMQ还没持久化,自己就挂了,可能导致少量数据会丢失的,但是这个概率较小。

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

而且持久化可以跟生产者那边的confirm机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者ack了,所以哪怕是在持久化到磁盘之前,rabbitMQ挂了,数据丢了,生产者收不到ack,你也是可以自己重发的。

哪怕是你给rabbitMQ开启了持久化机制,也有一种可能,就是这个消息写到了rabbitMQ中,但是还没来得及持久化到磁盘上,结果不巧,此时rabbitMQ挂了,就会导致内存里的一点点数据会丢失。

  1. 消费者弄丢了数据

rabbitMQ如果丢失了数据,主要是因为你消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,那么就尴尬了,rabbitMQ认为你都消费了,这数据就丢了。

这个时候得用rabbitMQ提供的ack机制,简单来说,就是你关闭rabbitMQ自动ack,可以通过一个api来调用就行,然后每次你自己代码里确保处理完的时候,再程序里ack一把。这样的话,如果你还没处理完,不就没有ack?那rabbitMQ就认为你还没处理完,这个时候rabbitMQ会把这个消费分配给别的consumer去处理,消息是不会丢的。

RabbitMQ的高可用实现;

基于主从下才谈高可用,她有3种模式:单机、普通集群、镜像集群模式 分别说明。

  1. 单机模式

自己玩,生产不会用到;

  1. 普通集群模式

多台机器启动多个RabbitMQ实例,每台机器启动一个。但是你创建的Queue,只存放在一个RabbitMQ实例上,其他实例都同步Queue的元数据,紧接着你消费的时候,若你连接到另外一个实例上,那么被连接的实例会从Queue所在的实例上拉取数据过来。

这种方式怎么想都感觉不理想,没做到真正的分布式。导致你每次随机连接一个额实例然后拉取数据,要么固定连接Queue所在的实例消费数据,前者有数据拉取的开销,后者有单实例的瓶颈,都不妥。若存放Queue的实例宕机,其他实例想来拉取数据也是不可能的。

选择开启持久化,让数据落盘,消息丢不了,但也得等实例恢复才能继续从Queue中拉数据。就没有所谓高可用性了;

  1. 镜像集群模式

你创建的Queue,无论是元数据还是Queue里的消息都会存在于多个实例上,然后每次你写消息到Queue的时候,都会自动把消息发送到多个实例的Queue中进行消息同步。

好处就是任何实例宕机,影响不大依然可用。坏处是开销大,消息同步所有机器,导致网络带宽压力和消耗严重!其次没有扩展性可言;若某个Queue负载很重,加机器也会包含这个Queue所有数据,并没有办法线性扩展你的Queue;