1.1介绍一下RabbitMQ

MQ全程是Message Queue,即消息队列,RabbitMQ是由erlang语言开发,基于AMQP协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统中应用非常广泛。在开发中,消息队列主要有以下的应用场景:
(1)任务异步处理:将不需要同步处理的并且耗时较长的操作由消息队列通知消息接收方进行异步处理,提高应用程序的响应速度。
(2)接口解耦:MQ相当于是一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦。

1.2为什么使用RabbitMQ?

(1)使用简单,功能强大
(2)基于AMQP协议
(3)社区活跃,文档完善
(4)高并发性能好,这主要得益于Erlang语言
(5)SpringBoot默认已集成RabbitMQ

1.3说说什么是AMQP?

RabbitMQ就是AMQP协议的Erlang语言的实现,AMQP的模型架构和RabbitMQ的模型架构基本一致,生产者将消息发送给交换器,交换器和队列绑定。RabbitMQ中的交换器、交换器类型、队列、绑定、路由键等都是遵循AMQP协议中的相应的概念。
【三层协议】:
(1)Module Layer:模型层,主要定义了一些客户端调用的命令,客户端可以使用这些命令实现自己的业务逻辑。
(2)Session Layer:会话层,主要将负责客户端命令发送给服务器,再将服务器应答返回给客户端,提供可靠性同步机制和错误处理。
(3)Transport Layer:传输层,主要传输二进制数据流,提供帧的处理、信道复用、错误检测和数据表示等。
【组件】
(1)交换器:消息代理服务器中用于把消息根据路由键路由到队列中的组件。
(2)队列:用来存储消息的数据的结构,可存储在磁盘或者内存中,消息到达队列中会转发给指定的消费方。
(3)绑定:一套规则,告知交换器应该将消息投递到哪个队列中。
(4)Broker:消息队列服务进程,主要包括Exchange和Queue。
image.png

1.4说说RabbitMQ一共有几种工作模式?

(1)工作队列模式:
1..一个生产者将消息发给一个队列
2.多个消费者共同监听一个队列的消息,消息不能被重复消费,rabbit采用轮询的方式将消息平均发送给消费者,只能有一个消费者消费这个消息。
image.png
(2)发布订阅模式:(共享资源)
image.png
1.生产者指定一个交换机,将消息发送给交换机
2.与交换机绑定的有多个队列,每个消费者监听自己的队列
3.交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。
应用场景:比如用户充值完成后,通过手机短信和邮箱两种方式通知给用户。
(3)路由模式:
image.png
1.生产者指定一个交换机。
2.一个交换机绑定多个队列,每个队列设置路由Key,并且一个队列可以设置多个路由Key。
3.每个消费者监听自己的队列。
4.生产者将消息发给交换机,发送消息时需要指定路由Key,交换机根据生产者指定的路由Key判断是否与自己绑定的队列相等,来判断应该把消息发送给哪个队列。
(4)通配符模式:(路由模式的一种)
image.png
1.井号和星号代表通配符
2.井号可以匹配一个或多个单词,星号可以匹配一个单词。
打个比方,inform.# 可以匹配inform.sms、inform.email、inform.user.sms
而inform.* 可以匹配inform.sms、inform.email,不能匹配inform.user.sms
3.路由功能可以允许通配符匹配,队列在绑定交换机的时候可以设置路由Key。生产者在给交换机发送消息的时候可以按照通配符的规则来指定路由Key,交换机根据通配符路由Key来转发到具体的队列。
应用场景:根据用户的通知设置去通知用户,有的用户只设置Email、有的用户只配置SMS,有的用户两种都设置。
(5)RPC模式:
image.png
RPC是客户端远程调用服务端的方法,使用MQ可以实现RPC的异步调用,基于Direct(路由)交换机,
1.客户端既是生产者又是消费者,向RPC请求队列发送RPC调用消息,同时监听RPC响应队列
2.服务端监听RPC请求队列的消息,收到消息后执行服务端的方法,得到方法返回的结果
3.服务端将RPC方法的结果发送到RPC响应队列中

1.5说说RabbitMQ的构造

image.png
(1)生产者:投递消息的一方,消息一般包含两个部分:消息体(payLoad)和标签(Label)。
(2)消费者:消费消息的一方。消费者连接到RabbitMQ服务器,并监听队列。消费时只消费消息体,丢弃标签。
(3)Broker服务节点:表示消息队列服务器。一个Broker可以看做一个RabbitMQ服务器。
(4)Queue消息队列:用来存放消息。一个消息可投入到一个或多个队列,对个消费者可以同时监听这个队列,这时队列会轮询给每个消费者进行处理。
(5)Exchange交换机:用来接收生产者投递的消息,根据路由Key将消息路由到绑定的队列上。
(6)Routing Key路由关键字:用于指定这个消息的路由规则,需要与交换器类型和绑定键(Binding Key)联合使用才能最终生效。
(7)Binging绑定:通过绑定将交换机和队列关联起来。
(8)Connection网络连接:生产者和消费者都需要与Broker通过TCP三次握手建立连接。
(9)Channel信道:AMQP命令都是在信道中进行的,不管是发布消息、订阅队列还是接收消息。因为TCP建立连接和断开连接都是非常昂贵的开销,所以引入了信道的概念,以复用一条TCP连接,一个TCP连接可以用使用多个信道。客户端可以创建多个channel,每个channel表示一个会话任务。
(10)Message消息:由消息头和消息体组成。消息头是由可选属性组成的,主要包括routingKey、priority(相对于其他消息的优先级)、delivery-mode(消息是否需要持久化存储)。
(11)Virtual host虚拟机:主要是用于逻辑隔离,表示一批独立的交换器、消息队列和相关对象。一个虚拟机可以有若干个Exchange和Queue,同一个Virtual host不同有同名的Exchange或Queue。

1.6RabbitMQ 概念里的 channel、exchange 和 queue 是逻辑概念,还是对应着进程实体?分别起什么作用?

(1)queu具有自己的erlang进程。
(2)exchange内部实现是保存绑定关系的查找表。
(3)channel是实际进行路由工作的实体,负责按照rounting_key将message投递到queue中。
由AMQP协议描述可知,channel是真实TCP连接之上的虚拟连接,所有AMQP命令都是通过channel发送的,且每一个channel有唯一标识的ID号。一个channel只能被单独一个操作系统线程使用,所以投递到channel上的message是有顺序的。但一个操作系统线程上允许使用多个channel。

1.7消息基于什么传输?

由于TCP连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。所以RabbitMQ使用信道的方式来传输数据。信道是建立在真实的TCP连接内的虚拟连接,且每条TCP连接上的信道数量没有限制。

1.8生产者消息如何投递?

(1)Producer先连接到Broker,建立Connection,开启一个信道Channel。
(2)Producer声明一个交换器并设置好相关属性。比如交换器的名称、交换器的类型。 fanout发布订阅模式、direct路由模式、topic通配符模式、headers工作模式。
(3)Producer声明一个队列并设置好相关属性。比如队列的名称、是否持久化。
(4)Producer通过绑定键将交换器和队列绑定起来。
(5)Producer投递消息发送到Broker,其中包含交换机、路由键、消息体和附加属性。
(6)交换器根据接收到的路由键查找相匹配的队列。如果找到,将消息转发到相应的队列;如果没有找到,根据生产者的配置丢弃或者退回给生产者。
(7)关闭信道。
(8)管理连接。

1.9消费者如何接受消息?

(1)消费者先连接到Broker,创建Connection,创建一个信道Channel。
(2)消费者监听某个队列,等待队列中消息的到达。
(3)消息到达队列后,消费者接收消息并回复ack给Broker。
(4)RabbitMQ从队列中删除已经确认的消息。
(5)关闭信道。
(8)关闭连接

1.10交换器无法根据自身类型和路由键以及绑定键找到符合条件的队列,怎么办?

直接抛弃或者返回消息给生产者。

1.11如何确保消息正确地发送到RabbitMQ?

RabbitMQ使用发送方确认模式,确保消息正确地发送到RabbitMQ。
(1)将信道设置成confirm模式(发送方确认模式),所有在信道上发布的消息都会被指派一个唯一的ID。
(2)一旦消息被投递到目标队列后,或者消息被写入磁盘后(可持久化的消息),信道会返回一个ack给生产者(包含消息唯一ID)。
(3)如果RabbitMQ因为内部发生错误而导致消息丢失,会发送一条nack(not ack)未确认消息给生产者。
(4)发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生成者应用程序,生产者的回调函数就会被触发来处理确认消息。

1.12如何保证RabbitMQ不会丢失消息?

如果消息非常重要,并且不允许丢失的话,可以使用消息持久化机制配合confirm发送方确认模式一起使用,在消息持久化到硬盘后,才会给发送方回复ack确认信号。如果RabbitMQ在消息持久化到磁盘之前宕机了,发送方收不到ack信号,生产者就会自动重新发送消息。并且因为这个消息是具有唯一id标识的,即使之前如果是还没来得及回复ack就挂掉了,也能保证该消息不会被MQ重复接收。MQ宕机重启后,能够从磁盘中重新恢复数据。

1.13如何保证消息接收方消费了消息?

RabbitMQ使用接收方确认消息机制,手动回复确认消息
(1)消费者接收每一条消息后都必须进行确认(消息接收和消息确认不是一个概念,消费者可以在消息被消费后再返回给Broker确认消息,保证自己已经消费了这条消息)。
(2)只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除,另外RabbitMQ通过消费者是否连接中断来确认是否需要重新发送消息。
(3)也就是说,只要连接不中断,RabbitMQ给了消费者足够长的时间来处理消息。
特殊情况:
(1)如果消费者接收到消息,在确认之前断开了连接或者不再监听这个队列,RabbitMQ会认为消息没有被分发,然后重新分发给下一个订阅该队列的消费者。(这里存在消息被重复消费的隐患)
(2)如果消费者接收到消息但没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多的消息。

1.14如何保证消息的可靠性?

(1)生产者到RabbitMQ:发送方确认机制、事务机制,但是这两个互斥。
(2)RabbitMQ本身:持久化、集群、普通模式、镜像模式
(3)RabbitMQ到消费者:接收方确认机制、死信队列、消息补偿机制。

1.15如何保证RabbitMQ的消息不被重复消费?

在正常情况下,消费者在处理完消息后,会发送一个确认ack给消息队列,消息队列就知道该消费者已经处理完消息,可以直接把该消息从队列中删除了。但是因为网络传输等等故障,确认消息没有传送到消息队列,导致消息队列不知道消费者是否已经处理完消息,然后再次将消息分发给其他的消费者。
【解决方案】:
(1)在消息生产时,MQ内部对每条生产者发送的消息生成一个inner-msg-id,作为去重的依据,避免重复的消息进队列。
(2)如果问题出在消费者端的话,需要结合业务场景来思考:
1.如果是拿个消息需要写入数据库,先根据主键查询一下,如果这条数据已经存在了,就别插入消息了。
2.如何是要拿数据写入到Redis中,这种情况稍微乐观点,因为Redis即使多次set同一个数据,数据也能保证唯一性。
3.让生产者发送每条数据的时候,里面增加一个全局唯一的ID(类似于订单ID),到了消费者这里,先去Redis中查询一下是否已经消费过了,如果没有消费过,就根据id写入redis中。如果已经消费过了,那就别处理了。

1.16如何保证RabbitMQ消息的顺序性?

image.png
保证消息的顺序性,就必须从三个方面来保证,发送消息的顺序、队列中消息的顺序、消费消息的顺序。
【发送消息的顺序性】
如果业务场景中要求一定要保证消息被顺序执行,那就必须先保证消息投递方,能够顺序得投递消息,不能开多线程并发投递消息。并且发送到RabbitMQ后,RabbitMQ必须将消息顺序性能转发到相应的队列中,如果是add、update、delete这三个操作,就必须得转发到同一个队列中。
【队列中消息的顺序】
在RabbitMQ中,消息最终会保存在队列中,同一个队列消息是顺序的,先进先出原则的。这个不需要开发者来担心。
【消费消息的顺序】
在多个消息者监听同一个队列的场景下,是不能保证消息被顺序消费的,因为RabbitMQ采用轮询的方式将消息转发给消费者。如果是三个消费者同时监听了同一个队列,这三个消费者并发执行add、delete、update的逻辑,就无法保证哪个消息先被消费,哪个消息后被消费。所以这种方式是不行的,要改善的话,就必须得是一个消费者监听一个队列。多个消费者监听不同的队列。然后一个一个得从队列中取出然后再去从处理消息。

1.17什么是元数据?元数据分为哪些类型?

在非Cluster模式下,元数据主要分为
(1)Queue元数据,队列名称和属性等。
(2)Exchange元数据,交换机名称、类型和属性等。
(3)Binding元数据(存放路由关系的查找表)。
在集群模式下。
还包括cluster中node位置信息和node关系信息。元数据按照erlang node的类型确定是仅保存在RAM中,还是同时保存在RAM和disk上。元数据在cluster中是全node分布的。

1.18死信队列和死信交换器是什么说说?

如果一个消息成为死信后,如果这个消息所在的队列存在x-dead-letter-exchange参数,那么它就会被发送到x-dead-letter-exchange对应值的交换器上,这个交换器就称为死信交换器,与死信交换器绑定的队列就是死信队列。

1.19消息在什么时候会变成死信?

(1)消息被消费者拒绝,没有没有设置重新入队。
(2)消息过期
(3)消息堆积,达到队列的最大长度了,先入队的消息会变成死信。
在RabbitMQ之前的版本,是没有提供延迟队列的这个功能的。但是可以通过死信队列和TTL来完成延迟队列的功能,延迟队列有非常多的应用场景,比如:1.订单在十分钟之内没有支付就自动取消 2.会员续费的定时通知 3.优惠券过期提醒等等。
TTL是RabbitMQ中一个消息或者队列的属性,表明一条消息或者该队列的最大存活时间,如果超过这个时间就会被队列丢弃了。但如此一个队列存在x-dead-letter-exchange参数的话,在过期以后会被投递到死信交换器上。
使用死信队列+TTL实现延迟队列
(1)创建一个正常的队列,生产者在投递消息时,指定消息的过期时间,并且如果消息过期后指定需要投递的死信交换器和死信队列。
(2)创建死信交换器和死信队列,将死信队列和死信交换器绑定。
(3)消费者监听死信队列。

1.20如何队列满了以后该怎么处理消息?

临时写个程序,连接到mq里面的消费数据,消费一个扔掉一个,快速消费完所有的消息。等通过高峰期后再重新导入这批消息再去处理这批消息。

1.21如何保证消息队列的高可用?

RabbitMQ有三种模式:单机模式、镜像集群模式。
(1)单机模式:一般生产环境是不可能使用单机模式的。
(2)镜像集群模式:
RabbitMQ真正的高可用模式,集群中一般会包含一个主节点master和若干个从节点slave,如果master由于某种原因失效,那么按照slave加入的时间排序,资历最老的slave会被提升为新的master。
镜像队列下,所有的信息只会向master发送,再由master将命令的执行结果广播给slave,所以master和slave节点的状态是相同的。比如,每次写消息到queue时,master会自动将将消息同步到各个slave实例的queue;如果消费者与slave建立连接并进行订阅消费,其实质上也是从master获取消息,只不过看似是从slave上消费而已,slave向master发送情况,master准备好数据返回给slave,最后由slave投递给消费者。
队列的元数据和消息存在于多个实例上,也就是说RabbitMQ节点都有这个queue的完整镜像,任何一个机器宕机了,其他机器节点还包含了这个queue的完整数据,其他消费者都可以到其他节点上去消费数据。
缺点:
(1)性能开销大,消息需要同步到所有机器上,导致网络带宽压力和消费很重。

1.22说说Kafka与RabbitMQ的区别?

使用RabbitMQ作为消息队列的话,如果不使用消息持久化机制,消息就是存储在内存中,容易造成消息丢失。而使用Kafka的话,消息会存储到磁盘,并且Kafka更加高性能和高吞吐量,有更强的消息堆积能力,因为磁盘的存储空间可不是内存能够比得了的。
(1)顺序读写:Kafka使用了磁盘的顺序读写,保证了消息的堆积。顺序读写的话基本上不需要寻址时间,只需要少量的磁道旋转时间和纳秒级的数据传输时间。并且顺序读写的话,磁盘会预读,将读取起始地址连续读取多个页面。
(2)零拷贝技术:Kafka基于Linux系统的零拷贝技术,能够实现快速的数据拷贝以及网络传输工作。磁盘文件->内核缓冲区->网卡接口->消费者进程。
(3)分区分段+索引:Kafka的message消息实际上是分布式存储在一个一个小的segment中的,每次文件操作也是操作这个segment。为了进一步查询优化,Kafka又为分段后的数组文件建立了索引文件,不仅提升数据读取的效率,还提高数据操作的并行度。
(4)批量压缩:多条消息一起压缩,节省网络带宽。
(5)直接操作Page Cache而不是JVM:避免GC耗时和对象创建耗时,且读写速度更快,进程重启又缓存也不会丢失,除非实例宕机。